/*******************************************************************************
* 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;
}
}