diff --git a/js/.jshintrc b/js/.jshintrc index 7a4e69ed..34520db6 100644 --- a/js/.jshintrc +++ b/js/.jshintrc @@ -25,7 +25,6 @@ "globals": { "$": true, "K3D": true, - "THREE": true, "_": true, "ImageData": true, "jsonpatch": true, diff --git a/js/package.json b/js/package.json index b27b6903..69c9962b 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "k3d", - "version": "2.6.1", + "version": "2.6.2", "description": "3D visualization library", "author": "k3d team", "main": "src/index.js", @@ -81,7 +81,6 @@ "requirejs": "^2.3.6", "screenfull": "^4.1.0", "stats.js": "^0.17.0", - "three": "^0.102.1", - "three-octree": "^0.6.1" + "three": "^0.102.1" } } diff --git a/js/src/core/Core.js b/js/src/core/Core.js index 73daabdf..154d3cd6 100644 --- a/js/src/core/Core.js +++ b/js/src/core/Core.js @@ -97,10 +97,9 @@ function K3D(provider, targetDOMNode, parameters) { if (!self.disabling) { self.gui.domElement.parentNode.style['max-height'] = world.targetDOMNode.offsetHeight + 'px'; self.Provider.Helpers.resizeListener(world); + dispatch(self.events.RESIZED); self.render(); } - - dispatch(self.events.RESIZED); }; world.overlayDOMNode = currentWindow.document.createElement('div'); @@ -772,13 +771,6 @@ function K3D(provider, targetDOMNode, parameters) { GUI.info.__controllers[1].__input.readOnly = true; } - if (self.parameters.specVersion) { - GUI.info.add({ - version: self.parameters.specVersion.substr(1) - }, 'version').name('Spec version:'); - GUI.info.__controllers[1].__input.readOnly = true; - } - self.setMenuVisibility(self.parameters.menuVisibility); self.setTime(self.parameters.time); self.setGridAutoFit(self.parameters.gridAutoFit); diff --git a/js/src/core/lib/clippingPlanesGUIProvider.js b/js/src/core/lib/clippingPlanesGUIProvider.js index 67bda74e..de7ef507 100644 --- a/js/src/core/lib/clippingPlanesGUIProvider.js +++ b/js/src/core/lib/clippingPlanesGUIProvider.js @@ -1,5 +1,7 @@ 'use strict'; +var THREE = require('three'); + function clippingPlanesGUIProvider(K3D, clippingPlanesGUI) { function dispatch() { diff --git a/js/src/core/lib/colorMapLegend.js b/js/src/core/lib/colorMapLegend.js index cf5d3509..9215cf6f 100644 --- a/js/src/core/lib/colorMapLegend.js +++ b/js/src/core/lib/colorMapLegend.js @@ -60,6 +60,7 @@ module.exports = function getColorLegend(K3D, object) { intervalOffset, intervalCount = 0, strokeWidth = 0.5, + resizeListenerId = null, i, y; if (K3D.colorMapNode) { @@ -152,13 +153,30 @@ module.exports = function getColorLegend(K3D, object) { K3D.getWorld().targetDOMNode.appendChild(svg); - maxTextWidth = texts.reduce(function (max, text) { - return Math.max(max, text.getBBox().width); - }, 0); + function tryPosLabels() { + if (K3D.getWorld().width < 10 || K3D.getWorld().height < 10) { + if (resizeListenerId === null) { + resizeListenerId = K3D.on(K3D.events.RESIZED, function () { + tryPosLabels(); + }); + } + } else { + if (resizeListenerId !== null) { + K3D.off(K3D.events.RESIZED, resizeListenerId); + resizeListenerId = null; + } + + maxTextWidth = texts.reduce(function (max, text) { + return Math.max(max, text.getBBox().width); + }, 0); + + texts.forEach(function (text) { + text.setAttribute('x', (maxTextWidth + 20).toString(10)); + }); + } + } - texts.forEach(function (text) { - text.setAttribute('x', (maxTextWidth + 20).toString(10)); - }); + tryPosLabels(); K3D.colorMapNode = svg; }; diff --git a/js/src/k3d.js b/js/src/k3d.js index 334217fc..afff9237 100644 --- a/js/src/k3d.js +++ b/js/src/k3d.js @@ -1,4 +1,5 @@ 'use strict'; +//jshint maxstatements:false var widgets = require('@jupyter-widgets/base'), K3D = require('./core/Core'), @@ -9,7 +10,7 @@ var widgets = require('@jupyter-widgets/base'), ChunkModel, ObjectModel, ObjectView, - semverRange = require('./version').EXTENSION_SPEC_VERSION, + semverRange = require('./version').version, objectsList = {}, chunkList = {}, plotsList = []; @@ -212,7 +213,6 @@ PlotView = widgets.DOMWidgetView.extend({ try { this.K3DInstance = new K3D(ThreeJsProvider, this.container, { antialias: this.model.get('antialias'), - specVersion: this.model.get('_view_module_version'), backendVersion: this.model.get('_backend_version'), screenshotScale: this.model.get('screenshot_scale'), menuVisibility: this.model.get('menu_visibility'), diff --git a/js/src/labplugin.js b/js/src/labplugin.js index 5f8cfe81..c05e942f 100644 --- a/js/src/labplugin.js +++ b/js/src/labplugin.js @@ -5,7 +5,7 @@ require('./deps'); var k3d = require('./index'), - EXTENSION_SPEC_VERSION = require('./version').EXTENSION_SPEC_VERSION, + version = require('./version').version, base = require('@jupyter-widgets/base'); module.exports = { @@ -14,7 +14,7 @@ module.exports = { activate: function (app, widgets) { widgets.registerWidget({ name: 'k3d', - version: EXTENSION_SPEC_VERSION, + version: version, exports: k3d }); }, diff --git a/js/src/providers/threejs/helpers/Fn.js b/js/src/providers/threejs/helpers/Fn.js index d0eff779..b5455c1f 100644 --- a/js/src/providers/threejs/helpers/Fn.js +++ b/js/src/providers/threejs/helpers/Fn.js @@ -1,5 +1,6 @@ 'use strict'; -var lut = require('./../../../core/lib/helpers/lut'), +var THREE = require('three'), + lut = require('./../../../core/lib/helpers/lut'), Float16Array = require('./../../../core/lib/helpers/float16Array'); function getSpaceDimensionsFromTargetElement(world) { diff --git a/js/src/providers/threejs/helpers/SSAAChunkedRender.js b/js/src/providers/threejs/helpers/SSAAChunkedRender.js index f5265b85..ebfb0b6f 100644 --- a/js/src/providers/threejs/helpers/SSAAChunkedRender.js +++ b/js/src/providers/threejs/helpers/SSAAChunkedRender.js @@ -6,37 +6,38 @@ // // Sample patterns reference: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476218%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 -var JitterVectors = [ - [ - [0, 0] - ], - [ - [4, 4], [-4, -4] - ], - [ - [-2, -6], [6, -2], [-6, 2], [2, 6] - ], - [ - [1, -3], [-1, 3], [5, 1], [-3, -5], - [-5, 5], [-7, -1], [3, 7], [7, -7] - ], - [ - [1, 1], [-1, -3], [-3, 2], [4, -1], - [-5, -2], [2, 5], [5, 3], [3, -5], - [-2, 6], [0, -7], [-4, -6], [-6, 4], - [-8, 0], [7, -4], [6, 7], [-7, -8] - ], - [ - [-4, -7], [-7, -5], [-3, -5], [-5, -4], - [-1, -4], [-2, -2], [-6, -1], [-4, 0], - [-7, 1], [-1, 2], [-6, 3], [-3, 3], - [-7, 6], [-3, 6], [-5, 7], [-1, 7], - [5, -7], [1, -6], [6, -5], [4, -4], - [2, -3], [7, -2], [1, -1], [4, -1], - [2, 1], [6, 2], [0, 4], [4, 4], - [2, 5], [7, 5], [5, 6], [3, 7] - ] -]; +var THREE = require('three'), + JitterVectors = [ + [ + [0, 0] + ], + [ + [4, 4], [-4, -4] + ], + [ + [-2, -6], [6, -2], [-6, 2], [2, 6] + ], + [ + [1, -3], [-1, 3], [5, 1], [-3, -5], + [-5, 5], [-7, -1], [3, 7], [7, -7] + ], + [ + [1, 1], [-1, -3], [-3, 2], [4, -1], + [-5, -2], [2, 5], [5, 3], [3, -5], + [-2, 6], [0, -7], [-4, -6], [-6, 4], + [-8, 0], [7, -4], [6, 7], [-7, -8] + ], + [ + [-4, -7], [-7, -5], [-3, -5], [-5, -4], + [-1, -4], [-2, -2], [-6, -1], [-4, 0], + [-7, 1], [-1, 2], [-6, 3], [-3, 3], + [-7, 6], [-3, 6], [-5, 7], [-1, 7], + [5, -7], [1, -6], [6, -5], [4, -4], + [2, -3], [7, -2], [1, -1], [4, -1], + [2, 1], [6, 2], [0, 4], [4, 4], + [2, 5], [7, 5], [5, 6], [3, 7] + ] + ]; function getArrayFromRenderTarget(renderer, rt) { var array = new Uint8Array(rt.width * rt.height * 4); diff --git a/js/src/providers/threejs/helpers/Streamline.js b/js/src/providers/threejs/helpers/Streamline.js index 48324d25..5062898e 100644 --- a/js/src/providers/threejs/helpers/Streamline.js +++ b/js/src/providers/threejs/helpers/Streamline.js @@ -1,6 +1,8 @@ 'use strict'; //jshint maxstatements:false +var THREE = require('three'); + module.exports = function (points, attributes, radius, radialSegments, color, verticesColors, colorRange) { var geometry = new THREE.BufferGeometry(), diff --git a/js/src/providers/threejs/helpers/THREE.CopyShader.js b/js/src/providers/threejs/helpers/THREE.CopyShader.js new file mode 100644 index 00000000..821aa57e --- /dev/null +++ b/js/src/providers/threejs/helpers/THREE.CopyShader.js @@ -0,0 +1,53 @@ +'use strict'; +// jshint ignore: start +// jscs:disable + +module.exports = function (THREE) { + /** + * @author alteredq / http://alteredqualia.com/ + * + * Full-screen textured quad shader + */ + + THREE.CopyShader = { + + uniforms: { + + "tDiffuse": { value: null }, + "opacity": { value: 1.0 } + + }, + + vertexShader: [ + + "varying vec2 vUv;", + + "void main() {", + + "vUv = uv;", + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join( "\n" ), + + fragmentShader: [ + + "uniform float opacity;", + + "uniform sampler2D tDiffuse;", + + "varying vec2 vUv;", + + "void main() {", + + "vec4 texel = texture2D( tDiffuse, vUv );", + "gl_FragColor = opacity * texel;", + + "}" + + ].join( "\n" ) + + }; + +}; diff --git a/js/src/providers/threejs/helpers/THREE.MeshLine.js b/js/src/providers/threejs/helpers/THREE.MeshLine.js index 707cd384..9b89bdd2 100644 --- a/js/src/providers/threejs/helpers/THREE.MeshLine.js +++ b/js/src/providers/threejs/helpers/THREE.MeshLine.js @@ -1,16 +1,8 @@ -//jshint maxstatements:false, maxcomplexity:false, maxdepth:false - -;(function () { - 'use strict'; - - var root = this, - has_require = typeof require !== 'undefined', - THREE = root.THREE || has_require && require('three'); - - if (!THREE) { - throw new Error('MeshLine requires three.js'); - } +'use strict'; +//jshint ignore: start +//jscs:disable +module.exports = function (THREE) { function MeshLine() { this.positions = []; this.previous = []; @@ -393,14 +385,8 @@ return this; }; - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = {MeshLine: MeshLine, MeshLineMaterial: MeshLineMaterial}; - } - exports.MeshLine = MeshLine; - exports.MeshLineMaterial = MeshLineMaterial; - } else { - root.MeshLine = MeshLine; - root.MeshLineMaterial = MeshLineMaterial; - } -}).call(this); + return { + MeshLine: MeshLine, + MeshLineMaterial: MeshLineMaterial + }; +}; diff --git a/js/src/providers/threejs/helpers/THREE.Octree.js b/js/src/providers/threejs/helpers/THREE.Octree.js new file mode 100644 index 00000000..97892852 --- /dev/null +++ b/js/src/providers/threejs/helpers/THREE.Octree.js @@ -0,0 +1,2248 @@ +'use strict'; +// jshint ignore: start +// jscs:disable + +module.exports = function (THREE) { + /*! + * + * threeoctree.js (r60) / https://github.com/collinhover/threeoctree + * (sparse) dynamic 3D spatial representation structure for fast searches. + * + * @author Collin Hover / http://collinhover.com/ + * based on Dynamic Octree by Piko3D @ http://www.piko3d.com/ and Octree by Marek Pawlowski @ pawlowski.it + * + */ + + /*=================================================== + + utility + + =====================================================*/ + + function isNumber(n) { + return !isNaN(n) && isFinite(n); + } + + function isArray(target) { + return Object.prototype.toString.call(target) === '[object Array]'; + } + + function toArray(target) { + return target ? (isArray(target) !== true ? [target] : target) : []; + } + + function indexOfValue(array, value) { + + for (var i = 0, il = array.length; i < il; i++) { + + if (array[i] === value) { + + return i; + + } + + } + + return -1; + + } + + function indexOfPropertyWithValue(array, property, value) { + + for (var i = 0, il = array.length; i < il; i++) { + + if (array[i][property] === value) { + + return i; + + } + + } + + return -1; + + } + + /*=================================================== + + octree + + =====================================================*/ + + THREE.Octree = function (parameters) { + + // handle parameters + + parameters = parameters || {}; + + parameters.tree = this; + + // static properties ( modification is not recommended ) + + this.nodeCount = 0; + + this.INDEX_INSIDE_CROSS = -1; + this.INDEX_OUTSIDE_OFFSET = 2; + + this.INDEX_OUTSIDE_POS_X = isNumber(parameters.INDEX_OUTSIDE_POS_X) ? parameters.INDEX_OUTSIDE_POS_X : 0; + this.INDEX_OUTSIDE_NEG_X = isNumber(parameters.INDEX_OUTSIDE_NEG_X) ? parameters.INDEX_OUTSIDE_NEG_X : 1; + this.INDEX_OUTSIDE_POS_Y = isNumber(parameters.INDEX_OUTSIDE_POS_Y) ? parameters.INDEX_OUTSIDE_POS_Y : 2; + this.INDEX_OUTSIDE_NEG_Y = isNumber(parameters.INDEX_OUTSIDE_NEG_Y) ? parameters.INDEX_OUTSIDE_NEG_Y : 3; + this.INDEX_OUTSIDE_POS_Z = isNumber(parameters.INDEX_OUTSIDE_POS_Z) ? parameters.INDEX_OUTSIDE_POS_Z : 4; + this.INDEX_OUTSIDE_NEG_Z = isNumber(parameters.INDEX_OUTSIDE_NEG_Z) ? parameters.INDEX_OUTSIDE_NEG_Z : 5; + + this.INDEX_OUTSIDE_MAP = []; + this.INDEX_OUTSIDE_MAP[this.INDEX_OUTSIDE_POS_X] = { + index: this.INDEX_OUTSIDE_POS_X, + count: 0, + x: 1, + y: 0, + z: 0 + }; + this.INDEX_OUTSIDE_MAP[this.INDEX_OUTSIDE_NEG_X] = { + index: this.INDEX_OUTSIDE_NEG_X, + count: 0, + x: -1, + y: 0, + z: 0 + }; + this.INDEX_OUTSIDE_MAP[this.INDEX_OUTSIDE_POS_Y] = { + index: this.INDEX_OUTSIDE_POS_Y, + count: 0, + x: 0, + y: 1, + z: 0 + }; + this.INDEX_OUTSIDE_MAP[this.INDEX_OUTSIDE_NEG_Y] = { + index: this.INDEX_OUTSIDE_NEG_Y, + count: 0, + x: 0, + y: -1, + z: 0 + }; + this.INDEX_OUTSIDE_MAP[this.INDEX_OUTSIDE_POS_Z] = { + index: this.INDEX_OUTSIDE_POS_Z, + count: 0, + x: 0, + y: 0, + z: 1 + }; + this.INDEX_OUTSIDE_MAP[this.INDEX_OUTSIDE_NEG_Z] = { + index: this.INDEX_OUTSIDE_NEG_Z, + count: 0, + x: 0, + y: 0, + z: -1 + }; + + this.FLAG_POS_X = 1 << (this.INDEX_OUTSIDE_POS_X + 1); + this.FLAG_NEG_X = 1 << (this.INDEX_OUTSIDE_NEG_X + 1); + this.FLAG_POS_Y = 1 << (this.INDEX_OUTSIDE_POS_Y + 1); + this.FLAG_NEG_Y = 1 << (this.INDEX_OUTSIDE_NEG_Y + 1); + this.FLAG_POS_Z = 1 << (this.INDEX_OUTSIDE_POS_Z + 1); + this.FLAG_NEG_Z = 1 << (this.INDEX_OUTSIDE_NEG_Z + 1); + + this.utilVec31Search = new THREE.Vector3(); + this.utilVec32Search = new THREE.Vector3(); + + // pass scene to see octree structure + + this.scene = parameters.scene; + + if (this.scene) { + + this.visualGeometry = new THREE.CubeGeometry(1, 1, 1); + this.visualMaterial = new THREE.MeshBasicMaterial({ + color: 0xFF0066, + wireframe: true, + wireframeLinewidth: 1 + }); + + } + + // properties + + this.objects = []; + this.objectsMap = {}; + this.objectsData = []; + this.objectsDeferred = []; + + this.depthMax = isNumber(parameters.depthMax) ? parameters.depthMax : Infinity; + this.objectsThreshold = isNumber(parameters.objectsThreshold) ? parameters.objectsThreshold : 8; + this.overlapPct = isNumber(parameters.overlapPct) ? parameters.overlapPct : 0.15; + this.undeferred = parameters.undeferred || false; + + this.root = parameters.root instanceof THREE.OctreeNode ? parameters.root : new THREE.OctreeNode(parameters); + + }; + + THREE.Octree.prototype = { + + update: function () { + + // add any deferred objects that were waiting for render cycle + + if (this.objectsDeferred.length > 0) { + + for (var i = 0, il = this.objectsDeferred.length; i < il; i++) { + + var deferred = this.objectsDeferred[i]; + + this.addDeferred(deferred.object, deferred.options); + + } + + this.objectsDeferred.length = 0; + + } + + }, + + add: function (object, options) { + + // add immediately + + if (this.undeferred) { + + this.updateObject(object); + + this.addDeferred(object, options); + + } else { + + // defer add until update called + + this.objectsDeferred.push({object: object, options: options}); + + } + + }, + + addDeferred: function (object, options) { + + var i, l, + geometry, + faces, + useFaces, + vertices, + useVertices; + + // ensure object is not object data + + if (object instanceof THREE.OctreeObjectData) { + + object = object.object; + + } + + // check uuid to avoid duplicates + + if (!object.uuid) { + + object.uuid = THREE.Math.generateUUID(); + + } + + if (!this.objectsMap[object.uuid]) { + + // store + + this.objects.push(object); + this.objectsMap[object.uuid] = object; + + // check options + + if (options) { + + useFaces = options.useFaces; + useVertices = options.useVertices; + + } + + if (useVertices === true) { + + geometry = object.geometry; + vertices = geometry.vertices; + + for (i = 0, l = vertices.length; i < l; i++) { + + this.addObjectData(object, vertices[i]); + + } + + } else if (useFaces === true) { + + geometry = object.geometry; + faces = geometry.faces; + + for (i = 0, l = faces.length; i < l; i++) { + + this.addObjectData(object, faces[i]); + + } + + } else { + + this.addObjectData(object); + + } + + } + + }, + + addObjectData: function (object, part) { + + var objectData = new THREE.OctreeObjectData(object, part); + + // add to tree objects data list + + this.objectsData.push(objectData); + + // add to nodes + + this.root.addObject(objectData); + + }, + + remove: function (object) { + + var i, l, + objectData = object, + index, + objectsDataRemoved; + + // ensure object is not object data for index search + + if (object instanceof THREE.OctreeObjectData) { + + object = object.object; + + } + + // check uuid + + if (this.objectsMap[object.uuid]) { + + this.objectsMap[object.uuid] = undefined; + + // check and remove from objects, nodes, and data lists + + index = indexOfValue(this.objects, object); + + if (index !== -1) { + + this.objects.splice(index, 1); + + // remove from nodes + + objectsDataRemoved = this.root.removeObject(objectData); + + // remove from objects data list + + for (i = 0, l = objectsDataRemoved.length; i < l; i++) { + + objectData = objectsDataRemoved[i]; + + index = indexOfValue(this.objectsData, objectData); + + if (index !== -1) { + + this.objectsData.splice(index, 1); + + } + + } + + } + + } else if (this.objectsDeferred.length > 0) { + + // check and remove from deferred + + index = indexOfPropertyWithValue(this.objectsDeferred, 'object', object); + + if (index !== -1) { + + this.objectsDeferred.splice(index, 1); + + } + + } + + }, + + extend: function (octree) { + + var i, l, + objectsData, + objectData; + + if (octree instanceof THREE.Octree) { + + // for each object data + + objectsData = octree.objectsData; + + for (i = 0, l = objectsData.length; i < l; i++) { + + objectData = objectsData[i]; + + this.add(objectData, {useFaces: objectData.faces, useVertices: objectData.vertices}); + + } + + } + + }, + + rebuild: function () { + + var i, l, + node, + objectData, + indexOctant, + indexOctantLast, + objectsUpdate = []; + + // check all object data for changes in position + // assumes all object matrices are up to date + + for (i = 0, l = this.objectsData.length; i < l; i++) { + + objectData = this.objectsData[i]; + + node = objectData.node; + + // update object + + objectData.update(); + + // if position has changed since last organization of object in tree + + if (node instanceof THREE.OctreeNode && !objectData.positionLast.equals(objectData.position)) { + + // get octant index of object within current node + + indexOctantLast = objectData.indexOctant; + + indexOctant = node.getOctantIndex(objectData); + + // if object octant index has changed + + if (indexOctant !== indexOctantLast) { + + // add to update list + + objectsUpdate.push(objectData); + + } + + } + + } + + // update changed objects + + for (i = 0, l = objectsUpdate.length; i < l; i++) { + + objectData = objectsUpdate[i]; + + // remove object from current node + + objectData.node.removeObject(objectData); + + // add object to tree root + + this.root.addObject(objectData); + + } + + }, + + updateObject: function (object) { + + var i, l, + parentCascade = [object], + parent, + parentUpdate; + + // search all parents between object and root for world matrix update + + parent = object.parent; + + while (parent) { + + parentCascade.push(parent); + parent = parent.parent; + + } + + for (i = 0, l = parentCascade.length; i < l; i++) { + + parent = parentCascade[i]; + + if (parent.matrixWorldNeedsUpdate === true) { + + parentUpdate = parent; + + } + + } + + // update world matrix starting at uppermost parent that needs update + + if (typeof parentUpdate !== 'undefined') { + + parentUpdate.updateMatrixWorld(); + + } + + }, + + search: function (position, radius, organizeByObject, direction) { + + var i, l, + node, + objects, + objectData, + object, + results, + resultData, + resultsObjectsIndices, + resultObjectIndex, + directionPct; + + // add root objects + + objects = [].concat(this.root.objects); + + // ensure radius (i.e. distance of ray) is a number + + if (!(radius > 0)) { + + radius = Number.MAX_VALUE; + + } + + // if direction passed, normalize and find pct + + if (direction instanceof THREE.Vector3) { + + direction = this.utilVec31Search.copy(direction).normalize(); + directionPct = this.utilVec32Search.set(1, 1, 1).divide(direction); + + } + + // search each node of root + + for (i = 0, l = this.root.nodesIndices.length; i < l; i++) { + + node = this.root.nodesByIndex[this.root.nodesIndices[i]]; + + objects = node.search(position, radius, objects, direction, directionPct); + + } + + // if should organize results by object + + if (organizeByObject === true) { + + results = []; + resultsObjectsIndices = []; + + // for each object data found + + for (i = 0, l = objects.length; i < l; i++) { + + objectData = objects[i]; + object = objectData.object; + + resultObjectIndex = indexOfValue(resultsObjectsIndices, object); + + // if needed, create new result data + + if (resultObjectIndex === -1) { + + resultData = { + object: object, + faces: [], + vertices: [] + }; + + results.push(resultData); + + resultsObjectsIndices.push(object); + + } else { + + resultData = results[resultObjectIndex]; + + } + + // object data has faces or vertices, add to list + + if (objectData.faces) { + + resultData.faces.push(objectData.faces); + + } else if (objectData.vertices) { + + resultData.vertices.push(objectData.vertices); + + } + + } + + } else { + + results = objects; + + } + + return results; + + }, + + findClosestVertex: function (position, radius) { + + var search = this.search(position, radius, true); + + if (!search[0]) { + + return null; + + } + + var object = search[0].object, + vertices = search[0].vertices; + + if (vertices.length === 0) { + + return null; + + } + + var distance, + vertex = null, + localPosition = object.worldToLocal(position.clone()); + + for (var i = 0, il = vertices.length; i < il; i++) { + + distance = vertices[i].distanceTo(localPosition); + + if (distance > radius) { + + continue; + + } + + // use distance in new comparison to find the closest point + + radius = distance; + + vertex = vertices[i]; + + } + + if (vertex === null) { + + return null; + + } + + return object.localToWorld(vertex.clone()); + + }, + + setRoot: function (root) { + + if (root instanceof THREE.OctreeNode) { + + // store new root + + this.root = root; + + // update properties + + this.root.updateProperties(); + + } + + }, + + getDepthEnd: function () { + + return this.root.getDepthEnd(); + + }, + + getNodeCountEnd: function () { + + return this.root.getNodeCountEnd(); + + }, + + getObjectCountEnd: function () { + + return this.root.getObjectCountEnd(); + + }, + + toConsole: function () { + + this.root.toConsole(); + + } + + }; + + /*=================================================== + + object data + + =====================================================*/ + + THREE.OctreeObjectData = function (object, part) { + + // properties + + this.object = object; + + // handle part by type + + if (part instanceof THREE.Face3) { + + this.faces = part; + this.face3 = true; + this.utilVec31FaceBounds = new THREE.Vector3(); + + } else if (part instanceof THREE.Face4) { + + this.face4 = true; + this.faces = part; + this.utilVec31FaceBounds = new THREE.Vector3(); + + } else if (part instanceof THREE.Vector3) { + + this.vertices = part; + + } + + this.radius = 0; + this.position = new THREE.Vector3(); + + // initial update + + if (this.object instanceof THREE.Object3D) { + + this.update(); + + } + else { + + // Generic object + this.position.set(object.x, object.y, object.z); + this.radius = object.radius; + + } + + this.positionLast = this.position.clone(); + + }; + + THREE.OctreeObjectData.prototype = { + + update: function () { + + if (this.face3) { + + this.radius = this.getFace3BoundingRadius(this.object, this.faces); + this.position.copy(this.faces.centroid).applyMatrix4(this.object.matrixWorld); + + } else if (this.face4) { + + this.radius = this.getFace4BoundingRadius(this.object, this.faces); + this.position.copy(this.faces.centroid).applyMatrix4(this.object.matrixWorld); + + } else if (this.vertices) { + + if (this.object.geometry.boundingSphere === null) { + + this.object.geometry.computeBoundingSphere(); + + } + + this.radius = this.object.geometry.boundingSphere.radius; + this.position.copy(this.vertices).applyMatrix4(this.object.matrixWorld); + + } else { + + if (this.object.geometry) { + + if (this.object.geometry.boundingSphere === null) { + + this.object.geometry.computeBoundingSphere(); + + } + + this.radius = this.object.geometry.boundingSphere.radius; + this.position.copy(this.object.geometry.boundingSphere.center).applyMatrix4(this.object.matrixWorld); + + } else { + + this.radius = this.object.boundRadius; + this.position.getPositionFromMatrix(this.object.matrixWorld); + + } + + } + + this.radius = this.radius * Math.max(this.object.scale.x, this.object.scale.y, this.object.scale.z); + + }, + + getFace3BoundingRadius: function (object, face) { + + var geometry = object.geometry || object, + vertices = geometry.vertices, + centroid = face.centroid, + va = vertices[face.a], vb = vertices[face.b], vc = vertices[face.c], + centroidToVert = this.utilVec31FaceBounds, + radius; + + centroid.addVectors(va, vb).add(vc).divideScalar(3); + radius = Math.max(centroidToVert.subVectors(centroid, va).length(), centroidToVert.subVectors(centroid, vb).length(), centroidToVert.subVectors(centroid, vc).length()); + + return radius; + + }, + + getFace4BoundingRadius: function (object, face) { + + var geometry = object.geometry || object, + vertices = geometry.vertices, + centroid = face.centroid, + va = vertices[face.a], vb = vertices[face.b], vc = vertices[face.c], vd = vertices[face.d], + centroidToVert = this.utilVec31FaceBounds, + radius; + + centroid.addVectors(va, vb).add(vc).add(vd).divideScalar(4); + radius = Math.max(centroidToVert.subVectors(centroid, va).length(), centroidToVert.subVectors(centroid, vb).length(), centroidToVert.subVectors(centroid, vc).length(), centroidToVert.subVectors(centroid, vd).length()); + + return radius; + + } + + }; + + /*=================================================== + + node + + =====================================================*/ + + THREE.OctreeNode = function (parameters) { + + // utility + + this.utilVec31Branch = new THREE.Vector3(); + this.utilVec31Expand = new THREE.Vector3(); + this.utilVec31Ray = new THREE.Vector3(); + + // handle parameters + + parameters = parameters || {}; + + // store or create tree + + if (parameters.tree instanceof THREE.Octree) { + + this.tree = parameters.tree; + + } else if (parameters.parent instanceof THREE.OctreeNode !== true) { + + parameters.root = this; + + this.tree = new THREE.Octree(parameters); + + } + + // basic properties + + this.id = this.tree.nodeCount++; + this.position = parameters.position instanceof THREE.Vector3 ? parameters.position : new THREE.Vector3(); + this.radius = parameters.radius > 0 ? parameters.radius : 1; + this.indexOctant = parameters.indexOctant; + this.depth = 0; + + // reset and assign parent + + this.reset(); + this.setParent(parameters.parent); + + // additional properties + + this.overlap = this.radius * this.tree.overlapPct; + this.radiusOverlap = this.radius + this.overlap; + this.left = this.position.x - this.radiusOverlap; + this.right = this.position.x + this.radiusOverlap; + this.bottom = this.position.y - this.radiusOverlap; + this.top = this.position.y + this.radiusOverlap; + this.back = this.position.z - this.radiusOverlap; + this.front = this.position.z + this.radiusOverlap; + + // visual + + if (this.tree.scene) { + + this.visual = new THREE.Mesh(this.tree.visualGeometry, this.tree.visualMaterial); + this.visual.scale.set(this.radiusOverlap * 2, this.radiusOverlap * 2, this.radiusOverlap * 2); + this.visual.position.copy(this.position); + this.tree.scene.add(this.visual); + + } + + }; + + THREE.OctreeNode.prototype = { + + setParent: function (parent) { + + // store new parent + + if (parent !== this && this.parent !== parent) { + + this.parent = parent; + + // update properties + + this.updateProperties(); + + } + + }, + + updateProperties: function () { + + var i, l; + + // properties + + if (this.parent instanceof THREE.OctreeNode) { + + this.tree = this.parent.tree; + this.depth = this.parent.depth + 1; + + } else { + + this.depth = 0; + + } + + // cascade + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + this.nodesByIndex[this.nodesIndices[i]].updateProperties(); + + } + + }, + + reset: function (cascade, removeVisual) { + + var i, l, + node, + nodesIndices = this.nodesIndices || [], + nodesByIndex = this.nodesByIndex; + + this.objects = []; + this.nodesIndices = []; + this.nodesByIndex = {}; + + // unset parent in nodes + + for (i = 0, l = nodesIndices.length; i < l; i++) { + + node = nodesByIndex[nodesIndices[i]]; + + node.setParent(undefined); + + if (cascade === true) { + + node.reset(cascade, removeVisual); + + } + + } + + // visual + + if (removeVisual === true && this.visual && this.visual.parent) { + + this.visual.parent.remove(this.visual); + + } + + }, + + addNode: function (node, indexOctant) { + + node.indexOctant = indexOctant; + + if (indexOfValue(this.nodesIndices, indexOctant) === -1) { + + this.nodesIndices.push(indexOctant); + + } + + this.nodesByIndex[indexOctant] = node; + + if (node.parent !== this) { + + node.setParent(this); + + } + + }, + + removeNode: function (indexOctant) { + + var index, + node; + + index = indexOfValue(this.nodesIndices, indexOctant); + + this.nodesIndices.splice(index, 1); + + node = node || this.nodesByIndex[indexOctant]; + + delete this.nodesByIndex[indexOctant]; + + if (node.parent === this) { + + node.setParent(undefined); + + } + + }, + + addObject: function (object) { + + var index, + indexOctant, + node; + + // get object octant index + + indexOctant = this.getOctantIndex(object); + + // if object fully contained by an octant, add to subtree + if (indexOctant > -1 && this.nodesIndices.length > 0) { + + node = this.branch(indexOctant); + + node.addObject(object); + + } else if (indexOctant < -1 && this.parent instanceof THREE.OctreeNode) { + + // if object lies outside bounds, add to parent node + + this.parent.addObject(object); + + } else { + + // add to this objects list + + index = indexOfValue(this.objects, object); + + if (index === -1) { + + this.objects.push(object); + + } + + // node reference + + object.node = this; + + // check if need to expand, split, or both + + this.checkGrow(); + + } + + }, + + addObjectWithoutCheck: function (objects) { + + var i, l, + object; + + for (i = 0, l = objects.length; i < l; i++) { + + object = objects[i]; + + this.objects.push(object); + + object.node = this; + + } + + }, + + removeObject: function (object) { + + var i, l, + nodesRemovedFrom, + removeData; + + // cascade through tree to find and remove object + + removeData = this.removeObjectRecursive(object, { + searchComplete: false, + nodesRemovedFrom: [], + objectsDataRemoved: [] + }); + + // if object removed, try to shrink the nodes it was removed from + + nodesRemovedFrom = removeData.nodesRemovedFrom; + + if (nodesRemovedFrom.length > 0) { + + for (i = 0, l = nodesRemovedFrom.length; i < l; i++) { + + nodesRemovedFrom[i].shrink(); + + } + + } + + return removeData.objectsDataRemoved; + + }, + + removeObjectRecursive: function (object, removeData) { + + var i, l, + index = -1, + objectData, + node, + objectRemoved; + + // find index of object in objects list + + // search and remove object data (fast) + if (object instanceof THREE.OctreeObjectData) { + + // remove from this objects list + + index = indexOfValue(this.objects, object); + + if (index !== -1) { + + this.objects.splice(index, 1); + object.node = undefined; + + removeData.objectsDataRemoved.push(object); + + removeData.searchComplete = objectRemoved = true; + + } + + } else { + + // search each object data for object and remove (slow) + + for (i = this.objects.length - 1; i >= 0; i--) { + + objectData = this.objects[i]; + + if (objectData.object === object) { + + this.objects.splice(i, 1); + objectData.node = undefined; + + removeData.objectsDataRemoved.push(objectData); + + objectRemoved = true; + + if (!objectData.faces && !objectData.vertices) { + + removeData.searchComplete = true; + break; + + } + + } + + } + + } + + // if object data removed and this is not on nodes removed from + + if (objectRemoved === true) { + + removeData.nodesRemovedFrom.push(this); + + } + + // if search not complete, search nodes + + if (removeData.searchComplete !== true) { + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + node = this.nodesByIndex[this.nodesIndices[i]]; + + // try removing object from node + + removeData = node.removeObjectRecursive(object, removeData); + + if (removeData.searchComplete === true) { + + break; + + } + + } + + } + + return removeData; + + }, + + checkGrow: function () { + + // if object count above max + + if (this.objects.length > this.tree.objectsThreshold && this.tree.objectsThreshold > 0) { + + this.grow(); + + } + + }, + + grow: function () { + + var indexOctant, + object, + objectsExpand = [], + objectsExpandOctants = [], + objectsSplit = [], + objectsSplitOctants = [], + objectsRemaining = [], + i, l; + + // for each object + + for (i = 0, l = this.objects.length; i < l; i++) { + + object = this.objects[i]; + + // get object octant index + + indexOctant = this.getOctantIndex(object); + + // if lies within octant + if (indexOctant > -1) { + + objectsSplit.push(object); + objectsSplitOctants.push(indexOctant); + + } else if (indexOctant < -1) { + + // lies outside radius + + objectsExpand.push(object); + objectsExpandOctants.push(indexOctant); + + } else { + + // lies across bounds between octants + + objectsRemaining.push(object); + + } + + } + + // if has objects to split + + if (objectsSplit.length > 0) { + + objectsRemaining = objectsRemaining.concat(this.split(objectsSplit, objectsSplitOctants)); + + } + + // if has objects to expand + + if (objectsExpand.length > 0) { + + objectsRemaining = objectsRemaining.concat(this.expand(objectsExpand, objectsExpandOctants)); + + } + + // store remaining + + this.objects = objectsRemaining; + + // merge check + + this.checkMerge(); + + }, + + split: function (objects, octants) { + + var i, l, + indexOctant, + object, + node, + objectsRemaining; + + // if not at max depth + + if (this.depth < this.tree.depthMax) { + + objects = objects || this.objects; + + octants = octants || []; + + objectsRemaining = []; + + // for each object + + for (i = 0, l = objects.length; i < l; i++) { + + object = objects[i]; + + // get object octant index + + indexOctant = octants[i]; + + // if object contained by octant, branch this tree + + if (indexOctant > -1) { + + node = this.branch(indexOctant); + + node.addObject(object); + + } else { + + objectsRemaining.push(object); + + } + + } + + // if all objects, set remaining as new objects + + if (objects === this.objects) { + + this.objects = objectsRemaining; + + } + + } else { + + objectsRemaining = this.objects; + + } + + return objectsRemaining; + + }, + + branch: function (indexOctant) { + + var node, + overlap, + radius, + radiusOffset, + offset, + position; + + // node exists + + if (this.nodesByIndex[indexOctant] instanceof THREE.OctreeNode) { + + node = this.nodesByIndex[indexOctant]; + + } else { + + // properties + + radius = (this.radiusOverlap) * 0.5; + overlap = radius * this.tree.overlapPct; + radiusOffset = radius - overlap; + offset = this.utilVec31Branch.set(indexOctant & 1 ? radiusOffset : -radiusOffset, indexOctant & 2 ? radiusOffset : -radiusOffset, indexOctant & 4 ? radiusOffset : -radiusOffset); + position = new THREE.Vector3().addVectors(this.position, offset); + + // node + + node = new THREE.OctreeNode({ + tree: this.tree, + parent: this, + position: position, + radius: radius, + indexOctant: indexOctant + }); + + // store + + this.addNode(node, indexOctant); + + } + + return node; + + }, + + expand: function (objects, octants) { + + var i, l, + object, + objectsRemaining, + objectsExpand, + indexOctant, + flagsOutside, + indexOctantInverse, + iom = this.tree.INDEX_OUTSIDE_MAP, + indexOutsideCounts, + infoIndexOutside1, + infoIndexOutside2, + infoIndexOutside3, + indexOutsideBitwise1, + indexOutsideBitwise2, + infoPotential1, + infoPotential2, + infoPotential3, + indexPotentialBitwise1, + indexPotentialBitwise2, + octantX, octantY, octantZ, + overlap, + radius, + radiusOffset, + radiusParent, + overlapParent, + offset = this.utilVec31Expand, + position, + parent; + + // handle max depth down tree + + if (this.tree.root.getDepthEnd() < this.tree.depthMax) { + + objects = objects || this.objects; + octants = octants || []; + + objectsRemaining = []; + objectsExpand = []; + + // reset counts + + for (i = 0, l = iom.length; i < l; i++) { + + iom[i].count = 0; + + } + + // for all outside objects, find outside octants containing most objects + + for (i = 0, l = objects.length; i < l; i++) { + + object = objects[i]; + + // get object octant index + + indexOctant = octants[i]; + + // if object outside this, include in calculations + + if (indexOctant < -1) { + + // convert octant index to outside flags + + flagsOutside = -indexOctant - this.tree.INDEX_OUTSIDE_OFFSET; + + // check against bitwise flags + + // x + + if (flagsOutside & this.tree.FLAG_POS_X) { + + iom[this.tree.INDEX_OUTSIDE_POS_X].count++; + + } else if (flagsOutside & this.tree.FLAG_NEG_X) { + + iom[this.tree.INDEX_OUTSIDE_NEG_X].count++; + + } + + // y + + if (flagsOutside & this.tree.FLAG_POS_Y) { + + iom[this.tree.INDEX_OUTSIDE_POS_Y].count++; + + } else if (flagsOutside & this.tree.FLAG_NEG_Y) { + + iom[this.tree.INDEX_OUTSIDE_NEG_Y].count++; + + } + + // z + + if (flagsOutside & this.tree.FLAG_POS_Z) { + + iom[this.tree.INDEX_OUTSIDE_POS_Z].count++; + + } else if (flagsOutside & this.tree.FLAG_NEG_Z) { + + iom[this.tree.INDEX_OUTSIDE_NEG_Z].count++; + + } + + // store in expand list + + objectsExpand.push(object); + + } else { + + objectsRemaining.push(object); + + } + + } + + // if objects to expand + + if (objectsExpand.length > 0) { + + // shallow copy index outside map + + indexOutsideCounts = iom.slice(0); + + // sort outside index count so highest is first + + indexOutsideCounts.sort(function (a, b) { + + return b.count - a.count; + + }); + + // get highest outside indices + + // first is first + infoIndexOutside1 = indexOutsideCounts[0]; + indexOutsideBitwise1 = infoIndexOutside1.index | 1; + + // second is ( one of next two bitwise OR 1 ) that is not opposite of ( first bitwise OR 1 ) + + infoPotential1 = indexOutsideCounts[1]; + infoPotential2 = indexOutsideCounts[2]; + + infoIndexOutside2 = (infoPotential1.index | 1) !== indexOutsideBitwise1 ? infoPotential1 : infoPotential2; + indexOutsideBitwise2 = infoIndexOutside2.index | 1; + + // third is ( one of next three bitwise OR 1 ) that is not opposite of ( first or second bitwise OR 1 ) + + infoPotential1 = indexOutsideCounts[2]; + infoPotential2 = indexOutsideCounts[3]; + infoPotential3 = indexOutsideCounts[4]; + + indexPotentialBitwise1 = infoPotential1.index | 1; + indexPotentialBitwise2 = infoPotential2.index | 1; + + infoIndexOutside3 = indexPotentialBitwise1 !== indexOutsideBitwise1 && indexPotentialBitwise1 !== indexOutsideBitwise2 ? infoPotential1 : indexPotentialBitwise2 !== indexOutsideBitwise1 && indexPotentialBitwise2 !== indexOutsideBitwise2 ? infoPotential2 : infoPotential3; + + // get this octant normal based on outside octant indices + + octantX = infoIndexOutside1.x + infoIndexOutside2.x + infoIndexOutside3.x; + octantY = infoIndexOutside1.y + infoIndexOutside2.y + infoIndexOutside3.y; + octantZ = infoIndexOutside1.z + infoIndexOutside2.z + infoIndexOutside3.z; + + // get this octant indices based on octant normal + + indexOctant = this.getOctantIndexFromPosition(octantX, octantY, octantZ); + indexOctantInverse = this.getOctantIndexFromPosition(-octantX, -octantY, -octantZ); + + // properties + + overlap = this.overlap; + radius = this.radius; + + // radius of parent comes from reversing overlap of this, unless overlap percent is 0 + + radiusParent = this.tree.overlapPct > 0 ? overlap / ((0.5 * this.tree.overlapPct) * (1 + this.tree.overlapPct)) : radius * 2; + overlapParent = radiusParent * this.tree.overlapPct; + + // parent offset is difference between radius + overlap of parent and child + + radiusOffset = (radiusParent + overlapParent) - (radius + overlap); + offset.set(indexOctant & 1 ? radiusOffset : -radiusOffset, indexOctant & 2 ? radiusOffset : -radiusOffset, indexOctant & 4 ? radiusOffset : -radiusOffset); + position = new THREE.Vector3().addVectors(this.position, offset); + + // parent + + parent = new THREE.OctreeNode({ + tree: this.tree, + position: position, + radius: radiusParent + }); + + // set self as node of parent + + parent.addNode(this, indexOctantInverse); + + // set parent as root + + this.tree.setRoot(parent); + + // add all expand objects to parent + + for (i = 0, l = objectsExpand.length; i < l; i++) { + + this.tree.root.addObject(objectsExpand[i]); + + } + + } + + // if all objects, set remaining as new objects + + if (objects === this.objects) { + + this.objects = objectsRemaining; + + } + + } else { + + objectsRemaining = objects; + + } + + return objectsRemaining; + + }, + + shrink: function () { + + // merge check + + this.checkMerge(); + + // contract check + + this.tree.root.checkContract(); + + }, + + checkMerge: function () { + + var nodeParent = this, + nodeMerge; + + // traverse up tree as long as node + entire subtree's object count is under minimum + + while (nodeParent.parent instanceof THREE.OctreeNode && nodeParent.getObjectCountEnd() < this.tree.objectsThreshold) { + + nodeMerge = nodeParent; + nodeParent = nodeParent.parent; + + } + + // if parent node is not this, merge entire subtree into merge node + + if (nodeParent !== this) { + + nodeParent.merge(nodeMerge); + + } + + }, + + merge: function (nodes) { + + var i, l, + node; + + // handle nodes + + nodes = toArray(nodes); + + for (i = 0, l = nodes.length; i < l; i++) { + + node = nodes[i]; + + // gather node + all subtree objects + + this.addObjectWithoutCheck(node.getObjectsEnd()); + + // reset node + entire subtree + + node.reset(true, true); + + // remove node + + this.removeNode(node.indexOctant, node); + + } + + // merge check + + this.checkMerge(); + + }, + + checkContract: function () { + + var i, l, + node, + nodeObjectsCount, + nodeHeaviest, + nodeHeaviestObjectsCount, + outsideHeaviestObjectsCount; + + // find node with highest object count + + if (this.nodesIndices.length > 0) { + + nodeHeaviestObjectsCount = 0; + outsideHeaviestObjectsCount = this.objects.length; + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + node = this.nodesByIndex[this.nodesIndices[i]]; + + nodeObjectsCount = node.getObjectCountEnd(); + outsideHeaviestObjectsCount += nodeObjectsCount; + + if (nodeHeaviest instanceof THREE.OctreeNode === false || nodeObjectsCount > nodeHeaviestObjectsCount) { + + nodeHeaviest = node; + nodeHeaviestObjectsCount = nodeObjectsCount; + + } + + } + + // subtract heaviest count from outside count + + outsideHeaviestObjectsCount -= nodeHeaviestObjectsCount; + + // if should contract + + if (outsideHeaviestObjectsCount < this.tree.objectsThreshold && nodeHeaviest instanceof THREE.OctreeNode) { + + this.contract(nodeHeaviest); + + } + + } + + }, + + contract: function (nodeRoot) { + + var i, l, + node; + + // handle all nodes + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + node = this.nodesByIndex[this.nodesIndices[i]]; + + // if node is not new root + + if (node !== nodeRoot) { + + // add node + all subtree objects to root + + nodeRoot.addObjectWithoutCheck(node.getObjectsEnd()); + + // reset node + entire subtree + + node.reset(true, true); + + } + + } + + // add own objects to root + + nodeRoot.addObjectWithoutCheck(this.objects); + + // reset self + + this.reset(false, true); + + // set new root + + this.tree.setRoot(nodeRoot); + + // contract check on new root + + nodeRoot.checkContract(); + + }, + + getOctantIndex: function (objectData) { + + var positionObj, + radiusObj, + position = this.position, + radiusOverlap = this.radiusOverlap, + overlap = this.overlap, + deltaX, deltaY, deltaZ, + distX, distY, distZ, + distance, + indexOctant = 0; + + // handle type + + if (objectData instanceof THREE.OctreeObjectData) { + + radiusObj = objectData.radius; + + positionObj = objectData.position; + + // update object data position last + + objectData.positionLast.copy(positionObj); + + } else if (objectData instanceof THREE.OctreeNode) { + + positionObj = objectData.position; + + radiusObj = 0; + + } + + // find delta and distance + + deltaX = positionObj.x - position.x; + deltaY = positionObj.y - position.y; + deltaZ = positionObj.z - position.z; + + distX = Math.abs(deltaX); + distY = Math.abs(deltaY); + distZ = Math.abs(deltaZ); + distance = Math.max(distX, distY, distZ); + + // if outside, use bitwise flags to indicate on which sides object is outside of + + if (distance + radiusObj > radiusOverlap) { + + // x + + if (distX + radiusObj > radiusOverlap) { + + indexOctant = indexOctant ^ (deltaX > 0 ? this.tree.FLAG_POS_X : this.tree.FLAG_NEG_X); + + } + + // y + + if (distY + radiusObj > radiusOverlap) { + + indexOctant = indexOctant ^ (deltaY > 0 ? this.tree.FLAG_POS_Y : this.tree.FLAG_NEG_Y); + + } + + // z + + if (distZ + radiusObj > radiusOverlap) { + + indexOctant = indexOctant ^ (deltaZ > 0 ? this.tree.FLAG_POS_Z : this.tree.FLAG_NEG_Z); + + } + + objectData.indexOctant = -indexOctant - this.tree.INDEX_OUTSIDE_OFFSET; + + return objectData.indexOctant; + + } + + // return octant index from delta xyz + + if (deltaX - radiusObj > -overlap) { + + // x right + + indexOctant = indexOctant | 1; + + } else if (!(deltaX + radiusObj < overlap)) { + + // x left + + objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS; + return objectData.indexOctant; + + } + + if (deltaY - radiusObj > -overlap) { + + // y right + + indexOctant = indexOctant | 2; + + } else if (!(deltaY + radiusObj < overlap)) { + + // y left + + objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS; + return objectData.indexOctant; + + } + + + if (deltaZ - radiusObj > -overlap) { + + // z right + + indexOctant = indexOctant | 4; + + } else if (!(deltaZ + radiusObj < overlap)) { + + // z left + + objectData.indexOctant = this.tree.INDEX_INSIDE_CROSS; + return objectData.indexOctant; + + } + + objectData.indexOctant = indexOctant; + return objectData.indexOctant; + + }, + + getOctantIndexFromPosition: function (x, y, z) { + + var indexOctant = 0; + + if (x > 0) { + + indexOctant = indexOctant | 1; + + } + + if (y > 0) { + + indexOctant = indexOctant | 2; + + } + + if (z > 0) { + + indexOctant = indexOctant | 4; + + } + + return indexOctant; + + }, + + search: function (position, radius, objects, direction, directionPct) { + + var i, l, + node, + intersects; + + // test intersects by parameters + + if (direction) { + + intersects = this.intersectRay(position, direction, radius, directionPct); + + } else { + + intersects = this.intersectSphere(position, radius); + + } + + // if intersects + + if (intersects === true) { + + // gather objects + + objects = objects.concat(this.objects); + + // search subtree + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + node = this.nodesByIndex[this.nodesIndices[i]]; + + objects = node.search(position, radius, objects, direction); + + } + + } + + return objects; + + }, + + intersectSphere: function (position, radius) { + + var distance = radius * radius, + px = position.x, + py = position.y, + pz = position.z; + + if (px < this.left) { + distance -= Math.pow(px - this.left, 2); + } else if (px > this.right) { + distance -= Math.pow(px - this.right, 2); + } + + if (py < this.bottom) { + distance -= Math.pow(py - this.bottom, 2); + } else if (py > this.top) { + distance -= Math.pow(py - this.top, 2); + } + + if (pz < this.back) { + distance -= Math.pow(pz - this.back, 2); + } else if (pz > this.front) { + distance -= Math.pow(pz - this.front, 2); + } + + return distance >= 0; + + }, + + intersectRay: function (origin, direction, distance, directionPct) { + + if (typeof directionPct === 'undefined') { + + directionPct = this.utilVec31Ray.set(1, 1, 1).divide(direction); + + } + + var t1 = (this.left - origin.x) * directionPct.x, + t2 = (this.right - origin.x) * directionPct.x, + t3 = (this.bottom - origin.y) * directionPct.y, + t4 = (this.top - origin.y) * directionPct.y, + t5 = (this.back - origin.z) * directionPct.z, + t6 = (this.front - origin.z) * directionPct.z, + tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)), + tmin; + + // ray would intersect in reverse direction, i.e. this is behind ray + if (tmax < 0) { + return false; + } + + tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); + + // if tmin > tmax or tmin > ray distance, ray doesn't intersect AABB + if (tmin > tmax || tmin > distance) { + return false; + } + + return true; + + }, + + getDepthEnd: function (depth) { + + var i, l, + node; + + if (this.nodesIndices.length > 0) { + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + node = this.nodesByIndex[this.nodesIndices[i]]; + + depth = node.getDepthEnd(depth); + + } + + } else { + + depth = !depth || this.depth > depth ? this.depth : depth; + + } + + return depth; + + }, + + getNodeCountEnd: function () { + + return this.tree.root.getNodeCountRecursive() + 1; + + }, + + getNodeCountRecursive: function () { + + var i, l, + count = this.nodesIndices.length; + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + count += this.nodesByIndex[this.nodesIndices[i]].getNodeCountRecursive(); + + } + + return count; + + }, + + getObjectsEnd: function (objects) { + + var i, l, + node; + + objects = (objects || []).concat(this.objects); + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + node = this.nodesByIndex[this.nodesIndices[i]]; + + objects = node.getObjectsEnd(objects); + + } + + return objects; + + }, + + getObjectCountEnd: function () { + + var i, l, + count = this.objects.length; + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + count += this.nodesByIndex[this.nodesIndices[i]].getObjectCountEnd(); + + } + + return count; + + }, + + getObjectCountStart: function () { + + var count = this.objects.length, + parent = this.parent; + + while (parent instanceof THREE.OctreeNode) { + + count += parent.objects.length; + parent = parent.parent; + + } + + return count; + + }, + + toConsole: function (space) { + + var i, l, + node, + spaceAddition = ' '; + + space = typeof space === 'string' ? space : spaceAddition; + + console.log((this.parent ? space + ' octree NODE > ' : ' octree ROOT > '), this, ' // id: ', this.id, ' // indexOctant: ', this.indexOctant, ' // position: ', this.position.x, this.position.y, this.position.z, ' // radius: ', this.radius, ' // depth: ', this.depth); + console.log((this.parent ? space + ' ' : ' '), '+ objects ( ', this.objects.length, ' ) ', this.objects); + console.log((this.parent ? space + ' ' : ' '), '+ children ( ', this.nodesIndices.length, ' )', this.nodesIndices, this.nodesByIndex); + + for (i = 0, l = this.nodesIndices.length; i < l; i++) { + + node = this.nodesByIndex[this.nodesIndices[i]]; + + node.toConsole(space + spaceAddition); + + } + + } + + }; + + /*=================================================== + + raycaster additional functionality + + =====================================================*/ + + THREE.Raycaster.prototype.intersectOctreeObject = function (object, recursive) { + + var intersects, + octreeObject, + facesAll, + facesSearch; + + if (object.object instanceof THREE.Object3D) { + + octreeObject = object; + object = octreeObject.object; + + // temporarily replace object geometry's faces with octree object faces + + facesSearch = octreeObject.faces; + facesAll = object.geometry.faces; + + if (facesSearch.length > 0) { + + object.geometry.faces = facesSearch; + + } + + // intersect + + intersects = this.intersectObject(object, recursive); + + // revert object geometry's faces + + if (facesSearch.length > 0) { + + object.geometry.faces = facesAll; + + } + + } else { + + intersects = this.intersectObject(object, recursive); + + } + + return intersects; + + }; + + THREE.Raycaster.prototype.intersectOctreeObjects = function (objects, recursive) { + + var i, il, + intersects = []; + + for (i = 0, il = objects.length; i < il; i++) { + + intersects = intersects.concat(this.intersectOctreeObject(objects[i], recursive)); + + } + + intersects.sort(function (a, b) { + return a.distance - b.distance; + }); + + return intersects; + + }; +}; diff --git a/js/src/providers/threejs/helpers/THREE.STLLoader.js b/js/src/providers/threejs/helpers/THREE.STLLoader.js new file mode 100644 index 00000000..b5302794 --- /dev/null +++ b/js/src/providers/threejs/helpers/THREE.STLLoader.js @@ -0,0 +1,348 @@ +'use strict'; +// jshint ignore: start +// jscs:disable + +module.exports = function (THREE) { + /** + * @author aleeper / http://adamleeper.com/ + * @author mrdoob / http://mrdoob.com/ + * @author gero3 / https://github.com/gero3 + * @author Mugen87 / https://github.com/Mugen87 + * + * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. + * + * Supports both binary and ASCII encoded files, with automatic detection of type. + * + * The loader returns a non-indexed buffer geometry. + * + * Limitations: + * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). + * There is perhaps some question as to how valid it is to always assume little-endian-ness. + * ASCII decoding assumes file is UTF-8. + * + * Usage: + * var loader = new THREE.STLLoader(); + * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { + * scene.add( new THREE.Mesh( geometry ) ); + * }); + * + * For binary STLs geometry might contain colors for vertices. To use it: + * // use the same code to load STL as above + * if (geometry.hasColors) { + * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors }); + * } else { .... } + * var mesh = new THREE.Mesh( geometry, material ); + */ + + + THREE.STLLoader = function (manager) { + + this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager; + + }; + + THREE.STLLoader.prototype = { + + constructor: THREE.STLLoader, + + load: function (url, onLoad, onProgress, onError) { + + var scope = this; + + var loader = new THREE.FileLoader(scope.manager); + loader.setPath(scope.path); + loader.setResponseType('arraybuffer'); + loader.load(url, function (text) { + + try { + + onLoad(scope.parse(text)); + + } catch (exception) { + + if (onError) { + + onError(exception); + + } + + } + + }, onProgress, onError); + + }, + + setPath: function (value) { + + this.path = value; + return this; + + }, + + parse: function (data) { + + function isBinary(data) { + + var expect, face_size, n_faces, reader; + reader = new DataView(data); + face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8); + n_faces = reader.getUint32(80, true); + expect = 80 + (32 / 8) + (n_faces * face_size); + + if (expect === reader.byteLength) { + + return true; + + } + + // An ASCII STL data must begin with 'solid ' as the first six bytes. + // However, ASCII STLs lacking the SPACE after the 'd' are known to be + // plentiful. So, check the first 5 bytes for 'solid'. + + // Several encodings, such as UTF-8, precede the text with up to 5 bytes: + // https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding + // Search for "solid" to start anywhere after those prefixes. + + // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd' + + var solid = [115, 111, 108, 105, 100]; + + for (var off = 0; off < 5; off++) { + + // If "solid" text is matched to the current offset, declare it to be an ASCII STL. + + if (matchDataViewAt(solid, reader, off)) return false; + + } + + // Couldn't find "solid" text at the beginning; it is binary STL. + + return true; + + } + + function matchDataViewAt(query, reader, offset) { + + // Check if each byte in query matches the corresponding byte from the current offset + + for (var i = 0, il = query.length; i < il; i++) { + + if (query[i] !== reader.getUint8(offset + i, false)) return false; + + } + + return true; + + } + + function parseBinary(data) { + + var reader = new DataView(data); + var faces = reader.getUint32(80, true); + + var r, g, b, hasColors = false, colors; + var defaultR, defaultG, defaultB, alpha; + + // process STL header + // check for default color in header ("COLOR=rgba" sequence). + + for (var index = 0; index < 80 - 10; index++) { + + if ((reader.getUint32(index, false) == 0x434F4C4F /*COLO*/) && + (reader.getUint8(index + 4) == 0x52 /*'R'*/) && + (reader.getUint8(index + 5) == 0x3D /*'='*/)) { + + hasColors = true; + colors = []; + + defaultR = reader.getUint8(index + 6) / 255; + defaultG = reader.getUint8(index + 7) / 255; + defaultB = reader.getUint8(index + 8) / 255; + alpha = reader.getUint8(index + 9) / 255; + + } + + } + + var dataOffset = 84; + var faceLength = 12 * 4 + 2; + + var geometry = new THREE.BufferGeometry(); + + var vertices = []; + var normals = []; + + for (var face = 0; face < faces; face++) { + + var start = dataOffset + face * faceLength; + var normalX = reader.getFloat32(start, true); + var normalY = reader.getFloat32(start + 4, true); + var normalZ = reader.getFloat32(start + 8, true); + + if (hasColors) { + + var packedColor = reader.getUint16(start + 48, true); + + if ((packedColor & 0x8000) === 0) { + + // facet has its own unique color + + r = (packedColor & 0x1F) / 31; + g = ((packedColor >> 5) & 0x1F) / 31; + b = ((packedColor >> 10) & 0x1F) / 31; + + } else { + + r = defaultR; + g = defaultG; + b = defaultB; + + } + + } + + for (var i = 1; i <= 3; i++) { + + var vertexstart = start + i * 12; + + vertices.push(reader.getFloat32(vertexstart, true)); + vertices.push(reader.getFloat32(vertexstart + 4, true)); + vertices.push(reader.getFloat32(vertexstart + 8, true)); + + normals.push(normalX, normalY, normalZ); + + if (hasColors) { + + colors.push(r, g, b); + + } + + } + + } + + geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3)); + geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(normals), 3)); + + if (hasColors) { + + geometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3)); + geometry.hasColors = true; + geometry.alpha = alpha; + + } + + return geometry; + + } + + function parseASCII(data) { + + var geometry = new THREE.BufferGeometry(); + var patternFace = /facet([\s\S]*?)endfacet/g; + var faceCounter = 0; + + var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; + var patternVertex = new RegExp('vertex' + patternFloat + patternFloat + patternFloat, 'g'); + var patternNormal = new RegExp('normal' + patternFloat + patternFloat + patternFloat, 'g'); + + var vertices = []; + var normals = []; + + var normal = new THREE.Vector3(); + + var result; + + while ((result = patternFace.exec(data)) !== null) { + + var vertexCountPerFace = 0; + var normalCountPerFace = 0; + + var text = result[0]; + + while ((result = patternNormal.exec(text)) !== null) { + + normal.x = parseFloat(result[1]); + normal.y = parseFloat(result[2]); + normal.z = parseFloat(result[3]); + normalCountPerFace++; + + } + + while ((result = patternVertex.exec(text)) !== null) { + + vertices.push(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])); + normals.push(normal.x, normal.y, normal.z); + vertexCountPerFace++; + + } + + // every face have to own ONE valid normal + + if (normalCountPerFace !== 1) { + + console.error('THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter); + + } + + // each face have to own THREE valid vertices + + if (vertexCountPerFace !== 3) { + + console.error('THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter); + + } + + faceCounter++; + + } + + geometry.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); + geometry.addAttribute('normal', new THREE.Float32BufferAttribute(normals, 3)); + + return geometry; + + } + + function ensureString(buffer) { + + if (typeof buffer !== 'string') { + + return THREE.LoaderUtils.decodeText(new Uint8Array(buffer)); + + } + + return buffer; + + } + + function ensureBinary(buffer) { + + if (typeof buffer === 'string') { + + var array_buffer = new Uint8Array(buffer.length); + for (var i = 0; i < buffer.length; i++) { + + array_buffer[i] = buffer.charCodeAt(i) & 0xff; // implicitly assumes little-endian + + } + return array_buffer.buffer || array_buffer; + + } else { + + return buffer; + + } + + } + + // start + + var binData = ensureBinary(data); + + return isBinary(binData) ? parseBinary(binData) : parseASCII(ensureString(data)); + + } + + }; +}; diff --git a/js/src/providers/threejs/helpers/THREE.TrackballControls.js b/js/src/providers/threejs/helpers/THREE.TrackballControls.js new file mode 100644 index 00000000..0ea13370 --- /dev/null +++ b/js/src/providers/threejs/helpers/THREE.TrackballControls.js @@ -0,0 +1,647 @@ +'use strict'; +// jshint ignore: start +// jscs:disable + +module.exports = function (THREE) { + /** + * @author Eberhard Graether / http://egraether.com/ + * @author Mark Lundin / http://mark-lundin.com + * @author Simone Manini / http://daron1337.github.io + * @author Luca Antiga / http://lantiga.github.io + */ + + THREE.TrackballControls = function (object, domElement) { + + var _this = this; + var STATE = {NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4}; + var currentWindow, currentDocument; + + if (domElement !== undefined) { + currentWindow = domElement.ownerDocument.defaultView || domElement.ownerDocument.parentWindow; + this.domElement = domElement; + currentDocument = domElement.ownerDocument; + } else { + currentWindow = window; + this.domElement = currentDocument = currentWindow.document; + } + + this.object = object; + + // API + + this.enabled = true; + + this.screen = {left: 0, top: 0, width: 0, height: 0}; + + this.rotateSpeed = 1.0; + this.zoomSpeed = 1.2; + this.panSpeed = 0.3; + + this.noRotate = false; + this.noZoom = false; + this.noPan = false; + + this.staticMoving = false; + this.dynamicDampingFactor = 0.2; + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.keys = [65 /*A*/, 83 /*S*/, 68 /*D*/]; + + // internals + + this.target = new THREE.Vector3(); + + // var EPS = 0.000001; + var EPS = 0.0000000001; + + var lastPosition = new THREE.Vector3(); + + var _state = STATE.NONE, + _prevState = STATE.NONE, + + _eye = new THREE.Vector3(), + + _movePrev = new THREE.Vector2(), + _moveCurr = new THREE.Vector2(), + + _lastAxis = new THREE.Vector3(), + _lastAngle = 0, + + _zoomStart = new THREE.Vector2(), + _zoomEnd = new THREE.Vector2(), + + _touchZoomDistanceStart = 0, + _touchZoomDistanceEnd = 0, + + _panStart = new THREE.Vector2(), + _panEnd = new THREE.Vector2(); + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.up0 = this.object.up.clone(); + + // events + + var changeEvent = {type: 'change'}; + var startEvent = {type: 'start'}; + var endEvent = {type: 'end'}; + + + // methods + + this.handleResize = function () { + + if (this.domElement === currentDocument) { + + this.screen.left = 0; + this.screen.top = 0; + this.screen.width = currentWindow.innerWidth; + this.screen.height = currentWindow.innerHeight; + + } else { + + var box = this.domElement.getBoundingClientRect(); + // adjustments come from similar code in the jquery offset() function + var d = this.domElement.ownerDocument.documentElement; + this.screen.left = box.left + currentWindow.pageXOffset - d.clientLeft; + this.screen.top = box.top + currentWindow.pageYOffset - d.clientTop; + this.screen.width = box.width; + this.screen.height = box.height; + + } + + }; + + this.handleEvent = function (event) { + + if (typeof this[event.type] == 'function') { + + this[event.type](event); + + } + + }; + + var getMouseOnScreen = (function () { + + var vector = new THREE.Vector2(); + + return function getMouseOnScreen(pageX, pageY) { + + vector.set( + (pageX - _this.screen.left) / _this.screen.width, + (pageY - _this.screen.top) / _this.screen.height + ); + + return vector; + + }; + + }()); + + var getMouseOnCircle = (function () { + + var vector = new THREE.Vector2(); + + return function getMouseOnCircle(pageX, pageY) { + + vector.set( + ((pageX - _this.screen.width * 0.5 - _this.screen.left) / (_this.screen.width * 0.5)), + ((_this.screen.height + 2 * (_this.screen.top - pageY)) / _this.screen.width) // screen.width intentional + ); + + return vector; + + }; + + }()); + + this.rotateCamera = (function () { + + var axis = new THREE.Vector3(), + quaternion = new THREE.Quaternion(), + eyeDirection = new THREE.Vector3(), + objectUpDirection = new THREE.Vector3(), + objectSidewaysDirection = new THREE.Vector3(), + moveDirection = new THREE.Vector3(), + angle; + + return function rotateCamera() { + + moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0); + angle = moveDirection.length(); + + if (angle) { + + _eye.copy(_this.object.position).sub(_this.target); + + eyeDirection.copy(_eye).normalize(); + objectUpDirection.copy(_this.object.up).normalize(); + objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize(); + + objectUpDirection.setLength(_moveCurr.y - _movePrev.y); + objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x); + + moveDirection.copy(objectUpDirection.add(objectSidewaysDirection)); + + axis.crossVectors(moveDirection, _eye).normalize(); + + angle *= _this.rotateSpeed; + quaternion.setFromAxisAngle(axis, angle); + + _eye.applyQuaternion(quaternion); + _this.object.up.applyQuaternion(quaternion); + + _lastAxis.copy(axis); + _lastAngle = angle; + + } else if (!_this.staticMoving && _lastAngle) { + + _lastAngle *= Math.sqrt(1.0 - _this.dynamicDampingFactor); + _eye.copy(_this.object.position).sub(_this.target); + quaternion.setFromAxisAngle(_lastAxis, _lastAngle); + _eye.applyQuaternion(quaternion); + _this.object.up.applyQuaternion(quaternion); + + } + + _movePrev.copy(_moveCurr); + + }; + + }()); + + + this.zoomCamera = function () { + + var factor; + + if (_state === STATE.TOUCH_ZOOM_PAN) { + + factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; + _touchZoomDistanceStart = _touchZoomDistanceEnd; + _eye.multiplyScalar(factor); + + } else { + + factor = 1.0 + (_zoomEnd.y - _zoomStart.y) * _this.zoomSpeed; + + if (factor !== 1.0 && factor > 0.0) { + + _eye.multiplyScalar(factor); + + if (_this.staticMoving) { + + _zoomStart.copy(_zoomEnd); + + } else { + + _zoomStart.y += (_zoomEnd.y - _zoomStart.y) * this.dynamicDampingFactor; + + } + + } + + } + + }; + + this.panCamera = (function () { + + var mouseChange = new THREE.Vector2(), + objectUp = new THREE.Vector3(), + pan = new THREE.Vector3(); + + return function panCamera() { + + mouseChange.copy(_panEnd).sub(_panStart); + + if (mouseChange.lengthSq()) { + + mouseChange.multiplyScalar(_eye.length() * _this.panSpeed); + + pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x); + pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y)); + + _this.object.position.add(pan); + _this.target.add(pan); + + if (_this.staticMoving) { + + _panStart.copy(_panEnd); + + } else { + + _panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(_this.dynamicDampingFactor)); + + } + + } + + }; + + }()); + + this.checkDistances = function () { + + if (!_this.noZoom || !_this.noPan) { + + if (_eye.lengthSq() > _this.maxDistance * _this.maxDistance) { + + _this.object.position.addVectors(_this.target, _eye.setLength(_this.maxDistance)); + _zoomStart.copy(_zoomEnd); + + } + + if (_eye.lengthSq() < _this.minDistance * _this.minDistance) { + + _this.object.position.addVectors(_this.target, _eye.setLength(_this.minDistance)); + _zoomStart.copy(_zoomEnd); + + } + + } + + }; + + this.update = function () { + + _eye.subVectors(_this.object.position, _this.target); + + if (!_this.noRotate) { + + _this.rotateCamera(); + + } + + if (!_this.noZoom) { + + _this.zoomCamera(); + + } + + if (!_this.noPan) { + + _this.panCamera(); + + } + + _this.object.position.addVectors(_this.target, _eye); + + _this.checkDistances(); + + _this.object.lookAt(_this.target); + + if (lastPosition.distanceToSquared(_this.object.position) > EPS) { + + _this.dispatchEvent(changeEvent); + + lastPosition.copy(_this.object.position); + + } + + }; + + this.reset = function () { + + _state = STATE.NONE; + _prevState = STATE.NONE; + + _this.target.copy(_this.target0); + _this.object.position.copy(_this.position0); + _this.object.up.copy(_this.up0); + + _eye.subVectors(_this.object.position, _this.target); + + _this.object.lookAt(_this.target); + + _this.dispatchEvent(changeEvent); + + lastPosition.copy(_this.object.position); + + }; + + // listeners + + function keydown(event) { + + if (_this.enabled === false) return; + + currentWindow.removeEventListener('keydown', keydown); + + _prevState = _state; + + if (_state !== STATE.NONE) { + + return; + + } else if (event.keyCode === _this.keys[STATE.ROTATE] && !_this.noRotate) { + + _state = STATE.ROTATE; + + } else if (event.keyCode === _this.keys[STATE.ZOOM] && !_this.noZoom) { + + _state = STATE.ZOOM; + + } else if (event.keyCode === _this.keys[STATE.PAN] && !_this.noPan) { + + _state = STATE.PAN; + + } + + } + + function keyup(event) { + + if (_this.enabled === false) return; + + _state = _prevState; + + currentWindow.addEventListener('keydown', keydown, false); + + } + + function mousedown(event) { + + if (_this.enabled === false) return; + + event.preventDefault(); + event.stopPropagation(); + + if (_state === STATE.NONE) { + + _state = event.button; + + } + + if (_state === STATE.ROTATE && !_this.noRotate) { + + _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); + _movePrev.copy(_moveCurr); + + } else if (_state === STATE.ZOOM && !_this.noZoom) { + + _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY)); + _zoomEnd.copy(_zoomStart); + + } else if (_state === STATE.PAN && !_this.noPan) { + + _panStart.copy(getMouseOnScreen(event.pageX, event.pageY)); + _panEnd.copy(_panStart); + + } + + currentDocument.addEventListener('mousemove', mousemove, false); + currentDocument.addEventListener('mouseup', mouseup, false); + + _this.dispatchEvent(startEvent); + + } + + function mousemove(event) { + + if (_this.enabled === false) return; + + event.preventDefault(); + event.stopPropagation(); + + if (_state === STATE.ROTATE && !_this.noRotate) { + + _movePrev.copy(_moveCurr); + _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); + + } else if (_state === STATE.ZOOM && !_this.noZoom) { + + _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); + + } else if (_state === STATE.PAN && !_this.noPan) { + + _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); + + } + + } + + function mouseup(event) { + + if (_this.enabled === false) return; + + event.preventDefault(); + event.stopPropagation(); + + _state = STATE.NONE; + + currentDocument.removeEventListener('mousemove', mousemove); + currentDocument.removeEventListener('mouseup', mouseup); + _this.dispatchEvent(endEvent); + + } + + function mousewheel(event) { + + if (_this.enabled === false) return; + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if (event.wheelDelta) { + + // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta / 40; + + } else if (event.detail) { + + // Firefox + + delta = -event.detail / 3; + + } + + _zoomStart.y += delta * 0.01; + _this.dispatchEvent(startEvent); + _this.dispatchEvent(endEvent); + + } + + function touchstart(event) { + + if (_this.enabled === false) return; + + switch (event.touches.length) { + + case 1: + _state = STATE.TOUCH_ROTATE; + _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)); + _movePrev.copy(_moveCurr); + break; + + case 2: + _state = STATE.TOUCH_ZOOM_PAN; + var dx = event.touches[0].pageX - event.touches[1].pageX; + var dy = event.touches[0].pageY - event.touches[1].pageY; + _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy); + + var x = (event.touches[0].pageX + event.touches[1].pageX) / 2; + var y = (event.touches[0].pageY + event.touches[1].pageY) / 2; + _panStart.copy(getMouseOnScreen(x, y)); + _panEnd.copy(_panStart); + break; + + default: + _state = STATE.NONE; + + } + _this.dispatchEvent(startEvent); + + + } + + function touchmove(event) { + + if (_this.enabled === false) return; + + event.preventDefault(); + event.stopPropagation(); + + switch (event.touches.length) { + + case 1: + _movePrev.copy(_moveCurr); + _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)); + break; + + case 2: + var dx = event.touches[0].pageX - event.touches[1].pageX; + var dy = event.touches[0].pageY - event.touches[1].pageY; + _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy); + + var x = (event.touches[0].pageX + event.touches[1].pageX) / 2; + var y = (event.touches[0].pageY + event.touches[1].pageY) / 2; + _panEnd.copy(getMouseOnScreen(x, y)); + break; + + default: + _state = STATE.NONE; + + } + + } + + function touchend(event) { + + if (_this.enabled === false) return; + + switch (event.touches.length) { + + case 1: + _movePrev.copy(_moveCurr); + _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)); + break; + + case 2: + _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; + + var x = (event.touches[0].pageX + event.touches[1].pageX) / 2; + var y = (event.touches[0].pageY + event.touches[1].pageY) / 2; + _panEnd.copy(getMouseOnScreen(x, y)); + _panStart.copy(_panEnd); + break; + + } + + _state = STATE.NONE; + _this.dispatchEvent(endEvent); + + } + + function contextmenu(event) { + + event.preventDefault(); + + } + + this.dispose = function () { + this.domElement.removeEventListener('contextmenu', contextmenu, false); + this.domElement.removeEventListener('mousedown', mousedown, false); + this.domElement.removeEventListener('mousewheel', mousewheel, false); + this.domElement.removeEventListener('MozMousePixelScroll', mousewheel, false); // firefox + + this.domElement.removeEventListener('touchstart', touchstart, false); + this.domElement.removeEventListener('touchend', touchend, false); + this.domElement.removeEventListener('touchmove', touchmove, false); + + currentDocument.removeEventListener('mousemove', mousemove, false); + currentDocument.removeEventListener('mouseup', mouseup, false); + + currentWindow.removeEventListener('keydown', keydown, false); + currentWindow.removeEventListener('keyup', keyup, false); + }; + + this.domElement.addEventListener('contextmenu', contextmenu, false); + this.domElement.addEventListener('mousedown', mousedown, false); + this.domElement.addEventListener('mousewheel', mousewheel, false); + this.domElement.addEventListener('MozMousePixelScroll', mousewheel, false); // firefox + + this.domElement.addEventListener('touchstart', touchstart, false); + this.domElement.addEventListener('touchend', touchend, false); + this.domElement.addEventListener('touchmove', touchmove, false); + + currentWindow.addEventListener('keydown', keydown, false); + currentWindow.addEventListener('keyup', keyup, false); + + // force an update at start + this.handleResize(); + this.update(); + + }; + + THREE.TrackballControls.prototype = Object.create(THREE.EventDispatcher.prototype); + THREE.TrackballControls.prototype.constructor = THREE.TrackballControls; +}; \ No newline at end of file diff --git a/js/src/providers/threejs/helpers/TrackballControls.js b/js/src/providers/threejs/helpers/TrackballControls.js deleted file mode 100644 index a7d8e3ec..00000000 --- a/js/src/providers/threejs/helpers/TrackballControls.js +++ /dev/null @@ -1,644 +0,0 @@ -// jshint ignore: start -// jscs:disable - -/** - * @author Eberhard Graether / http://egraether.com/ - * @author Mark Lundin / http://mark-lundin.com - * @author Simone Manini / http://daron1337.github.io - * @author Luca Antiga / http://lantiga.github.io - */ - -THREE.TrackballControls = function (object, domElement) { - - var _this = this; - var STATE = {NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4}; - var currentWindow, currentDocument; - - if (domElement !== undefined) { - currentWindow = domElement.ownerDocument.defaultView || domElement.ownerDocument.parentWindow; - this.domElement = domElement; - currentDocument = domElement.ownerDocument; - } else { - currentWindow = window; - this.domElement = currentDocument = currentWindow.document; - } - - this.object = object; - - // API - - this.enabled = true; - - this.screen = {left: 0, top: 0, width: 0, height: 0}; - - this.rotateSpeed = 1.0; - this.zoomSpeed = 1.2; - this.panSpeed = 0.3; - - this.noRotate = false; - this.noZoom = false; - this.noPan = false; - - this.staticMoving = false; - this.dynamicDampingFactor = 0.2; - - this.minDistance = 0; - this.maxDistance = Infinity; - - this.keys = [65 /*A*/, 83 /*S*/, 68 /*D*/]; - - // internals - - this.target = new THREE.Vector3(); - - // var EPS = 0.000001; - var EPS = 0.0000000001; - - var lastPosition = new THREE.Vector3(); - - var _state = STATE.NONE, - _prevState = STATE.NONE, - - _eye = new THREE.Vector3(), - - _movePrev = new THREE.Vector2(), - _moveCurr = new THREE.Vector2(), - - _lastAxis = new THREE.Vector3(), - _lastAngle = 0, - - _zoomStart = new THREE.Vector2(), - _zoomEnd = new THREE.Vector2(), - - _touchZoomDistanceStart = 0, - _touchZoomDistanceEnd = 0, - - _panStart = new THREE.Vector2(), - _panEnd = new THREE.Vector2(); - - // for reset - - this.target0 = this.target.clone(); - this.position0 = this.object.position.clone(); - this.up0 = this.object.up.clone(); - - // events - - var changeEvent = {type: 'change'}; - var startEvent = {type: 'start'}; - var endEvent = {type: 'end'}; - - - // methods - - this.handleResize = function () { - - if (this.domElement === currentDocument) { - - this.screen.left = 0; - this.screen.top = 0; - this.screen.width = currentWindow.innerWidth; - this.screen.height = currentWindow.innerHeight; - - } else { - - var box = this.domElement.getBoundingClientRect(); - // adjustments come from similar code in the jquery offset() function - var d = this.domElement.ownerDocument.documentElement; - this.screen.left = box.left + currentWindow.pageXOffset - d.clientLeft; - this.screen.top = box.top + currentWindow.pageYOffset - d.clientTop; - this.screen.width = box.width; - this.screen.height = box.height; - - } - - }; - - this.handleEvent = function (event) { - - if (typeof this[event.type] == 'function') { - - this[event.type](event); - - } - - }; - - var getMouseOnScreen = ( function () { - - var vector = new THREE.Vector2(); - - return function getMouseOnScreen(pageX, pageY) { - - vector.set( - ( pageX - _this.screen.left ) / _this.screen.width, - ( pageY - _this.screen.top ) / _this.screen.height - ); - - return vector; - - }; - - }() ); - - var getMouseOnCircle = ( function () { - - var vector = new THREE.Vector2(); - - return function getMouseOnCircle(pageX, pageY) { - - vector.set( - ( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ), - ( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional - ); - - return vector; - - }; - - }() ); - - this.rotateCamera = ( function () { - - var axis = new THREE.Vector3(), - quaternion = new THREE.Quaternion(), - eyeDirection = new THREE.Vector3(), - objectUpDirection = new THREE.Vector3(), - objectSidewaysDirection = new THREE.Vector3(), - moveDirection = new THREE.Vector3(), - angle; - - return function rotateCamera() { - - moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0); - angle = moveDirection.length(); - - if (angle) { - - _eye.copy(_this.object.position).sub(_this.target); - - eyeDirection.copy(_eye).normalize(); - objectUpDirection.copy(_this.object.up).normalize(); - objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize(); - - objectUpDirection.setLength(_moveCurr.y - _movePrev.y); - objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x); - - moveDirection.copy(objectUpDirection.add(objectSidewaysDirection)); - - axis.crossVectors(moveDirection, _eye).normalize(); - - angle *= _this.rotateSpeed; - quaternion.setFromAxisAngle(axis, angle); - - _eye.applyQuaternion(quaternion); - _this.object.up.applyQuaternion(quaternion); - - _lastAxis.copy(axis); - _lastAngle = angle; - - } else if (!_this.staticMoving && _lastAngle) { - - _lastAngle *= Math.sqrt(1.0 - _this.dynamicDampingFactor); - _eye.copy(_this.object.position).sub(_this.target); - quaternion.setFromAxisAngle(_lastAxis, _lastAngle); - _eye.applyQuaternion(quaternion); - _this.object.up.applyQuaternion(quaternion); - - } - - _movePrev.copy(_moveCurr); - - }; - - }() ); - - - this.zoomCamera = function () { - - var factor; - - if (_state === STATE.TOUCH_ZOOM_PAN) { - - factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; - _touchZoomDistanceStart = _touchZoomDistanceEnd; - _eye.multiplyScalar(factor); - - } else { - - factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; - - if (factor !== 1.0 && factor > 0.0) { - - _eye.multiplyScalar(factor); - - if (_this.staticMoving) { - - _zoomStart.copy(_zoomEnd); - - } else { - - _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; - - } - - } - - } - - }; - - this.panCamera = ( function () { - - var mouseChange = new THREE.Vector2(), - objectUp = new THREE.Vector3(), - pan = new THREE.Vector3(); - - return function panCamera() { - - mouseChange.copy(_panEnd).sub(_panStart); - - if (mouseChange.lengthSq()) { - - mouseChange.multiplyScalar(_eye.length() * _this.panSpeed); - - pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x); - pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y)); - - _this.object.position.add(pan); - _this.target.add(pan); - - if (_this.staticMoving) { - - _panStart.copy(_panEnd); - - } else { - - _panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(_this.dynamicDampingFactor)); - - } - - } - - }; - - }() ); - - this.checkDistances = function () { - - if (!_this.noZoom || !_this.noPan) { - - if (_eye.lengthSq() > _this.maxDistance * _this.maxDistance) { - - _this.object.position.addVectors(_this.target, _eye.setLength(_this.maxDistance)); - _zoomStart.copy(_zoomEnd); - - } - - if (_eye.lengthSq() < _this.minDistance * _this.minDistance) { - - _this.object.position.addVectors(_this.target, _eye.setLength(_this.minDistance)); - _zoomStart.copy(_zoomEnd); - - } - - } - - }; - - this.update = function () { - - _eye.subVectors(_this.object.position, _this.target); - - if (!_this.noRotate) { - - _this.rotateCamera(); - - } - - if (!_this.noZoom) { - - _this.zoomCamera(); - - } - - if (!_this.noPan) { - - _this.panCamera(); - - } - - _this.object.position.addVectors(_this.target, _eye); - - _this.checkDistances(); - - _this.object.lookAt(_this.target); - - if (lastPosition.distanceToSquared(_this.object.position) > EPS) { - - _this.dispatchEvent(changeEvent); - - lastPosition.copy(_this.object.position); - - } - - }; - - this.reset = function () { - - _state = STATE.NONE; - _prevState = STATE.NONE; - - _this.target.copy(_this.target0); - _this.object.position.copy(_this.position0); - _this.object.up.copy(_this.up0); - - _eye.subVectors(_this.object.position, _this.target); - - _this.object.lookAt(_this.target); - - _this.dispatchEvent(changeEvent); - - lastPosition.copy(_this.object.position); - - }; - - // listeners - - function keydown(event) { - - if (_this.enabled === false) return; - - currentWindow.removeEventListener('keydown', keydown); - - _prevState = _state; - - if (_state !== STATE.NONE) { - - return; - - } else if (event.keyCode === _this.keys[STATE.ROTATE] && !_this.noRotate) { - - _state = STATE.ROTATE; - - } else if (event.keyCode === _this.keys[STATE.ZOOM] && !_this.noZoom) { - - _state = STATE.ZOOM; - - } else if (event.keyCode === _this.keys[STATE.PAN] && !_this.noPan) { - - _state = STATE.PAN; - - } - - } - - function keyup(event) { - - if (_this.enabled === false) return; - - _state = _prevState; - - currentWindow.addEventListener('keydown', keydown, false); - - } - - function mousedown(event) { - - if (_this.enabled === false) return; - - event.preventDefault(); - event.stopPropagation(); - - if (_state === STATE.NONE) { - - _state = event.button; - - } - - if (_state === STATE.ROTATE && !_this.noRotate) { - - _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); - _movePrev.copy(_moveCurr); - - } else if (_state === STATE.ZOOM && !_this.noZoom) { - - _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY)); - _zoomEnd.copy(_zoomStart); - - } else if (_state === STATE.PAN && !_this.noPan) { - - _panStart.copy(getMouseOnScreen(event.pageX, event.pageY)); - _panEnd.copy(_panStart); - - } - - currentDocument.addEventListener('mousemove', mousemove, false); - currentDocument.addEventListener('mouseup', mouseup, false); - - _this.dispatchEvent(startEvent); - - } - - function mousemove(event) { - - if (_this.enabled === false) return; - - event.preventDefault(); - event.stopPropagation(); - - if (_state === STATE.ROTATE && !_this.noRotate) { - - _movePrev.copy(_moveCurr); - _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); - - } else if (_state === STATE.ZOOM && !_this.noZoom) { - - _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); - - } else if (_state === STATE.PAN && !_this.noPan) { - - _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); - - } - - } - - function mouseup(event) { - - if (_this.enabled === false) return; - - event.preventDefault(); - event.stopPropagation(); - - _state = STATE.NONE; - - currentDocument.removeEventListener('mousemove', mousemove); - currentDocument.removeEventListener('mouseup', mouseup); - _this.dispatchEvent(endEvent); - - } - - function mousewheel(event) { - - if (_this.enabled === false) return; - - event.preventDefault(); - event.stopPropagation(); - - var delta = 0; - - if (event.wheelDelta) { - - // WebKit / Opera / Explorer 9 - - delta = event.wheelDelta / 40; - - } else if (event.detail) { - - // Firefox - - delta = -event.detail / 3; - - } - - _zoomStart.y += delta * 0.01; - _this.dispatchEvent(startEvent); - _this.dispatchEvent(endEvent); - - } - - function touchstart(event) { - - if (_this.enabled === false) return; - - switch (event.touches.length) { - - case 1: - _state = STATE.TOUCH_ROTATE; - _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)); - _movePrev.copy(_moveCurr); - break; - - case 2: - _state = STATE.TOUCH_ZOOM_PAN; - var dx = event.touches[0].pageX - event.touches[1].pageX; - var dy = event.touches[0].pageY - event.touches[1].pageY; - _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy); - - var x = ( event.touches[0].pageX + event.touches[1].pageX ) / 2; - var y = ( event.touches[0].pageY + event.touches[1].pageY ) / 2; - _panStart.copy(getMouseOnScreen(x, y)); - _panEnd.copy(_panStart); - break; - - default: - _state = STATE.NONE; - - } - _this.dispatchEvent(startEvent); - - - } - - function touchmove(event) { - - if (_this.enabled === false) return; - - event.preventDefault(); - event.stopPropagation(); - - switch (event.touches.length) { - - case 1: - _movePrev.copy(_moveCurr); - _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)); - break; - - case 2: - var dx = event.touches[0].pageX - event.touches[1].pageX; - var dy = event.touches[0].pageY - event.touches[1].pageY; - _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy); - - var x = ( event.touches[0].pageX + event.touches[1].pageX ) / 2; - var y = ( event.touches[0].pageY + event.touches[1].pageY ) / 2; - _panEnd.copy(getMouseOnScreen(x, y)); - break; - - default: - _state = STATE.NONE; - - } - - } - - function touchend(event) { - - if (_this.enabled === false) return; - - switch (event.touches.length) { - - case 1: - _movePrev.copy(_moveCurr); - _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)); - break; - - case 2: - _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; - - var x = ( event.touches[0].pageX + event.touches[1].pageX ) / 2; - var y = ( event.touches[0].pageY + event.touches[1].pageY ) / 2; - _panEnd.copy(getMouseOnScreen(x, y)); - _panStart.copy(_panEnd); - break; - - } - - _state = STATE.NONE; - _this.dispatchEvent(endEvent); - - } - - function contextmenu(event) { - - event.preventDefault(); - - } - - this.dispose = function () { - this.domElement.removeEventListener('contextmenu', contextmenu, false); - this.domElement.removeEventListener('mousedown', mousedown, false); - this.domElement.removeEventListener('mousewheel', mousewheel, false); - this.domElement.removeEventListener('MozMousePixelScroll', mousewheel, false); // firefox - - this.domElement.removeEventListener('touchstart', touchstart, false); - this.domElement.removeEventListener('touchend', touchend, false); - this.domElement.removeEventListener('touchmove', touchmove, false); - - currentDocument.removeEventListener('mousemove', mousemove, false); - currentDocument.removeEventListener('mouseup', mouseup, false); - - currentWindow.removeEventListener('keydown', keydown, false); - currentWindow.removeEventListener('keyup', keyup, false); - }; - - this.domElement.addEventListener('contextmenu', contextmenu, false); - this.domElement.addEventListener('mousedown', mousedown, false); - this.domElement.addEventListener('mousewheel', mousewheel, false); - this.domElement.addEventListener('MozMousePixelScroll', mousewheel, false); // firefox - - this.domElement.addEventListener('touchstart', touchstart, false); - this.domElement.addEventListener('touchend', touchend, false); - this.domElement.addEventListener('touchmove', touchmove, false); - - currentWindow.addEventListener('keydown', keydown, false); - currentWindow.addEventListener('keyup', keyup, false); - - // force an update at start - this.handleResize(); - this.update(); - -}; - -THREE.TrackballControls.prototype = Object.create(THREE.EventDispatcher.prototype); -THREE.TrackballControls.prototype.constructor = THREE.TrackballControls; diff --git a/js/src/providers/threejs/helpers/Voxels.js b/js/src/providers/threejs/helpers/Voxels.js index 1ca8f4be..feae0391 100644 --- a/js/src/providers/threejs/helpers/Voxels.js +++ b/js/src/providers/threejs/helpers/Voxels.js @@ -1,6 +1,7 @@ 'use strict'; -var MeshLine = require('./THREE.MeshLine'), +var THREE = require('three'), + MeshLine = require('./THREE.MeshLine')(THREE), yieldingLoop = require('./../../../core/lib/helpers/yieldingLoop'), voxelMeshGenerator = require('./../../../core/lib/helpers/voxelMeshGenerator'), interactionsVoxels = require('./../interactions/Voxels'), diff --git a/js/src/providers/threejs/initializers/Camera.js b/js/src/providers/threejs/initializers/Camera.js index 4b120faf..1d957ee9 100644 --- a/js/src/providers/threejs/initializers/Camera.js +++ b/js/src/providers/threejs/initializers/Camera.js @@ -1,5 +1,7 @@ 'use strict'; +var THREE = require('three'); + /** * Camera initializer for Three.js library * @this K3D.Core~world diff --git a/js/src/providers/threejs/initializers/Canvas.js b/js/src/providers/threejs/initializers/Canvas.js index 401c5128..16d7eadc 100644 --- a/js/src/providers/threejs/initializers/Canvas.js +++ b/js/src/providers/threejs/initializers/Canvas.js @@ -1,4 +1,7 @@ 'use strict'; + +var THREE = require('three'); + /** * Canvas initializer for Three.js library * @this K3D.Core~world @@ -80,5 +83,9 @@ module.exports = function (K3D) { K3D.dispatch(K3D.events.CAMERA_CHANGE, r); }); + K3D.on(K3D.events.RESIZED, function () { + self.controls.handleResize(); + }); + refresh(); }; diff --git a/js/src/providers/threejs/initializers/Renderer.js b/js/src/providers/threejs/initializers/Renderer.js index bc75811b..cff78a58 100644 --- a/js/src/providers/threejs/initializers/Renderer.js +++ b/js/src/providers/threejs/initializers/Renderer.js @@ -1,5 +1,7 @@ 'use strict'; -var getSSAAChunkedRender = require('./../helpers/SSAAChunkedRender'); + +var THREE = require('three'), + getSSAAChunkedRender = require('./../helpers/SSAAChunkedRender'); /** * @memberof K3D.Providers.ThreeJS.Initializers diff --git a/js/src/providers/threejs/initializers/Scene.js b/js/src/providers/threejs/initializers/Scene.js index 74c9b07c..71b24452 100644 --- a/js/src/providers/threejs/initializers/Scene.js +++ b/js/src/providers/threejs/initializers/Scene.js @@ -1,7 +1,8 @@ 'use strict'; -var Text = require('./../objects/Text'), - MeshLine = require('./../helpers/THREE.MeshLine'), +var THREE = require('three'), + Text = require('./../objects/Text'), + MeshLine = require('./../helpers/THREE.MeshLine')(THREE), viewModes = require('./../../../core/lib/viewMode').viewModes, pow10ceil = require('./../../../core/lib/helpers/math').pow10ceil; @@ -553,5 +554,17 @@ module.exports = { } } }); + + K3D.on(K3D.events.RESIZED, function () { + // update outlines + Object.keys(grids.planes).forEach(function (axis) { + grids.planes[axis].forEach(function (plane) { + var objResolution = plane.obj.material.uniforms.resolution; + + objResolution.value.x = K3D.getWorld().width; + objResolution.value.y = K3D.getWorld().height; + }, this); + }, this); + }); } }; diff --git a/js/src/providers/threejs/interactions/Voxels.js b/js/src/providers/threejs/interactions/Voxels.js index c3d68de1..2afb5f95 100644 --- a/js/src/providers/threejs/interactions/Voxels.js +++ b/js/src/providers/threejs/interactions/Voxels.js @@ -1,6 +1,7 @@ 'use strict'; -var viewModes = require('./../../../core/lib/viewMode').viewModes; +var THREE = require('three'), + viewModes = require('./../../../core/lib/viewMode').viewModes; /** * Interactions handlers for Voxels object * @method Voxels diff --git a/js/src/providers/threejs/objects/LineMesh.js b/js/src/providers/threejs/objects/LineMesh.js index 66bf19d4..b667e332 100644 --- a/js/src/providers/threejs/objects/LineMesh.js +++ b/js/src/providers/threejs/objects/LineMesh.js @@ -1,6 +1,7 @@ 'use strict'; -var Fn = require('./../helpers/Fn'), +var THREE = require('three'), + Fn = require('./../helpers/Fn'), colorsToFloat32Array = require('./../../../core/lib/helpers/buffer').colorsToFloat32Array, streamLine = require('./../helpers/Streamline'), handleColorMap = Fn.handleColorMap; diff --git a/js/src/providers/threejs/objects/LineSimple.js b/js/src/providers/threejs/objects/LineSimple.js index c59a162f..7f91aeae 100644 --- a/js/src/providers/threejs/objects/LineSimple.js +++ b/js/src/providers/threejs/objects/LineSimple.js @@ -1,6 +1,7 @@ 'use strict'; -var colorsToFloat32Array = require('./../../../core/lib/helpers/buffer').colorsToFloat32Array, +var THREE = require('three'), + colorsToFloat32Array = require('./../../../core/lib/helpers/buffer').colorsToFloat32Array, Fn = require('./../helpers/Fn'), getColorsArray = Fn.getColorsArray, handleColorMap = Fn.handleColorMap; diff --git a/js/src/providers/threejs/objects/LineThick.js b/js/src/providers/threejs/objects/LineThick.js index 9f38ab3b..faf3bb64 100644 --- a/js/src/providers/threejs/objects/LineThick.js +++ b/js/src/providers/threejs/objects/LineThick.js @@ -1,8 +1,8 @@ 'use strict'; -var colorsToFloat32Array = require('./../../../core/lib/helpers/buffer').colorsToFloat32Array, -// diff = require('./../../../core/lib/helpers/diff').diff, - MeshLine = require('./../helpers/THREE.MeshLine'), +var THREE = require('three'), + colorsToFloat32Array = require('./../../../core/lib/helpers/buffer').colorsToFloat32Array, + MeshLine = require('./../helpers/THREE.MeshLine')(THREE), Fn = require('./../helpers/Fn'), lut = require('./../../../core/lib/helpers/lut'), getColorsArray = Fn.getColorsArray; diff --git a/js/src/providers/threejs/objects/MarchingCubes.js b/js/src/providers/threejs/objects/MarchingCubes.js index 5ec4da03..402383a9 100644 --- a/js/src/providers/threejs/objects/MarchingCubes.js +++ b/js/src/providers/threejs/objects/MarchingCubes.js @@ -1,6 +1,7 @@ 'use strict'; -var marchingCubesPolygonise = require('./../../../core/lib/helpers/marchingCubesPolygonise'), +var THREE = require('three'), + marchingCubesPolygonise = require('./../../../core/lib/helpers/marchingCubesPolygonise'), yieldingLoop = require('./../../../core/lib/helpers/yieldingLoop'); /** * Loader strategy to handle Marching Cubes object diff --git a/js/src/providers/threejs/objects/Mesh.js b/js/src/providers/threejs/objects/Mesh.js index f7479692..ea1b83bc 100644 --- a/js/src/providers/threejs/objects/Mesh.js +++ b/js/src/providers/threejs/objects/Mesh.js @@ -1,6 +1,7 @@ 'use strict'; -var handleColorMap = require('./../helpers/Fn').handleColorMap; +var THREE = require('three'), + handleColorMap = require('./../helpers/Fn').handleColorMap; /** * Loader strategy to handle Mesh object diff --git a/js/src/providers/threejs/objects/PointsBillboard.js b/js/src/providers/threejs/objects/PointsBillboard.js index e80829ba..19642529 100644 --- a/js/src/providers/threejs/objects/PointsBillboard.js +++ b/js/src/providers/threejs/objects/PointsBillboard.js @@ -1,6 +1,7 @@ 'use strict'; -var buffer = require('./../../../core/lib/helpers/buffer'), +var THREE = require('three'), + buffer = require('./../../../core/lib/helpers/buffer'), Fn = require('./../helpers/Fn'), getColorsArray = Fn.getColorsArray; diff --git a/js/src/providers/threejs/objects/PointsMesh.js b/js/src/providers/threejs/objects/PointsMesh.js index 3e73a3e1..8320f9c8 100644 --- a/js/src/providers/threejs/objects/PointsMesh.js +++ b/js/src/providers/threejs/objects/PointsMesh.js @@ -1,6 +1,7 @@ 'use strict'; -var buffer = require('./../../../core/lib/helpers/buffer'), +var THREE = require('three'), + buffer = require('./../../../core/lib/helpers/buffer'), Fn = require('./../helpers/Fn'), getColorsArray = Fn.getColorsArray; diff --git a/js/src/providers/threejs/objects/STL.js b/js/src/providers/threejs/objects/STL.js index efc8c819..ddbb4171 100644 --- a/js/src/providers/threejs/objects/STL.js +++ b/js/src/providers/threejs/objects/STL.js @@ -1,5 +1,7 @@ 'use strict'; +var THREE = require('three'); + /** * Loader strategy to handle STL object * @method STL diff --git a/js/src/providers/threejs/objects/Surface.js b/js/src/providers/threejs/objects/Surface.js index 04bb05be..244a31cd 100644 --- a/js/src/providers/threejs/objects/Surface.js +++ b/js/src/providers/threejs/objects/Surface.js @@ -1,5 +1,7 @@ 'use strict'; +var THREE = require('three'); + /** * Loader strategy to handle Surface object * @method MarchingCubes diff --git a/js/src/providers/threejs/objects/Text.js b/js/src/providers/threejs/objects/Text.js index ecff82d4..76c5e961 100644 --- a/js/src/providers/threejs/objects/Text.js +++ b/js/src/providers/threejs/objects/Text.js @@ -1,6 +1,7 @@ 'use strict'; -var katex = require('katex'); +var THREE = require('three'), + katex = require('katex'); /** * Loader strategy to handle LaTex object diff --git a/js/src/providers/threejs/objects/Text2d.js b/js/src/providers/threejs/objects/Text2d.js index 50a6cc19..9642923a 100644 --- a/js/src/providers/threejs/objects/Text2d.js +++ b/js/src/providers/threejs/objects/Text2d.js @@ -1,6 +1,7 @@ 'use strict'; -var katex = require('katex'); +var THREE = require('three'), + katex = require('katex'); /** * Loader strategy to handle LaTex object diff --git a/js/src/providers/threejs/objects/TextureData.js b/js/src/providers/threejs/objects/TextureData.js index b752207a..2f8f855e 100644 --- a/js/src/providers/threejs/objects/TextureData.js +++ b/js/src/providers/threejs/objects/TextureData.js @@ -1,6 +1,7 @@ 'use strict'; -var lut = require('./../../../core/lib/helpers/lut'), +var THREE = require('three'), + lut = require('./../../../core/lib/helpers/lut'), typedArrayToThree = require('./../helpers/Fn').typedArrayToThree; /** diff --git a/js/src/providers/threejs/objects/TextureImage.js b/js/src/providers/threejs/objects/TextureImage.js index d5e8fd7a..685ec95d 100644 --- a/js/src/providers/threejs/objects/TextureImage.js +++ b/js/src/providers/threejs/objects/TextureImage.js @@ -1,6 +1,7 @@ 'use strict'; -var buffer = require('./../../../core/lib/helpers/buffer'); +var THREE = require('three'), + buffer = require('./../../../core/lib/helpers/buffer'); /** * Loader strategy to handle Texture object diff --git a/js/src/providers/threejs/objects/TextureText.js b/js/src/providers/threejs/objects/TextureText.js index 4a400828..4d90376e 100644 --- a/js/src/providers/threejs/objects/TextureText.js +++ b/js/src/providers/threejs/objects/TextureText.js @@ -1,6 +1,7 @@ 'use strict'; -var closestPowOfTwo = require('./../helpers/Fn').closestPowOfTwo; +var THREE = require('three'), + closestPowOfTwo = require('./../helpers/Fn').closestPowOfTwo; /** * Loader strategy to handle Text object diff --git a/js/src/providers/threejs/objects/TorusKnot.js b/js/src/providers/threejs/objects/TorusKnot.js index 30a3a234..1ea41702 100644 --- a/js/src/providers/threejs/objects/TorusKnot.js +++ b/js/src/providers/threejs/objects/TorusKnot.js @@ -1,4 +1,7 @@ 'use strict'; + +var THREE = require('three'); + /** * Loader strategy to handle TorusKnot object * @method TorusKnot diff --git a/js/src/providers/threejs/objects/VectorField.js b/js/src/providers/threejs/objects/VectorField.js index 5670e36c..5a63974c 100644 --- a/js/src/providers/threejs/objects/VectorField.js +++ b/js/src/providers/threejs/objects/VectorField.js @@ -1,7 +1,8 @@ 'use strict'; -var buffer = require('./../../../core/lib/helpers/buffer'), - MeshLine = require('./../helpers/THREE.MeshLine'), +var THREE = require('three'), + buffer = require('./../../../core/lib/helpers/buffer'), + MeshLine = require('./../helpers/THREE.MeshLine')(THREE), getTwoColorsArray = require('./../helpers/Fn').getTwoColorsArray, generateArrow = require('./../helpers/Fn').generateArrow; diff --git a/js/src/providers/threejs/objects/Vectors.js b/js/src/providers/threejs/objects/Vectors.js index 4eea70c6..77cc03d3 100644 --- a/js/src/providers/threejs/objects/Vectors.js +++ b/js/src/providers/threejs/objects/Vectors.js @@ -1,7 +1,8 @@ 'use strict'; -var buffer = require('./../../../core/lib/helpers/buffer'), - MeshLine = require('./../helpers/THREE.MeshLine'), +var THREE = require('three'), + buffer = require('./../../../core/lib/helpers/buffer'), + MeshLine = require('./../helpers/THREE.MeshLine')(THREE), getTwoColorsArray = require('./../helpers/Fn').getTwoColorsArray, generateArrow = require('./../helpers/Fn').generateArrow, Text = require('./Text'); diff --git a/js/src/providers/threejs/objects/Volume.js b/js/src/providers/threejs/objects/Volume.js index 68cdd3c2..990834b8 100644 --- a/js/src/providers/threejs/objects/Volume.js +++ b/js/src/providers/threejs/objects/Volume.js @@ -2,7 +2,8 @@ 'use strict'; -var lut = require('./../../../core/lib/helpers/lut'), +var THREE = require('three'), + lut = require('./../../../core/lib/helpers/lut'), closestPowOfTwo = require('./../helpers/Fn').closestPowOfTwo, typedArrayToThree = require('./../helpers/Fn').typedArrayToThree; diff --git a/js/src/providers/threejs/provider.js b/js/src/providers/threejs/provider.js index 9018f8db..5c78ebf1 100644 --- a/js/src/providers/threejs/provider.js +++ b/js/src/providers/threejs/provider.js @@ -1,14 +1,11 @@ 'use strict'; -window.THREE = require('three'); +var THREE = require('three'); -require('three-octree'); -require('./../../../node_modules/three/examples/js/loaders/STLLoader'); -require('./../../../node_modules/three/examples/js/shaders/CopyShader'); -require('./../../../node_modules/three/examples/js/postprocessing/EffectComposer'); -require('./../../../node_modules/three/examples/js/postprocessing/SSAARenderPass'); - -require('./helpers/TrackballControls'); +require('./helpers/THREE.Octree')(THREE); +require('./helpers/THREE.STLLoader')(THREE); +require('./helpers/THREE.CopyShader')(THREE); +require('./helpers/THREE.TrackballControls')(THREE); /** * K3D ThreeJS Provider namespace diff --git a/js/src/version.js b/js/src/version.js index 2c7f33b9..8478efbe 100644 --- a/js/src/version.js +++ b/js/src/version.js @@ -1,17 +1,6 @@ // Import package data to define it only one place var pkg = require('../package.json'); -/** - * The version of the attribute spec that this package - * implements. This is the value used in - * _model_module_version/_view_module_version. - * - * Update this value when attributes are added/removed from - * your models, or serialized format changes. - */ -var EXTENSION_SPEC_VERSION = '3.0.0'; - module.exports = { - version: pkg.version, - EXTENSION_SPEC_VERSION: EXTENSION_SPEC_VERSION + version: pkg.version }; diff --git a/k3d/_frontend.py b/k3d/_frontend.py deleted file mode 100644 index 109cf900..00000000 --- a/k3d/_frontend.py +++ /dev/null @@ -1,8 +0,0 @@ - -# The version of the attribute spec that this package -# implements. This is the value used in -# _model_module_version/_view_module_version. -# -# Update this value when attributes are added/removed from -# your models, or serialized format changes. -EXTENSION_SPEC_VERSION = '^3.0.0' diff --git a/k3d/_version.py b/k3d/_version.py index 5a64136b..9f004f2a 100644 --- a/k3d/_version.py +++ b/k3d/_version.py @@ -1,2 +1,2 @@ -version_info = (2, 6, 1) +version_info = (2, 6, 2) __version__ = '.'.join(map(str, version_info)) diff --git a/k3d/objects.py b/k3d/objects.py index f7c7ec1c..3b52b00d 100644 --- a/k3d/objects.py +++ b/k3d/objects.py @@ -3,9 +3,9 @@ from traitlets import validate, TraitError from traittypes import Array from .helpers import array_serialization_wrap -from ._frontend import EXTENSION_SPEC_VERSION import numpy as np from ipydatawidgets import DataUnion, data_union_serialization +from ._version import __version__ as version EPSILON = np.finfo(np.float32).eps @@ -35,7 +35,7 @@ class VoxelChunk(widgets.Widget): _model_name = Unicode('ChunkModel').tag(sync=True) _model_module = Unicode('k3d').tag(sync=True) - _model_module_version = Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) + _model_module_version = Unicode(version).tag(sync=True) id = Int().tag(sync=True) voxels = Array(dtype=np.uint8).tag(sync=True, **array_serialization_wrap('voxels')) @@ -60,7 +60,7 @@ class Drawable(widgets.Widget): _model_name = Unicode('ObjectModel').tag(sync=True) _model_module = Unicode('k3d').tag(sync=True) - _model_module_version = Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) + _model_module_version = Unicode(version).tag(sync=True) id = Integer().tag(sync=True) name = Unicode(default_value=None, allow_none=True).tag(sync=True) diff --git a/k3d/plot.py b/k3d/plot.py index 0fb83109..a75e46f3 100644 --- a/k3d/plot.py +++ b/k3d/plot.py @@ -7,9 +7,8 @@ from traitlets import Unicode, Bool, Int, List, Float from IPython.display import display -from ._frontend import EXTENSION_SPEC_VERSION from .objects import Drawable, ListOrArray -from ._version import __version__ as BACKEND_VERSION +from ._version import __version__ as version class Plot(widgets.DOMWidget): @@ -54,10 +53,9 @@ class Plot(widgets.DOMWidget): _view_module = Unicode('k3d').tag(sync=True) _model_module = Unicode('k3d').tag(sync=True) - _view_module_version = Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) - _model_module_version = Unicode(EXTENSION_SPEC_VERSION).tag(sync=True) - - _backend_version = Unicode(BACKEND_VERSION).tag(sync=True) + _view_module_version = Unicode(version).tag(sync=True) + _model_module_version = Unicode(version).tag(sync=True) + _backend_version = Unicode(version).tag(sync=True) # readonly (specified at creation) antialias = Int(min=0, max=5).tag(sync=True)