[breaking] Switch hires tile format to prbm (modified prwm)

This commit is contained in:
Lukas Rieger (Blue) 2024-02-23 17:32:07 +01:00
parent 3a1e723a51
commit 6e68a8f0e0
No known key found for this signature in database
GPG Key ID: AA33883B1BBA03E6
24 changed files with 769 additions and 351 deletions

View File

@ -33,9 +33,9 @@ dependencies {
api ("de.bluecolored.bluemap.core:BlueMapCore")
compileOnly ("org.jetbrains:annotations:16.0.2")
compileOnly ("org.projectlombok:lombok:1.18.28")
compileOnly ("org.projectlombok:lombok:1.18.30")
annotationProcessor ("org.projectlombok:lombok:1.18.28")
annotationProcessor ("org.projectlombok:lombok:1.18.30")
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")

View File

@ -133,18 +133,7 @@ public class MapStorageRequestHandler implements HttpRequestHandler {
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
}
if (path.endsWith(".png")) {
return new HttpResponse(HttpStatusCode.NO_CONTENT);
}
if (path.endsWith(".json")) {
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
response.addHeader("Content-Type", "application/json");
response.setData("{}");
return response;
}
return new HttpResponse(HttpStatusCode.NOT_FOUND);
return new HttpResponse(HttpStatusCode.NO_CONTENT);
}
private String calculateETag(String path, TileInfo tileInfo) {

View File

@ -23,13 +23,14 @@
* THE SOFTWARE.
*/
import {pathFromCoords} from "../util/Utils";
import {BufferGeometryLoader, FileLoader, Mesh} from "three";
import {BufferGeometryLoader, FileLoader, Mesh, Material} from "three";
import {PRBMLoader} from "./hires/PRBMLoader";
export class TileLoader {
/**
* @param tilePath {string}
* @param material {THREE.Material | THREE.Material[]}
* @param material {Material | Material[]}
* @param tileSettings {{
* tileSize: {x: number, z: number},
* scale: {x: number, z: number},
@ -50,23 +51,17 @@ export class TileLoader {
this.loadBlocker = loadBlocker;
this.fileLoader = new FileLoader();
this.fileLoader.setResponseType('json');
this.fileLoader.setResponseType('arraybuffer');
this.bufferGeometryLoader = new BufferGeometryLoader();
this.bufferGeometryLoader = new PRBMLoader();
}
load = (tileX, tileZ, cancelCheck = () => false) => {
let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.json';
let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.prbm';
//await this.loadBlocker();
return new Promise((resolve, reject) => {
this.fileLoader.load(tileUrl + '?' + this.tileCacheHash,
async json => {
let geometryJson = json.tileGeometry || {};
if (!geometryJson.type || geometryJson.type !== 'BufferGeometry'){
reject({status: "empty"});
return;
}
async data => {
await this.loadBlocker();
if (cancelCheck()){
@ -74,7 +69,7 @@ export class TileLoader {
return;
}
let geometry = this.bufferGeometryLoader.parse(geometryJson);
let geometry = this.bufferGeometryLoader.parse(data);
let object = new Mesh(geometry, this.material);

View File

@ -0,0 +1,307 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) Kevin Chapelier <https://github.com/kchapelier>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Adapted version of PRWM by Kevin Chapelier
* See https://github.com/kchapelier/PRWM for more informations about this file format
*/
import {
DefaultLoadingManager,
BufferGeometry,
BufferAttribute,
FloatType
} from "three"
"use strict";
let bigEndianPlatform = null;
/**
* Check if the endianness of the platform is big-endian (most significant bit first)
* @returns {boolean} True if big-endian, false if little-endian
*/
function isBigEndianPlatform() {
if ( bigEndianPlatform === null ) {
let buffer = new ArrayBuffer( 2 ),
uint8Array = new Uint8Array( buffer ),
uint16Array = new Uint16Array( buffer );
uint8Array[ 0 ] = 0xAA; // set first byte
uint8Array[ 1 ] = 0xBB; // set second byte
bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB );
}
return bigEndianPlatform;
}
// match the values defined in the spec to the TypedArray types
let InvertedEncodingTypes = [
null,
Float32Array,
null,
Int8Array,
Int16Array,
null,
Int32Array,
Uint8Array,
Uint16Array,
null,
Uint32Array
];
// define the method to use on a DataView, corresponding the TypedArray type
let getMethods = {
Uint16Array: 'getUint16',
Uint32Array: 'getUint32',
Int16Array: 'getInt16',
Int32Array: 'getInt32',
Float32Array: 'getFloat32',
Float64Array: 'getFloat64'
};
function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) {
let bytesPerElement = viewType.BYTES_PER_ELEMENT,
result;
if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) {
result = new viewType( sourceArrayBuffer, position, length );
} else {
console.debug("PRWM file has opposite encoding, loading will be slow...");
let readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ),
getMethod = getMethods[ viewType.name ],
littleEndian = ! fromBigEndian,
i = 0;
result = new viewType( length );
for ( ; i < length; i ++ ) {
result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian );
}
}
return result;
}
/**
* @param buffer {ArrayBuffer}
* @param offset {number}
*/
function decodePrwm( buffer, offset ) {
offset = offset || 0;
let array = new Uint8Array( buffer, offset ),
version = array[ 0 ],
flags = array[ 1 ],
indexedGeometry = !! ( flags >> 7 & 0x01 ),
indicesType = flags >> 6 & 0x01,
bigEndian = ( flags >> 5 & 0x01 ) === 1,
attributesNumber = flags & 0x1F,
valuesNumber = 0,
indicesNumber = 0;
if ( bigEndian ) {
valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ];
indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ];
} else {
valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 );
indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 );
}
/** PRELIMINARY CHECKS **/
if ( offset / 4 % 1 !== 0 ) {
throw new Error( 'PRWM decoder: Offset should be a multiple of 4, received ' + offset );
}
if ( version === 0 ) {
throw new Error( 'PRWM decoder: Invalid format version: 0' );
} else if ( version !== 1 ) {
throw new Error( 'PRWM decoder: Unsupported format version: ' + version );
}
if ( ! indexedGeometry ) {
if ( indicesType !== 0 ) {
throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' );
} else if ( indicesNumber !== 0 ) {
throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' );
}
}
/** PARSING **/
let pos = 8;
let attributes = {},
attributeName,
char,
attributeType,
cardinality,
encodingType,
normalized,
arrayType,
values,
indices,
groups,
next,
i;
for ( i = 0; i < attributesNumber; i ++ ) {
attributeName = '';
while ( pos < array.length ) {
char = array[ pos ];
pos ++;
if ( char === 0 ) {
break;
} else {
attributeName += String.fromCharCode( char );
}
}
flags = array[ pos ];
attributeType = flags >> 7 & 0x01;
normalized = flags >> 6 & 0x01;
cardinality = ( flags >> 4 & 0x03 ) + 1;
encodingType = flags & 0x0F;
arrayType = InvertedEncodingTypes[ encodingType ];
pos ++;
// padding to next multiple of 4
pos = Math.ceil( pos / 4 ) * 4;
values = copyFromBuffer( buffer, arrayType, pos + offset, cardinality * valuesNumber, bigEndian );
pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber;
attributes[ attributeName ] = {
type: attributeType,
cardinality: cardinality,
values: values,
normalized: normalized === 1
};
}
indices = null;
if ( indexedGeometry ) {
pos = Math.ceil( pos / 4 ) * 4;
indices = copyFromBuffer(
buffer,
indicesType === 1 ? Uint32Array : Uint16Array,
pos + offset,
indicesNumber,
bigEndian
);
}
// read groups
groups = [];
pos = Math.ceil( pos / 4 ) * 4;
while ( pos < array.length ) {
next = read4ByteInt(array, pos);
if (next === -1) {
pos += 4;
break;
}
groups.push({
materialIndex: next,
start: read4ByteInt(array, pos + 4),
count: read4ByteInt(array, pos + 8)
});
pos += 12;
}
return {
version: version,
attributes: attributes,
indices: indices,
groups: groups
};
}
function read4ByteInt(array, pos) {
return array[pos] |
array[pos + 1] << 8 |
array[pos + 2] << 16 |
array[pos + 3] << 24;
}
export class PRBMLoader {
constructor ( manager ) {
this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
}
load ( url, onLoad, onProgress, onError ) {
let scope = this;
url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' );
let loader = new FileLoader( scope.manager );
loader.setPath( scope.path );
loader.setResponseType( 'arraybuffer' );
loader.load( url, function ( arrayBuffer ) {
onLoad( scope.parse( arrayBuffer ) );
}, onProgress, onError );
}
setPath ( value ) {
this.path = value;
return this;
}
parse ( arrayBuffer, offset ) {
let data = decodePrwm( arrayBuffer, offset ),
attributesKey = Object.keys( data.attributes ),
bufferGeometry = new BufferGeometry(),
attribute,
bufferAttribute,
i;
for ( i = 0; i < attributesKey.length; i ++ ) {
attribute = data.attributes[ attributesKey[ i ] ];
bufferAttribute = new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized );
bufferAttribute.gpuType = FloatType;
bufferGeometry.setAttribute( attributesKey[ i ], bufferAttribute );
}
if ( data.indices !== null ) {
bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) );
}
bufferGeometry.groups = data.groups;
return bufferGeometry;
}
isBigEndianPlatform () {
return isBigEndianPlatform();
}
}

