Behold, Nenya, Ring of Water and repository for our media and animation related

goodies, both Java 2D and LWJGL/JME 3D.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Michael Bayne
2006-06-23 18:07:28 +00:00
commit c2117ee86d
570 changed files with 61913 additions and 0 deletions
+582
View File
@@ -0,0 +1,582 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme;
import java.io.File;
import com.samskivert.util.Queue;
import com.samskivert.util.RunQueue;
import com.samskivert.util.StringUtil;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.JmeException;
import com.jme.system.PropertiesIO;
import com.jme.system.lwjgl.LWJGLPropertiesDialog;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.Mouse;
import com.jme.input.MouseInput;
import com.jmex.bui.BRootNode;
import com.jmex.bui.PolledRootNode;
import com.jme.light.PointLight;
import com.jme.math.Vector3f;
import com.jme.util.Timer;
import com.threerings.jme.camera.CameraHandler;
/**
* Defines a basic application framework providing integration with the
* <a href="../presents/package.html">Presents</a> networking system and
* targeting the framerate of the display.
*/
public class JmeApp
implements RunQueue
{
/**
* Returns a context implementation that provides access to all the
* necessary bits.
*/
public JmeContext getContext ()
{
return _ctx;
}
/**
* Does the main initialization of the application. This method should
* be called first, and then the {@link #run} method should be called
* to begin the rendering/event loop. Derived classes can override
* this, being sure to call super before doing their own
* initalization.
*
* @return true if the application initialized successfully, false if
* initialization failed. (See {@link #reportInitFailure}.)
*/
public boolean init ()
{
try {
// initialize the rendering system
initDisplay();
if (!_display.isCreated()) {
throw new IllegalStateException(
"Failed to initialize display?");
}
// create an appropriate timer
_timer = Timer.getTimer();
// start with the target FPS equal to the refresh rate (but
// sometimes the refresh rate is reported as zero so don't let that
// freak us out)
setTargetFPS(Math.max(_display.getFrequency(), 60));
// initialize our main camera controls and user input handling
initInput();
// initialize the root node
initRoot();
// initialize the lighting
initLighting();
// initialize the UI support stuff
initInterface();
// update everything for the zeroth tick
_iface.updateRenderState();
_geom.updateRenderState();
_root.updateGeometricState(0f, true);
_root.updateRenderState();
return true;
} catch (Throwable t) {
reportInitFailure(t);
return false;
}
}
/**
* Configures whether or not we display FPS and other statistics atop the
* display.
*/
public void displayStatistics (boolean display)
{
if (display && (_stats == null)) {
_stats = new StatsDisplay(_display.getRenderer());
_stats.updateGeometricState(0f, true);
_stats.updateRenderState();
} else if (!display && (_stats != null)) {
_stats = null;
}
}
/**
* Returns true if we are displaying statistics, false if not.
*/
public boolean showingStatistics ()
{
return (_stats != null);
}
/**
* Sets the target frames per second.
*
* @return the old target frames per second.
*/
public int setTargetFPS (int targetFPS)
{
int oldTargetFPS = _targetFPS;
_targetFPS = targetFPS;
_ticksPerFrame = _timer.getResolution() / _targetFPS;
return oldTargetFPS;
}
/**
* Returns the frames per second averaged over the last 32 frames.
*/
public float getRecentFrameRate ()
{
return _timer.getFrameRate();
}
/**
* Enables or disables the update and render part of the
* update/render/process events application loop.
*/
public void setEnabled (boolean update, boolean render)
{
_updateEnabled = update;
_renderEnabled = render;
}
/**
* Starts up the main rendering and event processing loop. This method
* will not return until the application is terminated with a call to
* {@link #stop}.
*/
public void run ()
{
synchronized (this) {
_dispatchThread = Thread.currentThread();
}
// enter the main rendering and event processing loop
while (!_finished && !_display.isClosing()) {
try {
// render the current frame
long frameStart = processFrame();
// and process events until it's time to render the next
PROCESS_EVENTS:
do {
Runnable r = (Runnable)_evqueue.getNonBlocking();
if (r != null) {
r.run();
}
if (_timer.getTime() - frameStart >= _ticksPerFrame) {
break PROCESS_EVENTS;
} else {
try {
Thread.sleep(1);
} catch (InterruptedException ie) {
}
}
} while (!_finished);
_failures = 0;
} catch (Throwable t) {
Log.logStackTrace(t);
// stick a fork in things if we fail too many times in a row
if (++_failures > MAX_SUCCESSIVE_FAILURES) {
stop();
}
}
}
try {
cleanup();
} catch (Throwable t) {
Log.logStackTrace(t);
} finally {
exit();
}
}
/**
* Returns the duration (in seconds) between the previous frame and the
* current frame.
*/
public float getFrameTime ()
{
return _frameTime;
}
/**
* Instructs the application to stop the main loop, cleanup and exit.
*/
public void stop ()
{
_finished = true;
}
// documentation inherited from interface RunQueue
public void postRunnable (Runnable r)
{
_evqueue.append(r);
}
// documentation inherited from interface RunQueue
public boolean isDispatchThread ()
{
return Thread.currentThread() == _dispatchThread;
}
/**
* Determines the display configuration and creates the display. This must
* fill in {@link #_api} as a side-effect.
*/
protected DisplaySystem createDisplay ()
{
PropertiesIO props = new PropertiesIO(getConfigPath("jme.cfg"));
if (!props.load()) {
LWJGLPropertiesDialog dialog =
new LWJGLPropertiesDialog(props, (String)null);
while (dialog.isVisible()) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
Log.warning("Error waiting for dialog system, " +
"using defaults.");
}
}
}
_api = props.getRenderer();
DisplaySystem display = DisplaySystem.getDisplaySystem(_api);
display.createWindow(props.getWidth(), props.getHeight(),
props.getDepth(), props.getFreq(),
props.getFullscreen());
return display;
}
/**
* Initializes the underlying rendering system, creating a display of
* the proper resolution and depth.
*/
protected void initDisplay ()
throws JmeException
{
// create the main display system
_display = createDisplay();
// create a camera
int width = _display.getWidth(), height = _display.getHeight();
_camera = _display.getRenderer().createCamera(width, height);
// start with a black background
_display.getRenderer().setBackgroundColor(ColorRGBA.black);
// set up the camera
_camera.setFrustumPerspective(45.0f, width/(float)height, 1, 10000);
Vector3f loc = new Vector3f(0.0f, 0.0f, 25.0f);
Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
Vector3f dir = new Vector3f(0.0f, 0f, -1.0f);
_camera.setFrame(loc, left, up, dir);
_camera.update();
_display.getRenderer().setCamera(_camera);
// tell the renderer to keep track of rendering information (total
// triangles drawn, etc.)
_display.getRenderer().enableStatistics(true);
}
/**
* Sets up a main input controller to handle the camera and deal with
* global user input.
*/
protected void initInput ()
{
_camhand = createCameraHandler(_camera);
_input = createInputHandler(_camhand);
}
/**
* Creates the camera handler which provides various camera manipulation
* functionality.
*/
protected CameraHandler createCameraHandler (Camera camera)
{
return new CameraHandler(camera);
}
/**
* Creates the input handler used to control our camera and manage non-UI
* keyboard input.
*/
protected InputHandler createInputHandler (CameraHandler hand)
{
return new InputHandler();
}
/**
* Creates our root node and sets up the basic rendering system.
*/
protected void initRoot ()
{
_root = new Node("Root");
// set up a node for our geometry
_geom = new Node("Geometry");
// make everything opaque by default
_geom.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
// set up a zbuffer
ZBufferState zbuf = _display.getRenderer().createZBufferState();
zbuf.setEnabled(true);
zbuf.setFunction(ZBufferState.CF_LEQUAL);
_geom.setRenderState(zbuf);
_root.attachChild(_geom);
}
/**
* Sets up some default lighting.
*/
protected void initLighting ()
{
PointLight light = new PointLight();
light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
light.setLocation(new Vector3f(100, 100, 100));
light.setEnabled(true);
_lights = _display.getRenderer().createLightState();
_lights.setEnabled(true);
_lights.attach(light);
_geom.setRenderState(_lights);
}
/**
* Initializes our user interface bits.
*/
protected void initInterface ()
{
// set up a node for our interface
_iface = new Node("Interface");
_root.attachChild(_iface);
// create our root node
_rnode = createRootNode();
_iface.attachChild(_rnode);
// we don't hide the cursor
MouseInput.get().setCursorVisible(true);
}
/**
* Allows a customized root node to be created.
*/
protected BRootNode createRootNode ()
{
return new PolledRootNode(_timer, _input);
}
/**
* Called when initialization fails to give the application a chance
* to report the failure to the user.
*/
protected void reportInitFailure (Throwable t)
{
Log.logStackTrace(t);
}
/**
* Processes a single frame.
*/
protected final long processFrame ()
{
// update our simulation and render a frame
long frameStart = _timer.getTime();
if (_updateEnabled) {
update(frameStart);
}
if (_renderEnabled) {
render(frameStart);
_display.getRenderer().displayBackBuffer();
}
return frameStart;
}
/**
* Called every frame to update whatever sort of real time business we
* have that needs updating.
*/
protected void update (long frameTick)
{
// recalculate the frame rate
_timer.update();
// update the camera handler
_camhand.update(_frameTime);
// run all of the controllers attached to nodes
_frameTime = (_lastTick == 0L) ? 0f : (float)(frameTick - _lastTick) /
_timer.getResolution();
_lastTick = frameTick;
_root.updateGeometricState(_frameTime, true);
// update our stats display if we have one
if (_stats != null) {
_stats.update(_timer, _display.getRenderer());
}
}
/**
* Called every frame to issue the rendering instructions for this frame.
*/
protected void render (float frameTime)
{
// clear out our previous information
_display.getRenderer().clearStatistics();
_display.getRenderer().clearBuffers();
// draw the root node and all of its children
_display.getRenderer().draw(_root);
// this would render bounding boxes
// _display.getRenderer().drawBounds(_root);
// draw our stats atop everything
if (_stats != null) {
_display.getRenderer().draw(_stats);
}
}
/**
* Called when the application is terminating cleanly after having
* successfully completed initialization and begun the main loop.
*/
protected void cleanup ()
{
_display.reset();
KeyInput.destroyIfInitalized();
MouseInput.destroyIfInitalized();
}
/**
* Closes the display and exits the JVM process.
*/
protected void exit ()
{
if (_display != null) {
_display.close();
}
System.exit(0);
}
/**
* Prepends the necessary bits onto the supplied path to properly
* locate it in our configuration directory.
*/
protected String getConfigPath (String file)
{
String cfgdir = ".narya";
String home = System.getProperty("user.home");
if (!StringUtil.isBlank(home)) {
cfgdir = home + File.separator + cfgdir;
}
// create the configuration directory if it does not already exist
File dir = new File(cfgdir);
if (!dir.exists()) {
dir.mkdir();
}
return cfgdir + File.separator + file;
}
/** Provides access to various needed bits. */
protected JmeContext _ctx = new JmeContext() {
public DisplaySystem getDisplay () {
return _display;
}
public Renderer getRenderer () {
return _display.getRenderer();
}
public CameraHandler getCameraHandler () {
return _camhand;
}
public Node getGeometry () {
return _geom;
}
public Node getInterface () {
return _iface;
}
public InputHandler getInputHandler () {
return _input;
}
public BRootNode getRootNode () {
return _rnode;
}
};
protected Timer _timer;
protected Thread _dispatchThread;
protected Queue _evqueue = new Queue();
protected long _lastTick;
protected float _frameTime;
protected String _api;
protected DisplaySystem _display;
protected Camera _camera;
protected CameraHandler _camhand;
protected InputHandler _input;
protected BRootNode _rnode;
protected long _ticksPerFrame;
protected int _targetFPS;
protected boolean _updateEnabled = true, _renderEnabled = true;
protected boolean _finished;
protected int _failures;
protected Node _root, _geom, _iface;
protected LightState _lights;
protected StatsDisplay _stats;
/** If we fail 100 frames in a row, stick a fork in ourselves. */
protected static final int MAX_SUCCESSIVE_FAILURES = 100;
}
@@ -0,0 +1,184 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.EventQueue;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import org.lwjgl.LWJGLException;
import com.jme.renderer.Renderer;
import com.jme.renderer.lwjgl.LWJGLRenderer;
import com.jme.scene.Node;
import com.jme.system.DisplaySystem;
import com.jmex.awt.JMECanvas;
import com.jmex.awt.JMECanvasImplementor;
import com.jmex.awt.lwjgl.LWJGLCanvas;
import com.jmex.bui.CanvasRootNode;
/**
* Extends the basic {@link JmeApp} with the necessary wiring to use the
* GL/AWT bridge to display our GL interface in an AWT component.
*/
public class JmeCanvasApp extends JmeApp
{
public JmeCanvasApp (int width, int height)
{
_display = DisplaySystem.getDisplaySystem("LWJGL");
// create a throwaway canvas so that the display system knows it's
// created, then create our custom canvas
_display.createCanvas(width, height);
try {
_canvas = createCanvas();
} catch (LWJGLException e) {
Log.warning("Failed to create LWJGL canvas [error=" + e + "].");
}
((JMECanvas)_canvas).setImplementor(_winimp);
_canvas.setBounds(0, 0, width, height);
_canvas.addComponentListener(new ComponentAdapter() {
public void componentResized (ComponentEvent ce) {
_winimp.resizeCanvas(_canvas.getWidth(), _canvas.getHeight());
}
});
}
/**
* Returns the AWT canvas that contains our GL display.
*/
public Canvas getCanvas ()
{
return _canvas;
}
public void run ()
{
Thread t = new Thread() {
public void run () {
while (!_finished) {
// queue up another repaint
_canvas.repaint();
try {
Thread.sleep(10);
} catch (Exception e) {
}
}
}
};
t.setDaemon(true);
t.start();
}
// documentation inherited from interface RunQueue
public void postRunnable (Runnable r)
{
EventQueue.invokeLater(r);
}
// documentation inherited from interface RunQueue
public boolean isDispatchThread ()
{
return EventQueue.isDispatchThread();
}
/**
* Creates and returns the LWJGL canvas instance.
*/
protected Canvas createCanvas ()
throws LWJGLException
{
// LWJGL's canvas releases the context after painting. we make it
// current again, because we want it valid when we process events.
return new LWJGLCanvas() {
public void update (Graphics g) {
super.update(g);
try {
makeCurrent();
} catch (LWJGLException e) {
Log.warning("Failed to make context current [error=" +
e + "].");
}
}
};
}
// documentation inherited
protected DisplaySystem createDisplay ()
{
// we've already created our display earlier so just return it
_api = "LWJGL";
return _display;
}
// documentation inherited
protected void initInterface ()
{
_iface = new Node("Interface");
_root.attachChild(_iface);
_rnode = new CanvasRootNode(_canvas);
_iface.attachChild(_rnode);
}
/** This is used if we embed our GL display in an AWT component. */
protected JMECanvasImplementor _winimp = new JMECanvasImplementor() {
public void doSetup () {
super.doSetup();
LWJGLRenderer renderer =
new LWJGLRenderer(_canvas.getWidth(), _canvas.getHeight());
renderer.setHeadless(true);
setRenderer(renderer);
_display.setRenderer(renderer);
DisplaySystem.updateStates(renderer);
if (!init()) {
Log.warning("JmeCanvasApp init failed.");
}
}
public void doUpdate () {
}
public void doRender () {
// here we do our normal frame processing
try {
processFrame();
// we don't process events as the AWT queue handles them
_failures = 0;
} catch (Throwable t) {
Log.logStackTrace(t);
// stick a fork in things if we fail too many
// times in a row
if (++_failures > MAX_SUCCESSIVE_FAILURES) {
JmeCanvasApp.this.stop();
}
}
}
};
protected Canvas _canvas;
}
@@ -0,0 +1,59 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme;
import com.jme.input.InputHandler;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.system.DisplaySystem;
import com.jmex.bui.BRootNode;
import com.threerings.jme.camera.CameraHandler;
/**
* Provides access to the various bits needed by things that operate in
* JME land.
*/
public interface JmeContext
{
/** Returns the display to which we are rendering. */
public DisplaySystem getDisplay ();
/** Returns the renderer being used to draw everything. */
public Renderer getRenderer ();
/** Returns the handler for the camera being used to view the scene. */
public CameraHandler getCameraHandler ();
/** Returns the main geometry of our scene graph. */
public Node getGeometry ();
/** Returns the main interface node of our scene graph. */
public Node getInterface ();
/** Returns our main input handler. */
public InputHandler getInputHandler ();
/** Returns our UI root node. */
public BRootNode getRootNode ();
}
+56
View File
@@ -0,0 +1,56 @@
//
// $Id: Log.java 3099 2004-08-27 02:21:06Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme;
/**
* A placeholder class that contains a reference to the log object used by
* this package.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("narya.jme");
/** Convenience function. */
public static void debug (String message)
{
log.debug(message);
}
/** Convenience function. */
public static void info (String message)
{
log.info(message);
}
/** Convenience function. */
public static void warning (String message)
{
log.warning(message);
}
/** Convenience function. */
public static void logStackTrace (Throwable t)
{
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
}
}
@@ -0,0 +1,83 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme;
import com.jme.image.Texture;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Text;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
/**
* Tracks and displays render statistics.
*/
public class StatsDisplay extends Node
{
public StatsDisplay (Renderer renderer)
{
super("StatsNode");
// create an alpha state that will allow us to blend the text on
// top of whatever else is below
AlphaState astate = renderer.createAlphaState();
astate.setBlendEnabled(true);
astate.setSrcFunction(AlphaState.SB_SRC_ALPHA);
astate.setDstFunction(AlphaState.DB_ONE);
astate.setTestEnabled(true);
astate.setTestFunction(AlphaState.TF_GREATER);
astate.setEnabled(true);
// create a font texture
TextureState font = renderer.createTextureState();
font.setTexture(
TextureManager.loadTexture(
getClass().getClassLoader().getResource(DEFAULT_JME_FONT),
Texture.MM_LINEAR, Texture.FM_LINEAR));
font.setEnabled(true);
_text = new Text("StatsLabel", "");
_text.setCullMode(CULL_NEVER);
_text.setTextureCombineMode(TextureState.REPLACE);
attachChild(_text);
setRenderState(font);
setRenderState(astate);
}
public void update (Timer timer, Renderer renderer)
{
_stats.setLength(0);
_stats.append("FPS: ").append((int)timer.getFrameRate());
_stats.append(" - ").append(renderer.getStatistics(_temp));
_text.print(_stats);
}
protected Text _text;
protected StringBuffer _stats = new StringBuffer();
protected StringBuffer _temp = new StringBuffer();
protected static final String DEFAULT_JME_FONT =
"rsrc/media/jme/defaultfont.tga";
}
@@ -0,0 +1,412 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.camera;
import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Plane;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.samskivert.util.ObserverList;
/**
* Provides various useful mechanisms for manipulating the camera.
*/
public class CameraHandler
{
/**
* Creates a new camera handler. The camera begins life at the origin,
* facing in the negative z direction (pointing at the ground).
*/
public CameraHandler (Camera camera)
{
_camera = camera;
resetAxes();
}
/**
* Resets the camera orientation to its initial state.
*/
public void resetAxes ()
{
_camera.getDirection().set(0, 0, -1);
_camera.getLeft().set(-1, 0, 0);
_camera.getUp().set(0, 1, 0);
_camera.update();
_rxdir.set(1, 0, 0);
_rydir.set(0, 1, 0);
}
/**
* Configures limits on the camera tilt.
*/
public void setTiltLimits (float minAngle, float maxAngle)
{
_minTilt = minAngle;
_maxTilt = maxAngle;
}
/**
* Configures limits on the distance the camera can be panned.
*
* @param boundViaFrustum if true instead of bounding the camera's
* position, we will compute the intersections of the view frustum with the
* ground plan and bound that rectangle into the specified bounds.
* <em>Note:</em> the camera must generally be pointing down at the ground
* (up to perhaps 45 degrees or so) for this to work. At higher angles the
* back of the view frustum will intersect the ground plane at or near
* infinity.
*/
public void setPanLimits (float minX, float minY, float maxX, float maxY,
boolean boundViaFrustum)
{
_minX = minX;
_minY = minY;
_maxX = maxX;
_maxY = maxY;
_boundViaFrustum = boundViaFrustum;
}
/**
* Configures the minimum and maximum zoom values allowed for the
* camera.
*/
public void setZoomLimits (float minZoom, float maxZoom)
{
_minZoom = minZoom;
_maxZoom = maxZoom;
}
/**
* Enables or disables the current set of limits.
*/
public void setLimitsEnabled (boolean enabled)
{
_limitsEnabled = enabled;
}
/**
* Sets the camera zoom level to a value between zero (zoomed in maximally)
* and 1 (zoomed out maximally). Zoom limits must have already been set up
* via a call to {@link #setZoomLimits}.
*/
public void setZoomLevel (float level)
{
// Log.info("Zoom " + level + " " + _camera.getLocation());
level = Math.max(0f, Math.min(level, 1f));
_camera.getLocation().z = _minZ + (_maxZ - _minZ) * level;
_camera.update();
}
/**
* Returns the current camera zoom level.
*/
public float getZoomLevel ()
{
return (_camera.getLocation().z - _minZ) / (_maxZ - _minZ);
}
/**
* Adds a camera path observer.
*/
public void addCameraObserver (CameraPath.Observer camobs)
{
_campathobs.add(camobs);
}
/**
* Removes a camera path observer.
*/
public void removeCameraObserver (CameraPath.Observer camobs)
{
_campathobs.remove(camobs);
}
/**
* Starts the camera moving along a path which will be updated every tick
* until it is complete.
*/
public void moveCamera (CameraPath path)
{
if (_campath != null) {
_campath.abort();
_campathobs.apply(new CompletedOp(_campath));
}
_campath = path;
}
/**
* Returns true if the camera is currently animating along a path, false if
* it is not.
*/
public boolean cameraIsMoving ()
{
return (_campath != null);
}
/**
* Skips immediately to the end of the current camera path.
*/
public void skipPath ()
{
// fake an update far enough into the future to trick the camera path
// into thinking it's done
update(100);
}
/**
* This is called by the {@link JmeApp} on every frame to allow the handler
* to update the camera as necessary.
*/
public void update (float frameTime)
{
if (_campath != null) {
if (_campath.tick(frameTime)) {
CameraPath opath = _campath;
_campath = null;
_campathobs.apply(new CompletedOp(opath));
}
}
}
/**
* Returns the camera being manipulated by this handler.
*/
public Camera getCamera ()
{
return _camera;
}
/**
* Adjusts the camera's location. The specified location will be bounded
* within the current pan and zoom limits.
*/
public void setLocation (Vector3f location)
{
_camera.setLocation(bound(location));
_camera.update();
}
/**
* Pans the camera the specified distance in the x and y directions. These
* distances will be multiplied by the current "view" x and y axes and
* added to the camera's position.
*/
public void panCamera (float x, float y)
{
Vector3f loc = _camera.getLocation();
loc.addLocal(_rxdir.mult(x, _temp));
loc.addLocal(_rydir.mult(y, _temp));
setLocation(loc);
}
/**
* Zooms the camera in (distance < 0) and out (distance > 0) by the
* specified amount. The distance is multiplied by the camera's current
* direction and added to its current position, which is then bounded into
* the pan and zoom volume.
*/
public void zoomCamera (float distance)
{
Vector3f loc = _camera.getLocation();
float dist = getGroundPoint().distance(loc),
ndist = Math.min(Math.max(dist + distance, _minZoom), _maxZoom);
if ((distance = ndist - dist) == 0f) {
return;
}
loc.subtractLocal(_camera.getDirection().mult(distance, _temp));
setLocation(loc);
}
/**
* Locates the point on the ground at which the camera is "looking" and
* rotates the camera from that point around the specified vector by the
* specified angle. Additionally zooms the camera in (deltaZoom < 0) or out
* (deltaZoom > 0) along its direction of view by the specified amount.
*/
public void rotateCamera (
Vector3f spot, Vector3f axis, float deltaAngle, float deltaZoom)
{
// get a vector from the camera's current position to the point around
// which we're going to orbit
Vector3f direction = _camera.getLocation().subtract(spot);
// if we're rotating around the left vector, impose tilt limits
if (axis == _camera.getLeft()) {
float angle = FastMath.asin(_ground.normal.dot(direction) /
direction.length());
float nangle = Math.min(Math.max(angle + deltaAngle, _minTilt),
_maxTilt);
if ((deltaAngle = nangle - angle) == 0f) {
return;
}
}
// create a rotation matrix
_rotm.fromAxisAngle(axis, deltaAngle);
// rotate the direction vector and the camera itself
_rotm.mult(direction, direction);
_rotm.mult(_camera.getUp(), _camera.getUp());
_rotm.mult(_camera.getLeft(), _camera.getLeft());
_rotm.mult(_camera.getDirection(), _camera.getDirection());
// if we're rotating around the ground normal, we need to update our
// notion of side-to-side and forward for panning
if (axis == _ground.normal) {
_rotm.mult(_rxdir, _rxdir);
_rotm.mult(_rydir, _rydir);
}
// finally move the camera to its new location, zooming in or out in
// the process
float scale = 1 + (deltaZoom / direction.length());
direction.scaleAdd(scale, spot);
setLocation(direction);
}
/**
* Swings the camera perpendicular to the ground normal, around the point
* on the ground at which it is looking.
*/
public void orbitCamera (float deltaAngle)
{
rotateCamera(getGroundPoint(), _ground.normal, deltaAngle, 0);
}
/**
* Swings the camera perpendicular to its left vector around the point on
* the ground at which it is looking.
*/
public void tiltCamera (float deltaAngle)
{
rotateCamera(getGroundPoint(), _camera.getLeft(), deltaAngle, 0);
}
/**
* Returns the point on the ground (z = 0) at which the camera is looking.
*/
public Vector3f getGroundPoint ()
{
float dist = -1f * _ground.normal.dot(_camera.getLocation()) /
_ground.normal.dot(_camera.getDirection());
return _camera.getLocation().add(_camera.getDirection().mult(dist));
}
/**
* Returns the ground normal. Z is the default ground plane and the normal
* points in the positive z direction. <em>Do not modify this value.</em>
*/
public Vector3f getGroundNormal ()
{
return _ground.normal;
}
protected Vector3f bound (Vector3f loc)
{
if (!_limitsEnabled) {
return loc;
}
if (_boundViaFrustum) {
bound(_camera.getFrustumLeft(), _camera.getFrustumTop(), loc);
bound(_camera.getFrustumLeft(), _camera.getFrustumBottom(), loc);
bound(_camera.getFrustumRight(), _camera.getFrustumTop(), loc);
bound(_camera.getFrustumRight(), _camera.getFrustumBottom(), loc);
} else {
loc.x = Math.max(Math.min(loc.x, _maxX), _minX);
loc.y = Math.max(Math.min(loc.y, _maxY), _minY);
}
loc.z = Math.max(Math.min(loc.z, _maxZ), _minZ);
return loc;
}
protected void bound (float left, float up, Vector3f loc)
{
// start with the location of the camera moved out into the near
// frustum plane
_temp.set(loc);
_temp.scaleAdd(_camera.getFrustumNear(), _camera.getDirection(), loc);
// then slide it over to a corner of the near frustum rectangle
_temp.scaleAdd(left, _camera.getLeft(), _temp);
_temp.scaleAdd(up, _camera.getUp(), _temp);
// turn this into a vector with origin at the camera's location
_temp.subtractLocal(loc);
_temp.normalizeLocal();
// determine the intersection of said vector with the ground plane
float dist = -1f * _ground.normal.dot(loc) / _ground.normal.dot(_temp);
_temp.scaleAdd(dist, _temp, loc);
// we then assume that if the corner of the "viewable ground rectangle"
// is outside our bounds that we can simply adjust the camera location
// by the amount it is out of bounds
if (_temp.x > _maxX) {
loc.x -= (_temp.x - _maxX);
} else if (_temp.x < _minX) {
loc.x += (_minX - _temp.x);
}
if (_temp.y > _maxY) {
loc.y -= (_temp.y - _maxY);
} else if (_temp.y < _minY) {
loc.y += (_minY - _temp.y);
}
}
/** Used to dispatch {@link CameraPath.Observer#pathCompleted}. */
protected static class CompletedOp implements ObserverList.ObserverOp
{
public CompletedOp (CameraPath path) {
_path = path;
}
public boolean apply (Object observer) {
return ((CameraPath.Observer)observer).pathCompleted(_path);
}
protected CameraPath _path;
}
protected Camera _camera;
protected CameraPath _campath;
protected ObserverList _campathobs =
new ObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY);
protected Matrix3f _rotm = new Matrix3f();
protected Vector3f _temp = new Vector3f();
protected boolean _boundViaFrustum, _limitsEnabled = true;
protected float _minX = -Float.MAX_VALUE, _maxX = Float.MAX_VALUE;
protected float _minY = -Float.MAX_VALUE, _maxY = Float.MAX_VALUE;
protected float _minZ = -Float.MAX_VALUE, _maxZ = Float.MAX_VALUE;
protected float _minZoom = 0f, _maxZoom = Float.MAX_VALUE;
protected float _minTilt = -Float.MAX_VALUE, _maxTilt = Float.MAX_VALUE;
protected Vector3f _rxdir = new Vector3f(1, 0, 0);
protected Vector3f _rydir = new Vector3f(0, 1, 0);
protected static final Vector3f _xdir = new Vector3f(1, 0, 0);
protected static final Vector3f _ydir = new Vector3f(0, 1, 0);
protected static final Plane _ground = new Plane(new Vector3f(0, 0, 1), 0);
}
@@ -0,0 +1,69 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.camera;
import com.jme.renderer.Camera;
/**
* Used to move the camera along a particular path.
*/
public abstract class CameraPath
{
/** Used to inform observers when a camera path is completed or aborted. */
public interface Observer
{
/**
* Called when this path is finished (potentially early because another
* path was set before this path completed).
*
* @param path the path that was completed.
*
* @return true if the observer should remain in the list, false if it
* should be removed following this notification.
*/
public boolean pathCompleted (CameraPath path);
}
/**
* This is called on every frame to allow the path to adjust the position
* of the camera.
*
* @return true if the path is completed and can be disposed, false if it
* is not yet completed.
*/
public abstract boolean tick (float secondsSince);
/**
* Called if this path is aborted prior to completion due to being replaced
* by a new camera path.
*/
public void abort ()
{
}
protected CameraPath (CameraHandler camhand)
{
_camhand = camhand;
}
protected CameraHandler _camhand;
}
@@ -0,0 +1,191 @@
//
// $Id$
package com.threerings.jme.camera;
import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.RelativeMouse;
import com.jme.input.action.*;
import com.jme.input.action.InputActionEvent;
import com.threerings.jme.JmeApp;
import com.threerings.jme.Log;
/**
* Sets up camera controls for moving around from a top-down perspective,
* suitable for strategy games and their ilk. The "ground" is assumed to be the
* XY plane.
*/
public class GodViewHandler extends InputHandler
{
/**
* Creates the handler.
*
* @param cam The camera to move with this handler.
*/
public GodViewHandler (CameraHandler camhand)
{
_camhand = camhand;
setKeyBindings();
addActions();
}
protected void setKeyBindings ()
{
KeyBindingManager keyboard = KeyBindingManager.getKeyBindingManager();
// the key bindings for the pan actions
keyboard.set("forward", KeyInput.KEY_W);
keyboard.set("arrow_forward", KeyInput.KEY_UP);
keyboard.set("backward", KeyInput.KEY_S);
keyboard.set("arrow_backward", KeyInput.KEY_DOWN);
keyboard.set("left", KeyInput.KEY_A);
keyboard.set("arrow_left", KeyInput.KEY_LEFT);
keyboard.set("right", KeyInput.KEY_D);
keyboard.set("arrow_right", KeyInput.KEY_RIGHT);
// the key bindings for the zoom actions
keyboard.set("zoomIn", KeyInput.KEY_UP);
keyboard.set("zoomOut", KeyInput.KEY_DOWN);
// the key bindings for the orbit actions
keyboard.set("turnRight", KeyInput.KEY_RIGHT);
keyboard.set("turnLeft", KeyInput.KEY_LEFT);
// the key bindings for the tilt actions
keyboard.set("tiltForward", KeyInput.KEY_HOME);
keyboard.set("tiltBack", KeyInput.KEY_END);
keyboard.set("screenshot", KeyInput.KEY_F12);
}
protected void addActions ()
{
addAction(new KeyScreenShotAction(), "screenshot", false);
addPanActions();
addZoomActions();
addOrbitActions();
addTiltActions();
}
/**
* Adds actions for panning the camera around the scene.
*/
protected void addPanActions ()
{
InputAction forward = new InputAction() {
public void performAction (InputActionEvent evt) {
_camhand.panCamera(0, speed * evt.getTime());
}
};
forward.setSpeed(0.5f);
addAction(forward, "forward", true);
addAction(forward, "arrow_forward", true);
InputAction backward = new InputAction() {
public void performAction (InputActionEvent evt) {
_camhand.panCamera(0, -speed * evt.getTime());
}
};
backward.setSpeed(0.5f);
addAction(backward, "backward", true);
addAction(backward, "arrow_backward", true);
InputAction left = new InputAction() {
public void performAction (InputActionEvent evt) {
_camhand.panCamera(-speed * evt.getTime(), 0);
}
};
left.setSpeed(0.5f);
addAction(left, "left", true);
addAction(left, "arrow_left", true);
InputAction right = new InputAction() {
public void performAction (InputActionEvent evt) {
_camhand.panCamera(speed * evt.getTime(), 0);
}
};
right.setSpeed(0.5f);
addAction(right, "right", true);
addAction(right, "arrow_right", true);
}
/**
* Adds actions for zooming the camaera in and out.
*/
protected void addZoomActions ()
{
InputAction zoomIn = new InputAction() {
public void performAction (InputActionEvent evt) {
_camhand.zoomCamera(-speed * evt.getTime());
}
};
zoomIn.setSpeed(0.5f);
addAction(zoomIn, "zoomIn", true);
InputAction zoomOut = new InputAction() {
public void performAction (InputActionEvent evt) {
_camhand.zoomCamera(speed * evt.getTime());
}
};
zoomOut.setSpeed(0.5f);
addAction(zoomOut, "zoomOut", true);
}
/**
* Adds actions for orbiting the camera around the viewpoint.
*/
protected void addOrbitActions ()
{
addAction(new OrbitAction(-FastMath.PI / 2), "turnRight", true);
addAction(new OrbitAction(FastMath.PI / 2), "turnLeft", true);
}
/**
* Adds actions for tilting the camera (rotating around the yaw axis).
*/
protected void addTiltActions ()
{
addAction(new TiltAction(-FastMath.PI / 2), "tiltForward", true);
addAction(new TiltAction(FastMath.PI / 2), "tiltBack", true);
}
protected class OrbitAction extends InputAction
{
public OrbitAction (float radPerSec)
{
_radPerSec = radPerSec;
}
public void performAction (InputActionEvent evt)
{
_camhand.orbitCamera(_radPerSec * evt.getTime());
}
protected float _radPerSec;
}
protected class TiltAction extends InputAction
{
public TiltAction (float radPerSec)
{
_radPerSec = radPerSec;
}
public void performAction (InputActionEvent evt)
{
_camhand.tiltCamera(_radPerSec * evt.getTime());
}
protected float _radPerSec;
}
protected CameraHandler _camhand;
}
@@ -0,0 +1,87 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.camera;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
/**
* Pans the camera to the specified location in the specified amount of time.
*/
public class PanPath extends CameraPath
{
/**
* Creates a panning path for the specified camera that will retain the
* camera's current orientation.
*
* @param target the target position for the camera.
* @param duration the number of seconds in which to pan the camera.
*/
public PanPath (CameraHandler camhand, Vector3f target, float duration)
{
this(camhand, target, null, duration);
}
/**
* Creates a panning path for the specified camera.
*
* @param target the target position for the camera.
* @param trot the target rotation for the camera (or <code>null</code> to
* keep its current orientation)
* @param duration the number of seconds in which to pan the camera.
*/
public PanPath (CameraHandler camhand, Vector3f target, Quaternion trot,
float duration)
{
super(camhand);
Camera cam = camhand.getCamera();
_start = new Vector3f(cam.getLocation());
_velocity = target.subtract(_start);
_velocity.divideLocal(duration);
if (trot != null) {
_irot = new Quaternion();
_irot.fromAxes(cam.getLeft(), cam.getUp(), cam.getDirection());
_trot = trot;
}
_duration = duration;
}
// documentation inherited
public boolean tick (float secondsSince)
{
_elapsed = Math.min(_elapsed + secondsSince, _duration);
_camloc.scaleAdd(_elapsed, _velocity, _start);
_camhand.setLocation(_camloc);
if (_irot != null) {
_camhand.getCamera().setAxes(_axes.slerp(_irot, _trot,
_elapsed / _duration));
}
return (_elapsed >= _duration);
}
protected Vector3f _start, _velocity, _camloc = new Vector3f();
protected Quaternion _irot, _trot, _axes = new Quaternion();
protected float _elapsed, _duration;
}
@@ -0,0 +1,113 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.camera;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.threerings.jme.Log;
/**
* Moves the camera along a cubic Hermite spline path defined by the start and
* end locations and directions. Spline formulas obtained from
* <a href="http://en.wikipedia.org/wiki/Cubic_Hermite_spline">Wikipedia</a>.
*/
public class SplinePath extends CameraPath
{
/**
* Creates a cubic spline path for the camera to follow.
*
* @param tloc the target location
* @param tdir the target direction
* @param axis the heading axis (typically {@link Vector3f#UNIT_Z})
* @param duration the duration of the path
* @param tension the tension parameter, which can range from zero to one:
* higher tension values create a more direct path with a sharper turn,
* lower values create a rounder path with a smoother turn
*/
public SplinePath (CameraHandler camhand, Vector3f tloc,
Vector3f tdir, Vector3f axis, float duration, float tension)
{
super(camhand);
// get the spline function coefficients
Camera cam = camhand.getCamera();
_p0 = new Vector3f(cam.getLocation());
_p1 = new Vector3f(tloc);
float tscale = (1f - tension) * _p0.distance(_p1);
_m0 = cam.getDirection().mult(tscale);
_m1 = tdir.mult(tscale);
_axis = axis;
_duration = duration;
}
// documentation inherited
public boolean tick (float secondsSince)
{
_elapsed = Math.min(_elapsed + secondsSince, _duration);
float t = _elapsed / _duration, t2 = t*t, t3 = t2*t,
h00 = 2*t3 - 3*t2 + 1,
h00p = 6*t2 - 6*t,
h10 = t3 - 2*t2 + t,
h10p = 3*t2 - 4*t + 1,
h01 = -2*t3 + 3*t2,
h01p = -6*t2 + 6*t,
h11 = t3 - t2,
h11p = 3*t2 - 2*t;
// take the derivative to find the direction
_p0.mult(h00p, _dir);
_dir.scaleAdd(h10p, _m0, _dir);
_dir.scaleAdd(h01p, _p1, _dir);
_dir.scaleAdd(h11p, _m1, _dir);
_dir.normalizeLocal();
// evaluate the spline function to find the location
_p0.mult(h00, _loc);
_loc.scaleAdd(h10, _m0, _loc);
_loc.scaleAdd(h01, _p1, _loc);
_loc.scaleAdd(h11, _m1, _loc);
// compute the left and up vectors using the direction and
// axis vectors
Camera cam = _camhand.getCamera();
_axis.cross(_dir, _left);
_left.normalizeLocal();
_dir.cross(_left, _up);
// update the camera's location and orientation
cam.setFrame(_loc, _left, _up, _dir);
return _elapsed >= _duration;
}
/** The parameters of the spline. */
Vector3f _p0, _m0, _p1, _m1, _axis;
/** Working vectors. */
Vector3f _loc = new Vector3f(), _left = new Vector3f(),
_up = new Vector3f(), _dir = new Vector3f();
/** The elapsed time and total duration. */
float _elapsed, _duration;
}
@@ -0,0 +1,168 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.camera;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.threerings.jme.Log;
/**
* Swings the camera around a point of interest (which should be somewhere
* along the camera's view vector). Also optionally pans the camera and/or
* zooms it in or out (moves it along its view vector) in the process.
*
* <p align="center"><img src="rotate_zoom.png">
*/
public class SwingPath extends CameraPath
{
/**
* Creates a rotating, zooming path for the specified camera.
*
* @param spot the point of interest around which to swing the camera.
* @param axis the axis around which to rotate the camera.
* @param angle the angle through which to rotate the camera.
* @param angvel the (absolute value of the) velocity at which to rotate
* the camera (in radians per second).
* @param zoom the distance to zoom along the camera's view axis (negative
* = in, positive = out).
*/
public SwingPath (CameraHandler camhand, Vector3f spot, Vector3f axis,
float angle, float angvel, float zoom)
{
this(camhand, spot, axis, angle, angvel, null, 0f, null, zoom);
}
/**
* Creates a rotating, panning, zooming path for the specified camera.
*
* @param spot the point of interest around which to swing the camera.
* @param paxis the primary axis around which to rotate the camera.
* @param pangle the angle through which to rotate the camera about the
* primary axis.
* @param angvel the (absolute value of the) velocity at which to rotate
* the camera (in radians per second) about the primary angle.
* @param saxis the secondary axis around which to rotate the camera, or
* <code>null</code> for none
* @param sangle the angle through which to rotate the camera about the
* secondary axis
* @param pan the amount to pan the camera, or <code>null</code> for none
* @param zoom the distance to zoom along the camera's view axis (negative
* = in, positive = out).
*/
public SwingPath (CameraHandler camhand, Vector3f spot, Vector3f paxis,
float pangle, float angvel, Vector3f saxis, float sangle,
Vector3f pan, float zoom)
{
super(camhand);
if (pangle == 0) {
Log.warning("Requested to swing camera through zero degrees " +
"[spot=" + spot + ", paxis=" + paxis +
", angvel=" + angvel + ", zoom=" + zoom + "].");
pangle = 0.0001f;
}
if (angvel <= 0) {
Log.warning("Requested to swing camera with invalid velocity " +
"[spot=" + spot + ", paxis=" + paxis + ", pangle=" +
pangle + ", angvel=" + angvel + ", zoom=" + zoom +
"].");
angvel = FastMath.PI;
}
_spot = new Vector3f(spot);
_paxis = paxis;
_pangle = pangle;
_pangvel = (pangle > 0) ? angvel : -1 * angvel;
_saxis = (saxis == null) ? null : new Vector3f(saxis);
_sangle = sangle;
_sangvel = _sangle * _pangvel / _pangle;
_pan = pan;
if (_pan != null) {
_panvel = _pan.mult(_pangvel / _pangle);
_panned = new Vector3f();
}
_zoom = zoom;
_zoomvel = _zoom * _pangvel / _pangle;
// Log.info("Swinging camera [angle=" + _angle + ", angvel=" + _angvel +
// ", zoom=" + _zoom + ", zoomvel=" + _zoomvel + "].");
}
// documentation inherited
public boolean tick (float secondsSince)
{
float deltaPAngle = (secondsSince * _pangvel);
float deltaSAngle = (secondsSince * _sangvel);
float deltaZoom = (secondsSince * _zoomvel);
_protated += deltaPAngle;
_srotated += deltaSAngle;
_zoomed += deltaZoom;
if (_pan != null) {
_panvel.mult(secondsSince, _deltaPan);
_panned.addLocal(_deltaPan);
}
// clamp our rotation at the target angle and determine whether or not
// we're done
boolean done = false;
if (_pangle > 0 && _protated > _pangle ||
_pangle < 0 && _protated < _pangle) {
deltaPAngle -= (_protated - _pangle);
deltaSAngle -= (_srotated - _sangle);
deltaZoom -= (_zoomed - _zoom);
if (_pan != null) {
_deltaPan.subtractLocal(_panned).addLocal(_pan);
}
_protated = _pangle;
_srotated = _sangle;
_panned = _pan;
_zoomed = _zoom;
done = true;
}
// have the camera handler do the necessary rotating, panning, zooming
if (_pan != null) {
_camhand.setLocation(
_camhand.getCamera().getLocation().addLocal(_deltaPan));
_spot.addLocal(_deltaPan);
}
_camhand.rotateCamera(_spot, _paxis, deltaPAngle, deltaZoom);
if (_saxis != null) {
_rot.fromAngleAxis(deltaPAngle, _paxis).multLocal(_saxis);
_camhand.rotateCamera(_spot, _saxis, deltaSAngle, 0f);
}
return done;
}
protected Vector3f _spot, _paxis, _saxis;
protected float _pangle, _pangvel, _protated;
protected float _sangle, _sangvel, _srotated;
protected Vector3f _pan, _panvel, _panned;
protected float _zoom, _zoomvel, _zoomed;
protected Quaternion _rot = new Quaternion();
protected Vector3f _deltaPan = new Vector3f();
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1,181 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.chat;
import java.util.StringTokenizer;
import com.jmex.bui.BContainer;
import com.jmex.bui.BTextArea;
import com.jmex.bui.BTextField;
import com.jmex.bui.event.ActionEvent;
import com.jmex.bui.event.ActionListener;
import com.jmex.bui.layout.BorderLayout;
import com.jme.renderer.ColorRGBA;
import com.threerings.util.Name;
import com.threerings.crowd.chat.client.ChatDirector;
import com.threerings.crowd.chat.client.ChatDisplay;
import com.threerings.crowd.chat.client.SpeakService;
import com.threerings.crowd.chat.data.ChatCodes;
import com.threerings.crowd.chat.data.ChatMessage;
import com.threerings.crowd.chat.data.SystemMessage;
import com.threerings.crowd.chat.data.UserMessage;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.jme.JmeContext;
import com.threerings.jme.Log;
/**
* Displays chat messages and allows for their input.
*/
public class ChatView extends BContainer
implements ChatDisplay
{
public ChatView (JmeContext ctx, ChatDirector chatdtr)
{
_chatdtr = chatdtr;
setLayoutManager(new BorderLayout(2, 2));
add(_text = new BTextArea(), BorderLayout.CENTER);
add(_input = new BTextField(), BorderLayout.SOUTH);
_input.addListener(new ActionListener() {
public void actionPerformed (ActionEvent event) {
if (handleInput(_input.getText())) {
_input.setText("");
}
}
});
}
public void willEnterPlace (PlaceObject plobj)
{
setSpeakService(plobj.speakService);
}
public void didLeavePlace (PlaceObject plobj)
{
clearSpeakService();
}
public void setSpeakService (SpeakService spsvc)
{
if (spsvc != null) {
_spsvc = spsvc;
_chatdtr.addChatDisplay(this);
}
}
public void clearSpeakService ()
{
if (_spsvc != null) {
_chatdtr.removeChatDisplay(this);
_spsvc = null;
}
}
/**
* Instructs our chat input field to request focus.
*/
public void requestFocus ()
{
_input.requestFocus();
}
// documentation inherited from interface ChatDisplay
public void clear ()
{
}
// documentation inherited from interface ChatDisplay
public void displayMessage (ChatMessage msg)
{
// we may be restricted in the chat types we handle
if (!handlesType(msg.localtype)) {
return;
}
if (msg instanceof UserMessage) {
UserMessage umsg = (UserMessage) msg;
if (umsg.localtype == ChatCodes.USER_CHAT_TYPE) {
append("[" + umsg.speaker + " whispers] ", ColorRGBA.green);
append(umsg.message + "\n");
} else {
append("<" + umsg.speaker + "> ", ColorRGBA.blue);
append(umsg.message + "\n");
}
} else if (msg instanceof SystemMessage) {
append(msg.message + "\n", ColorRGBA.red);
} else {
Log.warning("Received unknown message type: " + msg + ".");
}
}
// documentation inherited
public void setEnabled (boolean enabled)
{
_input.setEnabled(enabled);
}
protected void displayError (String message)
{
append(message + "\n", ColorRGBA.red);
}
protected void append (String text, ColorRGBA color)
{
_text.appendText(text, color);
}
protected void append (String text)
{
_text.appendText(text);
}
protected boolean handlesType (String localType)
{
return true;
}
protected boolean handleInput (String text)
{
if (text.length() == 0) {
// no empty banter
return false;
}
String msg = _chatdtr.requestChat(_spsvc, text, true);
if (msg.equals(ChatCodes.SUCCESS)) {
return true;
} else {
_chatdtr.displayFeedback(null, msg);
return false;
}
}
protected ChatDirector _chatdtr;
protected SpeakService _spsvc;
protected BTextArea _text;
protected BTextField _input;
}
@@ -0,0 +1,148 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.effect;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.system.DisplaySystem;
import com.threerings.jme.util.LinearTimeFunction;
import com.threerings.jme.util.TimeFunction;
/**
* Fades a supplied quad (or one that covers the screen) in from a solid color
* or out to a solid color.
*/
public class FadeInOutEffect extends Node
{
public FadeInOutEffect (ColorRGBA color, float startAlpha, float endAlpha,
float duration, boolean overUI)
{
this(color, new LinearTimeFunction(startAlpha, endAlpha, duration),
overUI);
}
public FadeInOutEffect (ColorRGBA color, TimeFunction alphaFunc,
boolean overUI)
{
this(null, color, alphaFunc, overUI);
setQuad(createCurtain());
}
public FadeInOutEffect (Quad quad, ColorRGBA color, TimeFunction alphaFunc,
boolean overUI)
{
super("FadeInOut");
_color = new ColorRGBA(
color.r, color.g, color.b, alphaFunc.getValue(0));
_alphaFunc = alphaFunc;
DisplaySystem ds = DisplaySystem.getDisplaySystem();
_astate = ds.getRenderer().createAlphaState();
_astate.setBlendEnabled(true);
_astate.setSrcFunction(AlphaState.SB_SRC_ALPHA);
_astate.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA);
_astate.setEnabled(true);
if (quad != null) {
setQuad(quad);
}
setRenderQueueMode(Renderer.QUEUE_ORTHO);
setZOrder(overUI ? -1 : 1);
}
/**
* Configures the quad that will be faded in or out.
*/
public void setQuad (Quad quad)
{
attachChild(quad);
quad.setDefaultColor(_color);
quad.setRenderState(_astate);
quad.updateRenderState();
}
/**
* Allows the fade to be paused.
*/
public void setPaused (boolean paused)
{
_paused = paused;
}
/**
* Indicates whether or not the fade is paused.
*/
public boolean isPaused ()
{
return _paused;
}
// documentation inherited
public void updateGeometricState (float time, boolean initiator)
{
super.updateGeometricState(time, initiator);
if (_paused) {
return;
}
float alpha = _alphaFunc.getValue(time);
_color.a = Math.min(1f, Math.max(0f, alpha));
if (_alphaFunc.isComplete()) {
fadeComplete();
}
}
/**
* Called (only once) when we have reached the end of our fade.
* Automatically detaches this effect from the hierarchy.
*/
protected void fadeComplete ()
{
getParent().detachChild(this);
}
/**
* Creates a quad that covers the entire screen for full-screen fades.
*/
protected Quad createCurtain ()
{
// create a quad the size of the screen
DisplaySystem ds = DisplaySystem.getDisplaySystem();
float width = ds.getWidth(), height = ds.getHeight();
Quad curtain = new Quad("curtain" + hashCode(), width, height);
curtain.setLocalTranslation(new Vector3f(width/2, height/2, 0f));
return curtain;
}
protected ColorRGBA _color;
protected AlphaState _astate;
protected TimeFunction _alphaFunc;
protected boolean _paused;
}
@@ -0,0 +1,127 @@
//
// $Id$
package com.threerings.jme.effect;
import com.jme.scene.Node;
import com.jme.system.DisplaySystem;
import com.jmex.bui.BWindow;
import com.threerings.jme.util.LinearTimeFunction;
import com.threerings.jme.util.TimeFunction;
/**
* Slides a window onto the center of the screen from offscreen or offscreen
* from the center of the screen.
*/
public class WindowSlider extends Node
{
public static final int FROM_TOP = 0;
public static final int FROM_RIGHT = 1;
public static final int TO_TOP = 2;
public static final int TO_RIGHT = 3;
/**
* Creates a window slider with the specified mode and window that will
* slide the window either onto or off of the screen in the specified
* number of seconds.
*
* @param dx an offset applied to the starting or destination position
* along the x axis (starting for sliding off, destination for sliding on).
* @param dy an offset applied along the y axis.
*/
public WindowSlider (BWindow window, int mode, float duration,
int dx, int dy)
{
super("slider");
DisplaySystem ds = DisplaySystem.getDisplaySystem();
int swidth = ds.getWidth(), sheight = ds.getHeight();
int wwidth = window.getWidth(), wheight = window.getHeight();
int start = 0, end = 0;
switch (mode) {
case FROM_TOP:
start = sheight+wheight;
end = (sheight-wheight)/2 + dy;
window.setLocation((swidth-wwidth)/2 + dx, start);
break;
case FROM_RIGHT:
start = swidth+wwidth;
end = (swidth-wwidth)/2 + dx;
window.setLocation(start, (sheight-wheight)/2 + dy);
break;
case TO_TOP:
start = (sheight-wheight)/2 + dy;
end = sheight+wheight;
window.setLocation((swidth-wwidth)/2 + dx, start);
break;
case TO_RIGHT:
start = (swidth-wwidth)/2 + dx;
end = swidth+wwidth;
window.setLocation(start, (sheight-wheight)/2 + dy);
break;
}
_mode = mode;
_window = window;
_tfunc = new LinearTimeFunction(start, end, duration);
}
/**
* Allows some number of ticks to be skipped to give the window that is
* being slid a chance to be layed out before we start keeping track of
* time. The layout may be expensive and cause the frame rate to drop for a
* frame or two, thus booching our smooth sliding onto the screen.
*/
public void setSkipTicks (int skipTicks)
{
_skipTicks = skipTicks;
}
// documentation inherited
public void updateGeometricState (float time, boolean initiator)
{
super.updateGeometricState(time, initiator);
// skip ticks as long as we need to
if (_skipTicks-- > 0) {
return;
}
int winx, winy;
if (_mode % 2 == 1) {
winx = (int)_tfunc.getValue(time);
winy = _window.getY();
} else {
winx = _window.getX();
winy = (int)_tfunc.getValue(time);
}
_window.setLocation(winx, winy);
if (_tfunc.isComplete()) {
slideComplete();
}
}
/**
* Called (only once) when we have reached the end of our slide.
* Automatically detaches this effect from the hierarchy.
*/
protected void slideComplete ()
{
getParent().detachChild(this);
}
protected int _mode;
protected BWindow _window;
protected TimeFunction _tfunc;
// skip two frames by default as that generally handles the normal window
// layout process
protected int _skipTicks = 2;
}
@@ -0,0 +1,79 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.util.Properties;
import com.jme.math.Vector3f;
import com.jme.scene.Geometry;
import com.jme.scene.Spatial;
import com.jme.util.geom.BufferUtils;
/**
* A model controller whose target represents an emitter.
*/
public abstract class EmissionController extends ModelController
{
@Override // documentation inherited
public void configure (Properties props, Spatial target)
{
// substitute underlying mesh for geometry targets
if (target instanceof ModelNode) {
Spatial mesh = ((ModelNode)target).getChild("mesh");
if (mesh != null) {
target = mesh;
}
}
super.configure(props, target);
}
@Override // documentation inherited
public void init (Model model)
{
super.init(model);
_target.setCullMode(Spatial.CULL_ALWAYS);
}
/**
* Determines the current location of the emitter in world coordinates.
*/
protected void getEmitterLocation (Vector3f result)
{
result.set(_target.getWorldTranslation());
}
/**
* Determines the current direction of the emitter in world coordinates.
*/
protected void getEmitterDirection (Vector3f result)
{
if (_target instanceof Geometry) {
BufferUtils.populateFromBuffer(result,
((Geometry)_target).getNormalBuffer(0), 0);
} else {
result.set(0f, 0f, -1f);
}
_target.getWorldRotation().multLocal(result);
}
private static final long serialVersionUID = 1;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,167 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.samskivert.util.StringUtil;
/**
* The superclass of procedural animation controllers for models.
*/
public abstract class ModelController extends Controller
implements Externalizable
{
/**
* Configures this controller based on the supplied (sub-)properties and
* controller target.
*/
public void configure (Properties props, Spatial target)
{
_target = target;
String[] anims = StringUtil.parseStringArray(
props.getProperty("animations", ""));
if (anims.length == 0) {
return;
}
_animations = new HashSet<String>();
Collections.addAll(_animations, anims);
}
/**
* Returns a reference to the controller's target.
*/
public Spatial getTarget ()
{
return _target;
}
/**
* Resolves any textures required by the controller.
*/
public void resolveTextures (TextureProvider tprov)
{
}
/**
* Initializes this controller.
*/
public void init (Model model)
{
model.addAnimationObserver(_animobs);
if (_animations != null) {
setActive(false);
}
}
/**
* Creates or populates and returns a clone of this object using the given
* clone properties.
*
* @param store an instance of this class to populate, or <code>null</code>
* to create a new instance
*/
public Controller putClone (
Controller store, Model.CloneCreator properties)
{
if (store == null) {
return null;
}
ModelController mstore = (ModelController)store;
mstore._target = ((ModelSpatial)_target).putClone(null, properties);
mstore._animations = _animations;
return mstore;
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
out.writeObject(_target);
out.writeObject(_animations);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
_target = (Spatial)in.readObject();
_animations = (HashSet<String>)in.readObject();
}
/**
* Called when an animation is started on the model.
*/
protected void animationStarted (String anim)
{
if (_animations != null && _animations.contains(anim)) {
setActive(true);
}
}
/**
* Called when an animation is stopped on the model.
*/
protected void animationStopped (String anim)
{
if (_animations != null) {
setActive(false);
}
}
/** The target to control. */
protected Spatial _target;
/** The animations for which this controller should be active, or
* <code>null</code> for all of them. */
protected HashSet<String> _animations;
/** Listens to the model's animation state. */
protected Model.AnimationObserver _animobs =
new Model.AnimationObserver() {
public boolean animationStarted (Model model, String anim) {
ModelController.this.animationStarted(anim);
return true;
}
public boolean animationCompleted (Model model, String anim) {
animationStopped(anim);
return true;
}
public boolean animationCancelled (Model model, String anim) {
animationStopped(anim);
return true;
}
};
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,557 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Properties;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.SharedMesh;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
/**
* A {@link TriMesh} with a serialization mechanism tailored to stored models.
*/
public class ModelMesh extends TriMesh
implements Externalizable, ModelSpatial
{
/**
* No-arg constructor for deserialization.
*/
public ModelMesh ()
{
super("mesh");
}
/**
* Creates a mesh with no vertex data.
*/
public ModelMesh (String name)
{
super(name);
}
/**
* Configures this mesh based on the given parameters and (sub-)properties.
*
* @param texture the texture specified in the model export, if any (can be
* overridden by textures specified in the properties)
* @param solid whether or not the mesh allows back face culling
* @param transparent whether or not the mesh is (partially) transparent
*/
public void configure (
boolean solid, String texture, boolean transparent, Properties props)
{
_textures = (texture == null) ? null : StringUtil.parseStringArray(
props.getProperty(texture, texture));
_solid = solid;
_transparent = transparent;
}
/**
* Sets the buffers as {@link ByteBuffer}s, because we can't create byte
* views of non-byte buffers. This method is where the model is
* initialized after loading.
*/
public void reconstruct (
ByteBuffer vertices, ByteBuffer normals, ByteBuffer colors,
ByteBuffer textures, ByteBuffer indices)
{
reconstruct(
vertices == null ? null : vertices.asFloatBuffer(),
normals == null ? null : normals.asFloatBuffer(),
colors == null ? null : colors.asFloatBuffer(),
textures == null ? null : textures.asFloatBuffer(),
indices == null ? null : indices.asIntBuffer());
_vertexByteBuffer = vertices;
_normalByteBuffer = normals;
_colorByteBuffer = colors;
_textureByteBuffer = textures;
_indexByteBuffer = indices;
// initialize the model if we're displaying
if (DisplaySystem.getDisplaySystem() == null) {
return;
}
if (_backCull == null) {
initSharedStates();
}
if (_solid) {
setRenderState(_backCull);
}
if (_transparent) {
setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
setRenderState(_blendAlpha);
setRenderState(_overlayZBuffer);
}
}
/**
* Adjusts the vertices and the transform of the mesh so that the mesh's
* position lies at the center of its bounding volume.
*/
public void centerVertices ()
{
Vector3f offset = getBatch(0).getModelBound().getCenter().negate();
if (!offset.equals(Vector3f.ZERO)) {
getLocalTranslation().subtractLocal(offset);
getBatch(0).getModelBound().getCenter().set(Vector3f.ZERO);
getBatch(0).translatePoints(offset);
}
}
@Override // documentation inherited
public void reconstruct (
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
FloatBuffer textures, IntBuffer indices)
{
super.reconstruct(vertices, normals, colors, textures, indices);
_vertexBufferSize = (vertices == null) ? 0 : vertices.capacity();
_normalBufferSize = (normals == null) ? 0 : normals.capacity();
_colorBufferSize = (colors == null) ? 0 : colors.capacity();
_textureBufferSize = (textures == null) ? 0 : textures.capacity();
_indexBufferSize = (indices == null) ? 0 : indices.capacity();
}
@Override // documentation inherited
public void reconstruct (
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
FloatBuffer textures)
{
super.reconstruct(vertices, normals, colors, textures);
_vertexBufferSize = (vertices == null) ? 0 : vertices.capacity();
_normalBufferSize = (normals == null) ? 0 : normals.capacity();
_colorBufferSize = (colors == null) ? 0 : colors.capacity();
_textureBufferSize = (textures == null) ? 0 : textures.capacity();
}
// documentation inherited from interface ModelSpatial
public Spatial putClone (Spatial store, Model.CloneCreator properties)
{
ModelMesh mstore = (ModelMesh)properties.originalToCopy.get(this);
if (mstore != null) {
return mstore;
} else if (store == null) {
mstore = new ModelMesh(getName());
} else {
mstore = (ModelMesh)store;
}
properties.originalToCopy.put(this, mstore);
mstore.normalsMode = normalsMode;
mstore.cullMode = cullMode;
for (int ii = 0; ii < RenderState.RS_MAX_STATE; ii++) {
RenderState rstate = getRenderState(ii);
if (rstate != null) {
mstore.setRenderState(rstate);
}
}
mstore.renderQueueMode = renderQueueMode;
mstore.lockedMode = lockedMode;
mstore.lightCombineMode = lightCombineMode;
mstore.textureCombineMode = textureCombineMode;
mstore.name = name;
mstore.isCollidable = isCollidable;
mstore.localRotation.set(localRotation);
mstore.localTranslation.set(localTranslation);
mstore.localScale.set(localScale);
for (Object controller : getControllers()) {
if (controller instanceof ModelController) {
mstore.addController(
((ModelController)controller).putClone(null, properties));
}
}
TriangleBatch batch = (TriangleBatch)getBatch(0),
mbatch = (TriangleBatch)mstore.getBatch(0);
mbatch.setVertexBuffer(properties.isSet("vertices") ?
batch.getVertexBuffer() :
BufferUtils.clone(batch.getVertexBuffer()));
mbatch.setColorBuffer(properties.isSet("colors") ?
batch.getColorBuffer() :
BufferUtils.clone(batch.getColorBuffer()));
mbatch.setNormalBuffer(properties.isSet("normals") ?
batch.getNormalBuffer() :
BufferUtils.clone(batch.getNormalBuffer()));
FloatBuffer texcoords;
for (int ii = 0; (texcoords = batch.getTextureBuffer(ii)) != null;
ii++) {
mbatch.setTextureBuffer(properties.isSet("texcoords") ?
texcoords : BufferUtils.clone(texcoords), ii);
}
mbatch.setIndexBuffer(properties.isSet("indices") ?
batch.getIndexBuffer() :
BufferUtils.clone(batch.getIndexBuffer()));
if (properties.isSet("vboinfo")) {
mbatch.setVBOInfo(batch.getVBOInfo());
}
if (properties.isSet("obbtree")) {
mbatch.setCollisionTree(batch.getCollisionTree());
}
if (properties.isSet("displaylistid")) {
mbatch.setDisplayListID(batch.getDisplayListID());
}
if (batch.getModelBound() != null) {
mbatch.setModelBound(properties.isSet("bound") ?
batch.getModelBound() : batch.getModelBound().clone(null));
}
if (_textures != null && _textures.length > 1) {
int tidx = properties.random % _textures.length;
mstore._textures = new String[] { _textures[tidx] };
mstore._tstates = new TextureState[] { _tstates[tidx] };
mstore.setRenderState(_tstates[tidx]);
} else {
mstore._textures = _textures;
mstore._tstates = _tstates;
}
return mstore;
}
@Override // documentation inherited
public void updateWorldVectors ()
{
if (!_transformLocked) {
super.updateWorldVectors();
}
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
out.writeUTF(getName());
out.writeObject(getLocalTranslation());
out.writeObject(getLocalRotation());
out.writeObject(getLocalScale());
out.writeObject(getBatch(0).getModelBound());
out.writeInt(_vertexBufferSize);
out.writeInt(_normalBufferSize);
out.writeInt(_colorBufferSize);
out.writeInt(_textureBufferSize);
out.writeInt(_indexBufferSize);
out.writeObject(_textures);
out.writeBoolean(_solid);
out.writeBoolean(_transparent);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
setName(in.readUTF());
setLocalTranslation((Vector3f)in.readObject());
setLocalRotation((Quaternion)in.readObject());
setLocalScale((Vector3f)in.readObject());
setModelBound((BoundingVolume)in.readObject());
_vertexBufferSize = in.readInt();
_normalBufferSize = in.readInt();
_colorBufferSize = in.readInt();
_textureBufferSize = in.readInt();
_indexBufferSize = in.readInt();
_textures = (String[])in.readObject();
_solid = in.readBoolean();
_transparent = in.readBoolean();
}
// documentation inherited from interface ModelSpatial
public void expandModelBounds ()
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void setReferenceTransforms ()
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
{
if (useVBOs && renderer.supportsVBO()) {
VBOInfo vboinfo = new VBOInfo(true);
vboinfo.setVBOIndexEnabled(true);
setVBOInfo(vboinfo);
} else if (useDisplayLists) {
lockMeshes(renderer);
}
}
// documentation inherited from interface ModelSpatial
public void resolveTextures (TextureProvider tprov)
{
if (_textures == null) {
return;
}
_tstates = new TextureState[_textures.length];
for (int ii = 0; ii < _textures.length; ii++) {
_tstates[ii] = tprov.getTexture(_textures[ii]);
}
if (_tstates[0] != null) {
setRenderState(_tstates[0]);
}
}
// documentation inherited from interface ModelSpatial
public void storeMeshFrame (int frameId, boolean blend)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void setMeshFrame (int frameId)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void writeBuffers (FileChannel out)
throws IOException
{
if (_vertexBufferSize > 0) {
_vertexByteBuffer.rewind();
convertOrder(_vertexByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_vertexByteBuffer);
convertOrder(_vertexByteBuffer, ByteOrder.nativeOrder());
}
if (_normalBufferSize > 0) {
_normalByteBuffer.rewind();
convertOrder(_normalByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_normalByteBuffer);
convertOrder(_normalByteBuffer, ByteOrder.nativeOrder());
}
if (_colorBufferSize > 0) {
_colorByteBuffer.rewind();
convertOrder(_colorByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_colorByteBuffer);
convertOrder(_colorByteBuffer, ByteOrder.nativeOrder());
}
if (_textureBufferSize > 0) {
_textureByteBuffer.rewind();
convertOrder(_textureByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_textureByteBuffer);
convertOrder(_textureByteBuffer, ByteOrder.nativeOrder());
}
if (_indexBufferSize > 0) {
_indexByteBuffer.rewind();
convertOrder(_indexByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_indexByteBuffer);
convertOrder(_indexByteBuffer, ByteOrder.nativeOrder());
}
}
// documentation inherited from interface ModelSpatial
public void readBuffers (FileChannel in)
throws IOException
{
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
ibbuf = null;
ByteOrder le = ByteOrder.LITTLE_ENDIAN;
if (_vertexBufferSize > 0) {
vbbuf = ByteBuffer.allocateDirect(_vertexBufferSize*4).order(le);
in.read(vbbuf);
vbbuf.rewind();
convertOrder(vbbuf, ByteOrder.nativeOrder());
}
if (_normalBufferSize > 0) {
nbbuf = ByteBuffer.allocateDirect(_normalBufferSize*4).order(le);
in.read(nbbuf);
nbbuf.rewind();
convertOrder(nbbuf, ByteOrder.nativeOrder());
}
if (_colorBufferSize > 0) {
cbbuf = ByteBuffer.allocateDirect(_colorBufferSize*4).order(le);
in.read(cbbuf);
cbbuf.rewind();
convertOrder(cbbuf, ByteOrder.nativeOrder());
}
if (_textureBufferSize > 0) {
tbbuf = ByteBuffer.allocateDirect(_textureBufferSize*4).order(le);
in.read(tbbuf);
tbbuf.rewind();
convertOrder(tbbuf, ByteOrder.nativeOrder());
}
if (_indexBufferSize > 0) {
ibbuf = ByteBuffer.allocateDirect(_indexBufferSize*4).order(le);
in.read(ibbuf);
ibbuf.rewind();
convertOrder(ibbuf, ByteOrder.nativeOrder());
}
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
}
// documentation inherited from interface ModelSpatial
public void sliceBuffers (MappedByteBuffer map)
{
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
ibbuf = null;
int total = 0;
if (_vertexBufferSize > 0) {
int npos = map.position() + _vertexBufferSize*4;
map.limit(npos);
vbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_normalBufferSize > 0) {
int npos = map.position() + _normalBufferSize*4;
map.limit(npos);
nbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_colorBufferSize > 0) {
int npos = map.position() + _colorBufferSize*4;
map.limit(npos);
cbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_textureBufferSize > 0) {
int npos = map.position() + _textureBufferSize*4;
map.limit(npos);
tbbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
if (_indexBufferSize > 0) {
int npos = map.position() + _indexBufferSize*4;
map.limit(npos);
ibbuf = map.slice().order(ByteOrder.LITTLE_ENDIAN);
map.position(npos);
}
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
}
/**
* Locks the transform and bounds of this mesh on the assumption that its
* position will not change.
*/
protected void lockInstance ()
{
lockBounds();
_transformLocked = true;
}
/**
* Imposes the specified order on the given buffer of 32 bit values.
*/
protected static void convertOrder (ByteBuffer buf, ByteOrder order)
{
if (buf.order() == order) {
return;
}
IntBuffer obuf = buf.asIntBuffer(),
nbuf = buf.order(order).asIntBuffer();
while (obuf.hasRemaining()) {
nbuf.put(obuf.get());
}
}
/**
* Initializes the states shared between all models. Requires an active
* display.
*/
protected static void initSharedStates ()
{
Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer();
_backCull = renderer.createCullState();
_backCull.setCullMode(CullState.CS_BACK);
_blendAlpha = renderer.createAlphaState();
_blendAlpha.setBlendEnabled(true);
_overlayZBuffer = renderer.createZBufferState();
_overlayZBuffer.setFunction(ZBufferState.CF_LEQUAL);
_overlayZBuffer.setWritable(false);
}
/** The sizes of the various buffers (zero for <code>null</code>). */
protected int _vertexBufferSize, _normalBufferSize, _colorBufferSize,
_textureBufferSize, _indexBufferSize;
/** The backing byte buffers for the various buffers. */
protected ByteBuffer _vertexByteBuffer, _normalByteBuffer,
_colorByteBuffer, _textureByteBuffer, _indexByteBuffer;
/** The type of bounding volume that this mesh should use. */
protected int _boundingType;
/** The name of this model's textures, or <code>null</code> for none. */
protected String[] _textures;
/** Whether or not this mesh can enable back-face culling. */
protected boolean _solid;
/** Whether or not this mesh must be rendered as transparent. */
protected boolean _transparent;
/** For prototype meshes, the resolved texture states. */
protected TextureState[] _tstates;
/** Whether or not the transform has been locked. This operates in a
* slightly different way than JME's locking, in that it allows applying
* transformations to display lists. */
protected boolean _transformLocked;
/** The shared state for back face culling. */
protected static CullState _backCull;
/** The shared state for alpha blending. */
protected static AlphaState _blendAlpha;
/** The shared state for checking, but not writing to, the z buffer. */
protected static ZBufferState _overlayZBuffer;
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,407 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.DataOutput;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashSet;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.state.RenderState;
import com.threerings.jme.Log;
/**
* A {@link Node} with a serialization mechanism tailored to stored models.
*/
public class ModelNode extends Node
implements Externalizable, ModelSpatial
{
/**
* No-arg constructor for deserialization.
*/
public ModelNode ()
{
super("node");
}
/**
* Standard constructor.
*/
public ModelNode (String name)
{
super(name);
}
/**
* Recursively searches the scene graph rooted at this node for a
* node with the provided name.
*/
public Spatial getDescendant (String name)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child.getName().equals(name)) {
return child;
} else if (child instanceof ModelNode) {
child = ((ModelNode)child).getDescendant(name);
if (child != null) {
return child;
}
}
}
return null;
}
/**
* Returns a reference to the model space transform of the node.
*/
public Matrix4f getModelTransform ()
{
return _modelTransform;
}
@Override // documentation inherited
public void updateWorldData (float time)
{
// we use locked bounds as an indication that we can skip the update
// altogether
if ((lockedMode & LOCKED_BOUNDS) == 0) {
super.updateWorldData(time);
}
}
@Override // documentation inherited
public void updateWorldBound ()
{
// if the node is culled, there are no mesh descendants and thus no
// bounds
if (cullMode != CULL_ALWAYS) {
super.updateWorldBound();
}
}
@Override // documentation inherited
public void updateWorldVectors ()
{
super.updateWorldVectors();
if (parent instanceof ModelNode) {
setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), _localTransform);
((ModelNode)parent).getModelTransform().mult(_localTransform,
_modelTransform);
} else {
_modelTransform.loadIdentity();
}
}
// documentation inherited from interface ModelSpatial
public Spatial putClone (Spatial store, Model.CloneCreator properties)
{
ModelNode mstore = (ModelNode)properties.originalToCopy.get(this);
if (mstore != null) {
return mstore;
} else if (store == null) {
mstore = new ModelNode(getName());
} else {
mstore = (ModelNode)store;
}
properties.originalToCopy.put(this, mstore);
mstore.normalsMode = normalsMode;
mstore.cullMode = cullMode;
for (int ii = 0; ii < RenderState.RS_MAX_STATE; ii++) {
RenderState rstate = getRenderState(ii);
if (rstate != null) {
mstore.setRenderState(rstate);
}
}
mstore.renderQueueMode = renderQueueMode;
mstore.lockedMode = lockedMode;
mstore.lightCombineMode = lightCombineMode;
mstore.textureCombineMode = textureCombineMode;
mstore.name = name;
mstore.isCollidable = isCollidable;
mstore.localRotation.set(localRotation);
mstore.localTranslation.set(localTranslation);
mstore.localScale.set(localScale);
for (Object controller : getControllers()) {
if (controller instanceof ModelController) {
mstore.addController(
((ModelController)controller).putClone(null, properties));
}
}
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
mstore.attachChild(
((ModelSpatial)child).putClone(null, properties));
}
}
return mstore;
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
out.writeUTF(getName());
out.writeObject(getLocalTranslation());
out.writeObject(getLocalRotation());
out.writeObject(getLocalScale());
out.writeObject(getChildren());
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
setName(in.readUTF());
setLocalTranslation((Vector3f)in.readObject());
setLocalRotation((Quaternion)in.readObject());
setLocalScale((Vector3f)in.readObject());
ArrayList<Spatial> children = (ArrayList<Spatial>)in.readObject();
if (children != null) {
for (Spatial child : children) {
attachChild(child);
}
}
}
// documentation inherited from interface ModelSpatial
public void expandModelBounds ()
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).expandModelBounds();
}
}
}
// documentation inherited from interface ModelSpatial
public void setReferenceTransforms ()
{
updateWorldVectors();
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).setReferenceTransforms();
}
}
}
// documentation inherited from interface ModelSpatial
public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).lockStaticMeshes(renderer, useVBOs,
useDisplayLists);
}
}
}
// documentation inherited from interface ModelSpatial
public void resolveTextures (TextureProvider tprov)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).resolveTextures(tprov);
}
}
}
// documentation inherited from interface ModelSpatial
public void storeMeshFrame (int frameId, boolean blend)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).storeMeshFrame(frameId, blend);
}
}
}
// documentation inherited from interface ModelSpatial
public void setMeshFrame (int frameId)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).setMeshFrame(frameId);
}
}
}
// documentation inherited from interface ModelSpatial
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).blendMeshFrames(
frameId1, frameId2, alpha);
}
}
}
// documentation inherited from interface ModelSpatial
public void writeBuffers (FileChannel out)
throws IOException
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).writeBuffers(out);
}
}
}
// documentation inherited from interface ModelSpatial
public void readBuffers (FileChannel in)
throws IOException
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).readBuffers(in);
}
}
}
// documentation inherited from interface ModelSpatial
public void sliceBuffers (MappedByteBuffer map)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).sliceBuffers(map);
}
}
}
/**
* Sets the cull state of any nodes that do not contain geometric
* descendants to {@link CULL_ALWAYS} so that they don't waste
* rendering time.
*
* @return true if this node should be drawn, false if it contains
* no mesh descendants
*/
protected boolean cullInvisibleNodes ()
{
boolean hasVisibleDescendants = false;
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (!(child instanceof ModelNode) ||
((ModelNode)child).cullInvisibleNodes()) {
hasVisibleDescendants = true;
}
}
setCullMode(hasVisibleDescendants ? CULL_INHERIT : CULL_ALWAYS);
return hasVisibleDescendants;
}
/**
* Locks the transforms and bounds of this instance with the assumption
* that the position will never change.
*
* @param targets the targets of the model's controllers, which determine
* the subset of nodes that can be locked
* @return true if this node is a target or contains any targets, otherwise
* false
*/
protected boolean lockInstance (HashSet<Spatial> targets)
{
updateWorldVectors();
lockedMode |= LOCKED_TRANSFORMS;
boolean containsTargets = false;
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (targets.contains(child) || (child instanceof ModelNode &&
((ModelNode)child).lockInstance(targets))) {
containsTargets = true;
} else if (child instanceof ModelMesh) {
((ModelMesh)child).lockInstance();
}
}
if (containsTargets) {
return true;
} else {
updateWorldBound();
lockedMode |= LOCKED_BOUNDS;
return false;
}
}
/**
* Sets a matrix to the transform defined by the given translation,
* rotation, and scale values.
*/
protected static Matrix4f setTransform (
Vector3f translation, Quaternion rotation, Vector3f scale,
Matrix4f result)
{
result.set(rotation);
result.setTranslation(translation);
result.m00 *= scale.x;
result.m01 *= scale.y;
result.m02 *= scale.z;
result.m10 *= scale.x;
result.m11 *= scale.y;
result.m12 *= scale.z;
result.m20 *= scale.x;
result.m21 *= scale.y;
result.m22 *= scale.z;
return result;
}
/** The node's transform in local and model space. */
protected Matrix4f _localTransform = new Matrix4f(),
_modelTransform = new Matrix4f();
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,115 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.Externalizable;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import com.jme.math.Matrix4f;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial;
/**
* Contains method common to both {@link ModelNode}s and {@link ModelMesh}es.
*/
public interface ModelSpatial
{
/**
* Recursively expands the model bounds of any deformable meshes so that
* they include the current vertex positions.
*/
public void expandModelBounds ();
/**
* Recursively sets the reference transforms for any bones in the model.
*/
public void setReferenceTransforms ();
/**
* Recursively compiles any static meshes to vertex buffer objects (VBOs)
* or display lists.
*
* @param useVBOs if true, use VBOs if the graphics card supports them
* @param useDisplayLists if true and not using VBOs, compile static
* objects to display lists
*/
public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists);
/**
* Recursively resolves texture references using the given provider.
*/
public void resolveTextures (TextureProvider tprov);
/**
* Recursively requests that the current state of all skinned meshes be
* stored as an animation frame on the next update.
*
* @param frameId the frame id, which uniquely identifies one frame of
* one animation
* @param blend whether or not the stored frames will be retrieved by
* calls to {@link #blendAnimationFrames} as opposed to
* {@link #setAnimationFrames}
*/
public void storeMeshFrame (int frameId, boolean blend);
/**
* Recursively switches all skinned meshes to a stored animation frame for
* the next update.
*/
public void setMeshFrame (int frameId);
/**
* Recursively blends all skinned meshes between two stored animation
* frames for the next update.
*/
public void blendMeshFrames (int frameId1, int frameId2, float alpha);
/**
* Creates or populates and returns a clone of this object using the given
* clone properties.
*
* @param store an instance of this class to populate, or <code>null</code>
* to create a new instance
*/
public Spatial putClone (Spatial store, Model.CloneCreator properties);
/**
* Recursively writes any data buffers to the output channel.
*/
public void writeBuffers (FileChannel out)
throws IOException;
/**
* Recursively reads any data buffers from the input channel.
*/
public void readBuffers (FileChannel in)
throws IOException;
/**
* Recursively slices any data buffers from the buffer map.
*/
public void sliceBuffers (MappedByteBuffer map);
}
@@ -0,0 +1,128 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Properties;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
/**
* A procedural animation that rotates a node around at a constant angular
* velocity.
*/
public class Rotator extends ModelController
{
@Override // documentation inherited
public void configure (Properties props, Spatial target)
{
super.configure(props, target);
String axisstr = props.getProperty("axis", "x"),
rpsstr = props.getProperty("radpersec", "3.14");
if (axisstr.equalsIgnoreCase("x")) {
_axis = Vector3f.UNIT_X;
} else if (axisstr.equalsIgnoreCase("y")) {
_axis = Vector3f.UNIT_Y;
} else if (axisstr.equalsIgnoreCase("z")) {
_axis = Vector3f.UNIT_Z;
} else {
float[] axis = StringUtil.parseFloatArray(axisstr);
if (axis != null && axis.length == 3) {
_axis = new Vector3f(axis[0], axis[1],
axis[2]).normalizeLocal();
} else {
Log.warning("Invalid rotation axis [axis=" + axisstr + "].");
}
}
try {
_radpersec = Float.parseFloat(rpsstr);
} catch (NumberFormatException e) {
Log.warning("Invalid rotation rate [radpersec=" + rpsstr + "].");
}
}
// documentation inherited
public void update (float time)
{
if (!isActive()) {
return;
}
_rot.fromAngleNormalAxis(time * _radpersec, _axis);
_target.getLocalRotation().multLocal(_rot);
}
@Override // documentation inherited
public Controller putClone (
Controller store, Model.CloneCreator properties)
{
Rotator rstore;
if (store == null) {
rstore = new Rotator();
} else {
rstore = (Rotator)store;
}
super.putClone(rstore, properties);
rstore._axis = _axis;
rstore._radpersec = _radpersec;
return rstore;
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
super.writeExternal(out);
out.writeObject(_axis);
out.writeFloat(_radpersec);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
super.readExternal(in);
_axis = (Vector3f)in.readObject();
_radpersec = in.readFloat();
}
/** The axis about which to rotate. */
protected Vector3f _axis;
/** The velocity at which to rotate in radians per second. */
protected float _radpersec;
/** A temporary quaternion. */
protected Quaternion _rot = new Quaternion();
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,495 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Matrix4f;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
import com.jme.scene.batch.SharedBatch;
import com.jme.scene.batch.TriangleBatch;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.HashIntMap;
import com.threerings.jme.Log;
/**
* A triangle mesh that deforms according to a bone hierarchy.
*/
public class SkinMesh extends ModelMesh
{
/** Represents the vertex weights of a group of vertices influenced by the
* same set of bones. */
public static class WeightGroup
implements Serializable
{
/** The number of vertices in this weight group. */
public int vertexCount;
/** The bones influencing this group. */
public Bone[] bones;
/** The array of interleaved weights (of length <code>vertexCount *
* boneIndices.length</code>): weights for first vertex, weights for
* second, etc. */
public float[] weights;
/**
* Rebinds this weight group for a prototype instance.
*
* @param bmap the mapping from prototype to instance bones
*/
public WeightGroup rebind (HashMap<Bone, Bone> bmap)
{
WeightGroup wgroup = new WeightGroup();
wgroup.vertexCount = vertexCount;
wgroup.bones = new Bone[bones.length];
for (int ii = 0; ii < bones.length; ii++) {
wgroup.bones[ii] = bmap.get(bones[ii]);
}
wgroup.weights = weights;
return wgroup;
}
private static final long serialVersionUID = 1;
}
/** Represents a bone that influences the mesh. */
public static class Bone
implements Serializable
{
/** The node that defines the bone's position. */
public ModelNode node;
/** The inverse of the bone's model space reference transform. */
public transient Matrix4f invRefTransform;
/** The bone's current transform in model space. */
public transient Matrix4f transform;
public Bone (ModelNode node)
{
this.node = node;
transform = new Matrix4f();
}
/**
* Rebinds this bone for a prototype instance.
*
* @param pnodes a mapping from prototype nodes to instance nodes
*/
public Bone rebind (HashMap pnodes)
{
Bone bone = new Bone((ModelNode)pnodes.get(node));
bone.invRefTransform = invRefTransform;
bone.transform = new Matrix4f();
return bone;
}
/**
* Initializes the bone's transient state.
*/
private void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
transform = new Matrix4f();
}
private static final long serialVersionUID = 1;
}
/**
* No-arg constructor for deserialization.
*/
public SkinMesh ()
{
}
/**
* Creates an empty mesh.
*/
public SkinMesh (String name)
{
super(name);
}
/**
* Sets the array of weight groups that determine how bones affect
* each vertex.
*/
public void setWeightGroups (WeightGroup[] weightGroups)
{
_weightGroups = weightGroups;
// compile a list of all referenced bones
HashSet<Bone> bones = new HashSet<Bone>();
for (WeightGroup group : weightGroups) {
Collections.addAll(bones, group.bones);
}
_bones = bones.toArray(new Bone[bones.size()]);
}
@Override // documentation inherited
public void reconstruct (
ByteBuffer vertices, ByteBuffer normals, ByteBuffer colors,
ByteBuffer textures, ByteBuffer indices)
{
super.reconstruct(vertices, normals, colors, textures, indices);
// store the current buffers as the originals
storeOriginalBuffers();
// initialize the quantized frame table
_frames = new HashIntMap<Object>();
}
@Override // documentation inherited
public void centerVertices ()
{
super.centerVertices();
storeOriginalBuffers();
}
@Override // documentation inherited
public Spatial putClone (Spatial store, Model.CloneCreator properties)
{
SkinMesh mstore = (SkinMesh)properties.originalToCopy.get(this);
if (mstore != null) {
return mstore;
} else if (store == null) {
mstore = new SkinMesh(getName());
} else {
mstore = (SkinMesh)store;
}
properties.removeProperty("vertices");
properties.removeProperty("normals");
properties.removeProperty("displaylistid");
super.putClone(mstore, properties);
properties.addProperty("vertices");
properties.addProperty("normals");
properties.addProperty("displaylistid");
mstore._frames = _frames;
mstore._useDisplayLists = _useDisplayLists;
mstore._invRefTransform = _invRefTransform;
mstore._bones = new Bone[_bones.length];
HashMap<Bone, Bone> bmap = new HashMap<Bone, Bone>();
for (int ii = 0; ii < _bones.length; ii++) {
bmap.put(_bones[ii], mstore._bones[ii] =
_bones[ii].rebind(properties.originalToCopy));
}
mstore._weightGroups = new WeightGroup[_weightGroups.length];
for (int ii = 0; ii < _weightGroups.length; ii++) {
mstore._weightGroups[ii] = _weightGroups[ii].rebind(bmap);
}
mstore._ovbuf = _ovbuf;
mstore._onbuf = _onbuf;
mstore._vbuf = new float[_vbuf.length];
mstore._nbuf = new float[_nbuf.length];
return mstore;
}
@Override // documentation inherited
public void writeExternal (ObjectOutput out)
throws IOException
{
super.writeExternal(out);
out.writeObject(_weightGroups);
}
@Override // documentation inherited
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
super.readExternal(in);
setWeightGroups((WeightGroup[])in.readObject());
}
@Override // documentation inherited
public void expandModelBounds ()
{
BoundingVolume obound =
(BoundingVolume)getBatch(0).getModelBound().clone(null);
updateModelBound();
getBatch(0).getModelBound().mergeLocal(obound);
}
@Override // documentation inherited
public void setReferenceTransforms ()
{
_invRefTransform = new Matrix4f();
if (parent instanceof ModelNode) {
Matrix4f transform = new Matrix4f();
ModelNode.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), transform);
((ModelNode)parent).getModelTransform().mult(transform,
_invRefTransform);
_invRefTransform.invertLocal();
}
for (Bone bone : _bones) {
bone.invRefTransform =
_invRefTransform.mult(bone.node.getModelTransform()).invert();
}
}
@Override // documentation inherited
public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
{
// we can use VBOs for color, texture, and indices
if (useVBOs && renderer.supportsVBO()) {
VBOInfo vboinfo = new VBOInfo(false);
vboinfo.setVBOColorEnabled(true);
vboinfo.setVBOTextureEnabled(true);
vboinfo.setVBOIndexEnabled(true);
setVBOInfo(vboinfo);
}
_useDisplayLists = useDisplayLists;
}
@Override // documentation inherited
public void storeMeshFrame (int frameId, boolean blend)
{
_storeFrameId = frameId;
_storeBlend = blend;
}
@Override // documentation inherited
public void setMeshFrame (int frameId)
{
TriangleBatch batch = getBatch(0),
tbatch = (TriangleBatch)_frames.get(frameId);
if (batch instanceof SharedBatch) {
((SharedBatch)batch).setTarget(tbatch);
} else {
clearBatches();
addBatch(new SharedBatch(tbatch));
getBatch(0).updateRenderState();
}
}
@Override // documentation inherited
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
BlendFrame frame1 = (BlendFrame)_frames.get(frameId1),
frame2 = (BlendFrame)_frames.get(frameId2);
frame1.blend(frame2, alpha, _vbuf, _nbuf);
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
vbuf.put(_vbuf);
nbuf.rewind();
nbuf.put(_nbuf);
}
@Override // documentation inherited
public void updateWorldData (float time)
{
super.updateWorldData(time);
if (_weightGroups == null || _storeFrameId == -1) {
return;
}
// update the bone transforms
for (Bone bone : _bones) {
_invRefTransform.mult(bone.node.getModelTransform(),
bone.transform);
bone.transform.multLocal(bone.invRefTransform);
}
// deform the mesh according to the positions of the bones (this code
// is ugly as sin because it's optimized at a low level)
Bone[] bones;
int vertexCount, jj, kk, ww;
float[] weights;
Matrix4f m;
float weight, ovx, ovy, ovz, onx, ony, onz, vx, vy, vz, nx, ny, nz;
for (int ii = 0, bidx = 0; ii < _weightGroups.length; ii++) {
vertexCount = _weightGroups[ii].vertexCount;
bones = _weightGroups[ii].bones;
weights = _weightGroups[ii].weights;
for (jj = 0, ww = 0; jj < vertexCount; jj++) {
ovx = _ovbuf[bidx];
ovy = _ovbuf[bidx + 1];
ovz = _ovbuf[bidx + 2];
onx = _onbuf[bidx];
ony = _onbuf[bidx + 1];
onz = _onbuf[bidx + 2];
vx = vy = vz = 0f;
nx = ny = nz = 0f;
for (kk = 0; kk < bones.length; kk++) {
m = bones[kk].transform;
weight = weights[ww++];
vx += (ovx*m.m00 + ovy*m.m01 + ovz*m.m02 + m.m03) * weight;
vy += (ovx*m.m10 + ovy*m.m11 + ovz*m.m12 + m.m13) * weight;
vz += (ovx*m.m20 + ovy*m.m21 + ovz*m.m22 + m.m23) * weight;
nx += (onx*m.m00 + ony*m.m01 + onz*m.m02) * weight;
ny += (onx*m.m10 + ony*m.m11 + onz*m.m12) * weight;
nz += (onx*m.m20 + ony*m.m21 + onz*m.m22) * weight;
}
_vbuf[bidx] = vx;
_vbuf[bidx + 1] = vy;
_vbuf[bidx + 2] = vz;
_nbuf[bidx++] = nx;
_nbuf[bidx++] = ny;
_nbuf[bidx++] = nz;
}
}
// if skinning in real time, copy the data from arrays to buffers;
// otherwise, store the mesh as an animation frame
if (_storeFrameId == 0) {
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
vbuf.put(_vbuf);
nbuf.rewind();
nbuf.put(_nbuf);
} else {
storeFrame();
_storeFrameId = -1;
}
}
/**
* Stores the current frame data for later use.
*/
protected void storeFrame ()
{
if (_storeBlend) {
_frames.put(_storeFrameId, new BlendFrame(
(float[])_vbuf.clone(), (float[])_nbuf.clone()));
} else {
TriangleBatch batch = getBatch(0), tbatch = new TriangleBatch();
tbatch.setParentGeom(DUMMY_MESH);
tbatch.setColorBuffer(batch.getColorBuffer());
tbatch.setTextureBuffer(batch.getTextureBuffer(0), 0);
tbatch.setIndexBuffer(batch.getIndexBuffer());
tbatch.setVertexBuffer(BufferUtils.createFloatBuffer(_vbuf));
tbatch.setNormalBuffer(BufferUtils.createFloatBuffer(_nbuf));
VBOInfo ovboinfo = batch.getVBOInfo();
if (ovboinfo != null) {
VBOInfo vboinfo = new VBOInfo(true);
vboinfo.setVBOIndexEnabled(true);
vboinfo.setVBOColorID(ovboinfo.getVBOColorID());
vboinfo.setVBOTextureID(0, ovboinfo.getVBOTextureID(0));
vboinfo.setVBOIndexID(ovboinfo.getVBOIndexID());
tbatch.setVBOInfo(vboinfo);
} else if (_useDisplayLists) {
tbatch.lockMeshes(
DisplaySystem.getDisplaySystem().getRenderer());
}
_frames.put(_storeFrameId, tbatch);
}
}
/**
* Stores the current vertex and normal buffers for later deformation.
*/
protected void storeOriginalBuffers ()
{
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
nbuf.rewind();
FloatBuffer.wrap(_ovbuf = new float[vbuf.capacity()]).put(vbuf);
FloatBuffer.wrap(_onbuf = new float[nbuf.capacity()]).put(nbuf);
_vbuf = new float[_ovbuf.length];
_nbuf = new float[_onbuf.length];
}
/** A stored frame used for linear blending. */
protected static class BlendFrame
{
/** The skinned vertex and normal values. */
public float[] vbuf, nbuf;
public BlendFrame (float[] vbuf, float[] nbuf)
{
this.vbuf = vbuf;
this.nbuf = nbuf;
}
public void blend (
BlendFrame next, float alpha, float[] rvbuf, float[] rnbuf)
{
float[] nvbuf = next.vbuf, nnbuf = next.nbuf;
float ialpha = 1f - alpha;
for (int ii = 0, nn = vbuf.length; ii < nn; ii++) {
rvbuf[ii] = vbuf[ii] * ialpha + nvbuf[ii] * alpha;
rnbuf[ii] = nbuf[ii] * ialpha + nnbuf[ii] * alpha;
}
}
}
/** Pre-skinned {@link TriangleBatch}es or {@link BlendFrame}s shared
* between all instances corresponding to frame ids from
* {@link #storeAnimationFrame}. */
protected HashIntMap<Object> _frames;
/** Whether or to use display lists if VBOs are unavailable for quantized
* meshes. */
protected boolean _useDisplayLists;
/** The inverse of the model space reference transform. */
protected Matrix4f _invRefTransform;
/** The groups of vertices influenced by different sets of bones. */
protected WeightGroup[] _weightGroups;
/** The bones referenced by the weight groups. */
protected Bone[] _bones;
/** The original (undeformed) vertex and normal buffers and the deformed
* versions. */
protected float[] _ovbuf, _onbuf, _vbuf, _nbuf;
/** The frame id to store on the next update. If 0, don't store any frame
* and skin the mesh as normal. If -1, a frame has been stored and thus
* skinning should only take place when further frames are requested. */
protected int _storeFrameId;
/** Whether or not the stored frame id will be used for blending. */
protected boolean _storeBlend;
/** A dummy mesh that simply hold transformation values. */
protected static final TriMesh DUMMY_MESH = new TriMesh();
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,39 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import com.jme.scene.state.TextureState;
/**
* Provides a means for models to resolve their texture references.
*/
public interface TextureProvider
{
/**
* Returns a texture state containing the named texture.
*
* @param name the name of the texture, which will be interpreted as an
* absolute resource path if it starts with a forward slash; otherwise,
* as a relative resource path
*/
public TextureState getTexture (String name);
}
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<!-- $Id: package.html 617 2001-11-13 00:12:20Z mdb $ -->
</head>
<body bgcolor="white">
Defines extensions to <a href="http://jmonkeyengine.com">jMonkeyEngine</a>
an OpenGL-based 3D rendering engine. An application framework is
provided that integrates jME with the Presents distributed object
system and other useful bits and bobs are also included.
</body>
</html>
@@ -0,0 +1,92 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
/**
* Moves a sprite ballistically.
*/
public class BallisticPath extends Path
{
/** Gravity: it's the law. */
public static final float G = -9.8f;
/**
* Moves the supplied sprite from the starting coordinate (which will
* be modified) using the starting velocity, under the specified
* acceleration.
*/
public BallisticPath (Sprite sprite, Vector3f start, Vector3f velocity,
Vector3f accel, float duration)
{
super(sprite);
_curpos = start;
_velocity = velocity;
_accel = accel;
_duration = duration;
}
// documentation inherited
public void update (float time)
{
// adjust the position
_velocity.mult(time, _temp);
_curpos.addLocal(_temp);
_sprite.setLocalTranslation(_curpos);
// check to see if we're done
_accum += time;
if (_accum >= _duration) {
_sprite.pathCompleted();
} else {
// adjust our velocity due to acceleration
_accel.mult(time, _temp);
_velocity.addLocal(_temp);
}
}
/**
* Computes and returns the angle of elevation needed to launch a
* ballistic projectile at the specified velocity and have it travel
* the specified range (when it will once again reach the launch
* elevation).
*/
public static float computeElevation (float range, float vel, float accel)
{
return FastMath.asin(accel * range / (vel * vel)) / 2;
}
/**
* Computes and returns the flight time of a projectile launched at an
* angle previously computed with {@link #computeElevation}..
*/
public static float computeFlightTime (float range, float vel, float angle)
{
return range / (vel * FastMath.cos(angle));
}
protected Vector3f _curpos, _velocity, _accel;
protected float _duration, _accum;
protected Vector3f _temp = new Vector3f(0, 0, 0);
}
@@ -0,0 +1,60 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
import com.jme.math.Vector3f;
/**
* Moves a sprite along a straight line.
*/
public class LinePath extends Path
{
/**
* Moves the supplied sprite from the starting coordinate to the
* specified destination coordinate in the specified time.
*/
public LinePath (Sprite sprite, Vector3f start, Vector3f finish,
float duration)
{
super(sprite);
_start = start;
_finish = finish;
_duration = duration;
}
// documentation inherited
public void update (float time)
{
_accum += time;
if (_accum >= _duration) {
_sprite.setLocalTranslation(_finish);
_sprite.pathCompleted();
} else {
_temp.interpolate(_start, _finish, _accum / _duration);
_sprite.setLocalTranslation(_temp);
}
}
protected Vector3f _start, _finish;
protected float _duration, _accum;
protected Vector3f _temp = new Vector3f(0, 0, 0);
}
@@ -0,0 +1,103 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
/**
* Moves a sprite along a series of straight lines.
*/
public class LineSegmentPath extends Path
{
/**
* Creates a path for the supplied sprite traversing the supplied
* series of points with the specified duration between points.
*
* @param points a list of points between which the sprite will be
* moved linearly (the sprite will be moved immediately to the first
* point).
* @param durations defines the elapsed time between each successive
* traversal. This will as a result be shorter by one element than the
* points array.
*/
public LineSegmentPath (Sprite sprite, Vector3f up, Vector3f orient,
Vector3f[] points, float[] durations)
{
super(sprite);
_up = up;
_orient = orient;
_points = points;
_durations = durations;
updateRotation();
}
// documentation inherited
public void update (float time)
{
// note the accumulated time
_accum += time;
// if we have surpassed the time for this segment, subtract the
// segment time and move on to the next segment
while (_current < _durations.length && _accum > _durations[_current]) {
_accum -= _durations[_current];
advance();
}
// if we have completed our path, move the sprite to the final
// position and wrap everything up
if (_current >= _durations.length) {
_sprite.setLocalTranslation(_points[_points.length-1]);
_sprite.pathCompleted();
return;
}
// move the sprite to the appropriate position between points
_temp.interpolate(_points[_current], _points[_current+1],
_accum / _durations[_current]);
_sprite.setLocalTranslation(_temp);
}
protected void advance ()
{
_current++;
if (_current < _points.length-1) {
updateRotation();
}
}
protected void updateRotation ()
{
_points[_current+1].subtract(_points[_current], _temp);
PathUtil.computeRotation(_up, _orient, _temp, _rotate);
_sprite.getLocalRotation().set(_rotate);
}
protected Vector3f _up, _orient;
protected Vector3f[] _points;
protected float[] _durations;
protected float _accum;
protected int _current;
protected Vector3f _temp = new Vector3f(0, 0, 0);
protected Quaternion _rotate = new Quaternion();
}
@@ -0,0 +1,70 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
/**
* A ballistic path that orients the sprite toward the velocity vector as
* it traverses the path.
*/
public class OrientingBallisticPath extends BallisticPath
{
/**
* Creates a {@link BallisticPath} that will rotate the sprite so that
* the supplied <code>orient</code> is aligned with the velocity
* vector at all points along the path. If the provided orientation is
* not initially in line with the starting velocity, the sprite will
* be rotated immediately and then adjusted as it follows the path.
*/
public OrientingBallisticPath (
Sprite sprite, Vector3f orient, Vector3f start, Vector3f velocity,
Vector3f accel, float duration)
{
super(sprite, start, velocity, accel, duration);
// TODO: handle orient that is not (1, 0, 0)
_orient = orient;
// compute the up vector (opposite of acceleration)
_up = accel.negate();
_up.normalizeLocal();
_sprite.setLocalRotation(
PathUtil.computeAxisRotation(_up, velocity, _rotate));
}
// documentation inherited
public void update (float time)
{
// do the normal update
super.update(time);
_sprite.setLocalRotation(
PathUtil.computeAxisRotation(_up, _velocity, _rotate));
}
protected Vector3f _orient;
protected Vector3f _up;
protected Quaternion _rotate = new Quaternion();
}
@@ -0,0 +1,51 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
import com.jme.scene.Controller;
/**
* Defines a framework for moving sprites around and notifying interested
* parties when the sprite has completed its path or if the path has been
* cancelled.
*/
public abstract class Path extends Controller
{
/**
* Creates and initializes this path with the sprite it will be
* manipulating.
*/
protected Path (Sprite sprite)
{
_sprite = sprite;
}
/**
* Called when this path is removed from its sprite (either due to
* completion or cancellation).
*/
public void wasRemoved ()
{
}
protected Sprite _sprite;
}
@@ -0,0 +1,44 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
/**
* Implement this interface to find out when a sprite completes or cancels
* its path.
*/
public interface PathObserver extends SpriteObserver
{
/**
* Called when a sprite's path is cancelled either because a new path
* was started or the path was explicitly cancelled with {@link
* Sprite#cancelMove}.
*/
public void pathCancelled (Sprite sprite, Path path);
/**
* Called when a sprite completes its traversal of a path.
*
* @param sprite the sprite that completed its path.
* @param path the path that was completed.
*/
public void pathCompleted (Sprite sprite, Path path);
}
@@ -0,0 +1,95 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
/**
* Path related utility functions.
*/
public class PathUtil
{
/**
* Computes a rotation to align the X axis with the specified
* orientation and stores it in the provided target. The provided up
* vector is crossed with the new orientation to find the left vector
* and the left vector is recrossed with the orientation to find the
* correct up vector.
*
* @return the supplied target quaternion.
*/
public static Quaternion computeAxisRotation (
Vector3f up, Vector3f orient, Quaternion target)
{
_axes[0].set(orient);
_axes[0].normalizeLocal();
up.cross(_axes[0], _axes[1]);
_axes[1].normalizeLocal();
_axes[0].cross(_axes[1], _axes[2]);
target.fromAxes(_axes);
return target;
}
/**
* Computes a rotation from the one vector to another.
*
* @param axis will be used as the axis of rotation if the two vectors
* are parallel.
*/
public static Quaternion computeRotation (
Vector3f axis, Vector3f from, Vector3f to, Quaternion target)
{
float angle = computeAngle(from, to);
if (angle == FastMath.PI) { // opposite
target.fromAngleAxis(angle, axis);
} else if (angle == 0) { // coincident
target.x = target.y = target.z = 0;
target.w = 1;
} else {
from.cross(to, _axis);
target.fromAngleAxis(angle, _axis);
}
return target;
}
/**
* Computes the angle between two arbitrary vectors.
*/
public static float computeAngle (Vector3f one, Vector3f two)
{
return FastMath.acos(one.dot(two) / (one.length() * two.length()));
}
/**
* Computes the angle between two normalized vectors.
*/
public static float computeAngleNormal (Vector3f one, Vector3f two)
{
return FastMath.acos(one.dot(two));
}
protected static Vector3f[] _axes = {
new Vector3f(), new Vector3f(), new Vector3f() };
protected static Vector3f _axis = new Vector3f();
}
@@ -0,0 +1,244 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.samskivert.util.ObserverList;
import com.threerings.jme.Log;
/**
* Represents a visual entity that one controls as a single unit. Sprites
* can be made to follow paths which is one of their primary reasons for
* existence.
*/
public class Sprite extends Node
{
/**
* Walks down the hierarchy provided setting the animation speed on
* any controllers found along the way.
*/
public static void setAnimationSpeed (Spatial spatial, float speed)
{
for (int ii = 0; ii < spatial.getControllers().size(); ii++) {
spatial.getController(ii).setSpeed(speed);
}
if (spatial instanceof Node) {
Node node = (Node)spatial;
for (int ii = 0; ii < node.getQuantity(); ii++) {
setAnimationSpeed(node.getChild(ii), speed);
}
}
}
/**
* Walks down the hierarchy provided turning on or off any controllers
* found along the way.
*/
public static void setAnimationActive (Spatial spatial, boolean active)
{
for (int ii = 0; ii < spatial.getControllers().size(); ii++) {
spatial.getController(ii).setActive(active);
}
if (spatial instanceof Node) {
Node node = (Node)spatial;
for (int ii = 0; ii < node.getQuantity(); ii++) {
setAnimationActive(node.getChild(ii), active);
}
}
}
/**
* Walks down the hierarchy provided setting the animation repeat type on
* any controllers found along the way.
*/
public static void setAnimationRepeatType (Spatial spatial, int repeatType)
{
for (int ii = 0; ii < spatial.getControllers().size(); ii++) {
spatial.getController(ii).setRepeatType(repeatType);
}
if (spatial instanceof Node) {
Node node = (Node)spatial;
for (int ii = 0; ii < node.getQuantity(); ii++) {
setAnimationRepeatType(node.getChild(ii), repeatType);
}
}
}
public Sprite ()
{
super("");
setName("sprite:" + hashCode());
}
/**
* Adds an observer to this sprite. Observers are notified when path
* related events take place.
*/
public void addObserver (SpriteObserver obs)
{
if (_observers == null) {
_observers = new ObserverList<SpriteObserver>(
ObserverList.SAFE_IN_ORDER_NOTIFY);
}
_observers.add(obs);
}
/**
* Removes the specified observer from this sprite.
*/
public void removeObserver (SpriteObserver obs)
{
if (_observers != null) {
_observers.remove(obs);
}
}
/**
* Returns true if this sprite is moving along a path, false if not.
*/
public boolean isMoving ()
{
return _path != null;
}
/**
* Instructs this sprite to move along the specified path. Any
* currently executing path will be cancelled.
*/
public void move (Path path)
{
// if there's a previous path, let it know that it's going away
cancelMove();
// save off this path
_path = path;
addController(_path);
}
/**
* Cancels any currently executing path. Any registered observers will
* be notified of the cancellation.
*/
public void cancelMove ()
{
if (_path != null) {
Path oldpath = _path;
_path = null;
oldpath.wasRemoved();
if (_observers != null) {
_observers.apply(new CancelledOp(this, oldpath));
}
}
}
/**
* Called by the active path when it has completed. <em>Note:</em>
* don't call this method unless you are implementing a {@link Path}.
*/
public void pathCompleted ()
{
if (_path == null) {
Log.warning("pathCompleted() called on pathless sprite " +
"(re-completed?) [sprite=" + this + "].");
Thread.dumpStack();
return;
}
Path oldpath = _path;
_path = null;
removeController(oldpath);
oldpath.wasRemoved();
if (_observers != null) {
_observers.apply(new CompletedOp(this, oldpath));
}
}
/**
* Configures the speed of all controllers in our model hierarchy.
*/
public void setAnimationSpeed (float speed)
{
setAnimationSpeed(this, speed);
}
/**
* Configures the active state of all controllers in our model hierarchy.
*/
public void setAnimationActive (boolean active)
{
setAnimationActive(this, active);
}
/**
* Configures the repeat type of all controllers in our model hierarchy.
*/
public void setAnimationRepeatType (int repeatType)
{
setAnimationRepeatType(this, repeatType);
}
/** Used to dispatch {@link PathObserver#pathCancelled}. */
protected static class CancelledOp
implements ObserverList.ObserverOp<SpriteObserver>
{
public CancelledOp (Sprite sprite, Path path) {
_sprite = sprite;
_path = path;
}
public boolean apply (SpriteObserver observer) {
if (observer instanceof PathObserver) {
((PathObserver)observer).pathCancelled(_sprite, _path);
}
return true;
}
protected Sprite _sprite;
protected Path _path;
}
/** Used to dispatch {@link PathObserver#pathCompleted}. */
protected static class CompletedOp
implements ObserverList.ObserverOp<SpriteObserver>
{
public CompletedOp (Sprite sprite, Path path) {
_sprite = sprite;
_path = path;
}
public boolean apply (SpriteObserver observer) {
if (observer instanceof PathObserver) {
((PathObserver)observer).pathCompleted(_sprite, _path);
}
return true;
}
protected Sprite _sprite;
protected Path _path;
}
protected ObserverList<SpriteObserver> _observers;
protected Path _path;
}
@@ -0,0 +1,29 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.sprite;
/**
* The super-interface for all sprite observers.
*/
public interface SpriteObserver
{
}
@@ -0,0 +1,160 @@
//
// $Id: FringeConfiguration.java 3403 2005-03-14 23:58:02Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tile;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
/**
* Used to manage data about which tiles fringe on which others and how
* they fringe.
*/
public class FringeConfiguration implements Serializable
{
/** Contains information on a type of tile and all of the fringe
* records associated with it. */
public static class TileRecord implements Serializable
{
/** The type of tile to which this applies. */
public String type;
/** The fringe priority of this type. */
public int priority;
/** A list of the fringe records that can be used for fringing. */
public ArrayList fringes = new ArrayList();
/** Used when parsing from an XML definition. */
public void addFringe (FringeRecord record)
{
if (record.isValid()) {
fringes.add(record);
} else {
Log.warning("Not adding invalid fringe record [tile=" + this +
", fringe=" + record + "].");
}
}
/** Did everything parse well? */
public boolean isValid ()
{
return ((type != null) && (priority > 0));
}
/** Generates a string representation of this instance. */
public String toString ()
{
return "[type=" + type + ", priority=" + priority +
", fringes=" + StringUtil.toString(fringes) + "]";
}
/** Increase this value when object's serialized state is impacted
* by a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
/** Used to parse the type fringe definitions. */
public static class FringeRecord implements Serializable
{
/** The name of the fringe tileset image. */
public String name;
/** Is this a mask? */
public boolean mask;
/** Did everything parse well? */
public boolean isValid ()
{
return (name != null);
}
/** Generates a string representation of this instance. */
public String toString ()
{
return "[name=" + name + ", mask=" + mask + "]";
}
/** Increase this value when object's serialized state is impacted
* by a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
/**
* Adds a parsed fringe record to this instance. This is used when
* parsing the fringe records from xml.
*/
public void addTileRecord (TileRecord record)
{
if (record.isValid()) {
_trecs.put(record.type, record);
} else {
Log.warning("Refusing to add invalid tile record " +
"[tile=" + record + "].");
}
}
/**
* If the first type fringes upon the second, return the fringe
* priority of the first type, otherwise return -1.
*/
public int fringesOn (String fringer, String fringed)
{
TileRecord f1 = (TileRecord)_trecs.get(fringer);
// we better have a fringe record for the fringer
if (null != f1) {
// it had better have some types defined
if (f1.fringes.size() > 0) {
TileRecord f2 = (TileRecord)_trecs.get(fringed);
// and we only fringe if fringed doesn't have a fringe
// record or has a lower priority
if ((null == f2) || (f1.priority > f2.priority)) {
return f1.priority;
}
}
}
return -1;
}
/**
* Get a random FringeRecord from amongst the ones listed for the
* specified type.
*/
public FringeRecord getFringe (String type, int hashValue)
{
TileRecord t = (TileRecord)_trecs.get(type);
return (FringeRecord)t.fringes.get(hashValue % t.fringes.size());
}
/** The mapping from tile type to tile record. */
protected HashMap _trecs = new HashMap();
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -0,0 +1,404 @@
//
// $Id: TileFringer.java 3177 2004-10-28 17:49:02Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tile;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import com.samskivert.util.QuickSort;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.tile.TileUtil;
import com.threerings.jme.Log;
/**
* Computes fringe tile images according to the rules in an associated
* fringe configuration.
*/
public class TileFringer
{
public static interface TileSource
{
/** Returns the type of tile at the specified coordinates or -1 if
* there is no tile at this coordinate. */
public String getTileType (int x, int y);
/** Returns the tile type to use when a coordinate has no tile. */
public String getDefaultType ();
}
public static interface ImageSource extends ImageUtil.ImageCreator
{
/** Creates a blank image into which various fringe images will be
* composited. */
public BufferedImage createImage (
int width, int height, int transparency);
/** Returns the source image for a tile of the specified type.
* This can be randomly selected and change from call to call. */
public BufferedImage getTileSource (String type);
/** Returns the named fringe source image (one long strip). */
public BufferedImage getFringeSource (String name);
}
/**
* Creates a fringer that will fringe according to the rules in the
* supplied configuration.
*/
public TileFringer (FringeConfiguration config, ImageSource isrc)
{
_config = config;
_isrc = isrc;
}
/**
* Computes, creates and returns the base tile with the appropriate
* fringe imagery applied to it for the specified location.
*
* @param masks used to cache intermediate images of tiles cut out
* using a fringe mask.
*/
public BufferedImage getFringeTile (
TileSource tiles, int col, int row, HashMap masks)
{
// get the type of the tile we are considering
String baseType = tiles.getTileType(col, row);
if (baseType == null) {
baseType = tiles.getDefaultType();
}
// start with an empty fringer list
FringerRec fringers = null;
// walk through our influence tiles
for (int y = row - 1, maxy = row + 2; y < maxy; y++) {
for (int x = col - 1, maxx = col + 2; x < maxx; x++) {
// we sensibly do not consider ourselves
if ((x == col) && (y == row)) {
continue;
}
// determine the type of our fringing neighbor
String fringerType = tiles.getTileType(x, y);
if (fringerType == null) {
fringerType = tiles.getDefaultType();
}
// determine if it fringes on our tile
int pri = _config.fringesOn(fringerType, baseType);
if (pri == -1) {
continue;
}
FringerRec fringer = (fringers == null) ?
null : fringers.find(fringerType);
if (fringer == null) {
fringer = fringers =
new FringerRec(fringerType, pri, fringers);
}
// now turn on the appropriate fringebits
int dy = y - row, dx = x - col;
fringer.bits |= FLAGMATRIX[dy*3+dx+4];
}
}
// if nothing fringed, we're done
if (fringers == null) {
return null;
}
// otherwise compose a fringe tile from the specified fringes
return composeFringeTile(
baseType, fringers.toArray(), masks, TileUtil.getTileHash(col, row));
}
/**
* Compose a fringe tile out of the various fringe images needed.
*/
protected BufferedImage composeFringeTile (
String baseType, FringerRec[] fringers, HashMap masks, int hashValue)
{
// sort the array so that higher priority fringers get drawn first
QuickSort.sort(fringers);
BufferedImage source = _isrc.getTileSource(baseType);
if (source == null) {
Log.warning("Missing source tile [type=" + baseType + "].");
return null;
}
BufferedImage ftimg = _isrc.createImage(
source.getWidth(), source.getHeight(), Transparency.OPAQUE);
Graphics2D gfx = (Graphics2D)ftimg.getGraphics();
try {
// start with the base tile image
gfx.drawImage(source, 0, 0, null);
// and stamp the fringers on top of it
for (int ii = 0; ii < fringers.length; ii++) {
int[] indexes = getFringeIndexes(fringers[ii].bits);
for (int jj = 0; jj < indexes.length; jj++) {
stampTileImage(gfx, fringers[ii].fringerType,
indexes[jj], masks, hashValue);
}
}
} finally {
gfx.dispose();
}
return ftimg;
}
/**
* Looks up or creates the appropriate fringe mask and draws it into
* the supplied graphics context.
*/
protected void stampTileImage (
Graphics2D gfx, String fringerType, int index,
HashMap masks, int hashValue)
{
FringeConfiguration.FringeRecord frec =
_config.getFringe(fringerType, hashValue);
BufferedImage fsimg = (frec == null) ? null :
_isrc.getFringeSource(frec.name);
if (fsimg == null) {
Log.warning("Missing fringe source image [type=" + fringerType +
", hash=" + hashValue + ", frec=" + frec + "].");
return;
}
if (frec.mask) {
// it's a mask; look for it in the cache
String maskkey = fringerType + ":" + frec.name + ":" + index;
BufferedImage mimg = (BufferedImage)masks.get(maskkey);
if (mimg == null) {
BufferedImage fsrc = getSubimage(fsimg, index);
BufferedImage bsrc = _isrc.getTileSource(fringerType);
mimg = ImageUtil.composeMaskedImage(_isrc, fsrc, bsrc);
masks.put(maskkey, mimg);
}
gfx.drawImage(mimg, 0, 0, null);
} else {
// this is a non-mask image so just use the data from the
// fringe source image directly
gfx.drawImage(getSubimage(fsimg, index), 0, 0, null);
}
}
/**
* Returns the <code>index</code>th tile image from the supplied
* source image. The source image is assumed to be a single strip of
* tile images, each with equal width and height.
*/
protected BufferedImage getSubimage (BufferedImage source, int index)
{
int size = source.getHeight(), x = size * index;
return source.getSubimage(x, 0, size, size);
}
/**
* Get the fringe index specified by the fringebits. If no index
* is available, try breaking down the bits into contiguous regions of
* bits and look for indexes for those.
*/
protected int[] getFringeIndexes (int bits)
{
int index = BITS_TO_INDEX[bits];
if (index != -1) {
int[] ret = new int[1];
ret[0] = index;
return ret;
}
// otherwise, split the bits into contiguous components
// look for a zero and start our first split
int start = 0;
while ((((1 << start) & bits) != 0) && (start < NUM_FRINGEBITS)) {
start++;
}
if (start == NUM_FRINGEBITS) {
// we never found an empty fringebit, and since index (above)
// was already -1, we have no fringe tile for these bits.. sad.
return new int[0];
}
ArrayList indexes = new ArrayList();
int weebits = 0;
for (int ii=(start + 1) % NUM_FRINGEBITS; ii != start;
ii = (ii + 1) % NUM_FRINGEBITS) {
if (((1 << ii) & bits) != 0) {
weebits |= (1 << ii);
} else if (weebits != 0) {
index = BITS_TO_INDEX[weebits];
if (index != -1) {
indexes.add(Integer.valueOf(index));
}
weebits = 0;
}
}
if (weebits != 0) {
index = BITS_TO_INDEX[weebits];
if (index != -1) {
indexes.add(Integer.valueOf(index));
}
}
int[] ret = new int[indexes.size()];
for (int ii=0; ii < ret.length; ii++) {
ret[ii] = ((Integer) indexes.get(ii)).intValue();
}
return ret;
}
/** A record for holding information about a particular fringe as
* we're computing what it will look like. */
protected static class FringerRec implements Comparable
{
public String fringerType;
public int priority;
public int bits;
public FringerRec next;
public FringerRec (String type, int pri, FringerRec next) {
fringerType = type;
priority = pri;
this.next = next;
}
public FringerRec find (String type)
{
if (fringerType.equals(type)) {
return this;
} else if (next != null) {
return next.find(type);
} else {
return null;
}
}
public FringerRec[] toArray ()
{
return toArray(0);
}
public int compareTo (Object o) {
return priority - ((FringerRec) o).priority;
}
public String toString () {
return "[type=" + fringerType + ", pri=" + priority +
", bits=" + Integer.toString(bits, 16) + "]";
}
protected FringerRec[] toArray (int index)
{
FringerRec[] array;
if (next == null) {
array = new FringerRec[index+1];
} else {
array = next.toArray(index+1);
}
array[index] = this;
return array;
}
}
protected static final int NORTH = 1 << 0;
protected static final int NORTHEAST = 1 << 1;
protected static final int EAST = 1 << 2;
protected static final int SOUTHEAST = 1 << 3;
protected static final int SOUTH = 1 << 4;
protected static final int SOUTHWEST = 1 << 5;
protected static final int WEST = 1 << 6;
protected static final int NORTHWEST = 1 << 7;
protected static final int NUM_FRINGEBITS = 8;
/** A matrix mapping adjacent tiles to which fringe bits they affect.
* (x and y are offset by +1, since we can't have -1 as an array index).
* These are "upside down" thanks to OpenGL. */
protected static final int[] FLAGMATRIX = {
SOUTHWEST, (SOUTHEAST | SOUTH | SOUTHWEST), SOUTHEAST,
(NORTHWEST | WEST | SOUTHWEST), 0, (NORTHEAST | EAST | SOUTHEAST),
NORTHWEST, (NORTHWEST | NORTH | NORTHEAST), NORTHEAST,
};
/** The fringe tiles we use. These are the 17 possible tiles made up
* of continuous fringebits sections. */
protected static final int[] FRINGETILES = {
SOUTHEAST,
SOUTHWEST | SOUTH | SOUTHEAST,
SOUTHWEST,
NORTHEAST | EAST | SOUTHEAST,
NORTHWEST | WEST | SOUTHWEST,
NORTHEAST,
NORTHWEST | NORTH | NORTHEAST,
NORTHWEST,
SOUTHWEST | WEST | NORTHWEST | NORTH | NORTHEAST,
NORTHWEST | NORTH | NORTHEAST | EAST | SOUTHEAST,
NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST,
SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST,
NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST,
SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST | WEST | SOUTHWEST,
SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST | NORTH | NORTHWEST,
NORTHWEST | WEST | SOUTHWEST | SOUTH | SOUTHEAST | EAST | NORTHEAST,
// all the directions!
NORTH | NORTHEAST | EAST | SOUTHEAST | SOUTH | SOUTHWEST |
WEST | NORTHWEST
};
/** A reverse map of the {@link #FRINGETILES} array, for quickly
* looking up which tile we want. */
protected static final int[] BITS_TO_INDEX;
// Construct the BITS_TO_INDEX array.
static {
int num = (1 << NUM_FRINGEBITS);
BITS_TO_INDEX = new int[num];
// first clear everything to -1 (meaning there is no tile defined)
for (int ii=0; ii < num; ii++) {
BITS_TO_INDEX[ii] = -1;
}
// then fill in with the defined tiles.
for (int ii=0; ii < FRINGETILES.length; ii++) {
BITS_TO_INDEX[FRINGETILES[ii]] = ii;
}
}
protected ImageSource _isrc;
protected FringeConfiguration _config;
}
@@ -0,0 +1,76 @@
//
// $Id: CompileFringeConfigurationTask.java 3099 2004-08-27 02:21:06Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tile.tools;
import java.io.File;
import java.io.Serializable;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import com.samskivert.io.PersistenceException;
import com.threerings.util.CompiledConfig;
import com.threerings.jme.tile.tools.xml.FringeConfigurationParser;
/**
* Compiles a fringe configuration from XML format into binary format.
*/
public class CompileFringeConfigurationTask extends Task
{
public void setConfig (File config)
{
_config = config;
}
public void setTarget (File target)
{
_target = target;
}
public void execute () throws BuildException
{
// make sure the config file exists
if (!_config.exists()) {
throw new BuildException(
"Fringe configuration file not found [path=" + _config + "].");
}
FringeConfigurationParser parser = new FringeConfigurationParser();
Serializable config;
try {
config = parser.parseConfig(_config);
} catch (Exception e) {
throw new BuildException("Failure parsing fringe config", e);
}
try {
// and write it on out
CompiledConfig.saveConfig(_target, config);
} catch (Exception e) {
throw new BuildException("Failure writing serialized config", e);
}
}
protected File _config;
protected File _target;
}
@@ -0,0 +1,84 @@
//
// $Id: FringeConfigurationParser.java 3254 2004-11-30 20:03:47Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tile.tools.xml;
import java.io.Serializable;
import org.apache.commons.digester.Digester;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.threerings.tools.xml.CompiledConfigParser;
import com.threerings.jme.Log;
import com.threerings.jme.tile.FringeConfiguration.TileRecord;
import com.threerings.jme.tile.FringeConfiguration.FringeRecord;
import com.threerings.jme.tile.FringeConfiguration;
/**
* Parses fringe config definitions, which look like so (with angle
* brackets instead of square):
* <pre>
* [fringe]
* [tile type="water" priority="100"]
* [fringe name="water_fringe_1"/]
* [fringe name="water_fringe_2"/]
* [fringe name="water_fringe_3"/]
* [/tile]
* [tile type="dirt" priority="10"]
* [fringe name="dirt_fringe_1" mask="true"/]
* [/tile]
* [tile type="cobble" priority="100"]
* [fringe name="cobble_fringe_1" mask="false"/]
* [/tile]
* [/fringe]
* </pre>
*/
public class FringeConfigurationParser extends CompiledConfigParser
{
// documentation inherited
protected Serializable createConfigObject ()
{
return new FringeConfiguration();
}
// documentation inherited
protected void addRules (Digester digest)
{
// configure top-level constraints
String prefix = "fringe";
digest.addRule(prefix, new SetPropertyFieldsRule());
// create and configure fringe config instances
prefix += "/tile";
digest.addObjectCreate(prefix, TileRecord.class.getName());
digest.addRule(prefix, new SetPropertyFieldsRule());
digest.addSetNext(prefix, "addTileRecord");
// create the fringe type records in each tile record
prefix += "/fringe";
digest.addObjectCreate(prefix, FringeRecord.class.getName());
digest.addRule(prefix, new SetPropertyFieldsRule());
digest.addSetNext(prefix, "addFringe");
}
}
@@ -0,0 +1,165 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
import com.threerings.jme.Log;
import com.threerings.jme.model.Model;
/**
* A basic representation for keyframe animations.
*/
public class AnimationDef
{
/** The rate of the animation in frames per second. */
public int frameRate;
/** A single frame of the animation. */
public static class FrameDef
{
/** Transform for affected nodes. */
public ArrayList<TransformDef> transforms =
new ArrayList<TransformDef>();
public void addTransform (TransformDef transform)
{
transforms.add(transform);
}
/** Adds all transform targets in this frame to the supplied set. */
public void addTransformTargets (
HashMap<String, Spatial> nodes, HashSet<Spatial> targets)
{
for (int ii = 0, nn = transforms.size(); ii < nn; ii++) {
String name = transforms.get(ii).name;
Spatial target = nodes.get(name);
if (target != null) {
targets.add(target);
} else {
Log.debug("Missing animation target [name=" + name +
"].");
}
}
}
/** Returns the array of transforms for this frame. */
public Model.Transform[] getTransforms (Spatial[] targets)
{
Model.Transform[] mtransforms =
new Model.Transform[targets.length];
for (int ii = 0; ii < targets.length; ii++) {
mtransforms[ii] = getTransform(targets[ii]);
}
return mtransforms;
}
/** Returns the transform for the supplied target. */
protected Model.Transform getTransform (Spatial target)
{
String name = target.getName();
for (int ii = 0, nn = transforms.size(); ii < nn; ii++) {
TransformDef transform = transforms.get(ii);
if (name.equals(transform.name)) {
return transform.getTransform();
}
}
return null;
}
}
/** A transform for a single node. */
public static class TransformDef
{
/** The name of the affected node. */
public String name;
/** The transformation parameters. */
public float[] translation;
public float[] rotation;
public float[] scale;
/** Returns the live transform object. */
public Model.Transform getTransform ()
{
return new Model.Transform(
new Vector3f(translation[0], translation[1], translation[2]),
new Quaternion(rotation[0], rotation[1], rotation[2],
rotation[3]),
new Vector3f(scale[0], scale[1], scale[2]));
}
}
/** The individual frames of the animation. */
public ArrayList<FrameDef> frames = new ArrayList<FrameDef>();
public void addFrame (FrameDef frame)
{
frames.add(frame);
}
/**
* Creates the "live" animation object that will be serialized with the
* object.
*
* @param props the animation properties
* @param nodes the nodes in the model, mapped by name
*/
public Model.Animation createAnimation (
Properties props, HashMap<String, Spatial> nodes)
{
// find all affected nodes
HashSet<Spatial> targets = new HashSet<Spatial>();
for (int ii = 0, nn = frames.size(); ii < nn; ii++) {
frames.get(ii).addTransformTargets(nodes, targets);
}
// create and configure the animation
Model.Animation anim = new Model.Animation();
anim.frameRate = frameRate;
String rtype = props.getProperty("repeat_type", "clamp");
if (rtype.equals("cycle")) {
anim.repeatType = Controller.RT_CYCLE;
} else if (rtype.equals("wrap")) {
anim.repeatType = Controller.RT_WRAP;
} else {
anim.repeatType = Controller.RT_CLAMP;
}
// collect all transforms
anim.transformTargets = targets.toArray(new Spatial[targets.size()]);
anim.transforms = new Model.Transform[frames.size()][targets.size()];
for (int ii = 0; ii < anim.transforms.length; ii++) {
anim.transforms[ii] =
frames.get(ii).getTransforms(anim.transformTargets);
}
return anim;
}
}
@@ -0,0 +1,167 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import com.jme.image.Texture;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
/**
* A tool for converting cube maps (sky boxes) to sphere maps.
*/
public class BuildSphereMap
{
public static void main (String[] args)
{
if (args.length < 7) {
System.err.println("Usage: BuildSphereMap front.ext back.ext " +
"left.ext right.ext up.ext dest.ext size");
System.exit(-1);
}
try {
execute(new File(args[0]), new File(args[1]), new File(args[2]),
new File(args[3]), new File(args[4]), new File(args[5]),
Integer.parseInt(args[6]));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Builds the sphere map.
*
* @param front the file containing the front side of the cube map
* @param back the file containing the back side of the cube map
* @param left the file containing the left side of the cube map
* @param right the file containing the right side of the cube map
* @param up the file containing the up side of the cube map
* @param target the file to contain the sphere map
* @param size the size of the sphere map to generate
*/
public static void execute (File front, File back, File left, File right,
File up, File target, int size)
throws IOException
{
// load up the sides of the cube map
BufferedImage[] sides = new BufferedImage[5];
sides[FRONT] = ImageIO.read(front);
sides[BACK] = ImageIO.read(back);
sides[LEFT] = ImageIO.read(left);
sides[RIGHT] = ImageIO.read(right);
sides[UP] = ImageIO.read(up);
// compute the pixels
int[] rgb = new int[size * size];
Vector3f vec = new Vector3f();
for (int y = 0, idx = 0; y < size; y++) {
for (int x = 0; x < size; x++, idx++) {
float vx = x / (size*0.5f) - 1f, vy = y / (size*0.5f) - 1f,
d2 = vx*vx + vy*vy;
int p = 0;
if (d2 <= 1f) {
vec.set(vx, vy, FastMath.sqrt(1f - d2));
rgb[idx] = getCubeMapPixel(vec, sides);
}
}
}
// create and write the image
BufferedImage image = new BufferedImage(size, size,
BufferedImage.TYPE_INT_RGB);
image.setRGB(0, 0, size, size, rgb, 0, size);
String dest = target.toString(),
ext = dest.substring(dest.lastIndexOf('.')+1);
ImageIO.write(image, ext, target);
}
/**
* Returns the pixel from the cube map to which the given vector points.
*/
protected static int getCubeMapPixel (Vector3f vec, BufferedImage[] sides)
{
int side = getCubeMapSide(vec);
float s, t;
switch (side) {
case FRONT:
s = -vec.x / vec.y;
t = vec.z / vec.y;
break;
case BACK:
s = -vec.x / vec.y;
t = -vec.z / vec.y;
break;
case LEFT:
s = vec.y / vec.x;
t = -vec.z / vec.x;
break;
case RIGHT:
s = vec.y / vec.x;
t = vec.z / vec.x;
break;
default:
case UP:
s = vec.x / vec.z;
t = -vec.y / vec.z;
break;
}
int width = sides[side].getWidth(), height = sides[side].getHeight();
return sides[side].getRGB((int)((width-1) * (s+1f)/2f),
(int)((height-1) * (1f-t)/2f));
}
/**
* Returns the side index identifying the face of the cube map to which
* the given vector points.
*/
protected static int getCubeMapSide (Vector3f vec)
{
if (vec.x > vec.z && vec.x > vec.y && vec.x > -vec.y) {
return RIGHT;
} else if (vec.x < -vec.z && vec.x < -vec.y && vec.x < vec.y) {
return LEFT;
} else if (vec.y > vec.z) {
return FRONT;
} else if (vec.y < -vec.z) {
return BACK;
} else {
return UP;
}
}
/** The sides of the cube map. */
protected static final int FRONT = 0, BACK = 1, LEFT = 2, RIGHT = 3,
UP = 4;
}
@@ -0,0 +1,86 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.io.File;
import java.io.IOException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* An ant task for converting cube maps (sky boxes) to sphere maps.
*/
public class BuildSphereMapTask extends Task
{
public void setFront (File front)
{
_front = front;
}
public void setBack (File back)
{
_back = back;
}
public void setLeft (File left)
{
_left = left;
}
public void setRight (File right)
{
_right = right;
}
public void setUp (File up)
{
_up = up;
}
public void setTarget (File target)
{
_target = target;
}
public void setSize (int size)
{
_size = size;
}
public void execute () throws BuildException
{
try {
BuildSphereMap.execute(_front, _back, _left, _right, _up, _target,
_size);
} catch (IOException e) {
throw new BuildException("Failure building sphere map", e);
}
}
/** The files representing the sides of the cube map and the target. */
protected File _front, _back, _left, _right, _up, _target;
/** The size of the target image. */
protected int _size;
}
@@ -0,0 +1,139 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;
import java.util.logging.Level;
import com.jme.scene.Spatial;
import com.jme.util.LoggingSystem;
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil;
import com.threerings.jme.model.Model;
import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.tools.xml.AnimationParser;
import com.threerings.jme.tools.xml.ModelParser;
/**
* An application for compiling 3D models defined in XML to fast-loading binary
* files.
*/
public class CompileModel
{
/**
* Loads the model described by the given properties file and compiles it
* to a <code>.dat</code> file in the same directory.
*
* @return the loaded model, or <code>null</code> if the compiled version
* is up-to-date
*/
public static Model compile (File source)
throws Exception
{
String spath = source.toString();
int didx = spath.lastIndexOf('.');
String root = (didx == -1) ? spath : spath.substring(0, didx);
File content = new File(root + ".mxml"),
target = new File(root + ".dat");
boolean needsUpdate = false;
if (source.lastModified() >= target.lastModified() ||
content.lastModified() >= target.lastModified()) {
needsUpdate = true;
}
// load the model properties
Properties props = new Properties();
FileInputStream in = new FileInputStream(source);
props.load(in);
in.close();
// locate the animations, if any
String[] anims =
StringUtil.parseStringArray(props.getProperty("animations", ""));
File[] afiles = new File[anims.length];
File dir = source.getParentFile();
for (int ii = 0; ii < anims.length; ii++) {
afiles[ii] = new File(dir, anims[ii] + ".mxml");
if (afiles[ii].lastModified() >= target.lastModified()) {
needsUpdate = true;
}
}
if (!needsUpdate) {
return null;
}
System.out.println("Compiling " + source.getParent() + "...");
// load the model content
ModelDef mdef = _mparser.parseModel(content.toString());
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
Model model = mdef.createModel(props, nodes);
model.initPrototype();
// load the animations, if any
for (int ii = 0; ii < anims.length; ii++) {
System.out.println(" Adding " + afiles[ii] + "...");
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString());
model.addAnimation(anims[ii], adef.createAnimation(
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
}
// write and return the model
model.writeToFile(target);
return model;
}
public static void main (String[] args)
{
if (args.length < 1) {
System.err.println("Usage: CompileModel source.properties");
System.exit(-1);
}
// create a dummy display system
new DummyDisplaySystem();
LoggingSystem.getLogger().setLevel(Level.WARNING);
try {
compile(new File(args[0]));
} catch (Exception e) {
System.err.println("Error compiling model: " + e);
}
}
/** A parser for the model definitions. */
protected static ModelParser _mparser = new ModelParser();
/** A parser for the animation definitions. */
protected static AnimationParser _aparser = new AnimationParser();
}
@@ -0,0 +1,77 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.io.File;
import java.util.ArrayList;
import java.util.logging.Level;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import com.jme.util.LoggingSystem;
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
/**
* An ant task for compiling 3D models defined in XML to fast-loading binary
* files.
*/
public class CompileModelTask extends Task
{
public void addFileset (FileSet set)
{
_filesets.add(set);
}
public void init () throws BuildException
{
// create a dummy display system
new DummyDisplaySystem();
LoggingSystem.getLogger().setLevel(Level.WARNING);
}
public void execute ()
throws BuildException
{
for (int ii = 0, nn = _filesets.size(); ii < nn; ii++) {
FileSet fs = _filesets.get(ii);
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (int f = 0; f < srcFiles.length; f++) {
File source = new File(fromDir, srcFiles[f]);
try {
CompileModel.compile(source);
} catch (Exception e) {
System.err.println("Error compiling " + source + ": " + e);
}
}
}
}
/** A list of filesets that contain XML models. */
protected ArrayList<FileSet> _filesets = new ArrayList<FileSet>();
}
@@ -0,0 +1,101 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import com.jmex.model.XMLparser.Converters.AseToJme;
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
import com.jmex.model.XMLparser.Converters.FormatConverter;
import com.jmex.model.XMLparser.Converters.MaxToJme;
import com.jmex.model.XMLparser.Converters.Md2ToJme;
import com.jmex.model.XMLparser.Converters.Md3ToJme;
import com.jmex.model.XMLparser.Converters.ObjToJme;
/**
* A tool for converting various 3D model formats into JME's internal
* format.
*/
public class ConvertModel
{
public static void main (String[] args)
{
if (args.length < 2) {
System.err.println("Usage: ConvertModel source.ext dest.jme");
System.exit(-1);
}
// create a dummy display system which the converters need
new DummyDisplaySystem();
ConvertModel app = new ConvertModel();
File source = new File(args[0]);
File target = new File(args[1]);
String path = source.getPath().toLowerCase();
String type = path.substring(path.lastIndexOf(".") + 1);
// set up our converter
FormatConverter convert = null;
if (type.equals("obj")) {
convert = new ObjToJme();
try {
convert.setProperty("mtllib", new URL("file:" + source));
} catch (Exception e) {
System.err.println("Failed to create material URL: " + e);
System.exit(-1);
}
} else if (type.equals("3ds")) {
convert = new MaxToJme();
} else if (type.equals("md2")) {
convert = new Md2ToJme();
} else if (type.equals("md3")) {
convert = new Md3ToJme();
} else if (type.equals("ase")) {
convert = new AseToJme();
} else {
System.err.println("Unknown model type '" + type + "'.");
System.exit(-1);
}
// and do the deed
try {
BufferedOutputStream bout = new BufferedOutputStream(
new FileOutputStream(target));
BufferedInputStream bin = new BufferedInputStream(
new FileInputStream(source));
convert.convert(bin, bout);
bout.close();
} catch (IOException ioe) {
System.err.println("Error converting '" + source +
"' to '" + target + "'.");
ioe.printStackTrace(System.err);
System.exit(-1);
}
}
}
@@ -0,0 +1,135 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;
import com.jme.util.LoggingSystem;
import com.jmex.model.XMLparser.Converters.AseToJme;
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
import com.jmex.model.XMLparser.Converters.FormatConverter;
import com.jmex.model.XMLparser.Converters.MaxToJme;
import com.jmex.model.XMLparser.Converters.Md2ToJme;
import com.jmex.model.XMLparser.Converters.Md3ToJme;
import com.jmex.model.XMLparser.Converters.ObjToJme;
/**
* An ant task for converting various 3D model formats into JME's internal
* format.
*/
public class ConvertModelTask extends Task
{
public void addFileset (FileSet set)
{
_filesets.add(set);
}
public void init () throws BuildException
{
// create a dummy display system which the converters need
new DummyDisplaySystem();
LoggingSystem.getLogger().setLevel(Level.WARNING);
}
public void execute () throws BuildException
{
for (int i = 0; i < _filesets.size(); i++) {
FileSet fs = (FileSet)_filesets.get(i);
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
File fromDir = fs.getDir(getProject());
String[] srcFiles = ds.getIncludedFiles();
for (int f = 0; f < srcFiles.length; f++) {
File cfile = new File(fromDir, srcFiles[f]);
String target = srcFiles[f];
int didx = target.lastIndexOf(".");
target = (didx == -1) ? target : target.substring(0, didx);
target += ".jme";
convertModel(cfile, new File(fromDir, target));
}
}
}
protected void convertModel (File source, File target)
{
if (source.lastModified() < target.lastModified()) {
return;
}
System.out.println("Converting " + source + "...");
String path = source.getPath().toLowerCase();
String type = path.substring(path.lastIndexOf(".") + 1);
// set up our converter
FormatConverter convert = null;
if (type.equals("obj")) {
convert = new ObjToJme();
try {
convert.setProperty("mtllib", new URL("file:" + source));
} catch (Exception e) {
System.err.println("Failed to create material URL: " + e);
return;
}
} else if (type.equals("3ds")) {
convert = new MaxToJme();
} else if (type.equals("md2")) {
convert = new Md2ToJme();
} else if (type.equals("md3")) {
convert = new Md3ToJme();
} else if (type.equals("ase")) {
convert = new AseToJme();
} else {
System.err.println("Unknown model type '" + type + "'.");
return;
}
// and do the deed
try {
BufferedOutputStream bout = new BufferedOutputStream(
new FileOutputStream(target));
BufferedInputStream bin = new BufferedInputStream(
new FileInputStream(source));
convert.convert(bin, bout);
bout.close();
} catch (IOException ioe) {
System.err.println("Error converting '" + source +
"' to '" + target + "'.");
ioe.printStackTrace(System.err);
}
}
/** A list of filesets that contain tileset bundle definitions. */
protected ArrayList _filesets = new ArrayList();
}
@@ -0,0 +1,573 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.math.FastMath;
import com.jme.scene.Spatial;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
import com.threerings.jme.model.Model;
import com.threerings.jme.model.ModelController;
import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh;
/**
* An intermediate representation for models used to store data parsed from
* XML and convert it into JME nodes.
*/
public class ModelDef
{
/** The base class of nodes in the model. */
public abstract static class SpatialDef
{
/** The node's name. */
public String name;
/** The name of the node's parent. */
public String parent;
/** The node's transformation. */
public float[] translation;
public float[] rotation;
public float[] scale;
/** Returns a JME node for this definition. */
public Spatial getSpatial (Properties props)
{
if (_spatial == null) {
_spatial = createSpatial(new NodeProperties(props, name));
setTransform();
}
return _spatial;
}
/** Sets the transform of the created node. */
protected void setTransform ()
{
_spatial.getLocalTranslation().set(translation[0], translation[1],
translation[2]);
_spatial.getLocalRotation().set(rotation[0], rotation[1],
rotation[2], rotation[3]);
_spatial.getLocalScale().set(scale[0], scale[1], scale[2]);
}
/** Creates a JME node for this definition. */
public abstract Spatial createSpatial (Properties props);
/** Resolves any name references using the supplied map. */
public void resolveReferences (
HashMap<String, Spatial> nodes, HashSet<Spatial> referenced)
{
Spatial pnode = nodes.get(parent);
if (pnode instanceof ModelNode) {
((ModelNode)pnode).attachChild(_spatial);
} else if (parent != null) {
Log.warning("Missing or invalid parent node [spatial=" +
name + ", parent=" + parent + "].");
}
}
/** The JME node created for this definition. */
protected Spatial _spatial;
}
/** A rigid triangle mesh. */
public static class TriMeshDef extends SpatialDef
{
/** The geometry offset transform. */
public float[] offsetTranslation;
public float[] offsetRotation;
public float[] offsetScale;
/** Whether or not the mesh allows back face culling. */
public boolean solid;
/** The texture of the mesh, if any. */
public String texture;
/** Whether or not the mesh is (partially) transparent. */
public boolean transparent;
/** The vertices of the mesh. */
public ArrayList<Vertex> vertices = new ArrayList<Vertex>();
/** The triangle indices. */
public ArrayList<Integer> indices = new ArrayList<Integer>();
/** Whether or not any of the vertices have texture coordinates. */
public boolean tcoords;
public void addVertex (Vertex vertex)
{
int idx = vertices.indexOf(vertex);
if (idx != -1) {
indices.add(idx);
} else {
indices.add(vertices.size());
vertices.add(vertex);
}
tcoords = tcoords || vertex.tcoords != null;
}
// documentation inherited
public Spatial createSpatial (Properties props)
{
ModelNode node = new ModelNode(name);
if (indices.size() > 0) {
_mesh = createMesh();
configureMesh(props);
node.attachChild(_mesh);
}
return node;
}
/** Creates the mesh to attach to the node. */
protected ModelMesh createMesh ()
{
return new ModelMesh("mesh");
}
/** Configures the mesh. */
protected void configureMesh (Properties props)
{
// set the geometry offset
if (offsetTranslation != null) {
_mesh.getLocalTranslation().set(offsetTranslation[0],
offsetTranslation[1], offsetTranslation[2]);
}
if (offsetRotation != null) {
_mesh.getLocalRotation().set(offsetRotation[0],
offsetRotation[1], offsetRotation[2], offsetRotation[3]);
}
if (offsetScale != null) {
_mesh.getLocalScale().set(offsetScale[0], offsetScale[1],
offsetScale[2]);
}
// make sure texture is just a filename
int sidx = (texture == null) ? -1 :
Math.max(texture.lastIndexOf('/'), texture.lastIndexOf('\\'));
if (sidx != -1) {
texture = texture.substring(sidx + 1);
}
// configure using properties
_mesh.configure(solid, texture, transparent, props);
// set the various buffers
int vsize = vertices.size();
ByteOrder no = ByteOrder.nativeOrder();
ByteBuffer vbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
nbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
tbbuf = tcoords ?
ByteBuffer.allocateDirect(vsize*2*4).order(no) : null,
ibbuf = ByteBuffer.allocateDirect(indices.size()*4).order(no);
FloatBuffer vbuf = vbbuf.asFloatBuffer(),
nbuf = nbbuf.asFloatBuffer(),
tbuf = tcoords ? tbbuf.asFloatBuffer() : null;
for (int ii = 0; ii < vsize; ii++) {
vertices.get(ii).setInBuffers(vbuf, nbuf, tbuf);
}
IntBuffer ibuf = ibbuf.asIntBuffer();
for (int ii = 0, nn = indices.size(); ii < nn; ii++) {
ibuf.put(indices.get(ii));
}
_mesh.reconstruct(vbbuf, nbbuf, null, tbbuf, ibbuf);
_mesh.setModelBound("sphere".equals(props.getProperty("bound")) ?
new BoundingSphere() : new BoundingBox());
_mesh.updateModelBound();
// set the mesh's origin to the center of its bounding box
_mesh.centerVertices();
}
/** The mesh that contains the actual geometry. */
protected ModelMesh _mesh;
}
/** A triangle mesh that deforms according to bone positions. */
public static class SkinMeshDef extends TriMeshDef
{
@Override // documentation inherited
protected ModelMesh createMesh ()
{
return new SkinMesh("mesh");
}
@Override // documentation inherited
public void resolveReferences (
HashMap<String, Spatial> nodes, HashSet<Spatial> referenced)
{
super.resolveReferences(nodes, referenced);
if (_mesh == null) {
return;
}
// create and set the final weight groups
SkinMesh.WeightGroup[] wgroups =
new SkinMesh.WeightGroup[_groups.size()];
HashMap<String, SkinMesh.Bone> bones =
new HashMap<String, SkinMesh.Bone>();
int ii = 0;
for (Map.Entry<Set<String>, WeightGroupDef> entry :
_groups.entrySet()) {
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
wgroup.vertexCount = entry.getValue().indices.size();
wgroup.bones = new SkinMesh.Bone[entry.getKey().size()];
int jj = 0;
for (String bname : entry.getKey()) {
SkinMesh.Bone bone = bones.get(bname);
if (bone == null) {
Spatial node = nodes.get(bname);
bones.put(bname,
bone = new SkinMesh.Bone((ModelNode)node));
referenced.add(node);
}
wgroup.bones[jj++] = bone;
}
wgroup.weights = toArray(entry.getValue().weights);
wgroups[ii++] = wgroup;
}
((SkinMesh)_mesh).setWeightGroups(wgroups);
}
@Override // documentation inherited
protected void configureMesh (Properties props)
{
// divide the vertices up by weight groups
_groups = new HashMap<Set<String>, WeightGroupDef>();
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
SkinVertex svertex = (SkinVertex)vertices.get(ii);
Set<String> bones = svertex.boneWeights.keySet();
WeightGroupDef group = _groups.get(bones);
if (group == null) {
_groups.put(bones, group = new WeightGroupDef());
}
group.indices.add(ii);
for (String bone : bones) {
group.weights.add(svertex.boneWeights.get(bone).weight);
}
}
// reorder the vertices by group
ArrayList<Vertex> overts = vertices;
vertices = new ArrayList<Vertex>();
int[] imap = new int[overts.size()];
for (Map.Entry<Set<String>, WeightGroupDef> entry :
_groups.entrySet()) {
for (int idx : entry.getValue().indices) {
imap[idx] = vertices.size();
vertices.add(overts.get(idx));
}
}
for (int ii = 0, nn = indices.size(); ii < nn; ii++) {
indices.set(ii, imap[indices.get(ii)]);
}
super.configureMesh(props);
}
/** The intermediate weight groups, mapped by bone names. */
protected HashMap<Set<String>, WeightGroupDef> _groups;
}
/** A generic node. */
public static class NodeDef extends SpatialDef
{
// documentation inherited
public Spatial createSpatial (Properties props)
{
return new ModelNode(name);
}
}
/** A basic vertex. */
public static class Vertex
{
public float[] location;
public float[] normal;
public float[] tcoords;
public void setInBuffers (
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
{
vbuf.put(location);
nbuf.put(normal);
if (tbuf != null) {
if (tcoords != null) {
tbuf.put(tcoords);
} else {
tbuf.put(0f);
tbuf.put(0f);
}
}
}
public boolean equals (Object obj)
{
Vertex overt = (Vertex)obj;
return Arrays.equals(location, overt.location) &&
Arrays.equals(normal, overt.normal) &&
Arrays.equals(tcoords, overt.tcoords);
}
}
/** A vertex influenced by a number of bones. */
public static class SkinVertex extends Vertex
{
/** The bones influencing the vertex, mapped by name. */
public HashMap<String, BoneWeight> boneWeights =
new HashMap<String, BoneWeight>();
public void addBoneWeight (BoneWeight weight)
{
if (weight.weight == 0f) {
return;
}
BoneWeight bweight = boneWeights.get(weight.bone);
if (bweight != null) {
bweight.weight += weight.weight;
} else {
boneWeights.put(weight.bone, weight);
}
}
/** Finds the bone nodes influencing this vertex. */
public HashSet<ModelNode> getBones (HashMap<String, Spatial> nodes)
{
HashSet<ModelNode> bones = new HashSet<ModelNode>();
for (String bone : boneWeights.keySet()) {
Spatial node = nodes.get(bone);
if (node instanceof ModelNode) {
bones.add((ModelNode)node);
} else {
Log.warning("Missing or invalid bone for bone weight " +
"[bone=" + bone + "].");
}
}
return bones;
}
/** Returns the weight of the given bone. */
public float getWeight (ModelNode bone)
{
BoneWeight bweight = boneWeights.get(bone.getName());
return (bweight == null) ? 0f : bweight.weight;
}
}
/** The influence of a bone on a vertex. */
public static class BoneWeight
{
/** The name of the influencing bone. */
public String bone;
/** The amount of influence. */
public float weight;
}
/** A group of vertices influenced by the same bone. */
public static class WeightGroupDef
{
/** The indices of the affected vertex. */
public ArrayList<Integer> indices = new ArrayList<Integer>();
/** The interleaved vertex weights. */
public ArrayList<Float> weights = new ArrayList<Float>();
}
/** The meshes and bones comprising the model. */
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
public void addSpatial (SpatialDef spatial)
{
// put nodes before meshes so that bones are updated before skin
spatials.add(spatial instanceof NodeDef ? 0 : spatials.size(),
spatial);
}
/**
* Creates the model node defined herein.
*
* @param props the properties of the model
* @param nodes a node map to populate
*/
public Model createModel (Properties props, HashMap<String, Spatial> nodes)
{
Model model = new Model(props.getProperty("name", "model"), props);
// set the overall scale
model.setLocalScale(Float.parseFloat(props.getProperty("scale", "1")));
// start by creating the spatials and mapping them to their names
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
Spatial spatial = spatials.get(ii).getSpatial(props);
nodes.put(spatial.getName(), spatial);
}
// then go through again, resolving any name references and attaching
// root children
HashSet<Spatial> referenced = new HashSet<Spatial>();
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
SpatialDef sdef = spatials.get(ii);
sdef.resolveReferences(nodes, referenced);
if (sdef.getSpatial(props).getParent() == null) {
model.attachChild(sdef.getSpatial(props));
}
}
// create any controllers listed
String[] controllers = StringUtil.parseStringArray(
props.getProperty("controllers", ""));
for (int ii = 0; ii < controllers.length; ii++) {
Spatial target = nodes.get(controllers[ii]);
if (target == null) {
Log.warning("Missing controller node [name=" +
controllers[ii] + "].");
continue;
}
ModelController ctrl = createController(
PropertiesUtil.getSubProperties(props, controllers[ii]),
target);
if (ctrl != null) {
model.addController(ctrl);
}
}
// get rid of any nodes that serve no purpose
pruneUnusedNodes(model, nodes, referenced);
return model;
}
/** Creates, configures, and returns a model controller. */
protected ModelController createController (
Properties props, Spatial target)
{
// attempt to create an instance of the controller
ModelController ctrl;
String cname = props.getProperty("class", "");
try {
ctrl = (ModelController)Class.forName(cname).newInstance();
} catch (Exception e) {
Log.warning("Error instantiating controller [class=" + cname +
", error=" + e + "].");
return null;
}
ctrl.configure(props, target);
return ctrl;
}
/** Recursively removes any unused nodes. */
protected boolean pruneUnusedNodes (
ModelNode node, HashMap<String, Spatial> nodes,
HashSet<Spatial> referenced)
{
boolean hasValidChildren = false;
for (int ii = node.getQuantity() - 1; ii >= 0; ii--) {
Spatial child = node.getChild(ii);
if (!(child instanceof ModelNode) ||
pruneUnusedNodes((ModelNode)child, nodes, referenced)) {
hasValidChildren = true;
} else {
node.detachChildAt(ii);
nodes.remove(child.getName());
}
}
return referenced.contains(node) || hasValidChildren;
}
/** Converts a boxed Integer list to an unboxed int array. */
protected static int[] toArray (ArrayList<Integer> list)
{
int[] array = new int[list.size()];
for (int ii = 0, nn = list.size(); ii < nn; ii++) {
array[ii] = list.get(ii);
}
return array;
}
/** Converts a boxed Float list to an unboxed float array. */
protected static float[] toArray (ArrayList<Float> list)
{
float[] array = new float[list.size()];
for (int ii = 0, nn = list.size(); ii < nn; ii++) {
array[ii] = list.get(ii);
}
return array;
}
/** A wrapper for the model properties providing access to the properties
* of a node within the model. */
protected static class NodeProperties extends Properties
{
public NodeProperties (Properties mprops, String name)
{
_mprops = mprops;
_prefix = name + ".";
}
@Override // documentation inherited
public String getProperty (String key)
{
return getProperty(key, null);
}
@Override // documentation inherited
public String getProperty (String key, String defaultValue)
{
return _mprops.getProperty(_prefix + key,
_mprops.getProperty(key, defaultValue));
}
/** The properties of the model. */
protected Properties _mprops;
/** The node prefix. */
protected String _prefix;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,88 @@
//
// $Id: SceneParser.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools.xml;
import java.io.FileInputStream;
import java.io.IOException;
import org.xml.sax.SAXException;
import org.apache.commons.digester.Digester;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.threerings.jme.tools.AnimationDef;
/**
* Parses XML files containing animations.
*/
public class AnimationParser
{
public AnimationParser ()
{
// create and configure our digester
_digester = new Digester();
// add the rules
String anim = "animation";
_digester.addObjectCreate(anim, AnimationDef.class.getName());
_digester.addRule(anim, new SetPropertyFieldsRule());
_digester.addSetNext(anim, "setAnimation",
AnimationDef.class.getName());
String frame = anim + "/frame";
_digester.addObjectCreate(frame,
AnimationDef.FrameDef.class.getName());
_digester.addSetNext(frame, "addFrame",
AnimationDef.FrameDef.class.getName());
String xform = frame + "/transform";
_digester.addObjectCreate(xform,
AnimationDef.TransformDef.class.getName());
_digester.addRule(xform, new SetPropertyFieldsRule());
_digester.addSetNext(xform, "addTransform",
AnimationDef.TransformDef.class.getName());
}
/**
* Parses the XML file at the specified path into an animation
* definition.
*/
public AnimationDef parseAnimation (String path)
throws IOException, SAXException
{
_animation = null;
_digester.push(this);
_digester.parse(new FileInputStream(path));
return _animation;
}
/**
* Called by the parser once the animation is parsed.
*/
public void setAnimation (AnimationDef animation)
{
_animation = animation;
}
protected Digester _digester;
protected AnimationDef _animation;
}
@@ -0,0 +1,109 @@
//
// $Id: SceneParser.java 3749 2005-11-09 04:00:16Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.tools.xml;
import java.io.FileInputStream;
import java.io.IOException;
import org.xml.sax.SAXException;
import org.apache.commons.digester.Digester;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.threerings.jme.tools.ModelDef;
/**
* Parses XML files containing 3D models.
*/
public class ModelParser
{
public ModelParser ()
{
// create and configure our digester
_digester = new Digester();
// add the rules
String model = "model";
_digester.addObjectCreate(model, ModelDef.class.getName());
_digester.addSetNext(model, "setModel", ModelDef.class.getName());
String tmesh = model + "/triMesh";
_digester.addObjectCreate(tmesh, ModelDef.TriMeshDef.class.getName());
_digester.addRule(tmesh, new SetPropertyFieldsRule());
_digester.addSetNext(tmesh, "addSpatial",
ModelDef.SpatialDef.class.getName());
String smesh = model + "/skinMesh";
_digester.addObjectCreate(smesh,
ModelDef.SkinMeshDef.class.getName());
_digester.addRule(smesh, new SetPropertyFieldsRule());
_digester.addSetNext(smesh, "addSpatial",
ModelDef.SpatialDef.class.getName());
String node = model + "/node";
_digester.addObjectCreate(node, ModelDef.NodeDef.class.getName());
_digester.addRule(node, new SetPropertyFieldsRule());
_digester.addSetNext(node, "addSpatial",
ModelDef.SpatialDef.class.getName());
String vertex = tmesh + "/vertex", svertex = smesh + "/vertex";
_digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
_digester.addObjectCreate(svertex,
ModelDef.SkinVertex.class.getName());
_digester.addRule(vertex, new SetPropertyFieldsRule());
_digester.addRule(svertex, new SetPropertyFieldsRule());
_digester.addSetNext(vertex, "addVertex",
ModelDef.Vertex.class.getName());
_digester.addSetNext(svertex, "addVertex",
ModelDef.Vertex.class.getName());
String bweight = smesh + "/vertex/boneWeight";
_digester.addObjectCreate(bweight,
ModelDef.BoneWeight.class.getName());
_digester.addRule(bweight, new SetPropertyFieldsRule());
_digester.addSetNext(bweight, "addBoneWeight",
ModelDef.BoneWeight.class.getName());
}
/**
* Parses the XML file at the specified path into a model definition.
*/
public ModelDef parseModel (String path)
throws IOException, SAXException
{
_model = null;
_digester.push(this);
_digester.parse(new FileInputStream(path));
return _model;
}
/**
* Called by the parser once the model is parsed.
*/
public void setModel (ModelDef model)
{
_model = model;
}
protected Digester _digester;
protected ModelDef _model;
}
@@ -0,0 +1,42 @@
//
// $Id: LinearTimeFunction.java 3122 2004-09-18 22:57:08Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.util;
/**
* Varies a value linearly with time.
*/
public class LinearTimeFunction extends TimeFunction
{
public LinearTimeFunction (float start, float end, float duration)
{
super(start, end, duration);
_range = (end - start);
}
// documentation inherited
protected float computeValue ()
{
return (_elapsed * _range / _duration) + _start;
}
protected float _range;
}
@@ -0,0 +1,99 @@
//
// $Id: TimeFunction.java 3122 2004-09-18 22:57:08Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.util;
/**
* Used to vary a value over time where time is provided at discrete increments
* (on the frame tick) and the value is computed appropriately. This can be
* used for fades and other effects where different functions (linear, ease-in
* ease-out, etc.) should be easy to plug in.
*/
public abstract class TimeFunction
{
/**
* Every time function varies a value from some starting value to some
* ending value over some duration. The way in which it varies
* (linearly, for example) is up to the derived class.
*
* @param start the starting value.
* @param end the ending value.
* @param duration the duration in seconds.
*/
public TimeFunction (float start, float end, float duration)
{
_start = start;
_end = end;
_duration = duration;
}
/**
* Returns the current value given the supplied elapsed time. The value
* will be bounded to the originally supplied starting and ending values at
* times 0 (and below) and {@link #_duration} (and above) respectively.
*
* @param deltaTime the amount of time that has elapsed since the last call
* to this method.
*/
public float getValue (float deltaTime)
{
_elapsed += deltaTime;
if (_elapsed <= 0) {
return _start;
} else if (_elapsed >= _duration) {
return _end;
} else {
return computeValue();
}
}
/**
* Returns true if this function has proceeded the full length of its
* duration. This should be called after a call to {@link #getValue} has
* been made to update our internal elapsed time.
*/
public boolean isComplete ()
{
return _elapsed >= _duration;
}
/** Generates a string representation of this instance. */
public String toString ()
{
return _start + " to " + _end + ", " + _elapsed + " of " + _duration;
}
/**
* This must be implemented by our derived class to compute our value given
* the currently stored {@link #_elapsed} time.
*/
protected abstract float computeValue ();
/** Our starting and ending values. */
protected float _start, _end;
/** The number of milliseconds over which we vary our value. */
protected float _duration;
/** The number of seconds that have elapsed since we started. */
protected float _elapsed;
}