BlueMap/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/PRBMWriter.java

347 lines
11 KiB
Java

/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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.
*/
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_NORMALIZED |
ATTRIBUTE_CARDINALITY_3D_VEC |
ATTRIBUTE_ENCODING_SIGNED_8BIT_INT
);
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
writeNormalizedSignedByteValue(normal.x);
writeNormalizedSignedByteValue(normal.y);
writeNormalizedSignedByteValue(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 writeNormalizedSignedByteValue(float value) throws IOException {
byte normalized = (byte) (value * 0x80 - 0.5);
out.write(normalized & 0xFF);
}
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);
}
}