Behold, Nenya, Ring of Water and repository for our media and animation related
goodies, both Java 2D and LWJGL/JME 3D. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.jme.math.Quaternion;
|
||||
import com.jme.math.Vector3f;
|
||||
import com.jme.scene.Controller;
|
||||
import com.jme.scene.Spatial;
|
||||
|
||||
import com.threerings.jme.Log;
|
||||
import com.threerings.jme.model.Model;
|
||||
|
||||
/**
|
||||
* A basic representation for keyframe animations.
|
||||
*/
|
||||
public class AnimationDef
|
||||
{
|
||||
/** The rate of the animation in frames per second. */
|
||||
public int frameRate;
|
||||
|
||||
/** A single frame of the animation. */
|
||||
public static class FrameDef
|
||||
{
|
||||
/** Transform for affected nodes. */
|
||||
public ArrayList<TransformDef> transforms =
|
||||
new ArrayList<TransformDef>();
|
||||
|
||||
public void addTransform (TransformDef transform)
|
||||
{
|
||||
transforms.add(transform);
|
||||
}
|
||||
|
||||
/** Adds all transform targets in this frame to the supplied set. */
|
||||
public void addTransformTargets (
|
||||
HashMap<String, Spatial> nodes, HashSet<Spatial> targets)
|
||||
{
|
||||
for (int ii = 0, nn = transforms.size(); ii < nn; ii++) {
|
||||
String name = transforms.get(ii).name;
|
||||
Spatial target = nodes.get(name);
|
||||
if (target != null) {
|
||||
targets.add(target);
|
||||
} else {
|
||||
Log.debug("Missing animation target [name=" + name +
|
||||
"].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the array of transforms for this frame. */
|
||||
public Model.Transform[] getTransforms (Spatial[] targets)
|
||||
{
|
||||
Model.Transform[] mtransforms =
|
||||
new Model.Transform[targets.length];
|
||||
for (int ii = 0; ii < targets.length; ii++) {
|
||||
mtransforms[ii] = getTransform(targets[ii]);
|
||||
}
|
||||
return mtransforms;
|
||||
}
|
||||
|
||||
/** Returns the transform for the supplied target. */
|
||||
protected Model.Transform getTransform (Spatial target)
|
||||
{
|
||||
String name = target.getName();
|
||||
for (int ii = 0, nn = transforms.size(); ii < nn; ii++) {
|
||||
TransformDef transform = transforms.get(ii);
|
||||
if (name.equals(transform.name)) {
|
||||
return transform.getTransform();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** A transform for a single node. */
|
||||
public static class TransformDef
|
||||
{
|
||||
/** The name of the affected node. */
|
||||
public String name;
|
||||
|
||||
/** The transformation parameters. */
|
||||
public float[] translation;
|
||||
public float[] rotation;
|
||||
public float[] scale;
|
||||
|
||||
/** Returns the live transform object. */
|
||||
public Model.Transform getTransform ()
|
||||
{
|
||||
return new Model.Transform(
|
||||
new Vector3f(translation[0], translation[1], translation[2]),
|
||||
new Quaternion(rotation[0], rotation[1], rotation[2],
|
||||
rotation[3]),
|
||||
new Vector3f(scale[0], scale[1], scale[2]));
|
||||
}
|
||||
}
|
||||
|
||||
/** The individual frames of the animation. */
|
||||
public ArrayList<FrameDef> frames = new ArrayList<FrameDef>();
|
||||
|
||||
public void addFrame (FrameDef frame)
|
||||
{
|
||||
frames.add(frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the "live" animation object that will be serialized with the
|
||||
* object.
|
||||
*
|
||||
* @param props the animation properties
|
||||
* @param nodes the nodes in the model, mapped by name
|
||||
*/
|
||||
public Model.Animation createAnimation (
|
||||
Properties props, HashMap<String, Spatial> nodes)
|
||||
{
|
||||
// find all affected nodes
|
||||
HashSet<Spatial> targets = new HashSet<Spatial>();
|
||||
for (int ii = 0, nn = frames.size(); ii < nn; ii++) {
|
||||
frames.get(ii).addTransformTargets(nodes, targets);
|
||||
}
|
||||
|
||||
// create and configure the animation
|
||||
Model.Animation anim = new Model.Animation();
|
||||
anim.frameRate = frameRate;
|
||||
String rtype = props.getProperty("repeat_type", "clamp");
|
||||
if (rtype.equals("cycle")) {
|
||||
anim.repeatType = Controller.RT_CYCLE;
|
||||
} else if (rtype.equals("wrap")) {
|
||||
anim.repeatType = Controller.RT_WRAP;
|
||||
} else {
|
||||
anim.repeatType = Controller.RT_CLAMP;
|
||||
}
|
||||
|
||||
// collect all transforms
|
||||
anim.transformTargets = targets.toArray(new Spatial[targets.size()]);
|
||||
anim.transforms = new Model.Transform[frames.size()][targets.size()];
|
||||
for (int ii = 0; ii < anim.transforms.length; ii++) {
|
||||
anim.transforms[ii] =
|
||||
frames.get(ii).getTransforms(anim.transformTargets);
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.jme.image.Texture;
|
||||
import com.jme.math.FastMath;
|
||||
import com.jme.math.Vector3f;
|
||||
|
||||
/**
|
||||
* A tool for converting cube maps (sky boxes) to sphere maps.
|
||||
*/
|
||||
public class BuildSphereMap
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 7) {
|
||||
System.err.println("Usage: BuildSphereMap front.ext back.ext " +
|
||||
"left.ext right.ext up.ext dest.ext size");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
try {
|
||||
execute(new File(args[0]), new File(args[1]), new File(args[2]),
|
||||
new File(args[3]), new File(args[4]), new File(args[5]),
|
||||
Integer.parseInt(args[6]));
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the sphere map.
|
||||
*
|
||||
* @param front the file containing the front side of the cube map
|
||||
* @param back the file containing the back side of the cube map
|
||||
* @param left the file containing the left side of the cube map
|
||||
* @param right the file containing the right side of the cube map
|
||||
* @param up the file containing the up side of the cube map
|
||||
* @param target the file to contain the sphere map
|
||||
* @param size the size of the sphere map to generate
|
||||
*/
|
||||
public static void execute (File front, File back, File left, File right,
|
||||
File up, File target, int size)
|
||||
throws IOException
|
||||
{
|
||||
// load up the sides of the cube map
|
||||
BufferedImage[] sides = new BufferedImage[5];
|
||||
sides[FRONT] = ImageIO.read(front);
|
||||
sides[BACK] = ImageIO.read(back);
|
||||
sides[LEFT] = ImageIO.read(left);
|
||||
sides[RIGHT] = ImageIO.read(right);
|
||||
sides[UP] = ImageIO.read(up);
|
||||
|
||||
// compute the pixels
|
||||
int[] rgb = new int[size * size];
|
||||
Vector3f vec = new Vector3f();
|
||||
for (int y = 0, idx = 0; y < size; y++) {
|
||||
for (int x = 0; x < size; x++, idx++) {
|
||||
float vx = x / (size*0.5f) - 1f, vy = y / (size*0.5f) - 1f,
|
||||
d2 = vx*vx + vy*vy;
|
||||
int p = 0;
|
||||
if (d2 <= 1f) {
|
||||
vec.set(vx, vy, FastMath.sqrt(1f - d2));
|
||||
rgb[idx] = getCubeMapPixel(vec, sides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create and write the image
|
||||
BufferedImage image = new BufferedImage(size, size,
|
||||
BufferedImage.TYPE_INT_RGB);
|
||||
image.setRGB(0, 0, size, size, rgb, 0, size);
|
||||
String dest = target.toString(),
|
||||
ext = dest.substring(dest.lastIndexOf('.')+1);
|
||||
ImageIO.write(image, ext, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pixel from the cube map to which the given vector points.
|
||||
*/
|
||||
protected static int getCubeMapPixel (Vector3f vec, BufferedImage[] sides)
|
||||
{
|
||||
int side = getCubeMapSide(vec);
|
||||
|
||||
float s, t;
|
||||
switch (side) {
|
||||
case FRONT:
|
||||
s = -vec.x / vec.y;
|
||||
t = vec.z / vec.y;
|
||||
break;
|
||||
case BACK:
|
||||
s = -vec.x / vec.y;
|
||||
t = -vec.z / vec.y;
|
||||
break;
|
||||
case LEFT:
|
||||
s = vec.y / vec.x;
|
||||
t = -vec.z / vec.x;
|
||||
break;
|
||||
case RIGHT:
|
||||
s = vec.y / vec.x;
|
||||
t = vec.z / vec.x;
|
||||
break;
|
||||
default:
|
||||
case UP:
|
||||
s = vec.x / vec.z;
|
||||
t = -vec.y / vec.z;
|
||||
break;
|
||||
}
|
||||
int width = sides[side].getWidth(), height = sides[side].getHeight();
|
||||
return sides[side].getRGB((int)((width-1) * (s+1f)/2f),
|
||||
(int)((height-1) * (1f-t)/2f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the side index identifying the face of the cube map to which
|
||||
* the given vector points.
|
||||
*/
|
||||
protected static int getCubeMapSide (Vector3f vec)
|
||||
{
|
||||
if (vec.x > vec.z && vec.x > vec.y && vec.x > -vec.y) {
|
||||
return RIGHT;
|
||||
|
||||
} else if (vec.x < -vec.z && vec.x < -vec.y && vec.x < vec.y) {
|
||||
return LEFT;
|
||||
|
||||
} else if (vec.y > vec.z) {
|
||||
return FRONT;
|
||||
|
||||
} else if (vec.y < -vec.z) {
|
||||
return BACK;
|
||||
|
||||
} else {
|
||||
return UP;
|
||||
}
|
||||
}
|
||||
|
||||
/** The sides of the cube map. */
|
||||
protected static final int FRONT = 0, BACK = 1, LEFT = 2, RIGHT = 3,
|
||||
UP = 4;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
/**
|
||||
* An ant task for converting cube maps (sky boxes) to sphere maps.
|
||||
*/
|
||||
public class BuildSphereMapTask extends Task
|
||||
{
|
||||
public void setFront (File front)
|
||||
{
|
||||
_front = front;
|
||||
}
|
||||
|
||||
public void setBack (File back)
|
||||
{
|
||||
_back = back;
|
||||
}
|
||||
|
||||
public void setLeft (File left)
|
||||
{
|
||||
_left = left;
|
||||
}
|
||||
|
||||
public void setRight (File right)
|
||||
{
|
||||
_right = right;
|
||||
}
|
||||
|
||||
public void setUp (File up)
|
||||
{
|
||||
_up = up;
|
||||
}
|
||||
|
||||
public void setTarget (File target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public void setSize (int size)
|
||||
{
|
||||
_size = size;
|
||||
}
|
||||
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
try {
|
||||
BuildSphereMap.execute(_front, _back, _left, _right, _up, _target,
|
||||
_size);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new BuildException("Failure building sphere map", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** The files representing the sides of the cube map and the target. */
|
||||
protected File _front, _back, _left, _right, _up, _target;
|
||||
|
||||
/** The size of the target image. */
|
||||
protected int _size;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.jme.scene.Spatial;
|
||||
import com.jme.util.LoggingSystem;
|
||||
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
|
||||
|
||||
import com.samskivert.util.PropertiesUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.jme.model.Model;
|
||||
import com.threerings.jme.model.ModelMesh;
|
||||
import com.threerings.jme.model.ModelNode;
|
||||
import com.threerings.jme.model.SkinMesh;
|
||||
import com.threerings.jme.tools.xml.AnimationParser;
|
||||
import com.threerings.jme.tools.xml.ModelParser;
|
||||
|
||||
/**
|
||||
* An application for compiling 3D models defined in XML to fast-loading binary
|
||||
* files.
|
||||
*/
|
||||
public class CompileModel
|
||||
{
|
||||
/**
|
||||
* Loads the model described by the given properties file and compiles it
|
||||
* to a <code>.dat</code> file in the same directory.
|
||||
*
|
||||
* @return the loaded model, or <code>null</code> if the compiled version
|
||||
* is up-to-date
|
||||
*/
|
||||
public static Model compile (File source)
|
||||
throws Exception
|
||||
{
|
||||
String spath = source.toString();
|
||||
int didx = spath.lastIndexOf('.');
|
||||
String root = (didx == -1) ? spath : spath.substring(0, didx);
|
||||
File content = new File(root + ".mxml"),
|
||||
target = new File(root + ".dat");
|
||||
boolean needsUpdate = false;
|
||||
if (source.lastModified() >= target.lastModified() ||
|
||||
content.lastModified() >= target.lastModified()) {
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
// load the model properties
|
||||
Properties props = new Properties();
|
||||
FileInputStream in = new FileInputStream(source);
|
||||
props.load(in);
|
||||
in.close();
|
||||
|
||||
// locate the animations, if any
|
||||
String[] anims =
|
||||
StringUtil.parseStringArray(props.getProperty("animations", ""));
|
||||
File[] afiles = new File[anims.length];
|
||||
File dir = source.getParentFile();
|
||||
for (int ii = 0; ii < anims.length; ii++) {
|
||||
afiles[ii] = new File(dir, anims[ii] + ".mxml");
|
||||
if (afiles[ii].lastModified() >= target.lastModified()) {
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
if (!needsUpdate) {
|
||||
return null;
|
||||
}
|
||||
System.out.println("Compiling " + source.getParent() + "...");
|
||||
|
||||
// load the model content
|
||||
ModelDef mdef = _mparser.parseModel(content.toString());
|
||||
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
|
||||
Model model = mdef.createModel(props, nodes);
|
||||
model.initPrototype();
|
||||
|
||||
// load the animations, if any
|
||||
for (int ii = 0; ii < anims.length; ii++) {
|
||||
System.out.println(" Adding " + afiles[ii] + "...");
|
||||
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString());
|
||||
model.addAnimation(anims[ii], adef.createAnimation(
|
||||
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
|
||||
}
|
||||
|
||||
// write and return the model
|
||||
model.writeToFile(target);
|
||||
return model;
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: CompileModel source.properties");
|
||||
System.exit(-1);
|
||||
}
|
||||
// create a dummy display system
|
||||
new DummyDisplaySystem();
|
||||
LoggingSystem.getLogger().setLevel(Level.WARNING);
|
||||
|
||||
try {
|
||||
compile(new File(args[0]));
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error compiling model: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/** A parser for the model definitions. */
|
||||
protected static ModelParser _mparser = new ModelParser();
|
||||
|
||||
/** A parser for the animation definitions. */
|
||||
protected static AnimationParser _aparser = new AnimationParser();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.jme.util.LoggingSystem;
|
||||
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
|
||||
|
||||
/**
|
||||
* An ant task for compiling 3D models defined in XML to fast-loading binary
|
||||
* files.
|
||||
*/
|
||||
public class CompileModelTask extends Task
|
||||
{
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
public void init () throws BuildException
|
||||
{
|
||||
// create a dummy display system
|
||||
new DummyDisplaySystem();
|
||||
LoggingSystem.getLogger().setLevel(Level.WARNING);
|
||||
}
|
||||
|
||||
public void execute ()
|
||||
throws BuildException
|
||||
{
|
||||
for (int ii = 0, nn = _filesets.size(); ii < nn; ii++) {
|
||||
FileSet fs = _filesets.get(ii);
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
|
||||
for (int f = 0; f < srcFiles.length; f++) {
|
||||
File source = new File(fromDir, srcFiles[f]);
|
||||
try {
|
||||
CompileModel.compile(source);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error compiling " + source + ": " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A list of filesets that contain XML models. */
|
||||
protected ArrayList<FileSet> _filesets = new ArrayList<FileSet>();
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import com.jmex.model.XMLparser.Converters.AseToJme;
|
||||
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
|
||||
import com.jmex.model.XMLparser.Converters.FormatConverter;
|
||||
import com.jmex.model.XMLparser.Converters.MaxToJme;
|
||||
import com.jmex.model.XMLparser.Converters.Md2ToJme;
|
||||
import com.jmex.model.XMLparser.Converters.Md3ToJme;
|
||||
import com.jmex.model.XMLparser.Converters.ObjToJme;
|
||||
|
||||
/**
|
||||
* A tool for converting various 3D model formats into JME's internal
|
||||
* format.
|
||||
*/
|
||||
public class ConvertModel
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 2) {
|
||||
System.err.println("Usage: ConvertModel source.ext dest.jme");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// create a dummy display system which the converters need
|
||||
new DummyDisplaySystem();
|
||||
|
||||
ConvertModel app = new ConvertModel();
|
||||
File source = new File(args[0]);
|
||||
File target = new File(args[1]);
|
||||
|
||||
String path = source.getPath().toLowerCase();
|
||||
String type = path.substring(path.lastIndexOf(".") + 1);
|
||||
|
||||
// set up our converter
|
||||
FormatConverter convert = null;
|
||||
if (type.equals("obj")) {
|
||||
convert = new ObjToJme();
|
||||
try {
|
||||
convert.setProperty("mtllib", new URL("file:" + source));
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to create material URL: " + e);
|
||||
System.exit(-1);
|
||||
}
|
||||
} else if (type.equals("3ds")) {
|
||||
convert = new MaxToJme();
|
||||
} else if (type.equals("md2")) {
|
||||
convert = new Md2ToJme();
|
||||
} else if (type.equals("md3")) {
|
||||
convert = new Md3ToJme();
|
||||
} else if (type.equals("ase")) {
|
||||
convert = new AseToJme();
|
||||
} else {
|
||||
System.err.println("Unknown model type '" + type + "'.");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
// and do the deed
|
||||
try {
|
||||
BufferedOutputStream bout = new BufferedOutputStream(
|
||||
new FileOutputStream(target));
|
||||
BufferedInputStream bin = new BufferedInputStream(
|
||||
new FileInputStream(source));
|
||||
convert.convert(bin, bout);
|
||||
bout.close();
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error converting '" + source +
|
||||
"' to '" + target + "'.");
|
||||
ioe.printStackTrace(System.err);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.jme.util.LoggingSystem;
|
||||
import com.jmex.model.XMLparser.Converters.AseToJme;
|
||||
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
|
||||
import com.jmex.model.XMLparser.Converters.FormatConverter;
|
||||
import com.jmex.model.XMLparser.Converters.MaxToJme;
|
||||
import com.jmex.model.XMLparser.Converters.Md2ToJme;
|
||||
import com.jmex.model.XMLparser.Converters.Md3ToJme;
|
||||
import com.jmex.model.XMLparser.Converters.ObjToJme;
|
||||
|
||||
/**
|
||||
* An ant task for converting various 3D model formats into JME's internal
|
||||
* format.
|
||||
*/
|
||||
public class ConvertModelTask extends Task
|
||||
{
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
public void init () throws BuildException
|
||||
{
|
||||
// create a dummy display system which the converters need
|
||||
new DummyDisplaySystem();
|
||||
LoggingSystem.getLogger().setLevel(Level.WARNING);
|
||||
}
|
||||
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
for (int i = 0; i < _filesets.size(); i++) {
|
||||
FileSet fs = (FileSet)_filesets.get(i);
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
|
||||
for (int f = 0; f < srcFiles.length; f++) {
|
||||
File cfile = new File(fromDir, srcFiles[f]);
|
||||
String target = srcFiles[f];
|
||||
int didx = target.lastIndexOf(".");
|
||||
target = (didx == -1) ? target : target.substring(0, didx);
|
||||
target += ".jme";
|
||||
convertModel(cfile, new File(fromDir, target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void convertModel (File source, File target)
|
||||
{
|
||||
if (source.lastModified() < target.lastModified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Converting " + source + "...");
|
||||
String path = source.getPath().toLowerCase();
|
||||
String type = path.substring(path.lastIndexOf(".") + 1);
|
||||
|
||||
// set up our converter
|
||||
FormatConverter convert = null;
|
||||
if (type.equals("obj")) {
|
||||
convert = new ObjToJme();
|
||||
try {
|
||||
convert.setProperty("mtllib", new URL("file:" + source));
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to create material URL: " + e);
|
||||
return;
|
||||
}
|
||||
} else if (type.equals("3ds")) {
|
||||
convert = new MaxToJme();
|
||||
} else if (type.equals("md2")) {
|
||||
convert = new Md2ToJme();
|
||||
} else if (type.equals("md3")) {
|
||||
convert = new Md3ToJme();
|
||||
} else if (type.equals("ase")) {
|
||||
convert = new AseToJme();
|
||||
} else {
|
||||
System.err.println("Unknown model type '" + type + "'.");
|
||||
return;
|
||||
}
|
||||
|
||||
// and do the deed
|
||||
try {
|
||||
BufferedOutputStream bout = new BufferedOutputStream(
|
||||
new FileOutputStream(target));
|
||||
BufferedInputStream bin = new BufferedInputStream(
|
||||
new FileInputStream(source));
|
||||
convert.convert(bin, bout);
|
||||
bout.close();
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error converting '" + source +
|
||||
"' to '" + target + "'.");
|
||||
ioe.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
/** A list of filesets that contain tileset bundle definitions. */
|
||||
protected ArrayList _filesets = new ArrayList();
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import com.jme.bounding.BoundingBox;
|
||||
import com.jme.bounding.BoundingSphere;
|
||||
import com.jme.math.FastMath;
|
||||
import com.jme.scene.Spatial;
|
||||
import com.jme.util.geom.BufferUtils;
|
||||
|
||||
import com.samskivert.util.PropertiesUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.jme.Log;
|
||||
import com.threerings.jme.model.Model;
|
||||
import com.threerings.jme.model.ModelController;
|
||||
import com.threerings.jme.model.ModelMesh;
|
||||
import com.threerings.jme.model.ModelNode;
|
||||
import com.threerings.jme.model.SkinMesh;
|
||||
|
||||
/**
|
||||
* An intermediate representation for models used to store data parsed from
|
||||
* XML and convert it into JME nodes.
|
||||
*/
|
||||
public class ModelDef
|
||||
{
|
||||
/** The base class of nodes in the model. */
|
||||
public abstract static class SpatialDef
|
||||
{
|
||||
/** The node's name. */
|
||||
public String name;
|
||||
|
||||
/** The name of the node's parent. */
|
||||
public String parent;
|
||||
|
||||
/** The node's transformation. */
|
||||
public float[] translation;
|
||||
public float[] rotation;
|
||||
public float[] scale;
|
||||
|
||||
/** Returns a JME node for this definition. */
|
||||
public Spatial getSpatial (Properties props)
|
||||
{
|
||||
if (_spatial == null) {
|
||||
_spatial = createSpatial(new NodeProperties(props, name));
|
||||
setTransform();
|
||||
}
|
||||
return _spatial;
|
||||
}
|
||||
|
||||
/** Sets the transform of the created node. */
|
||||
protected void setTransform ()
|
||||
{
|
||||
_spatial.getLocalTranslation().set(translation[0], translation[1],
|
||||
translation[2]);
|
||||
_spatial.getLocalRotation().set(rotation[0], rotation[1],
|
||||
rotation[2], rotation[3]);
|
||||
_spatial.getLocalScale().set(scale[0], scale[1], scale[2]);
|
||||
}
|
||||
|
||||
/** Creates a JME node for this definition. */
|
||||
public abstract Spatial createSpatial (Properties props);
|
||||
|
||||
/** Resolves any name references using the supplied map. */
|
||||
public void resolveReferences (
|
||||
HashMap<String, Spatial> nodes, HashSet<Spatial> referenced)
|
||||
{
|
||||
Spatial pnode = nodes.get(parent);
|
||||
if (pnode instanceof ModelNode) {
|
||||
((ModelNode)pnode).attachChild(_spatial);
|
||||
|
||||
} else if (parent != null) {
|
||||
Log.warning("Missing or invalid parent node [spatial=" +
|
||||
name + ", parent=" + parent + "].");
|
||||
}
|
||||
}
|
||||
|
||||
/** The JME node created for this definition. */
|
||||
protected Spatial _spatial;
|
||||
}
|
||||
|
||||
/** A rigid triangle mesh. */
|
||||
public static class TriMeshDef extends SpatialDef
|
||||
{
|
||||
/** The geometry offset transform. */
|
||||
public float[] offsetTranslation;
|
||||
public float[] offsetRotation;
|
||||
public float[] offsetScale;
|
||||
|
||||
/** Whether or not the mesh allows back face culling. */
|
||||
public boolean solid;
|
||||
|
||||
/** The texture of the mesh, if any. */
|
||||
public String texture;
|
||||
|
||||
/** Whether or not the mesh is (partially) transparent. */
|
||||
public boolean transparent;
|
||||
|
||||
/** The vertices of the mesh. */
|
||||
public ArrayList<Vertex> vertices = new ArrayList<Vertex>();
|
||||
|
||||
/** The triangle indices. */
|
||||
public ArrayList<Integer> indices = new ArrayList<Integer>();
|
||||
|
||||
/** Whether or not any of the vertices have texture coordinates. */
|
||||
public boolean tcoords;
|
||||
|
||||
public void addVertex (Vertex vertex)
|
||||
{
|
||||
int idx = vertices.indexOf(vertex);
|
||||
if (idx != -1) {
|
||||
indices.add(idx);
|
||||
} else {
|
||||
indices.add(vertices.size());
|
||||
vertices.add(vertex);
|
||||
}
|
||||
tcoords = tcoords || vertex.tcoords != null;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Spatial createSpatial (Properties props)
|
||||
{
|
||||
ModelNode node = new ModelNode(name);
|
||||
if (indices.size() > 0) {
|
||||
_mesh = createMesh();
|
||||
configureMesh(props);
|
||||
node.attachChild(_mesh);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Creates the mesh to attach to the node. */
|
||||
protected ModelMesh createMesh ()
|
||||
{
|
||||
return new ModelMesh("mesh");
|
||||
}
|
||||
|
||||
/** Configures the mesh. */
|
||||
protected void configureMesh (Properties props)
|
||||
{
|
||||
// set the geometry offset
|
||||
if (offsetTranslation != null) {
|
||||
_mesh.getLocalTranslation().set(offsetTranslation[0],
|
||||
offsetTranslation[1], offsetTranslation[2]);
|
||||
}
|
||||
if (offsetRotation != null) {
|
||||
_mesh.getLocalRotation().set(offsetRotation[0],
|
||||
offsetRotation[1], offsetRotation[2], offsetRotation[3]);
|
||||
}
|
||||
if (offsetScale != null) {
|
||||
_mesh.getLocalScale().set(offsetScale[0], offsetScale[1],
|
||||
offsetScale[2]);
|
||||
}
|
||||
|
||||
// make sure texture is just a filename
|
||||
int sidx = (texture == null) ? -1 :
|
||||
Math.max(texture.lastIndexOf('/'), texture.lastIndexOf('\\'));
|
||||
if (sidx != -1) {
|
||||
texture = texture.substring(sidx + 1);
|
||||
}
|
||||
|
||||
// configure using properties
|
||||
_mesh.configure(solid, texture, transparent, props);
|
||||
|
||||
// set the various buffers
|
||||
int vsize = vertices.size();
|
||||
ByteOrder no = ByteOrder.nativeOrder();
|
||||
ByteBuffer vbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
|
||||
nbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
|
||||
tbbuf = tcoords ?
|
||||
ByteBuffer.allocateDirect(vsize*2*4).order(no) : null,
|
||||
ibbuf = ByteBuffer.allocateDirect(indices.size()*4).order(no);
|
||||
FloatBuffer vbuf = vbbuf.asFloatBuffer(),
|
||||
nbuf = nbbuf.asFloatBuffer(),
|
||||
tbuf = tcoords ? tbbuf.asFloatBuffer() : null;
|
||||
for (int ii = 0; ii < vsize; ii++) {
|
||||
vertices.get(ii).setInBuffers(vbuf, nbuf, tbuf);
|
||||
}
|
||||
IntBuffer ibuf = ibbuf.asIntBuffer();
|
||||
for (int ii = 0, nn = indices.size(); ii < nn; ii++) {
|
||||
ibuf.put(indices.get(ii));
|
||||
}
|
||||
_mesh.reconstruct(vbbuf, nbbuf, null, tbbuf, ibbuf);
|
||||
|
||||
_mesh.setModelBound("sphere".equals(props.getProperty("bound")) ?
|
||||
new BoundingSphere() : new BoundingBox());
|
||||
_mesh.updateModelBound();
|
||||
|
||||
// set the mesh's origin to the center of its bounding box
|
||||
_mesh.centerVertices();
|
||||
}
|
||||
|
||||
/** The mesh that contains the actual geometry. */
|
||||
protected ModelMesh _mesh;
|
||||
}
|
||||
|
||||
/** A triangle mesh that deforms according to bone positions. */
|
||||
public static class SkinMeshDef extends TriMeshDef
|
||||
{
|
||||
@Override // documentation inherited
|
||||
protected ModelMesh createMesh ()
|
||||
{
|
||||
return new SkinMesh("mesh");
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void resolveReferences (
|
||||
HashMap<String, Spatial> nodes, HashSet<Spatial> referenced)
|
||||
{
|
||||
super.resolveReferences(nodes, referenced);
|
||||
if (_mesh == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create and set the final weight groups
|
||||
SkinMesh.WeightGroup[] wgroups =
|
||||
new SkinMesh.WeightGroup[_groups.size()];
|
||||
HashMap<String, SkinMesh.Bone> bones =
|
||||
new HashMap<String, SkinMesh.Bone>();
|
||||
int ii = 0;
|
||||
for (Map.Entry<Set<String>, WeightGroupDef> entry :
|
||||
_groups.entrySet()) {
|
||||
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
|
||||
wgroup.vertexCount = entry.getValue().indices.size();
|
||||
wgroup.bones = new SkinMesh.Bone[entry.getKey().size()];
|
||||
int jj = 0;
|
||||
for (String bname : entry.getKey()) {
|
||||
SkinMesh.Bone bone = bones.get(bname);
|
||||
if (bone == null) {
|
||||
Spatial node = nodes.get(bname);
|
||||
bones.put(bname,
|
||||
bone = new SkinMesh.Bone((ModelNode)node));
|
||||
referenced.add(node);
|
||||
}
|
||||
wgroup.bones[jj++] = bone;
|
||||
}
|
||||
wgroup.weights = toArray(entry.getValue().weights);
|
||||
wgroups[ii++] = wgroup;
|
||||
}
|
||||
((SkinMesh)_mesh).setWeightGroups(wgroups);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
protected void configureMesh (Properties props)
|
||||
{
|
||||
// divide the vertices up by weight groups
|
||||
_groups = new HashMap<Set<String>, WeightGroupDef>();
|
||||
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
|
||||
SkinVertex svertex = (SkinVertex)vertices.get(ii);
|
||||
Set<String> bones = svertex.boneWeights.keySet();
|
||||
WeightGroupDef group = _groups.get(bones);
|
||||
if (group == null) {
|
||||
_groups.put(bones, group = new WeightGroupDef());
|
||||
}
|
||||
group.indices.add(ii);
|
||||
for (String bone : bones) {
|
||||
group.weights.add(svertex.boneWeights.get(bone).weight);
|
||||
}
|
||||
}
|
||||
|
||||
// reorder the vertices by group
|
||||
ArrayList<Vertex> overts = vertices;
|
||||
vertices = new ArrayList<Vertex>();
|
||||
int[] imap = new int[overts.size()];
|
||||
for (Map.Entry<Set<String>, WeightGroupDef> entry :
|
||||
_groups.entrySet()) {
|
||||
for (int idx : entry.getValue().indices) {
|
||||
imap[idx] = vertices.size();
|
||||
vertices.add(overts.get(idx));
|
||||
}
|
||||
}
|
||||
for (int ii = 0, nn = indices.size(); ii < nn; ii++) {
|
||||
indices.set(ii, imap[indices.get(ii)]);
|
||||
}
|
||||
|
||||
super.configureMesh(props);
|
||||
}
|
||||
|
||||
/** The intermediate weight groups, mapped by bone names. */
|
||||
protected HashMap<Set<String>, WeightGroupDef> _groups;
|
||||
}
|
||||
|
||||
/** A generic node. */
|
||||
public static class NodeDef extends SpatialDef
|
||||
{
|
||||
// documentation inherited
|
||||
public Spatial createSpatial (Properties props)
|
||||
{
|
||||
return new ModelNode(name);
|
||||
}
|
||||
}
|
||||
|
||||
/** A basic vertex. */
|
||||
public static class Vertex
|
||||
{
|
||||
public float[] location;
|
||||
public float[] normal;
|
||||
public float[] tcoords;
|
||||
|
||||
public void setInBuffers (
|
||||
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
|
||||
{
|
||||
vbuf.put(location);
|
||||
nbuf.put(normal);
|
||||
|
||||
if (tbuf != null) {
|
||||
if (tcoords != null) {
|
||||
tbuf.put(tcoords);
|
||||
} else {
|
||||
tbuf.put(0f);
|
||||
tbuf.put(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals (Object obj)
|
||||
{
|
||||
Vertex overt = (Vertex)obj;
|
||||
return Arrays.equals(location, overt.location) &&
|
||||
Arrays.equals(normal, overt.normal) &&
|
||||
Arrays.equals(tcoords, overt.tcoords);
|
||||
}
|
||||
}
|
||||
|
||||
/** A vertex influenced by a number of bones. */
|
||||
public static class SkinVertex extends Vertex
|
||||
{
|
||||
/** The bones influencing the vertex, mapped by name. */
|
||||
public HashMap<String, BoneWeight> boneWeights =
|
||||
new HashMap<String, BoneWeight>();
|
||||
|
||||
public void addBoneWeight (BoneWeight weight)
|
||||
{
|
||||
if (weight.weight == 0f) {
|
||||
return;
|
||||
}
|
||||
BoneWeight bweight = boneWeights.get(weight.bone);
|
||||
if (bweight != null) {
|
||||
bweight.weight += weight.weight;
|
||||
} else {
|
||||
boneWeights.put(weight.bone, weight);
|
||||
}
|
||||
}
|
||||
|
||||
/** Finds the bone nodes influencing this vertex. */
|
||||
public HashSet<ModelNode> getBones (HashMap<String, Spatial> nodes)
|
||||
{
|
||||
HashSet<ModelNode> bones = new HashSet<ModelNode>();
|
||||
for (String bone : boneWeights.keySet()) {
|
||||
Spatial node = nodes.get(bone);
|
||||
if (node instanceof ModelNode) {
|
||||
bones.add((ModelNode)node);
|
||||
} else {
|
||||
Log.warning("Missing or invalid bone for bone weight " +
|
||||
"[bone=" + bone + "].");
|
||||
}
|
||||
}
|
||||
return bones;
|
||||
}
|
||||
|
||||
/** Returns the weight of the given bone. */
|
||||
public float getWeight (ModelNode bone)
|
||||
{
|
||||
BoneWeight bweight = boneWeights.get(bone.getName());
|
||||
return (bweight == null) ? 0f : bweight.weight;
|
||||
}
|
||||
}
|
||||
|
||||
/** The influence of a bone on a vertex. */
|
||||
public static class BoneWeight
|
||||
{
|
||||
/** The name of the influencing bone. */
|
||||
public String bone;
|
||||
|
||||
/** The amount of influence. */
|
||||
public float weight;
|
||||
}
|
||||
|
||||
/** A group of vertices influenced by the same bone. */
|
||||
public static class WeightGroupDef
|
||||
{
|
||||
/** The indices of the affected vertex. */
|
||||
public ArrayList<Integer> indices = new ArrayList<Integer>();
|
||||
|
||||
/** The interleaved vertex weights. */
|
||||
public ArrayList<Float> weights = new ArrayList<Float>();
|
||||
}
|
||||
|
||||
/** The meshes and bones comprising the model. */
|
||||
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
|
||||
|
||||
public void addSpatial (SpatialDef spatial)
|
||||
{
|
||||
// put nodes before meshes so that bones are updated before skin
|
||||
spatials.add(spatial instanceof NodeDef ? 0 : spatials.size(),
|
||||
spatial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the model node defined herein.
|
||||
*
|
||||
* @param props the properties of the model
|
||||
* @param nodes a node map to populate
|
||||
*/
|
||||
public Model createModel (Properties props, HashMap<String, Spatial> nodes)
|
||||
{
|
||||
Model model = new Model(props.getProperty("name", "model"), props);
|
||||
|
||||
// set the overall scale
|
||||
model.setLocalScale(Float.parseFloat(props.getProperty("scale", "1")));
|
||||
|
||||
// start by creating the spatials and mapping them to their names
|
||||
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
|
||||
Spatial spatial = spatials.get(ii).getSpatial(props);
|
||||
nodes.put(spatial.getName(), spatial);
|
||||
}
|
||||
|
||||
// then go through again, resolving any name references and attaching
|
||||
// root children
|
||||
HashSet<Spatial> referenced = new HashSet<Spatial>();
|
||||
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
|
||||
SpatialDef sdef = spatials.get(ii);
|
||||
sdef.resolveReferences(nodes, referenced);
|
||||
if (sdef.getSpatial(props).getParent() == null) {
|
||||
model.attachChild(sdef.getSpatial(props));
|
||||
}
|
||||
}
|
||||
|
||||
// create any controllers listed
|
||||
String[] controllers = StringUtil.parseStringArray(
|
||||
props.getProperty("controllers", ""));
|
||||
for (int ii = 0; ii < controllers.length; ii++) {
|
||||
Spatial target = nodes.get(controllers[ii]);
|
||||
if (target == null) {
|
||||
Log.warning("Missing controller node [name=" +
|
||||
controllers[ii] + "].");
|
||||
continue;
|
||||
}
|
||||
ModelController ctrl = createController(
|
||||
PropertiesUtil.getSubProperties(props, controllers[ii]),
|
||||
target);
|
||||
if (ctrl != null) {
|
||||
model.addController(ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
// get rid of any nodes that serve no purpose
|
||||
pruneUnusedNodes(model, nodes, referenced);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/** Creates, configures, and returns a model controller. */
|
||||
protected ModelController createController (
|
||||
Properties props, Spatial target)
|
||||
{
|
||||
// attempt to create an instance of the controller
|
||||
ModelController ctrl;
|
||||
String cname = props.getProperty("class", "");
|
||||
try {
|
||||
ctrl = (ModelController)Class.forName(cname).newInstance();
|
||||
} catch (Exception e) {
|
||||
Log.warning("Error instantiating controller [class=" + cname +
|
||||
", error=" + e + "].");
|
||||
return null;
|
||||
}
|
||||
ctrl.configure(props, target);
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
/** Recursively removes any unused nodes. */
|
||||
protected boolean pruneUnusedNodes (
|
||||
ModelNode node, HashMap<String, Spatial> nodes,
|
||||
HashSet<Spatial> referenced)
|
||||
{
|
||||
boolean hasValidChildren = false;
|
||||
for (int ii = node.getQuantity() - 1; ii >= 0; ii--) {
|
||||
Spatial child = node.getChild(ii);
|
||||
if (!(child instanceof ModelNode) ||
|
||||
pruneUnusedNodes((ModelNode)child, nodes, referenced)) {
|
||||
hasValidChildren = true;
|
||||
} else {
|
||||
node.detachChildAt(ii);
|
||||
nodes.remove(child.getName());
|
||||
}
|
||||
}
|
||||
return referenced.contains(node) || hasValidChildren;
|
||||
}
|
||||
|
||||
/** Converts a boxed Integer list to an unboxed int array. */
|
||||
protected static int[] toArray (ArrayList<Integer> list)
|
||||
{
|
||||
int[] array = new int[list.size()];
|
||||
for (int ii = 0, nn = list.size(); ii < nn; ii++) {
|
||||
array[ii] = list.get(ii);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Converts a boxed Float list to an unboxed float array. */
|
||||
protected static float[] toArray (ArrayList<Float> list)
|
||||
{
|
||||
float[] array = new float[list.size()];
|
||||
for (int ii = 0, nn = list.size(); ii < nn; ii++) {
|
||||
array[ii] = list.get(ii);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/** A wrapper for the model properties providing access to the properties
|
||||
* of a node within the model. */
|
||||
protected static class NodeProperties extends Properties
|
||||
{
|
||||
public NodeProperties (Properties mprops, String name)
|
||||
{
|
||||
_mprops = mprops;
|
||||
_prefix = name + ".";
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public String getProperty (String key)
|
||||
{
|
||||
return getProperty(key, null);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public String getProperty (String key, String defaultValue)
|
||||
{
|
||||
return _mprops.getProperty(_prefix + key,
|
||||
_mprops.getProperty(key, defaultValue));
|
||||
}
|
||||
|
||||
/** The properties of the model. */
|
||||
protected Properties _mprops;
|
||||
|
||||
/** The node prefix. */
|
||||
protected String _prefix;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// $Id: SceneParser.java 3749 2005-11-09 04:00:16Z mdb $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools.xml;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
|
||||
import com.threerings.jme.tools.AnimationDef;
|
||||
|
||||
/**
|
||||
* Parses XML files containing animations.
|
||||
*/
|
||||
public class AnimationParser
|
||||
{
|
||||
public AnimationParser ()
|
||||
{
|
||||
// create and configure our digester
|
||||
_digester = new Digester();
|
||||
|
||||
// add the rules
|
||||
String anim = "animation";
|
||||
_digester.addObjectCreate(anim, AnimationDef.class.getName());
|
||||
_digester.addRule(anim, new SetPropertyFieldsRule());
|
||||
_digester.addSetNext(anim, "setAnimation",
|
||||
AnimationDef.class.getName());
|
||||
|
||||
String frame = anim + "/frame";
|
||||
_digester.addObjectCreate(frame,
|
||||
AnimationDef.FrameDef.class.getName());
|
||||
_digester.addSetNext(frame, "addFrame",
|
||||
AnimationDef.FrameDef.class.getName());
|
||||
|
||||
String xform = frame + "/transform";
|
||||
_digester.addObjectCreate(xform,
|
||||
AnimationDef.TransformDef.class.getName());
|
||||
_digester.addRule(xform, new SetPropertyFieldsRule());
|
||||
_digester.addSetNext(xform, "addTransform",
|
||||
AnimationDef.TransformDef.class.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the XML file at the specified path into an animation
|
||||
* definition.
|
||||
*/
|
||||
public AnimationDef parseAnimation (String path)
|
||||
throws IOException, SAXException
|
||||
{
|
||||
_animation = null;
|
||||
_digester.push(this);
|
||||
_digester.parse(new FileInputStream(path));
|
||||
return _animation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the parser once the animation is parsed.
|
||||
*/
|
||||
public void setAnimation (AnimationDef animation)
|
||||
{
|
||||
_animation = animation;
|
||||
}
|
||||
|
||||
protected Digester _digester;
|
||||
protected AnimationDef _animation;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// $Id: SceneParser.java 3749 2005-11-09 04:00:16Z mdb $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.jme.tools.xml;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.xml.SetPropertyFieldsRule;
|
||||
|
||||
import com.threerings.jme.tools.ModelDef;
|
||||
|
||||
/**
|
||||
* Parses XML files containing 3D models.
|
||||
*/
|
||||
public class ModelParser
|
||||
{
|
||||
public ModelParser ()
|
||||
{
|
||||
// create and configure our digester
|
||||
_digester = new Digester();
|
||||
|
||||
// add the rules
|
||||
String model = "model";
|
||||
_digester.addObjectCreate(model, ModelDef.class.getName());
|
||||
_digester.addSetNext(model, "setModel", ModelDef.class.getName());
|
||||
|
||||
String tmesh = model + "/triMesh";
|
||||
_digester.addObjectCreate(tmesh, ModelDef.TriMeshDef.class.getName());
|
||||
_digester.addRule(tmesh, new SetPropertyFieldsRule());
|
||||
_digester.addSetNext(tmesh, "addSpatial",
|
||||
ModelDef.SpatialDef.class.getName());
|
||||
|
||||
String smesh = model + "/skinMesh";
|
||||
_digester.addObjectCreate(smesh,
|
||||
ModelDef.SkinMeshDef.class.getName());
|
||||
_digester.addRule(smesh, new SetPropertyFieldsRule());
|
||||
_digester.addSetNext(smesh, "addSpatial",
|
||||
ModelDef.SpatialDef.class.getName());
|
||||
|
||||
String node = model + "/node";
|
||||
_digester.addObjectCreate(node, ModelDef.NodeDef.class.getName());
|
||||
_digester.addRule(node, new SetPropertyFieldsRule());
|
||||
_digester.addSetNext(node, "addSpatial",
|
||||
ModelDef.SpatialDef.class.getName());
|
||||
|
||||
String vertex = tmesh + "/vertex", svertex = smesh + "/vertex";
|
||||
_digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
|
||||
_digester.addObjectCreate(svertex,
|
||||
ModelDef.SkinVertex.class.getName());
|
||||
_digester.addRule(vertex, new SetPropertyFieldsRule());
|
||||
_digester.addRule(svertex, new SetPropertyFieldsRule());
|
||||
_digester.addSetNext(vertex, "addVertex",
|
||||
ModelDef.Vertex.class.getName());
|
||||
_digester.addSetNext(svertex, "addVertex",
|
||||
ModelDef.Vertex.class.getName());
|
||||
|
||||
String bweight = smesh + "/vertex/boneWeight";
|
||||
_digester.addObjectCreate(bweight,
|
||||
ModelDef.BoneWeight.class.getName());
|
||||
_digester.addRule(bweight, new SetPropertyFieldsRule());
|
||||
_digester.addSetNext(bweight, "addBoneWeight",
|
||||
ModelDef.BoneWeight.class.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the XML file at the specified path into a model definition.
|
||||
*/
|
||||
public ModelDef parseModel (String path)
|
||||
throws IOException, SAXException
|
||||
{
|
||||
_model = null;
|
||||
_digester.push(this);
|
||||
_digester.parse(new FileInputStream(path));
|
||||
return _model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the parser once the model is parsed.
|
||||
*/
|
||||
public void setModel (ModelDef model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
protected Digester _digester;
|
||||
protected ModelDef _model;
|
||||
}
|
||||
Reference in New Issue
Block a user