Skip to content

Commit 6e68a8f

Browse files
committed
[breaking] Switch hires tile format to prbm (modified prwm)
1 parent 3a1e723 commit 6e68a8f

24 files changed

+769
-351
lines changed

BlueMapCommon/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ dependencies {
3333
api ("de.bluecolored.bluemap.core:BlueMapCore")
3434

3535
compileOnly ("org.jetbrains:annotations:16.0.2")
36-
compileOnly ("org.projectlombok:lombok:1.18.28")
36+
compileOnly ("org.projectlombok:lombok:1.18.30")
3737

38-
annotationProcessor ("org.projectlombok:lombok:1.18.28")
38+
annotationProcessor ("org.projectlombok:lombok:1.18.30")
3939

4040
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
4141
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")

BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/MapStorageRequestHandler.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,7 @@ public HttpResponse handle(HttpRequest request) {
133133
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
134134
}
135135

136-
if (path.endsWith(".png")) {
137-
return new HttpResponse(HttpStatusCode.NO_CONTENT);
138-
}
139-
140-
if (path.endsWith(".json")) {
141-
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
142-
response.addHeader("Content-Type", "application/json");
143-
response.setData("{}");
144-
return response;
145-
}
146-
147-
return new HttpResponse(HttpStatusCode.NOT_FOUND);
136+
return new HttpResponse(HttpStatusCode.NO_CONTENT);
148137
}
149138