View File

@ -74,14 +74,14 @@ dependencies {
api ("de.bluecolored.bluemap.api:BlueMapAPI")
compileOnly ("org.jetbrains:annotations:23.0.0")
compileOnly ("org.projectlombok:lombok:1.18.28")
compileOnly ("org.projectlombok:lombok:1.18.30")
annotationProcessor ("org.projectlombok:lombok:1.18.28")
annotationProcessor ("org.projectlombok:lombok:1.18.30")
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
testCompileOnly ("org.projectlombok:lombok:1.18.28")
testAnnotationProcessor ("org.projectlombok:lombok:1.18.28")
testCompileOnly ("org.projectlombok:lombok:1.18.30")
testAnnotationProcessor ("org.projectlombok:lombok:1.18.30")
}
spotless {

View File

@ -29,14 +29,14 @@ import de.bluecolored.bluemap.core.util.math.MatrixM4f;
public class BlockModelView {
private HiresTileModel hiresTile;
private TileModel hiresTile;
private int start, size;
public BlockModelView(HiresTileModel hiresTile) {
public BlockModelView(TileModel hiresTile) {
initialize(hiresTile);
}
public BlockModelView initialize(HiresTileModel hiresTile, int start) {
public BlockModelView initialize(TileModel hiresTile, int start) {
this.hiresTile = hiresTile;
this.start = start;
this.size = hiresTile.size() - start;
@ -44,7 +44,7 @@ public class BlockModelView {
return this;
}
public BlockModelView initialize(HiresTileModel hiresTile) {
public BlockModelView initialize(TileModel hiresTile) {
this.hiresTile = hiresTile;
this.start = hiresTile.size();
this.size = 0;
@ -138,7 +138,7 @@ public class BlockModelView {
return this;
}
public HiresTileModel getHiresTile() {
public TileModel getHiresTile() {
return hiresTile;
}

View File

@ -33,6 +33,7 @@ import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.util.Grid;
import de.bluecolored.bluemap.core.world.World;
import lombok.Getter;
import java.io.IOException;
import java.io.OutputStream;
@ -41,6 +42,8 @@ public class HiresModelManager {
private final Storage.TileStorage storage;
private final HiresModelRenderer renderer;
@Getter
private final Grid tileGrid;
public HiresModelManager(Storage.TileStorage storage, ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings, Grid tileGrid) {
@ -64,7 +67,7 @@ public class HiresModelManager {
Vector3i modelMin = new Vector3i(tileMin.getX(), Integer.MIN_VALUE, tileMin.getY());
Vector3i modelMax = new Vector3i(tileMax.getX(), Integer.MAX_VALUE, tileMax.getY());
HiresTileModel model = HiresTileModel.instancePool().claimInstance();
TileModel model = TileModel.instancePool().claimInstance();
renderer.render(world, modelMin, modelMax, model, tileMetaConsumer);
@ -73,22 +76,18 @@ public class HiresModelManager {
save(model, tile);
}
HiresTileModel.instancePool().recycleInstance(model);
TileModel.instancePool().recycleInstance(model);
}
private void save(final HiresTileModel model, Vector2i tile) {
try (OutputStream os = storage.write(tile)) {
model.writeBufferGeometryJson(os);
private void save(final TileModel model, Vector2i tile) {
try (
OutputStream out = storage.write(tile);
PRBMWriter modelWriter = new PRBMWriter(out)
) {
modelWriter.write(model);
} catch (IOException e){
Logger.global.logError("Failed to save hires model: " + tile, e);
}
}
/**
* Returns the tile-grid
*/
public Grid getTileGrid() {
return tileGrid;
}
}

View File

@ -46,11 +46,11 @@ public class HiresModelRenderer {
this.renderSettings = renderSettings;
}
public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileModel model) {
public void render(World world, Vector3i modelMin, Vector3i modelMax, TileModel model) {
render(world, modelMin, modelMax, model, (x, z, c, h, l) -> {});
}
public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileModel model, TileMetaConsumer tileMetaConsumer) {
public void render(World world, Vector3i modelMin, Vector3i modelMax, TileModel model, TileMetaConsumer tileMetaConsumer) {
Vector3i min = modelMin.max(renderSettings.getMinPos());
Vector3i max = modelMax.min(renderSettings.getMaxPos());
Vector3i modelAnchor = new Vector3i(modelMin.getX(), 0, modelMin.getZ());

View File

@ -0,0 +1,317 @@
package de.bluecolored.bluemap.core.map.hires;
import de.bluecolored.bluemap.core.util.math.VectorM3f;
import de.bluecolored.bluemap.core.util.stream.CountingOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@SuppressWarnings("unused")
public class PRBMWriter implements Closeable {
private static final int FORMAT_VERSION = 1;
private static final int HEADER_BITS = 0b0_0_0_00111; // indexed (no) _ indices-type (-) _ endianness (little) _ attribute-nr (7)
private static final int ATTRIBUTE_TYPE_FLOAT = 0;
private static final int ATTRIBUTE_TYPE_INTEGER = 1 << 7;
private static final int ATTRIBUTE_NOT_NORMALIZED = 0;
private static final int ATTRIBUTE_NORMALIZED = 1 << 6;
private static final int ATTRIBUTE_CARDINALITY_SCALAR = 0;
private static final int ATTRIBUTE_CARDINALITY_2D_VEC = 1 << 4;
private static final int ATTRIBUTE_CARDINALITY_3D_VEC = 2 << 4;
private static final int ATTRIBUTE_CARDINALITY_4D_VEC = 3 << 4;
private static final int ATTRIBUTE_ENCODING_SIGNED_32BIT_FLOAT = 1;
private static final int ATTRIBUTE_ENCODING_SIGNED_8BIT_INT = 3;
private static final int ATTRIBUTE_ENCODING_SIGNED_16BIT_INT = 4;
private static final int ATTRIBUTE_ENCODING_SIGNED_32BIT_INT = 6;
private static final int ATTRIBUTE_ENCODING_UNSIGNED_8BIT_INT = 7;
private static final int ATTRIBUTE_ENCODING_UNSIGNED_16BIT_INT = 8;
private static final int ATTRIBUTE_ENCODING_UNSIGNED_32BIT_INT = 10;
private final CountingOutputStream out;
public PRBMWriter(OutputStream out) {
this.out = new CountingOutputStream(out);
}
public void write(TileModel model) throws IOException {
out.write(FORMAT_VERSION); // version - 1 byte
out.write(HEADER_BITS); // format info - 1 byte
write3byteValue(model.size * 3); // number of values - 3 bytes
write3byteValue(0); // number of indices (0 for non-indexed) - 3 bytes
writePositionArray(model);
writeNormalArray(model);
writeColorArray(model);
writeUvArray(model);
writeAoArray(model);
writeBlocklightArray(model);
writeSunlightArray(model);
writeMaterialGroups(model);
}
@Override
public void close() throws IOException {
out.close();
}
private void writePositionArray(TileModel model) throws IOException {
float[] position = model.position;
writeString("position");
out.write(
ATTRIBUTE_TYPE_FLOAT |
ATTRIBUTE_NOT_NORMALIZED |
ATTRIBUTE_CARDINALITY_3D_VEC |
ATTRIBUTE_ENCODING_SIGNED_32BIT_FLOAT
);
writePadding();
int posSize = model.size * TileModel.FI_POSITION;
for (int i = 0; i < posSize; i++) {
writeFloat(position[i]);
}
}
private void writeNormalArray(TileModel model) throws IOException {
VectorM3f normal = new VectorM3f(0, 0, 0);
float[] position = model.position;
writeString("normal");
out.write(
ATTRIBUTE_TYPE_FLOAT |
ATTRIBUTE_NOT_NORMALIZED |
ATTRIBUTE_CARDINALITY_3D_VEC |
ATTRIBUTE_ENCODING_SIGNED_32BIT_FLOAT
);
writePadding();
int pi, i, j;
for (i = 0; i < model.size; i++) {
pi = i * TileModel.FI_POSITION;
calculateSurfaceNormal(
position[pi], position[pi + 1], position[pi + 2],
position[pi + 3], position[pi + 4], position[pi + 5],
position[pi + 6], position[pi + 7], position[pi + 8],
normal
);
for (j = 0; j < 3; j++) { // all 3 points
writeFloat(normal.x);
writeFloat(normal.y);
writeFloat(normal.z);
}
}
}
private void writeColorArray(TileModel model) throws IOException {
float[] color = model.color;
writeString("color");
out.write(
ATTRIBUTE_TYPE_FLOAT |
ATTRIBUTE_NORMALIZED |
ATTRIBUTE_CARDINALITY_3D_VEC |
ATTRIBUTE_ENCODING_UNSIGNED_8BIT_INT
);
writePadding();
int colorSize = model.size * TileModel.FI_COLOR, i, j;
for (i = 0; i < colorSize; i += 3) {
for (j = 0; j < 3; j++) {
writeNormalizedUnsignedByteValue(color[i]);
writeNormalizedUnsignedByteValue(color[i + 1]);
writeNormalizedUnsignedByteValue(color[i + 2]);
}
}
}
private void writeUvArray(TileModel model) throws IOException {
float[] uv = model.uv;
writeString("uv");
out.write(
ATTRIBUTE_TYPE_FLOAT |
ATTRIBUTE_NOT_NORMALIZED |
ATTRIBUTE_CARDINALITY_2D_VEC |
ATTRIBUTE_ENCODING_SIGNED_32BIT_FLOAT
);
writePadding();
int uvSize = model.size * TileModel.FI_UV;
for (int i = 0; i < uvSize; i++) {
writeFloat(uv[i]);
}
}
private void writeAoArray(TileModel model) throws IOException {
float[] ao = model.ao;
writeString("ao");
out.write(
ATTRIBUTE_TYPE_FLOAT |
ATTRIBUTE_NORMALIZED |
ATTRIBUTE_CARDINALITY_SCALAR |
ATTRIBUTE_ENCODING_UNSIGNED_8BIT_INT
);
writePadding();
int uvSize = model.size * TileModel.FI_AO;
for (int i = 0; i < uvSize; i++) {
writeNormalizedUnsignedByteValue(ao[i]);
}
}
private void writeBlocklightArray(TileModel model) throws IOException {
byte[] blocklight = model.blocklight;
writeString("blocklight");
out.write(
ATTRIBUTE_TYPE_FLOAT |
ATTRIBUTE_NOT_NORMALIZED |
ATTRIBUTE_CARDINALITY_SCALAR |
ATTRIBUTE_ENCODING_SIGNED_8BIT_INT
);
writePadding();
int blSize = model.size * TileModel.FI_BLOCKLIGHT;
for (int i = 0; i < blSize; i++) {
out.write(blocklight[i]);
out.write(blocklight[i]);
out.write(blocklight[i]);
}
}
private void writeSunlightArray(TileModel model) throws IOException {
byte[] sunlight = model.sunlight;
writeString("sunlight");
out.write(
ATTRIBUTE_TYPE_FLOAT |
ATTRIBUTE_NOT_NORMALIZED |
ATTRIBUTE_CARDINALITY_SCALAR |
ATTRIBUTE_ENCODING_SIGNED_8BIT_INT
);
writePadding();
int slSize = model.size * TileModel.FI_SUNLIGHT;
for (int i = 0; i < slSize; i++) {
out.write(sunlight[i]);
out.write(sunlight[i]);
out.write(sunlight[i]);
}
}
private void writeMaterialGroups(TileModel model) throws IOException {
writePadding();
if (model.size > 0) {
int[] materialIndex = model.materialIndex;
int miSize = model.size * TileModel.FI_MATERIAL_INDEX,
lastMaterial = materialIndex[0],
material = lastMaterial, groupStart = 0;
write4byteValue(material);
write4byteValue(0);
for (int i = 1; i < miSize; i++) {
material = materialIndex[i];
if (material != lastMaterial) {
write4byteValue((i - groupStart) * 3);
groupStart = i;
write4byteValue(material);
write4byteValue(groupStart * 3);
}
lastMaterial = material;
}
write4byteValue((miSize - groupStart) * 3);
}
write4byteValue(-1);
}
private void writePadding() throws IOException {
int paddingBytes = (int) (-out.getCount() & 0x3);
for (int i = 0; i < paddingBytes; i++) {
out.write(0);
}
}
private void write2byteValue(int value) throws IOException {
if (value > 0xFFFF) throw new IOException("Value too high: " + value);
out.write(value & 0xFF);
out.write((value >> 8) & 0xFF);
}
private void write3byteValue(int value) throws IOException {
if (value > 0xFFFFFF) throw new IOException("Value too high: " + value);
out.write(value & 0xFF);
out.write((value >> 8) & 0xFF);
out.write((value >> 16) & 0xFF);
}
private void write4byteValue(int value) throws IOException {
out.write(value & 0xFF);
out.write((value >> 8) & 0xFF);
out.write((value >> 16) & 0xFF);
out.write((value >> 24) & 0xFF);
}
private void writeFloat(float value) throws IOException {
write4byteValue(Float.floatToIntBits(value));
}
private void writeNormalizedUnsignedByteValue(float value) throws IOException {
int normalized = (int) (value * 0xFF);
out.write(normalized & 0xFF);
}
private void writeString(String value) throws IOException {
out.write(value.getBytes(StandardCharsets.US_ASCII));
out.write(0);
}
private void calculateSurfaceNormal(
float p1x, float p1y, float p1z,
float p2x, float p2y, float p2z,
float p3x, float p3y, float p3z,
VectorM3f target
){
p2x -= p1x; p2y -= p1y; p2z -= p1z;
p3x -= p1x; p3y -= p1y; p3z -= p1z;
p1x = p2y * p3z - p2z * p3y;
p1y = p2z * p3x - p2x * p3z;
p1z = p2x * p3y - p2y * p3x;
float length = (float) Math.sqrt(p1x * p1x + p1y * p1y + p1z * p1z);
p1x /= length;
p1y /= length;
p1z /= length;
target.set(p1x, p1y, p1z);
}
}

View File

@ -25,27 +25,16 @@
package de.bluecolored.bluemap.core.map.hires;
import com.flowpowered.math.TrigMath;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonWriter;
import de.bluecolored.bluemap.core.util.InstancePool;
import de.bluecolored.bluemap.core.util.MergeSort;
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
import de.bluecolored.bluemap.core.util.math.VectorM3f;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class HiresTileModel {
public class TileModel {
private static final double GROW_MULTIPLIER = 1.5;
// attributes per-vertex * per-face
private static final int
static final int
FI_POSITION = 3 * 3,
FI_UV = 2 * 3,
FI_AO = 3,
@ -54,20 +43,23 @@ public class HiresTileModel {
FI_BLOCKLIGHT = 1 ,
FI_MATERIAL_INDEX = 1 ;
private static final InstancePool<HiresTileModel> INSTANCE_POOL = new InstancePool<>(
() -> new HiresTileModel(100),
HiresTileModel::clear
private static final InstancePool<TileModel> INSTANCE_POOL = new InstancePool<>(
() -> new TileModel(100),
TileModel::clear
);
private int capacity;
private int size;
int size;
private float[] position;
private float[] color, uv, ao;
private byte[] sunlight, blocklight;
private int[] materialIndex, materialIndexSort, materialIndexSortSupport;
float[] position;
float[] color, uv, ao;
byte[] sunlight, blocklight;
int[] materialIndex, materialIndexSort, materialIndexSortSupport;
public HiresTileModel(int initialCapacity) {
float[] indexedPosition;
int[] positionIndex;
public TileModel(int initialCapacity) {
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity is negative");
setCapacity(initialCapacity);
clear();
@ -84,7 +76,7 @@ public class HiresTileModel {
return start;
}
public HiresTileModel setPositions(
public TileModel setPositions(
int face,
float x1, float y1, float z1,
float x2, float y2, float z2,
@ -107,7 +99,7 @@ public class HiresTileModel {
return this;
}
public HiresTileModel setUvs(
public TileModel setUvs(
int face,
float u1, float v1,
float u2, float v2,
@ -127,7 +119,7 @@ public class HiresTileModel {
return this;
}
public HiresTileModel setAOs(
public TileModel setAOs(
int face,
float ao1, float ao2, float ao3
) {
@ -140,7 +132,7 @@ public class HiresTileModel {
return this;
}
public HiresTileModel setColor(
public TileModel setColor(
int face,
float r, float g, float b
){
@ -153,22 +145,22 @@ public class HiresTileModel {
return this;
}
public HiresTileModel setSunlight(int face, int sl) {
public TileModel setSunlight(int face, int sl) {
sunlight[face * FI_SUNLIGHT] = (byte) sl;
return this;
}
public HiresTileModel setBlocklight(int face, int bl) {
public TileModel setBlocklight(int face, int bl) {
blocklight[face * FI_BLOCKLIGHT] = (byte) bl;
return this;
}
public HiresTileModel setMaterialIndex(int face, int m) {
public TileModel setMaterialIndex(int face, int m) {
materialIndex[face * FI_MATERIAL_INDEX] = m;
return this;
}
public HiresTileModel rotate(
public TileModel rotate(
int start, int count,
float angle, float axisX, float axisY, float axisZ
) {
@ -193,7 +185,7 @@ public class HiresTileModel {
return rotateByQuaternion(start, count, qx, qy, qz, qw);
}
public HiresTileModel rotate(
public TileModel rotate(
int start, int count,
float pitch, float yaw, float roll
) {
@ -228,7 +220,7 @@ public class HiresTileModel {
return rotateByQuaternion(start, count, qx, qy, qz, qw);
}
public HiresTileModel rotateByQuaternion(
public TileModel rotateByQuaternion(
int start, int count,
double qx, double qy, double qz, double qw
) {
@ -256,7 +248,7 @@ public class HiresTileModel {
return this;
}
public HiresTileModel scale(
public TileModel scale(
int start, int count,
float sx, float sy, float sz
) {
@ -273,7 +265,7 @@ public class HiresTileModel {
return this;
}
public HiresTileModel translate(
public TileModel translate(
int start, int count,
float dx, float dy, float dz
) {
@ -290,7 +282,7 @@ public class HiresTileModel {
return this;
}
public HiresTileModel transform(int start, int count, MatrixM3f t) {
public TileModel transform(int start, int count, MatrixM3f t) {
return transform(start, count,
t.m00, t.m01, t.m02,
t.m10, t.m11, t.m12,
@ -298,7 +290,7 @@ public class HiresTileModel {
);
}
public HiresTileModel transform(
public TileModel transform(
int start, int count,
float m00, float m01, float m02,
float m10, float m11, float m12,
@ -312,7 +304,7 @@ public class HiresTileModel {
);
}
public HiresTileModel transform(int start, int count, MatrixM4f t) {
public TileModel transform(int start, int count, MatrixM4f t) {
return transform(start, count,
t.m00, t.m01, t.m02, t.m03,
t.m10, t.m11, t.m12, t.m13,
@ -321,7 +313,7 @@ public class HiresTileModel {
);
}
public HiresTileModel transform(
public TileModel transform(
int start, int count,
float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
@ -346,12 +338,12 @@ public class HiresTileModel {
return this;
}
public HiresTileModel reset(int size) {
public TileModel reset(int size) {
this.size = size;
return this;
}
public HiresTileModel clear() {
public TileModel clear() {
this.size = 0;
return this;
}
@ -394,225 +386,6 @@ public class HiresTileModel {
materialIndexSortSupport = new int [materialIndex.length];
}
public void writeBufferGeometryJson(OutputStream out) throws IOException {
Gson gson = new GsonBuilder().create();
JsonWriter json = gson.newJsonWriter(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), 81920));
json.beginObject(); // main-object
json.name("tileGeometry").beginObject(); // tile-geometry-object
// set special values
json.name("type").value("BufferGeometry");
json.name("uuid").value(UUID.randomUUID().toString().toUpperCase());
json.name("data").beginObject(); // data
json.name("attributes").beginObject(); // attributes
writePositionArray(json);
writeNormalArray(json);
writeColorArray(json);
writeUvArray(json);
writeAoArray(json);
writeBlocklightArray(json);
writeSunlightArray(json);
json.endObject(); // attributes
writeMaterialGroups(json);
json.endObject(); // data
json.endObject(); // tile-geometry-object
json.endObject(); // main-object
// save and return
json.flush();
}
private void writePositionArray(JsonWriter json) throws IOException {
json.name("position");
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(3);
json.name("normalized").value(false);
json.name("array").beginArray();
int posSize = size * FI_POSITION;
for (int i = 0; i < posSize; i++) {
writeRounded(json, position[i]);
}
json.endArray();
json.endObject();
}
private void writeNormalArray(JsonWriter json) throws IOException {
VectorM3f normal = new VectorM3f(0, 0, 0);
json.name("normal");
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(3);
json.name("normalized").value(false);
json.name("array").beginArray();
int pi, i, j;
for (i = 0; i < size; i++) {
pi = i * FI_POSITION;
calculateSurfaceNormal(
position[pi ], position[pi + 1], position[pi + 2],
position[pi + 3], position[pi + 4], position[pi + 5],
position[pi + 6], position[pi + 7], position[pi + 8],
normal
);
for (j = 0; j < 3; j++) { // all 3 points
writeRounded(json, normal.x);
writeRounded(json, normal.y);
writeRounded(json, normal.z);
}
}
json.endArray();
json.endObject();
}
private void writeColorArray(JsonWriter json) throws IOException {
json.name("color");
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(3);
json.name("normalized").value(false);
json.name("array").beginArray();
int colorSize = size * FI_COLOR, i, j;
for (i = 0; i < colorSize; i += 3) {
for (j = 0; j < 3; j++) {
writeRounded(json, color[i]);
writeRounded(json, color[i + 1]);
writeRounded(json, color[i + 2]);
}
}
json.endArray();
json.endObject();
}
private void writeUvArray(JsonWriter json) throws IOException {
json.name("uv");
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(2);
json.name("normalized").value(false);
json.name("array").beginArray();
int uvSize = size * FI_UV;
for (int i = 0; i < uvSize; i++) {
writeRounded(json, uv[i]);
}
json.endArray();
json.endObject();
}
private void writeAoArray(JsonWriter json) throws IOException {
json.name("ao");
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(1);
json.name("normalized").value(false);
json.name("array").beginArray();
int aoSize = size * FI_AO;
for (int i = 0; i < aoSize; i++) {
writeRounded(json, ao[i]);
}
json.endArray();
json.endObject();
}
private void writeBlocklightArray(JsonWriter json) throws IOException {
json.name("blocklight");
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(1);
json.name("normalized").value(false);
json.name("array").beginArray();
int blSize = size * FI_BLOCKLIGHT;
for (int i = 0; i < blSize; i++) {
json.value(blocklight[i]);
json.value(blocklight[i]);
json.value(blocklight[i]);
}
json.endArray();
json.endObject();
}
private void writeSunlightArray(JsonWriter json) throws IOException {
json.name("sunlight");
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(1);
json.name("normalized").value(false);
json.name("array").beginArray();
int blSize = size * FI_SUNLIGHT;
for (int i = 0; i < blSize; i++) {
json.value(sunlight[i]);
json.value(sunlight[i]);
json.value(sunlight[i]);
}
json.endArray();
json.endObject();
}
private void writeMaterialGroups(JsonWriter json) throws IOException {
json.name("groups").beginArray(); // groups
if (size > 0) {
int miSize = size * FI_MATERIAL_INDEX, lastMaterial = materialIndex[0], material = lastMaterial, groupStart = 0;
json.beginObject();
json.name("materialIndex").value(material);
json.name("start").value(0);
for (int i = 1; i < miSize; i++) {
material = materialIndex[i];
if (material != lastMaterial) {
json.name("count").value((i - groupStart) * 3L);
json.endObject();
groupStart = i;
json.beginObject();
json.name("materialIndex").value(material);
json.name("start").value(groupStart * 3L);
}
lastMaterial = material;
}
json.name("count").value((miSize - groupStart) * 3L);
json.endObject();
}
json.endArray(); // groups
}
private void writeRounded(JsonWriter json, double value) throws IOException {
// rounding and remove ".0" to save string space
double d = Math.round(value * 10000d) / 10000d;
if (d == (long) d) json.value((long) d);
else json.value(d);
}
public void sort() {
if (size <= 1) return; // nothing to sort
@ -700,28 +473,7 @@ public class HiresTileModel {
materialIndex[face2] = vi;
}
private static void calculateSurfaceNormal(
double p1x, double p1y, double p1z,
double p2x, double p2y, double p2z,
double p3x, double p3y, double p3z,
VectorM3f target
){
p2x -= p1x; p2y -= p1y; p2z -= p1z;
p3x -= p1x; p3y -= p1y; p3z -= p1z;
p1x = p2y * p3z - p2z * p3y;
p1y = p2z * p3x - p2x * p3z;
p1z = p2x * p3y - p2y * p3x;
double length = Math.sqrt(p1x * p1x + p1y * p1y + p1z * p1z);
p1x /= length;
p1y /= length;
p1z /= length;
target.set((float) p1x, (float) p1y, (float) p1z);
}
public static InstancePool<HiresTileModel> instancePool() {
public static InstancePool<TileModel> instancePool() {
return INSTANCE_POOL;
}

View File

@ -28,7 +28,7 @@ import com.flowpowered.math.TrigMath;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.map.TextureGallery;
import de.bluecolored.bluemap.core.map.hires.BlockModelView;
import de.bluecolored.bluemap.core.map.hires.HiresTileModel;
import de.bluecolored.bluemap.core.map.hires.TileModel;
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory;
import de.bluecolored.bluemap.core.resources.ResourcePath;
@ -249,7 +249,7 @@ public class LiquidModelBuilder {
blockModel.initialize();
blockModel.add(2);
HiresTileModel tileModel = blockModel.getHiresTile();
TileModel tileModel = blockModel.getHiresTile();
int face1 = blockModel.getStart();
int face2 = face1 + 1;

View File

@ -30,7 +30,7 @@ import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.map.TextureGallery;
import de.bluecolored.bluemap.core.map.hires.BlockModelView;
import de.bluecolored.bluemap.core.map.hires.HiresTileModel;
import de.bluecolored.bluemap.core.map.hires.TileModel;
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory;
import de.bluecolored.bluemap.core.resources.ResourcePath;
@ -205,7 +205,7 @@ public class ResourceModelBuilder {
blockModel.initialize();
blockModel.add(2);
HiresTileModel tileModel = blockModel.getHiresTile();
TileModel tileModel = blockModel.getHiresTile();
int face1 = blockModel.getStart();
int face2 = face1 + 1;

View File

@ -24,7 +24,7 @@
*/
package de.bluecolored.bluemap.core.storage;
import de.bluecolored.bluemap.core.util.DelegateInputStream;
import de.bluecolored.bluemap.core.util.stream.DelegateInputStream;
import java.io.IOException;
import java.io.InputStream;

View File

@ -72,14 +72,7 @@ public class FileStorage extends Storage {
Path file = getFilePath(mapId, lod, tile);
OutputStream os = FileHelper.createFilepartOutputStream(file);
try {
os = new BufferedOutputStream(compression.compress(os));
} catch (IOException ex) {
os.close();
throw ex;
}
return os;
return new BufferedOutputStream(compression.compress(os));
}
@Override
@ -240,7 +233,7 @@ public class FileStorage extends Storage {
}
if (lod == 0) {
return p.resolve(fileName + ".json" + hiresCompression.getFileSuffix());
return p.resolve(fileName + ".prbm" + hiresCompression.getFileSuffix());
} else {
return p.resolve(fileName + ".png");
}

View File

@ -29,7 +29,7 @@ import de.bluecolored.bluemap.core.storage.CompressedInputStream;
import de.bluecolored.bluemap.core.storage.Compression;
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
import de.bluecolored.bluemap.core.storage.sql.dialect.PostgresDialect;
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream;
import java.io.*;
import java.net.MalformedURLException;

View File

@ -32,7 +32,7 @@ import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.storage.*;
import de.bluecolored.bluemap.core.storage.sql.dialect.DialectType;
import de.bluecolored.bluemap.core.storage.sql.dialect.Dialect;
import de.bluecolored.bluemap.core.util.OnCloseOutputStream;
import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream;
import org.apache.commons.dbcp2.*;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;

View File

@ -24,6 +24,8 @@
*/
package de.bluecolored.bluemap.core.util;
import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;

View File

@ -112,6 +112,14 @@ public class Key implements Keyed {
return new Key(namespace, value);
}
public static Key minecraft(String value) {
return new Key(MINECRAFT_NAMESPACE, value);
}
public static Key bluemap(String value) {
return new Key(BLUEMAP_NAMESPACE, value);
}
/**
* Using our own function instead of {@link String#intern()} since the ConcurrentHashMap is much faster.
*/

View File

@ -16,6 +16,13 @@ public class Registry<T extends Keyed> {
this.entries = new ConcurrentHashMap<>();
}
@SafeVarargs
public Registry(T... defaultEntires) {
this();
for (T entry : defaultEntires)
register(entry);
}
/**
* Registers a new entry, only if there is no entry with the same key registered already.
* Does nothing otherwise.

View File

@ -0,0 +1,49 @@
package de.bluecolored.bluemap.core.util.stream;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.OutputStream;
public class CountingOutputStream extends DelegateOutputStream {
private long count;
public CountingOutputStream(OutputStream out) {
this(out, 0);
}
public CountingOutputStream(OutputStream out, int initialCount) {
super(out);
this.count = initialCount;
}
@Override
public void write(int b) throws IOException {
out.write(b);
count ++;
}
@Override
public void write(byte @NotNull [] b) throws IOException {
out.write(b);
count += b.length;
}
@Override
public void write(byte @NotNull [] b, int off, int len) throws IOException {
out.write(b, off, len);
count += len;
}
public long getCount() {
return count;
}
@Override
public void close() throws IOException {
count = 0;
super.close();
}
}

View File

@ -1,4 +1,4 @@
package de.bluecolored.bluemap.core.util;
package de.bluecolored.bluemap.core.util.stream;
import org.jetbrains.annotations.NotNull;

View File

@ -1,4 +1,4 @@
package de.bluecolored.bluemap.core.util;
package de.bluecolored.bluemap.core.util.stream;
import org.jetbrains.annotations.NotNull;

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.util;
package de.bluecolored.bluemap.core.util.stream;
import java.io.IOException;
import java.io.InputStream;

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.util;
package de.bluecolored.bluemap.core.util.stream;
import java.io.IOException;
import java.io.OutputStream;