diff --git a/src/java/org/lwjgl/util/model/renderer/Renderable.java b/src/java/org/lwjgl/util/Renderable.java similarity index 97% rename from src/java/org/lwjgl/util/model/renderer/Renderable.java rename to src/java/org/lwjgl/util/Renderable.java index d94ec18e..23da87d2 100644 --- a/src/java/org/lwjgl/util/model/renderer/Renderable.java +++ b/src/java/org/lwjgl/util/Renderable.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.lwjgl.util.model.renderer; +package org.lwjgl.util; /** * $Id$ diff --git a/src/java/org/lwjgl/util/Timer.java b/src/java/org/lwjgl/util/Timer.java new file mode 100644 index 00000000..4b1b8622 --- /dev/null +++ b/src/java/org/lwjgl/util/Timer.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2002 Shaven Puppy Ltd + * 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 'Shaven Puppy' 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 org.lwjgl.util; + +import org.lwjgl.Sys; + +/** + * $Id$ + * + * A hires timer. This measures time in seconds as floating point values. + * All Timers created are updated simultaneously by calling the static method + * tick(). This ensures that within a single iteration of a game loop that + * all timers are updated consistently with each other. + * + * @author cix_foo + * @version $Revision$ + */ +public class Timer { + + // Record the timer resolution on classload + private static final long resolution = Sys.getTimerResolution(); + + // Globally keeps track of time for all instances of Timer + private static long currentTime; + + // When the timer was started + private long startTime; + + // The last time recorded by getTime() + private long lastTime; + + // Whether the timer is paused + private boolean paused; + + /** + * Constructs a timer. The timer will be reset to 0.0 and resumed immediately. + */ + public Timer() { + reset(); + resume(); + } + + /** + * @return the time in seconds, as a float + */ + public float getTime() { + if (!paused) + lastTime = currentTime - startTime; + + return (float) ((double) lastTime / (double) resolution); + } + /** + * @return whether this timer is paused + */ + public boolean isPaused() { + return paused; + } + + /** + * Pause the timer. Whilst paused the time will not change for this timer + * when tick() is called. + * + * @see #resume() + */ + public void pause() { + paused = true; + } + + /** + * Reset the timer. Equivalent to set(0.0f); + * @see #set() + */ + public void reset() { + set(0.0f); + } + + /** + * Resume the timer. + * @see #pause() + */ + public void resume() { + paused = false; + startTime = currentTime - lastTime; + } + + /** + * Set the time of this timer + * @param newTime the new time, in seconds + */ + public void set(float newTime) { + long newTimeInTicks = (long) ((double) newTime * (double) resolution); + startTime = currentTime - newTimeInTicks; + lastTime = newTimeInTicks; + } + + /** + * Get the next time update from the system's hires timer. This method should + * be called once per main loop iteration; all timers are updated simultaneously + * from it. + */ + public static void tick() { + currentTime = Sys.getTime(); + } + + /** + * Debug output. + */ + public String toString() { + return "Timer[Time=" + getTime() + ", Paused=" + paused + "]"; + } +} \ No newline at end of file diff --git a/src/java/org/lwjgl/util/model/BoneFrame.java b/src/java/org/lwjgl/util/model/BoneFrame.java index 0eea1c8f..729d9025 100644 --- a/src/java/org/lwjgl/util/model/BoneFrame.java +++ b/src/java/org/lwjgl/util/model/BoneFrame.java @@ -49,10 +49,11 @@ public class BoneFrame extends Frame { /** * C'tor * @param time + * @param action * @param bone[] */ - public BoneFrame(float time, Matrix4f[] bone) { - super(time); + public BoneFrame(float time, String action, Matrix4f[] bone) { + super(time, action); this.bone = bone; } diff --git a/src/java/org/lwjgl/util/model/BonedModel.java b/src/java/org/lwjgl/util/model/BonedModel.java index 39a5e93d..fde485ed 100644 --- a/src/java/org/lwjgl/util/model/BonedModel.java +++ b/src/java/org/lwjgl/util/model/BonedModel.java @@ -65,8 +65,8 @@ public class BonedModel extends Model { * @param animation * @param vertex */ - public BonedModel(String material, Triangle[] triangle, Vector2f[] skin, Color[] color, Map animation, BonedVertex[] vertex) { - super(material, triangle, skin, color, animation); + public BonedModel(String name, String material, Triangle[] triangle, Vector2f[] skin, Color[] color, Map animation, BonedVertex[] vertex) { + super(name, material, triangle, skin, color, animation); this.vertex = vertex; } diff --git a/src/java/org/lwjgl/util/model/Frame.java b/src/java/org/lwjgl/util/model/Frame.java index 861f42ad..4b65c3f4 100644 --- a/src/java/org/lwjgl/util/model/Frame.java +++ b/src/java/org/lwjgl/util/model/Frame.java @@ -39,19 +39,24 @@ import java.io.Serializable; * @author $Author$ * @version $Revision$ */ -public abstract class Frame implements Serializable { +public abstract class Frame implements Serializable, Comparable { public static final long serialVersionUID = 1L; /** Frame time */ private final float time; + /** User-defined action to occur after this frame has been used. May be null */ + private final String action; + /** * C'tor * @param time + * @param action */ - public Frame(float time) { + public Frame(float time, String action) { this.time = time; + this.action = action; } /** @@ -60,4 +65,35 @@ public abstract class Frame implements Serializable { public final float getTime() { return time; } + + /* (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Object o) { + if (o == null) { + return 0; + } + if (! (o instanceof Frame)) { + return 0; + } + Frame f = (Frame) o; + if (f.time == time) { + return 0; + } else if (f.time > time) { + return 1; + } else { + return -1; + } + } + + /** + * Gets the user-defined animation action. This can be processed by whatever + * is animating the model to perform some special action after the frame is + * used. For example, you could use "stop" to stop the animation, or "rewind" + * to repeat the animation ad infinitum. + * @return String, or null, for no action + */ + public final String getAction() { + return action; + } } diff --git a/src/java/org/lwjgl/util/model/MeshFrame.java b/src/java/org/lwjgl/util/model/MeshFrame.java index 2d747b02..a4365cd7 100644 --- a/src/java/org/lwjgl/util/model/MeshFrame.java +++ b/src/java/org/lwjgl/util/model/MeshFrame.java @@ -48,10 +48,11 @@ public class MeshFrame extends Frame { /** * C'tor * @param time + * @param action * @param bone[] */ - public MeshFrame(float time, Vertex[] vertex) { - super(time); + public MeshFrame(float time, String action, Vertex[] vertex) { + super(time, action); this.vertex = vertex; } diff --git a/src/java/org/lwjgl/util/model/MeshedModel.java b/src/java/org/lwjgl/util/model/MeshedModel.java index 2ba2e710..fdfdc42d 100644 --- a/src/java/org/lwjgl/util/model/MeshedModel.java +++ b/src/java/org/lwjgl/util/model/MeshedModel.java @@ -61,8 +61,8 @@ public class MeshedModel extends Model { * @param color[] * @param animation */ - public MeshedModel(String material, Triangle[] triangle, Vector2f[] skin, Color[] color, Map animation) { - super(material, triangle, skin, color, animation); + public MeshedModel(String name, String material, Triangle[] triangle, Vector2f[] skin, Color[] color, Map animation) { + super(name, material, triangle, skin, color, animation); } } diff --git a/src/java/org/lwjgl/util/model/Model.java b/src/java/org/lwjgl/util/model/Model.java index 24b49787..7ddfd086 100644 --- a/src/java/org/lwjgl/util/model/Model.java +++ b/src/java/org/lwjgl/util/model/Model.java @@ -52,6 +52,9 @@ public abstract class Model implements Serializable { public static final long serialVersionUID = 1L; + /** Model name */ + private final String name; + /** Material */ private final String material; @@ -69,13 +72,15 @@ public abstract class Model implements Serializable { /** * C'tor + * @param name * @param material * @param triangle * @param skin[] * @param color[] * @param animation */ - public Model(String material, Triangle[] triangle, Vector2f[] skin, Color[] color, Map animation) { + public Model(String name, String material, Triangle[] triangle, Vector2f[] skin, Color[] color, Map animation) { + this.name = name; this.material = material; this.triangle = triangle; this.skin = skin; @@ -88,8 +93,8 @@ public abstract class Model implements Serializable { * @param name The name of the animation * @return the Frames of an animation (or null, if no such animation exists) */ - public final BoneFrame[] getAnimation(String name) { - return (BoneFrame[]) animation.get(name); + public final Frame[] getAnimation(String name) { + return (Frame[]) animation.get(name); } /** @@ -120,5 +125,17 @@ public abstract class Model implements Serializable { return color; } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return "Model["+name+"]"; + } + /** + * @return Returns the name. + */ + public final String getName() { + return name; + } } diff --git a/src/java/org/lwjgl/util/model/loader/Loader.java b/src/java/org/lwjgl/util/model/loaders/XMLLoader.java similarity index 96% rename from src/java/org/lwjgl/util/model/loader/Loader.java rename to src/java/org/lwjgl/util/model/loaders/XMLLoader.java index f79bd52a..163bc79c 100644 --- a/src/java/org/lwjgl/util/model/loader/Loader.java +++ b/src/java/org/lwjgl/util/model/loaders/XMLLoader.java @@ -29,8 +29,9 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.lwjgl.util.model.loader; +package org.lwjgl.util.model.loaders; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -60,7 +61,7 @@ import org.w3c.dom.Element; * @author $Author$ * @version $Revision$ */ -public class Loader { +public class XMLLoader { /** The source document */ private final Document src; @@ -74,7 +75,7 @@ public class Loader { /** * C'tor */ - public Loader(Document src) { + public XMLLoader(Document src) { this.src = src; } @@ -84,12 +85,14 @@ public class Loader { * @throws Exception */ public Model load() throws Exception { + String name = XMLUtil.getString(src.getDocumentElement(), "name"); String material = XMLUtil.getString(src.getDocumentElement(), "material"); numVertices = XMLUtil.getInt(src.getDocumentElement(), "vertices"); if (XMLUtil.getString(src.getDocumentElement(), "type").equals("boned")) { // It's a boned model numBones = XMLUtil.getInt(src.getDocumentElement(), "bones", 0); return new BonedModel( + name, material, loadTriangles(), loadSkin(), @@ -100,6 +103,7 @@ public class Loader { } else if (XMLUtil.getString(src.getDocumentElement(), "type").equals("meshed")) { // It's a mesh keyframe model return new MeshedModel( + name, material, loadTriangles(), loadSkin(), @@ -371,6 +375,7 @@ public class Loader { Element frameElement = (Element) i.next(); frames[frameCount++] = loadBoneFrame(frameElement); } + Arrays.sort(frames); return frames; } @@ -388,6 +393,7 @@ public class Loader { Element frameElement = (Element) i.next(); frames[frameCount++] = loadMeshFrame(frameElement); } + Arrays.sort(frames); return frames; } @@ -409,7 +415,8 @@ public class Loader { bones[boneCount++] = loadBone(boneElement); } return new BoneFrame( - XMLUtil.getFloat(element, "time", 0.0f), + XMLUtil.getFloat(element, "time"), + XMLUtil.getString(element, "action", null), bones ); } @@ -432,7 +439,8 @@ public class Loader { vertices[vertexCount++] = loadMeshVertex(vertexElement); } return new MeshFrame( - XMLUtil.getFloat(element, "time", 0.0f), + XMLUtil.getFloat(element, "time"), + XMLUtil.getString(element, "action", null), vertices ); } diff --git a/src/java/org/lwjgl/util/model/loader/XMLUtil.java b/src/java/org/lwjgl/util/model/loaders/XMLUtil.java similarity index 99% rename from src/java/org/lwjgl/util/model/loader/XMLUtil.java rename to src/java/org/lwjgl/util/model/loaders/XMLUtil.java index cebffcba..48e4754f 100644 --- a/src/java/org/lwjgl/util/model/loader/XMLUtil.java +++ b/src/java/org/lwjgl/util/model/loaders/XMLUtil.java @@ -29,7 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.lwjgl.util.model.loader; +package org.lwjgl.util.model.loaders; import java.util.ArrayList; import java.util.List; diff --git a/src/java/org/lwjgl/util/model/renderer/Renderer.java b/src/java/org/lwjgl/util/model/renderer/Renderer.java index ca3ed709..99418cae 100644 --- a/src/java/org/lwjgl/util/model/renderer/Renderer.java +++ b/src/java/org/lwjgl/util/model/renderer/Renderer.java @@ -31,11 +31,15 @@ */ package org.lwjgl.util.model.renderer; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.lwjgl.util.model.BoneFrame; -import org.lwjgl.util.model.BonedModel; +import org.lwjgl.util.Renderable; +import org.lwjgl.util.Timer; +import org.lwjgl.util.model.Frame; +import org.lwjgl.util.model.Model; /** * $Id$ @@ -46,14 +50,35 @@ import org.lwjgl.util.model.BonedModel; * Material lookups are performed by mapping the material name to a Renderable thing. You must * suppy appropriate Renderables - typically something that binds a 2D texture and sets up some * GL state. + *

+ * To animate things, you will need to call Timer.tick() every frame to update the timers in your + * Renderables. Then they'll just animate themselves. Hurrah! * * @author $Author$ * @version $Revision$ */ -public class Renderer { +public class Renderer implements Renderable { /** Material map: String name->Renderable */ - private final Map materials = new HashMap(); + private static final Map materials = new HashMap(); + + /** The model we're rendering */ + private Model model; + + /** The current material */ + private Renderable material; + + /** The animation currently being animated */ + private Frame[] frame; + + /** The current time */ + private final Timer timer = new Timer(); + + /** Last frame rendered */ + private Frame lastFrame; + + /** Visibility */ + private boolean visible; /** * C'tor @@ -62,36 +87,135 @@ public class Renderer { } /** - * Render a Model - * @param model The model to render - * @param animation The name of the animation - * @param time The time for the animation + * @param model The model to set. */ - public void render(BonedModel model, String animation, float time) { + public void setModel(Model model) { + if (this.model == model) { + return; + } + this.model = model; + material = (Renderable) materials.get(model.getMaterial()); + frame = null; + } + + /** + * @return the Model we're rendering with this Renderer + */ + public Model getModel() { + return model; + } + + /** + * Set the animation + * @param animation + */ + public void setAnimation(String animation) { + if (model == null) { + return; + } + frame = model.getAnimation(animation); + timer.reset(); + lastFrame = null; + } + + /** + * Render a Model + */ + public void render() { + + // Don't do anything if there's no model or no animation + if (model == null || frame == null) { + return; + } // 1. Set up GL state from the Model's material - Renderable material = (Renderable) materials.get(model.getMaterial()); if (material != null) { material.render(); } - // 2. Get the animation - BoneFrame[] frame = model.getAnimation(animation); - if (frame == null) { - return; + // 2. Work out what frame to show + Frame frame = findFrame(); + if (frame != lastFrame) { + lastFrame = frame; + processFrame(); } - // 3. Work out what the nearest frame is to the specified time + // 3. Render the processed frame } + /** + * Find the nearest frame to the current time + * @return the Frame nearest the current time + */ + private Frame findFrame() { + float time = timer.getTime(); + + // Use a binary search to find the frame + int a = 0, b = + } + /** * Add a material * @param name The material's name * @param renderable The renderable object */ - public void putMaterial(String name, Renderable renderable) { + public static void putMaterial(String name, Renderable renderable) { materials.put(name, renderable); } + /** + * Remove a material + * @param name The material's name + * @return a Renderable + */ + public static Renderable removeMaterial(String name) { + return (Renderable) materials.remove(name); + } + + /** + * Determine if this Renderer is visible + * @return boolean + */ + public boolean isVisible() { + return visible; + } + + /** + * Sets the visibility of this Renderer + * @param visible + */ + public void setVisible(boolean visible) { + this.visible = visible; + } + + /** + * Determines if the animation is paused + * @return boolean + */ + public boolean isPaused() { + return timer.isPaused(); + } + + /** + * Pause the animation + */ + public void pause() { + timer.pause(); + } + + /** + * Rewind the animation + */ + public void rewind() { + timer.reset(); + } + + /** + * Resume a paused animation + */ + public void resume() { + timer.resume(); + } + }