diff --git a/web/app/3d/main.js b/web/app/3d/main.js index 6effa6e1..348ef667 100644 --- a/web/app/3d/main.js +++ b/web/app/3d/main.js @@ -2,7 +2,10 @@ TCAD = {}; TCAD.App = function() { - this.id = "DEFAULT"; + this.id = window.location.hash.substring(1); + if (!this.id) { + this.id = "DEFAULT"; + } this.bus = new TCAD.Bus(); this.viewer = new TCAD.Viewer(this.bus); this.ui = new TCAD.UI(this); @@ -42,6 +45,9 @@ TCAD.App = function() { } } + this.bus.subscribe("craft", function() { + app._refreshSketches(); + }); window.addEventListener('storage', storage_handler, false); }; @@ -63,59 +69,99 @@ TCAD.App.prototype.sketchFace = function() { } else { data = JSON.parse(savedFace); } - data.boundary = {lines : [], arcs : []}; + data.boundary = {lines : [], arcs : [], circles : []}; function sameSketchObject(a, b) { if (a.sketchConnectionObject === undefined || b.sketchConnectionObject === undefined) { return false; } return a.sketchConnectionObject.id === b.sketchConnectionObject.id; } - var paths = []; - polyFace.polygon.collectPaths(paths); - var _2dTr = polyFace.polygon.get2DTransformation(); + + var paths = TCAD.craft.reconstructSketchBounds(polyFace.solid.csg, polyFace); + + //polyFace.polygon.collectPaths(paths); + var _3dTransformation = new TCAD.Matrix().setBasis(polyFace.basis()); + var _2dTr = _3dTransformation.invert(); + + function addSegment(a, b) { + data.boundary.lines.push({ + a : {x : a.x, y: a.y}, + b : {x : b.x, y: b.y} + }); + } + function addArc(arc) { + if (arc.length < 2) { + return; + } + var a = arc[0], b = arc[arc.length - 1]; + if (arc.length == 2) { + addSegment(a, b); + return; + } + var mid = (arc.length / 2) >> 0; + var c = TCAD.math.circleFromPoints(arc[0], arc[mid], arc[arc.length-1]); + if (c == null) { + return; + } + if (!TCAD.geom.isCCW([arc[0], arc[mid], arc[arc.length-1]])) { + var t = a; + a = b; + b = t; + } + data.boundary.arcs.push({ + a : {x : a.x, y: a.y}, + b : {x : b.x, y: b.y}, + c : {x : c.x, y : c.y} + }); + } + function addCircle(circle) { + var n = circle.length; + //var c = TCAD.math.circleFromPoints(circle[0], circle[((n / 3) >> 0) % n], circle[((2 * n / 3) >> 0) % n]); + var c = TCAD.math.circleFromPoints(circle[0], circle[1], circle[2]); + if (c === null) return; + var r = TCAD.math.distanceAB(circle[0], c); + data.boundary.circles.push({ + c : {x : c.x, y: c.y}, + r : r + }); + } + function isCircle(path) { + for (var i = 0; i < path.length; i++) { + var p = path[i]; + if (p.sketchConnectionObject === undefined + || p.sketchConnectionObject._class !== 'TCAD.TWO.Circle' + || p.sketchConnectionObject.id !== path[0].sketchConnectionObject.id) { + return false; + } + } + return true; + } + + function trPath (path) { + var out = []; + for (var i = 0; i < path.length; i++) { + out.push(_2dTr.apply(path[i])); + } + return out; + } + for (var i = 0; i < paths.length; i++) { - var path = paths[i]; + var path = paths[i].vertices; + if (path.length < 3) continue; var shift = 0; + if (isCircle(path)) { + addCircle(trPath(path)); + continue; + } TCAD.utils.iteratePath(path, 0, function(a, b, ai, bi) { shift = bi; return sameSketchObject(a, b); }); - - function addSegment(a, b) { - data.boundary.lines.push({ - a : {x : a.x, y: a.y}, - b : {x : b.x, y: b.y} - }); - } - function addArc(arc) { - if (arc.length < 2) { - return; - } - var a = arc[0], b = arc[arc.length - 1]; - if (arc.length == 2) { - addSegment(a, b); - return; - } - var mid = (arc.length / 2) >> 0; - var c = TCAD.math.circleFromPoints(arc[0], arc[mid], arc[arc.length-1]); - if (c == null) { - return; - } - if (!TCAD.geom.isCCW([arc[0], arc[mid], arc[arc.length-1]])) { - var t = a; - a = b; - b = t; - } - data.boundary.arcs.push({ - a : {x : a.x, y: a.y}, - b : {x : b.x, y: b.y}, - c : {x : c.x, y : c.y} - }); - } var currSko = null; var arc = null; TCAD.utils.iteratePath(path, shift+1, function(a, b, ai, bi, iterNumber, path) { - var isArc = a.sketchConnectionObject !== undefined && a.sketchConnectionObject._class == 'TCAD.TWO.Arc'; + var isArc = a.sketchConnectionObject !== undefined && + (a.sketchConnectionObject._class == 'TCAD.TWO.Arc' || a.sketchConnectionObject._class == 'TCAD.TWO.Circle'); //if circle gets splitted var a2d = _2dTr.apply(a); if (isArc) { if (currSko !== a.sketchConnectionObject.id) { diff --git a/web/app/3d/viewer.js b/web/app/3d/viewer.js index 8198f9dc..21694aa7 100644 --- a/web/app/3d/viewer.js +++ b/web/app/3d/viewer.js @@ -51,26 +51,57 @@ TCAD.Viewer = function(bus) { **/ // controls = new THREE.OrbitControls( camera , renderer.domElement); - var controls = new THREE.TrackballControls( camera , renderer.domElement); + var trackballControls = new THREE.TrackballControls( camera , renderer.domElement); // document.addEventListener( 'mousemove', function(){ // controls.update(); // }, false ); - controls.rotateSpeed = 3.8; - controls.zoomSpeed = 1.2; - controls.panSpeed = 0.8; + trackballControls.rotateSpeed = 3.8; + trackballControls.zoomSpeed = 1.2; + trackballControls.panSpeed = 0.8; - controls.noZoom = false; - controls.noPan = false; + trackballControls.noZoom = false; + trackballControls.noPan = false; - controls.staticMoving = true; - controls.dynamicDampingFactor = 0.3; + trackballControls.staticMoving = true; + trackballControls.dynamicDampingFactor = 0.3; - controls.keys = [ 65, 83, 68 ]; - controls.addEventListener( 'change', render ); - this.controls = controls; + trackballControls.keys = [ 65, 83, 68 ]; + trackballControls.addEventListener( 'change', render ); + this.trackballControls = trackballControls; + + var transformControls = new THREE.TransformControls( camera, renderer.domElement ); + transformControls.addEventListener( 'change', render ); + scene.add( transformControls ); + this.transformControls = transformControls; + + function updateTransformControls() { + if (transformControls.object !== undefined) { + if (transformControls.object.parent === undefined) { + transformControls.detach(); + render(); + } + transformControls.update(); + } + } + + function addAxis(axis, color) { + var lineMaterial = new THREE.LineBasicMaterial({color: color, linewidth: 1}); + var axisGeom = new THREE.Geometry(); + axisGeom.vertices.push(axis.multiply(-1000).three()); + axisGeom.vertices.push(axis.multiply(1000).three()); + scene.add(new THREE.Segment(axisGeom, lineMaterial)); + } + addAxis(TCAD.math.AXIS.X, 0xFF0000); + addAxis(TCAD.math.AXIS.Y, 0x00FF00); + addAxis(TCAD.math.AXIS.Z, 0x0000FF); + + function updateControlsAndHelpers() { + trackballControls.update(); + updateTransformControls(); + } /** * TOOLS @@ -103,18 +134,25 @@ TCAD.Viewer = function(bus) { var scope = this; function onClick(e) { var intersects = scope.raycast(e); - if (intersects.length > 0) { - var pickResult = intersects[0]; + if (intersects.length === 0) scope.transformControls.detach(); + for (var ii = 0; ii < intersects.length; ii++) { + var pickResult = intersects[ii]; + if (!pickResult.face) continue; if (pickResult.face.__TCAD_polyFace !== undefined) { var poly = pickResult.face.__TCAD_polyFace; if (scope.selectionMgr.contains(poly)) { scope.toolMgr.handleClick(poly, pickResult); } else { - scope.select(poly); - pickResult.object.geometry.colorsNeedUpdate = true; + if (e.shiftKey) { + scope.transformControls.attach(pickResult.object); + } else { + scope.select(poly); + pickResult.object.geometry.colorsNeedUpdate = true; + } } } render(); + break; } } @@ -148,7 +186,7 @@ TCAD.Viewer = function(bus) { function animate() { // console.log("animate"); requestAnimationFrame( animate ); - controls.update(); + updateControlsAndHelpers(); } render(); diff --git a/web/app/engine.js b/web/app/engine.js index 66eb323f..89279568 100644 --- a/web/app/engine.js +++ b/web/app/engine.js @@ -5,21 +5,47 @@ TCAD.utils.createSquare = function(width) { width /= 2; - var shell = [ + return [ new TCAD.Vector(-width, -width, 0), new TCAD.Vector( width, -width, 0), new TCAD.Vector( width, width, 0), new TCAD.Vector(-width, width, 0) ]; +}; - return new TCAD.Polygon(shell); +TCAD.utils.csgVec = function(v) { + return new CSG.Vector3D(v.x, v.y, v.z); +}; + +TCAD.utils.vec = function(v) { + return new TCAD.Vector(v.x, v.y, v.z); }; TCAD.utils.createBox = function(width) { var square = TCAD.utils.createSquare(width); var rot = TCAD.math.rotateMatrix(3/4, TCAD.math.AXIS.Z, TCAD.math.ORIGIN); - square.eachVertex(function(path, i) { rot._apply(path[i]) } ) - return TCAD.geom.extrude(square, square.normal.multiply(width)); + square.forEach(function(v) { rot._apply(v) } ); + var normal = TCAD.geom.normalOfCCWSeq(square); + return TCAD.geom.extrude(square, normal.multiply(width), normal); +}; + +TCAD.utils.createCSGBox = function(width) { + var csg = CSG.fromPolygons(TCAD.utils.createBox(width)); + return TCAD.utils.createSolidMesh(csg); +}; + +TCAD.utils.toCsgGroups = function(polygons) { + var groups = []; + for (var i = 0; i < polygons.length; i++) { + var p = polygons[i]; + if (p.holes.length === 0) { + groups.push( new TCAD.CSGGroup([new TCAD.SimplePolygon(p.shell, p.normal)], p.normal) ); + } else { + // TODO: triangulation needed + groups.push( new TCAD.CSGGroup([new TCAD.SimplePolygon(p.shell, p.normal)], p.normal) ); + } + } + return groups; }; TCAD.utils.checkPolygon = function(poly) { @@ -79,7 +105,7 @@ TCAD.utils.createLine = function (a, b, color) { return new THREE.Segment(geometry, material); }; -TCAD.utils.createSolidMesh = function(faces) { +TCAD.utils.createSolidMesh = function(csg) { var material = new THREE.MeshPhongMaterial({ vertexColors: THREE.FaceColors, color: TCAD.view.FACE_COLOR, @@ -89,7 +115,7 @@ TCAD.utils.createSolidMesh = function(faces) { polygonOffsetUnits : 1 }); - var geometry = new TCAD.Solid(faces, material); + var geometry = new TCAD.Solid(csg, material); return geometry.meshObject; }; @@ -107,12 +133,6 @@ TCAD.utils.fixCCW = function(path, normal) { return path; }; -TCAD.utils.addAll = function(arr, arrToAdd) { - for (var i = 0; i < arrToAdd.length; i++) { - arr.push(arrToAdd[i]); - } -}; - TCAD.TOLERANCE = 1E-6; TCAD.utils.areEqual = function(v1, v2, tolerance) { @@ -230,6 +250,7 @@ TCAD.utils.sketchToPolygons = function(geom) { var polygons = []; for (var li = 0; li < loops.length; ++li) { var loop = loops[li]; + if (!TCAD.geom.isCCW(loop)) loop.reverse(); var polyPoints = []; for (var pi = 0; pi < loop.length; ++pi) { var point = loop[pi]; @@ -243,17 +264,37 @@ TCAD.utils.sketchToPolygons = function(geom) { point.sketchConnectionObject = edge.sketchObject; } if (polyPoints.length >= 3) { - var polygon = new TCAD.Polygon(polyPoints); - polygons.push(polygon); + polygons.push(polyPoints); } else { console.warn("Points count < 3!"); } } + for (var li = 0; li < geom.loops.length; ++li) { + var loop = geom.loops[li]; + var polyPoints = loop.slice(0); + for (var si = 0; si < polyPoints.length; si++) { + var conn = polyPoints[si]; + //reuse a point and ignore b point since it's a guaranteed loop + conn.a.sketchConnectionObject = conn.sketchObject; + polyPoints[si] = conn.a; + } + // we assume that connection object is the same al other the loop. That's why reverse is safe. + if (!TCAD.geom.isCCW(polyPoints)) polyPoints.reverse(); + if (polyPoints.length >= 3) { + polygons.push(polyPoints); + } + } return polygons; }; TCAD.geom = {}; +TCAD.geom.someBasis2 = function(normal) { + var x = normal.cross(normal.randomNonParallelVector()); + var y = normal.cross(x).unit(); + return [x, y, normal]; +}; + TCAD.geom.someBasis = function(twoPointsOnPlane, normal) { var a = twoPointsOnPlane[0]; var b = twoPointsOnPlane[1]; @@ -295,104 +336,154 @@ TCAD.geom.isCCW = function(path2D) { return TCAD.geom.area(path2D) >= 0; }; -TCAD.geom.extrude = function(source, target) { +TCAD.geom.extrude = function(source, target, sourceNormal) { - var dotProduct = target.normalize().dot(source.normal); - if (dotProduct == 0) { + var extrudeDistance = target.normalize().dot(sourceNormal); + if (extrudeDistance == 0) { return []; } - if (dotProduct > 0) { - source = source.flip(); + var negate = extrudeDistance < 0; + + var poly = [null, null]; + var lid = []; + for (var si = 0; si < source.length; ++si) { + lid[si] = source[si].plus(target); } - var poly = [source]; + var bottom, top; + if (negate) { + bottom = lid; + top = source; + } else { + bottom = source; + top = lid; + } - var lid = source.shift(target).flip(); - poly.push(lid); - var lidShell = lid.shell.slice(0); - lidShell.reverse(); - - var n = source.shell.length; + var n = source.length; for ( var p = n - 1, i = 0; i < n; p = i ++ ) { - var face = new TCAD.Polygon([ - source.shell[i], - source.shell[p], - lidShell[p], - lidShell[i] - ]); - face.csgInfo = {derivedFrom: source.shell[i].sketchConnectionObject}; + var shared = TCAD.utils.createShared(); + shared.__tcad.csgInfo = {derivedFrom: source[p].sketchConnectionObject}; + var face = new CSG.Polygon([ + new CSG.Vertex(TCAD.utils.csgVec(bottom[p])), + new CSG.Vertex(TCAD.utils.csgVec(bottom[i])), + new CSG.Vertex(TCAD.utils.csgVec(top[i])), + new CSG.Vertex(TCAD.utils.csgVec(top[p])) + ], shared); poly.push(face); } + + if (negate) { + lid.reverse(); + } else { + source = source.slice(0); + source.reverse(); + } + + function vecToVertex(v) { + return new CSG.Vertex(TCAD.utils.csgVec(v)); + } + + poly[0] = new CSG.Polygon(source.map(vecToVertex), TCAD.utils.createShared()); + poly[1] = new CSG.Polygon(lid.map(vecToVertex), TCAD.utils.createShared()); return poly; }; TCAD.geom.SOLID_COUNTER = 0; +TCAD.geom.triangulate = function(path, normal) { + var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis2(normal)); + var _2dTransformation = _3dTransformation.invert(); + var i; + var shell = []; + for (i = 0; i < path.length; ++i) { + shell[i] = _2dTransformation.apply(path[i].pos); + } + var myTriangulator = new PNLTRI.Triangulator(); + return myTriangulator.triangulate_polygon( [ shell ] ); +// return THREE.Shape.utils.triangulateShape( f2d.shell, f2d.holes ); +}; + +TCAD.utils.groupCSG = function(csg) { + var csgPolygons = csg.toPolygons(); + var groups = {}; + for (var i = 0; i < csgPolygons.length; i++) { + var p = csgPolygons[i]; + var tag = p.shared.getTag(); + if (groups[tag] === undefined) { + groups[tag] = { + tag : tag, + polygons : [], + shared : p.shared, + plane : p.plane + }; + } + groups[tag].polygons.push(p); + } + return groups; +}; + +TCAD.utils.SHARED_COUNTER = 0; +TCAD.utils.createShared = function() { + var id = TCAD.utils.SHARED_COUNTER ++; + var shared = new CSG.Polygon.Shared([id, id, id, id]); + shared.__tcad = {}; + return shared; +}; + /** @constructor */ -TCAD.Solid = function(polygons, material) { +TCAD.Solid = function(csg, material) { THREE.Geometry.call( this ); + this.csg = csg; this.dynamic = true; //true by default - + this.meshObject = new THREE.Mesh(this, material); - this.id = TCAD.geom.SOLID_COUNTER ++; + this.tCadId = TCAD.geom.SOLID_COUNTER ++; this.faceCounter = 0; + this.wireframeGroup = new THREE.Object3D(); + this.meshObject.add(this.wireframeGroup); + this.polyFaces = []; var scope = this; - function pushVertices(vertices) { - for ( var v = 0; v < vertices.length; ++ v ) { - scope.vertices.push( new THREE.Vector3( vertices[v].x, vertices[v].y, vertices[v].z ) ); - } - } + function threeV(v) {return new THREE.Vector3( v.x, v.y, v.z )} var off = 0; - for (var p = 0; p < polygons.length; ++ p) { - var poly = polygons[p]; - try { - var faces = poly.triangulate(); - } catch(e) { - console.log(e); - continue; - } - pushVertices(poly.shell); - for ( var h = 0; h < poly.holes.length; ++ h ) { - pushVertices(poly.holes[ h ]); - } - var polyFace = new TCAD.SketchFace(this, poly); + var groups = TCAD.utils.groupCSG(csg); + for (var gIdx in groups) { + var group = groups[gIdx]; + if (group.shared.__tcad === undefined) group.shared.__tcad = {}; + var polyFace = new TCAD.SketchFace(this, group); this.polyFaces.push(polyFace); + for (var p = 0; p < group.polygons.length; ++p) { + var poly = group.polygons[p]; + var vLength = poly.vertices.length; + if (vLength < 3) continue; + var firstVertex = poly.vertices[0]; + this.vertices.push(threeV(firstVertex.pos)); + this.vertices.push(threeV(poly.vertices[1].pos)); + var normal = threeV(poly.plane.normal); + for (var i = 2; i < vLength; i++) { + this.vertices.push(threeV(poly.vertices[i].pos)); - for ( var i = 0; i < faces.length; ++ i ) { - - var a = faces[i][0] + off; - var b = faces[i][1] + off; - var c = faces[i][2] + off; - - var fNormal = TCAD.geom.normalOfCCWSeqTHREE([ - this.vertices[a], this.vertices[b], this.vertices[c]]); - - if (!TCAD.utils.vectorsEqual(fNormal, poly.normal)) { - console.log("ASSERT"); - var _a = a; - a = c; - c = _a; + var a = off; + var b = i - 1 + off; + var c = i + off; + var face = new THREE.Face3(a, b, c); + polyFace.faces.push(face); + face.__TCAD_polyFace = polyFace; + face.normal = normal; + face.materialIndex = gIdx; + this.faces.push(face); + TCAD.view.setFaceColor(polyFace, !!group.shared.__tcad.csgInfo && !!group.shared.__tcad.csgInfo.derivedFrom && group.shared.__tcad.csgInfo.derivedFrom._class === 'TCAD.TWO.Arc' ? 0xFF0000 : null); } - - var face = new THREE.Face3( a, b, c ); - polyFace.faces.push(face); - face.__TCAD_polyFace = polyFace; - face.normal = poly.normal.three(); - face.materialIndex = p; - this.faces.push( face ); + off = this.vertices.length; } - off = this.vertices.length; } this.mergeVertices(); - this.wireframeGroup = new THREE.Object3D(); - this.meshObject.add(this.wireframeGroup); - this.makeWireframe(polygons); + //this.makeWireframe(polygons); }; if (typeof THREE !== "undefined") { @@ -428,25 +519,19 @@ TCAD.Solid.prototype.makeWireframe = function(polygons) { }; /** @constructor */ -TCAD.SketchFace = function(solid, poly) { - var proto = poly.__face; - poly.__face = this; - if (proto === undefined) { - this.id = solid.id + ":" + (solid.faceCounter++); - this.sketchGeom = null; +TCAD.SketchFace = function(solid, csgGroup) { + csgGroup.__face = this; + if (csgGroup.shared.__tcad.faceId === undefined) { + this.id = solid.tCadId + ":" + (solid.faceCounter++); } else { - this.id = proto.id; - this.sketchGeom = proto.sketchGeom; + this.id = csgGroup.shared.__tcad.faceId; } - + csgGroup.shared.__tcad.faceId = this.id; + this.solid = solid; - this.polygon = poly; + this.csgGroup = csgGroup; this.faces = []; this.sketch3DGroup = null; - - if (this.sketchGeom != null) { - this.syncSketches(this.sketchGeom); - } }; if (typeof THREE !== "undefined") { @@ -456,10 +541,34 @@ if (typeof THREE !== "undefined") { color: 0x2B3856, linewidth: 3}); } +TCAD.SketchFace.prototype.calcBasis = function() { + var vec = TCAD.utils.vec; + var normal = vec(this.csgGroup.plane.normal); + var alignPlane, x, y; + if (Math.abs(normal.dot(TCAD.math.AXIS.Y)) < 0.5) { + alignPlane = normal.cross(TCAD.math.AXIS.Y); + } else { + alignPlane = normal.cross(TCAD.math.AXIS.Z); + } + y = alignPlane.cross(normal); + x = y.cross(normal); + return [x, y, normal]; +}; + +TCAD.SketchFace.prototype.basis = function() { + if (!this._basis) { + this._basis = this.calcBasis(); + } + return this._basis; + //return TCAD.geom.someBasis(this.csgGroup.polygons[0].vertices.map(function (v) { + // return vec(v.pos) + //}), vec(this.csgGroup.plane.normal)); +}; + TCAD.SketchFace.prototype.syncSketches = function(geom) { var i; - var normal = this.polygon.normal; - var offVector = normal.multiply(0); // disable it. use polygon offset feature of material + var normal = this.csgGroup.plane.normal; + var offVector = normal.scale(0); // disable it. use polygon offset feature of material if (this.sketch3DGroup != null) { for (var i = this.sketch3DGroup.children.length - 1; i >= 0; --i) { @@ -470,11 +579,13 @@ TCAD.SketchFace.prototype.syncSketches = function(geom) { this.solid.meshObject.add(this.sketch3DGroup); } - var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis(this.polygon.shell, normal)); + var basis = this.basis(); + var _3dTransformation = new TCAD.Matrix().setBasis(basis); //we lost depth or z off in 2d sketch, calculate it again - var depth = normal.dot(this.polygon.shell[0]); - for (i = 0; i < geom.connections.length; ++i) { - var l = geom.connections[i]; + var depth = this.csgGroup.plane.w; + var connections = geom.connections.concat(TCAD.utils.arrFlatten1L(geom.loops)); + for (i = 0; i < connections.length; ++i) { + var l = connections[i]; var lg = new THREE.Geometry(); l.a.z = l.b.z = depth; var a = _3dTransformation.apply(l.a); @@ -485,8 +596,6 @@ TCAD.SketchFace.prototype.syncSketches = function(geom) { var line = new THREE.Segment(lg, this.SKETCH_MATERIAL); this.sketch3DGroup.add(line); } - this.sketchGeom = geom; - this.sketchGeom.depth = depth; }; TCAD.POLYGON_COUNTER = 0; @@ -624,3 +733,18 @@ TCAD.utils.iteratePath = function(path, shift, callback) { } } }; + +TCAD.utils.addAll = function(arr, arrToAdd) { + for (var i = 0; i < arrToAdd.length; i++) { + arr.push(arrToAdd[i]); + } +}; + +TCAD.utils.arrFlatten1L = function(arr) { + var result = []; + for (var i = 0; i < arr.length; i++) { + TCAD.utils.addAll(result, arr[i]); + + } + return result; +}; diff --git a/web/app/sketcher/io.js b/web/app/sketcher/io.js index 7b0af7e1..2024dfce 100644 --- a/web/app/sketcher/io.js +++ b/web/app/sketcher/io.js @@ -213,6 +213,7 @@ TCAD.IO.prototype.updateBoundary = function (boundary) { this.boundaryLayer.readOnly = true; this.viewer.layers.splice(0, 0, this.boundaryLayer); } + var layer = this.boundaryLayer; // if (bbox[0] < Number.MAX_VALUE && bbox[1] < Number.MAX_VALUE && -bbox[2] < Number.MAX_VALUE && -bbox[3] < Number.MAX_VALUE) { // this.viewer.showBounds(bbox[0], bbox[1], bbox[2], bbox[3]) // } @@ -233,13 +234,16 @@ TCAD.IO.prototype.updateBoundary = function (boundary) { // } // } //} + var id = 0; + function __makeAux(obj) { + obj.accept(function(o){o.aux = true; return true;}); + obj.edge = id ++; + } - var id, i = 0; - for (i = 0; i < boundary.lines.length; ++i, ++id) { + for (var i = 0; i < boundary.lines.length; ++i, ++id) { var edge = boundary.lines[i]; var seg = this.viewer.addSegment(edge.a.x, edge.a.y, edge.b.x, edge.b.y, this.boundaryLayer); - seg.accept(function(o){o.aux = true; return true;}); - seg.edge = id ++; + __makeAux(seg); } for (i = 0; i < boundary.arcs.length; ++i, ++id) { var a = boundary.arcs[i]; @@ -249,8 +253,15 @@ TCAD.IO.prototype.updateBoundary = function (boundary) { new TCAD.TWO.EndPoint(a.c.x, a.c.y) ); this.boundaryLayer.objects.push(arc); - arc.accept(function(o){o.aux = true; return true;}); - arc.edge = id ++; + __makeAux(arc); + } + for (i = 0; i < boundary.circles.length; ++i, ++id) { + var obj = boundary.circles[i]; + var circle = new TCAD.TWO.Circle(new TCAD.TWO.EndPoint(obj.c.x, obj.c.y)); + circle.r.set(obj.r); + this.boundaryLayer.objects.push(circle); + __makeAux(circle); + } }; diff --git a/web/app/workbench.js b/web/app/workbench.js index 35728495..ecf244d7 100644 --- a/web/app/workbench.js +++ b/web/app/workbench.js @@ -7,7 +7,7 @@ TCAD.workbench.SketchConnection = function(a, b, sketchObject) { }; TCAD.workbench.readSketchGeom = function(sketch) { - var out = {connections : []}; + var out = {connections : [], loops : []}; var id = 0; if (sketch.layers !== undefined) { for (var l = 0; l < sketch.layers.length; ++l) { @@ -15,25 +15,29 @@ TCAD.workbench.readSketchGeom = function(sketch) { var obj = sketch.layers[l].data[i]; if (obj.edge !== undefined) continue; if (!!obj.aux) continue; - var a = new TCAD.Vector(obj.points[0][1][1], obj.points[0][2][1], 0); - var b = new TCAD.Vector(obj.points[1][1][1], obj.points[1][2][1], 0); if (obj._class === 'TCAD.TWO.Segment') { - out.connections.push(new TCAD.workbench.SketchConnection( - a, b, {_class : obj._class, id : id++} - )); + var a = new TCAD.Vector(obj.points[0][1][1], obj.points[0][2][1], 0); + var b = new TCAD.Vector(obj.points[1][1][1], obj.points[1][2][1], 0); + out.connections.push(new TCAD.workbench.SketchConnection(a, b, {_class : obj._class, id : id++})); } else if (obj._class === 'TCAD.TWO.Arc') { + var a = new TCAD.Vector(obj.points[0][1][1], obj.points[0][2][1], 0); + var b = new TCAD.Vector(obj.points[1][1][1], obj.points[1][2][1], 0); var center = new TCAD.Vector(obj.points[2][1][1], obj.points[2][2][1], 0); var approxArc = TCAD.workbench.approxArc(a, b, center, 20); var data = {_class : obj._class, id : id++}; for (var j = 0; j < approxArc.length - 1; j++) { - var pa = approxArc[j]; - var pb = approxArc[j+1]; - out.connections.push(new TCAD.workbench.SketchConnection( - pa, pb, data - )); + out.connections.push(new TCAD.workbench.SketchConnection(approxArc[j], approxArc[j+1], data)); } - } else if (obj._class === 'TCAD.TWO.Circle') { + var center = new TCAD.Vector(obj.c[1][1], obj.c[2][1], 0); + var approxCircle = TCAD.workbench.approxCircle(center, obj.r, 20); + var data = {_class : obj._class, id : id++}; + var loop = []; + var p, q, n = approxCircle.length; + for (var p = n - 1, q = 0; q < n; p = q++) { + loop.push(new TCAD.workbench.SketchConnection(approxCircle[p], approxCircle[q], data)); + } + out.loops.push(loop); } } } @@ -61,6 +65,16 @@ TCAD.workbench.approxArc = function(ao, bo, c, k) { return points; }; +TCAD.workbench.approxCircle = function(c, r, k) { + var points = []; + var step = (2 * Math.PI) / k; + + for (var i = 0, angle = 0; i < k; ++i, angle += step) { + points.push(new TCAD.Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); + } + return points; +}; + TCAD.workbench.serializeSolid = function(solid) { var data = {}; data.faceCounter = TCAD.geom.FACE_COUNTER; @@ -82,29 +96,29 @@ TCAD.craft.getSketchedPolygons3D = function(app, face) { var geom = TCAD.workbench.readSketchGeom(JSON.parse(savedFace)); var polygons2D = TCAD.utils.sketchToPolygons(geom); - var normal = face.polygon.normal; + var normal = face.csgGroup.normal; var depth = null; var sketchedPolygons = []; for (var i = 0; i < polygons2D.length; i++) { var poly2D = polygons2D[i]; - if (poly2D.shell.length < 3) continue; + if (poly2D.length < 3) continue; if (depth == null) { - var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis(face.polygon.shell, normal)); + var _3dTransformation = new TCAD.Matrix().setBasis(face.basis()); //we lost depth or z off in 2d sketch, calculate it again - depth = normal.dot(face.polygon.shell[0]); + depth = face.csgGroup.plane.w; } - var shell = []; - for (var m = 0; m < poly2D.shell.length; ++m) { - var vec = poly2D.shell[m]; + var polygon = []; + for (var m = 0; m < poly2D.length; ++m) { + var vec = poly2D[m]; vec.z = depth; // var a = _3dTransformation.apply(new TCAD.Vector(poly2D[m][0], poly2D[m][1], depth)); var a = _3dTransformation.apply(vec); a.sketchConnectionObject = vec.sketchConnectionObject; - shell.push(a); + polygon.push(a); } - var polygon = new TCAD.Polygon(shell); + sketchedPolygons.push(polygon); } return sketchedPolygons; @@ -114,22 +128,18 @@ TCAD.craft.extrude = function(app, request) { var face = request.face; var sketchedPolygons = TCAD.craft.getSketchedPolygons3D(app, face); if (sketchedPolygons == null) return null; - face.polygon.__face = undefined; - - var faces = TCAD.craft.collectFaces(request.solids); - - var normal = face.polygon.normal; + var normal = TCAD.utils.vec(face.csgGroup.plane.normal); var toMeldWith = []; for (var i = 0; i < sketchedPolygons.length; i++) { - var extruded = TCAD.geom.extrude(sketchedPolygons[i], normal.multiply(request.height)); - toMeldWith = toMeldWith.concat(TCAD.craft._makeFromPolygons(extruded)); + var extruded = TCAD.geom.extrude(sketchedPolygons[i], normal.multiply(request.height), normal); + toMeldWith = toMeldWith.concat(extruded); } - var work = TCAD.craft._makeFromPolygons(faces.map(function(f){ return f.polygon })); - var meld = CSG.fromPolygons(work).union(CSG.fromPolygons(toMeldWith)); + var meld = request.solids[0].csg.union(CSG.fromPolygons(TCAD.craft._triangulateCSG(toMeldWith))); - return TCAD.craft.reconstruct(meld); + face.csgGroup.shared.__tcad.faceId += '$'; + return [TCAD.utils.createSolidMesh(meld).geometry]; }; TCAD.craft._pointOnLine = function(p, a, b) { @@ -145,25 +155,144 @@ TCAD.craft._pointOnLine = function(p, a, b) { return apLength > 0 && apLength < abLength && TCAD.utils.areEqual(abLength * apLength, dp, 1E-20); }; -TCAD.craft._mergeCSGPolygonsTest = function() { +TCAD.craft.reconstructSketchBounds = function(csg, face) { - function cppol(points) { - return { - vertices : points.map(function(e) { - return new TCAD.Vector(e[0], e[1], 0); - }), - normal : new TCAD.Vector(0,0,1) + var polygons = csg.toPolygons(); + var plane = face.csgGroup.plane; + var sketchSegments = []; + for (var pi = 0; pi < polygons.length; pi++) { + var poly = polygons[pi]; + if (poly.plane.equals(plane)) { + continue; + } + var p, q, n = poly.vertices.length; + for(p = n - 1, q = 0; q < n; p = q ++) { + var a = poly.vertices[p]; + var b = poly.vertices[q]; + var ab = b.pos.minus(a.pos); + var parallelTpPlane = TCAD.utils.equal(ab.unit().dot(plane.normal), 0); + var pointOnPlane = TCAD.utils.equal(plane.signedDistanceToPoint(a.pos), 0); + if (parallelTpPlane && pointOnPlane) { + sketchSegments.push([a.pos, b.pos, poly]); + } } - } - var paths = TCAD.craft._mergeCSGPolygons( - [ - cppol([0,0], [50,0], [50,10], [0,10]), - cppol([0,10], [50,10], [50,60], [0,60]), - cppol([0,60], [50,60], [50,100], [0,100]) - ] - ); - console.log(paths); + return TCAD.craft.segmentsToPaths(sketchSegments); +}; + +TCAD.craft.deleteRedundantPoints = function(path) { + var cleanedPath = []; + //Delete redundant point + var pathLength = path.length; + for (var pi = 0; pi < pathLength; pi++) { + var bIdx = ((pi + 1) % pathLength); + var a = path[pi]; + var b = path[bIdx]; + var c = path[(pi + 2) % pathLength]; + var eq = TCAD.utils.equal; + if (!eq(a.minus(b).unit().dot(a.minus(c).unit()), 1)) { + cleanedPath.push(b); + for (var ii = 0; ii < pathLength - pi - 1; ++ii) { + a = path[(ii + bIdx) % pathLength]; + b = path[(ii + bIdx + 1) % pathLength]; + c = path[(ii + bIdx + 2) % pathLength]; + if (!eq(a.minus(b).unit().dot(a.minus(c).unit()), 1)) { + cleanedPath.push(b); + } + } + break; + } + } + return cleanedPath; +}; + +TCAD.craft.segmentsToPaths = function(segments) { + + var veq = TCAD.struct.hashTable.vectorEquals; + + var paths = []; + var index = TCAD.struct.hashTable.forVector3d(); + var csgIndex = TCAD.struct.hashTable.forEdge(); + + function indexPoint(p, edge) { + var edges = index.get(p); + if (edges === null) { + edges = []; + index.put(p, edges); + } + edges.push(edge); + } + + for (var si = 0; si < segments.length; si++) { + var k = segments[si]; + indexPoint(k[0], k); + indexPoint(k[1], k); + var csgInfo = k[2]; + if (csgInfo !== undefined && csgInfo !== null) { + csgIndex.put([k[0], k[1]], csgInfo); + } + k[3] = false; + } + + function nextPoint(p) { + var edges = index.get(p); + if (edges === null) return null; + for (var i = 0; i < edges.length; i++) { + var edge = edges[i] + if (edge[3]) continue; + var res = null; + if (veq(p, edge[0])) res = edge[1]; + if (veq(p, edge[1])) res = edge[0]; + if (res != null) { + edge[3] = true; + return res; + } + } + return null; + } + + for (var ei = 0; ei < segments.length; ei++) { + var edge = segments[ei]; + if (edge[3]) { + continue; + } + edge[3] = true; + var path = [edge[0], edge[1]]; + paths.push(path); + var next = nextPoint(edge[1]); + while (next !== null) { + if (!veq(next, path[0])) { + path.push(next); + next = nextPoint(next); + } else { + next = null; + } + } + } + + var filteredPaths = []; + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + + //Set derived from object to be able to recunstruct + TCAD.utils.iteratePath(path, 0, function (a, b) { + var fromPolygon = csgIndex.get([a, b]); + if (fromPolygon !== null) { + if (fromPolygon.shared.__tcad.csgInfo) { + a.sketchConnectionObject = fromPolygon.shared.__tcad.csgInfo.derivedFrom; + } + } + return true; + }); + path = TCAD.craft.deleteRedundantPoints(path); + if (path.length > 2) { + filteredPaths.push({ + vertices: path + }); + } + } + + return filteredPaths; }; TCAD.craft._mergeCSGPolygons = function (__cgsPolygons, allPoints) { @@ -427,96 +556,27 @@ TCAD.craft._mergeCSGPolygons = function (__cgsPolygons, allPoints) { return filteredPaths; }; - - -TCAD.craft._mergeCSGPolygonsTest0 = function(data) { - TCAD.craft._mergeCSGPolygonsTester( - [ - [[0,0], [50,0], [50,10], [0,10]], - [[0,10], [50,10], [50,60], [0,60]], - [[0,60], [50,60], [50,100], [0,100]] - ] - ); -}; - -TCAD.craft._mergeCSGPolygonsTest1 = function(data) { - TCAD.craft._mergeCSGPolygonsTester( - [ - [[0,0], [50,0], [50,10], [0,10]], - [[0,10], [20,10], [20,60], [0,60]], - [[20,10], [30,10], [30,60], [20,60]], - [[40,10], [50,10], [50,60], [40,60]], - [[0,60], [50,60], [50,100], [0,100]] - ] - ); -}; - -TCAD.craft._mergeCSGPolygonsTest2 = function(data) { - TCAD.craft._mergeCSGPolygonsTester( - [ - [[0,0], [25,0], [50,0], [50,10], [0,10]], - [[0,10], [20,10], [20,60], [0,60]], - [[20,10], [30,10], [30,60], [20,60]], - [[40,10], [50,10], [50,60], [40,60]], - [[0,60], [50,60], [50,100], [0,100]] - ] - ); -}; - -TCAD.craft._mergeCSGPolygonsTester = function(data) { - - function cppol(points) { - return { - vertices : points.map(function(e) { - return {pos: new TCAD.Vector(e[0], e[1], 0)}; - }), - plane: {normal : new TCAD.Vector(0,0,1), w : 0} - - } - - } - var paths = TCAD.craft._mergeCSGPolygons(data.map(function(p) { - return cppol(p); - })); -}; - -TCAD.craft._makeFromPolygons = function(polygons) { +TCAD.craft._triangulateCSG = function(polygons) { function csgVec(v) { return new CSG.Vector3D(v.x, v.y, v.z); } - var points = []; - var csgPolygons = []; - var off = 0; + var triangled = []; for (var ei = 0; ei < polygons.length; ++ei) { var poly = polygons[ei]; - Array.prototype.push.apply( points, poly.shell ); - for ( var h = 0; h < poly.holes.length; h ++ ) { - Array.prototype.push.apply( points, poly.holes[h] ); - } - var pid = poly.id; - var shared = new CSG.Polygon.Shared([pid, pid, pid, pid]); - shared.__tcad = { - csgInfo : poly.csgInfo, - face : poly.__face - }; - var refs = poly.triangulate(); + var points = poly.vertices; + var refs = TCAD.geom.triangulate(points, poly.plane.normal); for ( var i = 0; i < refs.length; ++ i ) { - var a = refs[i][0] + off; - var b = refs[i][1] + off; - var c = refs[i][2] + off; - if (points[b].minus(points[a]).cross(points[c].minus(points[a])).length() === 0) { + var a = refs[i][0]; + var b = refs[i][1]; + var c = refs[i][2]; + if (points[b].pos.minus(points[a].pos).cross(points[c].pos.minus(points[a].pos)).length() === 0) { continue; } - var csgPoly = new CSG.Polygon([ - new CSG.Vertex(csgVec(points[a]), csgVec(poly.normal)), - new CSG.Vertex(csgVec(points[b]), csgVec(poly.normal)), - new CSG.Vertex(csgVec(points[c]), csgVec(poly.normal)) - ], shared); - csgPolygons.push(csgPoly); + var csgPoly = new CSG.Polygon([points[a], points[b], points[c]], poly.shared, poly.plane); + triangled.push(csgPoly); } - off = points.length; } - return csgPolygons; + return triangled; }; TCAD.craft.recoverySketchInfo = function(polygons) { @@ -707,27 +767,79 @@ TCAD.craft.collectFaces = function(solids) { return faces; }; +TCAD.craft.collectCSGPolygons = function(faces) { + var out = []; + for (var fi = 0; fi < faces.length; fi++) { + var face = faces[fi]; + TCAD.utils.addAll(out, face.csgGroup.toCSGPolygons()); + } + return out; +}; + +TCAD.craft.toGroups = function(csgPolygons) { + + function vec(p) { + var v = new TCAD.Vector(); + v.setV(p); + return v; + } + + var byShared = {}; + var infos = {}; + for (var i = 0; i < csgPolygons.length; i++) { + var p = csgPolygons[i]; + var tag = p.shared.getTag(); + infos[tag] = p.shared; + if (byShared[tag] === undefined) byShared[tag] = []; + byShared[tag].push(p); + } + var result = []; + for (var tag in byShared) { + var groupedPolygons = byShared[tag]; + if (groupedPolygons.length === 0) continue; + var plane = groupedPolygons[0].plane; + var normal = vec(plane.normal); + + var simplePolygons = groupedPolygons.map(function (p) { + + var vertices = p.vertices.map(function (v) { + return vec(v.pos); + }); + return new TCAD.SimplePolygon(vertices, normal); + }); + var csgGroup = new TCAD.CSGGroup(simplePolygons, normal, plane.w); + var tcadShared = infos[tag].__tcad; + if (tcadShared !== undefined) { + csgGroup.csgInfo = tcadShared.csgInfo; + csgGroup.__face = tcadShared.face; + } + result.push(csgGroup); + } + return result; +}; + TCAD.craft.cut = function(app, request) { var face = request.face; var sketchedPolygons = TCAD.craft.getSketchedPolygons3D(app, face); if (sketchedPolygons == null) return null; - //face.polygon.__face = undefined; - - var faces = TCAD.craft.collectFaces(request.solids); - - var normal = face.polygon.normal; + var normal = TCAD.utils.vec(face.csgGroup.plane.normal); var cutter = []; for (var i = 0; i < sketchedPolygons.length; i++) { - var extruded = TCAD.geom.extrude(sketchedPolygons[i], normal.multiply( - request.depth)); - cutter = cutter.concat(TCAD.craft._makeFromPolygons(extruded)); + var extruded = TCAD.geom.extrude(sketchedPolygons[i], normal.multiply( - request.depth), normal); + cutter = cutter.concat(extruded); } + var cutterCSG = CSG.fromPolygons(TCAD.craft._triangulateCSG(cutter)); - var work = TCAD.craft._makeFromPolygons(faces.map(function(f){ return f.polygon })); - - var cut = CSG.fromPolygons(work).subtract(CSG.fromPolygons(cutter)); - - return TCAD.craft.reconstruct(cut); + face.csgGroup.shared.__tcad.faceId += '$'; + var outSolids = []; + for (var si = 0; si < request.solids.length; si++) { + var work = request.solids[si].csg; + var cut = work.subtract(cutterCSG); + var solidMesh = TCAD.utils.createSolidMesh(cut); + outSolids.push(solidMesh.geometry); + } + return outSolids; }; TCAD.Craft = function(app) { @@ -760,18 +872,18 @@ TCAD.Craft.prototype.modify = function(request) { if (!op) return; var detachedRequest = TCAD.craft.detach(request); - var newFaces = op(this.app, request); + var newSolids = op(this.app, request); - if (newFaces == null) return; - for (var i = 0; i < request.solids.length; i++) { - var solid = request.solids[i]; - this.app.viewer.scene.remove( solid.meshObject ); + if (newSolids == null) return; + var i; + for (i = 0; i < request.solids.length; i++) { + this.app.viewer.scene.remove( request.solids[i].meshObject ); + } + for (i = 0; i < newSolids.length; i++) { + this.app.viewer.scene.add(newSolids[i].meshObject); } - this.app.viewer.scene.add(TCAD.utils.createSolidMesh(newFaces)); this.history.push(detachedRequest); this.app.bus.notify('craft'); - //REMOVE IT - this.app._refreshSketches(); this.app.viewer.render(); }; @@ -780,6 +892,6 @@ TCAD.craft.OPS = { CUT : TCAD.craft.cut, PAD : TCAD.craft.extrude, BOX : function(app, request) { - return TCAD.utils.createBox(request.size); + return [TCAD.utils.createCSGBox(request.size).geometry]; } }; diff --git a/web/index.html b/web/index.html index ce488e0f..17119553 100644 --- a/web/index.html +++ b/web/index.html @@ -13,8 +13,10 @@ + + diff --git a/web/lib/three/TransformControls.js b/web/lib/three/TransformControls.js new file mode 100644 index 00000000..6a513443 --- /dev/null +++ b/web/lib/three/TransformControls.js @@ -0,0 +1,981 @@ +/** + * @author arodic / https://github.com/arodic + */ +/*jshint sub:true*/ + +(function () { + + 'use strict'; + + var GizmoMaterial = function ( parameters ) { + + THREE.MeshBasicMaterial.call( this ); + + this.depthTest = false; + this.depthWrite = false; + this.side = THREE.FrontSide; + this.transparent = true; + + this.setValues( parameters ); + + this.oldColor = this.color.clone(); + this.oldOpacity = this.opacity; + + this.highlight = function( highlighted ) { + + if ( highlighted ) { + + this.color.setRGB( 1, 1, 0 ); + this.opacity = 1; + + } else { + + this.color.copy( this.oldColor ); + this.opacity = this.oldOpacity; + + } + + }; + + }; + + GizmoMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype ); + + var GizmoLineMaterial = function ( parameters ) { + + THREE.LineBasicMaterial.call( this ); + + this.depthTest = false; + this.depthWrite = false; + this.transparent = true; + this.linewidth = 1; + + this.setValues( parameters ); + + this.oldColor = this.color.clone(); + this.oldOpacity = this.opacity; + + this.highlight = function( highlighted ) { + + if ( highlighted ) { + + this.color.setRGB( 1, 1, 0 ); + this.opacity = 1; + + } else { + + this.color.copy( this.oldColor ); + this.opacity = this.oldOpacity; + + } + + }; + + }; + + GizmoLineMaterial.prototype = Object.create( THREE.LineBasicMaterial.prototype ); + + THREE.TransformGizmo = function () { + + var scope = this; + var showPickers = false; //debug + var showActivePlane = false; //debug + + this.init = function () { + + THREE.Object3D.call( this ); + + this.handles = new THREE.Object3D(); + this.pickers = new THREE.Object3D(); + this.planes = new THREE.Object3D(); + + this.add(this.handles); + this.add(this.pickers); + this.add(this.planes); + + //// PLANES + + var planeGeometry = new THREE.PlaneGeometry( 50, 50, 2, 2 ); + var planeMaterial = new THREE.MeshBasicMaterial( { wireframe: true } ); + planeMaterial.side = THREE.DoubleSide; + + var planes = { + "XY": new THREE.Mesh( planeGeometry, planeMaterial ), + "YZ": new THREE.Mesh( planeGeometry, planeMaterial ), + "XZ": new THREE.Mesh( planeGeometry, planeMaterial ), + "XYZE": new THREE.Mesh( planeGeometry, planeMaterial ) + }; + + this.activePlane = planes["XYZE"]; + + planes["YZ"].rotation.set( 0, Math.PI/2, 0 ); + planes["XZ"].rotation.set( -Math.PI/2, 0, 0 ); + + for (var i in planes) { + planes[i].name = i; + this.planes.add(planes[i]); + this.planes[i] = planes[i]; + planes[i].visible = false; + } + + //// HANDLES AND PICKERS + + var setupGizmos = function( gizmoMap, parent ) { + + for ( var name in gizmoMap ) { + + for ( i = gizmoMap[name].length; i--;) { + + var object = gizmoMap[name][i][0]; + var position = gizmoMap[name][i][1]; + var rotation = gizmoMap[name][i][2]; + + object.name = name; + + if ( position ) object.position.set( position[0], position[1], position[2] ); + if ( rotation ) object.rotation.set( rotation[0], rotation[1], rotation[2] ); + + parent.add( object ); + + } + + } + + }; + + setupGizmos(this.handleGizmos, this.handles); + setupGizmos(this.pickerGizmos, this.pickers); + + // reset Transformations + + this.traverse(function ( child ) { + if (child instanceof THREE.Mesh) { + child.updateMatrix(); + + var tempGeometry = new THREE.Geometry(); + tempGeometry.merge( child.geometry, child.matrix ); + + child.geometry = tempGeometry; + child.position.set( 0, 0, 0 ); + child.rotation.set( 0, 0, 0 ); + child.scale.set( 1, 1, 1 ); + } + }); + + }; + + this.hide = function () { + this.traverse(function( child ) { + child.visible = false; + }); + }; + + this.show = function () { + this.traverse(function( child ) { + child.visible = true; + if (child.parent == scope.pickers ) child.visible = showPickers; + if (child.parent == scope.planes ) child.visible = false; + }); + this.activePlane.visible = showActivePlane; + }; + + this.highlight = function ( axis ) { + this.traverse(function( child ) { + if ( child.material && child.material.highlight ){ + if ( child.name == axis ) { + child.material.highlight( true ); + } else { + child.material.highlight( false ); + } + } + }); + }; + + }; + + THREE.TransformGizmo.prototype = Object.create( THREE.Object3D.prototype ); + + THREE.TransformGizmo.prototype.update = function ( rotation, eye ) { + + var vec1 = new THREE.Vector3( 0, 0, 0 ); + var vec2 = new THREE.Vector3( 0, 1, 0 ); + var lookAtMatrix = new THREE.Matrix4(); + + this.traverse(function(child) { + if ( child.name.search("E") != -1 ) { + child.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( eye, vec1, vec2 ) ); + } else if ( child.name.search("X") != -1 || child.name.search("Y") != -1 || child.name.search("Z") != -1 ) { + child.quaternion.setFromEuler( rotation ); + } + }); + + }; + + THREE.TransformGizmoTranslate = function () { + + THREE.TransformGizmo.call( this ); + + var arrowGeometry = new THREE.Geometry(); + var mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0, 0.05, 0.2, 12, 1, false ) ); + mesh.position.y = 0.5; + mesh.updateMatrix(); + + arrowGeometry.merge( mesh.geometry, mesh.matrix ); + + var lineXGeometry = new THREE.Geometry(); + lineXGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ) ); + + var lineYGeometry = new THREE.Geometry(); + lineYGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) ); + + var lineZGeometry = new THREE.Geometry(); + lineZGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 1 ) ); + + this.handleGizmos = { + X: [ + [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI/2 ] ], + [ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ] + ], + Y: [ + [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ], + [ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] + ], + Z: [ + [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI/2, 0, 0 ] ], + [ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ] + ], + XYZ: [ + [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, 0 ] ] + ], + XY: [ + [ new THREE.Mesh( new THREE.PlaneGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.15, 0.15, 0 ] ] + ], + YZ: [ + [ new THREE.Mesh( new THREE.PlaneGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.15, 0.15 ], [ 0, Math.PI/2, 0 ] ] + ], + XZ: [ + [ new THREE.Mesh( new THREE.PlaneGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.15, 0, 0.15 ], [ -Math.PI/2, 0, 0 ] ] + ] + }; + + this.pickerGizmos = { + X: [ + [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI/2 ] ] + ], + Y: [ + [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0.6, 0 ] ] + ], + Z: [ + [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0.6 ], [ Math.PI/2, 0, 0 ] ] + ], + XYZ: [ + [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ] + ], + XY: [ + [ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.2, 0.2, 0 ] ] + ], + YZ: [ + [ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.2, 0.2 ], [ 0, Math.PI/2, 0 ] ] + ], + XZ: [ + [ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.2, 0, 0.2 ], [ -Math.PI/2, 0, 0 ] ] + ] + }; + + this.setActivePlane = function ( axis, eye ) { + + var tempMatrix = new THREE.Matrix4(); + eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) ); + + if ( axis == "X" ) { + this.activePlane = this.planes[ "XY" ]; + if ( Math.abs(eye.y) > Math.abs(eye.z) ) this.activePlane = this.planes[ "XZ" ]; + } + + if ( axis == "Y" ){ + this.activePlane = this.planes[ "XY" ]; + if ( Math.abs(eye.x) > Math.abs(eye.z) ) this.activePlane = this.planes[ "YZ" ]; + } + + if ( axis == "Z" ){ + this.activePlane = this.planes[ "XZ" ]; + if ( Math.abs(eye.x) > Math.abs(eye.y) ) this.activePlane = this.planes[ "YZ" ]; + } + + if ( axis == "XYZ" ) this.activePlane = this.planes[ "XYZE" ]; + + if ( axis == "XY" ) this.activePlane = this.planes[ "XY" ]; + + if ( axis == "YZ" ) this.activePlane = this.planes[ "YZ" ]; + + if ( axis == "XZ" ) this.activePlane = this.planes[ "XZ" ]; + + this.hide(); + this.show(); + + }; + + this.init(); + + }; + + THREE.TransformGizmoTranslate.prototype = Object.create( THREE.TransformGizmo.prototype ); + + THREE.TransformGizmoRotate = function () { + + THREE.TransformGizmo.call( this ); + + var CircleGeometry = function ( radius, facing, arc ) { + + var geometry = new THREE.Geometry(); + arc = arc ? arc : 1; + for ( var i = 0; i <= 64 * arc; ++i ) { + if ( facing == 'x' ) geometry.vertices.push( new THREE.Vector3( 0, Math.cos( i / 32 * Math.PI ), Math.sin( i / 32 * Math.PI ) ).multiplyScalar(radius) ); + if ( facing == 'y' ) geometry.vertices.push( new THREE.Vector3( Math.cos( i / 32 * Math.PI ), 0, Math.sin( i / 32 * Math.PI ) ).multiplyScalar(radius) ); + if ( facing == 'z' ) geometry.vertices.push( new THREE.Vector3( Math.sin( i / 32 * Math.PI ), Math.cos( i / 32 * Math.PI ), 0 ).multiplyScalar(radius) ); + } + + return geometry; + }; + + this.handleGizmos = { + X: [ + [ new THREE.Line( new CircleGeometry(1,'x',0.5), new GizmoLineMaterial( { color: 0xff0000 } ) ) ] + ], + Y: [ + [ new THREE.Line( new CircleGeometry(1,'y',0.5), new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] + ], + Z: [ + [ new THREE.Line( new CircleGeometry(1,'z',0.5), new GizmoLineMaterial( { color: 0x0000ff } ) ) ] + ], + E: [ + [ new THREE.Line( new CircleGeometry(1.25,'z',1), new GizmoLineMaterial( { color: 0xcccc00 } ) ) ] + ], + XYZE: [ + [ new THREE.Line( new CircleGeometry(1,'z',1), new GizmoLineMaterial( { color: 0x787878 } ) ) ] + ] + }; + + this.pickerGizmos = { + X: [ + [ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, -Math.PI/2, -Math.PI/2 ] ] + ], + Y: [ + [ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ Math.PI/2, 0, 0 ] ] + ], + Z: [ + [ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, -Math.PI/2 ] ] + ], + E: [ + [ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.12, 2, 24 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ) ] + ], + XYZE: [ + [ new THREE.Mesh( new THREE.Geometry() ) ]// TODO + ] + }; + + this.setActivePlane = function ( axis ) { + + if ( axis == "E" ) this.activePlane = this.planes[ "XYZE" ]; + + if ( axis == "X" ) this.activePlane = this.planes[ "YZ" ]; + + if ( axis == "Y" ) this.activePlane = this.planes[ "XZ" ]; + + if ( axis == "Z" ) this.activePlane = this.planes[ "XY" ]; + + this.hide(); + this.show(); + + }; + + this.update = function ( rotation, eye2 ) { + + THREE.TransformGizmo.prototype.update.apply( this, arguments ); + + var group = { + handles: this["handles"], + pickers: this["pickers"], + }; + + var tempMatrix = new THREE.Matrix4(); + var worldRotation = new THREE.Euler( 0, 0, 1 ); + var tempQuaternion = new THREE.Quaternion(); + var unitX = new THREE.Vector3( 1, 0, 0 ); + var unitY = new THREE.Vector3( 0, 1, 0 ); + var unitZ = new THREE.Vector3( 0, 0, 1 ); + var quaternionX = new THREE.Quaternion(); + var quaternionY = new THREE.Quaternion(); + var quaternionZ = new THREE.Quaternion(); + var eye = eye2.clone(); + + worldRotation.copy( this.planes["XY"].rotation ); + tempQuaternion.setFromEuler( worldRotation ); + + tempMatrix.makeRotationFromQuaternion( tempQuaternion ).getInverse( tempMatrix ); + eye.applyMatrix4( tempMatrix ); + + this.traverse(function(child) { + + tempQuaternion.setFromEuler( worldRotation ); + + if ( child.name == "X" ) { + quaternionX.setFromAxisAngle( unitX, Math.atan2( -eye.y, eye.z ) ); + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); + child.quaternion.copy( tempQuaternion ); + } + + if ( child.name == "Y" ) { + quaternionY.setFromAxisAngle( unitY, Math.atan2( eye.x, eye.z ) ); + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY ); + child.quaternion.copy( tempQuaternion ); + } + + if ( child.name == "Z" ) { + quaternionZ.setFromAxisAngle( unitZ, Math.atan2( eye.y, eye.x ) ); + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ ); + child.quaternion.copy( tempQuaternion ); + } + + }); + + }; + + this.init(); + + }; + + THREE.TransformGizmoRotate.prototype = Object.create( THREE.TransformGizmo.prototype ); + + THREE.TransformGizmoScale = function () { + + THREE.TransformGizmo.call( this ); + + var arrowGeometry = new THREE.Geometry(); + var mesh = new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ) ); + mesh.position.y = 0.5; + mesh.updateMatrix(); + + arrowGeometry.merge( mesh.geometry, mesh.matrix ); + + var lineXGeometry = new THREE.Geometry(); + lineXGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ) ); + + var lineYGeometry = new THREE.Geometry(); + lineYGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) ); + + var lineZGeometry = new THREE.Geometry(); + lineZGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 1 ) ); + + this.handleGizmos = { + X: [ + [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI/2 ] ], + [ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ] + ], + Y: [ + [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ], + [ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ] + ], + Z: [ + [ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI/2, 0, 0 ] ], + [ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ] + ], + XYZ: [ + [ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ] + ] + }; + + this.pickerGizmos = { + X: [ + [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0xff0000, opacity: 0.25 } ) ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI/2 ] ] + ], + Y: [ + [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x00ff00, opacity: 0.25 } ) ), [ 0, 0.6, 0 ] ] + ], + Z: [ + [ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), new GizmoMaterial( { color: 0x0000ff, opacity: 0.25 } ) ), [ 0, 0, 0.6 ], [ Math.PI/2, 0, 0 ] ] + ], + XYZ: [ + [ new THREE.Mesh( new THREE.BoxGeometry( 0.4, 0.4, 0.4 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ] + ] + }; + + this.setActivePlane = function ( axis, eye ) { + + var tempMatrix = new THREE.Matrix4(); + eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) ); + + if ( axis == "X" ) { + this.activePlane = this.planes[ "XY" ]; + if ( Math.abs(eye.y) > Math.abs(eye.z) ) this.activePlane = this.planes[ "XZ" ]; + } + + if ( axis == "Y" ){ + this.activePlane = this.planes[ "XY" ]; + if ( Math.abs(eye.x) > Math.abs(eye.z) ) this.activePlane = this.planes[ "YZ" ]; + } + + if ( axis == "Z" ){ + this.activePlane = this.planes[ "XZ" ]; + if ( Math.abs(eye.x) > Math.abs(eye.y) ) this.activePlane = this.planes[ "YZ" ]; + } + + if ( axis == "XYZ" ) this.activePlane = this.planes[ "XYZE" ]; + + this.hide(); + this.show(); + + }; + + this.init(); + + }; + + THREE.TransformGizmoScale.prototype = Object.create( THREE.TransformGizmo.prototype ); + + THREE.TransformControls = function ( camera, domElement ) { + + // TODO: Make non-uniform scale and rotate play nice in hierarchies + // TODO: ADD RXYZ contol + + THREE.Object3D.call( this ); + + domElement = ( domElement !== undefined ) ? domElement : document; + + this.gizmo = {}; + this.gizmo["translate"] = new THREE.TransformGizmoTranslate(); + this.gizmo["rotate"] = new THREE.TransformGizmoRotate(); + this.gizmo["scale"] = new THREE.TransformGizmoScale(); + + this.add(this.gizmo["translate"]); + this.add(this.gizmo["rotate"]); + this.add(this.gizmo["scale"]); + + this.gizmo["translate"].hide(); + this.gizmo["rotate"].hide(); + this.gizmo["scale"].hide(); + + this.object = undefined; + this.snap = null; + this.space = "world"; + this.size = 1; + this.axis = null; + + var scope = this; + + var _dragging = false; + var _mode = "translate"; + var _plane = "XY"; + + var changeEvent = { type: "change" }; + + var ray = new THREE.Raycaster(); + var projector = new THREE.Projector(); + var pointerVector = new THREE.Vector3(); + + var point = new THREE.Vector3(); + var offset = new THREE.Vector3(); + + var rotation = new THREE.Vector3(); + var offsetRotation = new THREE.Vector3(); + var scale = 1; + + var lookAtMatrix = new THREE.Matrix4(); + var eye = new THREE.Vector3(); + + var tempMatrix = new THREE.Matrix4(); + var tempVector = new THREE.Vector3(); + var tempQuaternion = new THREE.Quaternion(); + var unitX = new THREE.Vector3( 1, 0, 0 ); + var unitY = new THREE.Vector3( 0, 1, 0 ); + var unitZ = new THREE.Vector3( 0, 0, 1 ); + + var quaternionXYZ = new THREE.Quaternion(); + var quaternionX = new THREE.Quaternion(); + var quaternionY = new THREE.Quaternion(); + var quaternionZ = new THREE.Quaternion(); + var quaternionE = new THREE.Quaternion(); + + var oldPosition = new THREE.Vector3(); + var oldScale = new THREE.Vector3(); + var oldRotationMatrix = new THREE.Matrix4(); + + var parentRotationMatrix = new THREE.Matrix4(); + var parentScale = new THREE.Vector3(); + + var worldPosition = new THREE.Vector3(); + var worldRotation = new THREE.Euler(); + var worldRotationMatrix = new THREE.Matrix4(); + var camPosition = new THREE.Vector3(); + var camRotation = new THREE.Euler(); + + domElement.addEventListener( "mousedown", onPointerDown, false ); + domElement.addEventListener( "touchstart", onPointerDown, false ); + + domElement.addEventListener( "mousemove", onPointerHover, false ); + domElement.addEventListener( "touchmove", onPointerHover, false ); + + domElement.addEventListener( "mousemove", onPointerMove, false ); + domElement.addEventListener( "touchmove", onPointerMove, false ); + + domElement.addEventListener( "mouseup", onPointerUp, false ); + domElement.addEventListener( "mouseout", onPointerUp, false ); + domElement.addEventListener( "touchend", onPointerUp, false ); + domElement.addEventListener( "touchcancel", onPointerUp, false ); + domElement.addEventListener( "touchleave", onPointerUp, false ); + + this.attach = function ( object ) { + + scope.object = object; + + this.gizmo["translate"].hide(); + this.gizmo["rotate"].hide(); + this.gizmo["scale"].hide(); + this.gizmo[_mode].show(); + + scope.update(); + + }; + + this.detach = function ( object ) { + + scope.object = undefined; + this.axis = undefined; + + this.gizmo["translate"].hide(); + this.gizmo["rotate"].hide(); + this.gizmo["scale"].hide(); + + }; + + this.setMode = function ( mode ) { + + _mode = mode ? mode : _mode; + + if ( _mode == "scale" ) scope.space = "local"; + + this.gizmo["translate"].hide(); + this.gizmo["rotate"].hide(); + this.gizmo["scale"].hide(); + this.gizmo[_mode].show(); + + this.update(); + scope.dispatchEvent( changeEvent ); + + }; + + this.setSnap = function ( snap ) { + + scope.snap = snap; + + }; + + this.setSize = function ( size ) { + + scope.size = size; + this.update(); + scope.dispatchEvent( changeEvent ); + + }; + + this.setSpace = function ( space ) { + + scope.space = space; + this.update(); + scope.dispatchEvent( changeEvent ); + + }; + + this.update = function () { + + if ( scope.object === undefined ) return; + + scope.object.updateMatrixWorld(); + worldPosition.setFromMatrixPosition( scope.object.matrixWorld ); + worldRotation.setFromRotationMatrix( tempMatrix.extractRotation( scope.object.matrixWorld ) ); + + camera.updateMatrixWorld(); + camPosition.setFromMatrixPosition( camera.matrixWorld ); + camRotation.setFromRotationMatrix( tempMatrix.extractRotation( camera.matrixWorld ) ); + + scale = worldPosition.distanceTo( camPosition ) / 6 * scope.size; + this.position.copy( worldPosition ); + this.scale.set( scale, scale, scale ); + + eye.copy( camPosition ).sub( worldPosition ).normalize(); + + if ( scope.space == "local" ) + this.gizmo[_mode].update( worldRotation, eye ); + + else if ( scope.space == "world" ) + this.gizmo[_mode].update( new THREE.Euler(), eye ); + + this.gizmo[_mode].highlight( scope.axis ); + + }; + + function onPointerHover( event ) { + + if ( scope.object === undefined || _dragging === true ) return; + + event.preventDefault(); + + var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; + + var intersect = intersectObjects( pointer, scope.gizmo[_mode].pickers.children ); + + if ( intersect ) { + + scope.axis = intersect.object.name; + scope.update(); + scope.dispatchEvent( changeEvent ); + + } else if ( scope.axis !== null ) { + + scope.axis = null; + scope.update(); + scope.dispatchEvent( changeEvent ); + + } + + } + + function onPointerDown( event ) { + + if ( scope.object === undefined || _dragging === true ) return; + + event.preventDefault(); + event.stopPropagation(); + + var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event; + + if ( pointer.button === 0 || pointer.button === undefined ) { + + var intersect = intersectObjects( pointer, scope.gizmo[_mode].pickers.children ); + + if ( intersect ) { + + scope.axis = intersect.object.name; + + scope.update(); + + eye.copy( camPosition ).sub( worldPosition ).normalize(); + + scope.gizmo[_mode].setActivePlane( scope.axis, eye ); + + var planeIntersect = intersectObjects( pointer, [scope.gizmo[_mode].activePlane] ); + + oldPosition.copy( scope.object.position ); + oldScale.copy( scope.object.scale ); + + oldRotationMatrix.extractRotation( scope.object.matrix ); + worldRotationMatrix.extractRotation( scope.object.matrixWorld ); + + parentRotationMatrix.extractRotation( scope.object.parent.matrixWorld ); + parentScale.setFromMatrixScale( tempMatrix.getInverse( scope.object.parent.matrixWorld ) ); + + offset.copy( planeIntersect.point ); + + } + + } + + _dragging = true; + + } + + function onPointerMove( event ) { + + if ( scope.object === undefined || scope.axis === null || _dragging === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var pointer = event.changedTouches? event.changedTouches[0] : event; + + var planeIntersect = intersectObjects( pointer, [scope.gizmo[_mode].activePlane] ); + + point.copy( planeIntersect.point ); + + if ( _mode == "translate" ) { + + point.sub( offset ); + point.multiply(parentScale); + + if ( scope.space == "local" ) { + + point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); + + if ( scope.axis.search("X") == -1 ) point.x = 0; + if ( scope.axis.search("Y") == -1 ) point.y = 0; + if ( scope.axis.search("Z") == -1 ) point.z = 0; + + point.applyMatrix4( oldRotationMatrix ); + + scope.object.position.copy( oldPosition ); + scope.object.position.add( point ); + + } + + if ( scope.space == "world" || scope.axis.search("XYZ") != -1 ) { + + if ( scope.axis.search("X") == -1 ) point.x = 0; + if ( scope.axis.search("Y") == -1 ) point.y = 0; + if ( scope.axis.search("Z") == -1 ) point.z = 0; + + point.applyMatrix4( tempMatrix.getInverse( parentRotationMatrix ) ); + + scope.object.position.copy( oldPosition ); + scope.object.position.add( point ); + + } + + if ( scope.snap !== null ) { + + if ( scope.axis.search("X") != -1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.snap ) * scope.snap; + if ( scope.axis.search("Y") != -1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.snap ) * scope.snap; + if ( scope.axis.search("Z") != -1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.snap ) * scope.snap; + + } + + } else if ( _mode == "scale" ) { + + point.sub( offset ); + point.multiply(parentScale); + + if ( scope.space == "local" ) { + + if ( scope.axis == "XYZ") { + + scale = 1 + ( ( point.y ) / 50 ); + + scope.object.scale.x = oldScale.x * scale; + scope.object.scale.y = oldScale.y * scale; + scope.object.scale.z = oldScale.z * scale; + + } else { + + point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); + + if ( scope.axis == "X" ) scope.object.scale.x = oldScale.x * ( 1 + point.x / 50 ); + if ( scope.axis == "Y" ) scope.object.scale.y = oldScale.y * ( 1 + point.y / 50 ); + if ( scope.axis == "Z" ) scope.object.scale.z = oldScale.z * ( 1 + point.z / 50 ); + + } + + } + + } else if ( _mode == "rotate" ) { + + point.sub( worldPosition ); + point.multiply(parentScale); + tempVector.copy(offset).sub( worldPosition ); + tempVector.multiply(parentScale); + + if ( scope.axis == "E" ) { + + point.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) ); + tempVector.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) ); + + rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); + offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); + + tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); + + quaternionE.setFromAxisAngle( eye, rotation.z - offsetRotation.z ); + quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); + + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionE ); + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); + + scope.object.quaternion.copy( tempQuaternion ); + + } else if ( scope.axis == "XYZE" ) { + + quaternionE.setFromEuler( point.clone().cross(tempVector).normalize() ); // rotation axis + + tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); + quaternionX.setFromAxisAngle( quaternionE, - point.clone().angleTo(tempVector) ); + quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); + + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); + + scope.object.quaternion.copy( tempQuaternion ); + + } else if ( scope.space == "local" ) { + + point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); + + tempVector.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) ); + + rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); + offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); + + quaternionXYZ.setFromRotationMatrix( oldRotationMatrix ); + quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x ); + quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y ); + quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z ); + + if ( scope.axis == "X" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX ); + if ( scope.axis == "Y" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY ); + if ( scope.axis == "Z" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionZ ); + + scope.object.quaternion.copy( quaternionXYZ ); + + } else if ( scope.space == "world" ) { + + rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) ); + offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) ); + + tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) ); + + quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x ); + quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y ); + quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z ); + quaternionXYZ.setFromRotationMatrix( worldRotationMatrix ); + + if ( scope.axis == "X" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX ); + if ( scope.axis == "Y" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY ); + if ( scope.axis == "Z" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ ); + + tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ ); + + scope.object.quaternion.copy( tempQuaternion ); + + } + + } + + scope.update(); + scope.dispatchEvent( changeEvent ); + + } + + function onPointerUp( event ) { + + _dragging = false; + onPointerHover( event ); + + } + + function intersectObjects( pointer, objects ) { + + var rect = domElement.getBoundingClientRect(); + var x = (pointer.clientX - rect.left) / rect.width; + var y = (pointer.clientY - rect.top) / rect.height; + pointerVector.set( ( x ) * 2 - 1, - ( y ) * 2 + 1, 0.5 ); + + projector.unprojectVector( pointerVector, camera ); + ray.set( camPosition, pointerVector.sub( camPosition ).normalize() ); + + var intersections = ray.intersectObjects( objects, true ); + return intersections[0] ? intersections[0] : false; + + } + + }; + + THREE.TransformControls.prototype = Object.create( THREE.Object3D.prototype ); + +}()); \ No newline at end of file