@jdultra/threedtiles 5.0.0 → 5.1.1

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.
package/README.md CHANGED
@@ -20,7 +20,8 @@ import { OGC3DTile } from "./tileset/OGC3DTile";
20
20
  ...
21
21
 
22
22
  const ogc3DTile = new OGC3DTile({
23
- url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json"
23
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
24
+ renderer: renderer
24
25
  });
25
26
 
26
27
  scene.add(ogc3DTile);
@@ -45,6 +46,7 @@ Currently, the library is limmited to B3DM files.
45
46
  - Optimal tile load order
46
47
  - Occlusion culling
47
48
  - Instanced tilesets
49
+ - Center tileset and re-orient geolocated data
48
50
 
49
51
  ### geometric Error Multiplier
50
52
  The geometric error multiplier allows you to multiply the geometric error by a factor.
@@ -56,6 +58,7 @@ you may also set this value at initialization:
56
58
  ```
57
59
  const ogc3DTile = new OGC3DTile({
58
60
  url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
61
+ renderer: renderer,
59
62
  geometricErrorMultiplier: 2.0
60
63
  });
61
64
  ```
@@ -66,11 +69,12 @@ A value of 1.0 is the default.
66
69
 
67
70
  ### load tiles outside of view
68
71
  By default, only the tiles that intersect the view frustum are loaded. When the camera moves, the scene will have to load the missing tiles and the user might see some holes in the model.
69
- Instead of this behaviour, you can force the lowest possible LODs to be loaded for tiles outside the view so that there are no gaps in the mesh when the camera moves. This also allows displaying shadows correctly.
72
+ Instead of this behaviour, you can force the lowest possible LODs to be loaded for tiles outside the view so that there are no gaps in the mesh when the camera moves. This also allows displaying shadows from parts of the scene that are not in the view.
70
73
 
71
74
  ```
72
75
  const ogc3DTile = new OGC3DTile({
73
76
  url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
77
+ renderer: renderer,
74
78
  loadOutsideView: true
75
79
  });
76
80
  ```
@@ -84,11 +88,13 @@ This can be useful to position the tileset at a specific location when it is not
84
88
  ```
85
89
  const ogc3DTile = new OGC3DTile({
86
90
  url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
87
- onLoadCallback: tilese => {
88
- console.log(tileset.boundingVolume);
91
+ renderer: renderer,
92
+ onLoadCallback: tileset => {
93
+ console.log(tileset.json.boundingVolume);
89
94
  }
90
95
  });
91
96
  ```
97
+ Note that the callback is called with the OGC3DTile object as parameter and that this object has a "json" property giving you access to the original tileset.json with it's transform, geometric error, bounding volume, etc...
92
98
 
93
99
  #### Mesh callback
94
100
  Add a callback on loaded tiles in order to set a material or do some logic on the meshes.
@@ -96,6 +102,7 @@ Add a callback on loaded tiles in order to set a material or do some logic on th
96
102
  ```
97
103
  const ogc3DTile = new OGC3DTile({
98
104
  url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
105
+ renderer: renderer,
99
106
  meshCallback: mesh => {
100
107
  mesh.material.wireframe = true;
101
108
  mesh.material.side = THREE.DoubleSide;
@@ -116,6 +123,7 @@ import { TileLoader } from "@jdultra/threedtiles/src/tileset/TileLoader";
116
123
 
117
124
  const ogc3DTile = new OGC3DTile({
118
125
  url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
126
+ renderer: renderer,
119
127
  tileLoader: new TileLoader(mesh => {
120
128
  //// Insert code to be called on every newly decoded mesh e.g.:
121
129
  mesh.material.wireframe = false;
@@ -133,6 +141,7 @@ The OGC 3DTile object is a regular three.js Object3D so it can be transformed vi
133
141
  ```
134
142
  const ogc3DTile = new OGC3DTile({
135
143
  url: "https://ebeaufay.github.io/ThreedTilesViewer.github.io/momoyama/tileset.json"
144
+ renderer: renderer,
136
145
  });
137
146
 
138
147
  ogc3DTile.translateOnAxis(new THREE.Vector3(0,1,0), -450);
@@ -156,6 +165,7 @@ This service must be passed to every OGC3DTiles object like so:
156
165
  ```
157
166
  const ogc3DTile = new OGC3DTile({
158
167
  url: "path/to/tileset.json",
168
+ renderer: renderer,
159
169
  occlusionCullingService: occlusionCullingService
160
170
  });
161
171
  ```
