
com.irurueta.geometry.io.MeshWriterJson Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of irurueta-geometry-io Show documentation
Show all versions of irurueta-geometry-io Show documentation
Reads, writes and converts 3D files into different formats
The newest version!
/*
* Copyright (C) 2012 Alberto Irurueta Carro ([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.irurueta.geometry.io;
import org.apache.commons.codec.binary.Base64;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
public class MeshWriterJson extends MeshWriter {
/**
* Indicates if textures must be embedded into resulting file.
*/
public static final boolean DEFAULT_EMBED_TEXTURES = true;
/**
* Indicates if by default a URL indicating where the texture can be located
* should be included into resulting file.
*/
public static final boolean DEFAULT_USE_REMOTE_TEXTURE_URL = false;
/**
* Indicates if by default an identifier for the texture should be included
* into resulting file so that texture image can be fetched by some other
* mean.
*/
public static final boolean DEFAULT_USE_REMOTE_TEXTURE_ID = false;
/**
* Indicates charset to use in resulting JSON file. By default, this will be
* UTF-8.
*/
private Charset charset;
/**
* Writer to write resulting JSON into output stream.
*/
private BufferedWriter writer;
/**
* Counter for the number of textures that have been read.
*/
private int textureCounter;
/**
* Indicates if textures will be embedded into resulting JSON stream of
* data. When embedding textures their stream of bytes is written using
* BASE64 to the output stream.
*/
private boolean embedTexturesEnabled;
/**
* Indicates if a URL indicating where the texture can be located should
* be included into resulting file.
*/
private boolean remoteTextureUrlEnabled;
/**
* Indicates if an identifier for the texture should be included into
* resulting file so that texture image can be fetched by some other mean.
*/
private boolean remoteTextureIdEnabled;
/**
* Constructor.
*
* @param loader loader to load a 3D file.
* @param stream stream where trans-coded data will be written to.
*/
public MeshWriterJson(final Loader loader, final OutputStream stream) {
super(loader, stream);
charset = null;
embedTexturesEnabled = DEFAULT_EMBED_TEXTURES;
remoteTextureUrlEnabled = DEFAULT_USE_REMOTE_TEXTURE_URL;
remoteTextureIdEnabled = DEFAULT_USE_REMOTE_TEXTURE_ID;
}
/**
* Constructor.
*
* @param loader loader to load a 3D file.
* @param stream stream where trans-coded data will be written to.
* @param listener listener to be notified of progress changes or when
* transcoding process starts or finishes.
*/
public MeshWriterJson(final Loader loader, final OutputStream stream, final MeshWriterListener listener) {
super(loader, stream, listener);
charset = null;
embedTexturesEnabled = DEFAULT_EMBED_TEXTURES;
remoteTextureUrlEnabled = DEFAULT_USE_REMOTE_TEXTURE_URL;
remoteTextureIdEnabled = DEFAULT_USE_REMOTE_TEXTURE_ID;
}
/**
* Returns charset to use in resulting JSON file. By default, this will be
* UTF-8.
*
* @return charset to use in resulting JSON file.
*/
public Charset getCharset() {
return charset;
}
/**
* Sets charset to use in resulting JSON file. By default, this will be UTF-8.
*
* @param charset charset to use in resulting JSON file.
* @throws LockedException if this mesh writer is locked processing a file.
*/
public void setCharset(final Charset charset) throws LockedException {
if (isLocked()) {
throw new LockedException();
}
this.charset = charset;
}
/**
* Indicates if default charset will be used or not.
*
* @return true if default charset (UTF-8) will be used, false otherwise.
*/
public boolean isDefaultCharsetBeingUsed() {
return (charset == null);
}
/**
* Indicates if textures are embedded into resulting JSON or not.
* When embedding textures their stream of bytes is written using BASE64 to
* the output stream.
*
* @return true if textures are embedded into resulting JSON, false
* otherwise.
*/
public boolean isEmbedTexturesEnabled() {
return embedTexturesEnabled;
}
/**
* Specified whether textures are embedded into resulting JSON or not.
* When embedding textures their stream of bytes is written using BASE64 to
* the output stream.
*
* @param embedTexturesEnabled true if textures will be embedded into
* resulting JSON, false.
* @throws LockedException if this mesh writer is locked processing a file.
*/
public void setEmbedTexturedEnabled(final boolean embedTexturesEnabled) throws LockedException {
if (isLocked()) {
throw new LockedException();
}
this.embedTexturesEnabled = embedTexturesEnabled;
}
/**
* Indicates if a URL indicating where the texture can be located should
* be included into resulting file.
*
* @return true if a URL indicating where the texture can be located will
* be included into resulting file, false otherwise.
*/
public boolean isRemoteTextureUrlEnabled() {
return remoteTextureUrlEnabled;
}
/**
* Specifies whether a URL indicating where the texture can be located
* should be included into resulting file.
*
* @param remoteTextureUrlEnabled true if a URL indicating where the texture
* can be located will be included into resulting file, false otherwise.
* @throws LockedException if this mesh writer is locked processing a file.
*/
public void setRemoteTextureUrlEnabled(final boolean remoteTextureUrlEnabled) throws LockedException {
if (isLocked()) {
throw new LockedException();
}
this.remoteTextureUrlEnabled = remoteTextureUrlEnabled;
}
/**
* Indicates if an identifier for the texture should be included into
* resulting file so that texture image can be fetched by some other mean.
*
* @return true if an identifier for the texture should be included into.
* resulting file so that texture image can be fetched by some other mean.
*/
public boolean isRemoteTextureIdEnabled() {
return remoteTextureIdEnabled;
}
/**
* Specifies whether an identifier for the texture should be included into
* resulting file so that texture image can be fetched by some other mean.
*
* @param remoteTextureIdEnabled true if identifier for the texture should
* be included into resulting file.
* @throws LockedException if this mesh writer is locked processing file.
*/
public void setRemoteTextureIdEnabled(final boolean remoteTextureIdEnabled) throws LockedException {
if (isLocked()) {
throw new LockedException();
}
this.remoteTextureIdEnabled = remoteTextureIdEnabled;
}
/**
* Processes input file provided to loader and writes it trans-coded into
* output stream.
*
* @throws LoaderException if 3D file loading fails.
* @throws IOException if an I/O error occurs.
* @throws NotReadyException if mesh writer is not ready because either a
* loader has not been provided or an output stream has not been provided.
* @throws LockedException if this mesh writer is locked processing a file.
*/
@SuppressWarnings("DuplicatedCode")
@Override
public void write() throws LoaderException, IOException, NotReadyException, LockedException {
if (!isReady()) {
throw new NotReadyException();
}
if (isLocked()) {
throw new LockedException();
}
try {
if (charset == null) {
// use default charset
writer = new BufferedWriter(new OutputStreamWriter(stream));
} else {
// use provided charset
writer = new BufferedWriter(new OutputStreamWriter(stream, charset));
}
locked = true;
textureCounter = 0;
if (listener != null) {
listener.onWriteStart(this);
}
loader.setListener(this.internalListeners);
writer.write("{\"textures\":[");
final var iter = loader.load();
var minX = Float.MAX_VALUE;
var minY = Float.MAX_VALUE;
var minZ = Float.MAX_VALUE;
var maxX = -Float.MAX_VALUE;
var maxY = -Float.MAX_VALUE;
var maxZ = -Float.MAX_VALUE;
// write array opening
writer.write("],\"chunks\":[");
// indicate that no more textures can follow
ignoreTextureValidation = true;
while (iter.hasNext()) {
final var chunk = iter.next();
final var coords = chunk.getVerticesCoordinatesData();
final var colors = chunk.getColorData();
final var indices = chunk.getIndicesData();
final var textureCoords = chunk.getTextureCoordinatesData();
final var normals = chunk.getNormalsData();
final var material = chunk.getMaterial();
final var coordsAvailable = (coords != null);
final var colorsAvailable = (colors != null);
final var indicesAvailable = (indices != null);
final var textureCoordsAvailable = (textureCoords != null);
final var normalsAvailable = (normals != null);
var hasPreviousContent = false;
if (chunk.getMinX() < minX) {
minX = chunk.getMinX();
}
if (chunk.getMinY() < minY) {
minY = chunk.getMinY();
}
if (chunk.getMinZ() < minZ) {
minZ = chunk.getMinZ();
}
if (chunk.getMaxX() > maxX) {
maxX = chunk.getMaxX();
}
if (chunk.getMaxY() > maxY) {
maxY = chunk.getMaxY();
}
if (chunk.getMaxZ() > maxZ) {
maxZ = chunk.getMaxZ();
}
//write chunk opening
writer.write("{");
// CHUNK CONTENTS
if (material != null) {
// write material
writeMaterial(material);
hasPreviousContent = true;
}
if (indicesAvailable) {
// write separator for next piece of data
if (hasPreviousContent) {
writer.write(",");
}
// write indices opening
writer.write("\"indices\":[");
for (var i = 0; i < indices.length; i++) {
writer.write(Integer.toString(indices[i]));
// write separator if more elements in array
if (i < (indices.length - 1)) {
writer.write(",");
}
}
// write indices closing
writer.write("]");
hasPreviousContent = true;
}
if (normalsAvailable) {
// write separator for next piece of data
if (hasPreviousContent) {
writer.write(",");
}
// write normals opening
writer.write("\"vertexNormals\":[");
for (var i = 0; i < normals.length; i++) {
if (Float.isInfinite(normals[i]) || Float.isNaN(normals[i])) {
writer.write(Float.toString(Float.MAX_VALUE));
} else {
writer.write(Float.toString(normals[i]));
}
// write separator if more elements in array
if (i < (normals.length - 1)) {
writer.write(",");
}
}
// write normals closing
writer.write("]");
hasPreviousContent = true;
}
if (coordsAvailable) {
// write separator for next piece of data
if (hasPreviousContent) {
writer.write(",");
}
// write coords opening
writer.write("\"vertexPositions\":[");
for (var i = 0; i < coords.length; i++) {
if (Float.isInfinite(coords[i]) || Float.isNaN(coords[i])) {
writer.write(Float.toString(Float.MAX_VALUE));
} else {
writer.write(Float.toString(coords[i]));
}
// write separator if more elements in array
if (i < (coords.length - 1)) {
writer.write(",");
}
}
// write coords closing
writer.write("]");
hasPreviousContent = true;
}
if (textureCoordsAvailable) {
// write separator for next piece of data
if (hasPreviousContent) {
writer.write(",");
}
// write texture coords opening
writer.write("\"vertexTextureCoords\":[");
for (var i = 0; i < textureCoords.length; i++) {
if (Float.isInfinite(textureCoords[i]) || Float.isNaN(textureCoords[i])) {
writer.write(Float.toString(Float.MAX_VALUE));
} else {
writer.write(Float.toString(textureCoords[i]));
}
// write separator if more elements in array
if (i < (textureCoords.length - 1)) {
writer.write(",");
}
}
// write texture coords closing
writer.write("]");
hasPreviousContent = true;
}
if (coordsAvailable) {
// write separator for next piece of data
writer.write(",");
// write min corner
writer.write("\"minCorner\":[" + chunk.getMinX() + "," + chunk.getMinY() + ","
+ chunk.getMinZ() + "],");
// write max corner
writer.write("\"maxCorner\":[" + chunk.getMaxX() + "," + chunk.getMaxY() + ","
+ chunk.getMaxZ() + "]");
}
if (colorsAvailable) {
// write separator for next piece of data
if (hasPreviousContent) {
writer.write(",");
}
// write colors opening
writer.write("\"vertexColors\":[");
for (var i = 0; i < colors.length; i++) {
writer.write(Short.toString(colors[i]));
if (i < (colors.length - 1)) {
writer.write(",");
}
}
// write colors closing and color components
writer.write("],\"colorComponents\": " + chunk.getColorComponents());
}
// write chunk closing
writer.write("}");
// write chunk separator if more chunks are available
if (iter.hasNext()) {
writer.write(",");
}
writer.flush();
}
// write array closing
writer.write("],");
// write bounding box for all chunks
// write min corner
writer.write("\"minCorner\":[" + minX + "," + minY + "," + minZ + "],");
// write max corner
writer.write("\"maxCorner\":[" + maxX + "," + maxY + "," + maxZ + "]");
// write object closing
writer.write("}");
writer.flush();
if (listener != null) {
listener.onWriteEnd(this);
}
locked = false;
} catch (final LoaderException | IOException e) {
throw e;
} catch (final Exception e) {
throw new LoaderException(e);
}
}
/**
* Processes texture file. By reading provided texture file that has been
* created in a temporal location and embedding it into resulting output
* stream.
*
* @param texture reference to texture that uses texture image
* @param textureFile file containing texture image. File will usually be
* created in a temporal location.
* @throws IOException if an I/O error occurs
*/
@Override
protected void processTextureFile(final Texture texture, final File textureFile) throws IOException {
if (textureCounter > 0) {
writer.write(",");
}
writer.write("{\"id\":" + texture.getId());
writer.write(",\"width\":" + texture.getWidth());
writer.write(",\"height\":" + texture.getHeight());
if (listener instanceof MeshWriterJsonListener listener2) {
if (remoteTextureUrlEnabled) {
final String remoteUrl = listener2.onRemoteTextureUrlRequested(this, texture, textureFile);
if (remoteUrl != null) {
// add url to json
writer.write(",\"remoteUrl\":\"" + remoteUrl + "\"");
}
}
if (remoteTextureIdEnabled) {
final var remoteId = listener2.onRemoteTextureIdRequested(this, texture, textureFile);
if (remoteId != null) {
// add id to json
writer.write(",\"remoteId\": \"" + remoteId + "\"");
}
}
}
if (embedTexturesEnabled) {
writer.write(",\"data\":\"");
// write texture file data as base64
// flush to sync writer and stream
writer.flush();
try (final var textureStream = Files.newInputStream(textureFile.toPath())) {
final var imageData = new byte[(int) textureFile.length()];
if (textureStream.read(imageData) > 0) {
// convert image byte array into Base64 string
//TODO: could we use a Base64OutputStream to reduce memory usage,
// but doing the same replacement for safe json generation?
String base64 = Base64.encodeBase64String(imageData);
base64 = base64.replace("/", "\\/");
writer.write(base64);
writer.flush();
}
}
writer.write("\"");
}
writer.write("}");
textureCounter++;
}
/**
* Writes material into output JSON file.
*
* @param material material to be written.
* @throws IOException if an I/O error occurs.
*/
private void writeMaterial(final Material material) throws IOException {
writer.write("\"material\":{");
writer.write("\"id\":" + material.getId());
if (material.isAmbientColorAvailable()) {
writer.write(",\"ambientColor\":[" + material.getAmbientRedColor() + ","
+ material.getAmbientGreenColor() + "," + material.getAmbientBlueColor() + "]");
}
if (material.isDiffuseColorAvailable()) {
writer.write(",\"diffuseColor\":[" + material.getDiffuseRedColor() + ","
+ material.getDiffuseGreenColor() + "," + material.getDiffuseBlueColor() + "]");
}
if (material.isSpecularColorAvailable()) {
writer.write(",\"specularColor\":[" + material.getSpecularRedColor() + ","
+ material.getSpecularGreenColor() + "," + material.getSpecularBlueColor() + "]");
}
if (material.isSpecularCoefficientAvailable()) {
writer.write(",\"specularCoefficient\":" + material.getSpecularCoefficient());
}
if (material.isAmbientTextureMapAvailable()) {
final var tex = material.getAmbientTextureMap();
writer.write(",\"ambientTextureId\":" + tex.getId());
}
if (material.isDiffuseTextureMapAvailable()) {
final var tex = material.getDiffuseTextureMap();
writer.write(",\"diffuseTextureId\":" + tex.getId());
}
if (material.isSpecularTextureMapAvailable()) {
final var tex = material.getSpecularTextureMap();
writer.write(",\"specularTextureId\":" + tex.getId());
}
if (material.isAlphaTextureMapAvailable()) {
final var tex = material.getAlphaTextureMap();
writer.write(",\"alphaTextureId\":" + tex.getId());
}
if (material.isBumpTextureMapAvailable()) {
final var tex = material.getBumpTextureMap();
writer.write(",\"bumpTextureId\":" + tex.getId());
}
if (material.isTransparencyAvailable()) {
writer.write(",\"transparency\":" + material.getTransparency());
}
if (material.isIlluminationAvailable()) {
writer.write(",\"illumination\":\"" + material.getIllumination().name() + "\"");
}
writer.write("}");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy