@mcolabs/threebox-plugin 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,420 @@
1
+ import * as THREE from 'three';
2
+ import Constants from './constants.js';
3
+ import Validate from './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
+ //[jscastro] new orthographic matrix calculations https://en.wikipedia.org/wiki/Orthographic_projection and validated with https://bit.ly/3rPvB9Y
35
+ makeOrthographicMatrix: function (left, right, top, bottom, near, far) {
36
+ var out = new THREE.Matrix4();
37
+
38
+ const w = 1.0 / (right - left);
39
+ const h = 1.0 / (top - bottom);
40
+ const p = 1.0 / (far - near);
41
+
42
+ const x = (right + left) * w;
43
+ const y = (top + bottom) * h;
44
+ const z = near * p;
45
+
46
+ var newMatrix = [
47
+ 2 * w, 0, 0, 0,
48
+ 0, 2 * h, 0, 0,
49
+ 0, 0, - 1 * p, 0,
50
+ - x, -y, -z, 1
51
+ ]
52
+
53
+ out.elements = newMatrix
54
+ return out;
55
+ },
56
+
57
+ //gimme radians
58
+ radify: function (deg) {
59
+
60
+ function convert(degrees) {
61
+ degrees = degrees || 0;
62
+ return Math.PI * 2 * degrees / 360
63
+ }
64
+
65
+ if (typeof deg === 'object') {
66
+
67
+ //if [x,y,z] array of rotations
68
+ if (deg.length > 0) {
69
+ return deg.map(function (degree) {
70
+ return convert(degree)
71
+ })
72
+ }
73
+
74
+ // if {x: y: z:} rotation object
75
+ else {
76
+ return [convert(deg.x), convert(deg.y), convert(deg.z)]
77
+ }
78
+ }
79
+
80
+ //if just a number
81
+ else return convert(deg)
82
+ },
83
+
84
+ //gimme degrees
85
+ degreeify: function (rad) {
86
+ function convert(radians) {
87
+ radians = radians || 0;
88
+ return radians * 360 / (Math.PI * 2)
89
+ }
90
+
91
+ if (typeof rad === 'object') {
92
+ return [convert(rad.x), convert(rad.y), convert(rad.z)]
93
+ }
94
+
95
+ else return convert(rad)
96
+ },
97
+
98
+ projectToWorld: function (coords) {
99
+
100
+ // Spherical mercator forward projection, re-scaling to WORLD_SIZE
101
+
102
+ var projected = [
103
+ -Constants.MERCATOR_A * Constants.DEG2RAD * coords[0] * Constants.PROJECTION_WORLD_SIZE,
104
+ -Constants.MERCATOR_A * Math.log(Math.tan((Math.PI * 0.25) + (0.5 * Constants.DEG2RAD * coords[1]))) * Constants.PROJECTION_WORLD_SIZE
105
+ ];
106
+
107
+ //z dimension, defaulting to 0 if not provided
108
+
109
+ if (!coords[2]) projected.push(0)
110
+ else {
111
+ var pixelsPerMeter = this.projectedUnitsPerMeter(coords[1]);
112
+ projected.push(coords[2] * pixelsPerMeter);
113
+ }
114
+
115
+ var result = new THREE.Vector3(projected[0], projected[1], projected[2]);
116
+
117
+ return result;
118
+ },
119
+
120
+ projectedUnitsPerMeter: function (latitude) {
121
+ return Math.abs(Constants.WORLD_SIZE / Math.cos(Constants.DEG2RAD * latitude) / Constants.EARTH_CIRCUMFERENCE);
122
+ },
123
+
124
+ _circumferenceAtLatitude: function (latitude) {
125
+ return Constants.EARTH_CIRCUMFERENCE * Math.cos(latitude * Math.PI / 180);
126
+ },
127
+
128
+ mercatorZfromAltitude: function (altitude, lat) {
129
+ return altitude / this._circumferenceAtLatitude(lat);
130
+ },
131
+
132
+ _scaleVerticesToMeters: function (centerLatLng, vertices) {
133
+ var pixelsPerMeter = this.projectedUnitsPerMeter(centerLatLng[1]);
134
+ var centerProjected = this.projectToWorld(centerLatLng);
135
+
136
+ for (var i = 0; i < vertices.length; i++) {
137
+ vertices[i].multiplyScalar(pixelsPerMeter);
138
+ }
139
+
140
+ return vertices;
141
+ },
142
+
143
+ projectToScreen: function (coords) {
144
+ console.log("WARNING: Projecting to screen coordinates is not yet implemented");
145
+ },
146
+
147
+ unprojectFromScreen: function (pixel) {
148
+ console.log("WARNING: unproject is not yet implemented");
149
+ },
150
+
151
+ //world units to lnglat
152
+ unprojectFromWorld: function (worldUnits) {
153
+
154
+ var unprojected = [
155
+ -worldUnits.x / (Constants.MERCATOR_A * Constants.DEG2RAD * Constants.PROJECTION_WORLD_SIZE),
156
+ 2 * (Math.atan(Math.exp(worldUnits.y / (Constants.PROJECTION_WORLD_SIZE * (-Constants.MERCATOR_A)))) - Math.PI / 4) / Constants.DEG2RAD
157
+ ];
158
+
159
+ var pixelsPerMeter = this.projectedUnitsPerMeter(unprojected[1]);
160
+
161
+ //z dimension
162
+ var height = worldUnits.z || 0;
163
+ unprojected.push(height / pixelsPerMeter);
164
+
165
+ return unprojected;
166
+ },
167
+
168
+ toScreenPosition: function (obj, camera) {
169
+ var vector = new THREE.Vector3();
170
+
171
+ var widthHalf = 0.5 * renderer.context.canvas.width;
172
+ var heightHalf = 0.5 * renderer.context.canvas.height;
173
+
174
+ obj.updateMatrixWorld();
175
+ vector.setFromMatrixPosition(obj.matrixWorld);
176
+ vector.project(camera);
177
+
178
+ vector.x = (vector.x * widthHalf) + widthHalf;
179
+ vector.y = - (vector.y * heightHalf) + heightHalf;
180
+
181
+ return {
182
+ x: vector.x,
183
+ y: vector.y
184
+ };
185
+
186
+ },
187
+
188
+ //get the center point of a feature
189
+ getFeatureCenter: function getFeatureCenter(feature, model, level) {
190
+ let center = [];
191
+ let latitude = 0;
192
+ let longitude = 0;
193
+ let height = 0;
194
+ //deep copy to avoid modifying the original array
195
+ let coordinates = [...feature.geometry.coordinates[0]];
196
+ if (feature.geometry.type === "Point") {
197
+ center = [...coordinates[0]];//deep copy
198
+ }
199
+ else {
200
+ //features in mapbox repeat the first coordinates at the end. We remove it.
201
+ if (feature.geometry.type === "MultiPolygon") coordinates = coordinates[0];
202
+ coordinates.splice(-1, 1);
203
+ coordinates.forEach(function (c) {
204
+ latitude += c[0];
205
+ longitude += c[1];
206
+ });
207
+ center = [latitude / coordinates.length, longitude / coordinates.length];
208
+ }
209
+ height = this.getObjectHeightOnFloor(feature, model, level);
210
+
211
+ (center.length < 3 ? center.push(height) : center[2] = height);
212
+
213
+ return center;
214
+ },
215
+
216
+ getObjectHeightOnFloor: function (feature, obj, level = feature.properties.level || 0) {
217
+ let floorHeightMin = (level * (feature.properties.levelHeight || 0));
218
+ //object height is modelSize.z + base_height or min_height configured for this object
219
+ let base = (feature.properties.base_height || feature.properties.min_height || 0);
220
+ //let height = ((obj && obj.model) ? obj.modelSize.z : (feature.properties.height - base));
221
+ let height = ((obj && obj.model) ? 0 : (feature.properties.height - base));
222
+ let objectHeight = height + base;
223
+ let modelHeightFloor = floorHeightMin + objectHeight;
224
+ return modelHeightFloor;
225
+ },
226
+
227
+ _flipMaterialSides: function (obj) {
228
+
229
+ },
230
+
231
+ // to improve precision, normalize a series of vector3's to their collective center, and move the resultant mesh to that center
232
+ normalizeVertices(vertices) {
233
+
234
+ let geometry = new THREE.BufferGeometry();
235
+ let positions = [];
236
+
237
+ for (var j = 0; j < vertices.length; j++) {
238
+ let p = vertices[j];
239
+ positions.push(p.x, p.y, p.z);
240
+ positions.push(p.x, p.y, p.z);
241
+ }
242
+ geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
243
+ geometry.computeBoundingSphere();
244
+ var center = geometry.boundingSphere.center;
245
+
246
+ var scaled = vertices.map(function (v3) {
247
+ var normalized = v3.sub(center);
248
+ return normalized;
249
+ });
250
+
251
+ return { vertices: scaled, position: center }
252
+ },
253
+
254
+ //flatten an array of Vector3's into a shallow array of values in x-y-z order, for bufferGeometry
255
+ flattenVectors(vectors) {
256
+ var flattenedArray = [];
257
+ for (let vertex of vectors) {
258
+ flattenedArray.push(vertex.x, vertex.y, vertex.z);
259
+ }
260
+ return flattenedArray
261
+ },
262
+
263
+ //convert a line/polygon to Vector3's
264
+
265
+ lnglatsToWorld: function (coords) {
266
+
267
+ var vector3 = coords.map(
268
+ function (pt) {
269
+ var p = utils.projectToWorld(pt);
270
+ var v3 = new THREE.Vector3(p.x, p.y, p.z);
271
+ return v3
272
+ }
273
+ );
274
+
275
+ return vector3
276
+ },
277
+
278
+ extend: function (original, addition) {
279
+ for (let key in addition) original[key] = addition[key];
280
+ },
281
+
282
+ clone: function (original) {
283
+ var clone = {};
284
+ for (let key in original) clone[key] = original[key];
285
+ return clone;
286
+ },
287
+
288
+ clamp: function(n, min, max) {
289
+ return Math.min(max, Math.max(min, n));
290
+ },
291
+
292
+ // retrieve object parameters from an options object
293
+ types: {
294
+
295
+ rotation: function (r, currentRotation) {
296
+
297
+ //[jscastro] rotation default 0
298
+ if (!r) { r = 0; };
299
+
300
+ // if number provided, rotate only in Z by that amount
301
+ if (typeof r === 'number') r = { z: r };
302
+
303
+ var degrees = this.applyDefault([r.x, r.y, r.z], currentRotation);
304
+ var radians = utils.radify(degrees);
305
+ return radians;
306
+
307
+ },
308
+
309
+ scale: function (s, currentScale) {
310
+ //[jscastro] scale default 1
311
+ if (!s) { s = 1; };
312
+ if (typeof s === 'number') return s = [s, s, s];
313
+ else return this.applyDefault([s.x, s.y, s.z], currentScale);
314
+ },
315
+
316
+ applyDefault: function (array, current) {
317
+
318
+ var output = array.map(function (item, index) {
319
+ item = item || current[index];
320
+ return item
321
+ })
322
+
323
+ return output
324
+ },
325
+
326
+ },
327
+
328
+ toDecimal: function (n, d) {
329
+ return Number(n.toFixed(d));
330
+ },
331
+
332
+ equal: function (obj1, obj2) {
333
+ const keys1 = Object.keys(obj1);
334
+ const keys2 = Object.keys(obj2);
335
+
336
+ if (keys1.length !== keys2.length) {
337
+ return false;
338
+ }
339
+ if (keys1.length == 0 && keys2.length == 0 && keys1 !== keys2) {
340
+ return false;
341
+ }
342
+
343
+ for (const key of keys1) {
344
+ const val1 = obj1[key];
345
+ const val2 = obj2[key];
346
+ const areObjects = this.isObject(val1) && this.isObject(val2);
347
+ if (
348
+ areObjects && !equal(val1, val2) ||
349
+ !areObjects && val1 !== val2
350
+ ) {
351
+ return false;
352
+ }
353
+ }
354
+
355
+ return true;
356
+ },
357
+
358
+ isObject: function (object) {
359
+ return object != null && typeof object === 'object';
360
+ },
361
+
362
+ curveToLine: (curve, params) => {
363
+ let { width, color } = params;
364
+ let geometry = new THREE.BufferGeometry().setFromPoints(
365
+ curve.getPoints(100)
366
+ );
367
+
368
+ let material = new THREE.LineBasicMaterial({
369
+ color: color,
370
+ linewidth: width,
371
+ });
372
+
373
+ let line = new THREE.Line(geometry, material);
374
+
375
+ return line;
376
+ },
377
+
378
+ curvesToLines: (curves) => {
379
+ var colors = [0xff0000, 0x1eff00, 0x2600ff];
380
+ var lines = curves.map((curve, i) => {
381
+ let params = {
382
+ width: 3,
383
+ color: colors[i] || 'purple',
384
+ };
385
+ let curveline = curveToLine(curve, params);
386
+
387
+ return curveline;
388
+ });
389
+ return lines;
390
+ },
391
+
392
+ _validate: function (userInputs, defaults) {
393
+
394
+ userInputs = userInputs || {};
395
+ var validatedOutput = {};
396
+ utils.extend(validatedOutput, userInputs);
397
+
398
+ for (let key of Object.keys(defaults)) {
399
+
400
+ if (userInputs[key] === undefined) {
401
+ //make sure required params are present
402
+ if (defaults[key] === null) {
403
+ console.error(key + ' is required')
404
+ return;
405
+ }
406
+
407
+ else validatedOutput[key] = defaults[key]
408
+
409
+ }
410
+
411
+ else validatedOutput[key] = userInputs[key]
412
+ }
413
+
414
+ return validatedOutput
415
+ },
416
+ Validator: new Validate(),
417
+ exposedMethods: ['projectToWorld', 'projectedUnitsPerMeter', 'extend', 'unprojectFromWorld']
418
+ }
419
+
420
+ export default utils;
@@ -0,0 +1,114 @@
1
+ // Type validator
2
+
3
+ function Validate(){
4
+
5
+ };
6
+
7
+ Validate.prototype = {
8
+
9
+ Coords: function(input) {
10
+
11
+ if (input.constructor !== Array) {
12
+ console.error("Coords must be an array")
13
+ return
14
+ }
15
+
16
+ if (input.length < 2) {
17
+ console.error("Coords length must be at least 2")
18
+ return
19
+ }
20
+
21
+ for (const member of input) {
22
+ if (member.constructor !== Number) {
23
+ console.error("Coords values must be numbers")
24
+ return
25
+ }
26
+ }
27
+
28
+ if (Math.abs(input[1]) > 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 (const 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 (const 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 (const 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
+ export default Validate;