@@ -201,11 +211,12 @@ const instancedTileLoader = new InstancedTileLoader(scene, mesh => {
201
211
  const instancedTilesets = [];
202
212
  for (let i = 0; i < 100; i++) {
203
213
  const tileset = new InstancedOGC3DTile({
204
- url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
205
- geometricErrorMultiplier: 1.0,
206
- loadOutsideView: false,
207
- tileLoader: instancedTileLoader,
208
- static: true // when static is set to true, don't forget to call InstancedOGC3DTile#updateMatrix manually
214
+ url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
215
+ renderer: renderer,
216
+ geometricErrorMultiplier: 1.0,
217
+ loadOutsideView: false,
218
+ tileLoader: instancedTileLoader,
219
+ static: true // when static is set to true, don't forget to call InstancedOGC3DTile#updateMatrix manually
209
220
  });
210
221
 
211
222
  tileset.translateOnAxis(new THREE.Vector3(1, 0, 0), 50 * i);
@@ -230,6 +241,19 @@ function animate() {
230
241
  animate();
231
242
 
232
243
  ```
244
+ ### Center tileset and re-orient geolocated data
245
+
246
+ OGC3DTiles data is not necessarily centered on the origin and when it's georeferenced, it's also rotated relative to the cartesian coordinate system.
247
+ The optional property "centerModel" will center the model on the origin. In the case of georeferenced models, identified as those using the "region" bounding volume, it will also rotate it so that it's up-axis alligns with the y axis.
248
+
249
+ ```
250
+ const ogc3DTile = new OGC3DTile({
251
+ url: "https://storage.googleapis.com/ogc-3d-tiles/ayutthaya/tileset.json",
252
+ renderer: renderer,
253
+ centerModel:true
254
+ });
255
+ ```
256
+ This property is also available for instanced models.
233
257
 
234
258
  ### static tilesets and other performance tips
235
259
  When you know your tileset will be static, you can specify it in the OGC3DTile object constructor parameter.
@@ -238,6 +262,7 @@ This will skip recalculating the transformation matrix of every tile each frame
238
262
  ```
239
263
  const ogc3DTile = new OGC3DTile({
240
264
  url: "path/to/tileset.json",
265
+ renderer: renderer,
241
266
  static: true
242
267
  });
243
268
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jdultra/threedtiles",
3
- "version": "5.0.0",
3
+ "version": "5.1.1",
4
4
  "description": "An OGC 3DTiles viewer for Three.js",
5
5
  "main": "tileset.js",
6
6
  "scripts": {
@@ -31,15 +31,15 @@
31
31
  "path-browserify": "^1.0.1",
32
32
  "regenerator-runtime": "^0.13.11",
33
33
  "set-interval-async": "^2.0.3",
34
- "three": "0.146.0",
34
+ "three": "0.148.0",
35
35
  "uuid": "^8.3.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@babel/core": "^7.20.2",
38
+ "@babel/core": "^7.20.12",
39
39
  "@babel/preset-env": "^7.20.2",
40
40
  "babel-loader": "^8.3.0",
41
41
  "copy-webpack-plugin": "^6.3.2",
42
- "core-js": "^3.26.1",
42
+ "core-js": "^3.27.1",
43
43
  "html-loader": "^1.3.2",
44
44
  "html-webpack-plugin": "^4.5.0",
45
45
  "mini-css-extract-plugin": "^1.6.2",
@@ -1,12 +1,18 @@
1
1
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
2
2
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
3
3
  import * as THREE from 'three';
4
+ import {FeatureTable, BatchTable} from './FeatureTable';
4
5
 
5
6
  const gltfLoader = new GLTFLoader();
6
7
  const dracoLoader = new DRACOLoader();
8
+ const tempMatrix = new THREE.Matrix4();
7
9
  dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.4.3/');
8
10
  gltfLoader.setDRACOLoader(dracoLoader);
9
- const dummy = new THREE.Object3D();
11
+ const zUpToYUp = new THREE.Matrix4();
12
+ zUpToYUp.set(1,0,0,0,
13
+ 0,0,-1,0,
14
+ 0,1,0,0,
15
+ 0,0,0,1);
10
16
  //const legacyGLTFLoader = new LegacyGLTFLoader();
11
17
 
12
18
  function parseB3DM(arrayBuffer, meshCallback) {
@@ -31,10 +37,10 @@ function parseB3DM(arrayBuffer, meshCallback) {
31
37
  const batchTableBinaryByteLength = dataView.getUint32(24, true);
32
38
 
33
39
  const featureTableStart = 28;
34
- //const featureTable = new FeatureTable( arrayBuffer, featureTableStart, featureTableJSONByteLength, featureTableBinaryByteLength );
40
+ const featureTable = new FeatureTable(arrayBuffer, featureTableStart, featureTableJSONByteLength, featureTableBinaryByteLength);
35
41
 
36
42
  const batchTableStart = featureTableStart + featureTableJSONByteLength + featureTableBinaryByteLength;
37
- //const batchTable = new BatchTable( arrayBuffer, featureTable.getData( 'BATCH_LENGTH' ), batchTableStart, batchTableJSONByteLength, batchTableBinaryByteLength );
43
+ const batchTable = new BatchTable(arrayBuffer, featureTable.getData('BATCH_LENGTH'), batchTableStart, batchTableJSONByteLength, batchTableBinaryByteLength);
38
44
 
39
45
  const glbStart = batchTableStart + batchTableJSONByteLength + batchTableBinaryByteLength;
40
46
  const glbBytes = new Uint8Array(arrayBuffer, glbStart, byteLength - glbStart);
@@ -48,6 +54,7 @@ function parseB3DM(arrayBuffer, meshCallback) {
48
54
  gltfLoader.parse(gltfBuffer, null, model => {
49
55
 
50
56
  ////TODO
57
+
51
58
  //model.batchTable = b3dm.batchTable;
52
59
  //model.featureTable = b3dm.featureTable;
53
60
 
@@ -55,8 +62,18 @@ function parseB3DM(arrayBuffer, meshCallback) {
55
62
  //model.scene.featureTable = b3dm.featureTable;
56
63
 
57
64
  //const scene = mergeColoredObject(model.scene);
65
+
66
+ //model.scene.applyMatrix4(ytozUpMatrix);
67
+
68
+ const rtcCenter = featureTable.getData('RTC_CENTER');
69
+ if (rtcCenter) {
70
+ tempMatrix.makeTranslation(rtcCenter[0], rtcCenter[1], rtcCenter[2])
71
+ model.scene.applyMatrix4(tempMatrix);
72
+ }
73
+
58
74
  model.scene.traverse((o) => {
59
75
  if (o.isMesh) {
76
+ o.applyMatrix4(zUpToYUp);
60
77
  if (!!meshCallback) {
61
78
  meshCallback(o);
62
79
  }
@@ -76,11 +93,11 @@ const B3DMDecoder = {
76
93
 
77
94
  return parseB3DM(arrayBuffer, meshCallback).then(mesh => {
78
95
  let instancedMesh;
96
+ mesh.updateWorldMatrix(false, true)
79
97
  mesh.traverse(child => {
80
98
  if (child.isMesh) {
81
99
  instancedMesh = new THREE.InstancedMesh(child.geometry, child.material, maxCount);
82
- instancedMesh.baseMatrix = child.matrix;
83
- //console.log(child.matrix.elements[12])
100
+ instancedMesh.baseMatrix = child.matrixWorld;
84
101
  }
85
102
  });
86
103
  return instancedMesh;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * This class is taken straight from NASA-AMMOS library.
3
+ * https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/src/utilities/FeatureTable.js
4
+ */
5
+
6
+ const utf8decoder = new TextDecoder();
7
+ export class FeatureTable {
8
+
9
+ constructor(buffer, start, headerLength, binLength) {
10
+
11
+ this.buffer = buffer;
12
+ this.binOffset = start + headerLength;
13
+ this.binLength = binLength;
14
+
15
+ let header = null;
16
+ if (headerLength !== 0) {
17
+
18
+ try {
19
+ const headerData = new Uint8Array(buffer, start, headerLength);
20
+ header = JSON.parse(utf8decoder.decode(headerData));
21
+ } catch (e) {
22
+ header = {};
23
+ }
24
+
25
+ } else {
26
+
27
+ header = {};
28
+
29
+ }
30
+ this.header = header;
31
+
32
+ }
33
+
34
+ getKeys() {
35
+
36
+ return Object.keys(this.header);
37
+
38
+ }
39
+
40
+ getData(key, count, defaultComponentType = null, defaultType = null) {
41
+
42
+ const header = this.header;
43
+
44
+ if (!(key in header)) {
45
+
46
+ return null;
47
+
48
+ }
49
+
50
+ const feature = header[key];
51
+ if (!(feature instanceof Object)) {
52
+
53
+ return feature;
54
+
55
+ } else if (Array.isArray(feature)) {
56
+
57
+ return feature;
58
+
59
+ } else {
60
+
61
+ const { buffer, binOffset, binLength } = this;
62
+ const byteOffset = feature.byteOffset || 0;
63
+ const featureType = feature.type || defaultType;
64
+ const featureComponentType = feature.componentType || defaultComponentType;
65
+
66
+ if ('type' in feature && defaultType && feature.type !== defaultType) {
67
+
68
+ throw new Error('FeatureTable: Specified type does not match expected type.');
69
+
70
+ }
71
+
72
+ let stride;
73
+ switch (featureType) {
74
+
75
+ case 'SCALAR':
76
+ stride = 1;
77
+ break;
78
+
79
+ case 'VEC2':
80
+ stride = 2;
81
+ break;
82
+
83
+ case 'VEC3':
84
+ stride = 3;
85
+ break;
86
+
87
+ case 'VEC4':
88
+ stride = 4;
89
+ break;
90
+
91
+ default:
92
+ throw new Error(`FeatureTable : Feature type not provided for "${key}".`);
93
+
94
+ }
95
+
96
+ let data;
97
+ const arrayStart = binOffset + byteOffset;
98
+ const arrayLength = count * stride;
99
+
100
+ switch (featureComponentType) {
101
+
102
+ case 'BYTE':
103
+ data = new Int8Array(buffer, arrayStart, arrayLength);
104
+ break;
105
+
106
+ case 'UNSIGNED_BYTE':
107
+ data = new Uint8Array(buffer, arrayStart, arrayLength);
108
+ break;
109
+
110
+ case 'SHORT':
111
+ data = new Int16Array(buffer, arrayStart, arrayLength);
112
+ break;
113
+
114
+ case 'UNSIGNED_SHORT':
115
+ data = new Uint16Array(buffer, arrayStart, arrayLength);
116
+ break;
117
+
118
+ case 'INT':
119
+ data = new Int32Array(buffer, arrayStart, arrayLength);
120
+ break;
121
+
122
+ case 'UNSIGNED_INT':
123
+ data = new Uint32Array(buffer, arrayStart, arrayLength);
124
+ break;
125
+
126
+ case 'FLOAT':
127
+ data = new Float32Array(buffer, arrayStart, arrayLength);
128
+ break;
129
+
130
+ case 'DOUBLE':
131
+ data = new Float64Array(buffer, arrayStart, arrayLength);
132
+ break;
133
+
134
+ default:
135
+ throw new Error(`FeatureTable : Feature component type not provided for "${key}".`);
136
+
137
+ }
138
+
139
+ const dataEnd = arrayStart + arrayLength * data.BYTES_PER_ELEMENT;
140
+ if (dataEnd > binOffset + binLength) {
141
+
142
+ throw new Error('FeatureTable: Feature data read outside binary body length.');
143
+
144
+ }
145
+
146
+ return data;
147
+
148
+ }
149
+
150
+ }
151
+
152
+ }
153
+
154
+ export class BatchTable extends FeatureTable {
155
+
156
+ constructor(buffer, batchSize, start, headerLength, binLength) {
157
+
158
+ super(buffer, start, headerLength, binLength);
159
+ this.batchSize = batchSize;
160
+
161
+ }
162
+
163
+ getData(key, componentType = null, type = null) {
164
+
165
+ return super.getData(key, this.batchSize, componentType, type);
166
+
167
+ }
168
+
169
+ }
package/src/index.js CHANGED
@@ -14,7 +14,6 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
14
14
  import { InstancedOGC3DTile } from "./tileset/instanced/InstancedOGC3DTile.js"
15
15
  import { InstancedTileLoader } from "./tileset/instanced/InstancedTileLoader.js"
16
16
 
17
- import { B3DMDecoder } from "./decoder/B3DMDecoder";
18
17
 
19
18
  const occlusionCullingService = new OcclusionCullingService();
20
19
  occlusionCullingService.setSide(THREE.DoubleSide);
@@ -24,35 +23,15 @@ const domContainer = initDomContainer("screen");
24
23
  const camera = initCamera(domContainer.offsetWidth, domContainer.offsetHeight);
25
24
  const stats = initStats(domContainer);
26
25
  const renderer = initRenderer(camera, domContainer);
27
- //const ogc3DTiles = initTileset(scene);
26
+ const ogc3DTiles = initTileset(scene, 1.0);
28
27
 
29
-
30
- const instancedTileLoader = createInstancedTileLoader(scene);
31
- initInstancedTilesets(instancedTileLoader);
28
+ //const instancedTileLoader = createInstancedTileLoader(scene);
29
+ //initInstancedTilesets(instancedTileLoader);
32
30
 
33
31
  const controller = initController(camera, domContainer);
34
32
 
35
33
  const composer = initComposer(scene, camera, renderer);
36
34
 
37
-
38
- /* fetch("https://storage.googleapis.com/ogc-3d-tiles/droneship/1/2007.b3dm").then(result => {
39
-
40
- if (!result.ok) {
41
- console.error("could not load tile with path : " + path)
42
- throw new Error(`couldn't load "${path}". Request failed with status ${result.status} : ${result.statusText}`);
43
- }
44
- return result.arrayBuffer();
45
-
46
- })
47
- .then(resultArrayBuffer=>{
48
- return B3DMDecoder.parseB3DMInstanced(resultArrayBuffer, self.meshCallback, 1);
49
- })
50
- .then(mesh=>{
51
- scene.add(mesh)
52
-
53
- }) */
54
-
55
-
56
35
  animate();
57
36
 
58
37
 
@@ -70,10 +49,10 @@ function initScene() {
70
49
  const scene = new THREE.Scene();
71
50
  scene.matrixAutoUpdate = false;
72
51
  //scene.matrixWorldAutoUpdate = false;
73
- scene.background = new THREE.Color(0x404040);
52
+ scene.background = new THREE.Color(0xffffff);
74
53
  scene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
75
54
 
76
- /*const light = new THREE.PointLight(0xbbbbff, 2, 5000);
55
+ /* const light = new THREE.PointLight(0xbbbbff, 2, 5000);
77
56
  const sphere = new THREE.SphereGeometry(2, 16, 8);
78
57
  light.add(new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ color: 0xbbbbff })));
79
58
  scene.add(light);
@@ -84,9 +63,9 @@ function initScene() {
84
63
  const sphere2 = new THREE.SphereGeometry(2, 16, 8);
85
64
  light2.add(new THREE.Mesh(sphere2, new THREE.MeshBasicMaterial({ color: 0xffbbbb })));
86
65
  scene.add(light2);
87
- light2.position.set(200, 100, -100);*/
66
+ light2.position.set(200, 100, -100); */
67
+
88
68
 
89
-
90
69
  return scene;
91
70
  }
92
71
 
@@ -134,119 +113,94 @@ function initStats(dom) {
134
113
 
135
114
  function initCamera(width, height) {
136
115
  const camera = new THREE.PerspectiveCamera(60, width / height, 1, 100000);
137
- camera.position.set(-400.060421028462592,-14.561785966685625,700.123058268059668);
138
-
116
+ camera.position.set(10000,0,0);
117
+ camera.lookAt(0,0,0);
118
+
139
119
  camera.matrixAutoUpdate = true;
140
120
  return camera;
141
121
  }
142
122
 
143
- function initTileset(scene) {
123
+ function initTileset(scene, gem) {
144
124
 
145
125
  const tileLoader = new TileLoader(mesh => {
146
126
  //// Insert code to be called on every newly decoded mesh e.g.:
147
127
  mesh.material.wireframe = false;
148
128
  mesh.material.side = THREE.DoubleSide;
149
129
  mesh.material.metalness = 0.0
150
- }, 1000)
130
+ }, 100);
131
+
151
132
  const ogc3DTile = new OGC3DTile({
152
- //url: "http://localhost:8080/tileset.json",
153
- //url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
154
- url: "https://storage.googleapis.com/ogc-3d-tiles/berlinTileset/tileset.json",
155
- //url: "https://s3.eu-central-2.wasabisys.com/construkted-assets-eu/ands2ty8orz/tileset.json",
156
- //url: "https://s3.eu-central-2.wasabisys.com/construkted-assets-eu/an7opcnyije/tileset.json",
157
- //url: "https://s3.eu-central-2.wasabisys.com/construkted-assets-eu/ands2ty8orz/tileset.json",
158
- //url: "https://s3.eu-central-2.wasabisys.com/construkted-assets-eu/a88b3sungng/tileset.json",
159
- geometricErrorMultiplier: 0.01,
133
+ //url: "https://sampledata.luciad.com/data/ogc3dtiles/LucerneAirborneMesh/tileset.json",
134
+ url: "https://sampleservices.luciad.com/ogc/3dtiles/marseille-mesh/tileset.json",
135
+ //url: "https://storage.googleapis.com/ogc-3d-tiles/baltimore/tileset.json",
136
+ //url: "http://localhost:8082/tileset.json",
137
+ geometricErrorMultiplier: gem,
160
138
  loadOutsideView: false,
161
139
  tileLoader: tileLoader,
162
140
  //occlusionCullingService: occlusionCullingService,
163
141
  static: false,
164
- renderer: renderer
165
-
166
- });
167
-
168
-
169
-
170
-
171
- //// The OGC3DTile object is a threejs Object3D so you may do all the usual opperations like transformations e.g.:
172
- ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
173
- ogc3DTile.scale.set(100.0,100.0,100.0)
174
- //// If the OGC3DTile object is marked as "static" (constructorParameter), these operations will not work.
175
-
176
-
177
-
178
- //// It's up to the user to call updates on the tileset. You might call them whenever the camera moves or at regular time intervals like here
179
-
180
-
181
-
182
- var interval;
183
- document.addEventListener('keyup', (e) => {
184
- console.log(camera.position)
185
- if (!!e.key && e.key !== "p") {
186
-
187
- if (!!interval) {
188
- clearInterval(interval);
189
- interval = null;
190
- } else {
191
- startInterval();
142
+ centerModel:true,
143
+ renderer: renderer,
144
+ onLoadCallback: (tile)=>{
145
+ if (!!tile.json.boundingVolume.region) {
146
+ const halfHeight = (tile.json.boundingVolume.region[5] - tile.json.boundingVolume.region[4]) * 0.5;
147
+ ogc3DTile.translateOnAxis(new THREE.Vector3(0, 1, 0), halfHeight);
148
+ //ogc3DTile.updateWorldMatrix(true, true);
192
149
  }
193
150
  }
194
- if (!!e.key && e.key !== "l") {
195
-
196
- console.log("new THREE.Vector3(" + camera.position.x + "," + camera.position.y + "," + camera.position.z + ")");
197
- console.log("new THREE.Quaternion(" + camera.quaternion.x + "," + camera.quaternion.y + "," + camera.quaternion.z + "," + camera.quaternion.w + ")");
198
-
199
- }
200
151
 
201
152
  });
202
- function startInterval() {
203
- interval = setIntervalAsync(function () {
204
- ogc3DTile.update(camera);
153
+ setIntervalAsync(function () {
154
+ ogc3DTile.update(camera);
155
+ }, 20);
205
156
 
206
- }, 20);
207
- }
208
- startInterval();
157
+
158
+
159
+ //ogc3DTile.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
160
+ //ogc3DTile.translateOnAxis(new THREE.Vector3(0, 0, 1), 1)
161
+ /*
162
+ ogc3DTile.translateOnAxis(new THREE.Vector3(0, 0, 1), 10) // Z-UP to Y-UP
163
+ ogc3DTile.translateOnAxis(new THREE.Vector3(0, 1, 0), 18.5) // Z-UP to Y-UP */
164
+ scene.add(ogc3DTile);
209
165
 
210
- scene.add(ogc3DTile)
211
166
  return ogc3DTile;
212
167
  }
213
168
 
169
+
214
170
  function createInstancedTileLoader(scene) {
215
171
  return new InstancedTileLoader(scene, mesh => {
216
172
  //// Insert code to be called on every newly decoded mesh e.g.:
217
173
  mesh.material.wireframe = false;
218
174
  mesh.material.side = THREE.DoubleSide;
219
- }, 1000, 3375);
175
+ mesh.material.metalness = 0.0;
176
+ }, 0, 1);
220
177
  }
221
178
  function initInstancedTilesets(instancedTileLoader) {
222
179
 
180
+ /*new GLTFLoader().load('http://localhost:8080/test.glb', function ( gltf ) {
181
+ scene.add(gltf.scene);
182
+ } );*/
183
+
223
184
  const instancedTilesets = [];
224
185
 
225
- for (let x = 0; x < 15; x++) {
226
- for (let y = 0; y < 15; y++) {
227
- for (let z = 0; z < 15; z++) {
228
- const tileset = new InstancedOGC3DTile({
229
- url: "https://storage.googleapis.com/ogc-3d-tiles/droneship/tileset.json",
230
- //url: "https://storage.googleapis.com/ogc-3d-tiles/berlinTileset/tileset.json",
231
- //url: "http://localhost:8080/tileset.json",
232
- geometricErrorMultiplier: 1.0,
233
- loadOutsideView: true,
234
- tileLoader: instancedTileLoader,
235
- static: true,
236
- renderer: renderer
237
- });
238
- //tileset.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
239
- tileset.translateOnAxis(new THREE.Vector3(1, 0, 0), 50 * x)
240
- tileset.translateOnAxis(new THREE.Vector3(0, 1, 0), 50 * y)
241
- tileset.translateOnAxis(new THREE.Vector3(0, 0, 1), 50 * z)
242
- tileset.updateMatrix()
243
- scene.add(tileset);
244
- instancedTilesets.push(tileset);
245
186
 
187
+ const tileset = new InstancedOGC3DTile({
188
+ //url: "https://storage.googleapis.com/ogc-3d-tiles/berlinTileset/tileset.json",
189
+ url: "https://sampleservices.luciad.com/ogc/3dtiles/marseille-mesh/tileset.json",
190
+ //url: "https://s3.eu-central-2.wasabisys.com/construkted-assets-eu/ab13lasdc9i/tileset.json",
191
+ //url: "http://localhost:8081/tileset.json",
192
+ geometricErrorMultiplier: 1.0,
193
+ loadOutsideView: true,
194
+ tileLoader: instancedTileLoader,
195
+ static: false,
196
+ centerModel:true,
197
+ renderer: renderer
198
+ });
199
+ //tileset.rotateOnAxis(new THREE.Vector3(1, 0, 0), Math.PI * -0.5) // Z-UP to Y-UP
246
200
 
247
- }
248
- }
249
- }
201
+ tileset.updateMatrix()
202
+ scene.add(tileset);
203
+ instancedTilesets.push(tileset);
250
204
 
251
205
  scene.updateMatrixWorld(true)
252
206
  function now() {
@@ -255,14 +209,14 @@ function initInstancedTilesets(instancedTileLoader) {
255
209
  let updateIndex = 0;
256
210
  setInterval(() => {
257
211
  let startTime = now();
258
- do{
212
+ do {
259
213
  const frustum = new THREE.Frustum();
260
214
  frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse));
261
215
  instancedTilesets[updateIndex].update(camera, frustum);
262
- updateIndex= (updateIndex+1)%instancedTilesets.length;
263
- }while(updateIndex < instancedTilesets.length && now()-startTime<4);
264
- },40);
265
-
216
+ updateIndex = (updateIndex + 1) % instancedTilesets.length;
217
+ } while (updateIndex < instancedTilesets.length && now() - startTime < 4);
218
+ }, 40);
219
+
266
220
  //initLODMultiplierSlider(instancedTilesets);
267
221
  }
268
222
 
@@ -273,7 +227,7 @@ function initLODMultiplierSlider(instancedTilesets) {
273
227
 
274
228
  slider.oninput = () => {
275
229
  instancedTilesets.forEach(tileset => {
276
- tileset.setGeometricErrorMultiplier(slider.value*0.1)
230
+ tileset.setGeometricErrorMultiplier(slider.value * 0.1)
277
231
  })
278
232
  output.innerHTML = slider.value;
279
233
  }
@@ -283,7 +237,10 @@ function initController(camera, dom) {
283
237
  const controller = new OrbitControls(camera, dom);
284
238
 
285
239
  controller.target.set(0,0,0);
286
- controller.minDistance = 0.01;
240
+ //controller.target.set(0,0,0);
241
+
242
+
243
+ controller.minDistance = 0.1;
287
244
  controller.maxDistance = 100000;
288
245
  controller.update();
289
246
  return controller;
@@ -292,7 +249,7 @@ function initController(camera, dom) {
292
249
 
293
250
  function animate() {
294
251
  requestAnimationFrame(animate);
295
- instancedTileLoader.update();
252
+ //instancedTileLoader.update();
296
253
  composer.render();
297
254
  //occlusionCullingService.update(scene, renderer, camera)
298
255
  stats.update();
@@ -5,8 +5,12 @@ import { v4 as uuidv4 } from "uuid";
5
5
  import * as path from "path-browserify"
6
6
  import { clamp } from "three/src/math/MathUtils";
7
7
 
8
- const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
8
+ const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
9
+ const tempVec1 = new THREE.Vector3(0, 0, 0);
10
+ const tempVec2 = new THREE.Vector3(0, 0, 0);
11
+ const upVector = new THREE.Vector3(0, 1, 0);
9
12
  const rendererSize = new THREE.Vector2();
13
+ const tempQuaternion = new THREE.Quaternion();
10
14
 
11
15
  class OGC3DTile extends THREE.Object3D {
12
16
 
@@ -28,6 +32,7 @@ class OGC3DTile extends THREE.Object3D {
28
32
  * onLoadCallback: function,
29
33
  * occlusionCullingService: OcclusionCullingService,
30
34
  * static: Boolean,
35
+ * centerModel: Boolean
31
36
  * renderer: Renderer
32
37
  * } properties
33
38
  */
@@ -63,6 +68,7 @@ class OGC3DTile extends THREE.Object3D {
63
68
  if (this.static) {
64
69
  this.matrixAutoUpdate = false;
65
70
  }
71
+
66
72
  // declare properties specific to the tile for clarity
67
73
  this.childrenTiles = [];
68
74
  this.meshContent;
@@ -84,6 +90,7 @@ class OGC3DTile extends THREE.Object3D {
84
90
  if (!!properties.json) { // If this tile is created as a child of another tile, properties.json is not null
85
91
  self.setup(properties);
86
92
  if (properties.onLoadCallback) properties.onLoadCallback(self);
93
+
87
94
  } else if (properties.url) { // If only the url to the tileset.json is provided
88
95
  fetch(properties.url, { signal: self.abortController.signal }).then(result => {
89
96
  if (!result.ok) {
@@ -92,6 +99,33 @@ class OGC3DTile extends THREE.Object3D {
92
99
  result.json().then(json => {
93
100
  self.setup({ rootPath: path.dirname(properties.url), json: json });
94
101
  if (properties.onLoadCallback) properties.onLoadCallback(self);
102
+ if (!!properties.centerModel) {
103
+ const tempSphere = new THREE.Sphere();
104
+ if (self.boundingVolume instanceof OBB) {
105
+ // box
106
+ tempSphere.copy(self.boundingVolume.sphere);
107
+ } else if (self.boundingVolume instanceof THREE.Sphere) {
108
+ //sphere
109
+ tempSphere.copy(self.boundingVolume);
110
+ }
111
+
112
+ //tempSphere.applyMatrix4(self.matrixWorld);
113
+ if (!!this.json.boundingVolume.region) {
114
+ this.transformWGS84ToCartesian(
115
+ (this.json.boundingVolume.region[0] + this.json.boundingVolume.region[2]) * 0.5,
116
+ (this.json.boundingVolume.region[1] + this.json.boundingVolume.region[3]) * 0.5,
117
+ (this.json.boundingVolume.region[4] + this.json.boundingVolume.region[5]) * 0.5,
118
+ tempVec1);
119
+
120
+ tempQuaternion.setFromUnitVectors(tempVec1.normalize(), upVector.normalize());
121
+ self.applyQuaternion(tempQuaternion);
122
+ }
123
+
124
+ self.translateX(-tempSphere.center.x * self.scale.x);
125
+ self.translateY(-tempSphere.center.y * self.scale.y);
126
+ self.translateZ(-tempSphere.center.z * self.scale.z);
127
+
128
+ }
95
129
  });
96
130
  });
97
131
  }
@@ -133,7 +167,10 @@ class OGC3DTile extends THREE.Object3D {
133
167
  this.boundingVolume = new OBB(this.json.boundingVolume.box);
134
168
  } else if (!!this.json.boundingVolume.region) {
135
169
  const region = this.json.boundingVolume.region;
136
- this.boundingVolume = new THREE.Box3(new THREE.Vector3(region[0], region[2], region[4]), new THREE.Vector3(region[1], region[3], region[5]));
170
+ this.transformWGS84ToCartesian(region[0], region[1], region[4], tempVec1);
171
+ this.transformWGS84ToCartesian(region[2], region[3], region[5], tempVec2);
172
+ tempVec1.lerp(tempVec2, 0.5);
173
+ this.boundingVolume = new THREE.Sphere(new THREE.Vector3(tempVec1.x, tempVec1.y, tempVec1.z), tempVec1.distanceTo(tempVec2));
137
174
  } else if (!!this.json.boundingVolume.sphere) {
138
175
  const sphere = this.json.boundingVolume.sphere;
139
176
  this.boundingVolume = new THREE.Sphere(new THREE.Vector3(sphere[0], sphere[2], -sphere[1]), sphere[3]);
@@ -518,39 +555,30 @@ class OGC3DTile extends THREE.Object3D {
518
555
  tempSphere.copy(this.boundingVolume);
519
556
  tempSphere.applyMatrix4(this.matrixWorld);
520
557
  if (!frustum.intersectsSphere(tempSphere)) return -1;
521
- } else if (this.boundingVolume instanceof THREE.Box3) {
522
- // Region
523
- // Region not supported
524
- //throw Error("Region bounding volume not supported");
525
- return -1;
558
+ } else {
559
+ console.error("unsupported shape");
560
+ return -1
561
+
526
562
  }
527
563
 
528
564
  /////// return metric based on geometric error and distance
529
- if (this.boundingVolume instanceof OBB || this.boundingVolume instanceof THREE.Sphere) {
530
- // box
531
- const distance = Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
532
- if (distance == 0) {
533
- return 0;
534
- }
535
- const scale = this.matrixWorld.getMaxScaleOnAxis();
536
- this.renderer.getDrawingBufferSize(rendererSize);
537
- let s = rendererSize.y;
538
- let fov = camera.fov;
539
- if(camera.aspect < 1){
540
- fov *= camera.aspect;
541
- s = rendererSize.x;
542
- }
543
-
544
- let lambda = 2.0 * Math.tan(0.5 * fov * 0.01745329251994329576923690768489) * distance;
545
-
546
- return (window.devicePixelRatio * 16 * lambda) / (s * scale);
547
565
 
548
- } else if (this.boundingVolume instanceof THREE.Box3) {
549
- // Region
550
- // Region not supported
551
- //throw Error("Region bounding volume not supported");
552
- return -1;
566
+ const distance = Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
567
+ if (distance == 0) {
568
+ return 0;
553
569
  }
570
+ const scale = this.matrixWorld.getMaxScaleOnAxis();
571
+ this.renderer.getDrawingBufferSize(rendererSize);
572
+ let s = rendererSize.y;
573
+ let fov = camera.fov;
574
+ if (camera.aspect < 1) {
575
+ fov *= camera.aspect;
576
+ s = rendererSize.x;
577
+ }
578
+
579
+ let lambda = 2.0 * Math.tan(0.5 * fov * 0.01745329251994329576923690768489) * distance;
580
+
581
+ return (window.devicePixelRatio * 16 * lambda) / (s * scale);
554
582
  }
555
583
 
556
584
  getSiblings() {
@@ -583,8 +611,8 @@ class OGC3DTile extends THREE.Object3D {
583
611
  tempSphere.applyMatrix4(this.matrixWorld);
584
612
  //if (!frustum.intersectsSphere(tempSphere)) return -1;
585
613
  }
586
- if (this.boundingVolume instanceof THREE.Box3) {
587
- return -1; // region not supported
614
+ else {
615
+ console.error("unsupported shape")
588
616
  }
589
617
  return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
590
618
  }
@@ -592,5 +620,21 @@ class OGC3DTile extends THREE.Object3D {
592
620
  this.geometricErrorMultiplier = geometricErrorMultiplier;
593
621
  this.childrenTiles.forEach(child => child.setGeometricErrorMultiplier(geometricErrorMultiplier));
594
622
  }
623
+
624
+ transformWGS84ToCartesian(lon, lat, h, sfct) {
625
+ const a = 6378137.0;
626
+ const e = 0.006694384442042;
627
+ const N = a / (Math.sqrt(1.0 - (e * Math.pow(Math.sin(lat), 2))));
628
+ const cosLat = Math.cos(lat);
629
+ const cosLon = Math.cos(lon);
630
+ const sinLat = Math.sin(lat);
631
+ const sinLon = Math.sin(lon);
632
+ const nPh = (N + h);
633
+ const x = nPh * cosLat * cosLon;
634
+ const y = nPh * cosLat * sinLon;
635
+ const z = (0.993305615557957 * N + h) * sinLat;
636
+
637
+ sfct.set(x, y, z);
638
+ }
595
639
  }
596
640
  export { OGC3DTile };
@@ -4,8 +4,13 @@ import { v4 as uuidv4 } from "uuid";
4
4
  import * as path from "path-browserify";
5
5
  import * as _ from "lodash";
6
6
 
7
- const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0, 1));
7
+ const tempSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
8
+ const tempVec1 = new THREE.Vector3(0, 0, 0);
9
+ const tempVec2 = new THREE.Vector3(0, 0, 0);
10
+ const upVector = new THREE.Vector3(0, 1, 0);
8
11
  const rendererSize = new THREE.Vector2();
12
+ const tempQuaternion = new THREE.Quaternion();
13
+ const tempMatrix = new THREE.Matrix4();
9
14
 
10
15
  class InstancedTile extends THREE.Object3D {
11
16
 
@@ -23,7 +28,8 @@ class InstancedTile extends THREE.Object3D {
23
28
  * meshCallback: function,
24
29
  * cameraOnLoad: camera,
25
30
  * parentTile: OGC3DTile,
26
- * onLoadCallback: function
31
+ * onLoadCallback: function,
32
+ * centerModel: Boolean
27
33
  * } properties
28
34
  */
29
35
  constructor(properties) {
@@ -76,7 +82,35 @@ class InstancedTile extends THREE.Object3D {
76
82
  //json = JSON.parse(JSON.stringify(json))
77
83
  const p = path.dirname(url);
78
84
  self.setup({ rootPath: p, json: json });
85
+ if (!!properties.centerModel) {
86
+ const tempSphere = new THREE.Sphere();
87
+ if (self.boundingVolume instanceof OBB) {
88
+ // box
89
+ tempSphere.copy(self.boundingVolume.sphere);
90
+ } else if (self.boundingVolume instanceof THREE.Sphere) {
91
+ //sphere
92
+ tempSphere.copy(self.boundingVolume);
93
+ }
94
+
95
+ //tempSphere.applyMatrix4(self.matrixWorld);
96
+ if (!!this.json.boundingVolume.region) {
97
+ self.transformWGS84ToCartesian(
98
+ (self.json.boundingVolume.region[0] + self.json.boundingVolume.region[2]) * 0.5,
99
+ (self.json.boundingVolume.region[1] + self.json.boundingVolume.region[3]) * 0.5,
100
+ (self.json.boundingVolume.region[4] + self.json.boundingVolume.region[5]) * 0.5,
101
+ tempVec1);
102
+
103
+ tempQuaternion.setFromUnitVectors(tempVec1.normalize(), upVector.normalize());
104
+ self.master.applyQuaternion(tempQuaternion);
105
+ self.master.updateWorldMatrix(false, false)
106
+ }
107
+ tempMatrix.makeTranslation(-tempSphere.center.x * self.scale.x, -tempSphere.center.y * self.scale.y, -tempSphere.center.z * self.scale.z);
108
+ //self.master.applyMatrix4(tempMatrix);
109
+ self.master.matrix.multiply(tempMatrix);
110
+ self.master.matrix.decompose( self.master.position, self.master.quaternion, self.master.scale );
111
+ }
79
112
  if (properties.onLoadCallback) properties.onLoadCallback(self);
113
+
80
114
  }
81
115
  self.tileLoader.get(self.abortController, properties.url, self.uuid, self);
82
116
 
@@ -132,7 +166,10 @@ class InstancedTile extends THREE.Object3D {
132
166
  this.boundingVolume = new OBB(this.json.boundingVolume.box);
133
167
  } else if (!!this.json.boundingVolume.region) {
134
168
  const region = this.json.boundingVolume.region;
135
- this.boundingVolume = new THREE.Box3(new THREE.Vector3(region[0], region[2], region[4]), new THREE.Vector3(region[1], region[3], region[5]));
169
+ this.transformWGS84ToCartesian(region[0], region[1], region[4], tempVec1);
170
+ this.transformWGS84ToCartesian(region[2], region[3], region[5], tempVec2);
171
+ tempVec1.lerp(tempVec2, 0.5);
172
+ this.boundingVolume = new THREE.Sphere(new THREE.Vector3(tempVec1.x, tempVec1.y, tempVec1.z), tempVec1.distanceTo(tempVec2));
136
173
  } else if (!!this.json.boundingVolume.sphere) {
137
174
  const sphere = this.json.boundingVolume.sphere;
138
175
  this.boundingVolume = new THREE.Sphere(new THREE.Vector3(sphere[0], sphere[2], -sphere[1]), sphere[3]);
@@ -188,6 +225,8 @@ class InstancedTile extends THREE.Object3D {
188
225
 
189
226
  }
190
227
  }
228
+ self.matrixWorldNeedsUpdate = true;
229
+ self.updateWorldMatrix(true,true)
191
230
  }
192
231
 
193
232
  loadMesh(mesh) {
@@ -440,11 +479,9 @@ class InstancedTile extends THREE.Object3D {
440
479
  tempSphere.copy(this.boundingVolume);
441
480
  tempSphere.applyMatrix4(this.master.matrixWorld);
442
481
  if (!frustum.intersectsSphere(tempSphere)) return -1;
443
- } else if (this.boundingVolume instanceof THREE.Box3) {
444
- // Region
445
- // Region not supported
446
- //throw Error("Region bounding volume not supported");
447
- return -1;
482
+ } else {
483
+ console.error("unsupported shape");
484
+ return -1
448
485
  }
449
486
 
450
487
  /////// return metric based on geometric error and distance
@@ -505,8 +542,8 @@ class InstancedTile extends THREE.Object3D {
505
542
  tempSphere.applyMatrix4(this.master.matrixWorld);
506
543
  //if (!frustum.intersectsSphere(tempSphere)) return -1;
507
544
  }
508
- if (this.boundingVolume instanceof THREE.Box3) {
509
- return -1; // region not supported
545
+ else {
546
+ console.error("unsupported shape")
510
547
  }
511
548
  return Math.max(0, camera.position.distanceTo(tempSphere.center) - tempSphere.radius);
512
549
  }
@@ -515,5 +552,21 @@ class InstancedTile extends THREE.Object3D {
515
552
  const self = this;
516
553
  return self.master.matrixWorld;
517
554
  }
555
+
556
+ transformWGS84ToCartesian(lon, lat, h, sfct) {
557
+ const a = 6378137.0;
558
+ const e = 0.006694384442042;
559
+ const N = a / (Math.sqrt(1.0 - (e * Math.pow(Math.sin(lat), 2))));
560
+ const cosLat = Math.cos(lat);
561
+ const cosLon = Math.cos(lon);
562
+ const sinLat = Math.sin(lat);
563
+ const sinLon = Math.sin(lon);
564
+ const nPh = (N + h);
565
+ const x = nPh * cosLat * cosLon;
566
+ const y = nPh * cosLat * sinLon;
567
+ const z = (0.993305615557957 * N + h) * sinLat;
568
+
569
+ sfct.set(x, y, z);
570
+ }
518
571
  }
519
572
  export { InstancedTile };
@@ -1,6 +1,7 @@
1
1
  import * as THREE from 'three';
2
2
  import { InstancedMesh } from 'three';
3
3
 
4
+ const t = new THREE.Matrix4();
4
5
  class MeshTile{
5
6
  constructor(scene){
6
7
  const self = this;
@@ -60,7 +61,12 @@ class MeshTile{
60
61
  self.instancedTiles[i].meshContent = self.instancedMesh;
61
62
  if(self.instancedTiles[i].materialVisibility && !!self.instancedTiles[i].meshContent){
62
63
  self.instancedMesh.count++;
63
- self.instancedMesh.setMatrixAt(self.instancedMesh.count-1, self.reuseableMatrix.multiplyMatrices(self.instancedTiles[i].getWorldMatrix(), self.instancedMesh.baseMatrix) )
64
+ self.reuseableMatrix.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);
65
+ self.reuseableMatrix.multiply(self.instancedTiles[i].master.matrixWorld);
66
+ self.reuseableMatrix.multiply(self.instancedMesh.baseMatrix);
67
+ self.instancedMesh.setMatrixAt(self.instancedMesh.count-1, self.reuseableMatrix );
68
+ self.instancedMesh.getMatrixAt(0, t);
69
+ console.log()
64
70
  }
65
71
 
66
72
  }