├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── dist
├── .DS_Store
└── threebox.js
├── docs
├── SymbolLayer3D.md
├── Threebox.md
├── gallery.jpg
└── img
│ ├── features-3D-paths.png
│ ├── features-3D-symbols.png
│ ├── features-complex-buildings.png
│ ├── features-point-clouds.png
│ ├── features-shaders.png
│ └── features-terrain.png
├── examples
├── Object3D.html
├── basic.html
├── config_template.js
├── line.html
├── logistics.html
├── mercator.html
├── models
│ ├── Truck.mtl
│ └── Truck.obj
├── old
│ ├── basic.html
│ ├── flocking.html
│ └── orientation.html
├── raycaster.html
└── tube.html
├── exports.js
├── main.js
├── package-lock.json
├── package.json
├── src
├── Animation
│ └── AnimationManager.js
├── Camera
│ └── CameraSync.js
├── Threebox.js
├── Utils
│ ├── Utils.js
│ └── ValueGenerator.js
├── objects
│ ├── Object3D.js
│ ├── customLines
│ │ ├── Line2.js
│ │ ├── LineGeometry.js
│ │ ├── LineMaterial.js
│ │ ├── LineSegments2.js
│ │ ├── LineSegmentsGeometry.js
│ │ ├── Wireframe.js
│ │ └── WireframeGeometry2.js
│ ├── line.js
│ ├── loadObj.js
│ ├── loaders
│ │ ├── MTLLoader.js
│ │ └── OBJLoader.js
│ ├── objects.js
│ ├── sphere.js
│ └── tube.js
├── three.js
└── utils
│ ├── constants.js
│ ├── material.js
│ └── validate.js
└── tests
├── threebox-tests-bundle.js
├── threebox-tests.html
├── threebox-tests.js
└── unit
├── camera.test.js
├── material.test.js
├── object.test.js
├── utilities.test.js
└── validate.test.js
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.3.0
2 |
3 | #### Enhancements
4 |
5 | - Add an Object class: convenience methods to produce lines, spheres, tubes, and imported OBJ/MTL meshes, as well as a method to bring in THREE.Object3D's produced elsewhere with vanilla Three.js. Most of these are moveable, and have methods to move/rotate/rescale
6 |
7 | - No need to call `tb.update()` after putting it in the custom layer's `render()` function.
8 |
9 | #### Bug fixes
10 |
11 | - Automatically adjust for viewport size (https://github.com/peterqliu/threebox/issues/43)
12 |
13 | #### Deprecated (but still functional)
14 | - `.setupDefaultLights()` has moved to an optional `defaultLights` parameter, in the third argument for Threebox().
15 | - `tb.addAtCoordinate()` and `tb.moveToCoordinate()` have been deprecated. `tb.add(Object)` and `Object.setCoords()` replace them
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Peter Liu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `threebox`
2 |
3 | A three.js plugin for Mapbox GL JS, using the custom layer feature. Provides convenient methods to manage objects in lnglat coordinates, and to synchronize the map and scene cameras.
4 |
5 |
6 |
7 | ### Compatibility/Dependencies
8 |
9 | - Mapbox v.0.50.0 and later (for custom layer support)
10 | - Three.r94 (already bundled into the Threebox build). If desired, other versions can be swapped in and rebuilt [here](https://github.com/peterqliu/threebox/blob/master/src/three.js), though compatibility is not guaranteed.
11 |
12 | ### Getting started
13 |
14 | Download the bundle from [`dist/threebox.js`](dist/threebox.js) and add include it in a `
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
77 |
--------------------------------------------------------------------------------
/examples/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sphere Example
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
71 |
--------------------------------------------------------------------------------
/examples/config_template.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | accessToken: ''
3 | };
--------------------------------------------------------------------------------
/examples/line.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Line Example
4 |
5 |
6 |
7 |
8 |
9 |
21 |
22 |
23 |
24 |
25 |
110 |
--------------------------------------------------------------------------------
/examples/logistics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Animated truck
4 |
5 |
6 |
7 |
8 |
9 |
24 |
25 |
26 |
27 | Click on the map to drive the truck there
28 |
151 |
--------------------------------------------------------------------------------
/examples/mercator.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Mercator projection
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
100 |
--------------------------------------------------------------------------------
/examples/models/Truck.mtl:
--------------------------------------------------------------------------------
1 | # Blender MTL File: 'Truck.blend'
2 | # Material Count: 7
3 |
4 | newmtl Black
5 | Ns 96.078431
6 | Ka 1.000000 1.000000 1.000000
7 | Kd 0.013347 0.013347 0.013347
8 | Ks 0.500000 0.500000 0.500000
9 | Ke 0.000000 0.000000 0.000000
10 | Ni 1.000000
11 | d 1.000000
12 | illum 2
13 |
14 | newmtl Cream
15 | Ns 96.078431
16 | Ka 1.000000 1.000000 1.000000
17 | Kd 0.565467 0.640000 0.496259
18 | Ks 0.500000 0.500000 0.500000
19 | Ke 0.000000 0.000000 0.000000
20 | Ni 1.000000
21 | d 1.000000
22 | illum 2
23 |
24 | newmtl Silver
25 | Ns 96.078431
26 | Ka 1.000000 1.000000 1.000000
27 | Kd 0.385546 0.385546 0.385546
28 | Ks 0.500000 0.500000 0.500000
29 | Ke 0.000000 0.000000 0.000000
30 | Ni 1.000000
31 | d 1.000000
32 | illum 2
33 |
34 | newmtl Wheel_Rim
35 | Ns 96.078431
36 | Ka 1.000000 1.000000 1.000000
37 | Kd 0.640000 0.640000 0.640000
38 | Ks 0.500000 0.500000 0.500000
39 | Ke 0.000000 0.000000 0.000000
40 | Ni 1.000000
41 | d 1.000000
42 | illum 2
43 |
44 | newmtl Wheels
45 | Ns 96.078431
46 | Ka 1.000000 1.000000 1.000000
47 | Kd 0.047401 0.047401 0.047401
48 | Ks 0.500000 0.500000 0.500000
49 | Ke 0.000000 0.000000 0.000000
50 | Ni 1.000000
51 | d 1.000000
52 | illum 2
53 |
54 | newmtl White
55 | Ns 96.078431
56 | Ka 1.000000 1.000000 1.000000
57 | Kd 0.800000 0.800000 0.800000
58 | Ks 0.500000 0.500000 0.500000
59 | Ke 0.000000 0.000000 0.000000
60 | Ni 1.000000
61 | d 1.000000
62 | illum 2
63 |
64 | newmtl Window
65 | Ns 96.078431
66 | Ka 1.000000 1.000000 1.000000
67 | Kd 0.362586 0.585789 0.640000
68 | Ks 0.500000 0.500000 0.500000
69 | Ke 0.000000 0.000000 0.000000
70 | Ni 1.000000
71 | d 1.000000
72 | illum 2
73 |
--------------------------------------------------------------------------------
/examples/old/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Threebox Basic Example
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
63 |
--------------------------------------------------------------------------------
/examples/old/flocking.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Flocking Trucks SymbolLayer3D example
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
249 |
--------------------------------------------------------------------------------
/examples/old/orientation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | SymboLayer3D example
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
117 |
--------------------------------------------------------------------------------
/examples/raycaster.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Raycaster
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
112 |
--------------------------------------------------------------------------------
/examples/tube.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Tube Example
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
97 |
--------------------------------------------------------------------------------
/exports.js:
--------------------------------------------------------------------------------
1 | window.Threebox = require('./src/Threebox.js'),
2 | window.THREE = require('./src/three.js')
3 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | module.exports = exports = {
2 | Threebox: require('./src/Threebox'),
3 | THREE: require('./src/three.js')
4 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "threebox",
3 | "version": "0.2.0",
4 | "description": "A Mapbox GL JS plugin that combines the power of the Three.js 3D library with Mapbox geospatial tools.",
5 | "main": "main.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/peterqliu/threebox.git"
9 | },
10 | "author": "@peterqliu and @kronick",
11 | "license": "MIT",
12 | "bugs": {
13 | "url": "https://github.com/peterqliu/threebox/issues"
14 | },
15 | "dev-dependencies": {
16 | "tap-prettify": "0.0.2",
17 | "tape": "^4.6.3"
18 | },
19 | "scripts": {
20 | "build": "browserify -g ./node_modules/uglifyify exports.js > dist/threebox.min.js",
21 | "dev": "watchify exports.js --verbose -o dist/threebox.js",
22 | "test": "browserify tests/threebox-tests.js > tests/threebox-tests-bundle.js; echo 'Open tests/threebox-tests.html to run tests in the browser.'"
23 | },
24 | "dependencies": {
25 | "@turf/turf": "^5.1.6",
26 | "tape": "^4.10.1",
27 | "turf": "^3.0.14",
28 | "watchify": "^3.11.1",
29 | "uglifyify": "5.0.1"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Animation/AnimationManager.js:
--------------------------------------------------------------------------------
1 | var threebox = require('../Threebox.js');
2 | var utils = require("../utils/utils.js");
3 | var validate = require("../utils/validate.js");
4 |
5 | function AnimationManager(map) {
6 |
7 | this.map = map
8 | this.enrolledObjects = [];
9 | this.previousFrameTime;
10 |
11 | };
12 |
13 | AnimationManager.prototype = {
14 |
15 | enroll: function(obj) {
16 |
17 | /* Extend the provided object with animation-specific properties and track in the animation manager */
18 |
19 | this.enrolledObjects.push(obj);
20 |
21 | // Give this object its own internal animation queue
22 | obj.animationQueue = [];
23 |
24 | obj.set = function(options) {
25 |
26 | //if duration is set, animate to the new state
27 | if (options.duration > 0 ){
28 |
29 | var newParams = {
30 | start: Date.now(),
31 | expiration: Date.now() + options.duration,
32 | endState: {}
33 | }
34 |
35 | utils.extend(options, newParams);
36 |
37 | var translating = options.coords;
38 | var rotating = options.rotation;
39 | var scaling = options.scale || options.scaleX || options.scaleY || options.scaleZ;
40 |
41 | if (rotating) {
42 |
43 | var r = obj.rotation;
44 | options.startRotation = [r.x, r.y, r.z];
45 |
46 |
47 | options.endState.rotation = utils.types.rotation(options.rotation, options.startRotation);
48 | options.rotationPerMs = options.endState.rotation
49 | .map(function(angle, index){
50 | return (angle-options.startRotation[index])/options.duration;
51 | })
52 | }
53 |
54 | if (scaling) {
55 | var s = obj.scale;
56 | options.startScale = [s.x, s.y, s.z];
57 | options.endState.scale = utils.types.scale(options.scale, options.startScale);
58 |
59 | options.scalePerMs = options.endState.scale
60 | .map(function(scale, index){
61 | return (scale-options.startScale[index])/options.duration;
62 | })
63 | }
64 |
65 | if (translating) options.pathCurve = new THREE.CatmullRomCurve3(utils.lnglatsToWorld([obj.coordinates, options.coords]));
66 |
67 | var entry = {
68 | type:'set',
69 | parameters: options
70 | }
71 |
72 | this.animationQueue
73 | .push(entry);
74 |
75 | map.repaint = true;
76 | }
77 |
78 | //if no duration set, stop object's existing animations and go to that state immediately
79 | else {
80 | this.stop();
81 | options.rotation = utils.radify(options.rotation);
82 | this._setObject(options);
83 | }
84 |
85 | return this
86 |
87 | };
88 |
89 | obj.stop = function(){
90 | this.animationQueue = [];
91 | return this;
92 | }
93 |
94 | obj.followPath = function (options, cb){
95 |
96 | var entry = {
97 | type: 'followPath',
98 | parameters: utils._validate(options, defaults.followPath)
99 | };
100 |
101 | utils.extend(
102 | entry.parameters,
103 | {
104 | pathCurve: new THREE.CatmullRomCurve3(
105 | utils.lnglatsToWorld(options.path)
106 | ),
107 | start: Date.now(),
108 | expiration: Date.now() + entry.parameters.duration,
109 | cb: cb
110 | }
111 | );
112 |
113 | this.animationQueue
114 | .push(entry);
115 |
116 | map.repaint = true;
117 |
118 | return this;
119 | };
120 |
121 | obj._setObject = function (options){
122 |
123 | var p = options.position; // lnglat
124 | var r = options.rotation; // radians
125 | var s = options.scale; //
126 | var w = options.worldCoordinates; //Vector3
127 | var q = options.quaternion // [axis, angle]
128 |
129 | if (p) {
130 | this.coordinates = p;
131 | var c = utils.projectToWorld(p);
132 | this.position.copy(c)
133 | }
134 |
135 | if (r) this.rotation.set(r[0], r[1], r[2]);
136 |
137 | if (s) this.scale.set(s[0], s[1], s[2]);
138 |
139 | if (q) this.quaternion.setFromAxisAngle(q[0], q[1]);
140 |
141 | if (w) this.position.copy(w);
142 |
143 | map.repaint = true
144 | }
145 | },
146 |
147 | update: function(now) {
148 |
149 | if (this.previousFrameTime === undefined) this.previousFrameTime = now;
150 |
151 | var dimensions = ['X','Y','Z'];
152 |
153 | //iterate through objects in queue. count in reverse so we can cull objects without frame shifting
154 | for (var a = this.enrolledObjects.length-1; a>=0; a--){
155 |
156 | var object = this.enrolledObjects[a];
157 |
158 | if(!object.animationQueue || object.animationQueue.length === 0) continue;
159 |
160 | //focus on first item in queue
161 | var item = object.animationQueue[0];
162 | var options = item.parameters;
163 |
164 | // if an animation is past its expiration date, cull it
165 | if (!options.expiration) {
166 | // console.log('culled')
167 |
168 | object.animationQueue.splice(0,1);
169 |
170 | // set the start time of the next animation
171 | if (object.animationQueue[0]) object.animationQueue[0].parameters.start = now;
172 |
173 | return
174 | }
175 |
176 | //if finished, jump to end state and flag animation entry for removal next time around. Execute callback if there is one
177 | var expiring = now >= options.expiration;
178 |
179 | if (expiring) {
180 | options.expiration = false;
181 | if (options.endState) object._setObject(options.endState);
182 | options.cb();
183 | }
184 |
185 | else {
186 |
187 | var timeProgress = (now - options.start) / options.duration;
188 |
189 | if (item.type === 'set'){
190 |
191 | var objectState = {};
192 |
193 | if (options.pathCurve) objectState.worldCoordinates = options.pathCurve.getPoint(timeProgress);
194 |
195 | if (options.rotationPerMs) {
196 | objectState.rotation = options.startRotation.map(function(rad, index){
197 | return rad + options.rotationPerMs[index] * timeProgress * options.duration
198 | })
199 | }
200 |
201 | if (options.scalePerMs) {
202 | objectState.scale = options.startScale.map(function(scale, index){
203 | return scale + options.scalePerMs[index]*timeProgress * options.duration
204 | })
205 | }
206 |
207 | object._setObject(objectState);
208 | }
209 |
210 | if (item.type === 'followPath'){
211 |
212 | var position = options.pathCurve.getPointAt(timeProgress);
213 | objectState = {worldCoordinates: position};
214 |
215 | // if we need to track heading
216 | if (options.trackHeading){
217 |
218 | var tangent = options.pathCurve
219 | .getTangentAt(timeProgress)
220 | .normalize();
221 |
222 | var axis = new THREE.Vector3(0,0,0);
223 | var up = new THREE.Vector3(0,1,0);
224 |
225 | axis
226 | .crossVectors(up, tangent)
227 | .normalize();
228 |
229 | var radians = Math.acos(up.dot(tangent));
230 |
231 | objectState.quaternion = [axis, radians];
232 |
233 | }
234 |
235 | object._setObject(objectState);
236 |
237 | }
238 | }
239 |
240 | }
241 |
242 | this.previousFrameTime = now;
243 | }
244 | }
245 |
246 | const defaults = {
247 | followPath: {
248 | path: null,
249 | duration: 1000,
250 | trackHeading: true
251 | }
252 | }
253 | module.exports = exports = AnimationManager;
--------------------------------------------------------------------------------
/src/Camera/CameraSync.js:
--------------------------------------------------------------------------------
1 | var THREE = require("../three.js");
2 | var utils = require("../utils/utils.js");
3 | var ThreeboxConstants = require("../utils/constants.js");
4 |
5 | function CameraSync(map, camera, world) {
6 |
7 | this.map = map;
8 | this.camera = camera;
9 | this.active = true;
10 |
11 | this.camera.matrixAutoUpdate = false; // We're in charge of the camera now!
12 |
13 | // Postion and configure the world group so we can scale it appropriately when the camera zooms
14 | this.world = world || new THREE.Group();
15 | this.world.position.x = this.world.position.y = ThreeboxConstants.WORLD_SIZE/2
16 | this.world.matrixAutoUpdate = false;
17 |
18 |
19 | //set up basic camera state
20 |
21 | this.state = {
22 | fov: 0.6435011087932844,
23 | translateCenter: new THREE.Matrix4,
24 | worldSizeRatio: 512/ThreeboxConstants.WORLD_SIZE
25 | };
26 |
27 | this.state.translateCenter.makeTranslation(ThreeboxConstants.WORLD_SIZE/2, -ThreeboxConstants.WORLD_SIZE / 2, 0);
28 |
29 | // Listen for move events from the map and update the Three.js camera. Some attributes only change when viewport resizes, so update those accordingly
30 | var _this = this;
31 |
32 | this.map
33 | .on('move', function() {
34 | _this.updateCamera()
35 | })
36 | .on('resize', function(){
37 | _this.setupCamera();
38 | })
39 |
40 |
41 | this.setupCamera();
42 | }
43 |
44 | CameraSync.prototype = {
45 |
46 | setupCamera: function() {
47 |
48 | var t = this.map.transform
49 | const halfFov = this.state.fov / 2;
50 | var cameraToCenterDistance = 0.5 / Math.tan(halfFov) * t.height;
51 | const groundAngle = Math.PI / 2 + t._pitch;
52 |
53 | this.state.cameraToCenterDistance = cameraToCenterDistance;
54 | this.state.cameraTranslateZ = new THREE.Matrix4().makeTranslation(0,0,cameraToCenterDistance);
55 | this.state.topHalfSurfaceDistance = Math.sin(halfFov) * cameraToCenterDistance / Math.sin(Math.PI - groundAngle - halfFov);
56 |
57 | this.updateCamera();
58 | },
59 |
60 | updateCamera: function(ev) {
61 |
62 | if(!this.camera) {
63 | console.log('nocamera')
64 | return;
65 | }
66 |
67 | var t = this.map.transform
68 |
69 | // Calculate z distance of the farthest fragment that should be rendered.
70 | const furthestDistance = Math.cos(Math.PI / 2 - t._pitch) * this.state.topHalfSurfaceDistance + this.state.cameraToCenterDistance;
71 |
72 | // Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
73 | const farZ = furthestDistance * 1.01;
74 |
75 | this.camera.projectionMatrix = utils.makePerspectiveMatrix(this.state.fov, t.width / t.height, 1, farZ);
76 |
77 |
78 | var cameraWorldMatrix = new THREE.Matrix4();
79 | var cameraTranslateZ = new THREE.Matrix4().makeTranslation(0,0,this.state.cameraToCenterDistance);
80 | var rotatePitch = new THREE.Matrix4().makeRotationX(t._pitch);
81 | var rotateBearing = new THREE.Matrix4().makeRotationZ(t.angle);
82 |
83 | // Unlike the Mapbox GL JS camera, separate camera translation and rotation out into its world matrix
84 | // If this is applied directly to the projection matrix, it will work OK but break raycasting
85 |
86 | cameraWorldMatrix
87 | .premultiply(this.state.cameraTranslateZ)
88 | .premultiply(rotatePitch)
89 | .premultiply(rotateBearing)
90 |
91 | this.camera.matrixWorld.copy(cameraWorldMatrix);
92 |
93 |
94 | var zoomPow = t.scale * this.state.worldSizeRatio;
95 |
96 | // Handle scaling and translation of objects in the map in the world's matrix transform, not the camera
97 | var scale = new THREE.Matrix4;
98 | var translateCenter = new THREE.Matrix4;
99 | var translateMap = new THREE.Matrix4;
100 | var rotateMap = new THREE.Matrix4;
101 |
102 | scale
103 | .makeScale( zoomPow, zoomPow , zoomPow );
104 |
105 |
106 | var x = -this.map.transform.x || -this.map.transform.point.x;
107 | var y = this.map.transform.y || this.map.transform.point.y;
108 |
109 | translateMap
110 | .makeTranslation(x, y, 0);
111 |
112 | rotateMap
113 | .makeRotationZ(Math.PI);
114 |
115 | this.world.matrix = new THREE.Matrix4;
116 | this.world.matrix
117 | .premultiply(rotateMap)
118 | .premultiply(this.state.translateCenter)
119 | .premultiply(scale)
120 | .premultiply(translateMap)
121 |
122 |
123 | // utils.prettyPrintMatrix(this.camera.projectionMatrix.elements);
124 | }
125 | }
126 |
127 | module.exports = exports = CameraSync;
128 |
--------------------------------------------------------------------------------
/src/Threebox.js:
--------------------------------------------------------------------------------
1 | var THREE = require("./three.js");
2 | var CameraSync = require("./camera/CameraSync.js");
3 | var utils = require("./utils/utils.js");
4 | var AnimationManager = require("./animation/AnimationManager.js");
5 | var ThreeboxConstants = require("./utils/constants.js");
6 |
7 | var Objects = require("./objects/objects.js");
8 | var material = require("./utils/material.js");
9 | var sphere = require("./objects/sphere.js");
10 | var loadObj = require("./objects/loadObj.js");
11 | var Object3D = require("./objects/Object3D.js");
12 | var line = require("./objects/line.js");
13 | var tube = require("./objects/tube.js");
14 |
15 | function Threebox(map, glContext, options){
16 |
17 | this.init(map, glContext, options);
18 |
19 | };
20 |
21 | Threebox.prototype = {
22 |
23 | repaint: function(){
24 | this.map.repaint = true;
25 | },
26 |
27 | init: function (map, glContext, options){
28 |
29 | this.map = map;
30 |
31 | // Set up a THREE.js scene
32 | this.renderer = new THREE.WebGLRenderer( {
33 | alpha: true,
34 | antialias: true,
35 | canvas: map.getCanvas(),
36 | context: glContext
37 | } );
38 |
39 | this.renderer.shadowMap.enabled = true;
40 | this.renderer.autoClear = false;
41 |
42 | this.scene = new THREE.Scene();
43 | this.camera = new THREE.PerspectiveCamera( 28, window.innerWidth / window.innerHeight, 0.000000000001, Infinity);
44 |
45 | // The CameraSync object will keep the Mapbox and THREE.js camera movements in sync.
46 | // It requires a world group to scale as we zoom in. Rotation is handled in the camera's
47 | // projection matrix itself (as is field of view and near/far clipping)
48 | // It automatically registers to listen for move events on the map so we don't need to do that here
49 | this.world = new THREE.Group();
50 | this.scene.add(this.world);
51 |
52 | this.cameraSync = new CameraSync(this.map, this.camera, this.world);
53 |
54 | //raycaster for mouse events
55 | this.raycaster = new THREE.Raycaster();
56 |
57 | // apply starter options
58 |
59 | this.options = utils._validate(options || {}, defaultOptions);
60 | if (this.options.defaultLights) this.defaultLights();
61 |
62 | },
63 |
64 | // Objects
65 |
66 | objects: new Objects(AnimationManager),
67 |
68 | sphere: sphere,
69 |
70 | line: line,
71 |
72 | tube: function(obj){
73 | return tube(obj, this.world)
74 | },
75 |
76 | Object3D: function(obj, o) {
77 | return Object3D(obj, o)
78 | },
79 |
80 | loadObj: loadObj,
81 |
82 |
83 | // Material
84 |
85 | material: function(o){
86 | return material(o)
87 | },
88 |
89 | utils: utils,
90 |
91 | projectToWorld: function(coords) {
92 | return this.utils.projectToWorld(coords)
93 | },
94 |
95 | unprojectFromWorld: function(v3) {
96 | return this.utils.unprojectFromWorld(v3)
97 | },
98 |
99 | projectedUnitsPerMeter: function(lat) {
100 | return this.utils.projectedUnitsPerMeter(lat)
101 | },
102 |
103 | queryRenderedFeatures: function(point){
104 |
105 | var mouse = new THREE.Vector2();
106 |
107 | // // scale mouse pixel position to a percentage of the screen's width and height
108 | mouse.x = ( point.x / this.map.transform.width ) * 2 - 1;
109 | mouse.y = 1 - ( point.y / this.map.transform.height ) * 2;
110 |
111 | this.raycaster.setFromCamera(mouse, this.camera);
112 |
113 | // calculate objects intersecting the picking ray
114 | var intersects = this.raycaster.intersectObjects(this.world.children, true);
115 |
116 | return intersects
117 | },
118 |
119 | update: function() {
120 |
121 | if (this.map.repaint) this.map.repaint = false
122 |
123 | var timestamp = Date.now();
124 |
125 | // Update any animations
126 | this.objects.animationManager.update(timestamp);
127 |
128 | this.renderer.state.reset();
129 |
130 | // Render the scene and repaint the map
131 | this.renderer.render( this.scene, this.camera );
132 |
133 | if (this.options.passiveRendering === false) this.map.triggerRepaint();
134 | },
135 |
136 | add: function(obj) {
137 | this.world.add(obj);
138 | },
139 |
140 | remove: function(obj) {
141 | this.world.remove(obj);
142 | },
143 |
144 |
145 | defaultLights: function(){
146 |
147 | this.scene.add( new THREE.AmbientLight( 0xffffff ) );
148 | var sunlight = new THREE.DirectionalLight(0xffffff, 0.25);
149 | sunlight.position.set(0,80000000,100000000);
150 | sunlight.matrixWorldNeedsUpdate = true;
151 | this.world.add(sunlight);
152 |
153 | },
154 |
155 | memory: function (){ return this.renderer.info.memory},
156 |
157 | version: '0.3.0',
158 |
159 | // DEPRECATED METHODS
160 |
161 | setupDefaultLights: function() {
162 | console.warn('.setupDefaultLights() has been moved to a "defaultLights" option inside Threebox()')
163 | this.defaultLights();
164 | },
165 |
166 | addAtCoordinate: function(obj, lnglat, options) {
167 |
168 | console.warn('addAtCoordinate() has been deprecated. Check out the and threebox.add() Object.setCoords() methods instead.')
169 |
170 | obj = this.Object3D({obj:obj});
171 |
172 | obj.setCoords(lnglat)
173 | this.add(obj);
174 |
175 | return obj;
176 | },
177 |
178 | moveToCoordinate: function(obj, lnglat, options) {
179 | console.warn('addAtCoordinate() has been deprecated. Check out the Object.setCoords() and threebox.add() methods instead.')
180 |
181 | if (!obj.setCoords) obj = this.Object3D(obj);
182 | obj.setCoords(lnglat, options);
183 |
184 | return obj;
185 | }
186 | }
187 |
188 | var defaultOptions = {
189 | defaultLights: false,
190 | passiveRendering: true
191 | }
192 | module.exports = exports = Threebox;
193 |
194 |
--------------------------------------------------------------------------------
/src/Utils/Utils.js:
--------------------------------------------------------------------------------
1 | var THREE = require("../three.js");
2 | var Constants = require("./constants.js");
3 | var validate = require("./validate.js");
4 |
5 | var utils = {
6 |
7 | prettyPrintMatrix: function(uglymatrix){
8 | for (var s=0; s<4; s++){
9 | var quartet=[uglymatrix[s],
10 | uglymatrix[s+4],
11 | uglymatrix[s+8],
12 | uglymatrix[s+12]];
13 | console.log(quartet.map(function(num){return num.toFixed(4)}))
14 | }
15 | },
16 |
17 | makePerspectiveMatrix: function(fovy, aspect, near, far) {
18 |
19 | var out = new THREE.Matrix4();
20 | var f = 1.0 / Math.tan(fovy / 2),
21 | nf = 1 / (near - far);
22 |
23 | var newMatrix = [
24 | f / aspect, 0, 0, 0,
25 | 0, f, 0, 0,
26 | 0, 0, (far + near) * nf, -1,
27 | 0, 0, (2 * far * near) * nf, 0
28 | ]
29 |
30 | out.elements = newMatrix
31 | return out;
32 | },
33 |
34 | //gimme radians
35 | radify: function(deg){
36 |
37 | function convert(degrees){
38 | degrees = degrees || 0;
39 | return Math.PI*2*degrees/360
40 | }
41 |
42 | if (typeof deg === 'object'){
43 |
44 | //if [x,y,z] array of rotations
45 | if (deg.length > 0){
46 | return deg.map(function(degree){
47 | return convert(degree)
48 | })
49 | }
50 |
51 | // if {x: y: z:} rotation object
52 | else {
53 | return [convert(deg.x), convert(deg.y), convert(deg.z)]
54 | }
55 | }
56 |
57 | //if just a number
58 | else return convert(deg)
59 | },
60 |
61 | //gimme degrees
62 | degreeify: function(rad){
63 | function convert(radians){
64 | radians = radians || 0;
65 | return radians * 360/(Math.PI*2)
66 | }
67 |
68 | if (typeof rad === 'object') {
69 | return [convert(rad.x), convert(rad.y), convert(rad.z)]
70 | }
71 |
72 | else return convert(rad)
73 | },
74 |
75 | projectToWorld: function(coords){
76 |
77 | // Spherical mercator forward projection, re-scaling to WORLD_SIZE
78 |
79 | var projected = [
80 | -Constants.MERCATOR_A * Constants.DEG2RAD* coords[0] * Constants.PROJECTION_WORLD_SIZE,
81 | -Constants.MERCATOR_A * Math.log(Math.tan((Math.PI*0.25) + (0.5 * Constants.DEG2RAD * coords[1]) )) * Constants.PROJECTION_WORLD_SIZE
82 | ];
83 |
84 | //z dimension, defaulting to 0 if not provided
85 |
86 | if (!coords[2]) projected.push(0)
87 | else {
88 | var pixelsPerMeter = this.projectedUnitsPerMeter(coords[1]);
89 | projected.push( coords[2] * pixelsPerMeter );
90 | }
91 |
92 | var result = new THREE.Vector3(projected[0], projected[1], projected[2]);
93 |
94 | return result;
95 | },
96 |
97 | projectedUnitsPerMeter: function(latitude) {
98 | return Math.abs( Constants.WORLD_SIZE / Math.cos( Constants.DEG2RAD * latitude ) / Constants.EARTH_CIRCUMFERENCE );
99 | },
100 |
101 | _scaleVerticesToMeters: function(centerLatLng, vertices) {
102 | var pixelsPerMeter = this.projectedUnitsPerMeter(centerLatLng[1]);
103 | var centerProjected = this.projectToWorld(centerLatLng);
104 |
105 | for (var i = 0; i < vertices.length; i++) {
106 | vertices[i].multiplyScalar(pixelsPerMeter);
107 | }
108 |
109 | return vertices;
110 | },
111 |
112 | projectToScreen: function(coords) {
113 | console.log("WARNING: Projecting to screen coordinates is not yet implemented");
114 | },
115 |
116 | unprojectFromScreen: function (pixel) {
117 | console.log("WARNING: unproject is not yet implemented");
118 | },
119 |
120 | //world units to lnglat
121 | unprojectFromWorld: function (worldUnits) {
122 |
123 | var unprojected = [
124 | -worldUnits.x / (Constants.MERCATOR_A * Constants.DEG2RAD * Constants.PROJECTION_WORLD_SIZE),
125 | 2*(Math.atan(Math.exp(worldUnits.y/(Constants.PROJECTION_WORLD_SIZE*(-Constants.MERCATOR_A))))-Math.PI/4)/Constants.DEG2RAD
126 | ];
127 |
128 | var pixelsPerMeter = this.projectedUnitsPerMeter(unprojected[1]);
129 |
130 | //z dimension
131 | var height = worldUnits.z || 0;
132 | unprojected.push( height / pixelsPerMeter );
133 |
134 | return unprojected;
135 | },
136 |
137 | _flipMaterialSides: function(obj) {
138 |
139 | },
140 |
141 | // to improve precision, normalize a series of vector3's to their collective center, and move the resultant mesh to that center
142 | normalizeVertices(vertices) {
143 |
144 | var geometry = new THREE.Geometry();
145 |
146 | for (v3 of vertices) {
147 | geometry.vertices.push(v3)
148 | }
149 |
150 | geometry.computeBoundingSphere();
151 | var center = geometry.boundingSphere.center;
152 | var radius = geometry.boundingSphere.radius;
153 |
154 | var scaled = vertices.map(function(v3){
155 | var normalized = v3.sub(center);
156 | return normalized;
157 | });
158 |
159 | return {vertices: scaled, position: center}
160 | },
161 |
162 | //flatten an array of Vector3's into a shallow array of values in x-y-z order, for bufferGeometry
163 | flattenVectors(vectors) {
164 | var flattenedArray = [];
165 | for (vertex of vectors) {
166 | flattenedArray.push(vertex.x, vertex.y, vertex.z);
167 | }
168 | return flattenedArray
169 | },
170 |
171 | //convert a line/polygon to Vector3's
172 |
173 | lnglatsToWorld: function(coords){
174 |
175 | var vector3 = coords.map(
176 | function(pt){
177 | var p = utils.projectToWorld(pt);
178 | var v3 = new THREE.Vector3(p.x, p.y, p.z);
179 | return v3
180 | }
181 | );
182 |
183 | return vector3
184 | },
185 |
186 | extend: function(original, addition) {
187 | for (key in addition) original[key] = addition[key];
188 | },
189 |
190 | clone: function(original) {
191 | var clone = {};
192 | for (key in original) clone[key] = original[key];
193 | return clone;
194 | },
195 |
196 | // retrieve object parameters from an options object
197 |
198 | types: {
199 |
200 | rotation: function(r, currentRotation){
201 |
202 | // if number provided, rotate only in Z by that amount
203 | if (typeof r === 'number') r = {z:r};
204 |
205 | var degrees = this.applyDefault([r.x, r.y, r.z], currentRotation);
206 | var radians = utils.radify(degrees);
207 | return radians;
208 |
209 | },
210 |
211 | scale: function(s, currentScale){
212 | if (typeof s === 'number') return s = [s,s,s];
213 | else return this.applyDefault([s.x, s.y, s.z], currentScale);
214 | },
215 |
216 | applyDefault: function(array, current){
217 |
218 | var output = array.map(function(item, index){
219 | item = item || current[index];
220 | return item
221 | })
222 |
223 | return output
224 | }
225 | },
226 |
227 | _validate: function(userInputs, defaults){
228 |
229 | userInputs = userInputs || {};
230 | var validatedOutput = {};
231 | utils.extend(validatedOutput, userInputs);
232 |
233 | for (key of Object.keys(defaults)){
234 |
235 | if (userInputs[key] === undefined) {
236 | //make sure required params are present
237 | if (defaults[key] === null) {
238 | console.error(key + ' is required')
239 | return;
240 | }
241 |
242 | else validatedOutput[key] = defaults[key]
243 |
244 | }
245 |
246 | else validatedOutput[key] = userInputs[key]
247 | }
248 |
249 | return validatedOutput
250 | },
251 | Validator: new validate(),
252 | exposedMethods: ['projectToWorld', 'projectedUnitsPerMeter', 'extend', 'unprojectFromWorld']
253 | }
254 |
255 | module.exports = exports = utils
--------------------------------------------------------------------------------
/src/Utils/ValueGenerator.js:
--------------------------------------------------------------------------------
1 | const ValueGenerator = function(input) {
2 | if(typeof input === 'object' && input.property !== undefined) // Value name comes from a property in each item
3 | return (f => f.properties[input.property]);
4 | else if(typeof input === 'object' && input.generator !== undefined) // Value name generated by a function run on each item
5 | return input.generator;
6 | else return (() => input);
7 |
8 | return undefined;
9 | }
10 |
11 | module.exports = exports = ValueGenerator;
--------------------------------------------------------------------------------
/src/objects/Object3D.js:
--------------------------------------------------------------------------------
1 | var Objects = require('./objects.js');
2 | var utils = require("../utils/utils.js");
3 |
4 | function Object3D(options) {
5 | options = utils._validate(options, Objects.prototype._defaults.Object3D);
6 |
7 | var obj = options.obj;
8 |
9 | if (options.units === 'meters') obj = Objects.prototype._makeGroup(options.obj, options);
10 |
11 | obj = Objects.prototype._addMethods(obj);
12 | return obj
13 | }
14 |
15 |
16 | module.exports = exports = Object3D;
--------------------------------------------------------------------------------
/src/objects/customLines/Line2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author WestLangley / http://github.com/WestLangley
3 | *
4 | */
5 |
6 | THREE.Line2 = function ( geometry, material ) {
7 |
8 | THREE.LineSegments2.call( this );
9 |
10 | this.type = 'Line2';
11 |
12 | this.geometry = geometry !== undefined ? geometry : new THREE.LineGeometry();
13 | this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
14 |
15 | };
16 |
17 | THREE.Line2.prototype = Object.assign( Object.create( THREE.LineSegments2.prototype ), {
18 |
19 | constructor: THREE.Line2,
20 |
21 | isLine2: true,
22 |
23 | copy: function ( source ) {
24 |
25 | // todo
26 |
27 | return this;
28 |
29 | }
30 |
31 | } );
32 |
--------------------------------------------------------------------------------
/src/objects/customLines/LineGeometry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author WestLangley / http://github.com/WestLangley
3 | *
4 | */
5 |
6 | THREE.LineGeometry = function () {
7 |
8 | THREE.LineSegmentsGeometry.call( this );
9 |
10 | this.type = 'LineGeometry';
11 |
12 | };
13 |
14 | THREE.LineGeometry.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
15 |
16 | constructor: THREE.LineGeometry,
17 |
18 | isLineGeometry: true,
19 |
20 | setPositions: function ( array ) {
21 |
22 | // converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format
23 |
24 | var length = array.length - 3;
25 | var points = new Float32Array( 2 * length );
26 |
27 | for ( var i = 0; i < length; i += 3 ) {
28 |
29 | points[ 2 * i ] = array[ i ];
30 | points[ 2 * i + 1 ] = array[ i + 1 ];
31 | points[ 2 * i + 2 ] = array[ i + 2 ];
32 |
33 | points[ 2 * i + 3 ] = array[ i + 3 ];
34 | points[ 2 * i + 4 ] = array[ i + 4 ];
35 | points[ 2 * i + 5 ] = array[ i + 5 ];
36 |
37 | }
38 |
39 | THREE.LineSegmentsGeometry.prototype.setPositions.call( this, points );
40 |
41 | return this;
42 |
43 | },
44 |
45 | setColors: function ( array ) {
46 |
47 | // converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format
48 |
49 | var length = array.length - 3;
50 | var colors = new Float32Array( 2 * length );
51 |
52 | for ( var i = 0; i < length; i += 3 ) {
53 |
54 | colors[ 2 * i ] = array[ i ];
55 | colors[ 2 * i + 1 ] = array[ i + 1 ];
56 | colors[ 2 * i + 2 ] = array[ i + 2 ];
57 |
58 | colors[ 2 * i + 3 ] = array[ i + 3 ];
59 | colors[ 2 * i + 4 ] = array[ i + 4 ];
60 | colors[ 2 * i + 5 ] = array[ i + 5 ];
61 |
62 | }
63 |
64 | THREE.LineSegmentsGeometry.prototype.setColors.call( this, colors );
65 |
66 | return this;
67 |
68 | },
69 |
70 | fromLine: function ( line ) {
71 |
72 | var geometry = line.geometry;
73 |
74 | if ( geometry.isGeometry ) {
75 |
76 | this.setPositions( geometry.vertices );
77 |
78 | } else if ( geometry.isBufferGeometry ) {
79 |
80 | this.setPositions( geometry.position.array ); // assumes non-indexed
81 |
82 | }
83 |
84 | // set colors, maybe
85 |
86 | return this;
87 |
88 | },
89 |
90 | copy: function ( source ) {
91 |
92 | // todo
93 |
94 | return this;
95 |
96 | }
97 |
98 | } );
99 |
--------------------------------------------------------------------------------
/src/objects/customLines/LineMaterial.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author WestLangley / http://github.com/WestLangley
3 | *
4 | * parameters = {
5 | * color: ,
6 | * linewidth: ,
7 | * dashed: ,
8 | * dashScale: ,
9 | * dashSize: ,
10 | * gapSize: ,
11 | * resolution: , // to be set by renderer
12 | * }
13 | */
14 |
15 | THREE.UniformsLib.line = {
16 |
17 | linewidth: { value: 1 },
18 | resolution: { value: new THREE.Vector2( 1, 1 ) },
19 | dashScale: { value: 1 },
20 | dashSize: { value: 1 },
21 | gapSize: { value: 1 } // todo FIX - maybe change to totalSize
22 |
23 | };
24 |
25 | THREE.ShaderLib[ 'line' ] = {
26 |
27 | uniforms: THREE.UniformsUtils.merge( [
28 | THREE.UniformsLib.common,
29 | THREE.UniformsLib.fog,
30 | THREE.UniformsLib.line
31 | ] ),
32 |
33 | vertexShader:
34 | `
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 |
41 | uniform float linewidth;
42 | uniform vec2 resolution;
43 |
44 | attribute vec3 instanceStart;
45 | attribute vec3 instanceEnd;
46 |
47 | attribute vec3 instanceColorStart;
48 | attribute vec3 instanceColorEnd;
49 |
50 | varying vec2 vUv;
51 |
52 | #ifdef USE_DASH
53 |
54 | uniform float dashScale;
55 | attribute float instanceDistanceStart;
56 | attribute float instanceDistanceEnd;
57 | varying float vLineDistance;
58 |
59 | #endif
60 |
61 | void trimSegment( const in vec4 start, inout vec4 end ) {
62 |
63 | // trim end segment so it terminates between the camera plane and the near plane
64 |
65 | // conservative estimate of the near plane
66 | float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
67 | float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
68 | float nearEstimate = - 0.5 * b / a;
69 |
70 | float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
71 |
72 | end.xyz = mix( start.xyz, end.xyz, alpha );
73 |
74 | }
75 |
76 | void main() {
77 |
78 | #ifdef USE_COLOR
79 |
80 | vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
81 |
82 | #endif
83 |
84 | #ifdef USE_DASH
85 |
86 | vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
87 |
88 | #endif
89 |
90 | float aspect = resolution.x / resolution.y;
91 |
92 | vUv = uv;
93 |
94 | // camera space
95 | vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
96 | vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
97 |
98 | // special case for perspective projection, and segments that terminate either in, or behind, the camera plane
99 | // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
100 | // but we need to perform ndc-space calculations in the shader, so we must address this issue directly
101 | // perhaps there is a more elegant solution -- WestLangley
102 |
103 | bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
104 |
105 | if ( perspective ) {
106 |
107 | if ( start.z < 0.0 && end.z >= 0.0 ) {
108 |
109 | trimSegment( start, end );
110 |
111 | } else if ( end.z < 0.0 && start.z >= 0.0 ) {
112 |
113 | trimSegment( end, start );
114 |
115 | }
116 |
117 | }
118 |
119 | // clip space
120 | vec4 clipStart = projectionMatrix * start;
121 | vec4 clipEnd = projectionMatrix * end;
122 |
123 | // ndc space
124 | vec2 ndcStart = clipStart.xy / clipStart.w;
125 | vec2 ndcEnd = clipEnd.xy / clipEnd.w;
126 |
127 | // direction
128 | vec2 dir = ndcEnd - ndcStart;
129 |
130 | // account for clip-space aspect ratio
131 | dir.x *= aspect;
132 | dir = normalize( dir );
133 |
134 | // perpendicular to dir
135 | vec2 offset = vec2( dir.y, - dir.x );
136 |
137 | // undo aspect ratio adjustment
138 | dir.x /= aspect;
139 | offset.x /= aspect;
140 |
141 | // sign flip
142 | if ( position.x < 0.0 ) offset *= - 1.0;
143 |
144 | // endcaps
145 | if ( position.y < 0.0 ) {
146 |
147 | offset += - dir;
148 |
149 | } else if ( position.y > 1.0 ) {
150 |
151 | offset += dir;
152 |
153 | }
154 |
155 | // adjust for linewidth
156 | offset *= linewidth;
157 |
158 | // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
159 | offset /= resolution.y;
160 |
161 | // select end
162 | vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
163 |
164 | // back to clip space
165 | offset *= clip.w;
166 |
167 | clip.xy += offset;
168 |
169 | gl_Position = clip;
170 |
171 | #include
172 |
173 | #include
174 | #include
175 | #include
176 |
177 | }
178 | `,
179 |
180 | fragmentShader:
181 | `
182 | uniform vec3 diffuse;
183 | uniform float opacity;
184 |
185 | #ifdef USE_DASH
186 |
187 | uniform float dashSize;
188 | uniform float gapSize;
189 |
190 | #endif
191 |
192 | varying float vLineDistance;
193 |
194 | #include
195 | #include
196 | #include
197 | #include
198 | #include
199 |
200 | varying vec2 vUv;
201 |
202 | void main() {
203 |
204 | #include
205 |
206 | #ifdef USE_DASH
207 |
208 | if ( vUv.y < 0.5 || vUv.y > 0.5 ) discard; // discard endcaps
209 |
210 | if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
211 |
212 | #endif
213 |
214 | if ( vUv.y < 0.5 || vUv.y > 0.5 ) {
215 |
216 | float a = vUv.x - 0.5;
217 | float b = vUv.y - 0.5;
218 | float len2 = a * a + b * b;
219 |
220 | if ( len2 > 0.25 ) discard;
221 |
222 | }
223 |
224 | vec4 diffuseColor = vec4( diffuse, opacity );
225 |
226 | #include
227 | #include
228 |
229 | gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );
230 |
231 | #include
232 | #include
233 | #include
234 | #include
235 |
236 | }
237 | `
238 | };
239 |
240 | THREE.LineMaterial = function ( parameters ) {
241 |
242 | THREE.ShaderMaterial.call( this, {
243 |
244 | type: 'LineMaterial',
245 |
246 | uniforms: THREE.UniformsUtils.clone( THREE.ShaderLib[ 'line' ].uniforms ),
247 |
248 | vertexShader: THREE.ShaderLib[ 'line' ].vertexShader,
249 | fragmentShader: THREE.ShaderLib[ 'line' ].fragmentShader
250 |
251 | } );
252 |
253 | this.dashed = false;
254 |
255 | Object.defineProperties( this, {
256 |
257 | color: {
258 |
259 | enumerable: true,
260 |
261 | get: function () {
262 |
263 | return this.uniforms.diffuse.value;
264 |
265 | },
266 |
267 | set: function ( value ) {
268 |
269 | this.uniforms.diffuse.value = value;
270 |
271 | }
272 |
273 | },
274 |
275 | linewidth: {
276 |
277 | enumerable: true,
278 |
279 | get: function () {
280 |
281 | return this.uniforms.linewidth.value;
282 |
283 | },
284 |
285 | set: function ( value ) {
286 |
287 | this.uniforms.linewidth.value = value;
288 |
289 | }
290 |
291 | },
292 |
293 | dashScale: {
294 |
295 | enumerable: true,
296 |
297 | get: function () {
298 |
299 | return this.uniforms.dashScale.value;
300 |
301 | },
302 |
303 | set: function ( value ) {
304 |
305 | this.uniforms.dashScale.value = value;
306 |
307 | }
308 |
309 | },
310 |
311 | dashSize: {
312 |
313 | enumerable: true,
314 |
315 | get: function () {
316 |
317 | return this.uniforms.dashSize.value;
318 |
319 | },
320 |
321 | set: function ( value ) {
322 |
323 | this.uniforms.dashSize.value = value;
324 |
325 | }
326 |
327 | },
328 |
329 | gapSize: {
330 |
331 | enumerable: true,
332 |
333 | get: function () {
334 |
335 | return this.uniforms.gapSize.value;
336 |
337 | },
338 |
339 | set: function ( value ) {
340 |
341 | this.uniforms.gapSize.value = value;
342 |
343 | }
344 |
345 | },
346 |
347 | resolution: {
348 |
349 | enumerable: true,
350 |
351 | get: function () {
352 |
353 | return this.uniforms.resolution.value;
354 |
355 | },
356 |
357 | set: function ( value ) {
358 |
359 | this.uniforms.resolution.value.copy( value );
360 |
361 | }
362 |
363 | }
364 |
365 | } );
366 |
367 | this.setValues( parameters );
368 |
369 | };
370 |
371 | THREE.LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
372 | THREE.LineMaterial.prototype.constructor = THREE.LineMaterial;
373 |
374 | THREE.LineMaterial.prototype.isLineMaterial = true;
375 |
376 | THREE.LineMaterial.prototype.copy = function ( source ) {
377 |
378 | THREE.ShaderMaterial.prototype.copy.call( this, source );
379 |
380 | this.color.copy( source.color );
381 |
382 | this.linewidth = source.linewidth;
383 |
384 | this.resolution = source.resolution;
385 |
386 | // todo
387 |
388 | return this;
389 |
390 | };
391 |
392 |
--------------------------------------------------------------------------------
/src/objects/customLines/LineSegments2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author WestLangley / http://github.com/WestLangley
3 | *
4 | */
5 |
6 | THREE.LineSegments2 = function ( geometry, material ) {
7 |
8 | THREE.Mesh.call( this );
9 |
10 | this.type = 'LineSegments2';
11 |
12 | this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
13 | this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
14 |
15 | };
16 |
17 | THREE.LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
18 |
19 | constructor: THREE.LineSegments2,
20 |
21 | isLineSegments2: true,
22 |
23 | computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
24 |
25 | var start = new THREE.Vector3();
26 | var end = new THREE.Vector3();
27 |
28 | return function computeLineDistances() {
29 |
30 | var geometry = this.geometry;
31 |
32 | var instanceStart = geometry.attributes.instanceStart;
33 | var instanceEnd = geometry.attributes.instanceEnd;
34 | var lineDistances = new Float32Array( 2 * instanceStart.data.count );
35 |
36 | for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
37 |
38 | start.fromBufferAttribute( instanceStart, i );
39 | end.fromBufferAttribute( instanceEnd, i );
40 |
41 | lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
42 | lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
43 |
44 | }
45 |
46 | var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
47 |
48 | geometry.addAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
49 | geometry.addAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
50 |
51 | return this;
52 |
53 | };
54 |
55 | }() ),
56 |
57 | copy: function ( source ) {
58 |
59 | // todo
60 |
61 | return this;
62 |
63 | }
64 |
65 | } );
66 |
--------------------------------------------------------------------------------
/src/objects/customLines/LineSegmentsGeometry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author WestLangley / http://github.com/WestLangley
3 | *
4 | */
5 |
6 | THREE.LineSegmentsGeometry = function () {
7 |
8 | THREE.InstancedBufferGeometry.call( this );
9 |
10 | this.type = 'LineSegmentsGeometry';
11 |
12 | var plane = new THREE.BufferGeometry();
13 |
14 | var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
15 | var uvs = [ 0, 1, 1, 1, 0, .5, 1, .5, 0, .5, 1, .5, 0, 0, 1, 0 ];
16 | var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
17 |
18 | this.setIndex( index );
19 | this.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
20 | this.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
21 |
22 | };
23 |
24 | THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), {
25 |
26 | constructor: THREE.LineSegmentsGeometry,
27 |
28 | isLineSegmentsGeometry: true,
29 |
30 | applyMatrix: function ( matrix ) {
31 |
32 | var start = this.attributes.instanceStart;
33 | var end = this.attributes.instanceEnd;
34 |
35 | if ( start !== undefined ) {
36 |
37 | matrix.applyToBufferAttribute( start );
38 |
39 | matrix.applyToBufferAttribute( end );
40 |
41 | start.data.needsUpdate = true;
42 |
43 | }
44 |
45 | if ( this.boundingBox !== null ) {
46 |
47 | this.computeBoundingBox();
48 |
49 | }
50 |
51 | if ( this.boundingSphere !== null ) {
52 |
53 | this.computeBoundingSphere();
54 |
55 | }
56 |
57 | return this;
58 |
59 | },
60 |
61 | setPositions: function ( array ) {
62 |
63 | var lineSegments;
64 |
65 | if ( array instanceof Float32Array ) {
66 |
67 | lineSegments = array;
68 |
69 | } else if ( Array.isArray( array ) ) {
70 |
71 | lineSegments = new Float32Array( array );
72 |
73 | }
74 |
75 | var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
76 |
77 | this.addAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
78 | this.addAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
79 |
80 | //
81 |
82 | this.computeBoundingBox();
83 | this.computeBoundingSphere();
84 |
85 | return this;
86 |
87 | },
88 |
89 | setColors: function ( array ) {
90 |
91 | var colors;
92 |
93 | if ( array instanceof Float32Array ) {
94 |
95 | colors = array;
96 |
97 | } else if ( Array.isArray( array ) ) {
98 |
99 | colors = new Float32Array( array );
100 |
101 | }
102 |
103 | var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
104 |
105 | this.addAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
106 | this.addAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
107 |
108 | return this;
109 |
110 | },
111 |
112 | fromWireframeGeometry: function ( geometry ) {
113 |
114 | this.setPositions( geometry.attributes.position.array );
115 |
116 | return this;
117 |
118 | },
119 |
120 | fromEdgesGeometry: function ( geometry ) {
121 |
122 | this.setPositions( geometry.attributes.position.array );
123 |
124 | return this;
125 |
126 | },
127 |
128 | fromMesh: function ( mesh ) {
129 |
130 | this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) );
131 |
132 | // set colors, maybe
133 |
134 | return this;
135 |
136 | },
137 |
138 | fromLineSegements: function ( lineSegments ) {
139 |
140 | var geometry = lineSegments.geometry;
141 |
142 | if ( geometry.isGeometry ) {
143 |
144 | this.setPositions( geometry.vertices );
145 |
146 | } else if ( geometry.isBufferGeometry ) {
147 |
148 | this.setPositions( geometry.position.array ); // assumes non-indexed
149 |
150 | }
151 |
152 | // set colors, maybe
153 |
154 | return this;
155 |
156 | },
157 |
158 | computeBoundingBox: function () {
159 |
160 | var box = new THREE.Box3();
161 |
162 | return function computeBoundingBox() {
163 |
164 | if ( this.boundingBox === null ) {
165 |
166 | this.boundingBox = new THREE.Box3();
167 |
168 | }
169 |
170 | var start = this.attributes.instanceStart;
171 | var end = this.attributes.instanceEnd;
172 |
173 | if ( start !== undefined && end !== undefined ) {
174 |
175 | this.boundingBox.setFromBufferAttribute( start );
176 |
177 | box.setFromBufferAttribute( end );
178 |
179 | this.boundingBox.union( box );
180 |
181 | }
182 |
183 | };
184 |
185 | }(),
186 |
187 | computeBoundingSphere: function () {
188 |
189 | var vector = new THREE.Vector3();
190 |
191 | return function computeBoundingSphere() {
192 |
193 | if ( this.boundingSphere === null ) {
194 |
195 | this.boundingSphere = new THREE.Sphere();
196 |
197 | }
198 |
199 | if ( this.boundingBox === null ) {
200 |
201 | this.computeBoundingBox();
202 |
203 | }
204 |
205 | var start = this.attributes.instanceStart;
206 | var end = this.attributes.instanceEnd;
207 |
208 | if ( start !== undefined && end !== undefined ) {
209 |
210 | var center = this.boundingSphere.center;
211 |
212 | this.boundingBox.getCenter( center );
213 |
214 | var maxRadiusSq = 0;
215 |
216 | for ( var i = 0, il = start.count; i < il; i ++ ) {
217 |
218 | vector.fromBufferAttribute( start, i );
219 | maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
220 |
221 | vector.fromBufferAttribute( end, i );
222 | maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
223 |
224 | }
225 |
226 | this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
227 |
228 | if ( isNaN( this.boundingSphere.radius ) ) {
229 |
230 | console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
231 |
232 | }
233 |
234 | }
235 |
236 | };
237 |
238 | }(),
239 |
240 | toJSON: function () {
241 |
242 | // todo
243 |
244 | },
245 |
246 | clone: function () {
247 |
248 | // todo
249 |
250 | },
251 |
252 | copy: function ( source ) {
253 |
254 | // todo
255 |
256 | return this;
257 |
258 | }
259 |
260 | } );
261 |
--------------------------------------------------------------------------------
/src/objects/customLines/Wireframe.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author WestLangley / http://github.com/WestLangley
3 | *
4 | */
5 |
6 | THREE.Wireframe = function ( geometry, material ) {
7 |
8 | THREE.Mesh.call( this );
9 |
10 | this.type = 'Wireframe';
11 |
12 | this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
13 | this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
14 |
15 | };
16 |
17 | THREE.Wireframe.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
18 |
19 | constructor: THREE.Wireframe,
20 |
21 | isWireframe: true,
22 |
23 | computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
24 |
25 | var start = new THREE.Vector3();
26 | var end = new THREE.Vector3();
27 |
28 | return function computeLineDistances() {
29 |
30 | var geometry = this.geometry;
31 |
32 | var instanceStart = geometry.attributes.instanceStart;
33 | var instanceEnd = geometry.attributes.instanceEnd;
34 | var lineDistances = new Float32Array( 2 * instanceStart.data.count );
35 |
36 | for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
37 |
38 | start.fromBufferAttribute( instanceStart, i );
39 | end.fromBufferAttribute( instanceEnd, i );
40 |
41 | lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
42 | lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
43 |
44 | }
45 |
46 | var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
47 |
48 | geometry.addAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
49 | geometry.addAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
50 |
51 | return this;
52 |
53 | };
54 |
55 | }() ),
56 |
57 | copy: function ( source ) {
58 |
59 | // todo
60 |
61 | return this;
62 |
63 | }
64 |
65 | } );
66 |
--------------------------------------------------------------------------------
/src/objects/customLines/WireframeGeometry2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author WestLangley / http://github.com/WestLangley
3 | *
4 | */
5 |
6 | THREE.WireframeGeometry2 = function ( geometry ) {
7 |
8 | THREE.LineSegmentsGeometry.call( this );
9 |
10 | this.type = 'WireframeGeometry2';
11 |
12 | this.fromWireframeGeometry( new THREE.WireframeGeometry( geometry ) );
13 |
14 | // set colors, maybe
15 |
16 | };
17 |
18 | THREE.WireframeGeometry2.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
19 |
20 | constructor: THREE.WireframeGeometry2,
21 |
22 | isWireframeGeometry2: true,
23 |
24 | copy: function ( source ) {
25 |
26 | // todo
27 |
28 | return this;
29 |
30 | }
31 |
32 | } );
33 |
--------------------------------------------------------------------------------
/src/objects/line.js:
--------------------------------------------------------------------------------
1 | var THREE = require("../three.js");
2 | var utils = require("../utils/utils.js");
3 | var Objects = require('./objects.js');
4 |
5 | function line(obj){
6 |
7 | obj = utils._validate(obj, Objects.prototype._defaults.line);
8 |
9 | // Geometry
10 | var straightProject = utils.lnglatsToWorld(obj.geometry);
11 | var normalized = utils.normalizeVertices(straightProject);
12 | var flattenedArray = utils.flattenVectors(normalized.vertices);
13 | console.log('line', normalized.vertices)
14 |
15 | var geometry = new THREE.LineGeometry();
16 | geometry.setPositions( flattenedArray );
17 | // geometry.setColors( colors );
18 |
19 | // Material
20 | matLine = new THREE.LineMaterial( {
21 | color: obj.color,
22 | linewidth: obj.width, // in pixels
23 | dashed: false,
24 | opacity: obj.opacity
25 | } );
26 |
27 | matLine.resolution.set( window.innerWidth, window.innerHeight );
28 | matLine.isMaterial = true;
29 | matLine.transparent = true;
30 | matLine.depthWrite = false;
31 |
32 | // Mesh
33 | line = new THREE.Line2( geometry, matLine );
34 | line.position.copy(normalized.position)
35 | line.computeLineDistances();
36 |
37 | return line
38 | }
39 |
40 | module.exports = exports = line;
41 |
42 |
43 |
44 | /**
45 | * custom line shader by WestLangley, sourced from https://github.com/mrdoob/three.js/tree/master/examples/js/lines
46 | *
47 | */
48 |
49 | THREE.LineSegmentsGeometry = function () {
50 |
51 | THREE.InstancedBufferGeometry.call( this );
52 |
53 | this.type = 'LineSegmentsGeometry';
54 |
55 | var plane = new THREE.BufferGeometry();
56 |
57 | var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
58 | var uvs = [ 0, 1, 1, 1, 0, .5, 1, .5, 0, .5, 1, .5, 0, 0, 1, 0 ];
59 | var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
60 |
61 | this.setIndex( index );
62 | this.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
63 | this.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
64 |
65 | };
66 |
67 | THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), {
68 |
69 | constructor: THREE.LineSegmentsGeometry,
70 |
71 | isLineSegmentsGeometry: true,
72 |
73 | applyMatrix: function ( matrix ) {
74 |
75 | var start = this.attributes.instanceStart;
76 | var end = this.attributes.instanceEnd;
77 |
78 | if ( start !== undefined ) {
79 |
80 | matrix.applyToBufferAttribute( start );
81 |
82 | matrix.applyToBufferAttribute( end );
83 |
84 | start.data.needsUpdate = true;
85 |
86 | }
87 |
88 | if ( this.boundingBox !== null ) {
89 |
90 | this.computeBoundingBox();
91 |
92 | }
93 |
94 | if ( this.boundingSphere !== null ) {
95 |
96 | this.computeBoundingSphere();
97 |
98 | }
99 |
100 | return this;
101 |
102 | },
103 |
104 | setPositions: function ( array ) {
105 |
106 | var lineSegments;
107 |
108 | if ( array instanceof Float32Array ) {
109 |
110 | lineSegments = array;
111 |
112 | } else if ( Array.isArray( array ) ) {
113 |
114 | lineSegments = new Float32Array( array );
115 |
116 | }
117 |
118 | var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
119 |
120 | this.addAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
121 | this.addAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
122 |
123 | //
124 |
125 | this.computeBoundingBox();
126 | this.computeBoundingSphere();
127 |
128 | return this;
129 |
130 | },
131 |
132 | setColors: function ( array ) {
133 |
134 | var colors;
135 |
136 | if ( array instanceof Float32Array ) {
137 |
138 | colors = array;
139 |
140 | } else if ( Array.isArray( array ) ) {
141 |
142 | colors = new Float32Array( array );
143 |
144 | }
145 |
146 | var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
147 |
148 | this.addAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
149 | this.addAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
150 |
151 | return this;
152 |
153 | },
154 |
155 | fromWireframeGeometry: function ( geometry ) {
156 |
157 | this.setPositions( geometry.attributes.position.array );
158 |
159 | return this;
160 |
161 | },
162 |
163 | fromEdgesGeometry: function ( geometry ) {
164 |
165 | this.setPositions( geometry.attributes.position.array );
166 |
167 | return this;
168 |
169 | },
170 |
171 | fromMesh: function ( mesh ) {
172 |
173 | this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) );
174 |
175 | // set colors, maybe
176 |
177 | return this;
178 |
179 | },
180 |
181 | fromLineSegements: function ( lineSegments ) {
182 |
183 | var geometry = lineSegments.geometry;
184 |
185 | if ( geometry.isGeometry ) {
186 |
187 | this.setPositions( geometry.vertices );
188 |
189 | } else if ( geometry.isBufferGeometry ) {
190 |
191 | this.setPositions( geometry.position.array ); // assumes non-indexed
192 |
193 | }
194 |
195 | // set colors, maybe
196 |
197 | return this;
198 |
199 | },
200 |
201 | computeBoundingBox: function () {
202 |
203 | var box = new THREE.Box3();
204 |
205 | return function computeBoundingBox() {
206 |
207 | if ( this.boundingBox === null ) {
208 |
209 | this.boundingBox = new THREE.Box3();
210 |
211 | }
212 |
213 | var start = this.attributes.instanceStart;
214 | var end = this.attributes.instanceEnd;
215 |
216 | if ( start !== undefined && end !== undefined ) {
217 |
218 | this.boundingBox.setFromBufferAttribute( start );
219 |
220 | box.setFromBufferAttribute( end );
221 |
222 | this.boundingBox.union( box );
223 |
224 | }
225 |
226 | };
227 |
228 | }(),
229 |
230 | computeBoundingSphere: function () {
231 |
232 | var vector = new THREE.Vector3();
233 |
234 | return function computeBoundingSphere() {
235 |
236 | if ( this.boundingSphere === null ) {
237 |
238 | this.boundingSphere = new THREE.Sphere();
239 |
240 | }
241 |
242 | if ( this.boundingBox === null ) {
243 |
244 | this.computeBoundingBox();
245 |
246 | }
247 |
248 | var start = this.attributes.instanceStart;
249 | var end = this.attributes.instanceEnd;
250 |
251 | if ( start !== undefined && end !== undefined ) {
252 |
253 | var center = this.boundingSphere.center;
254 |
255 | this.boundingBox.getCenter( center );
256 |
257 | var maxRadiusSq = 0;
258 |
259 | for ( var i = 0, il = start.count; i < il; i ++ ) {
260 |
261 | vector.fromBufferAttribute( start, i );
262 | maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
263 |
264 | vector.fromBufferAttribute( end, i );
265 | maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
266 |
267 | }
268 |
269 | this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
270 |
271 | if ( isNaN( this.boundingSphere.radius ) ) {
272 |
273 | console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
274 |
275 | }
276 |
277 | }
278 |
279 | };
280 |
281 | }(),
282 |
283 | toJSON: function () {
284 |
285 | // todo
286 |
287 | },
288 |
289 | clone: function () {
290 |
291 | // todo
292 |
293 | },
294 |
295 | copy: function ( source ) {
296 |
297 | // todo
298 |
299 | return this;
300 |
301 | }
302 |
303 | } );
304 |
305 | /**
306 | * @author WestLangley / http://github.com/WestLangley
307 | *
308 | */
309 |
310 | THREE.LineGeometry = function () {
311 |
312 | THREE.LineSegmentsGeometry.call( this );
313 |
314 | this.type = 'LineGeometry';
315 |
316 | };
317 |
318 | THREE.LineGeometry.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
319 |
320 | constructor: THREE.LineGeometry,
321 |
322 | isLineGeometry: true,
323 |
324 | setPositions: function ( array ) {
325 |
326 | // converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format
327 |
328 | var length = array.length - 3;
329 | var points = new Float32Array( 2 * length );
330 |
331 | for ( var i = 0; i < length; i += 3 ) {
332 |
333 | points[ 2 * i ] = array[ i ];
334 | points[ 2 * i + 1 ] = array[ i + 1 ];
335 | points[ 2 * i + 2 ] = array[ i + 2 ];
336 |
337 | points[ 2 * i + 3 ] = array[ i + 3 ];
338 | points[ 2 * i + 4 ] = array[ i + 4 ];
339 | points[ 2 * i + 5 ] = array[ i + 5 ];
340 |
341 | }
342 |
343 | THREE.LineSegmentsGeometry.prototype.setPositions.call( this, points );
344 |
345 | return this;
346 |
347 | },
348 |
349 | setColors: function ( array ) {
350 |
351 | // converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format
352 |
353 | var length = array.length - 3;
354 | var colors = new Float32Array( 2 * length );
355 |
356 | for ( var i = 0; i < length; i += 3 ) {
357 |
358 | colors[ 2 * i ] = array[ i ];
359 | colors[ 2 * i + 1 ] = array[ i + 1 ];
360 | colors[ 2 * i + 2 ] = array[ i + 2 ];
361 |
362 | colors[ 2 * i + 3 ] = array[ i + 3 ];
363 | colors[ 2 * i + 4 ] = array[ i + 4 ];
364 | colors[ 2 * i + 5 ] = array[ i + 5 ];
365 |
366 | }
367 |
368 | THREE.LineSegmentsGeometry.prototype.setColors.call( this, colors );
369 |
370 | return this;
371 |
372 | },
373 |
374 | fromLine: function ( line ) {
375 |
376 | var geometry = line.geometry;
377 |
378 | if ( geometry.isGeometry ) {
379 |
380 | this.setPositions( geometry.vertices );
381 |
382 | } else if ( geometry.isBufferGeometry ) {
383 |
384 | this.setPositions( geometry.position.array ); // assumes non-indexed
385 |
386 | }
387 |
388 | // set colors, maybe
389 |
390 | return this;
391 |
392 | },
393 |
394 | copy: function ( source ) {
395 |
396 | // todo
397 |
398 | return this;
399 |
400 | }
401 |
402 | } );
403 |
404 | /**
405 | * @author WestLangley / http://github.com/WestLangley
406 | *
407 | */
408 |
409 | THREE.WireframeGeometry2 = function ( geometry ) {
410 |
411 | THREE.LineSegmentsGeometry.call( this );
412 |
413 | this.type = 'WireframeGeometry2';
414 |
415 | this.fromWireframeGeometry( new THREE.WireframeGeometry( geometry ) );
416 |
417 | // set colors, maybe
418 |
419 | };
420 |
421 | THREE.WireframeGeometry2.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
422 |
423 | constructor: THREE.WireframeGeometry2,
424 |
425 | isWireframeGeometry2: true,
426 |
427 | copy: function ( source ) {
428 |
429 | // todo
430 |
431 | return this;
432 |
433 | }
434 |
435 | } );
436 |
437 | /**
438 | * @author WestLangley / http://github.com/WestLangley
439 | *
440 | * parameters = {
441 | * color: ,
442 | * linewidth: ,
443 | * dashed: ,
444 | * dashScale: ,
445 | * dashSize: ,
446 | * gapSize: ,
447 | * resolution: , // to be set by renderer
448 | * }
449 | */
450 |
451 | THREE.UniformsLib.line = {
452 |
453 | linewidth: { value: 1 },
454 | resolution: { value: new THREE.Vector2( 1, 1 ) },
455 | dashScale: { value: 1 },
456 | dashSize: { value: 1 },
457 | gapSize: { value: 1 } // todo FIX - maybe change to totalSize
458 |
459 | };
460 |
461 | THREE.ShaderLib[ 'line' ] = {
462 |
463 | uniforms: THREE.UniformsUtils.merge( [
464 | THREE.UniformsLib.common,
465 | THREE.UniformsLib.fog,
466 | THREE.UniformsLib.line
467 | ] ),
468 |
469 | vertexShader:
470 | `
471 | #include
472 | #include
473 | #include
474 | #include
475 | #include
476 |
477 | uniform float linewidth;
478 | uniform vec2 resolution;
479 |
480 | attribute vec3 instanceStart;
481 | attribute vec3 instanceEnd;
482 |
483 | attribute vec3 instanceColorStart;
484 | attribute vec3 instanceColorEnd;
485 |
486 | varying vec2 vUv;
487 |
488 | #ifdef USE_DASH
489 |
490 | uniform float dashScale;
491 | attribute float instanceDistanceStart;
492 | attribute float instanceDistanceEnd;
493 | varying float vLineDistance;
494 |
495 | #endif
496 |
497 | void trimSegment( const in vec4 start, inout vec4 end ) {
498 |
499 | // trim end segment so it terminates between the camera plane and the near plane
500 |
501 | // conservative estimate of the near plane
502 | float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
503 | float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
504 | float nearEstimate = - 0.5 * b / a;
505 |
506 | float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
507 |
508 | end.xyz = mix( start.xyz, end.xyz, alpha );
509 |
510 | }
511 |
512 | void main() {
513 |
514 | #ifdef USE_COLOR
515 |
516 | vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
517 |
518 | #endif
519 |
520 | #ifdef USE_DASH
521 |
522 | vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
523 |
524 | #endif
525 |
526 | float aspect = resolution.x / resolution.y;
527 |
528 | vUv = uv;
529 |
530 | // camera space
531 | vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
532 | vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
533 |
534 | // special case for perspective projection, and segments that terminate either in, or behind, the camera plane
535 | // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
536 | // but we need to perform ndc-space calculations in the shader, so we must address this issue directly
537 | // perhaps there is a more elegant solution -- WestLangley
538 |
539 | bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
540 |
541 | if ( perspective ) {
542 |
543 | if ( start.z < 0.0 && end.z >= 0.0 ) {
544 |
545 | trimSegment( start, end );
546 |
547 | } else if ( end.z < 0.0 && start.z >= 0.0 ) {
548 |
549 | trimSegment( end, start );
550 |
551 | }
552 |
553 | }
554 |
555 | // clip space
556 | vec4 clipStart = projectionMatrix * start;
557 | vec4 clipEnd = projectionMatrix * end;
558 |
559 | // ndc space
560 | vec2 ndcStart = clipStart.xy / clipStart.w;
561 | vec2 ndcEnd = clipEnd.xy / clipEnd.w;
562 |
563 | // direction
564 | vec2 dir = ndcEnd - ndcStart;
565 |
566 | // account for clip-space aspect ratio
567 | dir.x *= aspect;
568 | dir = normalize( dir );
569 |
570 | // perpendicular to dir
571 | vec2 offset = vec2( dir.y, - dir.x );
572 |
573 | // undo aspect ratio adjustment
574 | dir.x /= aspect;
575 | offset.x /= aspect;
576 |
577 | // sign flip
578 | if ( position.x < 0.0 ) offset *= - 1.0;
579 |
580 | // endcaps
581 | if ( position.y < 0.0 ) {
582 |
583 | offset += - dir;
584 |
585 | } else if ( position.y > 1.0 ) {
586 |
587 | offset += dir;
588 |
589 | }
590 |
591 | // adjust for linewidth
592 | offset *= linewidth;
593 |
594 | // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
595 | offset /= resolution.y;
596 |
597 | // select end
598 | vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
599 |
600 | // back to clip space
601 | offset *= clip.w;
602 |
603 | clip.xy += offset;
604 |
605 | gl_Position = clip;
606 |
607 | #include
608 |
609 | #include
610 | #include
611 | #include
612 |
613 | }
614 | `,
615 |
616 | fragmentShader:
617 | `
618 | uniform vec3 diffuse;
619 | uniform float opacity;
620 |
621 | #ifdef USE_DASH
622 |
623 | uniform float dashSize;
624 | uniform float gapSize;
625 |
626 | #endif
627 |
628 | varying float vLineDistance;
629 |
630 | #include
631 | #include
632 | #include
633 | #include
634 | #include
635 |
636 | varying vec2 vUv;
637 |
638 | void main() {
639 |
640 | #include
641 |
642 | #ifdef USE_DASH
643 |
644 | if ( vUv.y < 0.5 || vUv.y > 0.5 ) discard; // discard endcaps
645 |
646 | if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
647 |
648 | #endif
649 |
650 | if ( vUv.y < 0.5 || vUv.y > 0.5 ) {
651 |
652 | float a = vUv.x - 0.5;
653 | float b = vUv.y - 0.5;
654 | float len2 = a * a + b * b;
655 |
656 | if ( len2 > 0.25 ) discard;
657 |
658 | }
659 |
660 | vec4 diffuseColor = vec4( diffuse, opacity );
661 |
662 | #include
663 | #include
664 |
665 | gl_FragColor = vec4( diffuseColor.rgb, opacity );
666 |
667 | #include
668 | #include
669 | #include
670 | #include
671 |
672 | }
673 | `
674 | };
675 |
676 | THREE.LineMaterial = function ( parameters ) {
677 |
678 | var lineUniforms = THREE.UniformsUtils.clone( THREE.ShaderLib[ 'line' ].uniforms );
679 | lineUniforms.opacity.value = parameters.opacity;
680 |
681 | THREE.ShaderMaterial.call( this, {
682 |
683 | type: 'LineMaterial',
684 |
685 | uniforms: lineUniforms,
686 |
687 | vertexShader: THREE.ShaderLib[ 'line' ].vertexShader,
688 | fragmentShader: THREE.ShaderLib[ 'line' ].fragmentShader
689 |
690 | } );
691 |
692 | this.dashed = false;
693 |
694 | Object.defineProperties( this, {
695 |
696 | color: {
697 |
698 | enumerable: true,
699 |
700 | get: function () {
701 |
702 | return this.uniforms.diffuse.value;
703 |
704 | },
705 |
706 | set: function ( value ) {
707 |
708 | this.uniforms.diffuse.value = value;
709 |
710 | }
711 |
712 | },
713 |
714 | linewidth: {
715 |
716 | enumerable: true,
717 |
718 | get: function () {
719 |
720 | return this.uniforms.linewidth.value;
721 |
722 | },
723 |
724 | set: function ( value ) {
725 |
726 | this.uniforms.linewidth.value = value;
727 |
728 | }
729 |
730 | },
731 |
732 | dashScale: {
733 |
734 | enumerable: true,
735 |
736 | get: function () {
737 |
738 | return this.uniforms.dashScale.value;
739 |
740 | },
741 |
742 | set: function ( value ) {
743 |
744 | this.uniforms.dashScale.value = value;
745 |
746 | }
747 |
748 | },
749 |
750 | dashSize: {
751 |
752 | enumerable: true,
753 |
754 | get: function () {
755 |
756 | return this.uniforms.dashSize.value;
757 |
758 | },
759 |
760 | set: function ( value ) {
761 |
762 | this.uniforms.dashSize.value = value;
763 |
764 | }
765 |
766 | },
767 |
768 | gapSize: {
769 |
770 | enumerable: true,
771 |
772 | get: function () {
773 |
774 | return this.uniforms.gapSize.value;
775 |
776 | },
777 |
778 | set: function ( value ) {
779 |
780 | this.uniforms.gapSize.value = value;
781 |
782 | }
783 |
784 | },
785 |
786 | resolution: {
787 |
788 | enumerable: true,
789 |
790 | get: function () {
791 |
792 | return this.uniforms.resolution.value;
793 |
794 | },
795 |
796 | set: function ( value ) {
797 |
798 | this.uniforms.resolution.value.copy( value );
799 |
800 | }
801 |
802 | }
803 |
804 | } );
805 |
806 | this.setValues( parameters );
807 |
808 | };
809 |
810 | THREE.LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
811 | THREE.LineMaterial.prototype.constructor = THREE.LineMaterial;
812 |
813 | THREE.LineMaterial.prototype.isLineMaterial = true;
814 |
815 | THREE.LineMaterial.prototype.copy = function ( source ) {
816 |
817 | THREE.ShaderMaterial.prototype.copy.call( this, source );
818 |
819 | this.color.copy( source.color );
820 |
821 | this.linewidth = source.linewidth;
822 |
823 | this.resolution = source.resolution;
824 |
825 | // todo
826 |
827 | return this;
828 |
829 | };
830 |
831 | /**
832 | * @author WestLangley / http://github.com/WestLangley
833 | *
834 | */
835 |
836 | THREE.LineSegments2 = function ( geometry, material ) {
837 |
838 | THREE.Mesh.call( this );
839 |
840 | this.type = 'LineSegments2';
841 |
842 | this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
843 | this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
844 |
845 | };
846 |
847 | THREE.LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
848 |
849 | constructor: THREE.LineSegments2,
850 |
851 | isLineSegments2: true,
852 |
853 | computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
854 |
855 | var start = new THREE.Vector3();
856 | var end = new THREE.Vector3();
857 |
858 | return function computeLineDistances() {
859 |
860 | var geometry = this.geometry;
861 |
862 | var instanceStart = geometry.attributes.instanceStart;
863 | var instanceEnd = geometry.attributes.instanceEnd;
864 | var lineDistances = new Float32Array( 2 * instanceStart.data.count );
865 |
866 | for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
867 |
868 | start.fromBufferAttribute( instanceStart, i );
869 | end.fromBufferAttribute( instanceEnd, i );
870 |
871 | lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
872 | lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
873 |
874 | }
875 |
876 | var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
877 |
878 | geometry.addAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
879 | geometry.addAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
880 |
881 | return this;
882 |
883 | };
884 |
885 | }() ),
886 |
887 | copy: function ( source ) {
888 |
889 | // todo
890 |
891 | return this;
892 |
893 | }
894 |
895 | } );
896 |
897 | /**
898 | * @author WestLangley / http://github.com/WestLangley
899 | *
900 | */
901 |
902 | THREE.Line2 = function ( geometry, material ) {
903 |
904 | THREE.LineSegments2.call( this );
905 |
906 | this.type = 'Line2';
907 |
908 | this.geometry = geometry !== undefined ? geometry : new THREE.LineGeometry();
909 | this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
910 |
911 | };
912 |
913 | THREE.Line2.prototype = Object.assign( Object.create( THREE.LineSegments2.prototype ), {
914 |
915 | constructor: THREE.Line2,
916 |
917 | isLine2: true,
918 |
919 | copy: function ( source ) {
920 |
921 | // todo
922 |
923 | return this;
924 |
925 | }
926 |
927 | } );
928 |
929 | /**
930 | * @author WestLangley / http://github.com/WestLangley
931 | *
932 | */
933 |
934 | THREE.Wireframe = function ( geometry, material ) {
935 |
936 | THREE.Mesh.call( this );
937 |
938 | this.type = 'Wireframe';
939 |
940 | this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
941 | this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
942 |
943 | };
944 |
945 | THREE.Wireframe.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
946 |
947 | constructor: THREE.Wireframe,
948 |
949 | isWireframe: true,
950 |
951 | computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
952 |
953 | var start = new THREE.Vector3();
954 | var end = new THREE.Vector3();
955 |
956 | return function computeLineDistances() {
957 |
958 | var geometry = this.geometry;
959 |
960 | var instanceStart = geometry.attributes.instanceStart;
961 | var instanceEnd = geometry.attributes.instanceEnd;
962 | var lineDistances = new Float32Array( 2 * instanceStart.data.count );
963 |
964 | for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
965 |
966 | start.fromBufferAttribute( instanceStart, i );
967 | end.fromBufferAttribute( instanceEnd, i );
968 |
969 | lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
970 | lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
971 |
972 | }
973 |
974 | var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
975 |
976 | geometry.addAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
977 | geometry.addAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
978 |
979 | return this;
980 |
981 | };
982 |
983 | }() ),
984 |
985 | copy: function ( source ) {
986 |
987 | // todo
988 |
989 | return this;
990 |
991 | }
992 |
993 | } );
994 |
--------------------------------------------------------------------------------
/src/objects/loadObj.js:
--------------------------------------------------------------------------------
1 | var utils = require("../utils/utils.js");
2 | var Objects = require('./objects.js');
3 | const OBJLoader = require("./loaders/OBJLoader.js");
4 | const MTLLoader = require("./loaders/MTLLoader.js");
5 |
6 | function loadObj(options, cb){
7 |
8 | if (options === undefined) return console.error("Invalid options provided to loadObj()");
9 |
10 | this.loaded = false;
11 |
12 | const modelComplete = (m) => {
13 | console.log("Model complete!", m);
14 |
15 | if(--remaining === 0) this.loaded = true;
16 | }
17 |
18 |
19 |
20 | // TODO: Support formats other than OBJ/MTL
21 | const objLoader = new OBJLoader();
22 | const materialLoader = new MTLLoader();
23 | materialLoader.load(options.mtl, loadObject, () => (null), error => {
24 | console.warn("No material file found for SymbolLayer3D model " + m);
25 | });
26 |
27 | function loadObject(materials) {
28 |
29 | if (materials) {
30 | materials.preload();
31 | objLoader.setMaterials( materials );
32 | }
33 |
34 | objLoader.load(options.obj, obj => {
35 |
36 | var r = utils.types.rotation(options, [0, 0, 0]);
37 | var s = utils.types.scale(options, [1, 1, 1]);
38 |
39 | obj = obj.children[0];
40 | obj.rotation.set(r[0] + Math.PI/2, r[1] + Math.PI, r[2]);
41 | obj.scale.set(s[0], s[1], s[2]);
42 |
43 | var projScaleGroup = new THREE.Group();
44 | projScaleGroup.add(obj)
45 | var userScaleGroup = Objects.prototype._makeGroup(projScaleGroup, options);
46 | Objects.prototype._addMethods(userScaleGroup);
47 |
48 | cb(userScaleGroup);
49 |
50 | }, () => (null), error => {
51 | console.error("Could not load model file.");
52 | } );
53 |
54 | };
55 | }
56 |
57 |
58 | module.exports = exports = loadObj;
--------------------------------------------------------------------------------
/src/objects/loaders/MTLLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Loads a Wavefront .mtl file specifying materials
3 | *
4 | * @author angelxuanchang
5 | */
6 |
7 | const THREE = require('../../three.js');
8 |
9 | const MTLLoader = function ( manager ) {
10 |
11 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
12 |
13 | };
14 |
15 | MTLLoader.prototype = {
16 |
17 | constructor: MTLLoader,
18 |
19 | /**
20 | * Loads and parses a MTL asset from a URL.
21 | *
22 | * @param {String} url - URL to the MTL file.
23 | * @param {Function} [onLoad] - Callback invoked with the loaded object.
24 | * @param {Function} [onProgress] - Callback for download progress.
25 | * @param {Function} [onError] - Callback for download errors.
26 | *
27 | * @see setPath setTexturePath
28 | *
29 | * @note In order for relative texture references to resolve correctly
30 | * you must call setPath and/or setTexturePath explicitly prior to load.
31 | */
32 | load: function ( url, onLoad, onProgress, onError ) {
33 |
34 | var scope = this;
35 |
36 | var loader = new THREE.FileLoader( this.manager );
37 | loader.setPath( this.path );
38 | loader.load( url, function ( text ) {
39 |
40 | onLoad( scope.parse( text ) );
41 |
42 | }, onProgress, onError );
43 |
44 | },
45 |
46 | /**
47 | * Set base path for resolving references.
48 | * If set this path will be prepended to each loaded and found reference.
49 | *
50 | * @see setTexturePath
51 | * @param {String} path
52 | *
53 | * @example
54 | * mtlLoader.setPath( 'assets/obj/' );
55 | * mtlLoader.load( 'my.mtl', ... );
56 | */
57 | setPath: function ( path ) {
58 |
59 | this.path = path;
60 |
61 | },
62 |
63 | /**
64 | * Set base path for resolving texture references.
65 | * If set this path will be prepended found texture reference.
66 | * If not set and setPath is, it will be used as texture base path.
67 | *
68 | * @see setPath
69 | * @param {String} path
70 | *
71 | * @example
72 | * mtlLoader.setPath( 'assets/obj/' );
73 | * mtlLoader.setTexturePath( 'assets/textures/' );
74 | * mtlLoader.load( 'my.mtl', ... );
75 | */
76 | setTexturePath: function ( path ) {
77 |
78 | this.texturePath = path;
79 |
80 | },
81 |
82 | setBaseUrl: function ( path ) {
83 |
84 | console.warn( 'THREE.MTLLoader: .setBaseUrl() is deprecated. Use .setTexturePath( path ) for texture path or .setPath( path ) for general base path instead.' );
85 |
86 | this.setTexturePath( path );
87 |
88 | },
89 |
90 | setCrossOrigin: function ( value ) {
91 |
92 | this.crossOrigin = value;
93 |
94 | },
95 |
96 | setMaterialOptions: function ( value ) {
97 |
98 | this.materialOptions = value;
99 |
100 | },
101 |
102 | /**
103 | * Parses a MTL file.
104 | *
105 | * @param {String} text - Content of MTL file
106 | * @return {THREE.MTLLoader.MaterialCreator}
107 | *
108 | * @see setPath setTexturePath
109 | *
110 | * @note In order for relative texture references to resolve correctly
111 | * you must call setPath and/or setTexturePath explicitly prior to parse.
112 | */
113 | parse: function ( text ) {
114 |
115 | var lines = text.split( '\n' );
116 | var info = {};
117 | var delimiter_pattern = /\s+/;
118 | var materialsInfo = {};
119 |
120 | for ( var i = 0; i < lines.length; i ++ ) {
121 |
122 | var line = lines[ i ];
123 | line = line.trim();
124 |
125 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
126 |
127 | // Blank line or comment ignore
128 | continue;
129 |
130 | }
131 |
132 | var pos = line.indexOf( ' ' );
133 |
134 | var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
135 | key = key.toLowerCase();
136 |
137 | var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
138 | value = value.trim();
139 |
140 | if ( key === 'newmtl' ) {
141 |
142 | // New material
143 |
144 | info = { name: value };
145 | materialsInfo[ value ] = info;
146 |
147 | } else if ( info ) {
148 |
149 | if ( key === 'ka' || key === 'kd' || key === 'ks' ) {
150 |
151 | var ss = value.split( delimiter_pattern, 3 );
152 | info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
153 |
154 | } else {
155 |
156 | info[ key ] = value;
157 |
158 | }
159 |
160 | }
161 |
162 | }
163 |
164 | var materialCreator = new MTLLoader.MaterialCreator( this.texturePath || this.path, this.materialOptions );
165 | materialCreator.setCrossOrigin( this.crossOrigin );
166 | materialCreator.setManager( this.manager );
167 | materialCreator.setMaterials( materialsInfo );
168 | return materialCreator;
169 |
170 | }
171 |
172 | };
173 |
174 | /**
175 | * Create a new THREE-MTLLoader.MaterialCreator
176 | * @param baseUrl - Url relative to which textures are loaded
177 | * @param options - Set of options on how to construct the materials
178 | * side: Which side to apply the material
179 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
180 | * wrap: What type of wrapping to apply for textures
181 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
182 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
183 | * Default: false, assumed to be already normalized
184 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
185 | * Default: false
186 | * @constructor
187 | */
188 |
189 | MTLLoader.MaterialCreator = function ( baseUrl, options ) {
190 |
191 | this.baseUrl = baseUrl || '';
192 | this.options = options;
193 | this.materialsInfo = {};
194 | this.materials = {};
195 | this.materialsArray = [];
196 | this.nameLookup = {};
197 |
198 | this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
199 | this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;
200 |
201 | };
202 |
203 | MTLLoader.MaterialCreator.prototype = {
204 |
205 | constructor: MTLLoader.MaterialCreator,
206 |
207 | setCrossOrigin: function ( value ) {
208 |
209 | this.crossOrigin = value;
210 |
211 | },
212 |
213 | setManager: function ( value ) {
214 |
215 | this.manager = value;
216 |
217 | },
218 |
219 | setMaterials: function ( materialsInfo ) {
220 |
221 | this.materialsInfo = this.convert( materialsInfo );
222 | this.materials = {};
223 | this.materialsArray = [];
224 | this.nameLookup = {};
225 |
226 | },
227 |
228 | convert: function ( materialsInfo ) {
229 |
230 | if ( ! this.options ) return materialsInfo;
231 |
232 | var converted = {};
233 |
234 | for ( var mn in materialsInfo ) {
235 |
236 | // Convert materials info into normalized form based on options
237 |
238 | var mat = materialsInfo[ mn ];
239 |
240 | var covmat = {};
241 |
242 | converted[ mn ] = covmat;
243 |
244 | for ( var prop in mat ) {
245 |
246 | var save = true;
247 | var value = mat[ prop ];
248 | var lprop = prop.toLowerCase();
249 |
250 | switch ( lprop ) {
251 |
252 | case 'kd':
253 | case 'ka':
254 | case 'ks':
255 |
256 | // Diffuse color (color under white light) using RGB values
257 |
258 | if ( this.options && this.options.normalizeRGB ) {
259 |
260 | value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
261 |
262 | }
263 |
264 | if ( this.options && this.options.ignoreZeroRGBs ) {
265 |
266 | if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
267 |
268 | // ignore
269 |
270 | save = false;
271 |
272 | }
273 |
274 | }
275 |
276 | break;
277 |
278 | default:
279 |
280 | break;
281 |
282 | }
283 |
284 | if ( save ) {
285 |
286 | covmat[ lprop ] = value;
287 |
288 | }
289 |
290 | }
291 |
292 | }
293 |
294 | return converted;
295 |
296 | },
297 |
298 | preload: function () {
299 |
300 | for ( var mn in this.materialsInfo ) {
301 |
302 | this.create( mn );
303 |
304 | }
305 |
306 | },
307 |
308 | getIndex: function ( materialName ) {
309 |
310 | return this.nameLookup[ materialName ];
311 |
312 | },
313 |
314 | getAsArray: function () {
315 |
316 | var index = 0;
317 |
318 | for ( var mn in this.materialsInfo ) {
319 |
320 | this.materialsArray[ index ] = this.create( mn );
321 | this.nameLookup[ mn ] = index;
322 | index ++;
323 |
324 | }
325 |
326 | return this.materialsArray;
327 |
328 | },
329 |
330 | create: function ( materialName ) {
331 |
332 | if ( this.materials[ materialName ] === undefined ) {
333 |
334 | this.createMaterial_( materialName );
335 |
336 | }
337 |
338 | return this.materials[ materialName ];
339 |
340 | },
341 |
342 | createMaterial_: function ( materialName ) {
343 |
344 | // Create material
345 |
346 | var scope = this;
347 | var mat = this.materialsInfo[ materialName ];
348 | var params = {
349 |
350 | name: materialName,
351 | side: this.side
352 |
353 | };
354 |
355 | function resolveURL( baseUrl, url ) {
356 |
357 | if ( typeof url !== 'string' || url === '' )
358 | return '';
359 |
360 | // Absolute URL
361 | if ( /^https?:\/\//i.test( url ) ) return url;
362 |
363 | return baseUrl + url;
364 |
365 | }
366 |
367 | function setMapForType( mapType, value ) {
368 |
369 | if ( params[ mapType ] ) return; // Keep the first encountered texture
370 |
371 | var texParams = scope.getTextureParams( value, params );
372 | var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
373 |
374 | map.repeat.copy( texParams.scale );
375 | map.offset.copy( texParams.offset );
376 |
377 | map.wrapS = scope.wrap;
378 | map.wrapT = scope.wrap;
379 |
380 | params[ mapType ] = map;
381 |
382 | }
383 |
384 | for ( var prop in mat ) {
385 |
386 | var value = mat[ prop ];
387 |
388 | if ( value === '' ) continue;
389 |
390 | switch ( prop.toLowerCase() ) {
391 |
392 | // Ns is material specular exponent
393 |
394 | case 'kd':
395 |
396 | // Diffuse color (color under white light) using RGB values
397 |
398 | params.color = new THREE.Color().fromArray( value );
399 |
400 | break;
401 |
402 | case 'ks':
403 |
404 | // Specular color (color when light is reflected from shiny surface) using RGB values
405 | params.specular = new THREE.Color().fromArray( value );
406 |
407 | break;
408 |
409 | case 'map_kd':
410 |
411 | // Diffuse texture map
412 |
413 | setMapForType( "map", value );
414 |
415 | break;
416 |
417 | case 'map_ks':
418 |
419 | // Specular map
420 |
421 | setMapForType( "specularMap", value );
422 |
423 | break;
424 |
425 | case 'map_bump':
426 | case 'bump':
427 |
428 | // Bump texture map
429 |
430 | setMapForType( "bumpMap", value );
431 |
432 | break;
433 |
434 | case 'ns':
435 |
436 | // The specular exponent (defines the focus of the specular highlight)
437 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
438 | params.shininess = parseFloat( value );
439 |
440 | break;
441 |
442 | case 'd':
443 |
444 | if ( value < 1 ) {
445 |
446 | params.opacity = value;
447 | params.transparent = true;
448 |
449 | }
450 |
451 | break;
452 |
453 | case 'Tr':
454 |
455 | if ( value > 0 ) {
456 |
457 | params.opacity = 1 - value;
458 | params.transparent = true;
459 |
460 | }
461 |
462 | break;
463 |
464 | default:
465 | break;
466 |
467 | }
468 |
469 | }
470 |
471 | this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
472 | return this.materials[ materialName ];
473 |
474 | },
475 |
476 | getTextureParams: function ( value, matParams ) {
477 |
478 | var texParams = {
479 |
480 | scale: new THREE.Vector2( 1, 1 ),
481 | offset: new THREE.Vector2( 0, 0 )
482 |
483 | };
484 |
485 | var items = value.split( /\s+/ );
486 | var pos;
487 |
488 | pos = items.indexOf( '-bm' );
489 |
490 | if ( pos >= 0 ) {
491 |
492 | matParams.bumpScale = parseFloat( items[ pos + 1 ] );
493 | items.splice( pos, 2 );
494 |
495 | }
496 |
497 | pos = items.indexOf( '-s' );
498 |
499 | if ( pos >= 0 ) {
500 |
501 | texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
502 | items.splice( pos, 4 ); // we expect 3 parameters here!
503 |
504 | }
505 |
506 | pos = items.indexOf( '-o' );
507 |
508 | if ( pos >= 0 ) {
509 |
510 | texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
511 | items.splice( pos, 4 ); // we expect 3 parameters here!
512 |
513 | }
514 |
515 | texParams.url = items.join( ' ' ).trim();
516 | return texParams;
517 |
518 | },
519 |
520 | loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
521 |
522 | var texture;
523 | var loader = THREE.Loader.Handlers.get( url );
524 | var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
525 |
526 | if ( loader === null ) {
527 |
528 | loader = new THREE.TextureLoader( manager );
529 |
530 | }
531 |
532 | if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
533 | texture = loader.load( url, onLoad, onProgress, onError );
534 |
535 | if ( mapping !== undefined ) texture.mapping = mapping;
536 |
537 | return texture;
538 |
539 | }
540 |
541 | };
542 |
543 | module.exports = exports = MTLLoader;
--------------------------------------------------------------------------------
/src/objects/loaders/OBJLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 | const THREE = require('../../three.js');
5 |
6 | const OBJLoader = function ( manager ) {
7 |
8 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
9 |
10 | this.materials = null;
11 |
12 | this.regexp = {
13 | // v float float float
14 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
15 | // vn float float float
16 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
17 | // vt float float
18 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/,
19 | // f vertex vertex vertex
20 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/,
21 | // f vertex/uv vertex/uv vertex/uv
22 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/,
23 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal
24 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/,
25 | // f vertex//normal vertex//normal vertex//normal
26 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/,
27 | // o object_name | g group_name
28 | object_pattern : /^[og]\s*(.+)?/,
29 | // s boolean
30 | smoothing_pattern : /^s\s+(\d+|on|off)/,
31 | // mtllib file_reference
32 | material_library_pattern : /^mtllib /,
33 | // usemtl material_name
34 | material_use_pattern : /^usemtl /
35 | };
36 |
37 | };
38 |
39 | OBJLoader.prototype = {
40 |
41 | constructor: OBJLoader,
42 |
43 | load: function ( url, onLoad, onProgress, onError ) {
44 |
45 | var scope = this;
46 |
47 | var loader = new THREE.FileLoader( scope.manager );
48 | loader.setPath( this.path );
49 | loader.load( url, function ( text ) {
50 |
51 | onLoad( scope.parse( text ) );
52 |
53 | }, onProgress, onError );
54 |
55 | },
56 |
57 | setPath: function ( value ) {
58 |
59 | this.path = value;
60 |
61 | },
62 |
63 | setMaterials: function ( materials ) {
64 |
65 | this.materials = materials;
66 |
67 | },
68 |
69 | _createParserState : function () {
70 |
71 | var state = {
72 | objects : [],
73 | object : {},
74 |
75 | vertices : [],
76 | normals : [],
77 | uvs : [],
78 |
79 | materialLibraries : [],
80 |
81 | startObject: function ( name, fromDeclaration ) {
82 |
83 | // If the current object (initial from reset) is not from a g/o declaration in the parsed
84 | // file. We need to use it for the first parsed g/o to keep things in sync.
85 | if ( this.object && this.object.fromDeclaration === false ) {
86 |
87 | this.object.name = name;
88 | this.object.fromDeclaration = ( fromDeclaration !== false );
89 | return;
90 |
91 | }
92 |
93 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
94 |
95 | if ( this.object && typeof this.object._finalize === 'function' ) {
96 |
97 | this.object._finalize( true );
98 |
99 | }
100 |
101 | this.object = {
102 | name : name || '',
103 | fromDeclaration : ( fromDeclaration !== false ),
104 |
105 | geometry : {
106 | vertices : [],
107 | normals : [],
108 | uvs : []
109 | },
110 | materials : [],
111 | smooth : true,
112 |
113 | startMaterial : function( name, libraries ) {
114 |
115 | var previous = this._finalize( false );
116 |
117 | // New usemtl declaration overwrites an inherited material, except if faces were declared
118 | // after the material, then it must be preserved for proper MultiMaterial continuation.
119 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
120 |
121 | this.materials.splice( previous.index, 1 );
122 |
123 | }
124 |
125 | var material = {
126 | index : this.materials.length,
127 | name : name || '',
128 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
129 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ),
130 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ),
131 | groupEnd : -1,
132 | groupCount : -1,
133 | inherited : false,
134 |
135 | clone : function( index ) {
136 | var cloned = {
137 | index : ( typeof index === 'number' ? index : this.index ),
138 | name : this.name,
139 | mtllib : this.mtllib,
140 | smooth : this.smooth,
141 | groupStart : 0,
142 | groupEnd : -1,
143 | groupCount : -1,
144 | inherited : false
145 | };
146 | cloned.clone = this.clone.bind(cloned);
147 | return cloned;
148 | }
149 | };
150 |
151 | this.materials.push( material );
152 |
153 | return material;
154 |
155 | },
156 |
157 | currentMaterial : function() {
158 |
159 | if ( this.materials.length > 0 ) {
160 | return this.materials[ this.materials.length - 1 ];
161 | }
162 |
163 | return undefined;
164 |
165 | },
166 |
167 | _finalize : function( end ) {
168 |
169 | var lastMultiMaterial = this.currentMaterial();
170 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) {
171 |
172 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
173 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
174 | lastMultiMaterial.inherited = false;
175 |
176 | }
177 |
178 | // Ignore objects tail materials if no face declarations followed them before a new o/g started.
179 | if ( end && this.materials.length > 1 ) {
180 |
181 | for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) {
182 | if ( this.materials[mi].groupCount <= 0 ) {
183 | this.materials.splice( mi, 1 );
184 | }
185 | }
186 |
187 | }
188 |
189 | // Guarantee at least one empty material, this makes the creation later more straight forward.
190 | if ( end && this.materials.length === 0 ) {
191 |
192 | this.materials.push({
193 | name : '',
194 | smooth : this.smooth
195 | });
196 |
197 | }
198 |
199 | return lastMultiMaterial;
200 |
201 | }
202 | };
203 |
204 | // Inherit previous objects material.
205 | // Spec tells us that a declared material must be set to all objects until a new material is declared.
206 | // If a usemtl declaration is encountered while this new object is being parsed, it will
207 | // overwrite the inherited material. Exception being that there was already face declarations
208 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
209 |
210 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) {
211 |
212 | var declared = previousMaterial.clone( 0 );
213 | declared.inherited = true;
214 | this.object.materials.push( declared );
215 |
216 | }
217 |
218 | this.objects.push( this.object );
219 |
220 | },
221 |
222 | finalize : function() {
223 |
224 | if ( this.object && typeof this.object._finalize === 'function' ) {
225 |
226 | this.object._finalize( true );
227 |
228 | }
229 |
230 | },
231 |
232 | parseVertexIndex: function ( value, len ) {
233 |
234 | var index = parseInt( value, 10 );
235 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
236 |
237 | },
238 |
239 | parseNormalIndex: function ( value, len ) {
240 |
241 | var index = parseInt( value, 10 );
242 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
243 |
244 | },
245 |
246 | parseUVIndex: function ( value, len ) {
247 |
248 | var index = parseInt( value, 10 );
249 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
250 |
251 | },
252 |
253 | addVertex: function ( a, b, c ) {
254 |
255 | var src = this.vertices;
256 | var dst = this.object.geometry.vertices;
257 |
258 | dst.push( src[ a + 0 ] );
259 | dst.push( src[ a + 1 ] );
260 | dst.push( src[ a + 2 ] );
261 | dst.push( src[ b + 0 ] );
262 | dst.push( src[ b + 1 ] );
263 | dst.push( src[ b + 2 ] );
264 | dst.push( src[ c + 0 ] );
265 | dst.push( src[ c + 1 ] );
266 | dst.push( src[ c + 2 ] );
267 |
268 | },
269 |
270 | addVertexLine: function ( a ) {
271 |
272 | var src = this.vertices;
273 | var dst = this.object.geometry.vertices;
274 |
275 | dst.push( src[ a + 0 ] );
276 | dst.push( src[ a + 1 ] );
277 | dst.push( src[ a + 2 ] );
278 |
279 | },
280 |
281 | addNormal : function ( a, b, c ) {
282 |
283 | var src = this.normals;
284 | var dst = this.object.geometry.normals;
285 |
286 | dst.push( src[ a + 0 ] );
287 | dst.push( src[ a + 1 ] );
288 | dst.push( src[ a + 2 ] );
289 | dst.push( src[ b + 0 ] );
290 | dst.push( src[ b + 1 ] );
291 | dst.push( src[ b + 2 ] );
292 | dst.push( src[ c + 0 ] );
293 | dst.push( src[ c + 1 ] );
294 | dst.push( src[ c + 2 ] );
295 |
296 | },
297 |
298 | addUV: function ( a, b, c ) {
299 |
300 | var src = this.uvs;
301 | var dst = this.object.geometry.uvs;
302 |
303 | dst.push( src[ a + 0 ] );
304 | dst.push( src[ a + 1 ] );
305 | dst.push( src[ b + 0 ] );
306 | dst.push( src[ b + 1 ] );
307 | dst.push( src[ c + 0 ] );
308 | dst.push( src[ c + 1 ] );
309 |
310 | },
311 |
312 | addUVLine: function ( a ) {
313 |
314 | var src = this.uvs;
315 | var dst = this.object.geometry.uvs;
316 |
317 | dst.push( src[ a + 0 ] );
318 | dst.push( src[ a + 1 ] );
319 |
320 | },
321 |
322 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) {
323 |
324 | var vLen = this.vertices.length;
325 |
326 | var ia = this.parseVertexIndex( a, vLen );
327 | var ib = this.parseVertexIndex( b, vLen );
328 | var ic = this.parseVertexIndex( c, vLen );
329 | var id;
330 |
331 | if ( d === undefined ) {
332 |
333 | this.addVertex( ia, ib, ic );
334 |
335 | } else {
336 |
337 | id = this.parseVertexIndex( d, vLen );
338 |
339 | this.addVertex( ia, ib, id );
340 | this.addVertex( ib, ic, id );
341 |
342 | }
343 |
344 | if ( ua !== undefined ) {
345 |
346 | var uvLen = this.uvs.length;
347 |
348 | ia = this.parseUVIndex( ua, uvLen );
349 | ib = this.parseUVIndex( ub, uvLen );
350 | ic = this.parseUVIndex( uc, uvLen );
351 |
352 | if ( d === undefined ) {
353 |
354 | this.addUV( ia, ib, ic );
355 |
356 | } else {
357 |
358 | id = this.parseUVIndex( ud, uvLen );
359 |
360 | this.addUV( ia, ib, id );
361 | this.addUV( ib, ic, id );
362 |
363 | }
364 |
365 | }
366 |
367 | if ( na !== undefined ) {
368 |
369 | // Normals are many times the same. If so, skip function call and parseInt.
370 | var nLen = this.normals.length;
371 | ia = this.parseNormalIndex( na, nLen );
372 |
373 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
374 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
375 |
376 | if ( d === undefined ) {
377 |
378 | this.addNormal( ia, ib, ic );
379 |
380 | } else {
381 |
382 | id = this.parseNormalIndex( nd, nLen );
383 |
384 | this.addNormal( ia, ib, id );
385 | this.addNormal( ib, ic, id );
386 |
387 | }
388 |
389 | }
390 |
391 | },
392 |
393 | addLineGeometry: function ( vertices, uvs ) {
394 |
395 | this.object.geometry.type = 'Line';
396 |
397 | var vLen = this.vertices.length;
398 | var uvLen = this.uvs.length;
399 |
400 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
401 |
402 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
403 |
404 | }
405 |
406 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
407 |
408 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
409 |
410 | }
411 |
412 | }
413 |
414 | };
415 |
416 | state.startObject( '', false );
417 |
418 | return state;
419 |
420 | },
421 |
422 | parse: function ( text ) {
423 |
424 | //console.time( 'OBJLoader' );
425 |
426 | var state = this._createParserState();
427 |
428 | if ( text.indexOf( '\r\n' ) !== - 1 ) {
429 |
430 | // This is faster than String.split with regex that splits on both
431 | text = text.replace( /\r\n/g, '\n' );
432 |
433 | }
434 |
435 | if ( text.indexOf( '\\\n' ) !== - 1) {
436 |
437 | // join lines separated by a line continuation character (\)
438 | text = text.replace( /\\\n/g, '' );
439 |
440 | }
441 |
442 | var lines = text.split( '\n' );
443 | var line = '', lineFirstChar = '', lineSecondChar = '';
444 | var lineLength = 0;
445 | var result = [];
446 |
447 | // Faster to just trim left side of the line. Use if available.
448 | var trimLeft = ( typeof ''.trimLeft === 'function' );
449 |
450 | for ( var i = 0, l = lines.length; i < l; i ++ ) {
451 |
452 | line = lines[ i ];
453 |
454 | line = trimLeft ? line.trimLeft() : line.trim();
455 |
456 | lineLength = line.length;
457 |
458 | if ( lineLength === 0 ) continue;
459 |
460 | lineFirstChar = line.charAt( 0 );
461 |
462 | // @todo invoke passed in handler if any
463 | if ( lineFirstChar === '#' ) continue;
464 |
465 | if ( lineFirstChar === 'v' ) {
466 |
467 | lineSecondChar = line.charAt( 1 );
468 |
469 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) {
470 |
471 | // 0 1 2 3
472 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
473 |
474 | state.vertices.push(
475 | parseFloat( result[ 1 ] ),
476 | parseFloat( result[ 2 ] ),
477 | parseFloat( result[ 3 ] )
478 | );
479 |
480 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) {
481 |
482 | // 0 1 2 3
483 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
484 |
485 | state.normals.push(
486 | parseFloat( result[ 1 ] ),
487 | parseFloat( result[ 2 ] ),
488 | parseFloat( result[ 3 ] )
489 | );
490 |
491 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) {
492 |
493 | // 0 1 2
494 | // ["vt 0.1 0.2", "0.1", "0.2"]
495 |
496 | state.uvs.push(
497 | parseFloat( result[ 1 ] ),
498 | parseFloat( result[ 2 ] )
499 | );
500 |
501 | } else {
502 |
503 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" );
504 |
505 | }
506 |
507 | } else if ( lineFirstChar === "f" ) {
508 |
509 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) {
510 |
511 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal
512 | // 0 1 2 3 4 5 6 7 8 9 10 11 12
513 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined]
514 |
515 | state.addFace(
516 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ],
517 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ],
518 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ]
519 | );
520 |
521 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) {
522 |
523 | // f vertex/uv vertex/uv vertex/uv
524 | // 0 1 2 3 4 5 6 7 8
525 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined]
526 |
527 | state.addFace(
528 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ],
529 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ]
530 | );
531 |
532 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) {
533 |
534 | // f vertex//normal vertex//normal vertex//normal
535 | // 0 1 2 3 4 5 6 7 8
536 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined]
537 |
538 | state.addFace(
539 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ],
540 | undefined, undefined, undefined, undefined,
541 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ]
542 | );
543 |
544 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) {
545 |
546 | // f vertex vertex vertex
547 | // 0 1 2 3 4
548 | // ["f 1 2 3", "1", "2", "3", undefined]
549 |
550 | state.addFace(
551 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ]
552 | );
553 |
554 | } else {
555 |
556 | throw new Error( "Unexpected face line: '" + line + "'" );
557 |
558 | }
559 |
560 | } else if ( lineFirstChar === "l" ) {
561 |
562 | var lineParts = line.substring( 1 ).trim().split( " " );
563 | var lineVertices = [], lineUVs = [];
564 |
565 | if ( line.indexOf( "/" ) === - 1 ) {
566 |
567 | lineVertices = lineParts;
568 |
569 | } else {
570 |
571 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
572 |
573 | var parts = lineParts[ li ].split( "/" );
574 |
575 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
576 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );
577 |
578 | }
579 |
580 | }
581 | state.addLineGeometry( lineVertices, lineUVs );
582 |
583 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) {
584 |
585 | // o object_name
586 | // or
587 | // g group_name
588 |
589 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
590 | // var name = result[ 0 ].substr( 1 ).trim();
591 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
592 |
593 | state.startObject( name );
594 |
595 | } else if ( this.regexp.material_use_pattern.test( line ) ) {
596 |
597 | // material
598 |
599 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
600 |
601 | } else if ( this.regexp.material_library_pattern.test( line ) ) {
602 |
603 | // mtl file
604 |
605 | state.materialLibraries.push( line.substring( 7 ).trim() );
606 |
607 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) {
608 |
609 | // smooth shading
610 |
611 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
612 | // but does not define a usemtl for each face set.
613 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
614 | // This requires some care to not create extra material on each smooth value for "normal" obj files.
615 | // where explicit usemtl defines geometry groups.
616 | // Example asset: examples/models/obj/cerberus/Cerberus.obj
617 |
618 | var value = result[ 1 ].trim().toLowerCase();
619 | state.object.smooth = ( value === '1' || value === 'on' );
620 |
621 | var material = state.object.currentMaterial();
622 | if ( material ) {
623 |
624 | material.smooth = state.object.smooth;
625 |
626 | }
627 |
628 | } else {
629 |
630 | // Handle null terminated files without exception
631 | if ( line === '\0' ) continue;
632 |
633 | throw new Error( "Unexpected line: '" + line + "'" );
634 |
635 | }
636 |
637 | }
638 |
639 | state.finalize();
640 |
641 | var container = new THREE.Group();
642 | container.materialLibraries = [].concat( state.materialLibraries );
643 |
644 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
645 |
646 | var object = state.objects[ i ];
647 | var geometry = object.geometry;
648 | var materials = object.materials;
649 | var isLine = ( geometry.type === 'Line' );
650 |
651 | // Skip o/g line declarations that did not follow with any faces
652 | if ( geometry.vertices.length === 0 ) continue;
653 |
654 | var buffergeometry = new THREE.BufferGeometry();
655 |
656 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) );
657 |
658 | if ( geometry.normals.length > 0 ) {
659 |
660 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) );
661 |
662 | } else {
663 |
664 | buffergeometry.computeVertexNormals();
665 |
666 | }
667 |
668 | if ( geometry.uvs.length > 0 ) {
669 |
670 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );
671 |
672 | }
673 |
674 | // Create materials
675 |
676 | var createdMaterials = [];
677 |
678 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {
679 |
680 | var sourceMaterial = materials[mi];
681 | var material = undefined;
682 |
683 | if ( this.materials !== null ) {
684 |
685 | material = this.materials.create( sourceMaterial.name );
686 |
687 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
688 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {
689 |
690 | var materialLine = new THREE.LineBasicMaterial();
691 | materialLine.copy( material );
692 | materialLine.lights = false;
693 | material = materialLine;
694 |
695 | }
696 |
697 | }
698 |
699 | if ( ! material ) {
700 |
701 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() );
702 | material.name = sourceMaterial.name;
703 |
704 | }
705 |
706 | material.shading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading;
707 |
708 | createdMaterials.push(material);
709 |
710 | }
711 |
712 | // Create mesh
713 |
714 | var mesh;
715 |
716 | if ( createdMaterials.length > 1 ) {
717 |
718 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) {
719 |
720 | var sourceMaterial = materials[mi];
721 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
722 |
723 | }
724 |
725 | var multiMaterial = new THREE.MultiMaterial( createdMaterials );
726 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.LineSegments( buffergeometry, multiMaterial ) );
727 |
728 | } else {
729 |
730 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) );
731 | }
732 |
733 | mesh.name = object.name;
734 |
735 | container.add( mesh );
736 |
737 | }
738 |
739 | //console.timeEnd( 'OBJLoader' );
740 |
741 | return container;
742 |
743 | }
744 |
745 | };
746 |
747 | module.exports = exports = OBJLoader;
--------------------------------------------------------------------------------
/src/objects/objects.js:
--------------------------------------------------------------------------------
1 | var utils = require("../utils/utils.js");
2 | var material = require("../utils/material.js");
3 |
4 | const AnimationManager = require("../animation/AnimationManager.js");
5 |
6 |
7 | function Objects(){
8 |
9 | }
10 |
11 | Objects.prototype = {
12 |
13 | // standard 1px line with gl
14 | line: function(obj){
15 |
16 | obj = utils._validate(obj, this._defaults.line);
17 |
18 | //project to world and normalize
19 | var straightProject = utils.lnglatsToWorld(obj.geometry);
20 | var normalized = utils.normalizeVertices(straightProject);
21 |
22 | //flatten array for buffergeometry
23 | var flattenedArray = utils.flattenVectors(normalized.vertices);
24 |
25 | var positions = new Float32Array(flattenedArray); // 3 vertices per point
26 | var geometry = new THREE.BufferGeometry();
27 | geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
28 |
29 | // material
30 | var material = new THREE.LineBasicMaterial( { color: 0xff0000, linewidth: 21 } );
31 | var line = new THREE.Line( geometry, material );
32 |
33 | line.options = options || {};
34 | line.position.copy(normalized.position)
35 |
36 | return line
37 | },
38 |
39 |
40 | extrusion: function(options){
41 |
42 | },
43 |
44 | _addMethods: function(obj, static){
45 |
46 | var root = this;
47 |
48 | if (static) {
49 |
50 | }
51 |
52 | else {
53 |
54 | if (!obj.coordinates) obj.coordinates = [0,0,0];
55 |
56 | // Bestow this mesh with animation superpowers and keeps track of its movements in the global animation queue
57 | root.animationManager.enroll(obj);
58 | obj.setCoords = function(lnglat){
59 |
60 | /** Place the given object on the map, centered around the provided longitude and latitude
61 | The object's internal coordinates are assumed to be in meter-offset format, meaning
62 | 1 unit represents 1 meter distance away from the provided coordinate.
63 | */
64 |
65 | // If object already added, scale the model so that its units are interpreted as meters at the given latitude
66 | if (obj.userData.units === 'meters'){
67 | var s = utils.projectedUnitsPerMeter(lnglat[1]);
68 | obj.scale.set(s,s,s);
69 | }
70 |
71 | obj.coordinates = lnglat;
72 | obj.set({position:lnglat})
73 |
74 |
75 | return obj;
76 |
77 | }
78 |
79 | obj.setRotation = function(xyz) {
80 |
81 | if (typeof xyz === 'number') xyz = {z: xyz}
82 |
83 | var r = {
84 | x: utils.radify(xyz.x) || obj.rotation.x,
85 | y: utils.radify(xyz.y) || obj.rotation.y,
86 | z: utils.radify(xyz.z) || obj.rotation.z
87 | }
88 |
89 | obj._setObject({rotation: [r.x, r.y, r.z]})
90 | }
91 |
92 | }
93 |
94 | obj.add = function(){
95 | root.world.add(obj);
96 | if (!static) obj.set({position:obj.coordinates});
97 | return obj;
98 | }
99 |
100 |
101 | obj.remove = function(){
102 | root.world.remove(obj);
103 | root.map.repaint = true;
104 | }
105 |
106 | obj.duplicate = function(a) {
107 | var dupe = obj.clone();
108 | dupe.userData = obj.userData;
109 | root._addMethods(dupe);
110 | return dupe
111 | }
112 |
113 | return obj
114 | },
115 |
116 | _makeGroup: function(obj, options){
117 | var geoGroup = new THREE.Group();
118 | geoGroup.userData = options || {};
119 | geoGroup.userData.isGeoGroup = true;
120 |
121 | var isArrayOfObjects = obj.length;
122 |
123 | if (isArrayOfObjects) for (o of obj) geoGroup.add(o)
124 |
125 |
126 | else geoGroup.add(obj);
127 |
128 | utils._flipMaterialSides(obj);
129 |
130 | return geoGroup
131 | },
132 |
133 | animationManager: new AnimationManager,
134 |
135 | _defaults: {
136 |
137 | line: {
138 | geometry: null,
139 | color: 'black',
140 | width:1,
141 | opacity:1
142 | },
143 |
144 | sphere: {
145 | position: [0,0,0],
146 | radius: 1,
147 | sides: 20,
148 | units: 'scene',
149 | material: 'MeshBasicMaterial'
150 | },
151 |
152 | tube: {
153 | geometry: null,
154 | radius: 1,
155 | sides:6,
156 | material: 'MeshBasicMaterial'
157 | },
158 |
159 | extrusion:{
160 | footprint: null,
161 | base: 0,
162 | top: 100,
163 | color:'black',
164 | material: 'MeshBasicMaterial',
165 | scaleToLatitude: false
166 | },
167 |
168 | loadObj:{
169 | obj: null,
170 | mtl: null,
171 | rotation: 0,
172 | scale: 1,
173 | units: 'scene'
174 | },
175 |
176 | Object3D: {
177 | obj: null,
178 | units: 'scene'
179 | }
180 | },
181 |
182 | geometries:{
183 | line: ['LineString'],
184 | tube: ['LineString'],
185 | sphere: ['Point'],
186 | }
187 | }
188 |
189 | module.exports = exports = Objects;
--------------------------------------------------------------------------------
/src/objects/sphere.js:
--------------------------------------------------------------------------------
1 | var utils = require("../utils/utils.js");
2 | var material = require("../utils/material.js");
3 | var Objects = require('./objects.js');
4 |
5 | function Sphere(obj){
6 |
7 | obj = utils._validate(obj, Objects.prototype._defaults.sphere);
8 | var geometry = new THREE.SphereBufferGeometry( obj.radius, obj.sides, obj.sides );
9 | var mat = material(obj)
10 | var output = new THREE.Mesh( geometry, mat );
11 |
12 | if (obj.units === 'meters') output = Objects.prototype._makeGroup(output, obj);
13 | Objects.prototype._addMethods(output);
14 | return output
15 | }
16 |
17 |
18 | module.exports = exports = Sphere;
--------------------------------------------------------------------------------
/src/objects/tube.js:
--------------------------------------------------------------------------------
1 | var utils = require("../utils/utils.js");
2 | var material = require("../utils/material.js");
3 | var Objects = require('./objects.js');
4 | var THREE = require("../three.js");
5 |
6 | function tube(obj, world){
7 |
8 | // validate and prep input geometry
9 | var obj = utils._validate(obj, Objects.prototype._defaults.tube);
10 | var straightProject = utils.lnglatsToWorld(obj.geometry);
11 | var normalized = utils.normalizeVertices(straightProject);
12 |
13 | var crossSection = tube.prototype.defineCrossSection(obj);
14 | var vertices = tube.prototype.buildVertices(crossSection, normalized.vertices, world);
15 | var geom = tube.prototype.buildFaces(vertices, normalized.vertices, obj);
16 |
17 | var mat = material(obj);
18 |
19 | var mesh = new THREE.Mesh( geom, mat );
20 | mesh.position.copy(normalized.position);
21 |
22 | return mesh
23 |
24 | }
25 |
26 | tube.prototype = {
27 |
28 | buildVertices: function (crossSection, spine, world){
29 |
30 | //create reusable plane for intersection calculations
31 | var geometry = new THREE.PlaneBufferGeometry(99999999999, 9999999999);
32 | var m = new THREE.MeshBasicMaterial( {color: 0xffffff, side: THREE.DoubleSide} );
33 | m.opacity = 0
34 | var plane = new THREE.Mesh( geometry, m );
35 | // world.add( plane );
36 |
37 | var geom = new THREE.Geometry();
38 | var lastElbow = false;
39 |
40 |
41 | // BUILD VERTICES: iterate through points in spine and position each vertex in cross section
42 |
43 |
44 | // get normalized vectors for each spine segment
45 | var spineSegments = [spine[0].clone().normalize()];
46 |
47 | for (i in spine) {
48 |
49 | i = parseFloat(i);
50 |
51 | var segment;
52 |
53 | if (spine[i+1]){
54 | segment = new THREE.Vector3()
55 | .subVectors( spine[i+1], spine[i])
56 | .normalize();
57 |
58 | }
59 |
60 | spineSegments.push(segment);
61 | }
62 |
63 | spineSegments.push(new THREE.Vector3());
64 |
65 | for (i in spine) {
66 |
67 | i = parseFloat(i);
68 | var lineVertex = spine[i];
69 |
70 | // ROTATE cross section
71 |
72 | var humerus = spineSegments[i]
73 |
74 | var forearm = spineSegments[i+1]
75 |
76 | var midpointToLookAt = humerus.clone()
77 | .add(forearm)
78 | .normalize();
79 |
80 | if (i === 0) midpointToLookAt = forearm;
81 |
82 | else if (i === spine.length - 1) midpointToLookAt = humerus;
83 |
84 |
85 | // if first point in input line, rotate and translate it to position
86 | if (!lastElbow) {
87 |
88 | var elbow = crossSection.clone();
89 |
90 | elbow
91 | .lookAt(midpointToLookAt)
92 |
93 | elbow.vertices.forEach(function(vertex){
94 | geom.vertices
95 | .push(vertex.add(lineVertex));
96 | })
97 |
98 | lastElbow = elbow.vertices;
99 |
100 | }
101 |
102 | else {
103 |
104 | var elbow = [];
105 | plane.position.copy(lineVertex);
106 | plane.lookAt(midpointToLookAt.clone().add(lineVertex));
107 | plane.updateMatrixWorld();
108 |
109 | lastElbow.forEach(function(v3){
110 |
111 | var raycaster = new THREE.Raycaster(v3, humerus);
112 |
113 | var intersection = raycaster
114 | .intersectObject(plane)[0];
115 |
116 | if (intersection) {
117 | geom.vertices.push(intersection.point);
118 | elbow.push(intersection.point);
119 | }
120 |
121 | else console.error('Tube geometry failed at vertex '+i+'. Consider reducing tube radius, or smoothening out the sharp angle at this vertex')
122 | })
123 |
124 | lastElbow = elbow
125 | }
126 |
127 | }
128 |
129 | world.remove(plane);
130 |
131 | return geom
132 | },
133 |
134 | defineCrossSection: function(obj){
135 | var crossSection = new THREE.Geometry();
136 | var count = obj.sides;
137 |
138 | for ( var i = 0; i < count; i ++ ) {
139 |
140 | var l = obj.radius;
141 | var a = (i+0.5) / count * Math.PI;
142 |
143 | crossSection.vertices.push(
144 | new THREE.Vector3 (
145 | -Math.sin( 2 * a ),
146 | Math.cos( 2 * a ),
147 | 0
148 | )
149 | .multiplyScalar(l)
150 | );
151 | }
152 |
153 | return crossSection
154 | },
155 |
156 | //build faces between vertices
157 |
158 | buildFaces: function(geom, spine, obj){
159 |
160 | for (var i in spine) {
161 |
162 | i = parseFloat(i);
163 | var vertex = spine[i];
164 |
165 | if (i < spine.length - 1) {
166 |
167 | for (var p = 0; p < obj.sides; p++) {
168 |
169 | var b1 = i * obj.sides + p;
170 | var b2 = i * obj.sides + (p+1) % obj.sides
171 | var t1 = b1 + obj.sides
172 | var t2 = b2 + obj.sides;
173 |
174 | var triangle1 = new THREE.Face3(t1, b1, b2);
175 | var triangle2 = new THREE.Face3(t1, b2, t2);
176 | geom.faces.push(triangle1, triangle2)
177 | }
178 | }
179 | }
180 |
181 | //add endcaps
182 | var v = geom.vertices.length;
183 |
184 | for (var c = 0; c+2 90) {
29 | console.error("Latitude must be between -90 and 90")
30 | return
31 | }
32 |
33 | return input
34 | },
35 |
36 | Line: function(input) {
37 |
38 | var scope = this;
39 |
40 | if (input.constructor !== Array) {
41 | console.error("Line must be an array")
42 | return
43 | }
44 |
45 | for (coord of input){
46 | if (!scope.Coords(coord)) {
47 | console.error("Each coordinate in a line must be a valid Coords type")
48 | return
49 | }
50 |
51 | }
52 |
53 | return input
54 | },
55 |
56 | Rotation: function(input) {
57 |
58 | if (input.constructor === Number) input = {z: input}
59 |
60 | else if (input.constructor === Object) {
61 |
62 | for (key of Object.keys(input)){
63 |
64 | if (!['x', 'y', 'z'].includes(key)) {
65 | console.error('Rotation parameters must be x, y, or z')
66 | return
67 | }
68 | if (input[key].constructor !== Number) {
69 | console.error('Individual rotation values must be numbers')
70 | return
71 | }
72 | }
73 | }
74 |
75 | else {
76 | console.error('Rotation must be an object or a number')
77 | return
78 | }
79 |
80 | return input
81 | },
82 |
83 | Scale: function(input) {
84 |
85 | if (input.constructor === Number) {
86 | input = {x:input, y:input, z: input}
87 | }
88 |
89 | else if (input.constructor === Object) {
90 |
91 | for (key of Object.keys(input)){
92 |
93 | if (!['x', 'y', 'z'].includes(key)) {
94 | console.error('Scale parameters must be x, y, or z')
95 | return
96 | }
97 | if (input[key].constructor !== Number) {
98 | console.error('Individual scale values must be numbers')
99 | return
100 | }
101 | }
102 | }
103 |
104 | else {
105 | console.error('Scale must be an object or a number')
106 | return
107 | }
108 |
109 | return input
110 | }
111 |
112 | }
113 |
114 |
115 | module.exports = exports = Validate;
--------------------------------------------------------------------------------
/tests/threebox-tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Threebox tests
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
26 |
27 |
28 | Open the console to see test results
29 |
30 |
31 |
97 |
--------------------------------------------------------------------------------
/tests/threebox-tests.js:
--------------------------------------------------------------------------------
1 | window.test = require('tape');
2 | // Threebox = require("../src/Threebox.js");
3 | // THREE = require("../src/three.js");
4 |
5 | // window.runTests = function () {
6 | // // material(instance);
7 | // }
8 |
9 | function vector3Equals(t, input, expected, allowableError, epsilon) {
10 | // Check that two Vector3s are equal to each other, allowing for a certain percentage of error due to floating point math
11 | if (allowableError === undefined) allowableError = 0.0000001;
12 | if (epsilon === undefined) epsilon = 0.00000000000001;
13 | var dX, dY, dZ;
14 | dX = Math.abs(input.x - expected.x) / (expected.x === 0 ? 1 : expected.x);
15 | dY = Math.abs(input.y - expected.y) / (expected.y === 0 ? 1 : expected.y);
16 | dZ = Math.abs(input.z - expected.z) / (expected.z === 0 ? 1 : expected.z);
17 |
18 | if (dX < epsilon) dX = 0;
19 | if (dY < epsilon) dY = 0;
20 | if (dZ < epsilon) dZ = 0;
21 |
22 | if(dX > allowableError || dY > allowableError || dZ > allowableError) {
23 | t.fail("Vector3 Equivalence failed: (" + input.x + ", " + input.y + ", " + input.z + ") expected: (" + expected.x + ", " + expected.y + ", " + expected.z + ")");
24 | console.log(dY);
25 | }
26 | t.pass("ok Vector3 equivalence");
27 | }
28 |
29 |
30 | function precisionRound(number, precision) {
31 | var factor = Math.pow(10, precision);
32 | var tempNumber = number * factor;
33 | var roundedTempNumber = Math.round(tempNumber);
34 | return roundedTempNumber / factor;
35 | };
--------------------------------------------------------------------------------
/tests/unit/camera.test.js:
--------------------------------------------------------------------------------
1 | function cameraTest(instance){
2 |
3 |
4 | }
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/unit/material.test.js:
--------------------------------------------------------------------------------
1 | function materialTest(instance){
2 |
3 | var material;
4 |
5 | test('MATERIAL default type, color, opacity', function(t) {
6 | material = instance.material();
7 | t.equal(material.type, 'MeshBasicMaterial');
8 | t.deepEqual(material.color, { r: 0, g: 0, b: 0 });
9 | t.equal(material.opacity, 1);
10 | t.end();
11 | });
12 |
13 | test('MATERIAL custom type', function(t) {
14 | material = instance.material({material:'MeshPhysicalMaterial'});
15 | t.equal(material.opacity, 1);
16 | t.equal(material.type, 'MeshPhysicalMaterial');
17 | t.deepEqual(material.color, { r: 0, g: 0, b: 0 });
18 | t.end();
19 | });
20 |
21 | test('MATERIAL custom color', function(t) {
22 | material = instance.material({color:'red'});
23 | t.equal(material.opacity, 1);
24 | t.equal(material.type, 'MeshBasicMaterial');
25 | t.deepEqual(material.color, { r: 1, g: 0, b: 0 });
26 | t.end();
27 | });
28 |
29 | test('MATERIAL custom opacity', function(t) {
30 | material = instance.material({opacity:0.5});
31 | t.equal(material.opacity, 0.5);
32 | t.equal(material.type, 'MeshBasicMaterial');
33 | t.deepEqual(material.color, { r: 0, g: 0, b: 0 });
34 | t.end();
35 | });
36 |
37 | test('MATERIAL custom color, opacity, type', function(t) {
38 | allCustom = instance.material({
39 | material: 'MeshBasicMaterial',
40 | opacity: 0.5,
41 | color: 'blue'
42 | });
43 | t.equal(allCustom.opacity, 0.5);
44 | t.equal(allCustom.type, 'MeshBasicMaterial');
45 | t.deepEqual(allCustom.color, { r: 0, g: 0, b: 1 });
46 | t.end();
47 | });
48 |
49 | test('MATERIAL when THREE.Material provided, other material params ignored except opacity', function(t) {
50 | threeMaterial = instance.material({
51 | material: new THREE.MeshBasicMaterial({color: 'cyan'}),
52 | opacity: 0.5,
53 | color: 'blue'
54 | });
55 |
56 | t.equal(threeMaterial.opacity, 0.5);
57 | t.equal(threeMaterial.type, 'MeshBasicMaterial');
58 | t.deepEqual(threeMaterial.color, { r: 0, g: 1, b: 1 });
59 | t.end();
60 | });
61 | }
62 |
63 |
64 |
--------------------------------------------------------------------------------
/tests/unit/object.test.js:
--------------------------------------------------------------------------------
1 | function objectTest(instance){
2 |
3 | var mesh = new THREE.Mesh();
4 | var group
5 |
6 | test('OBJECT _makeGroup from one object', function(t){
7 | group = instance.objects._makeGroup(mesh, {foo: true});
8 |
9 | t.equal(group.userData.foo, true);
10 | t.equal(group.type, 'Group');
11 | t.equal(group.children.length, 1);
12 | t.end();
13 |
14 | });
15 |
16 | test('OBJECT _makeGroup from multiple objects', function(t){
17 | var mesh2 = new THREE.Mesh();
18 | group = instance.objects._makeGroup([mesh, mesh2], {foo: false});
19 |
20 | t.equal(group.userData.foo, false);
21 | t.equal(group.type, 'Group');
22 | t.equal(group.children.length, 2);
23 | t.end();
24 |
25 | })
26 |
27 | test('OBJECT _addMethods static', function(t){
28 |
29 | group = instance.objects._makeGroup(mesh, {});
30 | var addedMethods = instance.objects._addMethods(group, true);
31 |
32 | t.equal(addedMethods.setCoords, undefined);
33 | t.equal(addedMethods.type, 'Group');
34 | t.end();
35 |
36 | })
37 |
38 | test('OBJECT _addMethods dynamic', function(t){
39 |
40 | var sphere = instance.sphere({units: 'meters'});
41 |
42 | t.equal(sphere.type, 'Group');
43 | t.equal(typeof sphere.followPath, 'function');
44 | t.deepEqual(sphere.coordinates, [0,0,0]);
45 | t.end();
46 |
47 | })
48 |
49 | test('OBJECT setCoords updates both position and scale', function(t){
50 |
51 | var sphere = instance.sphere({units: 'meters'});
52 | var newPosition = [0,-20];
53 |
54 | sphere.setCoords(newPosition)
55 | var scaleFactor = instance.projectedUnitsPerMeter(newPosition[1]);
56 |
57 | vector3Equals(t, sphere.position, instance.projectToWorld(newPosition));
58 | t.equal(sphere.scale.x, scaleFactor);
59 | t.deepEqual(sphere.coordinates, newPosition)
60 | t.end();
61 |
62 | })
63 |
64 | }
65 |
66 |
67 |
--------------------------------------------------------------------------------
/tests/unit/utilities.test.js:
--------------------------------------------------------------------------------
1 | function utilitiesTest(instance){
2 |
3 | test('PROJECTION project / unproject', function(t) {
4 |
5 | var coord, projected, expected;
6 |
7 | coord = [0,0,0];
8 | var projected = instance.projectToWorld(coord);
9 | var unprojected = instance.unprojectFromWorld(projected);
10 | var expected = new THREE.Vector3(0,0,0);
11 | vector3Equals(t, projected, expected);
12 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
13 |
14 | coord = [30,30,0];
15 | var projected = instance.projectToWorld(coord);
16 | var unprojected = instance.unprojectFromWorld(projected);
17 | var expected = new THREE.Vector3(-85333.33333333333, -89522.98305691125, 0);
18 | vector3Equals(t, projected, expected);
19 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
20 |
21 | coord = [30,-30,0];
22 | var projected = instance.projectToWorld(coord);
23 | var unprojected = instance.unprojectFromWorld(projected);
24 | var expected = new THREE.Vector3(-85333.33333333333, -89522.9830569113, 0);
25 | vector3Equals(t, projected, expected);
26 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
27 |
28 | coord = [-30,30,0];
29 | var projected = instance.projectToWorld(coord);
30 | var unprojected = instance.unprojectFromWorld(projected);
31 | var expected = new THREE.Vector3(-85333.33333333333, -89522.9830569113, 0);
32 | vector3Equals(t, projected, expected);
33 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
34 |
35 | coord = [-30,-30,0];
36 | var projected = instance.projectToWorld(coord);
37 | var unprojected = instance.unprojectFromWorld(projected);
38 | var expected = new THREE.Vector3(-85333.33333333333, 89522.9830569113, 0);
39 | vector3Equals(t, projected, expected);
40 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
41 |
42 | t.end();
43 | });
44 |
45 | test('PROJECTION project / unproject extended lat/lng range', function(t) {
46 | var coord, projected, expected;
47 |
48 | coord = [180,0,0];
49 | var projected = instance.projectToWorld(coord);
50 | var unprojected = instance.unprojectFromWorld(projected);
51 | var expected = new THREE.Vector3(-511999.99999999994,1.8093822187881337e-11,0);
52 | vector3Equals(t, projected, expected);
53 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
54 |
55 | coord = [-180,0,0];
56 | var projected = instance.projectToWorld(coord);
57 | var unprojected = instance.unprojectFromWorld(projected);
58 | var expected = new THREE.Vector3(511999.99999999994,1.8093822187881337e-11,0);
59 | vector3Equals(t, projected, expected);
60 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
61 |
62 | coord = [0,90,0];
63 | var projected = instance.projectToWorld(coord);
64 | var unprojected = instance.unprojectFromWorld(projected);
65 | var expected = new THREE.Vector3(0,-3042.073317352722,0);
66 | vector3Equals(t, projected, expected);
67 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
68 |
69 |
70 | coord = [0, 85.051129,0];
71 | var projected = instance.projectToWorld(coord);
72 | var unprojected = instance.unprojectFromWorld(projected);
73 | var expected = new THREE.Vector3(0, -512000.00726036413, 0);
74 | vector3Equals(t, projected, expected);
75 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
76 |
77 | coord = [0, -85.051129,0];
78 | var projected = instance.projectToWorld(coord);
79 | var unprojected = instance.unprojectFromWorld(projected);
80 | var expected = new THREE.Vector3(0, -512000.00726036413, 0);
81 | vector3Equals(t, projected, expected);
82 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
83 |
84 |
85 | coord = [300,0,0];
86 | var projected = instance.projectToWorld(coord);
87 | var unprojected = instance.unprojectFromWorld(projected);
88 | var expected = new THREE.Vector3(-853333.3333333333,1.8093822187881337e-11,0);
89 | vector3Equals(t, projected, expected);
90 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected));
91 |
92 | t.end();
93 | });
94 |
95 |
96 | test('PROJECTION with altitude', function(t) {
97 | var coord, projected, expected;
98 |
99 | coord = [0,0,10000];
100 | var projected = instance.projectToWorld(coord);
101 | var expected = new THREE.Vector3(0,0,255.52089831565812);
102 | vector3Equals(t, projected, expected);
103 |
104 |
105 | coord = [0,0,-10000];
106 | var projected = instance.projectToWorld(coord);
107 | var expected = new THREE.Vector3(0,0,-255.52089831565812);
108 | vector3Equals(t, projected, expected);
109 |
110 | t.end();
111 | });
112 |
113 | test('PROJECTION projectedUnitsPerMeter', function(t) {
114 |
115 | var pupm1 = instance.projectedUnitsPerMeter(10);
116 | var pupm2 = instance.projectedUnitsPerMeter(-35);
117 |
118 | t.equals(pupm1, 0.025946272004267072);
119 | t.equals(pupm2, 0.03119334195612554);
120 |
121 | t.end();
122 | });
123 |
124 |
125 | // test('PROJECTION project / unproject invalid input', function(t) {
126 | // // TODO: Check for null/undefined/NaN values
127 | // t.end();
128 | // });
129 |
130 |
131 | test('NORMALIZEVERTICES', function(t){
132 |
133 | var input = [
134 | new THREE.Vector3(100, 101, 102),
135 | new THREE.Vector3(103, 104, 105)
136 | ];
137 |
138 | var normalized = instance.utils.normalizeVertices(input);
139 |
140 | t.deepEqual(
141 | normalized.position,
142 | {
143 | x: 101.5,
144 | y: 102.5,
145 | z: 103.5
146 | }
147 | );
148 |
149 | t.deepEqual(
150 | normalized.vertices,
151 | [
152 | {x: -1.5, y: -1.5, z: -1.5},
153 | {x: 1.5, y: 1.5, z: 1.5}
154 | ]
155 | );
156 |
157 | t.end();
158 | })
159 |
160 | var defaults = {
161 | foo: 'bar',
162 | biz: false
163 | }
164 |
165 | test('VALIDATOR empty input', function(t) {
166 | var output = instance.utils._validate({}, defaults);
167 | t.deepEqual(output, defaults);
168 | t.end();
169 | });
170 |
171 | test('VALIDATOR does not overwrite unknown props', function(t) {
172 | var output = instance.utils._validate({a:true}, defaults);
173 |
174 | t.deepEqual(output, {
175 | foo: 'bar',
176 | biz: false,
177 | a: true
178 | });
179 |
180 | t.end();
181 | });
182 |
183 | test('VALIDATOR missing required params throw error', function(t) {
184 | var output = instance.utils._validate({}, {b: null});
185 | t.error(output, 'proper error');
186 | t.end();
187 | });
188 | }
189 |
--------------------------------------------------------------------------------
/tests/unit/validate.test.js:
--------------------------------------------------------------------------------
1 | function validateTest(instance){
2 |
3 | var v = instance.utils.Validator;
4 |
5 | // Coords validation
6 |
7 | test('VALIDATE invalid Coords', function(t){
8 |
9 | t.error(v.Coords(true), 'error');
10 | t.error(v.Coords(['a']), 'error');
11 | t.error(v.Coords([1]), 'error');
12 | t.error(v.Coords([3, false]), 'error');
13 |
14 | t.end();
15 |
16 | });
17 |
18 | test('VALIDATE valid Coords', function(t){
19 |
20 | t.deepEqual(v.Coords([22,33]), [22,33]);
21 | t.deepEqual(v.Coords([22,33,-10]), [22,33, -10]);
22 | t.end();
23 |
24 | });
25 |
26 | // Line validation
27 |
28 | test('VALIDATE invalid Line', function(t){
29 |
30 | t.error(v.Line([[1,2], [false, 4]]), 'error');
31 | t.end();
32 |
33 | });
34 |
35 | test('VALIDATE valid Line', function(t){
36 |
37 | t.deepEqual(v.Line([[-100, 20], [22,33]]), [[-100, 20], [22,33]]);
38 | t.end();
39 |
40 | });
41 |
42 | // Rotation validation
43 |
44 | test('VALIDATE invalid Rotation', function(t){
45 |
46 | t.error(v.Rotation('rotate'), 'error');
47 | t.end();
48 |
49 | });
50 |
51 | test('VALIDATE valid Rotation', function(t){
52 |
53 | t.deepEqual(v.Rotation(40), {z:40});
54 | t.deepEqual(v.Rotation({x:20, y:10, z:90}), {x:20, y:10, z:90});
55 |
56 | t.end();
57 |
58 | });
59 |
60 | // Scale validation
61 |
62 | test('VALIDATE invalid Scale', function(t){
63 |
64 | t.error(v.Scale('scale'), 'error');
65 | t.end();
66 |
67 | });
68 |
69 | test('VALIDATE valid Scale', function(t){
70 |
71 | t.deepEqual(v.Scale(22), {x:22, y:22, z:22});
72 | t.deepEqual(v.Scale({x:20, y:10, z:90}), {x:20, y:10, z:90});
73 |
74 | t.end();
75 |
76 | });
77 | }
78 |
79 |
80 |
--------------------------------------------------------------------------------