Skip to content

Commit 599e58d

Browse files
authored
Add support for Quantized Mesh (#1061)
* Add base loader * Add classes * Add plugin * Update * Updates * Separate deep zoom plugin file * Separate files * Updates * QMesh loader updates * Fixes * Fixes * Add metadata * Updates * Fix index * Fix value * Fixes * Updates * Updates * Skirt fixes * Normals fix * Fix skirts * Adjustments * Some performance improvements * Comments * Add smooth skirt normals * Fixes * Update docs * Update the water mask
1 parent 74e1cd7 commit 599e58d

17 files changed

Lines changed: 1277 additions & 259 deletions

src/base/TilesRendererBase.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,8 +627,8 @@ export class TilesRendererBase {
627627

628628
}
629629

630-
// remove trailing slash and last path-segment from the URL
631-
let basePath = url.replace( /\/[^/]*\/?$/, '' );
630+
// remove the last file path path-segment from the URL including the trailing slash
631+
let basePath = url.replace( /\/[^/]*$/, '' );
632632
basePath = new URL( basePath, window.location.href ).toString();
633633
this.preprocessNode( json.root, basePath, parent );
634634

src/base/loaders/LoaderBase.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ export class LoaderBase {
77

88
}
99

10-
load( url ) {
10+
load( ...args ) {
1111

1212
console.warn( 'Loader: "load" function has been deprecated in favor of "loadAsync".' );
13-
return this.loadAsync( url );
13+
return this.loadAsync( ...args );
1414

1515
}
1616

src/plugins/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,34 @@ Available options are as follows:
657657
}
658658
```
659659

660+
## QuantizedMeshPlugin
661+
662+
Plugin for adding support to load and display [quantized mesh](https://github.com/CesiumGS/quantized-mesh) tile sets.
663+
664+
### .constructor
665+
666+
```js
667+
constructor( options : Object )
668+
```
669+
670+
Available options are as follows:
671+
672+
```js
673+
{
674+
// If true then the TilesRenderer error target is set to 2 so an expected amount of tile detail is rendered.
675+
useRecommendedSettings: true,
676+
677+
// The length of the skirts to generate for each tile. No skirts are generated if set to 0.
678+
skirtLength: 1000,
679+
680+
// If true then the normals on the edge of the tile are replicated on skirt vertices. Otherwise the skits have flat edges.
681+
smoothSkirtNormals: true,
682+
683+
// Whether to generate the tiles as a solid form with bottom faces.
684+
solid: false,
685+
}
686+
```
687+
660688
## LoadRegionPlugin
661689

662690
Plugin to enhances the TilesRenderer by enabling selective loading of tiles based on regions or volumes up to a specified geometric error target. Regions take shapes in the local tile set coordinate frame:
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import { LoaderBase } from '../../../base/loaders/LoaderBase.js';
2+
3+
export function zigZagDecode( value ) {
4+
5+
return ( value >> 1 ) ^ ( - ( value & 1 ) );
6+
7+
}
8+
9+
export class QuantizedMeshLoaderBase extends LoaderBase {
10+
11+
constructor( ...args ) {
12+
13+
super( ...args );
14+
15+
this.fetchOptions.header = {
16+
Accept: 'application/vnd.quantized-mesh,application/octet-stream;q=0.9',
17+
};
18+
19+
}
20+
21+
loadAsync( ...args ) {
22+
23+
const { fetchOptions } = this;
24+
fetchOptions.header = fetchOptions.header || {};
25+
fetchOptions.header[ 'Accept' ] = 'application/vnd.quantized-mesh,application/octet-stream;q=0.9';
26+
fetchOptions.header[ 'Accept' ] += ';extensions=octvertexnormals-watermask-metadata';
27+
28+
return super.loadAsync( ...args );
29+
30+
}
31+
32+
parse( buffer ) {
33+
34+
let pointer = 0;
35+
const view = new DataView( buffer );
36+
const readFloat64 = () => {
37+
38+
const result = view.getFloat64( pointer, true );
39+
pointer += 8;
40+
return result;
41+
42+
};
43+
44+
const readFloat32 = () => {
45+
46+
const result = view.getFloat32( pointer, true );
47+
pointer += 4;
48+
return result;
49+
50+
};
51+
52+
const readInt = () => {
53+
54+
const result = view.getUint32( pointer, true );
55+
pointer += 4;
56+
return result;
57+
58+
};
59+
60+
const readByte = () => {
61+
62+
const result = view.getUint8( pointer );
63+
pointer += 1;
64+
return result;
65+
66+
};
67+
68+
const readBuffer = ( count, type ) => {
69+
70+
const result = new type( buffer, pointer, count );
71+
pointer += count * type.BYTES_PER_ELEMENT;
72+
return result;
73+
74+
};
75+
76+
// extract header
77+
const header = {
78+
center: [ readFloat64(), readFloat64(), readFloat64() ],
79+
minHeight: readFloat32(),
80+
maxHeight: readFloat32(),
81+
sphereCenter: [ readFloat64(), readFloat64(), readFloat64() ],
82+
sphereRadius: readFloat64(),
83+
horizonOcclusionPoint: [ readFloat64(), readFloat64(), readFloat64() ],
84+
};
85+
86+
// extract vertex data
87+
const vertexCount = readInt();
88+
const uBuffer = readBuffer( vertexCount, Uint16Array );
89+
const vBuffer = readBuffer( vertexCount, Uint16Array );
90+
const hBuffer = readBuffer( vertexCount, Uint16Array );
91+
92+
const uResult = new Float32Array( vertexCount );
93+
const vResult = new Float32Array( vertexCount );
94+
const hResult = new Float32Array( vertexCount );
95+
96+
// decode vertex data
97+
let u = 0;
98+
let v = 0;
99+
let h = 0;
100+
const MAX_VALUE = 32767;
101+
for ( let i = 0; i < vertexCount; ++ i ) {
102+
103+
u += zigZagDecode( uBuffer[ i ] );
104+
v += zigZagDecode( vBuffer[ i ] );
105+
h += zigZagDecode( hBuffer[ i ] );
106+
107+
uResult[ i ] = u / MAX_VALUE;
108+
vResult[ i ] = v / MAX_VALUE;
109+
hResult[ i ] = h / MAX_VALUE;
110+
111+
}
112+
113+
// align pointer for index data
114+
const is32 = vertexCount > 65536;
115+
const bufferType = is32 ? Uint32Array : Uint16Array;
116+
if ( is32 ) {
117+
118+
pointer = Math.ceil( pointer / 4 ) * 4;
119+
120+
} else {
121+
122+
pointer = Math.ceil( pointer / 2 ) * 2;
123+
124+
}
125+
126+
// extract index data
127+
const triangleCount = readInt();
128+
const indices = readBuffer( triangleCount * 3, bufferType );
129+
130+
// decode the index data
131+
let highest = 0;
132+
for ( var i = 0; i < indices.length; ++ i ) {
133+
134+
const code = indices[ i ];
135+
indices[ i ] = highest - code;
136+
if ( code === 0 ) {
137+
138+
++ highest;
139+
140+
}
141+
142+
}
143+
144+
// sort functions for the edges since they are not pre-sorted
145+
const vSort = ( a, b ) => vResult[ b ] - vResult[ a ];
146+
const vSortReverse = ( a, b ) => - vSort( a, b );
147+
148+
const uSort = ( a, b ) => uResult[ a ] - uResult[ b ];
149+
const uSortReverse = ( a, b ) => - uSort( a, b );
150+
151+
// get edge indices
152+
const westVertexCount = readInt();
153+
const westIndices = readBuffer( westVertexCount, bufferType );
154+
westIndices.sort( vSort );
155+
156+
const southVertexCount = readInt();
157+
const southIndices = readBuffer( southVertexCount, bufferType );
158+
southIndices.sort( uSort );
159+
160+
const eastVertexCount = readInt();
161+
const eastIndices = readBuffer( eastVertexCount, bufferType );
162+
eastIndices.sort( vSortReverse );
163+
164+
const northVertexCount = readInt();
165+
const northIndices = readBuffer( northVertexCount, bufferType );
166+
northIndices.sort( uSortReverse );
167+
168+
const edgeIndices = {
169+
westIndices,
170+
southIndices,
171+
eastIndices,
172+
northIndices,
173+
};
174+
175+
// parse extensions
176+
const extensions = {};
177+
while ( pointer < view.byteLength ) {
178+
179+
const extensionId = readByte();
180+
const extensionLength = readInt();
181+
182+
if ( extensionId === 1 ) {
183+
184+
// oct encoded normals
185+
const xy = readBuffer( vertexCount * 2, Uint8Array );
186+
const normals = new Float32Array( vertexCount * 3 );
187+
188+
// https://github.com/CesiumGS/cesium/blob/baaabaa49058067c855ad050be73a9cdfe9b6ac7/packages/engine/Source/Core/AttributeCompression.js#L119-L140
189+
for ( let i = 0; i < vertexCount; i ++ ) {
190+
191+
let x = ( xy[ 2 * i + 0 ] / 255 ) * 2 - 1;
192+
let y = ( xy[ 2 * i + 1 ] / 255 ) * 2 - 1;
193+
const z = 1.0 - ( Math.abs( x ) + Math.abs( y ) );
194+
195+
if ( z < 0.0 ) {
196+
197+
const oldVX = x;
198+
x = ( 1.0 - Math.abs( y ) ) * signNotZero( oldVX );
199+
y = ( 1.0 - Math.abs( oldVX ) ) * signNotZero( y );
200+
201+
}
202+
203+
const len = Math.sqrt( x * x + y * y + z * z );
204+
normals[ 3 * i + 0 ] = x / len;
205+
normals[ 3 * i + 1 ] = y / len;
206+
normals[ 3 * i + 2 ] = z / len;
207+
208+
}
209+
210+
extensions[ 'octvertexnormals' ] = {
211+
extensionId,
212+
normals,
213+
};
214+
215+
} else if ( extensionId === 2 ) {
216+
217+
// water mask
218+
const size = extensionLength === 1 ? 1 : 256;
219+
const mask = readBuffer( size * size, Uint8Array );
220+
extensions[ 'watermask' ] = {
221+
extensionId,
222+
mask,
223+
size,
224+
};
225+
226+
} else if ( extensionId === 4 ) {
227+
228+
// metadata
229+
const jsonLength = readInt();
230+
const jsonBuffer = readBuffer( jsonLength, Uint8Array );
231+
const json = new TextDecoder().decode( jsonBuffer );
232+
extensions[ 'metadata' ] = {
233+
extensionId,
234+
json: JSON.parse( json ),
235+
};
236+
237+
}
238+
239+
}
240+
241+
return {
242+
header,
243+
indices,
244+
vertexData: {
245+
u: uResult,
246+
v: vResult,
247+
height: hResult,
248+
},
249+
edgeIndices,
250+
extensions,
251+
};
252+
253+
}
254+
255+
}
256+
257+
function signNotZero( v ) {
258+
259+
return v < 0.0 ? - 1.0 : 1.0;
260+
261+
}

src/plugins/index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export * from './three/LoadRegionPlugin';
1212
export * from './three/DebugTilesPlugin';
1313

1414
// other formats
15-
export * from './three/images/ImageFormatPlugin';
16-
export * from './three/images/EllipsoidProjectionTilesPlugin';
15+
export * from './three/images/DeepZoomImagePlugin';
16+
export * from './three/images/EPSGTilesPlugin';
1717

1818
// common plugins
1919
export { ImplicitTilingPlugin } from './base/ImplicitTilingPlugin';

src/plugins/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ export * from './three/LoadRegionPlugin.js';
1212
export * from './three/DebugTilesPlugin.js';
1313

1414
// other formats
15-
export * from './three/images/ImageFormatPlugin.js';
16-
export * from './three/images/EllipsoidProjectionTilesPlugin.js';
15+
export * from './three/images/DeepZoomImagePlugin.js';
16+
export * from './three/images/EPSGTilesPlugin.js';
1717

1818
// common plugins
1919
export { ImplicitTilingPlugin } from './base/ImplicitTilingPlugin.js';

src/plugins/three/CesiumIonAuthPlugin.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { GoogleCloudAuthPlugin } from './GoogleCloudAuthPlugin.js';
2+
import { TMSTilesPlugin } from './images/EPSGTilesPlugin.js';
3+
import { QuantizedMeshPlugin } from './QuantizedMeshPlugin.js';
24

35
export class CesiumIonAuthPlugin {
46

5-
constructor( { apiToken, assetId = null, autoRefreshToken = false } ) {
7+
constructor( { apiToken, assetId = null, autoRefreshToken = false, useRecommendedSettings = true } ) {
68

79
this.name = 'CESIUM_ION_AUTH_PLUGIN';
810
this.priority = - Infinity;
911

1012
this.apiToken = apiToken;
1113
this.assetId = assetId;
1214
this.autoRefreshToken = autoRefreshToken;
15+
this.useRecommendedSettings = useRecommendedSettings;
1316
this.tiles = null;
1417
this.endpointURL = null;
1518

@@ -153,10 +156,32 @@ export class CesiumIonAuthPlugin {
153156
tiles.registerPlugin( new GoogleCloudAuthPlugin( {
154157
apiToken: url.searchParams.get( 'key' ),
155158
autoRefreshToken: this.autoRefreshToken,
159+
useRecommendedSettings: this.useRecommendedSettings,
156160
} ) );
157161

158162
} else {
159163

164+
// GLTF
165+
// CZML
166+
// KML
167+
// GEOJSON
168+
// TODO: Should we automatically add these? Or add a callback for defining them?
169+
// How can a user add custom options in these cases?
170+
if ( json.type === 'TERRAIN' ) {
171+
172+
tiles.registerPlugin( new QuantizedMeshPlugin( {
173+
useRecommendedSettings: this.useRecommendedSettings,
174+
} ) );
175+
176+
} else if ( json.type === 'IMAGERY' ) {
177+
178+
tiles.registerPlugin( new TMSTilesPlugin( {
179+
useRecommendedSettings: this.useRecommendedSettings,
180+
shape: 'ellipsoid',
181+
} ) );
182+
183+
}
184+
160185
tiles.rootURL = json.url;
161186
tiles.fetchOptions.headers = tiles.fetchOptions.headers || {};
162187
tiles.fetchOptions.headers.Authorization = `Bearer ${ json.accessToken }`;

0 commit comments

Comments
 (0)