150139
private String calculateETag(String path, TileInfo tileInfo) {

BlueMapCommon/webapp/src/js/map/TileLoader.js

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@
2323
* THE SOFTWARE.
2424
*/
2525
import {pathFromCoords} from "../util/Utils";
26-
import {BufferGeometryLoader, FileLoader, Mesh} from "three";
26+
import {BufferGeometryLoader, FileLoader, Mesh, Material} from "three";
27+
import {PRBMLoader} from "./hires/PRBMLoader";
2728

2829
export class TileLoader {
2930

3031
/**
3132
* @param tilePath {string}
32-
* @param material {THREE.Material | THREE.Material[]}
33+
* @param material {Material | Material[]}
3334
* @param tileSettings {{
3435
* tileSize: {x: number, z: number},
3536
* scale: {x: number, z: number},
@@ -50,31 +51,25 @@ export class TileLoader {
5051
this.loadBlocker = loadBlocker;
5152

5253
this.fileLoader = new FileLoader();
53-
this.fileLoader.setResponseType('json');
54+
this.fileLoader.setResponseType('arraybuffer');
5455

55-
this.bufferGeometryLoader = new BufferGeometryLoader();
56+
this.bufferGeometryLoader = new PRBMLoader();
5657
}
5758

5859
load = (tileX, tileZ, cancelCheck = () => false) => {
59-
let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.json';
60+
let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.prbm';
6061

61-
//await this.loadBlocker();
6262
return new Promise((resolve, reject) => {
6363
this.fileLoader.load(tileUrl + '?' + this.tileCacheHash,
64-
async json => {
65-
let geometryJson = json.tileGeometry || {};
66-
if (!geometryJson.type || geometryJson.type !== 'BufferGeometry'){
67-
reject({status: "empty"});
68-
return;
69-
}
64+
async data => {
7065

7166
await this.loadBlocker();
7267
if (cancelCheck()){
7368
reject({status: "cancelled"});
7469
return;
7570
}
7671

77-
let geometry = this.bufferGeometryLoader.parse(geometryJson);
72+
let geometry = this.bufferGeometryLoader.parse(data);
7873

7974
let object = new Mesh(geometry, this.material);
8075

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
/*
2+
* This file is part of BlueMap, licensed under the MIT License (MIT).
3+
*
4+
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
5+
* Copyright (c) Kevin Chapelier <https://github.com/kchapelier>
6+
* Copyright (c) contributors
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*
26+
* Adapted version of PRWM by Kevin Chapelier
27+
* See https://github.com/kchapelier/PRWM for more informations about this file format
28+
*/
29+
30+
import {
31+
DefaultLoadingManager,
32+
BufferGeometry,
33+
BufferAttribute,
34+
FloatType
35+
} from "three"
36+
37+
"use strict";
38+
39+
let bigEndianPlatform = null;
40+
41+
/**
42+
* Check if the endianness of the platform is big-endian (most significant bit first)
43+
* @returns {boolean} True if big-endian, false if little-endian
44+
*/
45+
function isBigEndianPlatform() {
46+
if ( bigEndianPlatform === null ) {
47+
let buffer = new ArrayBuffer( 2 ),
48+
uint8Array = new Uint8Array( buffer ),
49+
uint16Array = new Uint16Array( buffer );
50+
51+
uint8Array[ 0 ] = 0xAA; // set first byte
52+
uint8Array[ 1 ] = 0xBB; // set second byte
53+
bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB );
54+
}
55+
56+
return bigEndianPlatform;
57+
}
58+
59+
// match the values defined in the spec to the TypedArray types
60+
let InvertedEncodingTypes = [
61+
null,
62+
Float32Array,
63+
null,
64+
Int8Array,
65+
Int16Array,
66+
null,
67+
Int32Array,
68+
Uint8Array,
69+
Uint16Array,
70+
null,
71+
Uint32Array
72+
];
73+
74+
// define the method to use on a DataView, corresponding the TypedArray type
75+
let getMethods = {
76+
Uint16Array: 'getUint16',
77+
Uint32Array: 'getUint32',
78+
Int16Array: 'getInt16',
79+
Int32Array: 'getInt32',
80+
Float32Array: 'getFloat32',
81+
Float64Array: 'getFloat64'
82+
};
83+
84+
function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) {
85+
let bytesPerElement = viewType.BYTES_PER_ELEMENT,
86+
result;
87+
88+
if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) {
89+
result = new viewType( sourceArrayBuffer, position, length );
90+
} else {
91+
console.debug("PRWM file has opposite encoding, loading will be slow...");
92+
93+
let readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ),
94+
getMethod = getMethods[ viewType.name ],
95+
littleEndian = ! fromBigEndian,
96+
i = 0;
97+
98+
result = new viewType( length );
99+
100+
for ( ; i < length; i ++ ) {
101+
result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian );
102+
}
103+
}
104+
105+
return result;
106+
}
107+
108+
/**
109+
* @param buffer {ArrayBuffer}
110+
* @param offset {number}
111+
*/
112+
function decodePrwm( buffer, offset ) {
113+
offset = offset || 0;
114+
115+
let array = new Uint8Array( buffer, offset ),
116+
version = array[ 0 ],
117+
flags = array[ 1 ],
118+
indexedGeometry = !! ( flags >> 7 & 0x01 ),
119+
indicesType = flags >> 6 & 0x01,
120+
bigEndian = ( flags >> 5 & 0x01 ) === 1,
121+
attributesNumber = flags & 0x1F,
122+
valuesNumber = 0,
123+
indicesNumber = 0;
124+
125+
if ( bigEndian ) {
126+
valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ];
127+
indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ];
128+
} else {
129+
valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 );
130+
indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 );
131+
}
132+
133+
/** PRELIMINARY CHECKS **/
134+
135+
if ( offset / 4 % 1 !== 0 ) {
136+
throw new Error( 'PRWM decoder: Offset should be a multiple of 4, received ' + offset );
137+
}
138+
139+
if ( version === 0 ) {
140+
throw new Error( 'PRWM decoder: Invalid format version: 0' );
141+
} else if ( version !== 1 ) {
142+
throw new Error( 'PRWM decoder: Unsupported format version: ' + version );
143+
}
144+
145+
if ( ! indexedGeometry ) {
146+
if ( indicesType !== 0 ) {
147+
throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' );
148+
} else if ( indicesNumber !== 0 ) {
149+
throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' );
150+
}
151+
}
152+
153+
/** PARSING **/
154+
155+
let pos = 8;
156+
157+
let attributes = {},
158+
attributeName,
159+
char,
160+
attributeType,
161+
cardinality,
162+
encodingType,
163+
normalized,
164+
arrayType,
165+
values,
166+
indices,
167+
groups,
168+
next,
169+
i;
170+
171+
for ( i = 0; i < attributesNumber; i ++ ) {
172+
attributeName = '';
173+
174+
while ( pos < array.length ) {
175+
char = array[ pos ];
176+
pos ++;
177+
178+
if ( char === 0 ) {
179+
break;
180+
} else {
181+
attributeName += String.fromCharCode( char );
182+
}
183+
}
184+
185+
flags = array[ pos ];
186+
187+
attributeType = flags >> 7 & 0x01;
188+
normalized = flags >> 6 & 0x01;
189+
cardinality = ( flags >> 4 & 0x03 ) + 1;
190+
encodingType = flags & 0x0F;
191+
arrayType = InvertedEncodingTypes[ encodingType ];
192+
193+
pos ++;
194+
195+
// padding to next multiple of 4
196+
pos = Math.ceil( pos / 4 ) * 4;
197+
198+
values = copyFromBuffer( buffer, arrayType, pos + offset, cardinality * valuesNumber, bigEndian );
199+
200+
pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber;
201+
202+
attributes[ attributeName ] = {
203+
type: attributeType,
204+
cardinality: cardinality,
205+
values: values,
206+
normalized: normalized === 1
207+
};
208+
}
209+
210+
indices = null;
211+
if ( indexedGeometry ) {
212+
pos = Math.ceil( pos / 4 ) * 4;
213+
indices = copyFromBuffer(
214+
buffer,
215+
indicesType === 1 ? Uint32Array : Uint16Array,
216+
pos + offset,
217+
indicesNumber,
218+
bigEndian
219+
);
220+
}
221+
222+
// read groups
223+
groups = [];
224+
pos = Math.ceil( pos / 4 ) * 4;
225+
while ( pos < array.length ) {
226+
next = read4ByteInt(array, pos);
227+
if (next === -1) {
228+
pos += 4;
229+
break;
230+
}
231+
groups.push({
232+
materialIndex: next,
233+
start: read4ByteInt(array, pos + 4),
234+
count: read4ByteInt(array, pos + 8)
235+
});
236+
pos += 12;
237+
}
238+
239+
return {
240+
version: version,
241+
attributes: attributes,
242+
indices: indices,
243+
groups: groups
244+
};
245+
}
246+
247+
function read4ByteInt(array, pos) {
248+
return array[pos] |
249+
array[pos + 1] << 8 |
250+
array[pos + 2] << 16 |
251+
array[pos + 3] << 24;
252+
}
253+
254+
export class PRBMLoader {
255+
256+
constructor ( manager ) {
257+
this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
258+
}
259+
260+
load ( url, onLoad, onProgress, onError ) {
261+
let scope = this;
262+
263+
url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' );
264+
265+
let loader = new FileLoader( scope.manager );
266+
loader.setPath( scope.path );
267+
loader.setResponseType( 'arraybuffer' );
268+
269+
loader.load( url, function ( arrayBuffer ) {
270+
onLoad( scope.parse( arrayBuffer ) );
271+
}, onProgress, onError );
272+
}
273+
274+
setPath ( value ) {
275+
this.path = value;
276+
return this;
277+
}
278+
279+
parse ( arrayBuffer, offset ) {
280+
let data = decodePrwm( arrayBuffer, offset ),
281+
attributesKey = Object.keys( data.attributes ),
282+
bufferGeometry = new BufferGeometry(),
283+
attribute,
284+
bufferAttribute,
285+
i;
286+
287+
for ( i = 0; i < attributesKey.length; i ++ ) {
288+
attribute = data.attributes[ attributesKey[ i ] ];
289+
bufferAttribute = new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized );
290+
bufferAttribute.gpuType = FloatType;
291+
bufferGeometry.setAttribute( attributesKey[ i ], bufferAttribute );
292+
}
293+
294+
if ( data.indices !== null ) {
295+
bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) );
296+
}
297+
298+
bufferGeometry.groups = data.groups;
299+
300+
return bufferGeometry;
301+
}
302+
303+
isBigEndianPlatform () {
304+
return isBigEndianPlatform();
305+
}
306+
307+
}

0 commit comments

Comments
 (0)