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:
@@ -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 ();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user