Created proper javascript class for the viewer, moved viewer to openjscad.js
This commit is contained in:
parent
69ed3b365d
commit
a5ee34852b
61
index.html
61
index.html
|
@ -3,7 +3,7 @@
|
||||||
<html><head>
|
<html><head>
|
||||||
<script src="lightgl.js"></script>
|
<script src="lightgl.js"></script>
|
||||||
<script src="csg.js"></script>
|
<script src="csg.js"></script>
|
||||||
<script src="viewer.js"></script>
|
<script src="openjscad.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -56,51 +56,44 @@ function isChrome()
|
||||||
|
|
||||||
function onload()
|
function onload()
|
||||||
{
|
{
|
||||||
gViewer = new Viewer(new CSG(), 600, 600, 50);
|
var containerelement = document.getElementById("viewer");
|
||||||
document.getElementById("viewer").appendChild(gViewer.gl.canvas);
|
gViewer = new OpenJsCad.Viewer(containerelement, 600, 600, 50);
|
||||||
if(isChrome())
|
updateSolid();
|
||||||
{
|
|
||||||
updateSolid();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSolid()
|
function updateSolid()
|
||||||
{
|
{
|
||||||
if(!isChrome())
|
if(gViewer.supported())
|
||||||
{
|
{
|
||||||
alert("Please note that this currently only works reliably in Google Chrome. You may see a corrupted solid, or nothing at all.");
|
var code=document.getElementById('code').value;
|
||||||
}
|
if(code != gSolidSource)
|
||||||
var code=document.getElementById('code').value;
|
{
|
||||||
if(code != gSolidSource)
|
gSolidSource=code;
|
||||||
{
|
var errtxt = "";
|
||||||
gSolidSource=code;
|
var csg = new CSG();
|
||||||
var errdiv=document.getElementById('error');
|
try {
|
||||||
try {
|
csg = OpenJsCad.javaScriptToSolid(code);
|
||||||
gSolid = new Function(code)();
|
} catch (e) {
|
||||||
if( (typeof(gSolid) != "object") || (!('polygons' in gSolid)))
|
errtxt = 'Error: <code>' + e.toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') + '</code>';
|
||||||
{
|
|
||||||
throw new Error("Your javascript code should return a CSG object. Try for example: return CSG.cube();");
|
|
||||||
}
|
}
|
||||||
errdiv.innerHTML = '';
|
gSolid = csg;
|
||||||
} catch (e) {
|
var errdiv=document.getElementById('error');
|
||||||
errdiv.innerHTML = 'Error: <code>' + e.toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') + '</code>';
|
errdiv.innerHTML = errtxt;
|
||||||
gSolid=new CSG();
|
errdiv.style.display = (errtxt == "")? "none":"block";
|
||||||
|
gViewer.setCsg(gSolid);
|
||||||
|
var stlarea = document.getElementById('stloutput');
|
||||||
|
stlarea.style.display = "none";
|
||||||
}
|
}
|
||||||
gViewer.mesh = gSolid.toMesh();
|
|
||||||
gViewer.gl.ondraw();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePreview()
|
|
||||||
{
|
|
||||||
updateSolid();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStl()
|
function getStl()
|
||||||
{
|
{
|
||||||
updateSolid();
|
updateSolid();
|
||||||
var stl=gSolid.toStlString();
|
var stl=gSolid.toStlString();
|
||||||
document.getElementById('stloutput').value=stl;
|
var stlarea = document.getElementById('stloutput');
|
||||||
|
stlarea.value=stl;
|
||||||
|
stlarea.style.display = "inline";
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -133,9 +126,9 @@ return result;
|
||||||
|
|
||||||
<p id="error"></p>
|
<p id="error"></p>
|
||||||
<br>
|
<br>
|
||||||
<input type="submit" value="Update Preview" onclick="updatePreview(); return false;"><br>
|
<input type="submit" value="Update Preview" onclick="updateSolid(); return false;">
|
||||||
<input type="submit" value="Get STL" onclick="getStl(); return false;"><br>
|
<input type="submit" value="Get STL" onclick="getStl(); return false;"><br>
|
||||||
<textarea id="stloutput" readonly style="width: 80%; height: 100px"></textarea>
|
<textarea id="stloutput" readonly style="width: 80%; height: 100px; display: none;"></textarea>
|
||||||
<br>
|
<br>
|
||||||
<h1>About</h1>
|
<h1>About</h1>
|
||||||
This is intended to become a Javascript based alternative to <a href="http://www.openscad.org/">OpenSCAD</a>,
|
This is intended to become a Javascript based alternative to <a href="http://www.openscad.org/">OpenSCAD</a>,
|
||||||
|
|
209
openjscad.js
Normal file
209
openjscad.js
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
OpenJsCad = function() {
|
||||||
|
};
|
||||||
|
|
||||||
|
// A viewer is a WebGL canvas that lets the user view a mesh. The user can
|
||||||
|
// tumble it around by dragging the mouse.
|
||||||
|
OpenJsCad.Viewer = function(containerelement, width, height, initialdepth) {
|
||||||
|
var gl = GL.create();
|
||||||
|
if(!gl)
|
||||||
|
{
|
||||||
|
containerelement.innerHTML = "WebGL is required for the 3D viewer, but your browser doesn't seem to support this.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.gl = gl;
|
||||||
|
this.angleX = 0;
|
||||||
|
this.angleY = 0;
|
||||||
|
this.viewpointX = 0;
|
||||||
|
this.viewpointY = 0;
|
||||||
|
this.viewpointZ = initialdepth;
|
||||||
|
|
||||||
|
// Draw triangle lines:
|
||||||
|
this.drawLines = false;
|
||||||
|
// Set to true so lines don't use the depth buffer
|
||||||
|
this.lineOverlay = false;
|
||||||
|
|
||||||
|
// Set up the viewport
|
||||||
|
gl.canvas.width = width;
|
||||||
|
gl.canvas.height = height;
|
||||||
|
gl.viewport(0, 0, width, height);
|
||||||
|
gl.matrixMode(gl.PROJECTION);
|
||||||
|
gl.loadIdentity();
|
||||||
|
gl.perspective(45, width / height, 0.5, 1000);
|
||||||
|
gl.matrixMode(gl.MODELVIEW);
|
||||||
|
|
||||||
|
// Set up WebGL state
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
gl.clearColor(0.93, 0.93, 0.93, 1);
|
||||||
|
gl.enable(gl.DEPTH_TEST);
|
||||||
|
gl.enable(gl.CULL_FACE);
|
||||||
|
gl.polygonOffset(1, 1);
|
||||||
|
|
||||||
|
// Black shader for wireframe
|
||||||
|
this.blackShader = new GL.Shader('\
|
||||||
|
void main() {\
|
||||||
|
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
|
||||||
|
}\
|
||||||
|
', '\
|
||||||
|
void main() {\
|
||||||
|
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);\
|
||||||
|
}\
|
||||||
|
');
|
||||||
|
|
||||||
|
// Shader with diffuse and specular lighting
|
||||||
|
this.lightingShader = new GL.Shader('\
|
||||||
|
varying vec3 color;\
|
||||||
|
varying vec3 normal;\
|
||||||
|
varying vec3 light;\
|
||||||
|
void main() {\
|
||||||
|
const vec3 lightDir = vec3(1.0, 2.0, 3.0) / 3.741657386773941;\
|
||||||
|
light = lightDir;\
|
||||||
|
color = gl_Color.rgb;\
|
||||||
|
normal = gl_NormalMatrix * gl_Normal;\
|
||||||
|
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
|
||||||
|
}\
|
||||||
|
', '\
|
||||||
|
varying vec3 color;\
|
||||||
|
varying vec3 normal;\
|
||||||
|
varying vec3 light;\
|
||||||
|
void main() {\
|
||||||
|
vec3 n = normalize(normal);\
|
||||||
|
float diffuse = max(0.0, dot(light, n));\
|
||||||
|
float specular = pow(max(0.0, -reflect(light, n).z), 10.0) * sqrt(diffuse);\
|
||||||
|
gl_FragColor = vec4(mix(color * (0.3 + 0.7 * diffuse), vec3(1.0), specular), 1.0);\
|
||||||
|
}\
|
||||||
|
');
|
||||||
|
|
||||||
|
containerelement.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
var _this=this;
|
||||||
|
|
||||||
|
gl.onmousemove = function(e) {
|
||||||
|
_this.onMouseMove(e);
|
||||||
|
};
|
||||||
|
gl.ondraw = function() {
|
||||||
|
_this.onDraw();
|
||||||
|
};
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
OpenJsCad.Viewer.prototype = {
|
||||||
|
setCsg: function(csg) {
|
||||||
|
this.mesh = OpenJsCad.Viewer.csgToMesh(csg);
|
||||||
|
this.onDraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: function() {
|
||||||
|
// empty mesh:
|
||||||
|
this.mesh = new GL.Mesh();
|
||||||
|
this.onDraw();
|
||||||
|
},
|
||||||
|
|
||||||
|
supported: function() {
|
||||||
|
return !!this.gl;
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseMove: function(e) {
|
||||||
|
if (e.dragging) {
|
||||||
|
e.preventDefault();
|
||||||
|
if(e.altKey)
|
||||||
|
{
|
||||||
|
var factor = 1e-2;
|
||||||
|
this.viewpointZ *= Math.pow(2,factor * e.deltaY);
|
||||||
|
}
|
||||||
|
else if(e.shiftKey)
|
||||||
|
{
|
||||||
|
var factor = 5e-3;
|
||||||
|
this.viewpointX += factor * e.deltaX * this.viewpointZ;
|
||||||
|
this.viewpointY -= factor * e.deltaY * this.viewpointZ;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.angleY += e.deltaX * 2;
|
||||||
|
this.angleX += e.deltaY * 2;
|
||||||
|
this.angleX = Math.max(-90, Math.min(90, this.angleX));
|
||||||
|
}
|
||||||
|
this.onDraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDraw: function(e) {
|
||||||
|
var gl = this.gl;
|
||||||
|
gl.makeCurrent();
|
||||||
|
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
gl.loadIdentity();
|
||||||
|
gl.translate(this.viewpointX, this.viewpointY, -this.viewpointZ);
|
||||||
|
gl.rotate(this.angleX, 1, 0, 0);
|
||||||
|
gl.rotate(this.angleY, 0, 1, 0);
|
||||||
|
|
||||||
|
if (!this.lineOverlay) gl.enable(gl.POLYGON_OFFSET_FILL);
|
||||||
|
this.lightingShader.draw(this.mesh, gl.TRIANGLES);
|
||||||
|
if (!this.lineOverlay) gl.disable(gl.POLYGON_OFFSET_FILL);
|
||||||
|
|
||||||
|
if(this.drawLines)
|
||||||
|
{
|
||||||
|
if (this.lineOverlay) gl.disable(gl.DEPTH_TEST);
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
this.blackShader.draw(this.mesh, gl.LINES);
|
||||||
|
gl.disable(gl.BLEND);
|
||||||
|
if (this.lineOverlay) gl.enable(gl.DEPTH_TEST);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from CSG solid to GL.Mesh object
|
||||||
|
OpenJsCad.Viewer.csgToMesh = function(csg) {
|
||||||
|
var csg = csg.canonicalized();
|
||||||
|
var mesh = new GL.Mesh({ normals: true, colors: true });
|
||||||
|
var vertexTag2Index = {};
|
||||||
|
var vertices = [];
|
||||||
|
var colors = [];
|
||||||
|
var triangles = [];
|
||||||
|
// set to true if we want to use interpolated vertex normals
|
||||||
|
// this creates nice round spheres but does not represent the shape of
|
||||||
|
// the actual model
|
||||||
|
var smoothlighting = false;
|
||||||
|
var polygons = csg.toPolygons();
|
||||||
|
var numpolygons = polygons.length;
|
||||||
|
for(var polygonindex = 0; polygonindex < numpolygons; polygonindex++)
|
||||||
|
{
|
||||||
|
var polygon = polygons[polygonindex];
|
||||||
|
var indices = polygon.vertices.map(function(vertex) {
|
||||||
|
var vertextag = vertex.getTag();
|
||||||
|
var vertexindex;
|
||||||
|
if(smoothlighting && (vertextag in vertexTag2Index))
|
||||||
|
{
|
||||||
|
vertexindex = vertexTag2Index[vertextag];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vertexindex = vertices.length;
|
||||||
|
vertexTag2Index[vertextag] = vertexindex;
|
||||||
|
vertices.push([vertex.pos.x, vertex.pos.y, vertex.pos.z]);
|
||||||
|
colors.push([0,0,1]);
|
||||||
|
}
|
||||||
|
return vertexindex;
|
||||||
|
});
|
||||||
|
for (var i = 2; i < indices.length; i++) {
|
||||||
|
triangles.push([indices[0], indices[i - 1], indices[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mesh.triangles = triangles;
|
||||||
|
mesh.vertices = vertices;
|
||||||
|
mesh.colors = colors;
|
||||||
|
mesh.computeWireframe();
|
||||||
|
mesh.computeNormals();
|
||||||
|
return mesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
// parse javascript into solid:
|
||||||
|
OpenJsCad.javaScriptToSolid = function(script) {
|
||||||
|
var csg = new Function(script)();
|
||||||
|
if( (typeof(csg) != "object") || (!('polygons' in csg)))
|
||||||
|
{
|
||||||
|
throw new Error("Your javascript code should return a CSG object. Try for example: return CSG.cube();");
|
||||||
|
}
|
||||||
|
return csg;
|
||||||
|
};
|
192
viewer.js
192
viewer.js
|
@ -1,192 +0,0 @@
|
||||||
// Draw triangle lines:
|
|
||||||
Viewer.drawLines = false;
|
|
||||||
// Set to true so lines don't use the depth buffer
|
|
||||||
Viewer.lineOverlay = false;
|
|
||||||
|
|
||||||
// Set the color of all polygons in this solid
|
|
||||||
CSG.prototype.setColor = function(r, g, b) {
|
|
||||||
this.toPolygons().map(function(polygon) {
|
|
||||||
polygon.shared = [r, g, b];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert from CSG solid to GL.Mesh object
|
|
||||||
CSG.prototype.toMesh = function() {
|
|
||||||
var csg = this.canonicalized();
|
|
||||||
var mesh = new GL.Mesh({ normals: true, colors: true });
|
|
||||||
var vertexTag2Index = {};
|
|
||||||
var vertices = [];
|
|
||||||
var colors = [];
|
|
||||||
var triangles = [];
|
|
||||||
// set to true if we want to use interpolated vertex normals
|
|
||||||
// this creates nice round spheres but does not represent the shape of
|
|
||||||
// the actual model
|
|
||||||
var smoothlighting = false;
|
|
||||||
var polygons = csg.toPolygons();
|
|
||||||
var numpolygons = polygons.length;
|
|
||||||
for(var polygonindex = 0; polygonindex < numpolygons; polygonindex++)
|
|
||||||
{
|
|
||||||
var polygon = polygons[polygonindex];
|
|
||||||
var indices = polygon.vertices.map(function(vertex) {
|
|
||||||
var vertextag = vertex.getTag();
|
|
||||||
var vertexindex;
|
|
||||||
if(smoothlighting && (vertextag in vertexTag2Index))
|
|
||||||
{
|
|
||||||
vertexindex = vertexTag2Index[vertextag];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
vertexindex = vertices.length;
|
|
||||||
vertexTag2Index[vertextag] = vertexindex;
|
|
||||||
vertices.push([vertex.pos.x, vertex.pos.y, vertex.pos.z]);
|
|
||||||
colors.push([0,0,1]);
|
|
||||||
}
|
|
||||||
return vertexindex;
|
|
||||||
});
|
|
||||||
for (var i = 2; i < indices.length; i++) {
|
|
||||||
triangles.push([indices[0], indices[i - 1], indices[i]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mesh.triangles = triangles;
|
|
||||||
mesh.vertices = vertices;
|
|
||||||
mesh.colors = colors;
|
|
||||||
mesh.computeWireframe();
|
|
||||||
mesh.computeNormals();
|
|
||||||
return mesh;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var viewers = [];
|
|
||||||
|
|
||||||
|
|
||||||
// A viewer is a WebGL canvas that lets the user view a mesh. The user can
|
|
||||||
// tumble it around by dragging the mouse.
|
|
||||||
function Viewer(csg, width, height, depth) {
|
|
||||||
|
|
||||||
var angleX = 0;
|
|
||||||
var angleY = 0;
|
|
||||||
var viewpointX = 0;
|
|
||||||
var viewpointY = 0;
|
|
||||||
|
|
||||||
viewers.push(this);
|
|
||||||
|
|
||||||
// Get a new WebGL canvas
|
|
||||||
var gl = GL.create();
|
|
||||||
this.gl = gl;
|
|
||||||
this.mesh = csg.toMesh();
|
|
||||||
|
|
||||||
// Set up the viewport
|
|
||||||
gl.canvas.width = width;
|
|
||||||
gl.canvas.height = height;
|
|
||||||
gl.viewport(0, 0, width, height);
|
|
||||||
gl.matrixMode(gl.PROJECTION);
|
|
||||||
gl.loadIdentity();
|
|
||||||
gl.perspective(45, width / height, 0.5, 1000);
|
|
||||||
gl.matrixMode(gl.MODELVIEW);
|
|
||||||
|
|
||||||
// Set up WebGL state
|
|
||||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
||||||
gl.clearColor(0.93, 0.93, 0.93, 1);
|
|
||||||
gl.enable(gl.DEPTH_TEST);
|
|
||||||
gl.enable(gl.CULL_FACE);
|
|
||||||
gl.polygonOffset(1, 1);
|
|
||||||
|
|
||||||
// Black shader for wireframe
|
|
||||||
this.blackShader = new GL.Shader('\
|
|
||||||
void main() {\
|
|
||||||
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
|
|
||||||
}\
|
|
||||||
', '\
|
|
||||||
void main() {\
|
|
||||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);\
|
|
||||||
}\
|
|
||||||
');
|
|
||||||
|
|
||||||
// Shader with diffuse and specular lighting
|
|
||||||
this.lightingShader = new GL.Shader('\
|
|
||||||
varying vec3 color;\
|
|
||||||
varying vec3 normal;\
|
|
||||||
varying vec3 light;\
|
|
||||||
void main() {\
|
|
||||||
const vec3 lightDir = vec3(1.0, 2.0, 3.0) / 3.741657386773941;\
|
|
||||||
light = lightDir;\
|
|
||||||
color = gl_Color.rgb;\
|
|
||||||
normal = gl_NormalMatrix * gl_Normal;\
|
|
||||||
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
|
|
||||||
}\
|
|
||||||
', '\
|
|
||||||
varying vec3 color;\
|
|
||||||
varying vec3 normal;\
|
|
||||||
varying vec3 light;\
|
|
||||||
void main() {\
|
|
||||||
vec3 n = normalize(normal);\
|
|
||||||
float diffuse = max(0.0, dot(light, n));\
|
|
||||||
float specular = pow(max(0.0, -reflect(light, n).z), 10.0) * sqrt(diffuse);\
|
|
||||||
gl_FragColor = vec4(mix(color * (0.3 + 0.7 * diffuse), vec3(1.0), specular), 1.0);\
|
|
||||||
}\
|
|
||||||
');
|
|
||||||
|
|
||||||
var _this=this;
|
|
||||||
gl.onmousemove = function(e) {
|
|
||||||
if (e.dragging) {
|
|
||||||
e.preventDefault();
|
|
||||||
if(e.altKey)
|
|
||||||
{
|
|
||||||
var factor = 1e-2;
|
|
||||||
depth *= Math.pow(2,factor * e.deltaY);
|
|
||||||
}
|
|
||||||
else if(e.shiftKey)
|
|
||||||
{
|
|
||||||
var factor = 5e-3;
|
|
||||||
viewpointX += factor * e.deltaX * depth;
|
|
||||||
viewpointY -= factor * e.deltaY * depth;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
angleY += e.deltaX * 2;
|
|
||||||
angleX += e.deltaY * 2;
|
|
||||||
angleX = Math.max(-90, Math.min(90, angleX));
|
|
||||||
}
|
|
||||||
|
|
||||||
_this.gl.ondraw();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
gl.onmousewheel = function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var delta = e.wheelDelta();
|
|
||||||
var factor = 1e-5;
|
|
||||||
depth *= Math.pow(factor * delta);
|
|
||||||
};
|
|
||||||
|
|
||||||
var that = this;
|
|
||||||
gl.ondraw = function() {
|
|
||||||
gl.makeCurrent();
|
|
||||||
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
||||||
gl.loadIdentity();
|
|
||||||
gl.translate(viewpointX, viewpointY, -depth);
|
|
||||||
gl.rotate(angleX, 1, 0, 0);
|
|
||||||
gl.rotate(angleY, 0, 1, 0);
|
|
||||||
|
|
||||||
if (!Viewer.lineOverlay) gl.enable(gl.POLYGON_OFFSET_FILL);
|
|
||||||
that.lightingShader.draw(that.mesh, gl.TRIANGLES);
|
|
||||||
if (!Viewer.lineOverlay) gl.disable(gl.POLYGON_OFFSET_FILL);
|
|
||||||
|
|
||||||
if(Viewer.drawLines)
|
|
||||||
{
|
|
||||||
if (Viewer.lineOverlay) gl.disable(gl.DEPTH_TEST);
|
|
||||||
gl.enable(gl.BLEND);
|
|
||||||
that.blackShader.draw(that.mesh, gl.LINES);
|
|
||||||
gl.disable(gl.BLEND);
|
|
||||||
if (Viewer.lineOverlay) gl.enable(gl.DEPTH_TEST);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
gl.ondraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
var nextID = 0;
|
|
||||||
function addViewer(viewer) {
|
|
||||||
document.getElementById(nextID++).appendChild(viewer.gl.canvas);
|
|
||||||
}
|
|
Loading…
Reference in a new issue