Three.js: various control improvements.

Specifically:
  * touchscreen devices are now supported;
  * rotation is now more like what SolveSpace itself does.

The code is split in two parts because MSVC can't handle string
literals longer than 16Ki.
This commit is contained in:
whitequark 2016-05-06 17:24:18 +00:00
parent e1f614101f
commit affc88f342

View File

@ -843,197 +843,501 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename,
STriangle *tr;
SEdge *e;
Vector bndl, bndh;
const char htmlbegin[] =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
" <head>\n"
" <meta charset=\"utf-8\"></meta>\n"
" <title>Three.js Solvespace Mesh</title>\n"
" <script src=\"http://threejs.org/build/three.min.js\"></script>\n"
" <script src=\"http://threejs.org/examples/js/controls/OrthographicTrackballControls.js\"></script>\n"
" <style type=\"text/css\">\n"
" body { margin: 0; overflow: hidden; }\n"
" </style>\n"
" </head>\n"
" <body>\n"
" <script>\n"
" window.solvespace = function(obj, params) {\n"
" var scene, edgeScene, camera, edgeCamera, renderer;\n"
" var geometry, controls, material, mesh, edges;\n"
" var width, height, edgeBias;\n"
" var directionalLightArray = [];\n"
"\n"
" if(typeof params === \"undefined\" || !(\"width\" in params)) {\n"
" width = window.innerWidth;\n"
" } else {\n"
" width = params.width;\n"
" }\n"
"\n"
" if(typeof params === \"undefined\" || !(\"height\" in params)) {\n"
" height = window.innerHeight;\n"
" } else {\n"
" height = params.height;\n"
" }\n"
" edgeBias = obj.bounds.edgeBias;\n"
"\n"
" domElement = init();\n"
" render();\n"
" return domElement;\n"
"\n"
" function init() {\n"
" scene = new THREE.Scene();\n"
" edgeScene = new THREE.Scene();\n"
"\n"
" var ratio = (width/height);\n"
" camera = new THREE.OrthographicCamera(-obj.bounds.x * ratio,\n"
" obj.bounds.x * ratio, obj.bounds.y, -obj.bounds.y, obj.bounds.near,\n"
" obj.bounds.far*10);\n"
" camera.position.z = obj.bounds.z*3;\n"
"\n"
" mesh = createMesh(obj);\n"
" scene.add(mesh);\n"
" edges = createEdges(obj);\n"
" edgeScene.add(edges);\n"
"\n"
" for(var i = 0; i < obj.lights.d.length; i++) {\n"
" var lightColor = new THREE.Color(obj.lights.d[i].intensity,\n"
" obj.lights.d[i].intensity, obj.lights.d[i].intensity);\n"
" var directionalLight = new THREE.DirectionalLight(lightColor, 1);\n"
" directionalLight.position.set(obj.lights.d[i].direction[0],\n"
" obj.lights.d[i].direction[1], obj.lights.d[i].direction[2]);\n"
" directionalLightArray.push(directionalLight);\n"
" scene.add(directionalLight);\n"
" }\n"
"\n"
" var lightColor = new THREE.Color(obj.lights.a, obj.lights.a, obj.lights.a);\n"
" var ambientLight = new THREE.AmbientLight(lightColor.getHex());\n"
" scene.add(ambientLight);\n"
"\n"
" renderer = new THREE.WebGLRenderer();\n"
" renderer.setPixelRatio(window.devicePixelRatio);\n"
" renderer.setSize(width, height);\n"
" renderer.autoClear = false;\n"
"\n"
" controls = new THREE.OrthographicTrackballControls(camera, renderer.domElement);\n"
" controls.screen.width = width;\n"
" controls.screen.height = height;\n"
" controls.radius = (width + height)/4;\n"
" controls.rotateSpeed = 2.0;\n"
" controls.zoomSpeed = 2.0;\n"
" controls.panSpeed = 1.0;\n"
" controls.staticMoving = true;\n"
" controls.addEventListener(\"change\", render);\n"
" controls.addEventListener(\"change\", lightUpdate);\n"
" controls.addEventListener(\"change\", setControlsCenter);\n"
"\n"
" animate();\n"
" return renderer.domElement;\n"
" }\n"
"\n"
" function animate() {\n"
" requestAnimationFrame(animate);\n"
" controls.update();\n"
" }\n"
"\n"
" function render() {\n"
" renderer.clear();\n"
" renderer.render(scene, camera);\n"
" var oldFar = camera.far\n"
" camera.far = camera.far + edgeBias;\n"
" camera.updateProjectionMatrix();\n"
" renderer.render(edgeScene, camera);\n"
" camera.far = oldFar;\n"
" camera.updateProjectionMatrix();\n"
" }\n"
"\n"
" function lightUpdate() {\n"
" var projRight = new THREE.Vector3();\n"
" var projZ = new THREE.Vector3();\n"
" var changeBasis = new THREE.Matrix3();\n"
"\n"
" // The original light positions were in camera space.\n"
" // Project them into standard space using camera's basis\n"
" // vectors (up, target, and their cross product).\n"
" projRight.copy(camera.up);\n"
" projZ.copy(camera.position).sub(controls.target).normalize();\n"
" projRight.cross(projZ).normalize();\n"
" changeBasis.set(projRight.x, camera.up.x, controls.target.x,\n"
" projRight.y, camera.up.y, controls.target.y,\n"
" projRight.z, camera.up.z, controls.target.z);\n"
"\n"
" for(var i = 0; i < obj.lights.d.length; i++) {\n"
" var newLightPos = changeBasis.applyToVector3Array(\n"
" [obj.lights.d[i].direction[0], obj.lights.d[i].direction[1],\n"
" obj.lights.d[i].direction[2]]);\n"
" directionalLightArray[i].position.set(newLightPos[0],\n"
" newLightPos[1], newLightPos[2]);\n"
" }\n"
" }\n"
"\n"
" function setControlsCenter() {\n"
" var rect = renderer.domElement.getBoundingClientRect()\n"
" controls.screen.left = rect.left + document.body.scrollLeft;\n"
" controls.screen.top = rect.top + document.body.scrollTop;\n"
" }\n"
"\n"
" function createMesh(mesh_obj) {\n"
" var geometry = new THREE.Geometry();\n"
" var materialIndex = 0, materialList = [];\n"
" var opacitiesSeen = {};\n"
"\n"
" for(var i = 0; i < mesh_obj.points.length; i++) {\n"
" geometry.vertices.push(new THREE.Vector3(mesh_obj.points[i][0],\n"
" mesh_obj.points[i][1], mesh_obj.points[i][2]));\n"
" }\n"
"\n"
" for(var i = 0; i < mesh_obj.faces.length; i++) {\n"
" var currOpacity = ((mesh_obj.colors[i] & 0xFF000000) >>> 24)/255.0;\n"
" if(opacitiesSeen[currOpacity] === undefined) {\n"
" opacitiesSeen[currOpacity] = materialIndex;\n"
" materialIndex++;\n"
" materialList.push(new THREE.MeshLambertMaterial(\n"
" {vertexColors: THREE.FaceColors, opacity: currOpacity,\n"
" transparent: true}));\n"
" }\n"
"\n"
" geometry.faces.push(new THREE.Face3(mesh_obj.faces[i][0],\n"
" mesh_obj.faces[i][1], mesh_obj.faces[i][2],\n"
" [new THREE.Vector3(mesh_obj.normals[i][0][0],\n"
" mesh_obj.normals[i][0][1], mesh_obj.normals[i][0][2]),\n"
" new THREE.Vector3(mesh_obj.normals[i][1][0],\n"
" mesh_obj.normals[i][1][1], mesh_obj.normals[i][1][2]),\n"
" new THREE.Vector3(mesh_obj.normals[i][2][0],\n"
" mesh_obj.normals[i][2][1], mesh_obj.normals[i][2][2])],\n"
" new THREE.Color(mesh_obj.colors[i] & 0x00FFFFFF),\n"
" opacitiesSeen[currOpacity]));\n"
" }\n"
"\n"
" geometry.computeBoundingSphere();\n"
" return new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materialList));\n"
" }\n"
"\n"
" function createEdges(mesh_obj) {\n"
" var geometry = new THREE.Geometry();\n"
" var material = new THREE.LineBasicMaterial();\n"
"\n"
" for(var i = 0; i < mesh_obj.edges.length; i++) {\n"
" geometry.vertices.push(new THREE.Vector3(mesh_obj.edges[i][0][0],\n"
" mesh_obj.edges[i][0][1], mesh_obj.edges[i][0][2]),\n"
" new THREE.Vector3(mesh_obj.edges[i][1][0],\n"
" mesh_obj.edges[i][1][1], mesh_obj.edges[i][1][2]));\n"
" }\n"
"\n"
" return new THREE.Line(geometry, material, THREE.LinePieces);\n"
" }\n"
" };\n"
"\n"
" </script>\n"
" <script>\n";
const char htmlend[] =
" document.body.appendChild(solvespace(solvespace_model_%s));\n"
" </script>\n"
" </body>\n"
"</html>\n";
const char htmlbegin0[] = R"(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"></meta>
<title>Three.js Solvespace Mesh</title>
<script src="http://threejs.org/build/three.js"></script>
<script src="http://hammerjs.github.io/dist/hammer.js"></script>
<style type="text/css">
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<script>
</script>
<script>
SolvespaceCamera = function(renderWidth, renderHeight, scale, up, right, offset) {
THREE.Camera.call(this);
this.type = 'SolvespaceCamera';
this.renderWidth = renderWidth;
this.renderHeight = renderHeight;
this.zoomScale = scale; /* Avoid namespace collision w/ THREE.Object.scale */
this.up = up;
this.right = right;
this.offset = offset;
this.depthBias = 0;
this.updateProjectionMatrix();
};
SolvespaceCamera.prototype = Object.create(THREE.Camera.prototype);
SolvespaceCamera.prototype.constructor = SolvespaceCamera;
SolvespaceCamera.prototype.updateProjectionMatrix = function() {
var temp = new THREE.Matrix4();
var offset = new THREE.Matrix4().makeTranslation(this.offset.x, this.offset.y, this.offset.z);
// Convert to right handed- do up cross right instead.
var n = new THREE.Vector3().crossVectors(this.up, this.right);
var rotate = new THREE.Matrix4().makeBasis(this.right, this.up, n);
rotate.transpose();
/* FIXME: At some point we ended up using row-major.
THREE.js wants column major. Scale/depth correct unaffected b/c diagonal
matrices remain the same when transposed. makeTranslation also makes
a column-major matrix. */
/* TODO: If we want perspective, we need an additional matrix
here which will modify w for perspective divide. */
var scale = new THREE.Matrix4().makeScale(2 * this.zoomScale / this.renderWidth,
2 * this.zoomScale / this.renderHeight, this.zoomScale / 30000.0);
temp.multiply(scale);
temp.multiply(rotate);
temp.multiply(offset);
this.projectionMatrix.copy(temp);
};
SolvespaceCamera.prototype.NormalizeProjectionVectors = function() {
/* After rotating, up and right may no longer be orthogonal.
However, their cross product will produce the correct
rotated plane, and we can recover an orthogonal basis. */
var n = new THREE.Vector3().crossVectors(this.right, this.up);
this.up = new THREE.Vector3().crossVectors(n, this.right);
this.right.normalize();
this.up.normalize();
};
SolvespaceCamera.prototype.rotate = function(right, up) {
var oldRight = new THREE.Vector3().copy(this.right).normalize();
var oldUp = new THREE.Vector3().copy(this.up).normalize();
this.up.applyAxisAngle(oldRight, up);
this.right.applyAxisAngle(oldUp, right);
this.NormalizeProjectionVectors();
}
SolvespaceCamera.prototype.offsetProj = function(right, up) {
var shift = new THREE.Vector3(right * this.right.x + up * this.up.x,
right * this.right.y + up * this.up.y,
right * this.right.z + up * this.up.z);
this.offset.add(shift);
}
/* Calculate the offset in terms of up and right projection vectors
that will preserve the world coordinates of the current mouse position after
the zoom. */
SolvespaceCamera.prototype.zoomTo = function(x, y, delta) {
// Get offset components in world coordinates, in terms of up/right.
var projOffsetX = this.offset.dot(this.right);
var projOffsetY = this.offset.dot(this.up);
/* Remove offset before scaling so, that mouse position changes
proportionally to the model and independent of current offset. */
var centerRightI = x/this.zoomScale - projOffsetX;
var centerUpI = y/this.zoomScale - projOffsetY;
var zoomFactor;
/* Zoom 20% every 100 delta. */
if(delta < 0) {
zoomFactor = (-delta * 0.002 + 1);
}
else if(delta > 0) {
zoomFactor = (delta * (-1.0/600.0) + 1)
}
else {
return;
}
this.zoomScale = this.zoomScale * zoomFactor;
var centerRightF = x/this.zoomScale - projOffsetX;
var centerUpF = y/this.zoomScale - projOffsetY;
this.offset.addScaledVector(this.right, centerRightF - centerRightI);
this.offset.addScaledVector(this.up, centerUpF - centerUpI);
}
SolvespaceControls = function(object, domElement) {
var _this = this;
this.object = object;
this.domElement = ( domElement !== undefined ) ? domElement : document;
var threePan = new Hammer.Pan({event : 'threepan', pointers : 3, enable : false});
var panAfterTap = new Hammer.Pan({event : 'panaftertap', enable : false});
this.touchControls = new Hammer.Manager(domElement, {
recognizers: [
[Hammer.Pinch, { enable: true }],
[Hammer.Pan],
[Hammer.Tap],
]
});
this.touchControls.add(threePan);
this.touchControls.add(panAfterTap);
var changeEvent = {
type: 'change'
};
var startEvent = {
type: 'start'
};
var endEvent = {
type: 'end'
};
var _changed = false;
var _mouseMoved = false;
//var _touchPoints = new Array();
var _offsetPrev = new THREE.Vector2(0, 0);
var _offsetCur = new THREE.Vector2(0, 0);
var _rotatePrev = new THREE.Vector2(0, 0);
var _rotateCur = new THREE.Vector2(0, 0);
// Used during touch events.
var _rotateOrig = new THREE.Vector2(0, 0);
var _offsetOrig = new THREE.Vector2(0, 0);
var _prevScale = 1.0;
this.handleEvent = function(event) {
if (typeof this[event.type] == 'function') {
this[event.type](event);
}
}
function mousedown(event) {
event.preventDefault();
event.stopPropagation();
switch (event.button) {
case 0:
_rotateCur.set(event.screenX, event.screenY);
_rotatePrev.copy(_rotateCur);
document.addEventListener('mousemove', mousemove, false);
document.addEventListener('mouseup', mouseup, false);
break;
case 2:
_offsetCur.set(event.screenX, event.screenY);
_offsetPrev.copy(_offsetCur);
document.addEventListener('mousemove', mousemove, false);
document.addEventListener('mouseup', mouseup, false);
break;
default:
break;
}
}
function wheel( event ) {
event.preventDefault();
/* FIXME: Width and height might not be supported universally, but
can be calculated? */
var box = _this.domElement.getBoundingClientRect();
object.zoomTo(event.clientX - box.width/2 - box.left,
-(event.clientY - box.height/2 - box.top), event.deltaY);
_changed = true;
}
function mousemove(event) {
switch (event.button) {
case 0:
_rotateCur.set(event.screenX, event.screenY);
var diff = new THREE.Vector2().subVectors(_rotateCur, _rotatePrev)
.multiplyScalar(1 / object.zoomScale);
object.rotate(-0.3 * Math.PI / 180 * diff.x * object.zoomScale,
-0.3 * Math.PI / 180 * diff.y * object.zoomScale);
_changed = true;
_rotatePrev.copy(_rotateCur);
break;
case 2:
_mouseMoved = true;
_offsetCur.set(event.screenX, event.screenY);
var diff = new THREE.Vector2().subVectors(_offsetCur, _offsetPrev)
.multiplyScalar(1 / object.zoomScale);
object.offsetProj(diff.x, -diff.y);
_changed = true;
_offsetPrev.copy(_offsetCur)
break;
}
}
function mouseup(event) {
/* TODO: Opera mouse gestures will intercept this event, making it
possible to have multiple mousedown events consecutively without
a corresponding mouseup (so multiple viewports can be rotated/panned
simultaneously). Disable mouse gestures for now. */
event.preventDefault();
event.stopPropagation();
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
_this.dispatchEvent(endEvent);
}
function pan(event) {
/* neWcur - prev does not necessarily equal (cur + diff) - prev.
Floating point is not associative. */
touchDiff = new THREE.Vector2(event.deltaX, event.deltaY);
_rotateCur.addVectors(_rotateOrig, touchDiff);
incDiff = new THREE.Vector2().subVectors(_rotateCur, _rotatePrev)
.multiplyScalar(1 / object.zoomScale);
object.rotate(-0.3 * Math.PI / 180 * incDiff.x * object.zoomScale,
-0.3 * Math.PI / 180 * incDiff.y * object.zoomScale);
_changed = true;
_rotatePrev.copy(_rotateCur);
}
function panstart(event) {
/* TODO: Dynamically enable pan function? */
_rotateOrig.copy(_rotateCur);
}
function pinchstart(event) {
_prevScale = event.scale;
}
function pinch(event) {
/* FIXME: Width and height might not be supported universally, but
can be calculated? */
var box = _this.domElement.getBoundingClientRect();
/* 16.6... pixels chosen heuristically... matches my touchpad. */
if (event.scale < _prevScale) {
object.zoomTo(event.center.x - box.width/2 - box.left,
-(event.center.y - box.height/2 - box.top), 100/6.0);
_changed = true;
} else if (event.scale > _prevScale) {
object.zoomTo(event.center.x - box.width/2 - box.left,
-(event.center.y - box.height/2 - box.top), -100/6.0);
_changed = true;
}
_prevScale = event.scale;
}
/* A tap will enable panning/disable rotate. */
function tap(event) {
panAfterTap.set({enable : true});
_this.touchControls.get('pan').set({enable : false});
}
function panaftertap(event) {
touchDiff = new THREE.Vector2(event.deltaX, event.deltaY);
_offsetCur.addVectors(_offsetOrig, touchDiff);
incDiff = new THREE.Vector2().subVectors(_offsetCur, _offsetPrev)
.multiplyScalar(1 / object.zoomScale);
object.offsetProj(incDiff.x, -incDiff.y);
_changed = true;
_offsetPrev.copy(_offsetCur);
}
function panaftertapstart(event) {
_offsetOrig.copy(_offsetCur);
}
function panaftertapend(event) {
panAfterTap.set({enable : false});
_this.touchControls.get('pan').set({enable : true});
}
function contextmenu(event) {
event.preventDefault();
}
this.update = function() {
if (_changed) {
_this.dispatchEvent(changeEvent);
_changed = false;
}
}
this.domElement.addEventListener('mousedown', mousedown, false);
this.domElement.addEventListener('wheel', wheel, false);
this.domElement.addEventListener('contextmenu', contextmenu, false);
/* Hammer.on wraps addEventListener */
// Rotate
this.touchControls.on('pan', pan);
this.touchControls.on('panstart', panstart);
// Zoom
this.touchControls.on('pinch', pinch);
this.touchControls.on('pinchstart', pinchstart);
//Pan
this.touchControls.on('tap', tap);
this.touchControls.on('panaftertapstart', panaftertapstart);
this.touchControls.on('panaftertap', panaftertap);
this.touchControls.on('panaftertapend', panaftertapend);
}
SolvespaceControls.prototype = Object.create(THREE.EventDispatcher.prototype);
SolvespaceControls.prototype.constructor = SolvespaceControls;
solvespace = function(obj, params) {
var scene, edgeScene, camera, edgeCamera, renderer;
var geometry, controls, material, mesh, edges;
var width, height;
var directionalLightArray = [];
if (typeof params === "undefined" || !("width" in params)) {
width = window.innerWidth;
} else {
width = params.width;
}
if (typeof params === "undefined" || !("height" in params)) {
height = window.innerHeight;
} else {
height = params.height;
}
domElement = init();
render();
return domElement;
function init() {
scene = new THREE.Scene();
edgeScene = new THREE.Scene();
camera = new SolvespaceCamera(width,
height, 5, new THREE.Vector3(0, 1, 0),
new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 0));
mesh = createMesh(obj);
scene.add(mesh);
edges = createEdges(obj);
edgeScene.add(edges);
for (var i = 0; i < obj.lights.d.length; i++) {
var lightColor = new THREE.Color(obj.lights.d[i].intensity,
obj.lights.d[i].intensity, obj.lights.d[i].intensity);
var directionalLight = new THREE.DirectionalLight(lightColor, 1);
directionalLight.position.set(obj.lights.d[i].direction[0],
obj.lights.d[i].direction[1], obj.lights.d[i].direction[2]);
directionalLightArray.push(directionalLight);
scene.add(directionalLight);
}
var lightColor = new THREE.Color(obj.lights.a, obj.lights.a, obj.lights.a);
var ambientLight = new THREE.AmbientLight(lightColor.getHex());
scene.add(ambientLight);
renderer = new THREE.WebGLRenderer({ antialias: true});
renderer.setSize(width, height);
renderer.autoClear = false;
controls = new SolvespaceControls(camera, renderer.domElement);
controls.addEventListener("change", render);
controls.addEventListener("change", lightUpdate);
animate();
return renderer.domElement;
})";
const char htmlbegin1[] = R"(
function animate() {
requestAnimationFrame(animate);
controls.update();
}
function render() {
var context = renderer.getContext();
camera.updateProjectionMatrix();
renderer.clear();
context.depthRange(0.1, 1);
renderer.render(scene, camera);
context.depthRange(0.1-(2/60000.0), 1-(2/60000.0));
renderer.render(edgeScene, camera);
}
function lightUpdate() {
var changeBasis = new THREE.Matrix4();
// The original light positions were in camera space.
// Project them into standard space using camera's basis
// vectors (up, target, and their cross product).
n = new THREE.Vector3().crossVectors(camera.up, camera.right);
changeBasis.makeBasis(camera.right, camera.up, n);
for (var i = 0; i < 2; i++) {
var newLightPos = changeBasis.applyToVector3Array(
[obj.lights.d[i].direction[0], obj.lights.d[i].direction[1],
obj.lights.d[i].direction[2]]);
directionalLightArray[i].position.set(newLightPos[0],
newLightPos[1], newLightPos[2]);
}
}
function createMesh(meshObj) {
var geometry = new THREE.Geometry();
var materialIndex = 0;
var materialList = [];
var opacitiesSeen = {};
for (var i = 0; i < meshObj.points.length; i++) {
geometry.vertices.push(new THREE.Vector3(meshObj.points[i][0],
meshObj.points[i][1], meshObj.points[i][2]));
}
for (var i = 0; i < meshObj.faces.length; i++) {
var currOpacity = ((meshObj.colors[i] & 0xFF000000) >>> 24) / 255.0;
if (opacitiesSeen[currOpacity] === undefined) {
opacitiesSeen[currOpacity] = materialIndex;
materialIndex++;
materialList.push(new THREE.MeshLambertMaterial({
vertexColors: THREE.FaceColors,
opacity: currOpacity,
transparent: true,
side: THREE.DoubleSide
}));
}
geometry.faces.push(new THREE.Face3(meshObj.faces[i][0],
meshObj.faces[i][1], meshObj.faces[i][2],
[new THREE.Vector3(meshObj.normals[i][0][0],
meshObj.normals[i][0][1], meshObj.normals[i][0][2]),
new THREE.Vector3(meshObj.normals[i][1][0],
meshObj.normals[i][1][1], meshObj.normals[i][1][2]),
new THREE.Vector3(meshObj.normals[i][2][0],
meshObj.normals[i][2][1], meshObj.normals[i][2][2])],
new THREE.Color(meshObj.colors[i] & 0x00FFFFFF),
opacitiesSeen[currOpacity]));
}
geometry.computeBoundingSphere();
return new THREE.Mesh(geometry, new THREE.MultiMaterial(materialList));
}
function createEdges(meshObj) {
var geometry = new THREE.Geometry();
var material = new THREE.LineBasicMaterial();
for (var i = 0; i < meshObj.edges.length; i++) {
geometry.vertices.push(new THREE.Vector3(meshObj.edges[i][0][0],
meshObj.edges[i][0][1], meshObj.edges[i][0][2]),
new THREE.Vector3(meshObj.edges[i][1][0],
meshObj.edges[i][1][1], meshObj.edges[i][1][2]));
}
geometry.computeBoundingSphere();
return new THREE.LineSegments(geometry, material);
}
};
)";
const char htmlend[] = R"(
document.body.appendChild(solvespace(solvespace_model_%s));
</script>
</body>
</html>
)";
// A default three.js viewer with OrthographicTrackballControls is
// generated as a comment preceding the data.
@ -1073,8 +1377,10 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename,
baseFilename[i] = '_';
}
if(extension == "html")
fputs(htmlbegin, f);
if(extension == "html") {
fputs(htmlbegin0, f);
fputs(htmlbegin1, f);
}
fprintf(f, "var solvespace_model_%s = {\n"
" bounds: {\n"