package scene3d; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g3d.Environment; import com.badlogic.gdx.graphics.g3d.Material; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.ModelBatch; import com.badlogic.gdx.graphics.g3d.ModelInstance; import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; import com.badlogic.gdx.graphics.g3d.utils.AnimationController; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.math.collision.Ray; import com.badlogic.gdx.scenes.scene2d.Group; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.DelayedRemovalArray; import com.badlogic.gdx.utils.Disposable; public class Actor3d extends ModelInstance implements Disposable { private Stage3d stage3d; private Group3d parent; private final DelayedRemovalArray<Event3dListener> listeners = new DelayedRemovalArray<Event3dListener>(0); private final Array<Action3d> actions = new Array<Action3d>(0); public final Vector3 center = new Vector3(); public final Vector3 dimensions = new Vector3(); private BoundingBox boundBox = new BoundingBox(); public final float radius; private String name; private boolean visible = true; float x, y, z; float scaleX = 1, scaleY = 1, scaleZ = 1; float yaw = 0f,pitch =0f, roll=0f; Matrix4 rotationMatrix = new Matrix4(); private AnimationController animation; public Actor3d(){ this(new Model()); setScale(0,0,0); } public Actor3d(Model model){ this(model, 0f, 0f ,0f); } public Actor3d(Model model, float x, float y, float z){ super(model); setPosition(x,y,z); //boundBox = model.meshes.get(0).calculateBoundingBox(); calculateBoundingBox(boundBox); center.set(boundBox.getCenter()); dimensions.set(boundBox.getDimensions()); radius = dimensions.len() / 2f; animation = new AnimationController(this); } /** Updates the actor3d based on time. Typically this is called each frame by {@link Stage3d#act(float)}. * <p> * The default implementation calls {@link Action3d#act(float)} on each action and removes actions that are complete. * @param delta Time in seconds since the last frame. */ public void act (float delta) { for (int i = 0; i < actions.size; i++) { Action3d action3d = actions.get(i); if (action3d.act(delta) && i < actions.size) { actions.removeIndex(i); action3d.setActor3d(null); i--; } } if (animation.inAction) animation.update(delta); } public void draw(ModelBatch modelBatch, Environment environment){ modelBatch.render(this, environment); } public Actor3d hit (float x, float y) { return null; } /** Removes this actor3d from its parent, if it has a parent. * @see Group#removeActor3d(Actor3d) */ public boolean remove () { if (parent != null) return parent.removeActor3d(this); return false; } /** Add a listener to receive events that {@link #hit(float, float, boolean) hit} this actor3d. See {@link #fire(Event)}. * * @see InputListener * @see ClickListener */ public boolean addListener(Event3dListener listener) { if (!listeners.contains(listener, true)) { listeners.add(listener); return true; } return false; } public boolean removeListener (Event3dListener listener) { return listeners.removeValue(listener, true); } public Array<Event3dListener> getListeners () { return listeners; } public void addAction3d (Action3d action3d) { action3d.setActor3d(this); actions.add(action3d); } public void removeAction3d (Action3d action) { if (actions.removeValue(action, true)) action.setActor3d(null); } public Array<Action3d> getActions3d () { return actions; } /** Removes all actions on this actor3d. */ public void clearActions3d () { for (int i = actions.size - 1; i >= 0; i--) actions.get(i).setActor3d(null); actions.clear(); } /** Removes all listeners on this actor3d. */ public void clearListeners () { listeners.clear(); } /** Removes all actions and listeners on this actor3d. */ public void clear () { clearActions3d(); clearListeners(); } /** Called by the framework when this actor3d or any parent is added to a group that is in the stage3d. * @param stage3d May be null if the actor3d or any parent is no longer in a stage. */ protected void setStage3d(Stage3d stage3d) { this.stage3d = stage3d; } /** Returns the stage3d that this actor3d is currently in, or null if not in a stage. */ public Stage3d getStage3d() { return stage3d; } /** Returns true if this actor3d is the same as or is the descendant of the specified actor3d. */ public boolean isDescendantOf (Actor3d actor3d) { if (actor3d == null) throw new IllegalArgumentException("actor3d cannot be null."); Actor3d parent = this; while (true) { if (parent == null) return false; if (parent == actor3d) return true; parent = parent.parent; } } /** Returns true if this actor3d is the same as or is the ascendant of the specified actor3d. */ public boolean isAscendantOf (Actor3d actor3d) { if (actor3d == null) throw new IllegalArgumentException("actor3d cannot be null."); while (true) { if (actor3d == null) return false; if (actor3d == this) return true; actor3d = actor3d.parent; } } /** Returns true if the actor3d's parent is not null. */ public boolean hasParent () { return parent != null; } /** Returns the parent actor3d, or null if not in a stage. */ public Group3d getParent () { return parent; } /** Called by the framework when an actor3d is added to or removed from a group. * @param parent May be null if the actor3d has been removed from the parent. */ protected void setParent (Group3d parent) { this.parent = parent; } private static final Vector3 position = new Vector3(); public boolean isCullable(final Camera cam) { return cam.frustum.sphereInFrustum(getTransform().getTranslation(position).add(center), radius); } public boolean isVisible () { return visible; } /** If false, the actor3d will not be drawn and will not receive touch events. Default is true. */ public void setVisible (boolean visible) { this.visible = visible; } /** @return -1 on no intersection, or when there is an intersection: the squared distance between the center of this * object and the point on the ray closest to this object when there is intersection. */ public float intersects(Ray ray) { transform.getTranslation(position).add(center); final float len = ray.direction.dot(position.x-ray.origin.x, position.y-ray.origin.y, position.z-ray.origin.z); if (len < 0f) return -1f; float dist2 = position.dst2(ray.origin.x+ray.direction.x*len, ray.origin.y+ray.direction.y*len, ray.origin.z+ray.direction.z*len); return (dist2 <= radius * radius) ? dist2 : -1f; } public void setPosition(float x, float y, float z) { this.x = x; this.y = y; this.z = z; transform.setToTranslationAndScaling(this.x, this.y, this.z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } public void translate(float x, float y, float z) { this.x += x; this.y += y; this.z += z; transform.setToTranslationAndScaling(this.x, this.y, this.z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } /* * Set the actor3d's rotation values to new yaw, pitch and roll * @param newYaw, newPitch, newRoll these values must be within 360 degrees */ public void setRotation(float newYaw, float newPitch, float newRoll){ yaw = newYaw; pitch = newPitch; roll = newRoll; rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } /* * Set the actor3d's yaw * @param newYaw value must be within 360 degrees */ public void setYaw(float newYaw){ yaw = newYaw; rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } /* * Set the actor3d's pitch * @param newPitch value must be within 360 degrees */ public void setPitch(float newPitch){ pitch = newPitch; rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } /* * Set the actor3d's roll * @param newRoll value must be within 360 degrees */ public void setRoll(float newRoll){ roll = newRoll; rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } public static float normalizeDegrees(float degrees){ float newAngle = degrees; while (newAngle < -360) newAngle += 360; while (newAngle > 360) newAngle -= 360; return newAngle; } /* * Rotates the actor3d by the amount of yaw, pitch and roll * @param amountYaw,amountPitch,amountRoll These values must be within 360 degrees */ public void rotate(float amountYaw, float amountPitch, float amountRoll){ yaw = normalizeDegrees(yaw + amountYaw); pitch = normalizeDegrees(pitch + amountPitch); roll = normalizeDegrees(roll + amountRoll); rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } public void rotateYaw(float amountYaw){ yaw = normalizeDegrees(yaw + amountYaw); rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } public void rotatePitch(float amountPitch){ pitch = normalizeDegrees(pitch + amountPitch); rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } public void rotateRoll(float amountRoll){ roll = normalizeDegrees(roll + amountRoll); rotationMatrix = transform.setFromEulerAngles(yaw, pitch, roll).cpy(); transform.setToTranslationAndScaling(x, y, z, scaleX, scaleY, scaleZ); transform.mul(rotationMatrix); } public float getYaw(){ return yaw; } public float getPitch(){ return pitch; } public float getRoll(){ return roll; } public void setScale(float scaleX, float scaleY, float scaleZ) { this.scaleX = scaleX; this.scaleY = scaleY; this.scaleZ = scaleZ; transform.setToScaling(scaleX, scaleY, scaleZ); } public void setScale(float scale) { this.scaleX = scale; this.scaleY = scale; this.scaleZ = scale; transform.setToScaling(scaleX, scaleY, scaleZ); } /** Adds the specified scale to the current scale. */ public void scale(float scale) { scaleX += scale; scaleY += scale; scaleZ += scale; transform.scl(scale); // re-implement this } public void scale(float scaleX, float scaleY, float scaleZ) { this.scaleX += scaleX; this.scaleY += scaleY; this.scaleZ += scaleZ; transform.scl(scaleX, scaleY, scaleZ); // re-implement this } public void setX (float x) { this.x = x; transform.setToTranslation(x, y, z); } public float getX () { return x; } public void setY (float y) { this.y = y; transform.setToTranslation(x, y, z); } public float getY () { return y; } public void setZ (float z) { this.z = z; transform.setToTranslation(x, y, z); } public float getZ (){ return z; } public void setScaleX (float scaleX) { this.scaleX = scaleX; transform.scale(scaleX, scaleY, scaleZ); } public float getScaleX () { return scaleX; } public void setScaleY (float scaleY) { this.scaleY = scaleY; transform.scale(scaleX, scaleY, scaleZ); } public float getScaleY () { return scaleY; } public void setScaleZ (float scaleZ) { this.scaleY = scaleZ; transform.scale(scaleX, scaleY, scaleZ); } public float getScaleZ () { return scaleZ; } /** Sets a name for easier identification of the actor3d in application code. * @see Group#findActor(String) */ public void setName (String name) { this.name = name; } public String getName () { return name; } public String toString () { String name = this.name; if (name == null) { name = getClass().getName(); int dotIndex = name.lastIndexOf('.'); if (dotIndex != -1) name = name.substring(dotIndex + 1); } return name; } public Color getColor(){ return ((ColorAttribute)getMaterial("Color").get(ColorAttribute.Diffuse)).color; } public void setColor(Color color){ ColorAttribute ca = new ColorAttribute(ColorAttribute.Diffuse, color); if(getMaterial("Color") != null) getMaterial("Color").set(ca); else materials.add(new Material("Color", ca)); model.materials.add(new Material("Color", ca)); } public Matrix4 getTransform(){ return transform; } public void setTransform(Matrix4 transform){ this.transform = transform; } public BoundingBox getBoundingBox(){ return boundBox; } public void setBoundingBox(BoundingBox box){ boundBox = box; } public AnimationController getAnimation(){ return animation; } @Override public void dispose() { model.dispose(); } }