/******************************************************************************* * Copyright 2011 See AUTHORS file. * * 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.badlogic.gdx.graphics.g3d.decals; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.NumberUtils; /** * <p/> * Represents a sprite in 3d space. Typical 3d transformations such as translation, rotation and scaling are supported. * The position includes a z component other than setting the depth no manual layering has to be performed, correct * overlay is guaranteed by using the depth buffer. * <p/> * Decals are handled by the {@link DecalBatch}. */ public class Decal { // 3(x,y,z) + 1(color) + 2(u,v) /** Size of a decal vertex in floats */ private static final int VERTEX_SIZE = 3 + 1 + 2; /** Size of the decal in floats. It takes a float[SIZE] to hold the decal. */ public static final int SIZE = 4 * VERTEX_SIZE; /** Temporary vector for various calculations. */ private static Vector3 tmp = new Vector3(); private static Vector3 tmp2 = new Vector3(); /** Set a multipurpose value which can be queried and used for things like group identification. */ public int value; protected float[] vertices = new float[SIZE]; protected Vector3 position = new Vector3(); protected Quaternion rotation = new Quaternion(); protected Vector2 scale = new Vector2(1, 1); /** * The transformation offset can be used to change the pivot point for rotation and scaling. By default the pivot is * the middle of the decal. */ public Vector2 transformationOffset = null; protected Vector2 dimensions = new Vector2(); protected DecalMaterial material = new DecalMaterial(); protected boolean updated = false; protected Decal() { } /** * Sets the color of all four vertices to the specified color * * @param r * Red component * @param g * Green component * @param b * Blue component * @param a * Alpha component */ public void setColor(float r, float g, float b, float a) { int intBits = ((int) (255 * a) << 24) | ((int) (255 * b) << 16) | ((int) (255 * g) << 8) | ((int) (255 * r)); float color = NumberUtils.intToFloatColor(intBits); vertices[C1] = color; vertices[C2] = color; vertices[C3] = color; vertices[C4] = color; } /** * Sets the rotation on the local X axis to the specified angle * * @param angle * Angle in degrees to set rotation to */ public void setRotationX(float angle) { rotation.set(X_AXIS, angle); updated = false; } /** * Sets the rotation on the local Y axis to the specified angle * * @param angle * Angle in degrees to set rotation to */ public void setRotationY(float angle) { rotation.set(Y_AXIS, angle); updated = false; } /** * Sets the rotation on the local Z axis to the specified angle * * @param angle * Angle in degrees to set rotation to */ public void setRotationZ(float angle) { rotation.set(Z_AXIS, angle); updated = false; } /** * Rotates along local X axis by the specified angle * * @param angle * Angle in degrees to rotate by */ public void rotateX(float angle) { rotator.set(X_AXIS, angle); rotation.mul(rotator); updated = false; } /** * Rotates along local Y axis by the specified angle * * @param angle * Angle in degrees to rotate by */ public void rotateY(float angle) { rotator.set(Y_AXIS, angle); rotation.mul(rotator); updated = false; } /** * Rotates along local Z axis by the specified angle * * @param angle * Angle in degrees to rotate by */ public void rotateZ(float angle) { rotator.set(Z_AXIS, angle); rotation.mul(rotator); updated = false; } /** * Sets the rotation of this decal based on the (normalized) direction and up vector. * * @param dir * the direction vector * @param up * the up vector */ public void setRotation(Vector3 dir, Vector3 up) { tmp.set(up).crs(dir).nor(); tmp2.set(dir).crs(tmp).nor(); rotation.setFromAxes(tmp.x, tmp2.x, dir.x, tmp.y, tmp2.y, dir.y, tmp.z, tmp2.z, dir.z); updated = false; } /** * Returns the rotation. The returned quaternion should under no circumstances be modified. * * @return Quaternion representing the rotation */ public Quaternion getRotation() { return rotation; } /** * Moves by the specified amount of units along the x axis * * @param units * Units to move the decal */ public void translateX(float units) { this.position.x += units; updated = false; } /** * Sets the position on the x axis * * @param x * Position to locate the decal at */ public void setX(float x) { this.position.x = x; updated = false; } /** @return position on the x axis */ public float getX() { return this.position.x; } /** * Moves by the specified amount of units along the y axis * * @param units * Units to move the decal */ public void translateY(float units) { this.position.y += units; updated = false; } /** * Sets the position on the y axis * * @param y * Position to locate the decal at */ public void setY(float y) { this.position.y = y; updated = false; } /** @return position on the y axis */ public float getY() { return this.position.y; } /** * Moves by the specified amount of units along the z axis * * @param units * Units to move the decal */ public void translateZ(float units) { this.position.z += units; updated = false; } /** * Sets the position on the z axis * * @param z * Position to locate the decal at */ public void setZ(float z) { this.position.z = z; updated = false; } /** @return position on the z axis */ public float getZ() { return this.position.z; } /** * Translates by the specified amount of units * * @param x * Units to move along the x axis * @param y * Units to move along the y axis * @param z * Units to move along the z axis */ public void translate(float x, float y, float z) { this.position.add(x, y, z); updated = false; } /** * Sets the position to the given world coordinates * * @param x * X position * @param y * Y Position * @param z * Z Position */ public void setPosition(float x, float y, float z) { this.position.set(x, y, z); updated = false; } /** * Returns the position of this decal. The returned vector should under no circumstances be modified. * * @return vector representing the position */ public Vector3 getPosition() { return position; } /** * Sets scale along the x axis * * @param scale * New scale along x axis */ public void setScaleX(float scale) { this.scale.x = scale; updated = false; } /** @return Scale on the x axis */ public float getScaleX() { return this.scale.x; } /** * Sets scale along the y axis * * @param scale * New scale along y axis */ public void setScaleY(float scale) { this.scale.y += scale; updated = false; } /** @return Scale on the y axis */ public float getScaleY() { return this.scale.y; } /** * Sets scale along both the x and y axis * * @param scaleX * Scale on the x axis * @param scaleY * Scale on the y axis */ public void setScale(float scaleX, float scaleY) { this.scale.set(scaleX, scaleY); updated = false; } /** * Sets scale along both the x and y axis * * @param scale * New scale */ public void setScale(float scale) { this.scale.set(scale, scale); updated = false; } /** * Sets the width in world units * * @param width * Width in world units */ public void setWidth(float width) { this.dimensions.x = width; updated = false; } /** @return width in world units */ public float getWidth() { return this.dimensions.x; } /** * Sets the height in world units * * @param height * Height in world units */ public void setHeight(float height) { this.dimensions.y = height; updated = false; } /** @return height in world units */ public float getHeight() { return dimensions.y; } /** * Sets the width and height in world units * * @param width * Width in world units * @param height * Height in world units */ public void setDimensions(float width, float height) { dimensions.set(width, height); updated = false; } /** * Returns the vertices backing this sprite.<br/> * The returned value should under no circumstances be modified. * * @return vertex array backing the decal */ public float[] getVertices() { return vertices; } /** Recalculates vertices array if it grew out of sync with the properties (position, ..) */ protected void update() { if (!updated) { resetVertices(); transformVertices(); } } /** Transforms the position component of the vertices using properties such as position, scale, etc. */ protected void transformVertices() { /** * It would be possible to also load the x,y,z into a Vector3 and apply all the transformations using already * existing methods. Especially the quaternion rotation already exists in the Quaternion class, it then would * look like this: * ---------------------------------------------------------------------------------------------------- * v3.set(vertices[xIndex] * scale.x, vertices[yIndex] * scale.y, vertices[zIndex]); rotation.transform(v3); * v3.add(position); vertices[xIndex] = v3.x; vertices[yIndex] = v3.y; vertices[zIndex] = v3.z; * ---------------------------------------------------------------------------------------------------- However, * a half ass benchmark with dozens of thousands decals showed that doing it "by hand", as done here, is about * 10% faster. So while duplicate code should be avoided for maintenance reasons etc. the performance gain is * worth it. The math doesn't change. */ float x, y, z, w; float tx, ty; if (transformationOffset != null) { tx = -transformationOffset.x; ty = -transformationOffset.y; } else { tx = ty = 0; } /** Transform the first vertex */ // first apply the scale to the vector x = (vertices[X1] + tx) * scale.x; y = (vertices[Y1] + ty) * scale.y; z = vertices[Z1]; // then transform the vector using the rotation quaternion vertices[X1] = rotation.w * x + rotation.y * z - rotation.z * y; vertices[Y1] = rotation.w * y + rotation.z * x - rotation.x * z; vertices[Z1] = rotation.w * z + rotation.x * y - rotation.y * x; w = -rotation.x * x - rotation.y * y - rotation.z * z; rotation.conjugate(); x = vertices[X1]; y = vertices[Y1]; z = vertices[Z1]; vertices[X1] = w * rotation.x + x * rotation.w + y * rotation.z - z * rotation.y; vertices[Y1] = w * rotation.y + y * rotation.w + z * rotation.x - x * rotation.z; vertices[Z1] = w * rotation.z + z * rotation.w + x * rotation.y - y * rotation.x; rotation.conjugate(); // <- don't forget to conjugate the rotation back to normal // finally translate the vector according to position vertices[X1] += position.x - tx; vertices[Y1] += position.y - ty; vertices[Z1] += position.z; /** Transform the second vertex */ // first apply the scale to the vector x = (vertices[X2] + tx) * scale.x; y = (vertices[Y2] + ty) * scale.y; z = vertices[Z2]; // then transform the vector using the rotation quaternion vertices[X2] = rotation.w * x + rotation.y * z - rotation.z * y; vertices[Y2] = rotation.w * y + rotation.z * x - rotation.x * z; vertices[Z2] = rotation.w * z + rotation.x * y - rotation.y * x; w = -rotation.x * x - rotation.y * y - rotation.z * z; rotation.conjugate(); x = vertices[X2]; y = vertices[Y2]; z = vertices[Z2]; vertices[X2] = w * rotation.x + x * rotation.w + y * rotation.z - z * rotation.y; vertices[Y2] = w * rotation.y + y * rotation.w + z * rotation.x - x * rotation.z; vertices[Z2] = w * rotation.z + z * rotation.w + x * rotation.y - y * rotation.x; rotation.conjugate(); // <- don't forget to conjugate the rotation back to normal // finally translate the vector according to position vertices[X2] += position.x - tx; vertices[Y2] += position.y - ty; vertices[Z2] += position.z; /** Transform the third vertex */ // first apply the scale to the vector x = (vertices[X3] + tx) * scale.x; y = (vertices[Y3] + ty) * scale.y; z = vertices[Z3]; // then transform the vector using the rotation quaternion vertices[X3] = rotation.w * x + rotation.y * z - rotation.z * y; vertices[Y3] = rotation.w * y + rotation.z * x - rotation.x * z; vertices[Z3] = rotation.w * z + rotation.x * y - rotation.y * x; w = -rotation.x * x - rotation.y * y - rotation.z * z; rotation.conjugate(); x = vertices[X3]; y = vertices[Y3]; z = vertices[Z3]; vertices[X3] = w * rotation.x + x * rotation.w + y * rotation.z - z * rotation.y; vertices[Y3] = w * rotation.y + y * rotation.w + z * rotation.x - x * rotation.z; vertices[Z3] = w * rotation.z + z * rotation.w + x * rotation.y - y * rotation.x; rotation.conjugate(); // <- don't forget to conjugate the rotation back to normal // finally translate the vector according to position vertices[X3] += position.x - tx; vertices[Y3] += position.y - ty; vertices[Z3] += position.z; /** Transform the fourth vertex */ // first apply the scale to the vector x = (vertices[X4] + tx) * scale.x; y = (vertices[Y4] + ty) * scale.y; z = vertices[Z4]; // then transform the vector using the rotation quaternion vertices[X4] = rotation.w * x + rotation.y * z - rotation.z * y; vertices[Y4] = rotation.w * y + rotation.z * x - rotation.x * z; vertices[Z4] = rotation.w * z + rotation.x * y - rotation.y * x; w = -rotation.x * x - rotation.y * y - rotation.z * z; rotation.conjugate(); x = vertices[X4]; y = vertices[Y4]; z = vertices[Z4]; vertices[X4] = w * rotation.x + x * rotation.w + y * rotation.z - z * rotation.y; vertices[Y4] = w * rotation.y + y * rotation.w + z * rotation.x - x * rotation.z; vertices[Z4] = w * rotation.z + z * rotation.w + x * rotation.y - y * rotation.x; rotation.conjugate(); // <- don't forget to conjugate the rotation back to normal // finally translate the vector according to position vertices[X4] += position.x - tx; vertices[Y4] += position.y - ty; vertices[Z4] += position.z; updated = true; } /** Resets the position components of the vertices array based ont he dimensions (preparation for transformation) */ protected void resetVertices() { float left = -dimensions.x / 2f; float right = left + dimensions.x; float top = dimensions.y / 2f; float bottom = top - dimensions.y; // left top vertices[X1] = left; vertices[Y1] = top; vertices[Z1] = 0; // right top vertices[X2] = right; vertices[Y2] = top; vertices[Z2] = 0; // left bot vertices[X3] = left; vertices[Y3] = bottom; vertices[Z3] = 0; // right bot vertices[X4] = right; vertices[Y4] = bottom; vertices[Z4] = 0; updated = false; } /** Re-applies the uv coordinates from the material's texture region to the uv components of the vertices array */ protected void updateUVs() { TextureRegion tr = material.textureRegion; // left top vertices[U1] = tr.getU(); vertices[V1] = tr.getV(); // right top vertices[U2] = tr.getU2(); vertices[V2] = tr.getV(); // left bot vertices[U3] = tr.getU(); vertices[V3] = tr.getV2(); // right bot vertices[U4] = tr.getU2(); vertices[V4] = tr.getV2(); } /** * Sets the texture region * * @param textureRegion * Texture region to apply */ public void setTextureRegion(TextureRegion textureRegion) { this.material.textureRegion = textureRegion; updateUVs(); } /** @return the texture region this Decal uses. Do not modify it! */ public TextureRegion getTextureRegion() { return this.material.textureRegion; } /** * Sets the blending parameters for this decal * * @param srcBlendFactor * Source blend factor used by glBlendFunc * @param dstBlendFactor * Destination blend factor used by glBlendFunc */ public void setBlending(int srcBlendFactor, int dstBlendFactor) { material.srcBlendFactor = srcBlendFactor; material.dstBlendFactor = dstBlendFactor; } public DecalMaterial getMaterial() { return material; } final static Vector3 dir = new Vector3(); /** * Sets the rotation of the Decal to face the given point. Useful for billboarding. * * @param position * @param up */ public void lookAt(Vector3 position, Vector3 up) { dir.set(position).sub(this.position).nor(); setRotation(dir, up); } // meaning of the floats in the vertices array public static final int X1 = 0; public static final int Y1 = 1; public static final int Z1 = 2; public static final int C1 = 3; public static final int U1 = 4; public static final int V1 = 5; public static final int X2 = 6; public static final int Y2 = 7; public static final int Z2 = 8; public static final int C2 = 9; public static final int U2 = 10; public static final int V2 = 11; public static final int X3 = 12; public static final int Y3 = 13; public static final int Z3 = 14; public static final int C3 = 15; public static final int U3 = 16; public static final int V3 = 17; public static final int X4 = 18; public static final int Y4 = 19; public static final int Z4 = 20; public static final int C4 = 21; public static final int U4 = 22; public static final int V4 = 23; protected static Quaternion rotator = new Quaternion(0, 0, 0, 0); protected static final Vector3 X_AXIS = new Vector3(1, 0, 0); protected static final Vector3 Y_AXIS = new Vector3(0, 1, 0); protected static final Vector3 Z_AXIS = new Vector3(0, 0, 1); /** * Creates a decal assuming the dimensions of the texture region * * @param textureRegion * Texture region to use * @return Created decal */ public static Decal newDecal(TextureRegion textureRegion) { return newDecal(textureRegion.getRegionWidth(), textureRegion.getRegionHeight(), textureRegion, DecalMaterial.NO_BLEND, DecalMaterial.NO_BLEND); } /** * Creates a decal assuming the dimensions of the texture region and adding transparency * * @param textureRegion * Texture region to use * @param hasTransparency * Whether or not this sprite will be treated as having transparency (transparent png, etc.) * @return Created decal */ public static Decal newDecal(TextureRegion textureRegion, boolean hasTransparency) { return newDecal(textureRegion.getRegionWidth(), textureRegion.getRegionHeight(), textureRegion, hasTransparency ? GL10.GL_SRC_ALPHA : DecalMaterial.NO_BLEND, hasTransparency ? GL10.GL_ONE_MINUS_SRC_ALPHA : DecalMaterial.NO_BLEND); } /** * Creates a decal using the region for texturing * * @param width * Width of the decal in world units * @param height * Height of the decal in world units * @param textureRegion * TextureRegion to use * @return Created decal */ // TODO : it would be convenient if {@link com.badlogic.gdx.graphics.Texture} had a getFormat() method to assume transparency // from RGBA,.. public static Decal newDecal(float width, float height, TextureRegion textureRegion) { return newDecal(width, height, textureRegion, DecalMaterial.NO_BLEND, DecalMaterial.NO_BLEND); } /** * Creates a decal using the region for texturing * * @param width * Width of the decal in world units * @param height * Height of the decal in world units * @param textureRegion * TextureRegion to use * @param hasTransparency * Whether or not this sprite will be treated as having transparency (transparent png, etc.) * @return Created decal */ public static Decal newDecal(float width, float height, TextureRegion textureRegion, boolean hasTransparency) { return newDecal(width, height, textureRegion, hasTransparency ? GL10.GL_SRC_ALPHA : DecalMaterial.NO_BLEND, hasTransparency ? GL10.GL_ONE_MINUS_SRC_ALPHA : DecalMaterial.NO_BLEND); } /** * Creates a decal using the region for texturing and the specified blending parameters for blending * * @param width * Width of the decal in world units * @param height * Height of the decal in world units * @param textureRegion * TextureRegion to use * @param srcBlendFactor * Source blend used by glBlendFunc * @param dstBlendFactor * Destination blend used by glBlendFunc * @return Created decal */ public static Decal newDecal(float width, float height, TextureRegion textureRegion, int srcBlendFactor, int dstBlendFactor) { Decal decal = new Decal(); decal.setTextureRegion(textureRegion); decal.setBlending(srcBlendFactor, dstBlendFactor); decal.dimensions.x = width; decal.dimensions.y = height; decal.setColor(1, 1, 1, 1); return decal; } }