THREE.js and Browser Graphics

CS 493 Lecture, Dr. Lawlor

Tons of really interesting 3D work is possible today inside a web browser.  I've just slapped together a very simple edit-and-run IDE named "PixAnvil" (try it out!).  You can also try the boundary condition version we built in class, with the "CLANG!" camera shake effect when a boulder hits a boundary plane. The "Setup" code fills the scene with objects, and "Animate" code runs every frame to move them around.  Because it's JavaScript, the "Run" button compiles the code and executes it right in your web browser, using your local graphics card.  Like NetRun, the keyboard shortcut alt-R (chrome) or shift-alt-R (firefox) should run it.

You do need WebGL support, so IE is right out! 

There are way too many libraries to choose from to build this sort of thing, but I'm using the deservedly popular THREE.js library for the 3D graphics, and jQuery UI for the tabs.  Despite extensive searching and frustrating experimentation, I haven't found a good way to let you resize the simulation region onscreen.

Sadly, the documentation for this stuff is quite sketchy.  For example, THREE.Vector3 is the basic 3D coordinate class used *everywhere* in the library.  You're supposed to read the source code, which is strikingly devoid of comments.

There's a good slideshow explaining the basics by Google's Ilmari Heikkinen (use the arrow keys to change slides).  There are some useful tutorials on a few topics at AeroTwist.  And there are a billion cool demos out there.

But there's no substitute for trying it yourself, so... try it yourself!

JavaScript

The modern way to run code in a browser is via JavaScript, which I recommend learning at CodeAcademy.

I don't know if it's actually helpful, but I wrote a little library that makes C++ sorta work like JavaScript.  Something conceptually like this probably forms the basis of your browser's JavaScript interpreter. 
/*
JavaScript-ish C++ code. Defines a "var" class and overloaded
operators to make C++ kinda-sorta work like JavaScript.

Dr. Orion Lawlor, lawlor@alaska.edu, 2013-01-23 (Public Domain)
*/
#include <iostream>
#include <map>
#include <string>
#include <stdio.h>

// Error function: runtime type mismatch (FIXME: should throw)
void typeError(const char *str) {
std::cerr<<"Type error: "<<str<<"\n";
}
// Runtime error
void bad(const char *str) {
std::cerr<<"Logic error: "<<str<<"\n";
}


using std::string;

class Object;

/**
The only type of variable in JavaScript is "var".
It's used for local variables, function arguments, return values, etc.
It can represent an object handle, a string, or a number.
*/
class var {
// Value of our object is one of these:
double num;
std::string *str;
Object *obj;
public:
enum {
Null_t,
Numeric_t,
String_t,
Object_t
} type;

var() :type(Null_t) {}
var(double n) :num(n), type(Numeric_t) {}
var(Object *o) :obj(o), type(Object_t) { if (o==0) type=Null_t; }
var(string s) :str(new string(s)), type(String_t) {}
var(const char* s) :str(new string(s)), type(String_t) {}
/* copy and assignment by default */

/* Convert our value to a string */
string toString() const {
if (type==Null_t) return "null";
if (type==Numeric_t) {
char buf[100];
snprintf(buf,100,"%f",num);
return buf;
}
if (type==String_t) return *str;
if (type==Object_t) return "[object]";
else bad("unknown type in toString");
}

friend string typeOf(var v) {
if (v.type==Null_t) return "null";
if (v.type==Numeric_t) return "number";
if (v.type==Object_t) return "object";
if (v.type==String_t) return "string";
else bad("unknown type in typeof");
}

friend var operator+(var a,var b) {
if (a.type==Numeric_t && b.type==Numeric_t)
return a.num+b.num; /* add as numbers */
else if (a.type==String_t || b.type==String_t)
{ /* add as strings */
return a.toString()+b.toString();
}
else /* ??? */
typeError("operator+ given non-numbers");
}
/* FIXME: arithmetic, comparison, etc operators */

var &operator[](string index);
/* FIXME: obj.field should be the same as obj["field"] */
};


/**
An Object is a kind of var that has other vars inside of it.
obj.field and obj["field"] and even obj[field] are all equivalent.
The fields are all vars themselves, possibly representing other objects.

An Array is a not-very-special kind of Object with a "length" field.
*/
class Object {
std::map<string,var> v;
public:
var &field_access(string index) {return v[index];}
};

var &var::operator[](string index)
{
if (type==Object_t) return obj->field_access(index);
else {
typeError("operator[] given non-object");
return *this;
}
}

/* This is how I fake "console.log(x);", the JavaScript way to print things. */
class Console {
public:
void log(var v) {
std::string s=v.toString();
std::cout<<s<<"\n";
}
};
Console console;

/*********** End of interpreter code **********
Start of example JavaScript-like code:
*/
var addit(var a) {
return 3+a;
}

var f(var o) {
return o["timestep"];
}

int main() { /* <- no main, you usually use a <script> tag in an HTML page. */
var a="foo";
console.log(addit(a));

var sim=new Object; // more idiomatic would be "sim={};"
sim["timestep"]=0.01; // or sim.timestep, which does the same thing.
console.log(f(sim));

return 0;
}