mirror of https://github.com/webbukkit/dynmap.git
657 lines
27 KiB
Java
657 lines
27 KiB
Java
package org.dynmap.exporter;
|
|
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.nio.charset.Charset;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Set;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipOutputStream;
|
|
|
|
import org.dynmap.DynmapChunk;
|
|
import org.dynmap.DynmapCore;
|
|
import org.dynmap.DynmapWorld;
|
|
import org.dynmap.common.DynmapCommandSender;
|
|
import org.dynmap.hdmap.CustomBlockModel;
|
|
import org.dynmap.hdmap.HDBlockModels;
|
|
import org.dynmap.hdmap.HDBlockStateTextureMap;
|
|
import org.dynmap.hdmap.HDScaledBlockModels;
|
|
import org.dynmap.hdmap.HDShader;
|
|
import org.dynmap.hdmap.TexturePack.BlockTransparency;
|
|
import org.dynmap.renderer.CustomRenderer;
|
|
import org.dynmap.renderer.DynmapBlockState;
|
|
import org.dynmap.renderer.RenderPatch;
|
|
import org.dynmap.renderer.RenderPatchFactory.SideVisible;
|
|
import org.dynmap.utils.BlockStep;
|
|
import org.dynmap.utils.IndexedVector3D;
|
|
import org.dynmap.utils.IndexedVector3DList;
|
|
import org.dynmap.utils.MapChunkCache;
|
|
import org.dynmap.utils.MapIterator;
|
|
import org.dynmap.utils.PatchDefinition;
|
|
import org.dynmap.utils.PatchDefinitionFactory;
|
|
|
|
public class OBJExport {
|
|
private final File destZipFile; // Destination ZIP file
|
|
private final HDShader shader; // Shader to be used for textures
|
|
private final DynmapWorld world; // World to be rendered
|
|
private final DynmapCore core;
|
|
private final String basename;
|
|
private int minX, minY, minZ; // Minimum world coordinates to be rendered
|
|
private int maxX, maxY, maxZ; // Maximum world coordinates to be rendered
|
|
private static Charset UTF8 = Charset.forName("UTF-8");
|
|
private ZipOutputStream zos; // Output stream ZIP for result
|
|
private double originX, originY, originZ; // Origin for exported model
|
|
private double scale = 1.0; // Scale for exported model
|
|
private boolean centerOrigin = true; // Center at origin
|
|
private PatchDefinition[] defaultPathces; // Default patches for solid block, indexed by BlockStep.ordinal()
|
|
private HashSet<String> matIDs = new HashSet<String>(); // Set of defined material ids for RP
|
|
|
|
private static class Face {
|
|
String groupLine;
|
|
String faceLine;
|
|
}
|
|
|
|
private HashMap<String, List<Face>> facesByTexture = new HashMap<String, List<Face>>();
|
|
private static final int MODELSCALE = 16;
|
|
private static final double BLKSIZE = 1.0 / (double) MODELSCALE;
|
|
|
|
// Index of group settings
|
|
public static final int GROUP_CHUNK = 0;
|
|
public static final int GROUP_TEXTURE = 1;
|
|
public static final int GROUP_BLOCKID = 2;
|
|
public static final int GROUP_BLOCKIDMETA = 3;
|
|
public static final int GROUP_COUNT = 4;
|
|
private String[] group = new String[GROUP_COUNT];
|
|
private boolean[] enabledGroups = new boolean[GROUP_COUNT];
|
|
private String groupline = null;
|
|
|
|
// Vertex set
|
|
private IndexedVector3DList vertices;
|
|
// UV set
|
|
private IndexedVector3DList uvs;
|
|
// Scaled models
|
|
private HDScaledBlockModels models;
|
|
|
|
public static final int ROT0 = 0;
|
|
public static final int ROT90 = 1;
|
|
public static final int ROT180 = 2;
|
|
public static final int ROT270 = 3;
|
|
public static final int HFLIP = 4;
|
|
|
|
private static final double[][] pp = {
|
|
{ 0, 0, 0, 1, 0, 0, 0, 0, 1 },
|
|
{ 0, 1, 1, 1, 1, 1, 0, 1, 0 },
|
|
{ 1, 0, 0, 0, 0, 0, 1, 1, 0 },
|
|
{ 0, 0, 1, 1, 0, 1, 0, 1, 1 },
|
|
{ 0, 0, 0, 0, 0, 1, 0, 1, 0 },
|
|
{ 1, 0, 1, 1, 0, 0, 1, 1, 1 }
|
|
};
|
|
|
|
/**
|
|
* Constructor for OBJ file export
|
|
* @param dest - destination file (ZIP)
|
|
* @param shader - shader to be used for coloring/texturing
|
|
* @param world - world to be rendered
|
|
* @param core - core object
|
|
* @param basename - base file name
|
|
*/
|
|
public OBJExport(File dest, HDShader shader, DynmapWorld world, DynmapCore core, String basename) {
|
|
destZipFile = dest;
|
|
this.shader = shader;
|
|
this.world = world;
|
|
this.core = core;
|
|
this.basename = basename;
|
|
this.defaultPathces = new PatchDefinition[6];
|
|
PatchDefinitionFactory fact = HDBlockModels.getPatchDefinitionFactory();
|
|
for (BlockStep s : BlockStep.values()) {
|
|
double[] p = pp[s.getFaceEntered()];
|
|
int ord = s.ordinal();
|
|
defaultPathces[ord] = fact.getPatch(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], 0, 1, 0, 0, 1, 1, SideVisible.TOP, ord);
|
|
}
|
|
vertices = new IndexedVector3DList(new IndexedVector3DList.ListCallback() {
|
|
@Override
|
|
public void elementAdded(IndexedVector3DList list, IndexedVector3D newElement) {
|
|
try {
|
|
/* Minecraft XYZ maps to OBJ YZX */
|
|
addStringToExportedFile(String.format(Locale.US, "v %.4f %.4f %.4f\n",
|
|
(newElement.x - originX) * scale,
|
|
(newElement.y - originY) * scale,
|
|
(newElement.z - originZ) * scale
|
|
));
|
|
} catch (IOException iox) {
|
|
}
|
|
}
|
|
});
|
|
uvs = new IndexedVector3DList(new IndexedVector3DList.ListCallback() {
|
|
@Override
|
|
public void elementAdded(IndexedVector3DList list, IndexedVector3D newElement) {
|
|
try {
|
|
addStringToExportedFile(String.format(Locale.US, "vt %.4f %.4f\n", newElement.x, newElement.y));
|
|
} catch (IOException iox) {
|
|
}
|
|
}
|
|
});
|
|
// Get models
|
|
models = HDBlockModels.getModelsForScale(MODELSCALE);
|
|
}
|
|
/**
|
|
* Set render bounds
|
|
*
|
|
* @param minx - minimum X coord
|
|
* @param miny - minimum Y coord
|
|
* @param minz - minimum Z coord
|
|
* @param maxx - maximum X coord
|
|
* @param maxy - maximum Y coord
|
|
* @param maxz - maximum Z coord
|
|
*/
|
|
public void setRenderBounds(int minx, int miny, int minz, int maxx, int maxy, int maxz) {
|
|
if (minx < maxx) {
|
|
minX = minx; maxX = maxx;
|
|
}
|
|
else {
|
|
minX = maxx; maxX = minx;
|
|
}
|
|
if (miny < maxy) {
|
|
minY = miny; maxY = maxy;
|
|
}
|
|
else {
|
|
minY = maxy; maxY = miny;
|
|
}
|
|
if (minz < maxz) {
|
|
minZ = minz; maxZ = maxz;
|
|
}
|
|
else {
|
|
minZ = maxz; maxZ = minz;
|
|
}
|
|
if (minY < world.minY) minY = world.minY;
|
|
if (maxY >= world.worldheight) maxY = world.worldheight - 1;
|
|
if (centerOrigin) {
|
|
originX = (maxX + minX) / 2.0;
|
|
originY = minY;
|
|
originZ = (maxZ + minZ) / 2.0;
|
|
}
|
|
}
|
|
/**
|
|
* Set origin for exported model
|
|
* @param ox - origin x
|
|
* @param oy - origin y
|
|
* @param oz - origin z
|
|
*/
|
|
public void setOrigin(double ox, double oy, double oz) {
|
|
originX = ox;
|
|
originY = oy;
|
|
originZ = oz;
|
|
centerOrigin = false;
|
|
}
|
|
/**
|
|
* Set scale for exported model
|
|
* @param scale = scale
|
|
*/
|
|
public void setScale(double scale) {
|
|
this.scale = scale;
|
|
}
|
|
/**
|
|
* Process export
|
|
*
|
|
* @param sender - command sender: use for feedback messages
|
|
* @return true if successful, false if not
|
|
*/
|
|
public boolean processExport(DynmapCommandSender sender) {
|
|
boolean good = false;
|
|
try {
|
|
// Open ZIP file destination
|
|
zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(destZipFile)));
|
|
|
|
List<DynmapChunk> requiredChunks = new ArrayList<DynmapChunk>();
|
|
int mincx = (minX >> 4);
|
|
int maxcx = (maxX + 15) >> 4;
|
|
int mincz = (minZ >> 4);
|
|
int maxcz = (maxZ + 15) >> 4;
|
|
boolean[] edgebits = new boolean[6];
|
|
|
|
startExportedFile(basename + ".obj");
|
|
// Add material library
|
|
addStringToExportedFile("mtllib " + basename + ".mtl\n");
|
|
|
|
// Loop through - do 8x8 chunks at a time (plus 1 border each way)
|
|
for (int cx = mincx; cx <= maxcx; cx += 4) {
|
|
for (int cz = mincz; cz <= maxcz; cz += 4) {
|
|
// Build chunk cache for block of chunks
|
|
requiredChunks.clear();
|
|
for (int i = -1; i < 5; i++) {
|
|
for (int j = -1; j < 5; j++) {
|
|
if (((cx+i) <= maxcx) && ((cz+j) <= maxcz) && ((cx+i) >= mincx) && ((cz+j) >= mincz)) {
|
|
requiredChunks.add(new DynmapChunk(cx + i, cz + j));
|
|
}
|
|
}
|
|
}
|
|
// Get the chunk buffer
|
|
MapChunkCache cache = core.getServer().createMapChunkCache(world, requiredChunks, true, false, true, false);
|
|
if (cache == null) {
|
|
throw new IOException("Error loading chunk cache");
|
|
}
|
|
MapIterator iter = cache.getIterator(minX, minY, minZ);
|
|
for (int x = cx * 16; (x < (cx * 16 + 64)) && (x <= maxX); x++) {
|
|
if (x < minX) x = minX;
|
|
edgebits[BlockStep.X_PLUS.ordinal()] = (x == minX);
|
|
edgebits[BlockStep.X_MINUS.ordinal()] = (x == maxX);
|
|
for (int z = cz * 16; (z < (cz * 16 + 64)) && (z <= maxZ); z++) {
|
|
if (z < minZ) z = minZ;
|
|
edgebits[BlockStep.Z_PLUS.ordinal()] = (z == minZ);
|
|
edgebits[BlockStep.Z_MINUS.ordinal()] = (z == maxZ);
|
|
iter.initialize(x, minY, z);
|
|
updateGroup(GROUP_CHUNK, "chunk" + (x >> 4) + "_" + (z >> 4));
|
|
// Do first (bottom)
|
|
edgebits[BlockStep.Y_MINUS.ordinal()] = true;
|
|
edgebits[BlockStep.Y_PLUS.ordinal()] = false;
|
|
DynmapBlockState blk = iter.getBlockType();
|
|
if (blk.isNotAir()) { // Not air
|
|
handleBlock(blk, iter, edgebits);
|
|
}
|
|
// Do middle
|
|
edgebits[BlockStep.Y_MINUS.ordinal()] = false;
|
|
for (int y = minY + 1; y < maxY; y++) {
|
|
iter.setY(y);
|
|
blk = iter.getBlockType();
|
|
if (blk.isNotAir()) { // Not air
|
|
handleBlock(blk, iter, edgebits);
|
|
}
|
|
}
|
|
// Do top
|
|
edgebits[BlockStep.Y_PLUS.ordinal()] = true;
|
|
iter.setY(maxY);
|
|
blk = iter.getBlockType();
|
|
if (blk.isNotAir()) { // Not air
|
|
handleBlock(blk, iter, edgebits);
|
|
}
|
|
}
|
|
}
|
|
// Output faces by texture
|
|
String grp = "";
|
|
for (String material : facesByTexture.keySet()) {
|
|
List<Face> faces = facesByTexture.get(material);
|
|
matIDs.add(material); // Record material use
|
|
addStringToExportedFile(String.format("usemtl %s\n", material));
|
|
for (Face face : faces) {
|
|
if ((face.groupLine != null) && (!face.groupLine.equals(grp))) {
|
|
grp = face.groupLine;
|
|
addStringToExportedFile(grp);
|
|
}
|
|
addStringToExportedFile(face.faceLine);
|
|
}
|
|
}
|
|
// Clear face table
|
|
facesByTexture.clear();
|
|
// Clean up vertices we've moved past
|
|
vertices.resetSet(minX, minY, minZ, cx * 16 + 64, maxY, cz * 16 + 64);
|
|
}
|
|
}
|
|
finishExportedFile();
|
|
// If shader provided, add shader content to ZIP
|
|
if (shader != null) {
|
|
sender.sendMessage("Adding textures from shader " + shader.getName());
|
|
shader.exportAsMaterialLibrary(sender, this);
|
|
sender.sendMessage("Texture export completed");
|
|
}
|
|
// And close the ZIP
|
|
zos.finish();
|
|
zos.close();
|
|
zos = null;
|
|
good = true;
|
|
sender.sendMessage("Export completed - " + destZipFile.getPath());
|
|
} catch (IOException iox) {
|
|
sender.sendMessage("Export failed: " + iox.getMessage());
|
|
} finally {
|
|
if (zos != null) {
|
|
try { zos.close(); } catch (IOException e) {}
|
|
zos = null;
|
|
destZipFile.delete();
|
|
}
|
|
}
|
|
return good;
|
|
}
|
|
/**
|
|
* Start adding file to export
|
|
* @param fname - path/name of file in destination zip
|
|
* @throws IOException if error starting file
|
|
*/
|
|
public void startExportedFile(String fname) throws IOException {
|
|
ZipEntry ze = new ZipEntry(fname);
|
|
zos.putNextEntry(ze);
|
|
}
|
|
/**
|
|
* Add bytes to current exported file
|
|
* @param buf - buffer with bytes
|
|
* @param off - offset of start
|
|
* @param len - length to be added
|
|
* @throws IOException if error adding to file
|
|
*/
|
|
public void addBytesToExportedFile(byte[] buf, int off, int len) throws IOException {
|
|
zos.write(buf, off, len);
|
|
}
|
|
/**
|
|
* Add string to curent exported file (UTF-8)
|
|
* @param str - string to be written
|
|
* @throws IOException if error adding to file
|
|
*/
|
|
public void addStringToExportedFile(String str) throws IOException {
|
|
byte[] b = str.getBytes(UTF8);
|
|
zos.write(b, 0, b.length);
|
|
}
|
|
/**
|
|
* Finish adding file to export
|
|
* @throws IOException if error completing file
|
|
*/
|
|
public void finishExportedFile() throws IOException {
|
|
zos.closeEntry();
|
|
}
|
|
/**
|
|
* Handle block at current iterator coord
|
|
* @param id - block ID
|
|
* @param iter - iterator
|
|
* @param edgebits - bit N corresponds to side N being an endge (forge render)
|
|
*/
|
|
private void handleBlock(DynmapBlockState blk, MapIterator map, boolean[] edgebits) throws IOException {
|
|
BlockStep[] steps = BlockStep.values();
|
|
int[] txtidx = null;
|
|
// See if the block has a patch model
|
|
RenderPatch[] patches = models.getPatchModel(blk);
|
|
/* If no patches, see if custom model */
|
|
if(patches == null) {
|
|
CustomBlockModel cbm = models.getCustomBlockModel(blk);
|
|
if (cbm != null) { /* If so, get our meshes */
|
|
patches = cbm.getMeshForBlock(map);
|
|
}
|
|
}
|
|
if (patches != null) {
|
|
steps = new BlockStep[patches.length];
|
|
txtidx = new int[patches.length];
|
|
for (int i = 0; i < txtidx.length; i++) {
|
|
txtidx[i] = ((PatchDefinition) patches[i]).getTextureIndex();
|
|
steps[i] = ((PatchDefinition) patches[i]).step;
|
|
}
|
|
}
|
|
else { // See if volumetric
|
|
short[] smod = models.getScaledModel(blk);
|
|
if (smod != null) {
|
|
patches = getScaledModelAsPatches(smod);
|
|
steps = new BlockStep[patches.length];
|
|
txtidx = new int[patches.length];
|
|
for (int i = 0; i < patches.length; i++) {
|
|
PatchDefinition pd = (PatchDefinition) patches[i];
|
|
steps[i] = pd.step;
|
|
txtidx[i] = pd.getTextureIndex();
|
|
}
|
|
}
|
|
}
|
|
// Set block ID and ID+meta groups
|
|
updateGroup(GROUP_BLOCKID, "blk" + blk.baseState.globalStateIndex);
|
|
updateGroup(GROUP_BLOCKIDMETA, "blk" + blk.globalStateIndex);
|
|
|
|
// Get materials for patches
|
|
String[] mats = shader.getCurrentBlockMaterials(blk, map, txtidx, steps);
|
|
|
|
if (patches != null) { // Patch based model?
|
|
for (int i = 0; i < patches.length; i++) {
|
|
addPatch((PatchDefinition) patches[i], map.getX(), map.getY(), map.getZ(), mats[i]);
|
|
}
|
|
}
|
|
else {
|
|
boolean opaque = HDBlockStateTextureMap.getTransparency(blk) == BlockTransparency.OPAQUE;
|
|
for (int face = 0; face < 6; face++) {
|
|
DynmapBlockState blk2 = map.getBlockTypeAt(BlockStep.oppositeValues[face]); // Get block in direction
|
|
// If we're not solid, or adjacent block is not solid, draw side
|
|
if ((!opaque) || blk2.isAir() || edgebits[face] || (HDBlockStateTextureMap.getTransparency(blk2) != BlockTransparency.OPAQUE)) {
|
|
addPatch(defaultPathces[face], map.getX(), map.getY(), map.getZ(), mats[face]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private int[] getTextureUVs(PatchDefinition pd, int rot) {
|
|
int[] uv = new int[4];
|
|
if (rot == ROT0) {
|
|
uv[0] = uvs.getVectorIndex(pd.umin, pd.vmin, 0);
|
|
uv[1] = uvs.getVectorIndex(pd.umax, pd.vmin, 0);
|
|
uv[2] = uvs.getVectorIndex(pd.umax, pd.vmax, 0);
|
|
uv[3] = uvs.getVectorIndex(pd.umin, pd.vmax, 0);
|
|
}
|
|
else if (rot == ROT90) { // 90 degrees on texture
|
|
uv[0] = uvs.getVectorIndex(1.0 - pd.vmin, pd.umin, 0);
|
|
uv[1] = uvs.getVectorIndex(1.0 - pd.vmin, pd.umax, 0);
|
|
uv[2] = uvs.getVectorIndex(1.0 - pd.vmax, pd.umax, 0);
|
|
uv[3] = uvs.getVectorIndex(1.0 - pd.vmax, pd.umin, 0);
|
|
}
|
|
else if (rot == ROT180) { // 180 degrees on texture
|
|
uv[0] = uvs.getVectorIndex(1.0 - pd.umin, 1.0 - pd.vmin, 0);
|
|
uv[1] = uvs.getVectorIndex(1.0 - pd.umax, 1.0 - pd.vmin, 0);
|
|
uv[2] = uvs.getVectorIndex(1.0 - pd.umax, 1.0 - pd.vmax, 0);
|
|
uv[3] = uvs.getVectorIndex(1.0 - pd.umin, 1.0 - pd.vmax, 0);
|
|
}
|
|
else if (rot == ROT270) { // 270 degrees on texture
|
|
uv[0] = uvs.getVectorIndex(pd.vmin, 1.0 - pd.umin, 0);
|
|
uv[1] = uvs.getVectorIndex(pd.vmin, 1.0 - pd.umax, 0);
|
|
uv[2] = uvs.getVectorIndex(pd.vmax, 1.0 - pd.umax, 0);
|
|
uv[3] = uvs.getVectorIndex(pd.vmax, 1.0 - pd.umin, 0);
|
|
}
|
|
else if (rot == HFLIP) {
|
|
uv[0] = uvs.getVectorIndex(1.0 - pd.umin, pd.vmin, 0);
|
|
uv[1] = uvs.getVectorIndex(1.0 - pd.umax, pd.vmin, 0);
|
|
uv[2] = uvs.getVectorIndex(1.0 - pd.umax, pd.vmax, 0);
|
|
uv[3] = uvs.getVectorIndex(1.0 - pd.umin, pd.vmax, 0);
|
|
}
|
|
else {
|
|
uv[0] = uvs.getVectorIndex(pd.umin, pd.vmin, 0);
|
|
uv[1] = uvs.getVectorIndex(pd.umax, pd.vmin, 0);
|
|
uv[2] = uvs.getVectorIndex(pd.umax, pd.vmax, 0);
|
|
uv[3] = uvs.getVectorIndex(pd.umin, pd.vmax, 0);
|
|
}
|
|
return uv;
|
|
}
|
|
/**
|
|
* Add patch as face to output
|
|
*/
|
|
private void addPatch(PatchDefinition pd, double x, double y, double z, String material) throws IOException {
|
|
// No material? No face
|
|
if (material == null) {
|
|
return;
|
|
}
|
|
int rot = 0;
|
|
int rotidx = material.indexOf('@'); // Check for rotation modifier
|
|
if (rotidx >= 0) {
|
|
rot = material.charAt(rotidx+1) - '0'; // 0-3
|
|
material = material.substring(0, rotidx);
|
|
}
|
|
int[] v = new int[4];
|
|
int[] uv = getTextureUVs(pd, rot);
|
|
// Get offsets for U and V from origin
|
|
double ux = pd.xu - pd.x0;
|
|
double uy = pd.yu - pd.y0;
|
|
double uz = pd.zu - pd.z0;
|
|
double vx = pd.xv - pd.x0;
|
|
double vy = pd.yv - pd.y0;
|
|
double vz = pd.zv - pd.z0;
|
|
// Offset to origin corner
|
|
x = x + pd.x0;
|
|
y = y + pd.y0;
|
|
z = z + pd.z0;
|
|
// Origin corner, offset by umin, vmin
|
|
v[0] = vertices.getVectorIndex(x + ux*pd.umin + vx*pd.vmin, y + uy*pd.umin + vy*pd.vmin, z + uz*pd.umin + vz*pd.vmin);
|
|
uv[0] = uvs.getVectorIndex(pd.umin, pd.vmin, 0);
|
|
// Second is end of U (umax, vmin)
|
|
v[1] = vertices.getVectorIndex(x + ux*pd.umax + vx*pd.vmin, y + uy*pd.umax + vy*pd.vmin, z + uz*pd.umax + vz*pd.vmin);
|
|
uv[1] = uvs.getVectorIndex(pd.umax, pd.vmin, 0);
|
|
// Third is end of U+V (umax, vmax)
|
|
v[2] = vertices.getVectorIndex(x + ux*pd.umax + vx*pd.vmax, y + uy*pd.umax + vy*pd.vmax, z + uz*pd.umax + vz*pd.vmax);
|
|
uv[2] = uvs.getVectorIndex(pd.umax, pd.vmax, 0);
|
|
// Forth is end of V (umin, vmax)
|
|
v[3] = vertices.getVectorIndex(x + ux*pd.umin + vx*pd.vmax, y + uy*pd.umin + vy*pd.vmax, z + uz*pd.umin + vz*pd.vmax);
|
|
uv[3] = uvs.getVectorIndex(pd.umin, pd.vmax, 0);
|
|
// Add patch to file
|
|
addPatchToFile(v, uv, pd.sidevis, material, rot);
|
|
}
|
|
private void addPatchToFile(int[] v, int[] uv, SideVisible sv, String material, int rot) throws IOException {
|
|
List<Face> faces = facesByTexture.get(material);
|
|
if (faces == null) {
|
|
faces = new ArrayList<Face>();
|
|
facesByTexture.put(material, faces);
|
|
}
|
|
// If needed, rotate the UV sequence
|
|
if (rot == HFLIP) { // Flip horizonntal
|
|
int newuv[] = new int[uv.length];
|
|
for (int i = 0; i < uv.length; i++) {
|
|
newuv[i] = uv[i ^ 1];
|
|
}
|
|
uv = newuv;
|
|
}
|
|
else if (rot != ROT0) {
|
|
int newuv[] = new int[uv.length];
|
|
for (int i = 0; i < uv.length; i++) {
|
|
newuv[i] = uv[(i+4-rot) % uv.length];
|
|
}
|
|
uv = newuv;
|
|
}
|
|
Face f = new Face();
|
|
f.groupLine = updateGroup(GROUP_TEXTURE, material);
|
|
switch (sv) {
|
|
case TOP:
|
|
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
|
|
break;
|
|
case TOPFLIP:
|
|
f.faceLine += String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[2], v[2], uv[3], v[1], uv[0], v[0], uv[1]);
|
|
break;
|
|
case TOPFLIPV:
|
|
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
|
|
break;
|
|
case TOPFLIPHV:
|
|
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
|
|
break;
|
|
case BOTTOM:
|
|
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[3], v[2], uv[2], v[1], uv[1], v[0], uv[0]);
|
|
break;
|
|
case BOTH:
|
|
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
|
|
f.faceLine += String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[3], v[2], uv[2], v[1], uv[1], v[0], uv[0]);
|
|
break;
|
|
case FLIP:
|
|
f.faceLine = String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[0], uv[0], v[1], uv[1], v[2], uv[2], v[3], uv[3]);
|
|
f.faceLine += String.format("f %d/%d %d/%d %d/%d %d/%d\n", v[3], uv[2], v[2], uv[3], v[1], uv[0], v[0], uv[1]);
|
|
break;
|
|
}
|
|
faces.add(f);
|
|
}
|
|
|
|
public Set<String> getMaterialIDs() {
|
|
return matIDs;
|
|
}
|
|
|
|
private static final boolean getSubblock(short[] mod, int x, int y, int z) {
|
|
if ((x >= 0) && (x < MODELSCALE) && (y >= 0) && (y < MODELSCALE) && (z >= 0) && (z < MODELSCALE)) {
|
|
return mod[MODELSCALE*MODELSCALE*y + MODELSCALE*z + x] != 0;
|
|
}
|
|
return false;
|
|
}
|
|
// Scan along X axis
|
|
private int scanX(short[] tmod, int x, int y, int z) {
|
|
int xlen = 0;
|
|
while (getSubblock(tmod, x+xlen, y, z)) {
|
|
xlen++;
|
|
}
|
|
return xlen;
|
|
}
|
|
// Scan along Z axis for rows matching given x length
|
|
private int scanZ(short[] tmod, int x, int y, int z, int xlen) {
|
|
int zlen = 0;
|
|
while (scanX(tmod, x, y, z+zlen) >= xlen) {
|
|
zlen++;
|
|
}
|
|
return zlen;
|
|
}
|
|
// Scan along Y axis for layers matching given X and Z lengths
|
|
private int scanY(short[] tmod, int x, int y, int z, int xlen, int zlen) {
|
|
int ylen = 0;
|
|
while (scanZ(tmod, x, y+ylen, z, xlen) >= zlen) {
|
|
ylen++;
|
|
}
|
|
return ylen;
|
|
}
|
|
private void addSubblock(short[] tmod, int x, int y, int z, List<RenderPatch> list) {
|
|
// Find dimensions of cuboid
|
|
int xlen = scanX(tmod, x, y, z);
|
|
int zlen = scanZ(tmod, x, y, z, xlen);
|
|
int ylen = scanY(tmod, x, y, z, xlen, zlen);
|
|
// Add equivalent of boxblock
|
|
CustomRenderer.addBox(HDBlockModels.getPatchDefinitionFactory(), list,
|
|
BLKSIZE * x, BLKSIZE * (x+xlen),
|
|
BLKSIZE * y, BLKSIZE * (y+ylen),
|
|
BLKSIZE * z, BLKSIZE * (z+zlen),
|
|
HDBlockModels.boxPatchList);
|
|
// And remove blocks from model (since we have them covered)
|
|
for (int xx = 0; xx < xlen; xx++) {
|
|
for (int yy = 0; yy < ylen; yy++) {
|
|
for (int zz = 0; zz < zlen; zz++) {
|
|
tmod[MODELSCALE*MODELSCALE*(y+yy) + MODELSCALE*(z+zz) + (x+xx)] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private PatchDefinition[] getScaledModelAsPatches(short[] mod) {
|
|
ArrayList<RenderPatch> list = new ArrayList<RenderPatch>();
|
|
short[] tmod = Arrays.copyOf(mod, mod.length); // Make copy
|
|
for (int y = 0; y < MODELSCALE; y++) {
|
|
for (int z = 0; z < MODELSCALE; z++) {
|
|
for (int x = 0; x < MODELSCALE; x++) {
|
|
if (getSubblock(tmod, x, y, z)) { // If occupied, try to add to list
|
|
addSubblock(tmod, x, y, z, list);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
PatchDefinition[] pd = new PatchDefinition[list.size()];
|
|
for (int i = 0; i < pd.length; i++) {
|
|
pd[i] = (PatchDefinition) list.get(i);
|
|
}
|
|
return pd;
|
|
}
|
|
|
|
private String updateGroup(int grpIndex, String newgroup) {
|
|
if (enabledGroups[grpIndex]) {
|
|
if (!newgroup.equals(group[grpIndex])) {
|
|
group[grpIndex] = newgroup;
|
|
String newline = "g";
|
|
for (int i = 0; i < GROUP_COUNT; i++) {
|
|
if (enabledGroups[i]) {
|
|
newline += " " + group[i];
|
|
}
|
|
}
|
|
newline += "\n";
|
|
groupline = newline;
|
|
}
|
|
}
|
|
return groupline;
|
|
}
|
|
|
|
public boolean getGroupEnabled(int grpIndex) {
|
|
if (grpIndex < enabledGroups.length) {
|
|
return enabledGroups[grpIndex];
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
public void setGroupEnabled(int grpIndex, boolean set) {
|
|
if (grpIndex < enabledGroups.length) {
|
|
enabledGroups[grpIndex] = set;
|
|
}
|
|
}
|
|
public String getBaseName() {
|
|
return basename;
|
|
}
|
|
}
|