Hold onto your hats kids, it's the biggest little refactor this side of the

Pecos. I'll sort out the per-project niggling bits in just a moment.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4209 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2006-06-23 18:12:45 +00:00
parent 5ceed9c8b2
commit c0a244cb8d
594 changed files with 17 additions and 96041 deletions
+5 -96
View File
@@ -61,11 +61,7 @@
<providerless service="LocationService"/> <providerless service="LocationService"/>
<providerless service="BodyService"/> <providerless service="BodyService"/>
<providerless service="SimulatorService"/> <providerless service="SimulatorService"/>
<providerless service="ParlorService"/>
<providerless service="TimeBaseService"/> <providerless service="TimeBaseService"/>
<providerless service="SpotService"/>
<providerless service="ZoneService"/>
<providerless service="SceneService"/>
</service> </service>
</target> </target>
@@ -89,30 +85,6 @@
</receiver> </receiver>
</target> </target>
<!-- checks the availability of certain libraries -->
<target name="check-available">
<available property="lwjgl.present"
classname="org.lwjgl.LWJGLException" classpathref="classpath"/>
<available property="jme.present"
classname="com.jme.scene.Node" classpathref="classpath"/>
<available property="jme-bui.present"
classname="com.jmex.bui.BWindow" classpathref="classpath"/>
<available property="jme-sound.present"
classname="com.jmex.sound.openAL.SoundSystem" classpathref="classpath"/>
<condition property="build.jme">
<and>
<isset property="jme.present"/>
<isset property="jme-bui.present"/>
</and>
</condition>
<condition property="build.openal">
<and>
<isset property="lwjgl.present"/>
<isset property="jme-sound.present"/>
</and>
</condition>
</target>
<!-- prepares the application directories --> <!-- prepares the application directories -->
<target name="prepare"> <target name="prepare">
<mkdir dir="${deploy.dir}"/> <mkdir dir="${deploy.dir}"/>
@@ -135,17 +107,11 @@
</target> </target>
<!-- build the java class files --> <!-- build the java class files -->
<target name="compile" depends="check-available,prepare"> <target name="compile" depends="prepare">
<javac srcdir="src/java" destdir="${classes.dir}" <javac srcdir="src/java" destdir="${classes.dir}"
debug="on" optimize="{$build.optimize}" deprecation="on" debug="on" optimize="{$build.optimize}" deprecation="on"
source="1.5" target="1.5"> source="1.5" target="1.5">
<classpath refid="classpath"/> <classpath refid="classpath"/>
<exclude name="com/threerings/jme/**" unless="build.jme"/>
<exclude name="com/threerings/openal/**" unless="build.openal"/>
<exclude name="**/OggPlayer.java"/>
<exclude name="**/ModPlayer.java"/>
<exclude name="**/MidiPlayer.java"/>
<exclude name="**/Mp3Player.java"/>
<!--<compilerarg value="-Xlint:unchecked"/>--> <!--<compilerarg value="-Xlint:unchecked"/>-->
</javac> </javac>
</target> </target>
@@ -155,10 +121,6 @@
<echo message="Doing native compilation on ${os.name}..."/> <echo message="Doing native compilation on ${os.name}..."/>
<exec dir="src/java/com/threerings/util/signal/${os.name}" <exec dir="src/java/com/threerings/util/signal/${os.name}"
executable="make"><arg line="install"/></exec> executable="make"><arg line="install"/></exec>
<exec os="Linux" dir="src/java/com/threerings/util/keybd/Linux"
executable="make"><arg line="install"/></exec>
<exec dir="src/java/com/threerings/util/unsafe/${os.name}"
executable="make"><arg line="install"/></exec>
</target> </target>
<!-- build the javadoc documentation --> <!-- build the javadoc documentation -->
@@ -172,15 +134,14 @@
<!-- ant documentation is not available online, sorry kids --> <!-- ant documentation is not available online, sorry kids -->
<link href="file:///usr/share/doc/ant-doc/javadocs"/> <link href="file:///usr/share/doc/ant-doc/javadocs"/>
<link href="http://samskivert.com/code/samskivert/samskivert/docs/api"/> <link href="http://samskivert.com/code/samskivert/samskivert/docs/api"/>
<link href="http://www.jmonkeyengine.com/doc"/>
</javadoc> </javadoc>
<copy todir="${javadoc.home}"> <copy todir="${javadoc.home}">
<fileset dir="src/java" includes="**/*.png"/> <fileset dir="src/java" includes="**/*.png"/>
</copy> </copy>
</target> </target>
<!-- builds the javadocs and stuffs them in a directory where they won't <!-- builds the javadocs and stuffs them in a directory where they -->
be blown away when we do "clean" next time --> <!-- won't be blown away when we do "clean" next time -->
<target name="savedoc" depends="javadoc"> <target name="savedoc" depends="javadoc">
<delete dir="${savedoc.dir}/api"/> <delete dir="${savedoc.dir}/api"/>
<copy todir="${savedoc.dir}/api"> <copy todir="${savedoc.dir}/api">
@@ -203,13 +164,8 @@
<jar destfile="${deploy.dir}/${app.name}-base.jar"> <jar destfile="${deploy.dir}/${app.name}-base.jar">
<fileset dir="${classes.dir}" includes="com/threerings/io/**"/> <fileset dir="${classes.dir}" includes="com/threerings/io/**"/>
<fileset dir="${classes.dir}" includes="com/threerings/util/**"/> <fileset dir="${classes.dir}" includes="com/threerings/util/**"/>
<fileset dir="${classes.dir}" includes="com/threerings/resource/**"/>
<fileset dir="${classes.dir}" includes="com/threerings/tools/**"/> <fileset dir="${classes.dir}" includes="com/threerings/tools/**"/>
</jar> </jar>
<jar destfile="${deploy.dir}/${app.name}-media.jar">
<fileset dir="${classes.dir}" includes="com/threerings/geom/**"/>
<fileset dir="${classes.dir}" includes="com/threerings/media/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-distrib.jar"> <jar destfile="${deploy.dir}/${app.name}-distrib.jar">
<fileset dir="${classes.dir}" includes="com/threerings/presents/**"/> <fileset dir="${classes.dir}" includes="com/threerings/presents/**"/>
<fileset dir="${classes.dir}" includes="com/threerings/crowd/**"/> <fileset dir="${classes.dir}" includes="com/threerings/crowd/**"/>
@@ -217,39 +173,8 @@
<fileset dir="${classes.dir}" includes="rsrc/config/presents/**"/> <fileset dir="${classes.dir}" includes="rsrc/config/presents/**"/>
<fileset dir="${classes.dir}" includes="rsrc/config/crowd/**"/> <fileset dir="${classes.dir}" includes="rsrc/config/crowd/**"/>
</jar> </jar>
<jar destfile="${deploy.dir}/${app.name}-parlor.jar"> <jar destfile="${deploy.dir}/${app.name}-tests.jar">
<fileset dir="${classes.dir}" includes="com/threerings/parlor/**"/> <fileset dir="tests/${classes.dir}" includes="com/threerings/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-puzzle.jar">
<fileset dir="${classes.dir}" includes="com/threerings/puzzle/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-micasa.jar">
<fileset dir="${classes.dir}" includes="com/threerings/micasa/**"/>
<fileset dir="${classes.dir}" includes="rsrc/config/micasa/**"/>
<fileset dir="${classes.dir}" includes="rsrc/i18n/micasa*"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-whirled.jar">
<fileset dir="${classes.dir}" includes="com/threerings/whirled/**"/>
<fileset dir="${classes.dir}" includes="rsrc/config/whirled/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-miso.jar">
<fileset dir="${classes.dir}" includes="com/threerings/miso/**"/>
<fileset dir="${classes.dir}" includes="rsrc/config/miso/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-cast.jar">
<fileset dir="${classes.dir}" includes="com/threerings/cast/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-stage.jar">
<fileset dir="${classes.dir}" includes="com/threerings/stage/**"/>
<fileset dir="${classes.dir}" includes="rsrc/media/stage/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-jme.jar">
<fileset dir="${classes.dir}" includes="com/threerings/jme/**"/>
<fileset dir="${classes.dir}" includes="rsrc/media/jme/**"/>
<fileset dir="${classes.dir}" includes="rsrc/i18n/jme/**"/>
</jar>
<jar destfile="${deploy.dir}/${app.name}-openal.jar">
<fileset dir="${classes.dir}" includes="com/threerings/openal/**"/>
</jar> </jar>
</target> </target>
@@ -279,27 +204,11 @@
<!-- we weave everything a first time without verification so that --> <!-- we weave everything a first time without verification so that -->
<!-- interdependencies will resolve the second time --> <!-- interdependencies will resolve the second time -->
<weave inputjar="${inpre}-base.jar" outputjar="${outpre}-base.jar"/> <weave inputjar="${inpre}-base.jar" outputjar="${outpre}-base.jar"/>
<weave inputjar="${inpre}-cast.jar" outputjar="${outpre}-cast.jar"/>
<weave inputjar="${inpre}-distrib.jar" outputjar="${outpre}-distrib.jar"/> <weave inputjar="${inpre}-distrib.jar" outputjar="${outpre}-distrib.jar"/>
<weave inputjar="${inpre}-micasa.jar" outputjar="${outpre}-micasa.jar"/>
<weave inputjar="${inpre}-miso.jar" outputjar="${outpre}-miso.jar"/>
<weave inputjar="${inpre}-media.jar" outputjar="${outpre}-media.jar"/>
<weave inputjar="${inpre}-parlor.jar" outputjar="${outpre}-parlor.jar"/>
<weave inputjar="${inpre}-puzzle.jar" outputjar="${outpre}-puzzle.jar"/>
<weave inputjar="${inpre}-stage.jar" outputjar="${outpre}-stage.jar"/>
<weave inputjar="${inpre}-whirled.jar" outputjar="${outpre}-whirled.jar"/>
<!-- now weave again with the verifier to check for unweavable 1.5isms --> <!-- now weave again with the verifier to check for unweavable 1.5isms -->
<antcall target="vweave"><param name="which" value="base"/></antcall> <antcall target="vweave"><param name="which" value="base"/></antcall>
<antcall target="vweave"><param name="which" value="cast"/></antcall>
<antcall target="vweave"><param name="which" value="distrib"/></antcall> <antcall target="vweave"><param name="which" value="distrib"/></antcall>
<antcall target="vweave"><param name="which" value="micasa"/></antcall>
<antcall target="vweave"><param name="which" value="miso"/></antcall>
<antcall target="vweave"><param name="which" value="media"/></antcall>
<antcall target="vweave"><param name="which" value="parlor"/></antcall>
<antcall target="vweave"><param name="which" value="puzzle"/></antcall>
<antcall target="vweave"><param name="which" value="stage"/></antcall>
<antcall target="vweave"><param name="which" value="whirled"/></antcall>
</target> </target>
<!-- generate a class hierarchy diagram --> <!-- generate a class hierarchy diagram -->
@@ -23,11 +23,11 @@ package com.threerings.admin.client;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import com.samskivert.swing.VGroupLayout; import com.samskivert.swing.VGroupLayout;
import com.threerings.media.SafeScrollPane;
import com.threerings.presents.util.PresentsContext; import com.threerings.presents.util.PresentsContext;
import com.threerings.admin.Log; import com.threerings.admin.Log;
@@ -84,7 +84,7 @@ public class ConfigEditorPanel extends JPanel
// when we're hidden, we want to clear out our subscriptions // when we're hidden, we want to clear out our subscriptions
int ccount = _oeditors.getComponentCount(); int ccount = _oeditors.getComponentCount();
for (int ii = 0; ii < ccount; ii++) { for (int ii = 0; ii < ccount; ii++) {
SafeScrollPane scrolly = (SafeScrollPane)_oeditors.getComponent(ii); JScrollPane scrolly = (JScrollPane)_oeditors.getComponent(ii);
ObjectEditorPanel opanel = (ObjectEditorPanel) ObjectEditorPanel opanel = (ObjectEditorPanel)
scrolly.getViewport().getView(); scrolly.getViewport().getView();
opanel.cleanup(); opanel.cleanup();
@@ -101,7 +101,7 @@ public class ConfigEditorPanel extends JPanel
for (int ii = 0; ii < keys.length; ii++) { for (int ii = 0; ii < keys.length; ii++) {
ObjectEditorPanel panel = ObjectEditorPanel panel =
new ObjectEditorPanel(_ctx, keys[ii], oids[ii]); new ObjectEditorPanel(_ctx, keys[ii], oids[ii]);
SafeScrollPane scrolly = new SafeScrollPane(panel); JScrollPane scrolly = new JScrollPane(panel);
_oeditors.addTab(keys[ii], scrolly); _oeditors.addTab(keys[ii], scrolly);
if (keys[ii].equals(_defaultPane)) { if (keys[ii].equals(_defaultPane)) {
_oeditors.setSelectedComponent(scrolly); _oeditors.setSelectedComponent(scrolly);
@@ -29,6 +29,7 @@ import java.lang.reflect.Field;
import java.util.BitSet; import java.util.BitSet;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
@@ -39,8 +40,6 @@ import com.samskivert.util.ClassUtil;
import com.samskivert.util.ComparableArrayList; import com.samskivert.util.ComparableArrayList;
import com.samskivert.util.ListUtil; import com.samskivert.util.ListUtil;
import com.threerings.media.SafeScrollPane;
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.dobj.AttributeChangeListener; import com.threerings.presents.dobj.AttributeChangeListener;
import com.threerings.presents.dobj.AttributeChangedEvent; import com.threerings.presents.dobj.AttributeChangedEvent;
@@ -109,7 +108,7 @@ public class DSetEditor extends JPanel
_table = new ObjectEditorTable(entryClass, editableFields, interp); _table = new ObjectEditorTable(entryClass, editableFields, interp);
add(new SafeScrollPane(_table), BorderLayout.CENTER); add(new JScrollPane(_table), BorderLayout.CENTER);
} }
/** /**
@@ -1,42 +0,0 @@
//
// $Id$
//
// 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.cast;
/**
* A mechanism for caching composited character action animations on disk.
*/
public interface ActionCache
{
/**
* Fetches from the cache a composited set of images for a particular
* character for a particular action.
*/
public ActionFrames getActionFrames (
CharacterDescriptor descrip, String action);
/**
* Requests that the specified set of action frames for the specified
* character be cached.
*/
public void cacheActionFrames (
CharacterDescriptor descrip, String action, ActionFrames frames);
}
@@ -1,67 +0,0 @@
//
// $Id$
//
// 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.cast;
import com.threerings.media.image.Colorization;
import com.threerings.util.DirectionCodes;
/**
* Encapsulates a set of frames in each of {@link
* DirectionCodes#DIRECTION_COUNT} orientations that are used to render a
* character sprite.
*/
public interface ActionFrames
{
/**
* Returns the number of orientations available in this set of action
* frames.
*/
public int getOrientationCount ();
/**
* Returns the multi-frame image that comprises the frames for the
* specified orientation.
*/
public TrimmedMultiFrameImage getFrames (int orient);
/**
* Returns the x offset from the upper left of the image to the
* "origin" for this character frame. A sprite with location (x, y)
* will be rendered such that its origin is coincident with that
* location.
*/
public int getXOrigin (int orient, int frameIdx);
/**
* Returns the y offset from the upper left of the image to the
* "origin" for this character frame. A sprite with location (x, y)
* will be rendered such that its origin is coincident with that
* location.
*/
public int getYOrigin (int orient, int frameIdx);
/**
* Creates a clone of these action frames which will have the supplied
* colorizations applied to the frame images.
*/
public ActionFrames cloneColorized (Colorization[] zations);
}
@@ -1,72 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.awt.Point;
import java.io.Serializable;
/**
* The action sequence class describes a particular character animation
* sequence. An animation sequence consists of one or more frames of
* animation, renders at a particular frame rate, and has an origin point
* that specifies the location of the base of the character in relation to
* the bounds of the animation images.
*/
public class ActionSequence implements Serializable
{
/**
* Defines the name of the default action sequence. When component
* tilesets are loaded to build a set of composited images for a
* particular action sequence, a check is first made for a component
* tileset specific to the action sequence and then for the
* component's default tileset if the action specific tileset did not
* exist.
*/
public static final String DEFAULT_SEQUENCE = "default";
/** The action sequence name. */
public String name;
/** The number of frames per second to show when animating. */
public float framesPerSecond;
/** The position of the character's base for this sequence. */
public Point origin = new Point();
/** Orientation codes for the orientations available for this
* action. */
public int[] orients;
/**
* Returns a string representation of this action sequence.
*/
public String toString ()
{
return "[name=" + name + ", framesPerSecond=" + framesPerSecond +
", origin=" + origin +
", orients=" + (orients == null ? 0 : orients.length) + "]";
}
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -1,35 +0,0 @@
//
// $Id$
//
// 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.cast;
import com.samskivert.util.Config;
/**
* Provides access to runtime configuration parameters for this package
* and its subpackages.
*/
public class CastPrefs
{
/** Used to load our preferences from a properties file and map them
* to the persistent Java preferences repository. */
public static Config config = new Config("rsrc/config/cast");
}
@@ -1,100 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.io.Serializable;
import com.samskivert.util.StringUtil;
import com.threerings.media.sprite.Sprite;
/**
* The character component represents a single component that can be
* composited with other character components to generate an image
* representing a complete character displayable in any of the eight
* compass directions as detailed in the {@link Sprite} class direction
* constants.
*/
public class CharacterComponent implements Serializable
{
/** The unique component identifier. */
public int componentId;
/** The component's name. */
public String name;
/** The class of components to which this one belongs. */
public ComponentClass componentClass;
/**
* Constructs a character component with the specified id of the
* specified class.
*/
public CharacterComponent (int componentId, String name,
ComponentClass compClass, FrameProvider fprov)
{
this.componentId = componentId;
this.name = name;
this.componentClass = compClass;
_frameProvider = fprov;
}
/**
* Returns the image frames for the specified action animation or null if
* no animation for the specified action is available for this component.
*
* @param type null for the normal action frames or one of the custom
* action sub-types: {@link StandardActions#SHADOW_TYPE}, etc.
*/
public ActionFrames getFrames (String action, String type)
{
return _frameProvider.getFrames(this, action, type);
}
/**
* Returns true if this component is equal to the other component. The
* comparison is made on <code>componentId</code>.
*/
public boolean equals (Object other)
{
if (other instanceof CharacterComponent) {
return componentId == ((CharacterComponent)other).componentId;
} else {
return false;
}
}
/**
* Returns a string representation of this character component.
*/
public String toString ()
{
return StringUtil.fieldsToString(this);
}
/** The entity from which we obtain our animation frames. */
protected FrameProvider _frameProvider;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
@@ -1,144 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.util.Arrays;
import com.samskivert.util.StringUtil;
import com.threerings.media.image.Colorization;
/**
* The character descriptor object details the components that are
* pieced together to create a single character image.
*/
public class CharacterDescriptor
{
/**
* Constructs a character descriptor.
*
* @param components the component ids of the individual components
* that make up this character.
* @param zations the colorizations to apply to each of the character
* component images when compositing actions (an array of
* colorizations for each component, elements of which can be null to
* make it easier to support per-component specialized colorizations).
* This can be null if image recolorization is not desired.
*/
public CharacterDescriptor (int[] components, Colorization[][] zations)
{
_components = components;
_zations = zations;
}
/**
* Returns an array of the component identifiers comprising the
* character described by this descriptor.
*/
public int[] getComponentIds ()
{
return _components;
}
/**
* Returns an array of colorization arrays to be applied to the
* components when compositing action images (one array per
* component).
*/
public Colorization[][] getColorizations ()
{
return _zations;
}
/**
* Updates the colorizations to be used by this character descriptor.
*/
public void setColorizations (Colorization[][] zations)
{
_zations = zations;
}
/**
* Compute a sensible hashcode for this object.
*/
public int hashCode ()
{
int code = 0, clength = _components.length;
for (int i = 0; i < clength; i++) {
code ^= _components[i];
}
return code;
}
/**
* Compares this character descriptor to another.
*/
public boolean equals (Object other)
{
if (!(other instanceof CharacterDescriptor)) {
return false;
}
// both the component ids and the colorizations must be equal
CharacterDescriptor odesc = (CharacterDescriptor)other;
if (!Arrays.equals(_components, odesc._components)) {
return false;
}
Colorization[][] zations = odesc._zations;
if (zations == null && _zations == null) {
// if neither has colorizations, we're clear
return true;
} else if (zations == null || _zations == null) {
// if one has colorizations whilst the other doesn't, they
// can't be equal
return false;
}
// otherwise, all of the colorizations must be equal as well
int zlength = zations.length;
if (zlength != _zations.length) {
return false;
}
for (int ii = 0; ii < zlength; ii++) {
if (!Arrays.equals(_zations[ii], zations[ii])) {
return false;
}
}
return true;
}
/**
* Returns a string representation of this character descriptor.
*/
public String toString ()
{
return "[cids=" + StringUtil.toString(_components) +
", colors=" + StringUtil.toString(_zations) + "]";
}
/** The component identifiers comprising the character. */
protected int[] _components;
/** The colorizations to apply when compositing this character. */
protected Colorization[][] _zations;
}
@@ -1,430 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.samskivert.swing.RuntimeAdjust;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Throttle;
import com.samskivert.util.Tuple;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageManager;
import com.threerings.util.DirectionCodes;
import com.threerings.cast.CompositedActionFrames.ComponentFrames;
import com.threerings.cast.Log;
/**
* The character manager provides facilities for constructing sprites that
* are used to represent characters in a scene. It also handles the
* compositing and caching of composited character animations.
*/
public class CharacterManager
implements DirectionCodes
{
/**
* Constructs the character manager.
*/
public CharacterManager (ImageManager imgr, ComponentRepository crepo)
{
// keep these around
_imgr = imgr;
_crepo = crepo;
// populate our actions table
Iterator iter = crepo.enumerateActionSequences();
while (iter.hasNext()) {
ActionSequence action = (ActionSequence)iter.next();
_actions.put(action.name, action);
}
// create a cache for our composited action frames
int acsize = _cacheSize.getValue();
Log.debug("Creating action cache [size=" + acsize + "k].");
_frameCache = new LRUHashMap(acsize*1024, new LRUHashMap.ItemSizer() {
public int computeSize (Object value) {
return (int)((CompositedMultiFrameImage)
value).getEstimatedMemoryUsage();
}
});
_frameCache.setTracking(true); // TODO
}
/**
* Returns the component repository being used by this manager.
*/
public ComponentRepository getComponentRepository ()
{
return _crepo;
}
/**
* Instructs the character manager to construct instances of this
* derived class of {@link CharacterSprite} when creating new sprites.
*
* @exception IllegalArgumentException thrown if the supplied class
* does not derive from {@link CharacterSprite}.
*/
public void setCharacterClass (Class charClass)
{
// sanity check
if (!CharacterSprite.class.isAssignableFrom(charClass)) {
String errmsg = "Requested to use character sprite class that " +
"does not derive from CharacterSprite " +
"[class=" + charClass.getName() + "].";
throw new IllegalArgumentException(errmsg);
}
// make a note of it
_charClass = charClass;
}
/**
* Instructs the character manager to use the provided cache for
* composited action animations.
*/
public void setActionCache (ActionCache cache)
{
_acache = cache;
}
/**
* Returns a {@link CharacterSprite} representing the character
* described by the given {@link CharacterDescriptor}, or
* <code>null</code> if an error occurs.
*
* @param desc the character descriptor.
*/
public CharacterSprite getCharacter (CharacterDescriptor desc)
{
return getCharacter(desc, _charClass);
}
/**
* Returns a {@link CharacterSprite} representing the character
* described by the given {@link CharacterDescriptor}, or
* <code>null</code> if an error occurs.
*
* @param desc the character descriptor.
* @param charClass the {@link CharacterSprite} derived class that
* should be instantiated instead of the configured default (which is
* set via {@link #setCharacterClass}).
*/
public CharacterSprite getCharacter (CharacterDescriptor desc,
Class charClass)
{
try {
CharacterSprite sprite = (CharacterSprite)
charClass.newInstance();
sprite.init(desc, this);
return sprite;
} catch (Exception e) {
Log.warning("Failed to instantiate character sprite " +
"[e=" + e + "].");
Log.logStackTrace(e);
return null;
}
}
/**
* Obtains the composited animation frames for the specified action for a
* character with the specified descriptor. The resulting composited
* animation will be cached.
*
* @exception NoSuchComponentException thrown if any of the components in
* the supplied descriptor do not exist.
* @exception IllegalArgumentException thrown if any of the components
* referenced in the descriptor do not support the specified action.
*/
public ActionFrames getActionFrames (
CharacterDescriptor descrip, String action)
throws NoSuchComponentException
{
Tuple key = new Tuple(descrip, action);
ActionFrames frames = (ActionFrames)_actionFrames.get(key);
if (frames == null) {
// this doesn't actually composite the images, but prepares an
// object to be able to do so
frames = createCompositeFrames(descrip, action);
_actionFrames.put(key, frames);
}
// periodically report our frame image cache performance
if (!_cacheStatThrottle.throttleOp()) {
long size = getEstimatedCacheMemoryUsage();
int[] eff = _frameCache.getTrackedEffectiveness();
Log.debug("CharacterManager LRU [mem=" + (size / 1024) + "k" +
", size=" + _frameCache.size() + ", hits=" + eff[0] +
", misses=" + eff[1] + "].");
}
return frames;
}
/**
* Informs the character manager that the action sequence for the
* given character descriptor is likely to be needed in the near
* future and so any efforts that can be made to load it into the
* action sequence cache in advance should be undertaken.
*
* <p> This will eventually be revamped to spiffily load action
* sequences in the background.
*/
public void resolveActionSequence (CharacterDescriptor desc, String action)
{
try {
if (getActionFrames(desc, action) == null) {
Log.warning("Failed to resolve action sequence " +
"[desc=" + desc + ", action=" + action + "].");
}
} catch (NoSuchComponentException nsce) {
Log.warning("Failed to resolve action sequence " +
"[nsce=" + nsce + "].");
}
}
/**
* Returns the action sequence instance with the specified name or
* null if no such sequence exists.
*/
public ActionSequence getActionSequence (String action)
{
return (ActionSequence)_actions.get(action);
}
/**
* Returns the estimated memory usage in bytes for all images
* currently cached by the cached action frames.
*/
protected long getEstimatedCacheMemoryUsage ()
{
long size = 0;
Iterator iter = _frameCache.values().iterator();
while (iter.hasNext()) {
size += ((CompositedMultiFrameImage)
iter.next()).getEstimatedMemoryUsage();
}
return size;
}
/**
* Generates the composited animation frames for the specified action
* for a character with the specified descriptor.
*
* @exception NoSuchComponentException thrown if any of the components
* in the supplied descriptor do not exist.
* @exception IllegalArgumentException thrown if any of the components
* referenced in the descriptor do not support the specified action.
*/
protected ActionFrames createCompositeFrames (
CharacterDescriptor descrip, String action)
throws NoSuchComponentException
{
int[] cids = descrip.getComponentIds();
int ccount = cids.length;
Colorization[][] zations = descrip.getColorizations();
Log.debug("Compositing action [action=" + action +
", descrip=" + descrip + "].");
// this will be used to construct any shadow layers
HashMap shadows = null;
// maps components by class name for masks
HashMap ccomps = new HashMap();
// create colorized versions of all of the source action frames
ArrayList sources = new ArrayList(ccount);
for (int ii = 0; ii < ccount; ii++) {
ComponentFrames cframes = new ComponentFrames();
sources.add(cframes);
CharacterComponent ccomp =
(cframes.ccomp = _crepo.getComponent(cids[ii]));
ccomps.put(ccomp.componentClass.name, ccomp);
// load up the main component images
ActionFrames source = ccomp.getFrames(action, null);
if (source == null) {
String errmsg = "Cannot composite action frames; no such " +
"action for component [action=" + action +
", desc=" + descrip + ", comp=" + ccomp + "]";
throw new RuntimeException(errmsg);
}
cframes.frames = (zations == null || zations[ii] == null) ?
source : source.cloneColorized(zations[ii]);
// if this component has a shadow, make a note of it
if (ccomp.componentClass.isShadowed()) {
if (shadows == null) {
shadows = new HashMap();
}
ArrayList shadlist = (ArrayList)
shadows.get(ccomp.componentClass.shadow);
if (shadlist == null) {
shadows.put(ccomp.componentClass.shadow,
shadlist = new ArrayList());
}
shadlist.add(ccomp);
}
}
// add any necessary masks
for (int ii = 0; ii < ccount; ii++) {
ComponentFrames cframes = (ComponentFrames)sources.get(ii);
CharacterComponent mcomp = (CharacterComponent)ccomps.get(
cframes.ccomp.componentClass.mask);
if (mcomp != null) {
cframes.frames = compositeMask(action, cframes.ccomp,
cframes.frames, mcomp);
}
}
// now create any necessary shadow layers
if (shadows != null) {
Iterator iter = shadows.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
String sclass = (String)entry.getKey();
ArrayList scomps = (ArrayList)entry.getValue();
ComponentFrames scf = compositeShadow(action, sclass, scomps);
if (scf != null) {
sources.add(scf);
}
}
}
// use those to create an entity that will lazily composite things
// together as they are needed
ComponentFrames[] cfvec = (ComponentFrames[])sources.toArray(
new ComponentFrames[sources.size()]);
return new CompositedActionFrames(_imgr, _frameCache, action, cfvec);
}
protected ActionFrames compositeMask (
String action, CharacterComponent ccomp, ActionFrames cframes,
CharacterComponent mcomp)
{
ActionFrames mframes = mcomp.getFrames(action,
StandardActions.CROP_TYPE);
if (mframes == null) {
return cframes;
}
return new CompositedActionFrames(
_imgr, _frameCache, action, new ComponentFrames[] {
new ComponentFrames(ccomp, cframes),
new ComponentFrames(mcomp, mframes) }) {
protected CompositedMultiFrameImage createFrames (int orient) {
return new CompositedMaskedImage(
_imgr, _sources, _action, orient);
}
};
}
protected ComponentFrames compositeShadow (
String action, String sclass, ArrayList scomps)
{
final ComponentClass cclass = _crepo.getComponentClass(sclass);
if (cclass == null) {
Log.warning("Components reference non-existent shadow layer " +
"class [sclass=" + sclass +
", scomps=" + StringUtil.toString(scomps) + "].");
return null;
}
ComponentFrames cframes = new ComponentFrames();
// create a fake component for the shadow layer
cframes.ccomp = new CharacterComponent(-1, "shadow", cclass, null);
ArrayList sources = new ArrayList();
for (int ii = 0, ll = scomps.size(); ii < ll; ii++) {
ComponentFrames source = new ComponentFrames();
source.ccomp = (CharacterComponent)scomps.get(ii);
source.frames = source.ccomp.getFrames(
action, StandardActions.SHADOW_TYPE);
if (source.frames == null) {
// skip this shadow component
continue;
}
sources.add(source);
}
// if we ended up with no shadow, no problem!
if (sources.size() == 0) {
return null;
}
// create custom action frames that use a special compositing
// multi-frame image that does the necessary shadow magic
ComponentFrames[] svec = (ComponentFrames[])
sources.toArray(new ComponentFrames[sources.size()]);
cframes.frames = new CompositedActionFrames(
_imgr, _frameCache, action, svec) {
protected CompositedMultiFrameImage createFrames (int orient) {
return new CompositedShadowImage(
_imgr, _sources, _action, orient, cclass.shadowAlpha);
}
};
return cframes;
}
/** The image manager with whom we interact. */
protected ImageManager _imgr;
/** The component repository. */
protected ComponentRepository _crepo;
/** A table of our action sequences. */
protected HashMap _actions = new HashMap();
/** A table of composited action sequences (these don't reference the
* actual image data directly and thus take up little memory). */
protected HashMap _actionFrames = new HashMap();
/** A cache of composited animation frames. */
protected LRUHashMap _frameCache;
/** The character class to be created. */
protected Class _charClass = CharacterSprite.class;
/** The action animation cache, if we have one. */
protected ActionCache _acache;
/** Throttle our cache status logging to once every 30 seconds. */
protected Throttle _cacheStatThrottle = new Throttle(1, 30000L);
/** Register our image cache size with the runtime adjustments
* framework. */
protected static RuntimeAdjust.IntAdjust _cacheSize =
new RuntimeAdjust.IntAdjust(
"Size (in kb of memory used) of the character manager LRU " +
"action cache [requires restart]", "narya.cast.action_cache_size",
CastPrefs.config, 1024);
}
@@ -1,402 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.SwingUtilities;
import com.threerings.media.sprite.ImageSprite;
/**
* A character sprite is a sprite that animates itself while walking
* about in a scene.
*/
public class CharacterSprite extends ImageSprite
implements StandardActions
{
/**
* Initializes this character sprite with the specified character
* descriptor and character manager. It will obtain animation data
* from the supplied character manager.
*/
public void init (CharacterDescriptor descrip, CharacterManager charmgr)
{
// keep track of this stuff
_descrip = descrip;
_charmgr = charmgr;
// sanity check our values
sanityCheckDescrip();
// assign an arbitrary starting orientation
_orient = SOUTHWEST;
// pass the buck to derived classes
didInit();
}
/**
* Called after this sprite has been initialized with its character
* descriptor and character manager. Derived classes can do post-init
* business here.
*/
protected void didInit ()
{
}
/**
* Reconfigures this sprite to use the specified character descriptor.
*/
public void setCharacterDescriptor (CharacterDescriptor descrip)
{
// keep the new descriptor
_descrip = descrip;
// sanity check our values
sanityCheckDescrip();
// update our action frames
updateActionFrames();
}
/**
* Specifies the action to use when the sprite is at rest. The default
* is <code>STANDING</code>.
*/
public void setRestingAction (String action)
{
_restingAction = action;
}
/**
* Returns the action to be used when the sprite is at rest. Derived
* classes may wish to override this method and vary the action based
* on external parameters (or randomly).
*/
public String getRestingAction ()
{
return _restingAction;
}
/**
* Specifies the action to use when the sprite is following a path.
* The default is <code>WALKING</code>.
*/
public void setFollowingPathAction (String action)
{
_followingPathAction = action;
}
/**
* Returns the action to be used when the sprite is following a path.
* Derived classes may wish to override this method and vary the
* action based on external parameters (or randomly).
*/
public String getFollowingPathAction ()
{
return _followingPathAction;
}
/**
* Sets the action sequence used when rendering the character, from
* the set of available sequences.
*/
public void setActionSequence (String action)
{
// sanity check
if (action == null) {
Log.warning("Refusing to set null action sequence " + this + ".");
Thread.dumpStack();
return;
}
// no need to noop
if (action.equals(_action)) {
return;
}
_action = action;
updateActionFrames();
}
// documentation inherited
public void setOrientation (int orient)
{
if (orient < 0 || orient >= FINE_DIRECTION_COUNT) {
Log.info("Refusing to set invalid orientation [sprite=" + this +
", orient=" + orient + "].");
Thread.dumpStack();
return;
}
int oorient = _orient;
super.setOrientation(orient);
if (_orient != oorient) {
_frames = null;
}
}
// documentation inherited
public boolean hitTest (int x, int y)
{
// the irect adjustments are to account for our decorations
return (_frames != null && _ibounds.contains(x, y) &&
_frames.hitTest(_frameIdx, x - _ibounds.x, y - _ibounds.y));
}
// documentation inherited
public void tick (long tickStamp)
{
// composite our action frames if something since the last call to
// tick caused them to become invalid
compositeActionFrames();
super.tick(tickStamp);
// composite our action frames if something during tick() caused
// them to become invalid
compositeActionFrames();
}
// documentation inherited
public void cancelMove ()
{
super.cancelMove();
halt();
}
// documentation inherited
public void pathBeginning ()
{
super.pathBeginning();
// enable walking animation
setAnimationMode(TIME_BASED);
setActionSequence(getFollowingPathAction());
}
// documentation inherited
public void pathCompleted (long timestamp)
{
super.pathCompleted(timestamp);
halt();
}
// documentation inherited
public void paint (Graphics2D gfx)
{
if (_frames != null) {
decorateBehind(gfx);
// paint the image using _ibounds rather than _bounds which
// has been modified to include the bounds of our decorations
_frames.paintFrame(gfx, _frameIdx, _ibounds.x, _ibounds.y);
decorateInFront(gfx);
} else {
super.paint(gfx);
}
}
/**
* Called to paint any decorations that should appear behind the
* character sprite image.
*/
protected void decorateBehind (Graphics2D gfx)
{
}
/**
* Called to paint any decorations that should appear in front of the
* character sprite image.
*/
protected void decorateInFront (Graphics2D gfx)
{
}
/**
* Rebuilds our action frames given our current character descriptor
* and action sequence. This is called when either of those two things
* changes.
*/
protected void updateActionFrames ()
{
// get a reference to the action sequence so that we can obtain
// our animation frames and configure our frames per second
ActionSequence actseq = _charmgr.getActionSequence(_action);
if (actseq == null) {
String errmsg = "No such action '" + _action + "'.";
throw new IllegalArgumentException(errmsg);
}
try {
// obtain our animation frames for this action sequence
_aframes = _charmgr.getActionFrames(_descrip, _action);
// clear out our frames so that we recomposite on next tick
_frames = null;
// update the sprite render attributes
setFrameRate(actseq.framesPerSecond);
} catch (NoSuchComponentException nsce) {
Log.warning("Character sprite references non-existent " +
"component [sprite=" + this + ", err=" + nsce + "].");
} catch (Exception e) {
Log.warning("Failed to obtain action frames [sprite=" + this +
", descrip=" + _descrip + ", action=" + _action + "].");
Log.logStackTrace(e);
}
}
/** Called to recomposite our action frames if needed. */
protected final void compositeActionFrames ()
{
if (_frames == null && _aframes != null) {
setFrames(_aframes.getFrames(_orient));
}
}
/**
* Makes it easier to track down problems with bogus character descriptors.
*/
protected void sanityCheckDescrip ()
{
if (_descrip.getComponentIds() == null ||
_descrip.getComponentIds().length == 0) {
Log.warning("Invalid character descriptor [sprite=" + this +
", descrip=" + _descrip + "].");
Thread.dumpStack();
}
}
// documentation inherited
protected boolean tickPath (long tickStamp)
{
boolean moved = super.tickPath(tickStamp);
// composite our action frames if our path caused them to become
// invalid
compositeActionFrames();
return moved;
}
// documentation inherited
protected void updateRenderOrigin ()
{
super.updateRenderOrigin();
// adjust our image bounds to reflect the new location
_ibounds.x = _bounds.x + _ioff.x;
_ibounds.y = _bounds.y + _ioff.y;
}
// documentation inherited
protected void accomodateFrame (int frameIdx, int width, int height)
{
// this will update our width and height
super.accomodateFrame(frameIdx, width, height);
// we now need to update the render offset for this frame
if (_aframes == null) {
Log.warning("Have no action frames! " + _aframes + ".");
} else {
_oxoff = _aframes.getXOrigin(_orient, frameIdx);
_oyoff = _aframes.getYOrigin(_orient, frameIdx);
}
// and cause those changes to be reflected in our bounds
updateRenderOrigin();
// start out with our bounds the same as our image bounds
_ibounds.setBounds(_bounds);
// now we can call down and incorporate the dimensions of any
// decorations that will be rendered along with our image
unionDecorationBounds(_bounds);
// compute our new render origin
_oxoff = _ox - _bounds.x;
_oyoff = _oy - _bounds.y;
// track the offset from our expanded bounds to our image bounds
_ioff.x = _ibounds.x - _bounds.x;
_ioff.y = _ibounds.y - _bounds.y;
}
/**
* Called by {@link #accomodateFrame} to give derived classes an
* opportunity to incorporate the bounds of any decorations that will
* be drawn along with this sprite. The {@link #_ibounds} rectangle
* will contain the bounds of the image that comprises the undecorated
* sprite. From that the position and size of decorations can be
* computed and unioned with the supplied bounds rectangle (most
* likely by using {@link SwingUtilities#computeUnion} which applies
* the union in place rather than creating a new rectangle).
*/
protected void unionDecorationBounds (Rectangle bounds)
{
}
/**
* Updates the sprite animation frame to reflect the cessation of
* movement and disables any further animation.
*/
protected void halt ()
{
// only do something if we're actually animating
if (_animMode != NO_ANIMATION) {
// disable animation
setAnimationMode(NO_ANIMATION);
// come to a halt looking settled and at peace
String rest = getRestingAction();
if (rest != null) {
setActionSequence(rest);
}
}
}
/** The action to use when at rest. */
protected String _restingAction = STANDING;
/** The action to use when following a path. */
protected String _followingPathAction = WALKING;
/** A reference to the descriptor for the character that we're
* visualizing. */
protected CharacterDescriptor _descrip;
/** A reference to the character manager that created us. */
protected CharacterManager _charmgr;
/** The action we are currently displaying. */
protected String _action;
/** The animation frames for the active action sequence in each
* orientation. */
protected ActionFrames _aframes;
/** The offset from the upper-left of the total sprite bounds to the
* upper-left of the image within those bounds. */
protected Point _ioff = new Point();
/** The bounds of the current sprite image. */
protected Rectangle _ibounds = new Rectangle();
}
@@ -1,241 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.awt.Color;
import java.io.Serializable;
import com.samskivert.util.ArrayIntSet;
import com.samskivert.util.ComparableArrayList;
import com.samskivert.util.StringUtil;
/**
* Denotes a class of components to which {@link CharacterComponent}
* objects belong. Examples include "Hat", "Head", and "Feet". A component
* class dictates a component's rendering priority so that components can
* be rendered in an order that causes them to overlap properly.
*
* <p> Components support render priority overrides for particular
* actions, orientations or combinations of actions and orientations. The
* system is currently structured with the expectation that the overrides
* will be relatively few (less than fifteen, say) for any given component
* class. A system that relied on many overrides for its components would
* want to implement a more scalable algorithm for determining which, if
* any, override matches a particular action and orientation combination.
*/
public class ComponentClass implements Serializable
{
/** Used to effect custom render orders for particular actions,
* orientations, etc. */
public static class PriorityOverride
implements Comparable, Serializable
{
/** The overridden render priority value. */
public int renderPriority;
/** The action, if any, for which this override is appropriate. */
public String action;
/** The orientations, if any, for which this override is
* appropriate. */
public ArrayIntSet orients;
/**
* Determines whether this priority override matches the specified
* action and orientation combination.
*/
public boolean matches (String action, int orient)
{
return (((orients == null) || orients.contains(orient)) &&
((this.action == null) || this.action.equals(action)));
}
// documentation inherited from interface
public int compareTo (Object other)
{
// overrides with both an action and an orientation should
// come first in the list
PriorityOverride po = (PriorityOverride)other;
int pri = priority(), opri = po.priority();
if (pri == opri) {
return hashCode() - po.hashCode();
} else {
return pri - opri;
}
}
protected int priority ()
{
int priority = 0;
if (action != null) {
priority++;
}
if (orients != null) {
priority++;
}
return priority;
}
/** Generates a string representation of this instance. */
public String toString ()
{
return "[pri=" + renderPriority + ", action=" + action +
", orients=" + orients + "]";
}
/** Increase this value when object's serialized state is impacted
* by a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 1;
}
/** The component class name. */
public String name;
/** The default render priority. */
public int renderPriority;
/** The color classes to use when recoloring components of this class. May
* be null if a system does not use recolorable components. */
public String[] colors;
/** The class name of the layer from which this component class obtains a
* mask to limit rendering to certain areas. */
public String mask;
/** Indicates the class name of the shadow layer to which this component
* class contributes a shadow. */
public String shadow;
/** 1.0 for a normal component, the alpha value of the pre-composited
* shadow for the special "shadow" component class. */
public float shadowAlpha = 1.0f;
/**
* Creates an uninitialized instance suitable for unserialization or
* population during XML parsing.
*/
public ComponentClass ()
{
}
/**
* Returns the render priority appropriate for the specified action
* and orientation.
*/
public int getRenderPriority (String action, int orientation)
{
// because we expect there to be relatively few priority
// overrides, we simply search linearly through the list for the
// closest match
int ocount = (_overrides != null) ? _overrides.size() : 0;
for (int ii = 0; ii < ocount; ii++) {
PriorityOverride over = (PriorityOverride)_overrides.get(ii);
// based on the way the overrides are sorted, the first match
// is the most specific and the one we want
if (over.matches(action, orientation)) {
return over.renderPriority;
}
}
return renderPriority;
}
/**
* Adds the supplied render priority override record to this component
* class.
*/
public void addPriorityOverride (PriorityOverride override)
{
if (_overrides == null) {
_overrides = new ComparableArrayList();
}
_overrides.insertSorted(override);
}
/**
* Returns true if this component class contributes a shadow to a
* particular shadow layer. Note: this is different from <em>being</em> a
* shadow layer which is determined by calling {@link #isShadow}.
*/
public boolean isShadowed ()
{
return (shadow != null);
}
/**
* Returns true if this component class is a shadow layer rather than a
* normal component class.
*/
public boolean isShadow ()
{
return (shadowAlpha != 1.0f);
}
/**
* Classes with the same name are the same.
*/
public boolean equals (Object other)
{
if (other instanceof ComponentClass) {
return name.equals(((ComponentClass)other).name);
} else {
return false;
}
}
/**
* Hashcode is based on component class name.
*/
public int hashCode ()
{
return name.hashCode();
}
/**
* Returns a string representation of this component class.
*/
public String toString ()
{
StringBuilder buf = new StringBuilder("[");
buf.append("name=").append(name);
buf.append(", pri=").append(renderPriority);
if (colors != null) {
buf.append(", colors=").append(StringUtil.toString(colors));
}
if (mask != null) {
buf.append(", mask=").append(mask);
}
if (shadowAlpha != 1.0f) {
buf.append(", shadow=").append(shadowAlpha);
} else if (shadow != null) {
buf.append(", shadow=").append(shadow);
}
return buf.append("]").toString();
}
/** A list of render priority overrides. */
protected ComparableArrayList _overrides;
/** Increase this value when object's serialized state is impacted by
* a class change (modification of fields, inheritance). */
private static final long serialVersionUID = 3;
}
@@ -1,63 +0,0 @@
//
// $Id$
//
// 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.cast;
import com.samskivert.io.PersistenceException;
/**
* Brokers component ids. The component repository interface makes
* available a collection of components based on a unique identifier. The
* expectation is that a collection of components will be used to populate
* a repository and in that population process, component ids will be
* assigned to the components. The component id broker system provides a
* means by which named components can be mapped consistently to a set of
* component ids. Humans can then be responsible for assigning unique
* names to the components and the broker will ensure that those names map
* to unique ids that won't change if the repository is rebuilt from the
* source components.
*/
public interface ComponentIDBroker
{
/**
* Returns the unique identifier for the named component. If no
* identifier has yet been assigned to the specified named component,
* one should be assigned and returned.
*
* @param cclass the name of the class to which the component belongs.
* @param cname the name of the component.
*
* @exception PersistenceException thrown if an error occurs
* communicating with the underlying persistence mechanism used to
* store the name to id mappings.
*/
public int getComponentID (String cclass, String cname)
throws PersistenceException;
/**
* When the user of a component id broker is done obtaining component
* ids, it must call this method to give the component id broker an
* opportunity to flush any newly created component ids back to its
* persistent store.
*/
public void commit ()
throws PersistenceException;
}
@@ -1,71 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.util.Iterator;
/**
* Makes available a collection of character components and associated
* metadata. Character components are animated sequences that can be
* composited together to create a complete character visualization
* (imagine interchanging pairs of boots, torsos, hats, etc.).
*/
public interface ComponentRepository
{
/**
* Returns the {@link CharacterComponent} object for the given
* component identifier.
*/
public CharacterComponent getComponent (int componentId)
throws NoSuchComponentException;
/**
* Returns the {@link CharacterComponent} object with the given
* component class and name.
*/
public CharacterComponent getComponent (String className, String compName)
throws NoSuchComponentException;
/**
* Returns the {@link ComponentClass} with the specified name or null
* if none exists with that name.
*/
public ComponentClass getComponentClass (String className);
/**
* Iterates over the {@link ComponentClass} instances representing all
* available character component classes.
*/
public Iterator enumerateComponentClasses ();
/**
* Iterates over the {@link ActionSequence} instances representing
* every available action sequence.
*/
public Iterator enumerateActionSequences ();
/**
* Iterates over the component ids of all components in the specified
* class.
*/
public Iterator enumerateComponentIds (ComponentClass compClass);
}
@@ -1,186 +0,0 @@
//
// $Id$
//
// 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.cast;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.StringUtil;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageManager;
import com.threerings.media.util.MultiFrameImage;
import com.threerings.cast.CharacterComponent;
import com.threerings.util.DirectionCodes;
/**
* An implementation of the {@link MultiFrameImage} interface that is used
* to lazily create composited character frames when they are requested.
*/
public class CompositedActionFrames
implements ActionFrames, DirectionCodes
{
/** Used to associate a {@link CharacterComponent} with its {@link
* ActionFrames} for a particular action. */
public static class ComponentFrames
{
public CharacterComponent ccomp;
public ActionFrames frames;
public ComponentFrames () {
}
public ComponentFrames (
CharacterComponent ccomp, ActionFrames frames) {
this.ccomp = ccomp;
this.frames = frames;
}
public String toString () {
return ccomp + ":" + frames;
}
}
/**
* Constructs a set of composited action frames with the supplied
* source frames and colorization configuration. The actual component
* frame images will not be composited until they are requested.
*/
public CompositedActionFrames (ImageManager imgr, LRUHashMap frameCache,
String action, ComponentFrames[] sources)
{
// sanity check
if (sources == null || sources.length == 0) {
String errmsg = "Requested to composite invalid set of source " +
"frames! [action=" + action +
", sources=" + StringUtil.toString(sources) + "].";
throw new RuntimeException(errmsg);
}
_imgr = imgr;
_frameCache = frameCache;
_sources = sources;
_action = action;
// the sources must all have the same orientation count, so we
// just use the first
_orientCount = _sources[0].frames.getOrientationCount();
}
// documentation inherited from interface
public int getOrientationCount ()
{
return _orientCount;
}
// documentation inherited from interface
public TrimmedMultiFrameImage getFrames (int orient)
{
_key.setOrient(orient);
CompositedMultiFrameImage cmfi =
(CompositedMultiFrameImage)_frameCache.get(_key);
if (cmfi == null) {
cmfi = createFrames(orient);
_frameCache.put(new CompositedFramesKey(orient), cmfi);
}
return cmfi;
}
// documentation inherited from interface
public int getXOrigin (int orient, int frameIdx)
{
CompositedMultiFrameImage cmfi = (CompositedMultiFrameImage)
getFrames(orient);
return cmfi.getXOrigin(frameIdx);
}
// documentation inherited from interface
public int getYOrigin (int orient, int frameIdx)
{
CompositedMultiFrameImage cmfi = (CompositedMultiFrameImage)
getFrames(orient);
return cmfi.getYOrigin(frameIdx);
}
// documentation inherited from interface
public ActionFrames cloneColorized (Colorization[] zations)
{
throw new RuntimeException("What you talkin' about Willis?");
}
/**
* Creates our underlying multi-frame image for a particular orientation.
*/
protected CompositedMultiFrameImage createFrames (int orient)
{
return new CompositedMultiFrameImage(_imgr, _sources, _action, orient);
}
/** Used to cache composited frames for a particular action and
* orientation. */
protected class CompositedFramesKey
{
public CompositedFramesKey (int orient) {
_orient = orient;
}
public void setOrient (int orient) {
_orient = orient;
}
public CompositedActionFrames getOwner () {
return CompositedActionFrames.this;
}
public boolean equals (Object other) {
CompositedFramesKey okey = (CompositedFramesKey)other;
return ((getOwner() == okey.getOwner()) &&
(_orient == okey._orient));
}
public int hashCode () {
return CompositedActionFrames.this.hashCode() ^ _orient;
}
protected int _orient;
}
/** The image manager from whom we can obtain prepared volatile images
* onto which to render our composited actions. */
protected ImageManager _imgr;
/** Used to cache our composited action frame images. */
protected LRUHashMap _frameCache;
/** The action for which we're compositing frames. */
protected String _action;
/** The number of orientations. */
protected int _orientCount;
/** Our source components and action frames. */
protected ComponentFrames[] _sources;
/** Used to avoid creating a new key object every time we do a cache
* lookup. */
protected CompositedFramesKey _key = new CompositedFramesKey(0);
}
@@ -1,131 +0,0 @@
//
// $Id: CompositedMultiFrameImage.java 3310 2005-01-24 23:08:21Z 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.cast;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Transparency;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
import com.threerings.media.image.VolatileMirage;
import com.threerings.cast.CompositedActionFrames.ComponentFrames;
/**
* Used to composite action frames with mask frames.
*/
public class CompositedMaskedImage extends CompositedMultiFrameImage
{
public CompositedMaskedImage (
ImageManager imgr, ComponentFrames[] sources, String action,
int orient)
{
super(imgr, sources, action, orient);
}
// documentation inherited from interface
public int getWidth (int index) {
return _sources[0].frames.getFrames(_orient).getWidth(index);
}
// documentation inherited from interface
public int getHeight (int index) {
return _sources[0].frames.getFrames(_orient).getHeight(index);
}
public int getXOrigin (int index) {
return _sources[0].frames.getXOrigin(_orient, index);
}
public int getYOrigin (int index) {
return _sources[0].frames.getYOrigin(_orient, index);
}
// documentation inherited from interface
public void paintFrame (Graphics2D g, int index, int x, int y) {
_images[index].paint(g, x + _images[index].getX(),
y + _images[index].getY());
}
// documentation inherited from interface
public boolean hitTest (int index, int x, int y) {
return _images[index].hitTest(x + _images[index].getX(),
y + _images[index].getY());
}
// documentation inherited from interface TrimmedMultiFrameImage
public void getTrimmedBounds (int index, Rectangle bounds) {
bounds.setBounds(_images[index].getX(), _images[index].getY(),
_images[index].getWidth(), _images[index].getHeight());
}
// documentation inherited
protected CompositedMirage createCompositedMirage (int index)
{
return new MaskedMirage(index);
}
/**
* Combines the image in the first source with the mask in the second. */
protected class MaskedMirage extends CompositedMirage
{
public MaskedMirage (int index)
{
super(index);
}
// documentation inherited
protected Rectangle combineBounds (Rectangle bounds, Rectangle tbounds)
{
if (bounds.width == 0 && bounds.height == 0) {
bounds.setBounds(tbounds);
} else {
bounds = bounds.intersection(tbounds);
}
return bounds;
}
// documentation inherited
protected void refreshVolatileImage ()
{
Graphics2D g = (Graphics2D)_image.getGraphics();
try {
TrimmedMultiFrameImage source =
_sources[0].frames.getFrames(_orient),
mask = _sources[1].frames.getFrames(_orient);
source.paintFrame(g, _index, -_bounds.x, -_bounds.y);
g.setComposite(AlphaComposite.DstIn);
mask.paintFrame(g, _index, -_bounds.x, -_bounds.y);
} finally {
// clean up after ourselves
if (g != null) {
g.dispose();
}
}
}
}
};
@@ -1,254 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.util.Arrays;
import java.util.Comparator;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
import com.threerings.media.image.VolatileMirage;
import com.threerings.cast.CompositedActionFrames.ComponentFrames;
import com.threerings.cast.TrimmedMultiFrameImage;
/**
* Used to composite the action frames for a particular orientation of a
* {@link CompositedActionFrames}.
*/
public class CompositedMultiFrameImage
implements TrimmedMultiFrameImage
{
public CompositedMultiFrameImage (
ImageManager imgr, ComponentFrames[] sources,
String action, int orient)
{
_imgr = imgr;
_sources = sources;
_action = action;
_orient = orient;
// create our frame images (which will do the compositing)
int fcount = sources[0].frames.getFrames(orient).getFrameCount();
_images = new CompositedMirage[fcount];
for (int ii = 0; ii < fcount; ii++) {
_images[ii] = createCompositedMirage(ii);
}
}
// documentation inherited
public int getFrameCount () {
return _images.length;
}
// documentation inherited from interface
public int getWidth (int index) {
return _images[index].getWidth();
}
// documentation inherited from interface
public int getHeight (int index) {
return _images[index].getHeight();
}
public int getXOrigin (int index) {
return _images[index].getXOrigin();
}
public int getYOrigin (int index) {
return _images[index].getYOrigin();
}
// documentation inherited from interface
public void paintFrame (Graphics2D g, int index, int x, int y) {
_images[index].paint(g, x, y);
}
// documentation inherited from interface
public boolean hitTest (int index, int x, int y) {
return _images[index].hitTest(x, y);
}
// documentation inherited from interface
public void getTrimmedBounds (int index, Rectangle bounds) {
bounds.setBounds(0, 0, getWidth(index), getHeight(index));
}
/**
* Returns the estimated memory usage of our composited frame images.
*/
public long getEstimatedMemoryUsage ()
{
long size = 0;
for (int ii = 0; ii < _images.length; ii++) {
size += _images[ii].getEstimatedMemoryUsage();
}
return size;
}
/**
* Creates a composited image for the specified frame.
*/
protected CompositedMirage createCompositedMirage (int index)
{
return new CompositedMirage(index);
}
// documentation inherited
protected Mirage getFrame (int orient, int index)
{
return _images[index];
}
/** The image manager from whom we load our images. */
protected ImageManager _imgr;
/** The action frames from which we obtain our source imagery. */
protected ComponentFrames[] _sources;
/** The action we're compositing. */
protected String _action;
/** The orientation we're compositing. */
protected int _orient;
/** Our composited action frame images. */
protected CompositedMirage[] _images;
/**
* Used to create our mirage using the source action frame images.
*/
protected class CompositedMirage extends VolatileMirage
implements Comparator
{
public CompositedMirage (int index)
{
super(CompositedMultiFrameImage.this._imgr,
new Rectangle(0, 0, 0, 0));
// keep this for later
_index = index;
// first we need to determine the bounds of the rectangle that
// will enclose all of our various components
Rectangle tbounds = new Rectangle();
int scount = _sources.length;
for (int ii = 0; ii < scount; ii++) {
TrimmedMultiFrameImage source =
_sources[ii].frames.getFrames(_orient);
source.getTrimmedBounds(index, tbounds);
_bounds = combineBounds(_bounds, tbounds);
}
// compute our new origin
_origin.x = (_sources[0].frames.getXOrigin(_orient, index) -
_bounds.x);
_origin.y = (_sources[0].frames.getYOrigin(_orient, index) -
_bounds.y);
// Log.info("New origin [x=" + _origin.x + ", y=" + _origin.y + "].");
// render our volatile image for the first time
createVolatileImage();
}
public int getXOrigin ()
{
return _origin.x;
}
public int getYOrigin ()
{
return _origin.y;
}
// documentation inherited from interface
public int compare (Object o1, Object o2)
{
ComponentFrames cf1 = (ComponentFrames)o1,
cf2 = (ComponentFrames)o2;
return (cf1.ccomp.componentClass.getRenderPriority(
_action, _orient) -
cf2.ccomp.componentClass.getRenderPriority(
_action, _orient));
}
/**
* Combines the working bounds with a new set of bounds.
*/
protected Rectangle combineBounds (Rectangle bounds, Rectangle tbounds)
{
// the first one defines our initial bounds
if (bounds.width == 0 && bounds.height == 0) {
bounds.setBounds(tbounds);
} else {
bounds.add(tbounds);
}
return bounds;
}
// documentation inherited
protected int getTransparency ()
{
return Transparency.BITMASK;
}
// documentation inherited
protected void refreshVolatileImage ()
{
// long start = System.currentTimeMillis();
// sort the sources appropriately for this orientation
Arrays.sort(_sources, this);
// now render each of the components into a composited frame
int scount = _sources.length;
Graphics2D g = (Graphics2D)_image.getGraphics();
try {
for (int ii = 0; ii < scount; ii++) {
TrimmedMultiFrameImage source =
_sources[ii].frames.getFrames(_orient);
source.paintFrame(g, _index, -_bounds.x, -_bounds.y);
}
} finally {
// clean up after ourselves
if (g != null) {
g.dispose();
}
}
// Log.info("Composited [orient=" + _orient + ", index=" + _index +
// ", tbounds=" + StringUtil.toString(_bounds) + "].");
// long now = System.currentTimeMillis();
// Log.info("Composited " + scount + " frames in " +
// (now-start) + " millis.");
}
protected int _index;
protected Point _origin = new Point();
}
};
@@ -1,92 +0,0 @@
//
// $Id: CompositedMultiFrameImage.java 3310 2005-01-24 23:08:21Z 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.cast;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.threerings.media.image.ImageManager;
import com.threerings.media.image.Mirage;
import com.threerings.media.image.VolatileMirage;
import com.threerings.cast.CompositedActionFrames.ComponentFrames;
/**
* Used to composite the special shadow action frames for a particular
* orientation of a {@link CompositedActionFrames}.
*/
public class CompositedShadowImage extends CompositedMultiFrameImage
{
public CompositedShadowImage (ImageManager imgr, ComponentFrames[] sources,
String action, int orient, float shadowAlpha)
{
super(imgr, sources, action, orient);
// create the appropriate alpha composite for rendering the shadow
_shadowAlpha = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, shadowAlpha);
}
// documentation inherited from interface
public int getWidth (int index) {
return _sources[0].frames.getFrames(_orient).getWidth(index);
}
// documentation inherited from interface
public int getHeight (int index) {
return _sources[0].frames.getFrames(_orient).getHeight(index);
}
public int getXOrigin (int index) {
return _sources[0].frames.getXOrigin(_orient, index);
}
public int getYOrigin (int index) {
return _sources[0].frames.getYOrigin(_orient, index);
}
// documentation inherited from interface
public void paintFrame (Graphics2D g, int index, int x, int y) {
Composite ocomp = g.getComposite();
g.setComposite(_shadowAlpha);
_images[index].paint(g, x + _images[index].getX(),
y + _images[index].getY());
g.setComposite(ocomp);
}
// documentation inherited from interface
public boolean hitTest (int index, int x, int y) {
return _images[index].hitTest(x + _images[index].getX(),
y + _images[index].getY());
}
// documentation inherited from interface TrimmedMultiFrameImage
public void getTrimmedBounds (int index, Rectangle bounds) {
bounds.setBounds(_images[index].getX(), _images[index].getY(),
_images[index].getWidth(), _images[index].getHeight());
}
/** The alpha value at which we render our shadow. */
protected AlphaComposite _shadowAlpha;
};
@@ -1,37 +0,0 @@
//
// $Id$
//
// 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.cast;
/**
* Provides a mechanism where by a character component can obtain access
* to its image frames for a particular action in an on demand manner.
*/
public interface FrameProvider
{
/**
* Returns the animation frames (in the eight sprite directions) for
* the specified action of the specified component. May return null if
* the specified action does not exist for the specified component.
*/
public ActionFrames getFrames (
CharacterComponent component, String action, String type);
}
-56
View File
@@ -1,56 +0,0 @@
//
// $Id$
//
// 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.cast;
/**
* A placeholder class that contains a reference to the log object used by
* the cast package.
*/
public class Log
{
public static com.samskivert.util.Log log =
new com.samskivert.util.Log("cast");
/** 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);
}
}
@@ -1,50 +0,0 @@
//
// $Id$
//
// 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.cast;
/**
* Thrown when an attempt is made to retrieve a non-existent character
* component from the component repository.
*/
public class NoSuchComponentException extends Exception
{
public NoSuchComponentException (int componentId)
{
super("No such component [componentId=" + componentId + "]");
_componentId = componentId;
}
public NoSuchComponentException (
String componentClass, String componentName)
{
super("No such component [class=" + componentClass +
", name=" + componentName + "]");
_componentId = -1;
}
public int getComponentId ()
{
return _componentId;
}
protected int _componentId;
}
@@ -1,42 +0,0 @@
//
// $Id$
//
// 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.cast;
/**
* Actions are referenced by name and this interface defines constants for
* standard actions and action suffixes used by the shadow and cropping
* support.
*/
public interface StandardActions
{
/** The name of the standard standing action. */
public static final String STANDING = "standing";
/** The name of the standard walking action. */
public static final String WALKING = "walking";
/** A special action sub-type for shadow imagery. */
public static final String SHADOW_TYPE = "shadow";
/** A special action sub-type for crop imagery. */
public static final String CROP_TYPE = "crop";
}
@@ -1,41 +0,0 @@
//
// $Id$
//
// 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.cast;
import java.awt.Rectangle;
import com.threerings.media.util.MultiFrameImage;
/**
* Used to generate more memory efficient composited images in
* circumstances where we have trimmed underlying component images.
*/
public interface TrimmedMultiFrameImage extends MultiFrameImage
{
/**
* Fills in the minimum bounding rectangle for this image that
* contains all non-transparent pixels. If this information is
* unavailable, the bounds of the entire image may be returned in
* exchange for improved performance.
*/
public void getTrimmedBounds (int index, Rectangle bounds);
}
@@ -1,151 +0,0 @@
//
// $Id$
//
// 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.cast.builder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import com.samskivert.util.CollectionUtil;
import com.threerings.cast.ComponentRepository;
import com.threerings.cast.ComponentClass;
/**
* The builder model represents the current state of the character the
* user is building.
*/
public class BuilderModel
{
/**
* Constructs a builder model.
*/
public BuilderModel (ComponentRepository crepo)
{
gatherComponentInfo(crepo);
}
/**
* Adds a builder model listener.
*
* @param l the listener.
*/
public void addListener (BuilderModelListener l)
{
if (!_listeners.contains(l)) {
_listeners.add(l);
}
}
/**
* Notifies all model listeners that the builder model has changed.
*/
protected void notifyListeners (int event)
{
int size = _listeners.size();
for (int ii = 0; ii < size; ii++) {
((BuilderModelListener)_listeners.get(ii)).modelChanged(event);
}
}
/**
* Returns a list of the available component classes.
*/
public List getComponentClasses ()
{
return Collections.unmodifiableList(_classes);
}
/**
* Returns the list of components available in the specified class.
*/
public List getComponents (ComponentClass cclass)
{
List list = (List)_components.get(cclass);
if (list == null) {
list = new ArrayList();
}
return list;
}
/**
* Returns the selected components in an array.
*/
public int[] getSelectedComponents ()
{
int[] values = new int[_selected.size()];
Iterator iter = _selected.values().iterator();
for (int i = 0; iter.hasNext(); i++) {
values[i] = ((Integer)iter.next()).intValue();
}
return values;
}
/**
* Sets the selected component for the given component class.
*/
public void setSelectedComponent (ComponentClass cclass, int cid)
{
_selected.put(cclass, Integer.valueOf(cid));
notifyListeners(BuilderModelListener.COMPONENT_CHANGED);
}
/**
* Gathers component class and component information from the
* character manager for later reference by others.
*/
protected void gatherComponentInfo (ComponentRepository crepo)
{
// get the list of all component classes
CollectionUtil.addAll(_classes, crepo.enumerateComponentClasses());
for (int ii = 0; ii < _classes.size(); ii++) {
// get the list of components available for this class
ComponentClass cclass = (ComponentClass)_classes.get(ii);
Iterator iter = crepo.enumerateComponentIds(cclass);
while (iter.hasNext()) {
Integer cid = (Integer)iter.next();
ArrayList clist = (ArrayList)_components.get(cclass);
if (clist == null) {
_components.put(cclass, clist = new ArrayList());
}
clist.add(cid);
}
}
}
/** The currently selected character components. */
protected HashMap _selected = new HashMap();
/** The hashtable of available component ids for each class. */
protected HashMap _components = new HashMap();
/** The list of all available component classes. */
protected ArrayList _classes = new ArrayList();
/** The model listeners. */
protected ArrayList _listeners = new ArrayList();
}
@@ -1,40 +0,0 @@
//
// $Id$
//
// 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.cast.builder;
/**
* The builder model listener interface should be implemented by
* classes that would like to be notified when the builder model is
* changed.
*
* @see BuilderModel
*/
public interface BuilderModelListener
{
/**
* Called by the {@link BuilderModel} when the model is changed.
*/
public void modelChanged (int event);
/** Notification event constants. */
public static final int COMPONENT_CHANGED = 0;
}
@@ -1,60 +0,0 @@
//
// $Id$
//
// 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.cast.builder;
import javax.swing.*;
import com.samskivert.swing.*;
import com.threerings.cast.CharacterManager;
import com.threerings.cast.ComponentRepository;
/**
* The builder panel presents the user with an overview of a composited
* character and facilities for altering the individual components that
* comprise the character's display image.
*/
public class BuilderPanel extends JPanel
{
/**
* Constructs the builder panel.
*/
public BuilderPanel (CharacterManager charmgr,
ComponentRepository crepo, String cprefix)
{
setLayout(new VGroupLayout());
// give ourselves a wee bit of a border
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
GroupLayout gl = new HGroupLayout(GroupLayout.STRETCH);
gl.setOffAxisPolicy(GroupLayout.STRETCH);
// create the builder model
BuilderModel model = new BuilderModel(crepo);
// create the component selection and sprite display panels
JPanel sub = new JPanel(gl);
sub.add(new ComponentPanel(model, cprefix));
sub.add(new SpritePanel(charmgr, model));
add(sub);
}
}
@@ -1,109 +0,0 @@
//
// $Id$
//
// 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.cast.builder;
import java.util.List;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.samskivert.swing.*;
import com.threerings.cast.ComponentClass;
/**
* The class editor displays a label and a slider that allow the user to
* select the desired component for a given component class.
*/
public class ClassEditor extends JPanel implements ChangeListener
{
/**
* Constructs a class editor.
*/
public ClassEditor (
BuilderModel model, ComponentClass cclass, List components)
{
_model = model;
_components = components;
_cclass = cclass;
GroupLayout gl = new VGroupLayout(GroupLayout.STRETCH);
gl.setOffAxisPolicy(GroupLayout.STRETCH);
setLayout(gl);
gl = new HGroupLayout();
gl.setJustification(GroupLayout.LEFT);
JPanel sub = new JPanel(gl);
sub.add(new JLabel(cclass.name + ": "));
sub.add(_clabel = new JLabel("0"));
add(sub);
// create the slider allowing selection of available components
int max = components.size() - 1;
JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, max, 0);
slider.setSnapToTicks(true);
slider.addChangeListener(this);
add(slider);
// set the starting component for this class
setSelectedComponent(0);
}
// documentation inherited
public void stateChanged (ChangeEvent e)
{
JSlider source = (JSlider)e.getSource();
if (!source.getValueIsAdjusting()) {
int val = source.getValue();
// update the model with the newly selected component
setSelectedComponent(val);
// update the label with the new value
_clabel.setText(Integer.toString(val));
}
}
/**
* Sets the selected component in the builder model for the
* component class associated with this editor to the component at
* the given index in this editor's list of available components.
*/
protected void setSelectedComponent (int idx)
{
int cid = ((Integer)_components.get(idx)).intValue();
_model.setSelectedComponent(_cclass, cid);
}
/** The component class associated with this editor. */
protected ComponentClass _cclass;
/** The components selectable via this editor. */
protected List _components;
/** The label denoting the currently selected component index. */
protected JLabel _clabel;
/** The builder model. */
protected BuilderModel _model;
}
@@ -1,74 +0,0 @@
//
// $Id$
//
// 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.cast.builder;
import java.util.List;
import javax.swing.*;
import com.samskivert.swing.*;
import com.threerings.cast.Log;
import com.threerings.cast.*;
/**
* The component panel displays the available components for all
* component classes and allows the user to choose a set of components
* for compositing into a character image.
*/
public class ComponentPanel extends JPanel
{
/**
* Constructs the component panel.
*/
public ComponentPanel (BuilderModel model, String cprefix)
{
setLayout(new VGroupLayout(GroupLayout.STRETCH));
// set up a border
setBorder(BorderFactory.createEtchedBorder());
// add the component editors to the panel
addClassEditors(model, cprefix);
}
/**
* Adds editor user interface elements for each component class to
* allow the user to select the desired component.
*/
protected void addClassEditors (BuilderModel model, String cprefix)
{
List classes = model.getComponentClasses();
int size = classes.size();
for (int ii = 0; ii < size; ii++) {
ComponentClass cclass = (ComponentClass)classes.get(ii);
if (!cclass.name.startsWith(cprefix)) {
continue;
}
List ccomps = model.getComponents(cclass);
if (ccomps.size() > 0) {
add(new ClassEditor(model, cclass, ccomps));
} else {
Log.info("Not creating editor for empty class " +
"[class=" + cclass + "].");
}
}
}
}
@@ -1,131 +0,0 @@
//
// $Id$
//
// 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.cast.builder;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import com.threerings.util.DirectionCodes;
import com.threerings.cast.CharacterDescriptor;
import com.threerings.cast.CharacterManager;
import com.threerings.cast.CharacterSprite;
import com.threerings.cast.StandardActions;
/**
* The sprite panel displays a character sprite centered in the panel
* suitable for user perusal.
*/
public class SpritePanel extends JPanel
implements DirectionCodes, BuilderModelListener
{
/**
* Constructs the sprite panel.
*/
public SpritePanel (CharacterManager charmgr, BuilderModel model)
{
// save off references
_charmgr = charmgr;
_model = model;
// listen to the builder model so that we can update the
// sprite when a new component is selected
_model.addListener(this);
}
// documentation inherited
public void paintComponent (Graphics g)
{
super.paintComponent(g);
Graphics2D gfx = (Graphics2D)g;
if (_sprite != null) {
// render the sprite
_sprite.paint(gfx);
}
}
// documentation inherited
public void doLayout ()
{
super.doLayout();
generateSprite();
centerSprite();
}
// documentation inherited
public void modelChanged (int event)
{
if (event == COMPONENT_CHANGED) {
generateSprite();
}
}
/**
* Generates a new character sprite for display to reflect the
* currently selected character components.
*/
protected void generateSprite ()
{
int components[] = _model.getSelectedComponents();
CharacterDescriptor desc = new CharacterDescriptor(components, null);
CharacterSprite sprite = _charmgr.getCharacter(desc);
setSprite(sprite);
}
/**
* Sets the sprite to be displayed.
*/
protected void setSprite (CharacterSprite sprite)
{
sprite.setActionSequence(StandardActions.STANDING);
sprite.setOrientation(WEST);
_sprite = sprite;
centerSprite();
repaint();
}
/**
* Sets the sprite's location to render it centered within the panel.
*/
protected void centerSprite ()
{
if (_sprite != null) {
Dimension d = getSize();
int swid = _sprite.getWidth(), shei = _sprite.getHeight();
int x = d.width / 2, y = (d.height + shei) / 2;
_sprite.setLocation(x, y);
}
}
/** The sprite displayed by the panel. */
protected CharacterSprite _sprite;
/** The character manager. */
protected CharacterManager _charmgr;
/** The builder model. */
protected BuilderModel _model;
}
@@ -1,110 +0,0 @@
//
// $Id$
//
// 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.cast.bundle;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import com.samskivert.io.StreamUtil;
import com.threerings.cast.Log;
import com.threerings.resource.ResourceBundle;
/**
* Utility functions related to creating and manipulating component
* bundles.
*/
public class BundleUtil
{
/** The path in the metadata bundle to the serialized action table. */
public static final String ACTIONS_PATH = "actions.dat";
/** The path in the metadata bundle to the serialized action tile sets
* table. */
public static final String ACTION_SETS_PATH = "action_sets.dat";
/** The path in the metadata bundle to the serialized component class
* table. */
public static final String CLASSES_PATH = "classes.dat";
/** The path in the component bundle to the serialized component id to
* class/type mapping. */
public static final String COMPONENTS_PATH = "components.dat";
/** The file extension of our action tile images. */
public static final String IMAGE_EXTENSION = ".png";
/** The serialized tileset extension for our action tilesets. */
public static final String TILESET_EXTENSION = ".dat";
/**
* Attempts to load an object from the supplied resource bundle with
* the specified path.
*
* @param wipeBundleOnFailure if there is an error reading the object
* from the bundle and this parameter is true, we will instruct the
* bundle to delete its underlying jar file before propagating the
* exception with the expectation that it will be redownloaded and
* repaired the next time the application is run.
*
* @return the unserialized object in question.
*
* @exception IOException thrown if an I/O error occurs while reading
* the object from the bundle.
*/
public static Object loadObject (ResourceBundle bundle, String path,
boolean wipeBundleOnFailure)
throws IOException, ClassNotFoundException
{
InputStream bin = null;
try {
bin = bundle.getResource(path);
if (bin == null) {
return null;
}
return new ObjectInputStream(bin).readObject();
} catch (InvalidClassException ice) {
Log.warning("Aiya! Serialized object is hosed " +
"[bundle=" + bundle.getSource().getPath() +
", element=" + path +
", error=" + ice.getMessage() + "].");
return null;
} catch (IOException ioe) {
Log.warning("Error reading resource from bundle " +
"[bundle=" + bundle + ", path=" + path +
", wiping?=" + wipeBundleOnFailure + "].");
if (wipeBundleOnFailure) {
StreamUtil.close(bin);
bin = null;
bundle.wipeBundle(false);
}
throw ioe;
} finally {
StreamUtil.close(bin);
}
}
}
@@ -1,528 +0,0 @@
//
// $Id$
//
// 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.cast.bundle;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.IntIntMap;
import com.samskivert.util.Tuple;
import org.apache.commons.collections.iterators.FilterIterator;
import org.apache.commons.collections.Predicate;
import com.threerings.resource.ResourceBundle;
import com.threerings.resource.ResourceManager;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.FastImageIO;
import com.threerings.media.image.ImageDataProvider;
import com.threerings.media.image.ImageManager;
import com.threerings.media.tile.IMImageProvider;
import com.threerings.media.tile.Tile;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TrimmedTile;
import com.threerings.util.DirectionCodes;
import com.threerings.cast.ActionFrames;
import com.threerings.cast.ActionSequence;
import com.threerings.cast.CharacterComponent;
import com.threerings.cast.ComponentClass;
import com.threerings.cast.ComponentRepository;
import com.threerings.cast.FrameProvider;
import com.threerings.cast.Log;
import com.threerings.cast.NoSuchComponentException;
import com.threerings.cast.StandardActions;
import com.threerings.cast.TrimmedMultiFrameImage;
/**
* A component repository implementation that obtains information from
* resource bundles.
*
* @see ResourceManager
*/
public class BundledComponentRepository
implements DirectionCodes, ComponentRepository
{
/**
* Constructs a repository which will obtain its resource set from the
* supplied resource manager.
*
* @param rmgr the resource manager from which to obtain our resource
* set.
* @param imgr the image manager that we'll use to decode and cache
* images.
* @param name the name of the resource set from which we will be
* loading our component data.
*
* @exception IOException thrown if an I/O error occurs while reading
* our metadata from the resource bundles.
*/
public BundledComponentRepository (
ResourceManager rmgr, ImageManager imgr, String name)
throws IOException
{
// keep this guy around
_imgr = imgr;
// first we obtain the resource set from whence will come our
// bundles
ResourceBundle[] rbundles = rmgr.getResourceSet(name);
// look for our metadata info in each of the bundles
try {
int rcount = (rbundles == null) ? 0 : rbundles.length;
for (int i = 0; i < rcount; i++) {
if (_actions == null) {
_actions = (HashMap)BundleUtil.loadObject(
rbundles[i], BundleUtil.ACTIONS_PATH, true);
}
if (_actionSets == null) {
_actionSets = (HashMap)BundleUtil.loadObject(
rbundles[i], BundleUtil.ACTION_SETS_PATH, true);
}
if (_classes == null) {
_classes = (HashMap)BundleUtil.loadObject(
rbundles[i], BundleUtil.CLASSES_PATH, true);
}
}
// now go back and load up all of the component information
for (int i = 0; i < rcount; i++) {
HashIntMap comps = null;
comps = (HashIntMap)BundleUtil.loadObject(
rbundles[i], BundleUtil.COMPONENTS_PATH, true);
if (comps == null) {
continue;
}
// create a frame provider for this bundle
FrameProvider fprov =
new ResourceBundleProvider(_imgr, rbundles[i]);
// now create character component instances for each component
// in the serialized table
Iterator iter = comps.keySet().iterator();
while (iter.hasNext()) {
int componentId = ((Integer)iter.next()).intValue();
Tuple info = (Tuple)comps.get(componentId);
createComponent(componentId, (String)info.left,
(String)info.right, fprov);
}
}
} catch (ClassNotFoundException cnfe) {
throw (IOException) new IOException(
"Internal error unserializing metadata").initCause(cnfe);
}
// if we failed to load our classes or actions, create empty
// hashtables so that we can safely enumerate our emptiness
if (_actions == null) {
_actions = new HashMap();
}
if (_classes == null) {
_classes = new HashMap();
}
}
/**
* Configures the bundled component repository to wipe any bundles
* that report certain kinds of failure. In the event that an unpacked
* bundle becomes corrupt, this is useful in that it will force the
* bundle to be unpacked on the next application invocation,
* potentially remedying the problem of a corrupt unpacking.
*/
public void setWipeOnFailure (boolean wipeOnFailure)
{
_wipeOnFailure = true;
}
// documentation inherited
public CharacterComponent getComponent (int componentId)
throws NoSuchComponentException
{
CharacterComponent component = (CharacterComponent)
_components.get(componentId);
if (component == null) {
throw new NoSuchComponentException(componentId);
}
return component;
}
// documentation inherited
public CharacterComponent getComponent (String className, String compName)
throws NoSuchComponentException
{
// look up the list for that class
ArrayList comps = (ArrayList)_classComps.get(className);
if (comps != null) {
// scan the list for the named component
int ccount = comps.size();
for (int i = 0; i < ccount; i++) {
CharacterComponent comp = (CharacterComponent)comps.get(i);
if (comp.name.equals(compName)) {
return comp;
}
}
}
throw new NoSuchComponentException(className, compName);
}
// documentation inherited
public ComponentClass getComponentClass (String className)
{
return (ComponentClass)_classes.get(className);
}
// documentation inherited
public Iterator enumerateComponentClasses ()
{
return _classes.values().iterator();
}
// documentation inherited
public Iterator enumerateActionSequences ()
{
return _actions.values().iterator();
}
// documentation inherited
public Iterator enumerateComponentIds (final ComponentClass compClass)
{
Predicate classP = new Predicate() {
public boolean evaluate (Object input) {
CharacterComponent comp = (CharacterComponent)
_components.get(input);
return comp.componentClass.equals(compClass);
}
};
return new FilterIterator(_components.keySet().iterator(), classP);
}
/**
* Creates a component and inserts it into the component table.
*/
protected void createComponent (
int componentId, String cclass, String cname, FrameProvider fprov)
{
// look up the component class information
ComponentClass clazz = (ComponentClass)_classes.get(cclass);
if (clazz == null) {
Log.warning("Non-existent component class " +
"[class=" + cclass + ", name=" + cname +
", id=" + componentId + "].");
return;
}
// create the component
CharacterComponent component = new CharacterComponent(
componentId, cname, clazz, fprov);
// stick it into the appropriate tables
_components.put(componentId, component);
// we have a hash of lists for mapping components by class/name
ArrayList comps = (ArrayList)_classComps.get(cclass);
if (comps == null) {
comps = new ArrayList();
_classComps.put(cclass, comps);
}
if (!comps.contains(component)) {
comps.add(component);
} else {
Log.info("Requested to register the same component twice? " +
"[component=" + component + "].");
}
}
/**
* Instances of these provide images to our component action tilesets
* and frames to our components.
*/
protected class ResourceBundleProvider extends IMImageProvider
implements ImageDataProvider, FrameProvider
{
/**
* Constructs an instance that will obtain image data from the
* specified resource bundle.
*/
public ResourceBundleProvider (ImageManager imgr, ResourceBundle bundle)
{
super(imgr, (String)null);
_dprov = this;
_bundle = bundle;
}
// documentation inherited from interface
public String getIdent ()
{
return "bcr:" + _bundle.getSource();
}
// documentation inherited from interface
public BufferedImage loadImage (String path) throws IOException
{
return FastImageIO.read(_bundle.getResourceFile(path));
}
// documentation inherited
public ActionFrames getFrames (
CharacterComponent component, String action, String type)
{
// obtain the action sequence definition for this action
ActionSequence actseq = (ActionSequence)_actions.get(action);
if (actseq == null) {
Log.warning("Missing action sequence definition " +
"[action=" + action +
", component=" + component + "].");
return null;
}
// determine our image path name
String imgpath = action, dimgpath = ActionSequence.DEFAULT_SEQUENCE;
if (type != null) {
imgpath += "_" + type;
dimgpath += "_" + type;
}
String root = component.componentClass.name + "/" +
component.name + "/";
String cpath = root + imgpath + BundleUtil.TILESET_EXTENSION;
String dpath = root + dimgpath + BundleUtil.TILESET_EXTENSION;
// look to see if this tileset is already cached (as the
// custom action or the default action)
TileSet aset = (TileSet)_setcache.get(cpath);
if (aset == null) {
aset = (TileSet)_setcache.get(dpath);
if (aset != null) {
// save ourselves a lookup next time
_setcache.put(cpath, aset);
}
}
try {
// then try loading up a tileset customized for this action
if (aset == null) {
aset = (TileSet)BundleUtil.loadObject(
_bundle, cpath, false);
}
// if that failed, try loading the default tileset
if (aset == null) {
aset = (TileSet)BundleUtil.loadObject(
_bundle, dpath, false);
_setcache.put(dpath, aset);
}
// if that failed too, we're hosed
if (aset == null) {
// if this is a shadow image, no need to freak out as they
// are optional
if (!StandardActions.SHADOW_TYPE.equals(type)) {
Log.warning("Unable to locate tileset for action '" +
imgpath + "' " + component + ".");
if (_wipeOnFailure) {
_bundle.wipeBundle(false);
}
}
return null;
}
aset.setImageProvider(this);
_setcache.put(cpath, aset);
return new TileSetFrameImage(aset, actseq);
} catch (Exception e) {
Log.warning("Error loading tileset for action '" + imgpath +
"' " + component + ".");
Log.logStackTrace(e);
return null;
}
}
/** The resource bundle from which we obtain image data. */
protected ResourceBundle _bundle;
/** Cache of tilesets loaded from our bundle. */
protected HashMap _setcache = new HashMap();
}
/**
* Used to provide multiframe images using data obtained from a
* tileset.
*/
protected static class TileSetFrameImage implements ActionFrames
{
/**
* Constructs a tileset frame image with the specified tileset and
* for the specified orientation.
*/
public TileSetFrameImage (TileSet set, ActionSequence actseq)
{
_set = set;
_actseq = actseq;
// compute these now to avoid pointless recomputation later
_ocount = actseq.orients.length;
_fcount = set.getTileCount() / _ocount;
// create our mapping from orientation to animation sequence
// index
for (int ii = 0; ii < _ocount; ii++) {
_orients.put(actseq.orients[ii], ii);
}
}
// documentation inherited from interface
public int getOrientationCount ()
{
return _ocount;
}
// documentation inherited from interface
public TrimmedMultiFrameImage getFrames (final int orient)
{
return new TrimmedMultiFrameImage() {
// documentation inherited
public int getFrameCount ()
{
return _fcount;
}
// documentation inherited from interface
public int getWidth (int index)
{
Tile tile = getTile(orient, index);
return (tile == null) ? 0 : tile.getWidth();
}
// documentation inherited from interface
public int getHeight (int index)
{
Tile tile = getTile(orient, index);
return (tile == null) ? 0 : tile.getHeight();
}
// documentation inherited from interface
public void paintFrame (Graphics2D g, int index, int x, int y)
{
Tile tile = getTile(orient, index);
if (tile != null) {
tile.paint(g, x, y);
}
}
// documentation inherited from interface
public boolean hitTest (int index, int x, int y)
{
Tile tile = getTile(orient, index);
return (tile != null) ? tile.hitTest(x, y) : false;
}
// documentation inherited from interface
public void getTrimmedBounds (int index, Rectangle bounds)
{
Tile tile = getTile(orient, index);
if (tile instanceof TrimmedTile) {
((TrimmedTile)tile).getTrimmedBounds(bounds);
} else {
bounds.setBounds(
0, 0, tile.getWidth(), tile.getHeight());
}
}
};
}
// documentation inherited from interface
public int getXOrigin (int orient, int index)
{
return _actseq.origin.x;
}
// documentation inherited from interface
public int getYOrigin (int orient, int index)
{
return _actseq.origin.y;
}
// documentation inherited from interface
public ActionFrames cloneColorized (Colorization[] zations)
{
return new TileSetFrameImage(_set.clone(zations), _actseq);
}
/**
* Fetches the requested tile.
*/
protected Tile getTile (int orient, int index)
{
int tileIndex = _orients.get(orient) * _fcount + index;
return _set.getTile(tileIndex);
}
/** The tileset from which we obtain our frame images. */
protected TileSet _set;
/** The action sequence for which we're providing frame images. */
protected ActionSequence _actseq;
/** Frame and orientation counts. */
protected int _fcount, _ocount;
/** A mapping from orientation code to animation sequence
* index. */
protected IntIntMap _orients = new IntIntMap();
}
/** We use the image manager to decode and cache images. */
protected ImageManager _imgr;
/** A table of action sequences. */
protected HashMap _actions;
/** A table of action sequence tilesets. */
protected HashMap _actionSets;
/** A table of component classes. */
protected HashMap _classes;
/** A table of component lists indexed on classname. */
protected HashMap _classComps = new HashMap();
/** The component table. */
protected HashIntMap _components = new HashIntMap();
/** Whether or not we wipe our bundles on any failure. */
protected boolean _wipeOnFailure;
}
@@ -1,624 +0,0 @@
//
// $Id$
//
// 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.cast.bundle.tools;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.jar.JarOutputStream;
import java.util.jar.JarEntry;
import java.util.zip.Deflater;
import org.apache.commons.digester.Digester;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.ComparableArrayList;
import com.samskivert.util.FileUtil;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Tuple;
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.threerings.media.tile.ImageProvider;
import com.threerings.media.tile.SimpleCachingImageProvider;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.TrimmedTileSet;
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
import com.threerings.cast.ComponentIDBroker;
import com.threerings.cast.StandardActions;
import com.threerings.cast.bundle.BundleUtil;
import com.threerings.cast.tools.xml.ActionRuleSet;
/**
* Ant task for creating component bundles. This task must be configured
* with a number of parameters:
*
* <pre>
* target=[path to bundle file, which will be created]
* mapfile=[path to the component map file which maintains a mapping from
* component id to component class/name, it will be created the
* first time and updated as new components are mapped]
* </pre>
*
* It should also contain one or more nested &lt;fileset&gt; elements that
* enumerate the action tileset images that should be included in the
* component bundle.
*/
public class ComponentBundlerTask extends Task
{
/**
* Sets the path to the bundle file that we'll be creating.
*/
public void setTarget (File target)
{
_target = target;
}
/**
* Sets the path to the component map file that we'll use to obtain
* component ids for the bundled components.
*/
public void setMapfile (File mapfile)
{
_mapfile = mapfile;
}
/**
* Sets the path to the action tilesets definition file.
*/
public void setActiondef (File actiondef)
{
_actionDef = actiondef;
}
/**
* Sets the root path which will be stripped from the image paths
* prior to parsing them to obtain the component class, type and
* action names.
*/
public void setRoot (File root)
{
_root = root.getPath();
}
/**
* Adds a nested &lt;fileset&gt; element.
*/
public void addFileset (FileSet set)
{
_filesets.add(set);
}
/**
* Performs the actual work of the task.
*/
public void execute () throws BuildException
{
// make sure everything was set up properly
ensureSet(_target, "Must specify the path to the target bundle " +
"file via the 'target' attribute.");
ensureSet(_mapfile, "Must specify the path to the component map " +
"file via the 'mapfile' attribute.");
ensureSet(_actionDef, "Must specify the action sequence " +
"definitions via the 'actiondef' attribute.");
// parse in the action tilesets
HashMap actsets = parseActionTileSets();
// load up our component ID broker
ComponentIDBroker broker = loadBroker(_mapfile);
// check to see if any of the source files are newer than the
// target file
ArrayList sources = (ArrayList)_filesets.clone();
sources.add(_mapfile);
sources.add(_actionDef);
if (!outOfDate(sources, _target)) {
System.out.println(_target.getPath() + " is up to date.");
return;
}
// create an image provider for loading our component images
ImageProvider improv = new SimpleCachingImageProvider() {
protected BufferedImage loadImage (String path)
throws IOException {
return ImageIO.read(new File(path));
}
};
System.out.println("Generating " + _target.getPath() + "...");
try {
// make sure we can create our bundle file
FileOutputStream fout = new FileOutputStream(_target);
JarOutputStream jout = new JarOutputStream(fout);
jout.setLevel(Deflater.BEST_COMPRESSION);
// we'll fill this with component id to tuple mappings
HashIntMap mapping = new HashIntMap();
// herein we'll insert trimmed tileset objects that go along
// with each of the trimmed action images
HashIntMap actionSets = new HashIntMap();
// deal with the filesets
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]);
// determine the [class, name, action] triplet
String[] info = decomposePath(cfile.getPath());
// make sure we have an action tileset definition
TileSet aset = (TileSet)actsets.get(info[2]);
if (aset == null) {
System.err.println(
"No tileset definition for component action " +
"[class=" + info[0] + ", name=" + info[1] +
", action=" + info[2] + "].");
continue;
}
aset.setImageProvider(improv);
// obtain the component id from our id broker
int cid = broker.getComponentID(info[0], info[1]);
// add a mapping for this component
mapping.put(cid, new Tuple(info[0], info[1]));
// process and store the main component image
processComponent(info, aset, cfile, jout);
// pick up any auxiliary images as well like the shadow or
// crop files
String action = info[2];
String ext = BundleUtil.IMAGE_EXTENSION;
for (int aa = 0; aa < AUX_EXTS.length; aa++) {
File afile = new File(
FileUtil.resuffix(cfile, ext, AUX_EXTS[aa] + ext));
if (afile.exists()) {
info[2] = action + AUX_EXTS[aa];
processComponent(info, aset, afile, jout);
}
}
}
}
// write our mapping table to the jar file as well
jout.putNextEntry(new JarEntry(BundleUtil.COMPONENTS_PATH));
ObjectOutputStream oout = new ObjectOutputStream(jout);
oout.writeObject(mapping);
oout.flush();
// seal up our jar file
jout.close();
} catch (IOException ioe) {
String errmsg = "Unable to create component bundle.";
throw new BuildException(errmsg, ioe);
} catch (PersistenceException pe) {
String errmsg = "Unable to obtain component ID mapping.";
throw new BuildException(errmsg, pe);
}
// save our updated component ID broker
saveBroker(_mapfile, broker);
}
protected void processComponent (
String[] info, TileSet aset, File cfile, JarOutputStream jout)
throws IOException
{
// construct the path that'll go in the jar file
String ipath = composePath(
info, BundleUtil.IMAGE_EXTENSION);
jout.putNextEntry(new JarEntry(ipath));
// create a trimmed tileset based on the source action tileset and
// stuff the new trimmed image into the jar file at the same time
aset.setImagePath(cfile.getPath());
TrimmedTileSet tset = null;
try {
tset = TrimmedTileSet.trimTileSet(aset, jout);
tset.setImagePath(ipath);
} catch (Throwable t) {
System.err.println(
"Failure trimming tileset " +
"[class=" + info[0] + ", name=" + info[1] +
", action=" + info[2] +
", srcimg=" + aset.getImagePath() + "].");
t.printStackTrace(System.err);
}
// then write our trimmed tileset to the jar file
if (tset != null) {
String tpath = composePath(
info, BundleUtil.TILESET_EXTENSION);
jout.putNextEntry(new JarEntry(tpath));
ObjectOutputStream oout = new ObjectOutputStream(jout);
oout.writeObject(tset);
oout.flush();
}
}
protected boolean outOfDate (ArrayList sources, File target)
{
for (int ii = 0; ii < sources.size(); ii++) {
if (outOfDate(sources.get(ii), target)) {
return true;
}
}
return false;
}
protected boolean outOfDate (Object source, File target)
{
if (source instanceof FileSet) {
FileSet fs = (FileSet)source;
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]);
if (newer(cfile, target)) {
return true;
}
}
return false;
} else if (source instanceof File) {
return newer((File)source, target);
} else {
System.err.println("Can't compare " + source +
" to " + target + ".");
return true;
}
}
/**
* Returns true if <code>source</code> is newer than
* <code>target</code>.
*/
protected boolean newer (File source, File target)
{
return source.lastModified() > target.lastModified();
}
/**
* Decomposes the full path to a component image into a [class, name,
* action] triplet.
*/
protected String[] decomposePath (String path)
throws BuildException
{
// first strip off the root
if (!path.startsWith(_root)) {
throw new BuildException("Can't bundle images outside the " +
"root directory [root=" + _root +
", path=" + path + "].");
}
path = path.substring(_root.length());
// strip off any preceding slash
if (path.startsWith("/")) {
path = path.substring(1);
}
// now strip off the file extension
if (!path.endsWith(BundleUtil.IMAGE_EXTENSION)) {
throw new BuildException("Can't bundle malformed image file " +
"[path=" + path + "].");
}
path = path.substring(0, path.length() -
BundleUtil.IMAGE_EXTENSION.length());
// now decompose the path; the component type and action must
// always be a single string but the class can span multiple
// directories for easier component organization; thus
// "male/head/goatee/standing" will be parsed as [class=male/head,
// type=goatee, action=standing]
String malmsg = "Can't decode malformed image path: '" + path + "'";
String[] info = new String[3];
int lsidx = path.lastIndexOf(File.separator);
if (lsidx == -1) {
throw new BuildException(malmsg);
}
info[2] = path.substring(lsidx+1);
int slsidx = path.lastIndexOf(File.separator, lsidx-1);
if (slsidx == -1) {
throw new BuildException(malmsg);
}
info[1] = path.substring(slsidx+1, lsidx);
info[0] = path.substring(0, slsidx);
return info;
}
/**
* Composes a triplet of [class, name, action] into the path that
* should be supplied to the JarEntry that contains the associated
* image data.
*/
protected String composePath (String[] info, String extension)
{
return (info[0] + File.separator + info[1] +
File.separator + info[2] + extension);
}
protected void ensureSet (Object value, String errmsg)
throws BuildException
{
if (value == null) {
throw new BuildException(errmsg);
}
}
/**
* Parses the action tileset definitions and puts them into a hash
* map, keyed on action name.
*/
protected HashMap parseActionTileSets ()
{
Digester digester = new Digester();
SwissArmyTileSetRuleSet srules = new SwissArmyTileSetRuleSet();
String aprefix = "actions" + ActionRuleSet.ACTION_PATH;
srules.setPrefix(aprefix);
digester.addRuleSet(srules);
digester.addSetProperties(aprefix);
digester.addSetNext(aprefix + SwissArmyTileSetRuleSet.TILESET_PATH,
"addTileSet", TileSet.class.getName());
HashMap actsets = new ActionMap();
digester.push(actsets);
try {
FileInputStream fin = new FileInputStream(_actionDef);
BufferedInputStream bin = new BufferedInputStream(fin);
digester.parse(bin);
} catch (FileNotFoundException fnfe) {
String errmsg = "Unable to load action definition file " +
"[path=" + _actionDef.getPath() + "].";
throw new BuildException(errmsg, fnfe);
} catch (Exception e) {
throw new BuildException("Parsing error.", e);
}
return actsets;
}
/** Used when parsing action tilesets. */
public static class ActionMap extends HashMap
{
public void setName (String name) {
_name = name;
}
public void addTileSet (TileSet set) {
set.setName(_name);
put(_name, set);
}
protected String _name;
}
/**
* Loads the hashmap ID broker from its persistent representation in
* the specified file.
*/
protected HashMapIDBroker loadBroker (File mapfile)
throws BuildException
{
HashMapIDBroker broker = new HashMapIDBroker();
try {
BufferedReader bin = new BufferedReader(new FileReader(mapfile));
broker.readFrom(bin);
bin.close();
} catch (FileNotFoundException fnfe) {
// if the file doesn't yet exist, start with a blank broker
} catch (Exception e) {
throw new BuildException("Error loading component ID map " +
"[mapfile=" + mapfile + "]", e);
}
return broker;
}
/**
* Stores a persistent representation of the supplied hashmap ID
* broker in the specified file.
*/
protected void saveBroker (File mapfile, ComponentIDBroker broker)
throws BuildException
{
HashMapIDBroker hbroker = (HashMapIDBroker)broker;
// bail if the broker wasn't modified
if (!hbroker.isModified()) {
return;
}
try {
BufferedWriter bout = new BufferedWriter(new FileWriter(mapfile));
hbroker.writeTo(bout);
bout.close();
} catch (IOException ioe) {
throw new BuildException("Unable to store component ID map " +
"[mapfile=" + mapfile + "]", ioe);
}
}
protected static class HashMapIDBroker
extends HashMap implements ComponentIDBroker
{
public int getComponentID (String cclass, String cname)
throws PersistenceException
{
Tuple key = new Tuple(cclass, cname);
Integer cid = (Integer)get(key);
if (cid == null) {
cid = Integer.valueOf(++_nextCID);
put(key, cid);
}
return cid.intValue();
}
public void commit ()
throws PersistenceException
{
// nothing doing
}
public boolean isModified ()
{
return _nextCID != _startCID;
}
public void writeTo (BufferedWriter bout)
throws IOException
{
// write out our most recently assigned component id
String cidline = "" + _nextCID;
bout.write(cidline, 0, cidline.length());
bout.newLine();
// write out the keys and values
ComparableArrayList lines = new ComparableArrayList();
Iterator keys = keySet().iterator();
while (keys.hasNext()) {
Tuple key = (Tuple)keys.next();
Integer value = (Integer)get(key);
String line = key.left + SEP_STR + key.right + SEP_STR + value;
lines.add(line);
}
// sort the output
lines.sort();
// now write it to the file
int lcount = lines.size();
for (int ii = 0; ii < lcount; ii++) {
String line = (String)lines.get(ii);
bout.write(line, 0, line.length());
bout.newLine();
}
}
public void readFrom (BufferedReader bin)
throws IOException
{
// read in our most recently assigned component id
_nextCID = readInt(bin);
// keep track of this so that we can tell if we were modified
_startCID = _nextCID;
// now read in our keys and values
String line;
while ((line = bin.readLine()) != null) {
String orig = line;
int sidx = line.indexOf(SEP_STR);
if (sidx == -1) {
throw new IOException("Malformed line '" + orig + "'");
}
String cclass = line.substring(0, sidx);
line = line.substring(sidx + SEP_STR.length());
sidx = line.indexOf(SEP_STR);
if (sidx == -1) {
throw new IOException("Malformed line '" + orig + "'");
}
String cname = line.substring(0, sidx);
line = line.substring(sidx + SEP_STR.length());
try {
put(new Tuple(cclass, cname), Integer.valueOf(line));
} catch (NumberFormatException nfe) {
String err = "Malformed line, invalid code '" + orig + "'";
throw new IOException(err);
}
}
}
protected int readInt (BufferedReader bin)
throws IOException
{
String line = bin.readLine();
try {
return Integer.parseInt(line);
} catch (NumberFormatException nfe) {
throw new IOException("Expected number, got '" + line + "'");
}
}
protected int _nextCID = 0;
protected int _startCID = 0;
}
/** The path to our component bundle file. */
protected File _target;
/** The path to our component map file. */
protected File _mapfile;
/** The path to our action tilesets definition file. */
protected File _actionDef;
/** The component directory root. */
protected String _root;
/** A list of filesets that contain tile images. */
protected ArrayList _filesets = new ArrayList();
/** Used to separate keys and values in the map file. */
protected static final String SEP_STR = " := ";
/** Used to process auxilliary tilesets. */
protected static final String[] AUX_EXTS = {
"_" + StandardActions.SHADOW_TYPE,
"_" + StandardActions.CROP_TYPE };
}
@@ -1,84 +0,0 @@
//
// $Id$
//
// 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.cast.bundle.tools;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.threerings.resource.ResourceBundle;
import com.threerings.cast.bundle.BundleUtil;
/**
* Dumps the contents of a component bundle to stdout.
*/
public class DumpBundle
{
public static void main (String[] args)
{
if (args.length < 1) {
String usage = "Usage: DumpBundle bundle.jar [bundle.jar ...]";
System.err.println(usage);
System.exit(-1);
}
for (int i = 0; i < args.length; i++) {
File file = new File(args[i]);
try {
ResourceBundle bundle = new ResourceBundle(file);
HashMap actions = (HashMap)BundleUtil.loadObject(
bundle, BundleUtil.ACTIONS_PATH, false);
dumpTable("actions: ", actions);
HashMap actionSets = (HashMap)BundleUtil.loadObject(
bundle, BundleUtil.ACTION_SETS_PATH, false);
dumpTable("actionSets: ", actionSets);
HashMap classes = (HashMap)BundleUtil.loadObject(
bundle, BundleUtil.CLASSES_PATH, false);
dumpTable("classes: ", classes);
HashIntMap comps = (HashIntMap)BundleUtil.loadObject(
bundle, BundleUtil.COMPONENTS_PATH, false);
dumpTable("components: ", comps);
} catch (Exception e) {
System.err.println("Error dumping bundle [path=" + args[i] +
", error=" + e + "].");
e.printStackTrace();
}
}
}
protected static void dumpTable (String prefix, Map table)
{
if (table != null) {
Iterator iter = table.entrySet().iterator();
System.out.println(prefix + StringUtil.toString(iter));
}
}
}
@@ -1,267 +0,0 @@
//
// $Id$
//
// 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.cast.bundle.tools;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.jar.JarOutputStream;
import java.util.jar.JarEntry;
import java.util.zip.Deflater;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.commons.digester.Digester;
import com.samskivert.util.Tuple;
import com.threerings.media.tile.TileSet;
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
import com.threerings.cast.ActionSequence;
import com.threerings.cast.ComponentClass;
import com.threerings.cast.bundle.BundleUtil;
import com.threerings.cast.tools.xml.ActionRuleSet;
import com.threerings.cast.tools.xml.ClassRuleSet;
/**
* Ant task for creating metadata bundles, which contain action sequence
* and component class definition information. This task must be
* configured with a number of parameters:
*
* <pre>
* actiondef=[path to actions.xml]
* classdef=[path to classes.xml]
* file=[path to metadata bundle, which will be created]
* </pre>
*/
public class MetadataBundlerTask extends Task
{
public void setActiondef (String actiondef)
{
_actionDef = actiondef;
}
public void setClassdef (String classdef)
{
_classDef = classdef;
}
public void setTarget (File target)
{
_target = target;
}
/**
* Performs the actual work of the task.
*/
public void execute ()
throws BuildException
{
// make sure everythign was set up properly
ensureSet(_actionDef, "Must specify the action sequence " +
"definitions via the 'actiondef' attribute.");
ensureSet(_classDef, "Must specify the component class definitions " +
"via the 'classdef' attribute.");
ensureSet(_target, "Must specify the path to the target bundle " +
"file via the 'target' attribute.");
// make sure we can write to the target bundle file
FileOutputStream fout = null;
try {
fout = new FileOutputStream(_target);
// parse our metadata
Tuple tuple = parseActions();
HashMap actions = (HashMap)tuple.left;
HashMap actionSets = (HashMap)tuple.right;
HashMap classes = parseClasses();
// and create the bundle file
JarOutputStream jout = new JarOutputStream(fout);
jout.setLevel(Deflater.BEST_COMPRESSION);
// throw the serialized actions table in there
JarEntry aentry = new JarEntry(BundleUtil.ACTIONS_PATH);
jout.putNextEntry(aentry);
ObjectOutputStream oout = new ObjectOutputStream(jout);
oout.writeObject(actions);
oout.flush();
// throw the serialized action tilesets table in there
JarEntry sentry = new JarEntry(BundleUtil.ACTION_SETS_PATH);
jout.putNextEntry(sentry);
oout = new ObjectOutputStream(jout);
oout.writeObject(actionSets);
oout.flush();
// throw the serialized classes table in there
JarEntry centry = new JarEntry(BundleUtil.CLASSES_PATH);
jout.putNextEntry(centry);
oout = new ObjectOutputStream(jout);
oout.writeObject(classes);
oout.flush();
// close it up and we're done
jout.close();
} catch (IOException ioe) {
String errmsg = "Unable to output to target bundle " +
"[path=" + _target.getPath() + "].";
throw new BuildException(errmsg, ioe);
} finally {
if (fout != null) {
try {
fout.close();
} catch (IOException ioe) {
// nothing to complain about here
}
}
}
}
protected Tuple parseActions ()
throws BuildException
{
// scan through the XML once to read the actions
Digester digester = new Digester();
ActionRuleSet arules = new ActionRuleSet();
arules.setPrefix("actions");
digester.addRuleSet(arules);
digester.addSetNext("actions" + ActionRuleSet.ACTION_PATH,
"add", Object.class.getName());
ArrayList actlist = parseList(digester, _actionDef);
// now go through a second time reading the tileset info
digester = new Digester();
SwissArmyTileSetRuleSet srules = new SwissArmyTileSetRuleSet();
srules.setPrefix("actions" + ActionRuleSet.ACTION_PATH);
digester.addRuleSet(srules);
digester.addSetNext("actions" + ActionRuleSet.ACTION_PATH +
SwissArmyTileSetRuleSet.TILESET_PATH, "add",
Object.class.getName());
ArrayList setlist = parseList(digester, _actionDef);
// sanity check
if (actlist.size() != setlist.size()) {
String errmsg = "An action is missing its tileset " +
"definition, or something even wackier is going on.";
throw new BuildException(errmsg);
}
// now create our mappings
HashMap actmap = new HashMap();
HashMap setmap = new HashMap();
// create the action map
for (int i = 0; i < setlist.size(); i++) {
TileSet set = (TileSet)setlist.get(i);
ActionSequence act = (ActionSequence)actlist.get(i);
// make sure nothing was missing in the action sequence
// definition parsed from XML
String errmsg = ActionRuleSet.validate(act);
if (errmsg != null) {
errmsg = "Action sequence invalid [seq=" + act +
", error=" + errmsg + "].";
throw new BuildException(errmsg);
}
actmap.put(act.name, act);
setmap.put(act.name, set);
}
return new Tuple(actmap, setmap);
}
protected HashMap parseClasses ()
throws BuildException
{
// load up our action and class info
Digester digester = new Digester();
// add our action rule set and a a rule to grab parsed actions
ClassRuleSet crules = new ClassRuleSet();
crules.setPrefix("classes");
digester.addRuleSet(crules);
digester.addSetNext("classes" + ClassRuleSet.CLASS_PATH,
"add", Object.class.getName());
ArrayList setlist = parseList(digester, _classDef);
HashMap clmap = new HashMap();
// create the action map
for (int i = 0; i < setlist.size(); i++) {
ComponentClass cl = (ComponentClass)setlist.get(i);
clmap.put(cl.name, cl);
}
return clmap;
}
protected ArrayList parseList (Digester digester, String path)
throws BuildException
{
try {
FileInputStream fin = new FileInputStream(path);
BufferedInputStream bin = new BufferedInputStream(fin);
ArrayList setlist = new ArrayList();
digester.push(setlist);
// now fire up the digester to parse the stream
try {
digester.parse(bin);
} catch (Exception e) {
throw new BuildException("Parsing error.", e);
}
return setlist;
} catch (FileNotFoundException fnfe) {
String errmsg = "Unable to load metadata definition file " +
"[path=" + path + "].";
throw new BuildException(errmsg, fnfe);
}
}
protected void ensureSet (Object value, String errmsg)
throws BuildException
{
if (value == null) {
throw new BuildException(errmsg);
}
}
protected String _actionDef;
protected String _classDef;
protected File _target;
}
@@ -1,146 +0,0 @@
//
// $Id$
//
// 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.cast.tools.xml;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.RuleSetBase;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.CallMethodSpecialRule;
import com.samskivert.xml.SetFieldRule;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.threerings.util.DirectionCodes;
import com.threerings.util.DirectionUtil;
import com.threerings.cast.ActionSequence;
/**
* The action rule set is used to parse the attributes of an action
* sequence instance.
*/
public class ActionRuleSet extends RuleSetBase
{
/** The component of the digester path that is appended by the action
* rule set to match a action. This is appended to whatever prefix is
* provided to the action rule set to obtain the complete XML path to
* a matched action. */
public static final String ACTION_PATH = "/action";
/**
* Instructs the action rule set to match actions with the supplied
* prefix. For example, passing a prefix of <code>actions</code> will
* match actions in the following XML file:
*
* <pre>
* &lt;actions&gt;
* &lt;action&gt;
* // ...
* &lt;/action&gt;
* &lt;/actions&gt;
* </pre>
*
* This must be called before adding the ruleset to a digester.
*/
public void setPrefix (String prefix)
{
_prefix = prefix;
}
/**
* Adds the necessary rules to the digester to parse our actions.
*/
public void addRuleInstances (Digester digester)
{
// this creates the appropriate instance when we encounter a
// <action> tag
digester.addObjectCreate(_prefix + ACTION_PATH,
ActionSequence.class.getName());
// grab the name attribute from the <action> tag
digester.addRule(_prefix + ACTION_PATH, new SetPropertyFieldsRule());
// grab the other attributes from their respective tags
digester.addRule(_prefix + ACTION_PATH + "/framesPerSecond",
new SetFieldRule("framesPerSecond"));
CallMethodSpecialRule origin = new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
throws Exception {
int[] coords = StringUtil.parseIntArray(bodyText);
if (coords.length != 2) {
String errmsg = "Invalid <origin> specification '" +
bodyText + "'.";
throw new Exception(errmsg);
}
((ActionSequence)target).origin.setLocation(
coords[0], coords[1]);
}
};
digester.addRule(_prefix + ACTION_PATH + "/origin", origin);
CallMethodSpecialRule orient = new CallMethodSpecialRule() {
public void parseAndSet (String bodyText, Object target)
throws Exception {
ActionSequence seq = ((ActionSequence)target);
String[] ostrs = StringUtil.parseStringArray(bodyText);
seq.orients = new int[ostrs.length];
for (int ii = 0; ii < ostrs.length; ii++) {
int orient = DirectionUtil.fromShortString(ostrs[ii]);
if (orient != DirectionCodes.NONE) {
seq.orients[ii] = orient;
} else {
String errmsg = "Invalid orientation specification " +
"[index=" + ii + ", orient=" + ostrs[ii] + "].";
throw new Exception(errmsg);
}
}
}
};
digester.addRule(_prefix + ACTION_PATH + "/orients", orient);
}
/**
* Validates that all necessary fields have been parsed and set in
* this action sequence object and are valid.
*
* @return null if the sequence is valid, a string explaining the
* invalidity if it is not.
*/
public static String validate (ActionSequence seq)
{
if (StringUtil.isBlank(seq.name)) {
return "Missing 'name' definition.";
}
if (seq.framesPerSecond == 0) {
return "Missing 'framesPerSecond' definition.";
}
if (seq.orients == null) {
return "Missing 'orients' definition.";
}
return null;
}
/** The prefix at which me match our actions. */
protected String _prefix;
}
@@ -1,110 +0,0 @@
//
// $Id$
//
// 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.cast.tools.xml;
import java.awt.Color;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.RuleSetBase;
import com.samskivert.util.ArrayIntSet;
import com.samskivert.util.StringUtil;
import com.samskivert.xml.SetPropertyFieldsRule;
import com.samskivert.xml.SetPropertyFieldsRule.FieldParser;
import com.threerings.cast.ComponentClass.PriorityOverride;
import com.threerings.cast.ComponentClass;
import com.threerings.util.DirectionUtil;
/**
* The class rule set is used to parse the attributes of a component class
* instance.
*/
public class ClassRuleSet extends RuleSetBase
{
/** The component of the digester path that is appended by the class
* rule set to match a component class. This is appended to whatever
* prefix is provided to the class rule set to obtain the complete XML
* path to a matched class. */
public static final String CLASS_PATH = "/class";
/**
* Instructs the class rule set to match component classes with the
* supplied prefix. For example, passing a prefix of
* <code>classes</code> will match classes in the following XML file:
*
* <pre>
* &lt;classes&gt;
* &lt;class .../&gt;
* &lt;/classes&gt;
* </pre>
*
* This must be called before adding the ruleset to a digester.
*/
public void setPrefix (String prefix)
{
_prefix = prefix;
}
/**
* Adds the necessary rules to the digester to parse our classes.
*/
public void addRuleInstances (Digester digester)
{
// this creates the appropriate instance when we encounter a
// <class> tag
digester.addObjectCreate(_prefix + CLASS_PATH,
ComponentClass.class.getName());
// grab the attributes from the <class> tag
SetPropertyFieldsRule rule = new SetPropertyFieldsRule();
rule.addFieldParser("shadowColor", new FieldParser() {
public Object parse (String text) {
int[] values = StringUtil.parseIntArray(text);
return new Color(values[0], values[1], values[2], values[3]);
}
});
digester.addRule(_prefix + CLASS_PATH, rule);
// parse render priority overrides
String opath = _prefix + CLASS_PATH + "/override";
digester.addObjectCreate(opath, PriorityOverride.class.getName());
rule = new SetPropertyFieldsRule();
rule.addFieldParser("orients", new FieldParser() {
public Object parse (String text) {
String[] orients = StringUtil.parseStringArray(text);
ArrayIntSet oset = new ArrayIntSet();
for (int ii = 0; ii < orients.length; ii++) {
oset.add(DirectionUtil.fromShortString(orients[ii]));
}
return oset;
}
});
digester.addRule(opath, rule);
digester.addSetNext(opath, "addPriorityOverride",
PriorityOverride.class.getName());
}
/** The prefix at which me match our component classes. */
protected String _prefix;
}
@@ -1,97 +0,0 @@
//
// $Id$
//
// 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.cast.util;
import java.util.ArrayList;
import java.util.Iterator;
import com.samskivert.util.CollectionUtil;
import com.samskivert.util.RandomUtil;
import com.threerings.cast.CharacterDescriptor;
import com.threerings.cast.ComponentClass;
import com.threerings.cast.ComponentRepository;
import com.threerings.cast.Log;
/**
* Miscellaneous cast utility routines.
*/
public class CastUtil
{
/**
* Returns a new character descriptor populated with a random set of
* components.
*/
public static CharacterDescriptor getRandomDescriptor (
String gender, ComponentRepository crepo)
{
// get all available classes
ArrayList classes = new ArrayList();
for (int i = 0; i < CLASSES.length; i++) {
String cname = gender + "/" + CLASSES[i];
ComponentClass cclass = crepo.getComponentClass(cname);
// make sure the component class exists
if (cclass == null) {
Log.warning("Missing definition for component class " +
"[class=" + cname + "].");
continue;
}
// make sure there are some components in this class
Iterator iter = crepo.enumerateComponentIds(cclass);
if (!iter.hasNext()) {
Log.info("Skipping class for which we have no components " +
"[class=" + cclass + "].");
continue;
}
classes.add(cclass);
}
// select the components
int size = classes.size();
int components[] = new int[size];
for (int ii = 0; ii < size; ii++) {
ComponentClass cclass = (ComponentClass)classes.get(ii);
// get the components available for this class
ArrayList choices = new ArrayList();
Iterator iter = crepo.enumerateComponentIds(cclass);
CollectionUtil.addAll(choices, iter);
// choose a random component
if (choices.size() > 0) {
int idx = RandomUtil.getInt(choices.size());
components[ii] = ((Integer)choices.get(idx)).intValue();
} else {
Log.info("Have no components in class [class=" + cclass + "].");
}
}
return new CharacterDescriptor(components, null);
}
protected static final String[] CLASSES = {
"legs", "feet", "hand_left", "hand_right", "torso",
"head", "hair", "hat", "eyepatch" };
}
-251
View File
@@ -1,251 +0,0 @@
//
// $Id$
//
// 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.geom;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
/**
* General geometry utilites.
*/
public class GeomUtil
{
/**
* Computes and returns the dot product of the two vectors.
*
* @param v1s the starting point of the first vector.
* @param v1e the ending point of the first vector.
* @param v2s the starting point of the second vector.
* @param v2e the ending point of the second vector.
*/
public static int dot (Point v1s, Point v1e, Point v2s, Point v2e)
{
return ((v1e.x - v1s.x) * (v2e.x - v2s.x) +
(v1e.y - v1s.y) * (v2e.y - v2s.y));
}
/**
* Computes and returns the dot product of the two vectors. See
* {@link #dot(Point,Point,Point,Point)} for an explanation of the
* arguments
*/
public static int dot (int v1sx, int v1sy, int v1ex, int v1ey,
int v2sx, int v2sy, int v2ex, int v2ey)
{
return ((v1ex - v1sx) * (v2ex - v2sx) + (v1ey - v1sy) * (v2ey - v2sy));
}
/**
* Computes and returns the dot product of the two vectors. The
* vectors are assumed to start with the same coordinate and end with
* different coordinates.
*
* @param vs the starting point of both vectors.
* @param v1e the ending point of the first vector.
* @param v2e the ending point of the second vector.
*/
public static int dot (Point vs, Point v1e, Point v2e)
{
return ((v1e.x - vs.x) * (v2e.x - vs.x) +
(v1e.y - vs.y) * (v2e.y - vs.y));
}
/**
* Computes and returns the dot product of the two vectors.
* See {@link #dot(Point,Point,Point)} for an explanation of the
* arguments
*/
public static int dot (int vsx, int vsy, int v1ex, int v1ey,
int v2ex, int v2ey)
{
return ((v1ex - vsx) * (v2ex - vsx) + (v1ey - vsy) * (v2ey - vsy));
}
/**
* Computes the point nearest to the specified point <code>p3</code>
* on the line defined by the two points <code>p1</code> and
* <code>p2</code>. The computed point is stored into <code>n</code>.
* <em>Note:</em> <code>p1</code> and <code>p2</code> must not be
* coincident.
*
* @param p1 one point on the line.
* @param p2 another point on the line (not equal to <code>p1</code>).
* @param p3 the point to which we wish to be most near.
* @param n the point on the line defined by <code>p1</code> and
* <code>p2</code> that is nearest to <code>p</code>.
*
* @return the point object supplied via <code>n</code>.
*/
public static Point nearestToLine (Point p1, Point p2, Point p3, Point n)
{
// see http://astronomy.swin.edu.au/~pbourke/geometry/pointline/
// for a (not very good) explanation of the math
int Ax = p2.x - p1.x, Ay = p2.y - p1.y;
float u = (p3.x - p1.x) * Ax + (p3.y - p1.y) * Ay;
u /= (Ax * Ax + Ay * Ay);
n.x = p1.x + Math.round(Ax * u);
n.y = p1.y + Math.round(Ay * u);
return n;
}
/**
* Calculate the intersection of two lines. Either line may be
* considered as a line segment, and the intersecting point
* is only considered valid if it lies upon the segment.
* Note that Point extends Point2D.
*
* @param p1 and p2 the coordinates of the first line.
* @param seg1 if the first line should be considered a segment.
* @param p3 and p4 the coordinates of the second line.
* @param seg2 if the second line should be considered a segment.
* @param result the point that will be filled in with the intersecting
* point.
*
* @return true if result was filled in, or false if the lines
* are parallel or the point of intersection lies outside of a
* segment.
*/
public static boolean lineIntersection (
Point2D p1, Point2D p2, boolean seg1,
Point2D p3, Point2D p4, boolean seg2, Point2D result)
{
// see http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/
double y43 = p4.getY() - p3.getY();
double x21 = p2.getX() - p1.getX();
double x43 = p4.getX() - p3.getX();
double y21 = p2.getY() - p1.getY();
double denom = y43 * x21 - x43 * y21;
if (denom == 0) {
return false;
}
double y13 = p1.getY() - p3.getY();
double x13 = p1.getX() - p3.getX();
double ua = (x43 * y13 - y43 * x13) / denom;
if (seg1 && ((ua < 0) || (ua > 1))) {
return false;
}
if (seg2) {
double ub = (x21 * y13 - y21 * x13) / denom;
if ((ub < 0) || (ub > 1)) {
return false;
}
}
double x = p1.getX() + ua * x21;
double y = p1.getY() + ua * y21;
result.setLocation(x, y);
return true;
}
/**
* Returns less than zero if <code>p2</code> is on the left hand side
* of the line created by <code>p1</code> and <code>theta</code> and
* greater than zero if it is on the right hand side. In theory, it
* will return zero if the point is on the line, but due to rounding
* errors it almost always decides that it's not exactly on the line.
*
* @param p1 the point on the line whose side we're checking.
* @param theta the (logical) angle defining the line.
* @param p2 the point that lies on one side or the other of the line.
*/
public static int whichSide (Point p1, double theta, Point p2)
{
// obtain the point defining the right hand normal (N)
theta += Math.PI/2;
int x = p1.x + (int)Math.round(1000*Math.cos(theta)),
y = p1.y + (int)Math.round(1000*Math.sin(theta));
// now dot the vector from p1->p2 with the vector from p1->N, if
// it's positive, we're on the right hand side, if it's negative
// we're on the left hand side and if it's zero, we're on the line
return dot(p1.x, p1.y, p2.x, p2.y, x, y);
}
/**
* Shifts the position of the <code>tainer</code> rectangle to ensure
* that it contains the <code>tained</code> rectangle. The
* <code>tainer</code> rectangle must be larger than or equal to the
* size of the <code>tained</code> rectangle.
*/
public static void shiftToContain (Rectangle tainer, Rectangle tained)
{
if (tained.x < tainer.x) {
tainer.x = tained.x;
}
if (tained.y < tainer.y) {
tainer.y = tained.y;
}
if (tained.x + tained.width > tainer.x + tainer.width) {
tainer.x = tained.x - (tainer.width - tained.width);
}
if (tained.y + tained.height > tainer.y + tainer.height) {
tainer.y = tained.y - (tainer.height - tained.height);
}
}
/**
* Adds the target rectangle to the bounds of the source rectangle. If
* the source rectangle is null, a new rectangle is created that is the
* size of the target rectangle.
*
* @return the source rectangle.
*/
public static Rectangle grow (Rectangle source, Rectangle target)
{
if (target == null) {
Log.warning("Can't grow with null rectangle [src=" + source +
", tgt=" + target + "].");
Thread.dumpStack();
} else if (source == null) {
source = new Rectangle(target);
} else {
source.add(target);
}
return source;
}
/**
* Returns the rectangle containing the specified tile in the supplied
* larger rectangle. Tiles go from left to right, top to bottom.
*/
public static Rectangle getTile (
int width, int height, int tileWidth, int tileHeight, int tileIndex)
{
// figure out from whence to crop the tile
int tilesPerRow = width / tileWidth;
// if we got a bogus region, return bogus tile bounds
if (tilesPerRow == 0) {
return new Rectangle(0, 0, width, height);
}
int row = tileIndex / tilesPerRow;
int col = tileIndex % tilesPerRow;
// crop the tile-sized image chunk from the full image
return new Rectangle(
tileWidth*col, tileHeight*row, tileWidth, tileHeight);
}
}
-63
View File
@@ -1,63 +0,0 @@
//
// $Id$
//
// 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.geom;
/**
* A placeholder class that contains a reference to the log object used by
* this package.
*/
public class Log
{
public static final String PACKAGE = "geom";
public static com.samskivert.util.Log log =
new com.samskivert.util.Log(PACKAGE);
/** 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);
}
public static int getLevel ()
{
return com.samskivert.util.Log.getLevel(PACKAGE);
}
}
-582
View File
@@ -1,582 +0,0 @@
//
// $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;
}
@@ -1,184 +0,0 @@
//
// $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;
}
@@ -1,59 +0,0 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme;
import com.jme.input.InputHandler;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.system.DisplaySystem;
import com.jmex.bui.BRootNode;
import com.threerings.jme.camera.CameraHandler;
/**
* Provides access to the various bits needed by things that operate in
* JME land.
*/
public interface JmeContext
{
/** Returns the display to which we are rendering. */
public DisplaySystem getDisplay ();
/** Returns the renderer being used to draw everything. */
public Renderer getRenderer ();
/** Returns the handler for the camera being used to view the scene. */
public CameraHandler getCameraHandler ();
/** Returns the main geometry of our scene graph. */
public Node getGeometry ();
/** Returns the main interface node of our scene graph. */
public Node getInterface ();
/** Returns our main input handler. */
public InputHandler getInputHandler ();
/** Returns our UI root node. */
public BRootNode getRootNode ();
}
-56
View File
@@ -1,56 +0,0 @@
//
// $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);
}
}
@@ -1,83 +0,0 @@
//
// $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";
}
@@ -1,412 +0,0 @@
//
// $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);
}
@@ -1,69 +0,0 @@
//
// $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;
}
@@ -1,191 +0,0 @@
//
// $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;
}
@@ -1,87 +0,0 @@
//
// $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;
}
@@ -1,113 +0,0 @@
//
// $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;
}
@@ -1,168 +0,0 @@
//
// $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.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -1,181 +0,0 @@
//
// $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;
}
@@ -1,148 +0,0 @@
//
// $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;
}
@@ -1,127 +0,0 @@
//
// $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;
}
@@ -1,79 +0,0 @@
//
// $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
@@ -1,167 +0,0 @@
//
// $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;
}
@@ -1,557 +0,0 @@
//
// $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;
}
@@ -1,407 +0,0 @@
//
// $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;
}
@@ -1,115 +0,0 @@
//
// $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);
}
@@ -1,128 +0,0 @@
//
// $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;
}
@@ -1,495 +0,0 @@
//
// $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;
}
@@ -1,39 +0,0 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import com.jme.scene.state.TextureState;
/**
* Provides a means for models to resolve their texture references.
*/
public interface TextureProvider
{
/**
* Returns a texture state containing the named texture.
*
* @param name the name of the texture, which will be interpreted as an
* absolute resource path if it starts with a forward slash; otherwise,
* as a relative resource path
*/
public TextureState getTexture (String name);
}
-15
View File
@@ -1,15 +0,0 @@
<!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>
@@ -1,92 +0,0 @@
//
// $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);
}
@@ -1,60 +0,0 @@
//
// $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);
}
@@ -1,103 +0,0 @@
//
// $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();
}
@@ -1,70 +0,0 @@
//
// $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();
}
@@ -1,51 +0,0 @@
//
// $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;
}
@@ -1,44 +0,0 @@
//
// $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);
}
@@ -1,95 +0,0 @@
//
// $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();
}
@@ -1,244 +0,0 @@
//
// $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;
}
@@ -1,29 +0,0 @@
//
// $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
{
}
@@ -1,160 +0,0 @@
//
// $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;
}
@@ -1,404 +0,0 @@
//
// $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;
}
@@ -1,76 +0,0 @@
//
// $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;
}
@@ -1,84 +0,0 @@
//
// $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");
}
}
@@ -1,165 +0,0 @@
//
// $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;
}
}
@@ -1,167 +0,0 @@
//
// $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;
}
@@ -1,86 +0,0 @@
//
// $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;
}
@@ -1,139 +0,0 @@
//
// $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();
}
@@ -1,77 +0,0 @@
//
// $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>();
}
@@ -1,101 +0,0 @@
//
// $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);
}
}
}
@@ -1,135 +0,0 @@
//
// $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();
}
@@ -1,573 +0,0 @@
//
// $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
@@ -1,88 +0,0 @@
//
// $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;
}
@@ -1,109 +0,0 @@
//
// $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;
}
@@ -1,42 +0,0 @@
//
// $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;
}
@@ -1,99 +0,0 @@
//
// $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;
}
@@ -1,332 +0,0 @@
//
// $Id$
//
// 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.media;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import com.samskivert.util.ObserverList;
import com.samskivert.util.StringUtil;
/**
* Something that can be rendered on the media panel.
*/
public abstract class AbstractMedia
implements Shape
{
/** A {@link #_renderOrder} value at or above which, indicates that this
* media is in the HUD (heads up display) and should not scroll when the
* view scrolls. */
public static final int HUD_LAYER = 65536;
/**
* Instantiate an abstract media object.
*/
public AbstractMedia (Rectangle bounds)
{
_bounds = bounds;
}
/**
* Called periodically by this media's manager to give it
* a chance to do its thing.
*
* @param tickStamp a time stamp associated with this tick.
* <em>Note:</em> this is not obtained from a call to {@link
* System#currentTimeMillis} and cannot be compared to timestamps
* obtained there from.
*/
public abstract void tick (long tickStamp);
/**
* Called by the appropriate manager to request that the
* media render itself with the given graphics context. The
* media may wish to inspect the clipping region that has been set
* on the graphics context to render itself more efficiently. This
* method will only be called after it has been established that this
* media's bounds intersect the clipping region.
*/
public abstract void paint (Graphics2D gfx);
/**
* Called when the appropriate media manager has been paused for some
* length of time and is then unpaused. Media should adjust any time stamps
* that are maintained internally forward by the delta so that time
* maintains the illusion of flowing smoothly forward.
*/
public void fastForward (long timeDelta)
{
// adjust our first tick stamp
_firstTick += timeDelta;
}
/**
* Invalidate the media's bounding rectangle for later painting.
*/
public void invalidate ()
{
if (_mgr != null) {
_mgr.getRegionManager().invalidateRegion(_bounds);
}
}
/**
* Set the location.
*/
public void setLocation (int x, int y)
{
_bounds.x = x;
_bounds.y = y;
}
/**
* Returns a rectangle containing all the pixels rendered by this media.
*/
public Rectangle getBounds ()
{
return _bounds;
}
// documentation inherited from interface Shape
public Rectangle2D getBounds2D ()
{
return _bounds;
}
// documentation inherited from interface Shape
public boolean contains (double x, double y)
{
return _bounds.contains(x, y);
}
// documentation inherited from interface Shape
public boolean contains (Point2D p)
{
return _bounds.contains(p);
}
// documentation inherited from interface Shape
public boolean intersects (double x, double y, double w, double h)
{
return _bounds.intersects(x, y, w, h);
}
// documentation inherited from interface Shape
public boolean intersects (Rectangle2D r)
{
return _bounds.intersects(r);
}
// documentation inherited from interface Shape
public boolean contains (double x, double y, double w, double h)
{
return _bounds.contains(x, y, w, h);
}
// documentation inherited from interface Shape
public boolean contains (Rectangle2D r)
{
return _bounds.contains(r);
}
// documentation inherited from interface Shape
public PathIterator getPathIterator (AffineTransform at)
{
return _bounds.getPathIterator(at);
}
// documentation inherited from interface Shape
public PathIterator getPathIterator (AffineTransform at, double flatness)
{
return _bounds.getPathIterator(at, flatness);
}
/**
* Sets the render order associated with this media. Media
* can be rendered in two layers; those with negative render order and
* those with positive render order. In the same layer, they
* will be rendered according to their render order's cardinal value
* (least to greatest). Those with the same render order value will be
* rendered in arbitrary order.
*
* <p>This method may not be called during a tick.
*
* @see #HUD_LAYER
*/
public void setRenderOrder (int renderOrder)
{
if (_renderOrder != renderOrder) {
_renderOrder = renderOrder;
if (_mgr != null) {
_mgr.renderOrderDidChange(this);
invalidate();
}
}
}
/**
* Returns the render order of this media element.
*/
public int getRenderOrder ()
{
return _renderOrder;
}
/**
* Queues the supplied notification up to be dispatched to this
* abstract media's observers.
*/
public void queueNotification (ObserverList.ObserverOp amop)
{
if (_observers != null) {
if (_mgr != null) {
_mgr.queueNotification(_observers, amop);
} else {
Log.warning("Have no manager, dropping notification " +
"[media=" + this + ", op=" + amop + "].");
}
}
}
/**
* Called by the {@link AbstractMediaManager} when we are in a
* {@link VirtualMediaPanel} that just scrolled.
*/
public void viewLocationDidChange (int dx, int dy)
{
if (_renderOrder >= HUD_LAYER) {
setLocation(_bounds.x + dx, _bounds.y + dy);
}
}
/**
* Dumps this media to a String object.
*/
public String toString ()
{
StringBuilder buf = new StringBuilder();
buf.append(StringUtil.shortClassName(this));
buf.append("[");
toString(buf);
return buf.append("]").toString();
}
/**
* Initialize the media.
*/
protected final void init (AbstractMediaManager manager)
{
_mgr = manager;
init();
}
/**
* Called when the media has had its manager set.
* Derived classes may override this method, but should be sure to
* call <code>super.init()</code>.
*/
protected void init ()
{
}
/**
* Prior to the first call to {@link #tick} on an abstract media, this
* method is called by the {@link AbstractMediaManager}. It is called
* during the normal tick cycle, immediately prior to the first call
* to {@link #tick}.
*
* <p><em>Note:</em> It is imperative that
* <code>super.willStart()</code> is called by any entity that
* overrides this method because the {@link AbstractMediaManager}
* depends on the setting of the {@link #_firstTick} value to know
* whether or not to call this method.
*/
protected void willStart (long tickStamp)
{
_firstTick = tickStamp;
}
/**
* Called by the media manager after the media is removed from service.
* Derived classes may override this method, but should be sure to
* call <code>super.shutdown()</code>.
*/
protected void shutdown ()
{
invalidate();
_mgr = null;
}
/**
* Add the specified observer to this media element.
*/
protected void addObserver (Object obs)
{
if (_observers == null) {
_observers = new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
}
_observers.add(obs);
}
/**
* Remove the specified observer from this media element.
*/
protected void removeObserver (Object obs)
{
if (_observers != null) {
_observers.remove(obs);
}
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific information to the string buffer.
*/
protected void toString (StringBuilder buf)
{
buf.append("bounds=").append(StringUtil.toString(_bounds));
buf.append(", renderOrder=").append(_renderOrder);
}
/** The layer in which to render. */
protected int _renderOrder = 0;
/** The bounds of the media's rendering area. */
protected Rectangle _bounds;
/** Our manager. */
protected AbstractMediaManager _mgr;
/** Our observers. */
protected ObserverList _observers = null;
/** The tick stamp associated with our first call to {@link #tick}.
* This is set up automatically in {@link #willStart}. */
protected long _firstTick;
}
@@ -1,335 +0,0 @@
//
// $Id$
//
// 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.media;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.Comparator;
import com.samskivert.util.ObserverList.ObserverOp;
import com.samskivert.util.ObserverList;
import com.samskivert.util.SortableArrayList;
import com.samskivert.util.Tuple;
/**
* Manages, ticks, and paints {@link AbstractMedia}.
*/
public abstract class AbstractMediaManager
implements MediaConstants
{
/**
* Default constructor.
*/
public AbstractMediaManager (MediaPanel panel)
{
_panel = panel;
_remgr = panel.getRegionManager();
}
/**
* Returns region manager in use by this manager.
*/
public RegionManager getRegionManager ()
{
return _remgr;
}
/**
* Returns the media panel with which we are coordinating.
*/
public MediaPanel getMediaPanel ()
{
return _panel;
}
/**
* Must be called every frame so that the media can be properly
* updated.
*/
public void tick (long tickStamp)
{
_tickStamp = tickStamp;
tickAllMedia(tickStamp);
dispatchNotifications();
// we clear our tick stamp when we're about to be painted, this
// lets us handle situations when yet more media is slipped in
// between our being ticked and our being painted
}
/**
* This will always be called prior to the {@link #paint} calls for a
* particular tick. Because it is possible that there will be no dirty
* regions and thus no calls to {@link #paint} this method exists so
* that the media manager can guarantee that it will be notified when
* all ticking is complete and the painting phase has begun.
*/
public void willPaint ()
{
// now that we're done ticking, we can safely clear this
_tickStamp = 0;
}
/**
* Renders all registered media in the given layer that intersect
* the supplied clipping rectangle to the given graphics context.
*
* @param layer the layer to render; one of {@link #FRONT}, {@link
* #BACK}, or {@link #ALL}. The front layer contains all animations
* with a positive render order; the back layer contains all
* animations with a negative render order; all, both.
*/
public void paint (Graphics2D gfx, int layer, Shape clip)
{
for (int ii = 0, nn = _media.size(); ii < nn; ii++) {
AbstractMedia media = (AbstractMedia)_media.get(ii);
int order = media.getRenderOrder();
try {
if (((layer == ALL) ||
(layer == FRONT && order >= 0) ||
(layer == BACK && order < 0)) &&
clip.intersects(media.getBounds())) {
media.paint(gfx);
}
} catch (Exception e) {
Log.warning("Failed to render media " +
"[media=" + media + ", e=" + e + "].");
Log.logStackTrace(e);
}
}
}
/**
* If the manager is paused for some length of time, it should
* be fast forwarded by the appropriate number of milliseconds. This
* allows media to smoothly pick up where they left off rather than
* abruptly jumping into the future, thinking that some outrageous
* amount of time passed since their last tick.
*/
public void fastForward (long timeDelta)
{
if (_tickStamp > 0) {
Log.warning("Egads! Asked to fastForward() during a tick.");
Thread.dumpStack();
}
for (int ii=0, nn=_media.size(); ii < nn; ii++) {
((AbstractMedia) _media.get(ii)).fastForward(timeDelta);
}
}
/**
* Returns true if the specified media is being managed by this media
* manager.
*/
public boolean isManaged (AbstractMedia media)
{
return _media.contains(media);
}
/**
* Called by a {@link VirtualMediaPanel} when the view that contains our
* media is scrolled.
*
* @param dx the scrolled distance in the x direction (in pixels).
* @param dy the scrolled distance in the y direction (in pixels).
*/
public void viewLocationDidChange (int dx, int dy)
{
// let our media know
for (int ii = 0, ll = _media.size(); ii < ll; ii++) {
((AbstractMedia)_media.get(ii)).viewLocationDidChange(dx, dy);
}
}
/**
* Called by a {@link AbstractMedia} when its render order has changed.
*/
public void renderOrderDidChange (AbstractMedia media)
{
if (_tickStamp > 0) {
Log.warning("Egads! Render order changed during a tick.");
Thread.dumpStack();
}
_media.remove(media);
_media.insertSorted(media, RENDER_ORDER);
}
/**
* Calls {@link AbstractMedia#tick} on all media to give them a chance
* to move about, change their look, generate dirty regions, and so
* forth.
*/
protected void tickAllMedia (long tickStamp)
{
// we use _tickpos so that it can be adjusted if media is added or
// removed during the tick dispatch
for (_tickpos = 0; _tickpos < _media.size(); _tickpos++) {
tickMedia((AbstractMedia) _media.get(_tickpos), tickStamp);
}
_tickpos = -1;
}
/**
* Inserts the specified media into this manager, return true on
* success.
*/
protected boolean insertMedia (AbstractMedia media)
{
if (_media.contains(media)) {
Log.warning("Attempt to insert media more than once " +
"[media=" + media + "].");
Thread.dumpStack();
return false;
}
media.init(this);
int ipos = _media.insertSorted(media, RENDER_ORDER);
// if we've started our tick but have not yet painted our media,
// we need to take care that this newly added media will be ticked
// before our upcoming render
if (_tickStamp > 0L) {
if (_tickpos == -1) {
// if we're done with our own call to tick(), we
// definitely need to tick this new media
tickMedia(media, _tickStamp);
} else if (ipos <= _tickpos) {
// otherwise, we're in the middle of our call to tick()
// and we only need to tick this guy if he's being
// inserted before our current tick position (if he's
// inserted after our current position, we'll get to him
// as part of this tick iteration)
_tickpos++;
tickMedia(media, _tickStamp);
}
}
return true;
}
/** A helper function used to call {@link AbstractMedia#tick}. */
protected final void tickMedia (AbstractMedia media, long tickStamp)
{
if (media._firstTick == 0L) {
media.willStart(tickStamp);
}
media.tick(tickStamp);
}
/**
* Removes the specified media from this manager, return true on
* success.
*/
protected boolean removeMedia (AbstractMedia media)
{
int mpos = _media.indexOf(media);
if (mpos != -1) {
_media.remove(mpos);
media.invalidate();
media.shutdown();
// if we're in the middle of ticking, we need to adjust the
// _tickpos if necessary
if (mpos <= _tickpos) {
_tickpos--;
}
return true;
}
Log.warning("Attempt to remove media that wasn't inserted " +
"[media=" + media + "].");
return false;
}
/**
* Clears all media from the manager and calls {@link
* AbstractMedia#shutdown} on each. This does not invalidate the
* media's vacated bounds; it is assumed that it will be ok.
*/
protected void clearMedia ()
{
if (_tickStamp > 0) {
Log.warning("Egads! Requested to clearMedia() during a tick.");
Thread.dumpStack();
}
for (int ii=_media.size() - 1; ii >= 0; ii--) {
AbstractMedia media = (AbstractMedia) _media.remove(ii);
media.shutdown();
}
}
/**
* Queues the notification for dispatching after we've ticked all the
* media.
*/
public void queueNotification (ObserverList observers, ObserverOp event)
{
_notify.add(new Tuple(observers, event));
}
/**
* Dispatches all queued media notifications.
*/
protected void dispatchNotifications ()
{
for (int ii = 0, nn = _notify.size(); ii < nn; ii++) {
Tuple tuple = (Tuple)_notify.get(ii);
((ObserverList)tuple.left).apply((ObserverOp)tuple.right);
}
_notify.clear();
}
/** The media panel we're working with. */
protected MediaPanel _panel;
/** The region manager. */
protected RegionManager _remgr;
/** List of observers to notify at the end of the tick. */
protected ArrayList _notify = new ArrayList();
/** Our render-order sorted list of media. */
protected SortableArrayList _media = new SortableArrayList();
/** The position in our media list that we're ticking in the middle of
* a call to {@link #tick} otherwise -1. */
protected int _tickpos = -1;
/** The tick stamp if the manager is in the midst of a call to {@link
* #tick}, else <code>0</code>. */
protected long _tickStamp;
/** Used to sort media by render order. */
protected static final Comparator RENDER_ORDER = new Comparator() {
public int compare (Object o1, Object o2) {
int result = (((AbstractMedia)o1)._renderOrder -
((AbstractMedia)o2)._renderOrder);
return (result != 0) ? result :
// find some other way to keep them stable relative to
// each other
o1.hashCode() - o2.hashCode();
}
};
}
@@ -1,502 +0,0 @@
//
// $Id$
//
// 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.media;
import java.applet.Applet;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Window;
import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import com.samskivert.util.ListUtil;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
/**
* Used to get Swing's repainting to jive with our active rendering
* strategy.
*
* @see FrameManager
*/
public class ActiveRepaintManager extends RepaintManager
{
/**
* Components that are rooted in this component (which must be a {@link
* Window} or an {@link Applet}) will be rendered into the offscreen buffer
* managed by the frame manager. Other components will be rendered into
* separate offscreen buffers and repainted in the normal Swing manner.
*/
public ActiveRepaintManager (Component root)
{
_root = root;
}
// documentation inherited
public synchronized void addInvalidComponent (JComponent comp)
{
Component vroot = null;
if (DEBUG) {
Log.info("Maybe invalidating " + toString(comp) + ".");
}
// locate the validation root for this component
for (Component c = comp; c != null; c = c.getParent()) {
// if the component is not part of an active widget hierarcy,
// we can stop now; if the component is a cell render pane,
// we're apparently supposed to ignore it as wel
if (!c.isDisplayable() || c instanceof CellRendererPane) {
return;
}
// skip non-Swing components
if (!(c instanceof JComponent)) {
continue;
}
// if we find our validate root, we can stop looking; NOTE:
// JTextField incorrectly claims to be a validate root thereby
// fucking up the program something serious; we jovially
// ignore it's claims here and restore order to the universe;
// see bug #403550 for more fallout from Sun's fuckup
if (!(c instanceof JTextField) &&
!(c instanceof JScrollPane) &&
((JComponent)c).isValidateRoot()) {
vroot = c;
break;
}
}
// if we found no validation root we can abort as this component
// is not part of any valid widget hierarchy
if (vroot == null) {
if (DEBUG) {
Log.info("Skipping vrootless component: " + toString(comp));
}
return;
}
// make sure that the component is actually in a window or applet
// that is showing
if (getRoot(vroot) == null) {
if (DEBUG) {
Log.info("Skipping rootless component " +
"[comp=" + toString(comp) +
", vroot=" + toString(vroot) + "].");
}
return;
}
// add the invalid component to our list and we'll validate it on
// the next frame
if (!ListUtil.containsRef(_invalid, vroot)) {
if (DEBUG) {
Log.info("Invalidating " + toString(vroot) + ".");
}
_invalid = ListUtil.add(_invalid, vroot);
// on the mac, components frequently do not repaint themselves
// after being invalidated so we have to force a repaint from
// the validation roon on down
if (RunAnywhere.isMacOS() && vroot instanceof JComponent) {
addDirtyRegion((JComponent)vroot, 0, 0,
vroot.getWidth(), vroot.getHeight());
}
}
}
// documentation inherited
public synchronized void addDirtyRegion (
JComponent comp, int x, int y, int width, int height)
{
// ignore invalid requests
if ((width <= 0) || (height <= 0) || (comp == null) ||
(comp.getWidth() <= 0) || (comp.getHeight() <= 0)) {
// Log.info("Skipping bogus region " + comp.getClass().getName() +
// ", x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ".");
return;
}
// if this component is already dirty, simply expand their
// existing dirty rectangle
Rectangle drect = (Rectangle)_dirty.get(comp);
if (drect != null) {
drect.add(x, y);
drect.add(x+width, y+height);
return;
}
// make sure this component has a valid root
if (getRoot(comp) == null) {
// Log.info("Skipping rootless repaint " + comp + ".");
return;
}
if (DEBUG) {
Log.info("Dirtying component [comp=" + toString(comp) +
", drect=" + StringUtil.toString(
new Rectangle(x, y, width, height)) + "].");
}
// if we made it this far, we can queue up a dirty region for this
// component to be repainted on the next tick
_dirty.put(comp, new Rectangle(x, y, width, height));
}
/**
* Returns the root component for the supplied component or null if it
* is not part of a rooted hierarchy or if any parent along the way is
* found to be hidden or without a peer.
*/
protected Component getRoot (Component comp)
{
for (Component c = comp; c != null; c = c.getParent()) {
boolean hidden = !c.isDisplayable();
// on the mac, the JRootPane is invalidated before it is
// visible and is never again invalidated or repainted, so we
// punt and allow all invisible components to be invalidated
// and revalidated
if (!RunAnywhere.isMacOS()) {
hidden |= !c.isVisible();
}
if (hidden) {
return null;
}
if (c instanceof Window || c instanceof Applet) {
return c;
}
}
return null;
}
// documentation inherited
public synchronized Rectangle getDirtyRegion (JComponent comp)
{
Rectangle drect = (Rectangle)_dirty.get(comp);
// copy the rectangle if we found one, otherwise create an empty
// rectangle because we don't want them leaving empty handed
return (drect == null) ?
new Rectangle(0, 0, 0, 0) : new Rectangle(drect);
}
// documentation inherited
public synchronized void markCompletelyClean (JComponent comp)
{
_dirty.remove(comp);
}
/**
* Validates the invalid components that have been queued up since the
* last frame tick.
*/
public void validateComponents ()
{
// swap out our invalid array
Object[] invalid = null;
synchronized (this) {
invalid = _invalid;
_invalid = null;
}
// if there's nothing to validate, we're home free
if (invalid == null) {
return;
}
// validate everything therein
int icount = invalid.length;
for (int ii = 0; ii < icount; ii++) {
if (invalid[ii] != null) {
if (DEBUG) {
Log.info("Validating " + invalid[ii]);
}
((Component)invalid[ii]).validate();
}
}
}
/**
* Paints the components that have become dirty since the last tick.
*
* @return true if any components were painted.
*/
public boolean paintComponents (Graphics g, FrameManager fmgr)
{
synchronized (this) {
// exit now if there are no dirty rectangles to paint
if (_dirty.isEmpty()) {
return false;
}
// otherwise, swap our hashmaps
HashMap tmap = _spare;
_spare = _dirty;
_dirty = tmap;
}
// scan through the list, looking for components for whom a parent
// component is also dirty. in such a case, the dirty rectangle
// for the parent component is expanded to contain the dirty
// rectangle of the child and the child is removed from the
// repaint list (painting the parent will repaint the child)
Iterator iter = _spare.entrySet().iterator();
PRUNE:
while (iter.hasNext()) {
Entry entry = (Entry)iter.next();
JComponent comp = (JComponent)entry.getKey();
Rectangle drect = (Rectangle)entry.getValue();
int x = comp.getX() + drect.x, y = comp.getY() + drect.y;
// climb up the parent hierarchy, looking for the first opaque
// parent as well as the root component
for (Component c = comp.getParent(); c != null; c = c.getParent()) {
// stop looking for combinable parents for non-visible or
// non-JComponents
if (!c.isVisible() || !c.isDisplayable() ||
!(c instanceof JComponent)) {
break;
}
// check to see if this parent is dirty
Rectangle prect = (Rectangle)_spare.get(c);
if (prect != null) {
// that we were going to merge it with its parent and
// blow it away
drect.x = x;
drect.y = y;
if (DEBUG) {
Log.info("Found dirty parent " +
"[comp=" + toString(comp) +
", drect=" + StringUtil.toString(drect) +
", pcomp=" + toString(c) +
", prect=" + StringUtil.toString(prect) +
"].");
}
prect.add(drect);
if (DEBUG) {
Log.info("New prect " + StringUtil.toString(prect));
}
// remove the child component and be on our way
iter.remove();
continue PRUNE;
}
// translate the coordinates into this component's
// coordinate system
x += c.getX();
y += c.getY();
}
}
// now paint each of the dirty components, by setting the clipping
// rectangle appropriately and calling paint() on the associated
// root component
iter = _spare.entrySet().iterator();
while (iter.hasNext()) {
Entry entry = (Entry)iter.next();
JComponent comp = (JComponent)entry.getKey();
Rectangle drect = (Rectangle)entry.getValue();
// get the root component, adjust the clipping (dirty)
// rectangle and obtain the bounds of the client in absolute
// coordinates
Component root = null, ocomp = null;
// start with the components bounds which we'll switch to the
// opaque parent component's bounds if and when we find one
_cbounds.setBounds(0, 0, comp.getWidth(), comp.getHeight());
// climb up the parent hierarchy, looking for the first opaque
// parent as well as the root component
for (Component c = comp; c != null; c = c.getParent()) {
if (!c.isVisible() || !c.isDisplayable()) {
break;
}
if (c instanceof JComponent) {
// make a note of the first opaque parent we find
if (ocomp == null && ((JComponent)c).isOpaque()) {
ocomp = c;
// we need to obtain the opaque parent's coordinates
// in the root coordinate system for when we repaint
_cbounds.setBounds(
0, 0, ocomp.getWidth(), ocomp.getHeight());
}
} else {
// oh god the hackery. apparently the fscking
// JEditorPane wraps a heavy weight component around
// every swing component it uses when doing forms
Component tp = c.getParent();
if (!(tp instanceof JEditorPane)) {
root = c;
break;
}
}
// translate the coordinates into this component's
// coordinate system
drect.x += c.getX();
drect.y += c.getY();
_cbounds.x += c.getX();
_cbounds.y += c.getY();
// clip the dirty region to the bounds of this component
SwingUtilities.computeIntersection(
c.getX(), c.getY(), c.getWidth(), c.getHeight(), drect);
}
// if we found no opaque parent, just paint the component
// itself (this seems to happen with the top-level layered
// pane)
if (ocomp == null) {
ocomp = comp;
}
// if this component is rooted in our frame, repaint it into
// the supplied graphics instance
if (root == _root) {
if (DEBUG) {
Log.info("Repainting [comp=" + toString(comp) +
StringUtil.toString(_cbounds) +
", ocomp=" + toString(ocomp) +
", drect=" + StringUtil.toString(drect) + "].");
}
g.setClip(drect);
g.translate(_cbounds.x, _cbounds.y);
try {
// some components are ill-behaved and may throw an
// exception while painting themselves, and so we
// needs must deal with these fellows gracefully
ocomp.paint(g);
} catch (Exception e) {
Log.warning("Exception while painting component " +
"[comp=" + ocomp + ", e=" + e + "].");
Log.logStackTrace(e);
}
g.translate(-_cbounds.x, -_cbounds.y);
// we also need to repaint any components in this layer
// that are above our freshly repainted component
fmgr.renderLayers((Graphics2D)g, ocomp, _cbounds, _clipped);
} else if (root != null) {
if (DEBUG) {
Log.info("Repainting old-school " +
"[comp=" + toString(comp) +
", ocomp=" + toString(ocomp) +
", root=" + toString(root) +
", bounds=" + StringUtil.toString(_cbounds) +
"].");
dumpHierarchy(comp);
}
// otherwise, repaint with standard swing double buffers
Image obuf = getOffscreenBuffer(
ocomp, _cbounds.width, _cbounds.height);
Graphics og = null, cg = null;
try {
og = obuf.getGraphics();
ocomp.paint(og);
cg = ocomp.getGraphics();
cg.drawImage(obuf, 0, 0, null);
} finally {
if (og != null) {
og.dispose();
}
if (cg != null) {
cg.dispose();
}
}
}
}
// clear out the mapping of dirty components
_spare.clear();
return true;
}
/**
* Used to dump a component when debugging.
*/
protected static String toString (Component comp)
{
return comp.getClass().getName() +
StringUtil.toString(comp.getBounds());
}
/**
* Dumps the containment hierarchy for the supplied component.
*/
protected static void dumpHierarchy (Component comp)
{
for (String indent = ""; comp != null; indent += " ") {
Log.info(indent + toString(comp));
comp = comp.getParent();
}
}
/** The root of our interface. */
protected Component _root;
/** A list of invalid components. */
protected Object[] _invalid;
/** A mapping of invalid rectangles for each widget that is dirty. */
protected HashMap _dirty = new HashMap();
/** A spare hashmap that we swap in while repainting dirty components
* in the old hashmap. */
protected HashMap _spare = new HashMap();
/** Used to compute dirty components' bounds. */
protected Rectangle _cbounds = new Rectangle();
/** Used when rendering "layered" components. */
protected boolean[] _clipped = new boolean[] { true };
/** We debug so much that we have to make it easy to enable and
* disable debug logging. Yay! */
protected static final boolean DEBUG = false;
}
@@ -1,148 +0,0 @@
//
// $Id$
//
// 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.media;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Rectangle;
import java.awt.image.VolatileImage;
/**
* A {@link FrameManager} extension that uses a volatile off-screen image
* to do its rendering.
*/
public class BackFrameManager extends FrameManager
{
// documentation inherited
protected void paint (long tickStamp)
{
// start out assuming we can do an incremental render
boolean incremental = true;
do {
GraphicsConfiguration gc = _window.getGraphicsConfiguration();
// create our off-screen buffer if necessary
if (_backimg == null || _backimg.getWidth() != _window.getWidth() ||
_backimg.getHeight() != _window.getHeight()) {
createBackBuffer(gc);
}
// make sure our back buffer hasn't disappeared
int valres = _backimg.validate(gc);
// if we've changed resolutions, recreate the buffer
if (valres == VolatileImage.IMAGE_INCOMPATIBLE) {
Log.info("Back buffer incompatible, recreating.");
createBackBuffer(gc);
}
// if the image wasn't A-OK, we need to rerender the whole
// business rather than just the dirty parts
if (valres != VolatileImage.IMAGE_OK) {
// Log.info("Lost back buffer, redrawing " + valres);
if (_bgfx != null) {
_bgfx.dispose();
}
_bgfx = (Graphics2D)_backimg.getGraphics();
if (_fgfx != null) {
_fgfx.dispose();
_fgfx = null;
}
incremental = false;
}
// dirty everything if we're not incrementally rendering
if (!incremental) {
_root.getRootPane().revalidate();
_root.getRootPane().repaint();
}
if (!paint(_bgfx)) {
return;
}
// we cache our frame's graphics object so that we can avoid
// instantiating a new one on every tick
if (_fgfx == null) {
_fgfx = (Graphics2D)_window.getGraphics();
}
_fgfx.drawImage(_backimg, 0, 0, null);
// if we loop through a second time, we'll need to rerender
// everything
incremental = false;
} while (_backimg.contentsLost());
}
// documentation inherited
protected void restoreFromBack (Rectangle dirty)
{
if (_fgfx == null || _backimg == null) {
return;
}
// Log.info("Restoring from back " + StringUtil.toString(dirty) + ".");
_fgfx.setClip(dirty);
_fgfx.drawImage(_backimg, 0, 0, null);
_fgfx.setClip(null);
}
/**
* Creates the off-screen buffer used to perform double buffered
* rendering of the animated panel.
*/
protected void createBackBuffer (GraphicsConfiguration gc)
{
// if we have an old image, clear it out
if (_backimg != null) {
_backimg.flush();
_bgfx.dispose();
}
// create the offscreen buffer
int width = _window.getWidth(), height = _window.getHeight();
_backimg = gc.createCompatibleVolatileImage(width, height);
// fill the back buffer with white
_bgfx = (Graphics2D)_backimg.getGraphics();
_bgfx.fillRect(0, 0, width, height);
// clear out our frame graphics in case that became invalid for
// the same reasons our back buffer became invalid
if (_fgfx != null) {
_fgfx.dispose();
_fgfx = null;
}
// Log.info("Created back buffer [" + width + "x" + height + "].");
}
/** The image used to render off-screen. */
protected VolatileImage _backimg;
/** The graphics object from our back buffer. */
protected Graphics2D _bgfx;
/** The graphics object from our frame. */
protected Graphics2D _fgfx;
}
@@ -1,101 +0,0 @@
//
// $Id$
//
// 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.media;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Graphics2D;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.image.BufferStrategy;
/**
* A {@link FrameManager} extension that uses a flip-buffer (via {@link
* BufferStrategy} to do its rendering.
*/
public class FlipFrameManager extends FrameManager
{
// documentation inherited
protected void paint (long tickStamp)
{
// create our buffer strategy if we don't already have one
if (_bufstrat == null) {
BufferCapabilities cap = new BufferCapabilities(
new ImageCapabilities(true), new ImageCapabilities(true),
BufferCapabilities.FlipContents.COPIED);
try {
_window.createBufferStrategy(2, cap);
} catch (AWTException ae) {
Log.warning("Failed creating flip bufstrat: " + ae + ".");
// fall back to one without custom capabilities
_window.createBufferStrategy(2);
}
_bufstrat = _window.getBufferStrategy();
}
// start out assuming we can do an incremental render
boolean incremental = true;
do {
Graphics2D gfx = null;
try {
gfx = (Graphics2D)_bufstrat.getDrawGraphics();
// dirty everything if we're not incrementally rendering
if (!incremental) {
Log.info("Doing non-incremental render; contents lost " +
"[lost=" + _bufstrat.contentsLost() +
", rest=" + _bufstrat.contentsRestored() + "].");
_root.getRootPane().revalidate();
_root.getRootPane().repaint();
}
// request to paint our participants and components and bail
// if they paint nothing
if (!paint(gfx)) {
return;
}
// flip our buffer to visible
_bufstrat.show();
// if we loop through a second time, we'll need to rerender
// everything
incremental = false;
} finally {
if (gfx != null) {
gfx.dispose();
}
}
} while (_bufstrat.contentsLost());
}
// documentation inherited
protected void restoreFromBack (Rectangle dirty)
{
// nothing doing
}
/** The buffer strategy used to do our rendering. */
protected BufferStrategy _bufstrat;
}
@@ -1,111 +0,0 @@
package com.threerings.media;
import java.awt.Component;
import com.threerings.media.FrameManager;
import com.threerings.media.FrameParticipant;
public abstract class FrameInterval
implements FrameParticipant
{
/**
* Constructor - registers the interval as a frame participant
*/
public FrameInterval (FrameManager mgr)
{
_mgr = mgr;
}
// documentation inhertied from FrameParticipant
public Component getComponent ()
{
return null;
}
// documentation inherited from FrameParticipant
public boolean needsPaint ()
{
return false;
}
// documentation inherited from FrameParticipant
public void tick (long tickStamp)
{
if (_nextTime == -1) {
// First time through
_nextTime = tickStamp + _initDelay;
} else if (tickStamp >= _nextTime) {
// If we're repeating, set the next time to run, otherwise, reset
if (_repeatDelay != 0L) {
_nextTime += _repeatDelay;
} else {
_nextTime = -1;
cancel();
}
expired();
}
}
/**
*
* The main method where your interval should do its work.
*
*/
public abstract void expired ();
/**
* Schedule the interval to execute once, after the specified delay.
* Supersedes any previous schedule that this Interval may have had.
*/
public final void schedule (long delay)
{
schedule(delay, 0L);
}
/**
* Schedule the interval to execute repeatedly, with the same delay.
* Supersedes any previous schedule that this Interval may have had.
*/
public final void schedule (long delay, boolean repeat)
{
schedule(delay, repeat ? delay : 0L);
}
/**
* Schedule the interval to execute repeatedly with the specified
* initial delay and repeat delay.
* Supersedes any previous schedule that this Interval may have had.
*/
public final void schedule (long initialDelay, long repeatDelay)
{
if (!_mgr.isRegisteredFrameParticipant(this)) {
_mgr.registerFrameParticipant(this);
}
_repeatDelay = repeatDelay;
_initDelay = initialDelay;
_nextTime = -1;
}
/**
* Cancel the current schedule, and ensure that any expirations that
* are queued up but have not yet run do not run.
*/
public final void cancel ()
{
_mgr.removeFrameParticipant(this);
}
/** Time of the next expiration. */
protected long _nextTime;
/** Time between expirations. */
protected long _repeatDelay;
/** Time between expirations. */
protected long _initDelay;
/** The context whose FrameManager we are using. */
protected FrameManager _mgr;
}
@@ -1,846 +0,0 @@
//
// $Id$
//
// 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.media;
import java.applet.Applet;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.EventQueue;
import javax.swing.JLayeredPane;
import javax.swing.RepaintManager;
import javax.swing.JRootPane;
import javax.swing.RootPaneContainer;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import com.samskivert.swing.RuntimeAdjust;
import com.samskivert.util.ListUtil;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
import com.threerings.media.timer.MediaTimer;
import com.threerings.media.timer.SystemMediaTimer;
import com.threerings.media.util.TrailingAverage;
import com.threerings.util.unsafe.Unsafe;
/**
* Provides a central point from which the computation for each "frame" or tick
* can be dispatched. This assumed that the application structures its activity
* around the rendering of each frame, which is a common architecture for
* games. The animation and sprite support provided by other classes in this
* package are structured for use in an application that uses a frame manager
* to tick everything once per frame.
*
* <p> The frame manager goes through a simple two part procedure every frame:
*
* <ul>
* <li> Ticking all of the frame participants: in {@link
* FrameParticipant#tick}, any processing that need be performed during this
* frame should be performed. Care should be taken not to execute code that
* will take unduly long, instead such processing should be broken up so that
* it can be performed in small pieces every frame (or performed on a separate
* thread with the results safely communicated back to the frame participants
* for incorporation into the rendering loop).
*
* <li> Painting the user interface hierarchy: the top-level component (the
* frame) is painted (via a call to {@link JRootPane#paint}) into a flip buffer
* (if supported, an off-screen buffer if not). Updates that were computed
* during the tick should be rendered in this call to paint. The paint call
* will propagate down to all components in the UI hierarchy, some of which may
* be {@link FrameParticipant}s and will have prepared themselves for their
* upcoming painting in the previous call to {@link
* FrameParticipant#tick}. When the call to paint completes, the flip buffer is
* flipped and the process starts all over again. </ul>
*
* <p> The ticking and rendering takes place on the AWT thread so as to
* avoid the need for complicated coordination between AWT event handler
* code and frame code. However, this means that all AWT (and Swing) event
* handlers <em>must not</em> perform any complicated processing. After
* each frame, control of the AWT thread is given back to the AWT which
* processes all pending AWT events before giving the frame manager an
* opportunity to process the next frame. Thus the convenience of
* everything running on the AWT thread comes with the price of requiring
* that AWT event handlers not block or perform any intensive processing.
* In general, this is a sensible structure for an application anyhow, so
* this organization tends to be preferable to an organization where the
* AWT and frame threads are separate and must tread lightly so as not to
* collide.
*
* <p> Note: the way that <code>JScrollPane</code> goes about improving
* performance when scrolling complicated contents cannot work with active
* rendering. If you use a <code>JScrollPane</code> in an application that
* uses the frame manager, you should either use the provided {@link
* SafeScrollPane} or set your scroll panes' viewports to
* <code>SIMPLE_SCROLL_MODE</code>.
*/
public abstract class FrameManager
{
/**
* Normally, the frame manager will repaint any component in a {@link
* JLayeredPane} layer (popups, overlays, etc.) that overlaps a frame
* participant on every tick because the frame participant <em>could</em>
* have changed underneath the overlay which would require that the overlay
* be repainted. If the application knows that the frame participant
* beneath the overlay will never change, it can have its overlay implement
* this interface and avoid the expense of forcibly fully repainting the
* overlay on every frame.
*/
public static interface SafeLayerComponent
{
}
/**
* Creates a frame manager that will use a {@link SystemMediaTimer} to
* obtain timing information, which is available on every platform, but
* returns inaccurate time stamps on many platforms.
*
* @see #newInstance(Window, RootPaneContainer, MediaTimer)
*/
public static FrameManager newInstance (
Window window, RootPaneContainer root)
{
// first try creating a PerfTimer which is the best if we're using
// JDK1.4.2
MediaTimer timer = null;
try {
timer = (MediaTimer)Class.forName(PERF_TIMER).newInstance();
} catch (Throwable t) {
Log.info("Can't use PerfTimer (" + t + ") reverting to " +
"System.currentTimeMillis() based timer.");
timer = new SystemMediaTimer();
}
return newInstance(window, root, timer);
}
/**
* Constructs a frame manager that will do its rendering to the
* supplied frame. It is likely that the caller will want to have put
* the frame into full-screen exclusive mode prior to providing it to
* the frame manager so that the frame manager can take advantage of
* optimizations available in that mode.
*
* @see GraphicsDevice#setFullScreenWindow
*/
public static FrameManager newInstance (
Window window, RootPaneContainer root, MediaTimer timer)
{
FrameManager fmgr;
if (_useFlip.getValue()) {
Log.info("Creating flip frame manager.");
fmgr = new FlipFrameManager();
} else {
Log.info("Creating back frame manager.");
fmgr = new BackFrameManager();
}
fmgr.init(window, root, timer);
return fmgr;
}
/**
* Initializes this frame manager and prepares it for operation.
*/
protected void init (
Window window, RootPaneContainer root, MediaTimer timer)
{
_window = window;
_root = root;
if (window instanceof ManagedJFrame) {
((ManagedJFrame)window).init(this);
}
_timer = timer;
// set up our custom repaint manager
_remgr = new ActiveRepaintManager(_window);
RepaintManager.setCurrentManager(_remgr);
// turn off double buffering for the whole business because we
// handle repaints
_remgr.setDoubleBufferingEnabled(false);
if (DEBUG_EVENTS) {
addTestListeners();
}
}
/**
* Adds a variety of listeners to the frame in order to provide
* visibility into the various events received by the frame.
*/
protected void addTestListeners ()
{
// add a test window listener
_window.addWindowListener(new WindowListener() {
public void windowActivated (WindowEvent e) {
Log.info("Window activated [evt=" + e + "].");
}
public void windowClosed (WindowEvent e) {
Log.info("Window closed [evt=" + e + "].");
}
public void windowClosing (WindowEvent e) {
Log.info("Window closing [evt=" + e + "].");
}
public void windowDeactivated (WindowEvent e) {
Log.info("Window deactivated [evt=" + e + "].");
}
public void windowDeiconified (WindowEvent e) {
Log.info("Window deiconified [evt=" + e + "].");
}
public void windowIconified (WindowEvent e) {
Log.info("Window iconified [evt=" + e + "].");
}
public void windowOpened (WindowEvent e) {
Log.info("Window opened [evt=" + e + "].");
}
});
// add a component listener
_window.addComponentListener(new ComponentListener() {
public void componentHidden (ComponentEvent e) {
Log.info("Window component hidden [evt=" + e + "].");
}
public void componentShown (ComponentEvent e) {
Log.info("Window component shown [evt=" + e + "].");
}
public void componentMoved (ComponentEvent e) {
Log.info("Window component moved [evt=" + e + "].");
}
public void componentResized (ComponentEvent e) {
Log.info("Window component resized [evt=" + e + "].");
}
});
// add test ancestor focus listener
_root.getRootPane().addAncestorListener(
new AncestorListener() {
public void ancestorAdded (AncestorEvent e) {
Log.info("Root pane ancestor added [e=" + e + "].");
}
public void ancestorRemoved (AncestorEvent e) {
Log.info("Root pane ancestor removed [e=" + e + "].");
}
public void ancestorMoved (AncestorEvent e) {
Log.info("Root pane ancestor moved [e=" + e + "].");
}
});
// add test key event dispatcher
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addKeyEventDispatcher(new KeyEventDispatcher() {
public boolean dispatchKeyEvent (KeyEvent e) {
// if ((e.getModifiersEx() & KeyEvent.ALT_DOWN_MASK) != 0 &&
// e.getKeyCode() == KeyEvent.VK_TAB) {
// Log.info("Detected alt-tab key event " +
// "[e=" + e + "].");
// // attempt to eat the event so that windows
// // doesn't alt-tab into unhappy land
// e.consume();
// return true;
// }
return false;
}
});
}
/**
* Instructs the frame manager to target the specified number of
* frames per second. If the computation and rendering for a frame are
* completed with time to spare, the frame manager will wait until the
* proper time to begin processing for the next frame. If a frame
* takes longer than its alotted time, the frame manager will
* immediately begin processing on the next frame.
*/
public void setTargetFrameRate (int fps)
{
// compute the number of milliseconds per frame
_millisPerFrame = 1000/fps;
}
/**
* Registers a frame participant. The participant will be given the
* opportunity to do processing and rendering on each frame.
*/
public void registerFrameParticipant (FrameParticipant participant)
{
Object[] nparts = ListUtil.testAndAddRef(_participants, participant);
if (nparts == null) {
Log.warning("Refusing to add duplicate frame participant! " +
participant);
} else {
_participants = nparts;
}
}
/**
* Returns true if the specified participant is registered.
*/
public boolean isRegisteredFrameParticipant (FrameParticipant participant)
{
return ListUtil.containsRef(_participants, participant);
}
/**
* Removes a frame participant.
*/
public void removeFrameParticipant (FrameParticipant participant)
{
ListUtil.clearRef(_participants, participant);
}
/**
* Returns a millisecond granularity time stamp using the {@link
* MediaTimer} with which this frame manager was configured.
* <em>Note:</em> this should only be called from the AWT thread.
*/
public long getTimeStamp ()
{
return _timer.getElapsedMillis();
}
/**
* Starts up the per-frame tick
*/
public void start ()
{
if (_ticker == null) {
_ticker = new Ticker();
_ticker.start();
_lastTickStamp = 0;
}
}
/**
* Stops the per-frame tick.
*/
public synchronized void stop ()
{
if (_ticker != null) {
_ticker.cancel();
_ticker = null;
}
}
/**
* Returns true if the tick interval is be running (not necessarily at
* that instant, but in general).
*/
public synchronized boolean isRunning ()
{
return (_ticker != null);
}
/**
* Returns the number of ticks executed in the last second.
*/
public int getPerfTicks ()
{
return Math.round(_fps[1]);
}
/**
* Returns the number of ticks requested in the last second.
*/
public int getPerfTries ()
{
return Math.round(_fps[0]);
}
/**
* Returns debug performance metrics.
*/
public TrailingAverage[] getPerfMetrics ()
{
if (_metrics == null) {
_metrics = new TrailingAverage[] {
new TrailingAverage(150),
new TrailingAverage(150),
new TrailingAverage(150) };
}
return _metrics;
}
/**
* Called to perform the frame processing and rendering.
*/
protected void tick (long tickStamp)
{
long start = 0L, paint = 0L;
if (MediaPanel._perfDebug.getValue()) {
start = paint = _timer.getElapsedMicros();
}
// if our frame is not showing (or is impossibly sized), don't try
// rendering anything
if (_window.isShowing() &&
_window.getWidth() > 0 && _window.getHeight() > 0) {
// tick our participants
tickParticipants(tickStamp);
paint = _timer.getElapsedMicros();
// repaint our participants and components
paint(tickStamp);
}
if (MediaPanel._perfDebug.getValue()) {
long end = _timer.getElapsedMicros();
getPerfMetrics()[1].record((int)(paint-start)/100);
getPerfMetrics()[2].record((int)(end-paint)/100);
}
}
/**
* Called once per frame to invoke {@link FrameParticipant#tick} on
* all of our frame participants.
*/
protected void tickParticipants (long tickStamp)
{
long gap = tickStamp - _lastTickStamp;
if (_lastTickStamp != 0 && gap > (HANG_DEBUG ? HANG_GAP : BIG_GAP)) {
Log.debug("Long tick delay [delay=" + gap + "ms].");
}
_lastTickStamp = tickStamp;
// validate any invalid components
try {
_remgr.validateComponents();
} catch (Throwable t) {
Log.warning("Failure validating components.");
Log.logStackTrace(t);
}
// tick all of our frame participants
for (int ii = 0; ii < _participants.length; ii++) {
FrameParticipant part = (FrameParticipant)_participants[ii];
if (part == null) {
continue;
}
try {
long start = 0L;
if (HANG_DEBUG) {
start = System.currentTimeMillis();
}
part.tick(tickStamp);
if (HANG_DEBUG) {
long delay = (System.currentTimeMillis() - start);
if (delay > HANG_GAP) {
Log.info("Whoa nelly! Ticker took a long time " +
"[part=" + part + ", time=" + delay + "ms].");
}
}
} catch (Throwable t) {
Log.warning("Frame participant choked during tick " +
"[part=" + StringUtil.safeToString(part) + "].");
Log.logStackTrace(t);
}
}
}
/**
* Called once per frame to invoke {@link Component#paint} on all of
* our frame participants' components and all dirty components managed
* by our {@link ActiveRepaintManager}.
*/
protected abstract void paint (long tickStamp);
/**
* Paints our frame participants and any dirty components via the
* repaint manager.
*
* @return true if anything was painted, false if not.
*/
protected boolean paint (Graphics2D gfx)
{
// paint our frame participants (which want to be handled
// specially)
int painted = 0;
for (int ii = 0; ii < _participants.length; ii++) {
FrameParticipant part = (FrameParticipant)_participants[ii];
if (part == null) {
continue;
}
Component pcomp = part.getComponent();
if (pcomp == null || !part.needsPaint()) {
continue;
}
long start = 0L;
if (HANG_DEBUG) {
start = System.currentTimeMillis();
}
// get the bounds of this component
pcomp.getBounds(_tbounds);
// the bounds adjustment we're about to call will add in the
// components initial bounds offsets, so we remove them here
_tbounds.setLocation(0, 0);
// convert them into top-level coordinates; also note that if
// this component does not have a valid or visible root, we
// don't want to paint it either
if (getRoot(pcomp, _tbounds) == null) {
continue;
}
try {
// render this participant; we don't set the clip because
// frame participants are expected to handle clipping
// themselves; otherwise we might pointlessly set the clip
// here, creating a few Rectangle objects in the process,
// only to have the frame participant immediately set the
// clip to something more sensible
gfx.translate(_tbounds.x, _tbounds.y);
pcomp.paint(gfx);
gfx.translate(-_tbounds.x, -_tbounds.y);
painted++;
} catch (Throwable t) {
String ptos = StringUtil.safeToString(part);
Log.warning("Frame participant choked during paint " +
"[part=" + ptos + "].");
Log.logStackTrace(t);
}
// render any components in our layered pane that are not in
// the default layer
_clipped[0] = false;
renderLayers(gfx, pcomp, _tbounds, _clipped);
if (HANG_DEBUG) {
long delay = (System.currentTimeMillis() - start);
if (delay > HANG_GAP) {
Log.warning("Whoa nelly! Painter took a long time " +
"[part=" + part + ", time=" + delay + "ms].");
}
}
}
// repaint any widgets that have declared they need to be
// repainted since the last tick
boolean pcomp = _remgr.paintComponents(gfx, this);
// let the caller know if anybody painted anything
return ((painted > 0) || pcomp);
}
/**
* Called by the {@link ManagedJFrame} when our window was hidden and
* reexposed.
*/
protected abstract void restoreFromBack (Rectangle dirty);
/**
* Renders all components in all {@link JLayeredPane} layers that
* intersect the supplied bounds.
*/
protected void renderLayers (Graphics2D g, Component pcomp,
Rectangle bounds, boolean[] clipped)
{
JLayeredPane lpane =
JLayeredPane.getLayeredPaneAbove(pcomp);
if (lpane != null) {
renderLayer(g, bounds, lpane, clipped, JLayeredPane.PALETTE_LAYER);
renderLayer(g, bounds, lpane, clipped, JLayeredPane.MODAL_LAYER);
renderLayer(g, bounds, lpane, clipped, JLayeredPane.POPUP_LAYER);
renderLayer(g, bounds, lpane, clipped, JLayeredPane.DRAG_LAYER);
}
}
/**
* Renders all components in the specified layer of the supplied
* layered pane that intersect the supplied bounds.
*/
protected void renderLayer (Graphics2D g, Rectangle bounds,
JLayeredPane pane, boolean[] clipped,
Integer layer)
{
// stop now if there are no components in that layer
int ccount = pane.getComponentCountInLayer(layer.intValue());
if (ccount == 0) {
return;
}
// render them up
Component[] comps = pane.getComponentsInLayer(layer.intValue());
for (int ii = 0; ii < ccount; ii++) {
Component comp = comps[ii];
if (!comp.isVisible() || comp instanceof SafeLayerComponent) {
continue;
}
// if this overlay does not intersect the component we just
// rendered, we don't need to repaint it
_tbounds.setBounds(0, 0, comp.getWidth(), comp.getHeight());
getRoot(comp, _tbounds);
if (!_tbounds.intersects(bounds)) {
continue;
}
// if the clipping region has not yet been set during this
// render pass, the time has come to do so
if (!clipped[0]) {
g.setClip(bounds);
clipped[0] = true;
}
// translate into the components coordinate system and render
g.translate(_tbounds.x, _tbounds.y);
try {
comp.paint(g);
} catch (Exception e) {
Log.warning("Component choked while rendering.");
Log.logStackTrace(e);
}
g.translate(-_tbounds.x, -_tbounds.y);
}
}
// documentation inherited
public void checkpoint (String name, int ticks)
{
Log.info("Frames in last second: " + ticks);
}
/**
* Returns the root component for the supplied component or null if it
* is not part of a rooted hierarchy or if any parent along the way is
* found to be hidden or without a peer. Along the way, it adjusts the
* supplied component-relative rectangle to be relative to the
* returned root component.
*/
public static Component getRoot (Component comp, Rectangle rect)
{
for (Component c = comp; c != null; c = c.getParent()) {
if (!c.isVisible() || !c.isDisplayable()) {
return null;
}
if (c instanceof Window || c instanceof Applet) {
return c;
}
rect.x += c.getX();
rect.y += c.getY();
}
return null;
}
/** Used to effect periodic calls to {@link #tick}. */
protected class Ticker extends Thread
{
public void run ()
{
Log.info("Frame manager ticker running " +
"[sleepGran=" + _sleepGranularity.getValue() + "].");
while (_running) {
long start = 0L;
if (MediaPanel._perfDebug.getValue()) {
start = _timer.getElapsedMicros();
}
Unsafe.sleep(_sleepGranularity.getValue());
long woke = _timer.getElapsedMicros();
if (start > 0L) {
getPerfMetrics()[0].record((int)(woke-start)/100);
int elapsed = (int)(woke-start);
if (elapsed > _sleepGranularity.getValue()*1500) {
Log.warning("Long tick [elapsed=" + elapsed + "us].");
}
}
// work around sketchy bug on WinXP that causes the clock
// to leap into the past from time to time
if (woke < _lastAttempt) {
Log.warning("Zoiks! We've leapt into the past, coping " +
"as best we can [dt=" +
(woke - _lastAttempt) + "].");
_lastAttempt = woke;
}
if (woke - _lastAttempt >= _millisPerFrame * 1000) {
_lastAttempt = woke;
if (testAndSet()) {
EventQueue.invokeLater(_awtTicker);
}
// else: drop the frame
}
}
}
public void cancel ()
{
_running = false;
}
protected final synchronized boolean testAndSet ()
{
_tries++;
if (!_ticking) {
_ticking = true;
return true;
}
return false;
}
protected final synchronized void clearTicking (long elapsed)
{
if (++_ticks == 100) {
long time = (elapsed - _lastTick);
_fps[0] = _tries * 1000f / time;
_fps[1] = _ticks * 1000f / time;
_lastTick = elapsed;
_ticks = _tries = 0;
}
_ticking = false;
}
/** Used to invoke the call to {@link #tick} on the AWT event
* queue thread. */
protected Runnable _awtTicker = new Runnable ()
{
public void run ()
{
long elapsed = _timer.getElapsedMillis();
try {
tick(elapsed);
} finally {
clearTicking(elapsed);
}
}
};
/** Used to stick a fork in our ticker when desired. */
protected transient boolean _running = true;
/** Used to detect when we need to drop frames. */
protected boolean _ticking;
/** The time at which we last attempted to tick. */
protected long _lastAttempt;
/** Used to compute metrics. */
protected int _tries, _ticks, _time;
/** Used to compute metrics. */
protected long _lastTick;
};
/** The window into which we do our rendering. */
protected Window _window;
/** Provides access to our Swing bits. */
protected RootPaneContainer _root;
/** Used to obtain timing measurements. */
protected MediaTimer _timer;
/** Our custom repaint manager. */
protected ActiveRepaintManager _remgr;
/** The number of milliseconds per frame (14 by default, which gives
* an fps of ~71). */
protected long _millisPerFrame = 14;
/** Used to track big delays in calls to our tick method. */
protected long _lastTickStamp;
/** The thread that dispatches our frame ticks. */
protected Ticker _ticker;
/** Used to track and report frames per second. */
protected float[] _fps = new float[2];
/** Used to track performance metrics. */
protected TrailingAverage[] _metrics;
/** A temporary bounds rectangle used to avoid lots of object creation. */
protected Rectangle _tbounds = new Rectangle();
/** Used to lazily set the clip when painting popups and other
* "layered" components. */
protected boolean[] _clipped = new boolean[1];
/** The entites that are ticked each frame. */
protected Object[] _participants = new Object[4];
/** If we don't get ticked for 500ms, that's worth complaining about. */
protected static final long BIG_GAP = 500L;
/** If we don't get ticked for 100ms and we're hang debugging,
* complain. */
protected static final long HANG_GAP = 100L;
/** Enable this to log warnings when ticking or painting takes too
* long. */
protected static final boolean HANG_DEBUG = false;
/** A debug hook that toggles debug rendering of sprite paths. */
protected static RuntimeAdjust.BooleanAdjust _useFlip =
new RuntimeAdjust.BooleanAdjust(
"When active a flip-buffer will be used to manage our " +
"rendering, otherwise a volatile back buffer is used " +
"[requires restart]", "narya.media.frame",
// back buffer rendering doesn't work on the Mac, so we
// default to flip buffer on that platform; we still allow it
// to be toggled so that we can easily test things when they
// release new JVMs
MediaPrefs.config, RunAnywhere.isMacOS());
/** Allows us to tweak the sleep granularity. */
protected static RuntimeAdjust.IntAdjust _sleepGranularity =
new RuntimeAdjust.IntAdjust(
"The number of milliseconds slept before checking to see if " +
"it's time to queue up a new frame tick.", "narya.media.sleep_gran",
MediaPrefs.config, RunAnywhere.isWindows() ? 10 : 7);
/** Whether to enable AWT event debugging for the frame. */
protected static final boolean DEBUG_EVENTS = false;
/** The name of the high-performance timer class we attempt to load. */
protected static final String PERF_TIMER =
"com.threerings.media.timer.PerfTimer";
}
@@ -1,64 +0,0 @@
//
// $Id$
//
// 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.media;
import java.awt.Component;
/**
* Provides a mechanism for participating in the frame tick managed by the
* {@link FrameManager}.
*/
public interface FrameParticipant
{
/**
* This is called on all registered frame participants, one for every
* frame. Following the tick the interface will be rendered, so
* participants can prepare themselves for their upcoming render in
* this method (making use of the timestamp provided for the frame if
* choreography is desired between different participants).
*/
public void tick (long tickStamp);
/**
* Called immediately prior to {@link #getComponent} and then {@link
* Component#paint} on said component, to determine whether or not
* this frame participant needs to be painted.
*/
public boolean needsPaint ();
/**
* If a frame participant wishes also to be actively rendered every
* frame rather than use passive rendering (which for Swing, at least,
* is hijacked when using the frame manager such that we take care of
* repainting dirty Swing components every frame into our off-screen
* buffer), it can return a component here which will have {@link
* Component#paint} called on it once per frame with a translated but
* unclipped graphics object.
*
* <p> Because clipping is expensive in terms of rectangle object
* allocation, frame participants are given the opportunity to do
* their own clipping because they are likely to want to clip to a
* more fine grained region than their entire bounds. If a particpant
* does not wish to be actively rendered, it can safely return null.
*/
public Component getComponent ();
}
@@ -1,175 +0,0 @@
//
// $Id: HourglassView.java 18862 2005-01-27 00:34:51Z tedv $
package com.threerings.media;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import javax.swing.JComponent;
import com.samskivert.util.Interval;
import com.samskivert.util.ResultListener;
import com.threerings.media.image.Mirage;
import com.threerings.media.util.MultiFrameImage;
/**
* Displays an hourglass with the sand level representing the amount of
* time remaining.
*/
public class HourglassView extends TimerView
{
/**
* Constructs an hourglass view.
*/
public HourglassView (FrameManager fmgr, JComponent host, int x, int y,
Mirage glassImage, Mirage topImage, Rectangle topRect,
Mirage botImage, Rectangle botRect,
MultiFrameImage sandTrickle)
{
this(fmgr, host, x, y, glassImage, topImage, topRect, new Point(0, 0),
botImage, botRect, new Point(0, 0), sandTrickle);
}
/**
* Constructs an hourglass view.
*/
public HourglassView (
FrameManager fmgr, JComponent host, int x, int y, Mirage glassImage,
Mirage topImage, Rectangle topRect, Point topOff,
Mirage botImage, Rectangle botRect, Point botOff,
MultiFrameImage sandTrickle)
{
super(fmgr, host, new Rectangle(x, y, glassImage.getWidth(),
glassImage.getHeight()));
// Store relevant coordinate data
_topRect = topRect;
_topOff = topOff;
_botRect = botRect;
_botOff = botOff;
// Save hourglass images
_hourglass = glassImage;
_sandTop = topImage;
_sandBottom = botImage;
_sandTrickle = sandTrickle;
// Initialize the falling grain of sand
_sandY = 0;
// Percentage changes smaller than one pixel in the hourglass
// itself definitely won't be noticed
_changeThreshold = 1.0f / _bounds.height;
}
// documentation inherited
public void changeComplete (float complete)
{
super.changeComplete(complete);
setSandTrickleY();
}
// documentation inherited
public void tick (long tickStamp)
{
// Let the parent handle its stuff
super.tick(tickStamp);
// check if the sand should be updated
if (_enabled && _running && tickStamp > _sandStamp) {
// update the sand frame
setSandTrickleY();
_sandFrame = (_sandFrame + 1) % _sandTrickle.getFrameCount();
_sandStamp = tickStamp + SAND_RATE;
// Make sure the timer gets repainted
invalidate();
}
}
// documentation inherited
public void paint (Graphics2D gfx, float completed)
{
// Handle processing from parent class
super.paint(gfx, completed);
// Paint the hourglass
gfx.translate(_bounds.x, _bounds.y);
_hourglass.paint(gfx, 0, 0);
// Paint the remaining top sand level
Shape oclip = gfx.getClip();
int top = _topRect.y + (int)(_topRect.height * completed);
gfx.clipRect(_topRect.x, top, _topRect.width,
_topRect.height - (top-_topRect.y));
_sandTop.paint(gfx, _topOff.x, _topOff.y);
// paint the current sand frame
gfx.setClip(oclip);
int sandtop = _topRect.y + _topRect.height;
if (_sandY < _botRect.height) {
gfx.clipRect(_botRect.x, sandtop, _botRect.width, _sandY);
}
_sandTrickle.paintFrame(gfx, _sandFrame,
_botRect.x + (_botRect.width - _sandTrickle.getWidth(_sandFrame))/2,
sandtop);
gfx.setClip(oclip);
// Paint the current bottom sand level
top = getSandBottomTop(completed);
gfx.clipRect(_botRect.x, top, _botRect.width,
_botRect.height-(top-_botRect.y));
_sandBottom.paint(gfx, _botOff.x, _botOff.y);
gfx.setClip(oclip);
// untranslate
gfx.translate(-_bounds.x, -_bounds.y);
}
/**
* Set the y position of the trickle.
*/
protected void setSandTrickleY ()
{
_sandY = (int) (_botRect.height * Math.min(1f, (_complete / .025)));
}
/**
* Returns the current top coordinate of the bottom sand pile.
*/
protected int getSandBottomTop (float completed)
{
return _botRect.y + _botRect.height -
(int)(_botRect.height * completed);
}
/** The bounds of the sand within the top and bottom sand images. */
protected Rectangle _topRect, _botRect;
/** Offsets at which to render the sand images. */
protected Point _topOff, _botOff;
/** Our images. */
protected Mirage _hourglass, _sandTop, _sandBottom;
/** The sand trickle. */
protected MultiFrameImage _sandTrickle;
/** The last time the sand updated moved. */
protected long _sandStamp;
/** The next sand frame to be painted. */
protected int _sandFrame;
/** The position of the top of the sand. */
protected int _sandY;
/** How often the sand frame updates. */
protected static final long SAND_RATE = 80;
}

Some files were not shown because too many files have changed in this diff Show More