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;
}