/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.scene.plugins.blender.textures; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingVolume; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType; import com.jme3.util.BufferUtils; /** * This class is used for UV coordinates generation. * * @author Marcin Roguski (Kaelthas) */ public class UVCoordinatesGenerator { private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName()); public static enum UVCoordinatesType { TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128), TEXCO_STICKY(256), TEXCO_OSA(512), TEXCO_WINDOW(1024), NEED_UV(2048), TEXCO_TANGENT(4096), TEXCO_PARTICLE_OR_STRAND(8192), //TEXCO_PARTICLE (since blender 2.6x) has also the value of: 8192 but is used for halo materials instead of normal materials TEXCO_STRESS(16384), TEXCO_SPEED(32768); public final int blenderValue; private UVCoordinatesType(int blenderValue) { this.blenderValue = blenderValue; } public static UVCoordinatesType valueOf(int blenderValue) { for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) { if (coordinatesType.blenderValue == blenderValue) { return coordinatesType; } } return null; } } /** * Generates a UV coordinates for 2D texture. * * @param mesh * the mesh we generate UV's for * @param texco * UV coordinates type * @param projection * projection type * @param geometries * the geometris the given mesh belongs to (required to compute * bounding box) * @return UV coordinates for the given mesh */ public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, Geometry geometries) { List<Vector2f> result = new ArrayList<Vector2f>(); BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); float[] inputData = null;// positions, normals, reflection vectors, etc. switch (texco) { case TEXCO_ORCO: inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); break; case TEXCO_UV:// this should be used if not defined by user explicitly Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; for (int i = 0; i < mesh.getVertexCount(); ++i) { result.add(data[i % 3]); } break; case TEXCO_NORM: inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); break; case TEXCO_REFL: case TEXCO_GLOB: case TEXCO_TANGENT: case TEXCO_STRESS: case TEXCO_LAVECTOR: case TEXCO_OBJECT: case TEXCO_OSA: case TEXCO_PARTICLE_OR_STRAND: case TEXCO_SPEED: case TEXCO_STICKY: case TEXCO_VIEW: case TEXCO_WINDOW: LOGGER.warning("Texture coordinates type not currently supported: " + texco); break; default: throw new IllegalStateException("Unknown texture coordinates value: " + texco); } if (inputData != null) {// make projection calculations switch (projection) { case PROJECTION_FLAT: inputData = UVProjectionGenerator.flatProjection(inputData, bb); break; case PROJECTION_CUBE: inputData = UVProjectionGenerator.cubeProjection(inputData, bb); break; case PROJECTION_TUBE: BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); inputData = UVProjectionGenerator.tubeProjection(inputData, bt); break; case PROJECTION_SPHERE: BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); inputData = UVProjectionGenerator.sphereProjection(inputData, bs); break; default: throw new IllegalStateException("Unknown projection type: " + projection); } for (int i = 0; i < inputData.length; i += 2) { result.add(new Vector2f(inputData[i], inputData[i + 1])); } } return result; } /** * Generates a UV coordinates for 3D texture. * * @param mesh * the mesh we generate UV's for * @param texco * UV coordinates type * @param coordinatesSwappingIndexes * coordinates swapping indexes * @param geometries * the geometris the given mesh belongs to (required to compute * bounding box) * @return UV coordinates for the given mesh */ public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, Geometry... geometries) { List<Vector3f> result = new ArrayList<Vector3f>(); BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); float[] inputData = null;// positions, normals, reflection vectors, etc. switch (texco) { case TEXCO_ORCO: inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); break; case TEXCO_UV: Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; for (int i = 0; i < mesh.getVertexCount(); ++i) { Vector2f uv = data[i % 3]; result.add(new Vector3f(uv.x, uv.y, 0)); } break; case TEXCO_NORM: inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); break; case TEXCO_REFL: case TEXCO_GLOB: case TEXCO_TANGENT: case TEXCO_STRESS: case TEXCO_LAVECTOR: case TEXCO_OBJECT: case TEXCO_OSA: case TEXCO_PARTICLE_OR_STRAND: case TEXCO_SPEED: case TEXCO_STICKY: case TEXCO_VIEW: case TEXCO_WINDOW: LOGGER.warning("Texture coordinates type not currently supported: " + texco); break; default: throw new IllegalStateException("Unknown texture coordinates value: " + texco); } if (inputData != null) {// make calculations Vector3f min = bb.getMin(null); float[] uvCoordsResults = new float[4];// used for coordinates swapping float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; for (int i = 0; i < ext.length; ++i) { if (ext[i] == 0) { ext[i] = 1; } } // now transform the coordinates so that they are in the range of // <0; 1> for (int i = 0; i < inputData.length; i += 3) { uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); } } return result; } /** * This method should be used to determine if the texture will ever be * computed. If the texture coordinates are not supported then the try of * flattening the texture might result in runtime exceptions occurence. * * @param texco * the texture coordinates type * @return <b>true</b> if the type is supported and false otherwise */ public static boolean isTextureCoordinateTypeSupported(UVCoordinatesType texco) { switch (texco) { case TEXCO_ORCO: case TEXCO_UV: case TEXCO_NORM: return true; case TEXCO_REFL: case TEXCO_GLOB: case TEXCO_TANGENT: case TEXCO_STRESS: case TEXCO_LAVECTOR: case TEXCO_OBJECT: case TEXCO_OSA: case TEXCO_PARTICLE_OR_STRAND: case TEXCO_SPEED: case TEXCO_STICKY: case TEXCO_VIEW: case TEXCO_WINDOW: return false; default: throw new IllegalStateException("Unknown texture coordinates value: " + texco); } } /** * This method returns the bounding box of the given geometries. * * @param geometries * the list of geometries * @return bounding box of the given geometries */ public static BoundingBox getBoundingBox(Geometry... geometries) { BoundingBox result = null; for (Geometry geometry : geometries) { geometry.updateModelBound(); BoundingVolume bv = geometry.getModelBound(); if (bv instanceof BoundingBox) { return (BoundingBox) bv; } else if (bv instanceof BoundingSphere) { BoundingSphere bs = (BoundingSphere) bv; float r = bs.getRadius(); return new BoundingBox(bs.getCenter(), r, r, r); } else { throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); } } return result; } /** * This method returns the bounding sphere of the given geometries. * * @param geometries * the list of geometries * @return bounding sphere of the given geometries */ /* package */static BoundingSphere getBoundingSphere(Geometry... geometries) { BoundingSphere result = null; for (Geometry geometry : geometries) { geometry.updateModelBound(); BoundingVolume bv = geometry.getModelBound(); if (bv instanceof BoundingBox) { BoundingBox bb = (BoundingBox) bv; float r = Math.max(bb.getXExtent(), bb.getYExtent()); r = Math.max(r, bb.getZExtent()); return new BoundingSphere(r, bb.getCenter()); } else if (bv instanceof BoundingSphere) { return (BoundingSphere) bv; } else { throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); } } return result; } /** * This method returns the bounding tube of the given geometries. * * @param geometries * the list of geometries * @return bounding tube of the given geometries */ /* package */static BoundingTube getBoundingTube(Geometry... geometries) { BoundingTube result = null; for (Geometry geometry : geometries) { BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry); Vector3f max = bb.getMax(null); Vector3f min = bb.getMin(null); float radius = Math.max(max.x - min.x, max.y - min.y) * 0.5f; BoundingTube bt = new BoundingTube(radius, max.z - min.z, bb.getCenter()); if (result == null) { result = bt; } else { result.merge(bt); } } return result; } /** * A very simple bounding tube. It holds only the basic data bout the * bounding tube and does not provide full functionality of a * BoundingVolume. Should be replaced with a bounding tube that extends the * BoundingVolume if it is ever created. * * @author Marcin Roguski (Kaelthas) */ /* package */static class BoundingTube { private float radius; private float height; private Vector3f center; /** * Constructor creates the tube with the given params. * * @param radius * the radius of the tube * @param height * the height of the tube * @param center * the center of the tube */ public BoundingTube(float radius, float height, Vector3f center) { this.radius = radius; this.height = height; this.center = center; } /** * This method merges two bounding tubes. * * @param boundingTube * bounding tube to be merged woth the current one * @return new instance of bounding tube representing the tubes' merge */ public BoundingTube merge(BoundingTube boundingTube) { // get tubes (tube1.radius >= tube2.radius) BoundingTube tube1, tube2; if (radius >= boundingTube.radius) { tube1 = this; tube2 = boundingTube; } else { tube1 = boundingTube; tube2 = this; } float r1 = tube1.radius; float r2 = tube2.radius; float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f); float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f); float height = maxZ - minZ; Vector3f distance = tube2.center.subtract(tube1.center); Vector3f center = tube1.center.add(distance.mult(0.5f)); distance.z = 0;// projecting this vector on XY plane float d = distance.length(); // d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the // inside // d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1 float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f; return new BoundingTube(radius, height, center); } /** * @return the radius of the tube */ public float getRadius() { return radius; } /** * @return the height of the tube */ public float getHeight() { return height; } /** * @return the center of the tube */ public Vector3f getCenter() { return center; } } }