Where possible, merge meshes with identical attributes.

git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@201 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Andrzej Kapolka
2007-04-18 21:06:08 +00:00
parent f7cb60407e
commit 488d47262b
2 changed files with 211 additions and 62 deletions
@@ -29,6 +29,7 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties; import java.util.Properties;
import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingBox;
@@ -141,6 +142,51 @@ public class ModelMesh extends TriMesh
Boolean.parseBoolean(tprops.getProperty("translucent")); Boolean.parseBoolean(tprops.getProperty("translucent"));
} }
/**
* Returns an opaque object representing this mesh's attributes. The
* object will implement {@link Object#hashCode} and {@link Object#equals}
* so that meshes with the same attributes will have identical keys.
*/
public Object getAttributeKey ()
{
return new AttributeKey(_textureKey, _textures, _sphereMapped,
_filterMode, _mipMapMode, _emissiveMap, _emissive, _additive,
_solid, _transparent, _alphaThreshold, _translucent);
}
/**
* Merges another mesh into this one. The mesh is assumed to be in the same coordinate system
* and have the same attributes.
*/
public void merge (ModelMesh mesh)
{
TriangleBatch batch = (TriangleBatch)getBatch(0);
TriangleBatch mbatch = (TriangleBatch)mesh.getBatch(0);
int vcount = batch.getVertexCount();
batch.setVertexBuffer(concatenate(batch.getVertexBuffer(), mbatch.getVertexBuffer()));
batch.setNormalBuffer(concatenate(batch.getNormalBuffer(), mbatch.getNormalBuffer()));
if (batch.getColorBuffer() != null && mbatch.getColorBuffer() != null) {
batch.setColorBuffer(concatenate(batch.getColorBuffer(), mbatch.getColorBuffer()));
}
batch.setTextureBuffer(concatenate(
batch.getTextureBuffer(0), mbatch.getTextureBuffer(0)), 0);
for (int ii = 1, nn = getTextureCount(); ii < nn; ii++) {
batch.setTextureBuffer(batch.getTextureBuffer(0), ii);
}
IntBuffer ibuf = batch.getIndexBuffer(), mibuf = mbatch.getIndexBuffer();
IntBuffer nibuf = BufferUtils.createIntBuffer(ibuf.capacity() + mibuf.capacity());
ibuf.clear();
nibuf.put(ibuf);
mibuf.clear();
while (mibuf.hasRemaining()) {
nibuf.put(mibuf.get() + vcount);
}
nibuf.clear();
batch.setIndexBuffer(nibuf);
}
/** /**
* Adjusts the vertices and the transform of the mesh so that the mesh's * Adjusts the vertices and the transform of the mesh so that the mesh's
* position lies at the center of its bounding volume. * position lies at the center of its bounding volume.
@@ -588,6 +634,20 @@ public class ModelMesh extends TriMesh
return astate; return astate;
} }
/**
* Concatenates the two provided buffers.
*/
protected static FloatBuffer concatenate (FloatBuffer b1, FloatBuffer b2)
{
FloatBuffer nb = BufferUtils.createFloatBuffer(b1.capacity() + b2.capacity());
b1.clear();
nb.put(b1);
b2.clear();
nb.put(b2);
nb.clear();
return nb;
}
/** /**
* Sorts the encoded triangle index/distance pairs in {@link #_tcodes} * Sorts the encoded triangle index/distance pairs in {@link #_tcodes}
* using a two-pass (16 bit) radix sort (as described by * using a two-pass (16 bit) radix sort (as described by
@@ -745,8 +805,29 @@ public class ModelMesh extends TriMesh
new RenderState[RenderState.RS_MAX_STATE]; new RenderState[RenderState.RS_MAX_STATE];
} }
/** The type of bounding volume that this mesh should use. */ /** A wrapper around an array of attributes that uses {@link Arrays#deepHashCode} and
protected int _boundingType; * {@link Arrays#deepEquals} for hashing/comparison. */
protected static class AttributeKey
{
public AttributeKey (Object... attrs)
{
_attrs = attrs;
}
@Override // documentation inherited
public int hashCode ()
{
return Arrays.deepHashCode(_attrs);
}
@Override // documentation inherited
public boolean equals (Object other)
{
return Arrays.deepEquals(_attrs, ((AttributeKey)other)._attrs);
}
protected Object[] _attrs;
}
/** The name of the texture specified in the model file, which acts as a /** The name of the texture specified in the model file, which acts as a
* property key and a default value. */ * property key and a default value. */
+128 -60
View File
@@ -39,7 +39,9 @@ import java.util.Set;
import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere; import com.jme.bounding.BoundingSphere;
import com.jme.math.FastMath; import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.scene.Spatial; import com.jme.scene.Spatial;
import com.jme.scene.batch.GeomBatch;
import com.jme.util.geom.BufferUtils; import com.jme.util.geom.BufferUtils;
import com.samskivert.util.PropertiesUtil; import com.samskivert.util.PropertiesUtil;
@@ -51,6 +53,7 @@ import com.threerings.jme.model.ModelController;
import com.threerings.jme.model.ModelMesh; import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode; import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh; import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.util.SpatialVisitor;
/** /**
* An intermediate representation for models used to store data parsed from * An intermediate representation for models used to store data parsed from
@@ -63,15 +66,15 @@ public class ModelDef
{ {
/** The node's name. */ /** The node's name. */
public String name; public String name;
/** The name of the node's parent. */ /** The name of the node's parent. */
public String parent; public String parent;
/** The node's transformation. */ /** The node's transformation. */
public float[] translation; public float[] translation;
public float[] rotation; public float[] rotation;
public float[] scale; public float[] scale;
/** Returns a JME node for this definition. */ /** Returns a JME node for this definition. */
public Spatial getSpatial (Properties props) public Spatial getSpatial (Properties props)
{ {
@@ -82,7 +85,7 @@ public class ModelDef
} }
return _spatial; return _spatial;
} }
/** Sets the transform of the created node. */ /** Sets the transform of the created node. */
protected void setTransform () protected void setTransform ()
{ {
@@ -92,10 +95,10 @@ public class ModelDef
rotation[2], rotation[3]); rotation[2], rotation[3]);
_spatial.getLocalScale().set(scale[0], scale[1], scale[2]); _spatial.getLocalScale().set(scale[0], scale[1], scale[2]);
} }
/** Creates a JME node for this definition. */ /** Creates a JME node for this definition. */
public abstract Spatial createSpatial (Properties props); public abstract Spatial createSpatial (Properties props);
/** Resolves any name references using the supplied map. */ /** Resolves any name references using the supplied map. */
public void resolveReferences ( public void resolveReferences (
HashMap<String, Spatial> nodes, HashSet<Spatial> referenced) HashMap<String, Spatial> nodes, HashSet<Spatial> referenced)
@@ -103,17 +106,17 @@ public class ModelDef
Spatial pnode = nodes.get(parent); Spatial pnode = nodes.get(parent);
if (pnode instanceof ModelNode) { if (pnode instanceof ModelNode) {
((ModelNode)pnode).attachChild(_spatial); ((ModelNode)pnode).attachChild(_spatial);
} else if (parent != null) { } else if (parent != null) {
Log.warning("Missing or invalid parent node [spatial=" + Log.warning("Missing or invalid parent node [spatial=" +
name + ", parent=" + parent + "]."); name + ", parent=" + parent + "].");
} }
} }
/** The JME node created for this definition. */ /** The JME node created for this definition. */
protected Spatial _spatial; protected Spatial _spatial;
} }
/** A rigid triangle mesh. */ /** A rigid triangle mesh. */
public static class TriMeshDef extends SpatialDef public static class TriMeshDef extends SpatialDef
{ {
@@ -121,25 +124,25 @@ public class ModelDef
public float[] offsetTranslation; public float[] offsetTranslation;
public float[] offsetRotation; public float[] offsetRotation;
public float[] offsetScale; public float[] offsetScale;
/** Whether or not the mesh allows back face culling. */ /** Whether or not the mesh allows back face culling. */
public boolean solid; public boolean solid;
/** The texture of the mesh, if any. */ /** The texture of the mesh, if any. */
public String texture; public String texture;
/** Whether or not the mesh is (partially) transparent. */ /** Whether or not the mesh is (partially) transparent. */
public boolean transparent; public boolean transparent;
/** The vertices of the mesh. */ /** The vertices of the mesh. */
public ArrayList<Vertex> vertices = new ArrayList<Vertex>(); public ArrayList<Vertex> vertices = new ArrayList<Vertex>();
/** The triangle indices. */ /** The triangle indices. */
public ArrayList<Integer> indices = new ArrayList<Integer>(); public ArrayList<Integer> indices = new ArrayList<Integer>();
/** Whether or not any of the vertices have texture coordinates. */ /** Whether or not any of the vertices have texture coordinates. */
public boolean tcoords; public boolean tcoords;
public void addVertex (Vertex vertex) public void addVertex (Vertex vertex)
{ {
int idx = vertices.indexOf(vertex); int idx = vertices.indexOf(vertex);
@@ -151,7 +154,7 @@ public class ModelDef
} }
tcoords = tcoords || vertex.tcoords != null; tcoords = tcoords || vertex.tcoords != null;
} }
// documentation inherited // documentation inherited
public Spatial createSpatial (Properties props) public Spatial createSpatial (Properties props)
{ {
@@ -163,13 +166,13 @@ public class ModelDef
} }
return node; return node;
} }
/** Creates the mesh to attach to the node. */ /** Creates the mesh to attach to the node. */
protected ModelMesh createMesh () protected ModelMesh createMesh ()
{ {
return new ModelMesh("mesh"); return new ModelMesh("mesh");
} }
/** Configures the mesh. */ /** Configures the mesh. */
protected void configureMesh (Properties props) protected void configureMesh (Properties props)
{ {
@@ -186,17 +189,17 @@ public class ModelDef
_mesh.getLocalScale().set(offsetScale[0], offsetScale[1], _mesh.getLocalScale().set(offsetScale[0], offsetScale[1],
offsetScale[2]); offsetScale[2]);
} }
// make sure texture is just a filename // make sure texture is just a filename
int sidx = (texture == null) ? -1 : int sidx = (texture == null) ? -1 :
Math.max(texture.lastIndexOf('/'), texture.lastIndexOf('\\')); Math.max(texture.lastIndexOf('/'), texture.lastIndexOf('\\'));
if (sidx != -1) { if (sidx != -1) {
texture = texture.substring(sidx + 1); texture = texture.substring(sidx + 1);
} }
// configure using properties // configure using properties
_mesh.configure(solid, texture, transparent, props); _mesh.configure(solid, texture, transparent, props);
// set the various buffers // set the various buffers
int vsize = vertices.size(); int vsize = vertices.size();
FloatBuffer vbuf = BufferUtils.createVector3Buffer(vsize), FloatBuffer vbuf = BufferUtils.createVector3Buffer(vsize),
@@ -210,19 +213,19 @@ public class ModelDef
ibuf.put(indices.get(ii)); ibuf.put(indices.get(ii));
} }
_mesh.reconstruct(vbuf, nbuf, null, tbuf, ibuf); _mesh.reconstruct(vbuf, nbuf, null, tbuf, ibuf);
_mesh.setModelBound("sphere".equals(props.getProperty("bound")) ? _mesh.setModelBound("sphere".equals(props.getProperty("bound")) ?
new BoundingSphere() : new BoundingBox()); new BoundingSphere() : new BoundingBox());
_mesh.updateModelBound(); _mesh.updateModelBound();
// set the mesh's origin to the center of its bounding box // set the mesh's origin to the center of its bounding box
_mesh.centerVertices(); _mesh.centerVertices();
} }
/** The mesh that contains the actual geometry. */ /** The mesh that contains the actual geometry. */
protected ModelMesh _mesh; protected ModelMesh _mesh;
} }
/** A triangle mesh that deforms according to bone positions. */ /** A triangle mesh that deforms according to bone positions. */
public static class SkinMeshDef extends TriMeshDef public static class SkinMeshDef extends TriMeshDef
{ {
@@ -231,7 +234,7 @@ public class ModelDef
{ {
return new SkinMesh("mesh"); return new SkinMesh("mesh");
} }
@Override // documentation inherited @Override // documentation inherited
public void resolveReferences ( public void resolveReferences (
HashMap<String, Spatial> nodes, HashSet<Spatial> referenced) HashMap<String, Spatial> nodes, HashSet<Spatial> referenced)
@@ -240,7 +243,7 @@ public class ModelDef
if (_mesh == null) { if (_mesh == null) {
return; return;
} }
// create and set the final weight groups // create and set the final weight groups
SkinMesh.WeightGroup[] wgroups = SkinMesh.WeightGroup[] wgroups =
new SkinMesh.WeightGroup[_groups.size()]; new SkinMesh.WeightGroup[_groups.size()];
@@ -268,7 +271,7 @@ public class ModelDef
} }
((SkinMesh)_mesh).setWeightGroups(wgroups); ((SkinMesh)_mesh).setWeightGroups(wgroups);
} }
@Override // documentation inherited @Override // documentation inherited
protected void configureMesh (Properties props) protected void configureMesh (Properties props)
{ {
@@ -286,7 +289,7 @@ public class ModelDef
group.weights.add(svertex.boneWeights.get(bone).weight); group.weights.add(svertex.boneWeights.get(bone).weight);
} }
} }
// reorder the vertices by group // reorder the vertices by group
ArrayList<Vertex> overts = vertices; ArrayList<Vertex> overts = vertices;
vertices = new ArrayList<Vertex>(); vertices = new ArrayList<Vertex>();
@@ -301,14 +304,14 @@ public class ModelDef
for (int ii = 0, nn = indices.size(); ii < nn; ii++) { for (int ii = 0, nn = indices.size(); ii < nn; ii++) {
indices.set(ii, imap[indices.get(ii)]); indices.set(ii, imap[indices.get(ii)]);
} }
super.configureMesh(props); super.configureMesh(props);
} }
/** The intermediate weight groups, mapped by bone names. */ /** The intermediate weight groups, mapped by bone names. */
protected HashMap<Set<String>, WeightGroupDef> _groups; protected HashMap<Set<String>, WeightGroupDef> _groups;
} }
/** A generic node. */ /** A generic node. */
public static class NodeDef extends SpatialDef public static class NodeDef extends SpatialDef
{ {
@@ -318,20 +321,20 @@ public class ModelDef
return new ModelNode(name); return new ModelNode(name);
} }
} }
/** A basic vertex. */ /** A basic vertex. */
public static class Vertex public static class Vertex
{ {
public float[] location; public float[] location;
public float[] normal; public float[] normal;
public float[] tcoords; public float[] tcoords;
public void setInBuffers ( public void setInBuffers (
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf) FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
{ {
vbuf.put(location); vbuf.put(location);
nbuf.put(normal); nbuf.put(normal);
if (tbuf != null) { if (tbuf != null) {
if (tcoords != null) { if (tcoords != null) {
tbuf.put(tcoords); tbuf.put(tcoords);
@@ -341,7 +344,7 @@ public class ModelDef
} }
} }
} }
public boolean equals (Object obj) public boolean equals (Object obj)
{ {
Vertex overt = (Vertex)obj; Vertex overt = (Vertex)obj;
@@ -350,14 +353,14 @@ public class ModelDef
Arrays.equals(tcoords, overt.tcoords); Arrays.equals(tcoords, overt.tcoords);
} }
} }
/** A vertex influenced by a number of bones. */ /** A vertex influenced by a number of bones. */
public static class SkinVertex extends Vertex public static class SkinVertex extends Vertex
{ {
/** The bones influencing the vertex, mapped by name. */ /** The bones influencing the vertex, mapped by name. */
public HashMap<String, BoneWeight> boneWeights = public HashMap<String, BoneWeight> boneWeights =
new HashMap<String, BoneWeight>(); new HashMap<String, BoneWeight>();
public void addBoneWeight (BoneWeight weight) public void addBoneWeight (BoneWeight weight)
{ {
if (weight.weight == 0f) { if (weight.weight == 0f) {
@@ -370,7 +373,7 @@ public class ModelDef
boneWeights.put(weight.bone, weight); boneWeights.put(weight.bone, weight);
} }
} }
/** Finds the bone nodes influencing this vertex. */ /** Finds the bone nodes influencing this vertex. */
public HashSet<ModelNode> getBones (HashMap<String, Spatial> nodes) public HashSet<ModelNode> getBones (HashMap<String, Spatial> nodes)
{ {
@@ -386,7 +389,7 @@ public class ModelDef
} }
return bones; return bones;
} }
/** Returns the weight of the given bone. */ /** Returns the weight of the given bone. */
public float getWeight (ModelNode bone) public float getWeight (ModelNode bone)
{ {
@@ -394,13 +397,13 @@ public class ModelDef
return (bweight == null) ? 0f : bweight.weight; return (bweight == null) ? 0f : bweight.weight;
} }
} }
/** The influence of a bone on a vertex. */ /** The influence of a bone on a vertex. */
public static class BoneWeight public static class BoneWeight
{ {
/** The name of the influencing bone. */ /** The name of the influencing bone. */
public String bone; public String bone;
/** The amount of influence. */ /** The amount of influence. */
public float weight; public float weight;
} }
@@ -410,21 +413,21 @@ public class ModelDef
{ {
/** The indices of the affected vertex. */ /** The indices of the affected vertex. */
public ArrayList<Integer> indices = new ArrayList<Integer>(); public ArrayList<Integer> indices = new ArrayList<Integer>();
/** The interleaved vertex weights. */ /** The interleaved vertex weights. */
public ArrayList<Float> weights = new ArrayList<Float>(); public ArrayList<Float> weights = new ArrayList<Float>();
} }
/** The meshes and bones comprising the model. */ /** The meshes and bones comprising the model. */
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>(); public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
public void addSpatial (SpatialDef spatial) public void addSpatial (SpatialDef spatial)
{ {
// put nodes before meshes so that bones are updated before skin // put nodes before meshes so that bones are updated before skin
spatials.add(spatial instanceof NodeDef ? 0 : spatials.size(), spatials.add(spatial instanceof NodeDef ? 0 : spatials.size(),
spatial); spatial);
} }
/** /**
* Creates the model node defined herein. * Creates the model node defined herein.
* *
@@ -434,16 +437,13 @@ public class ModelDef
public Model createModel (Properties props, HashMap<String, Spatial> nodes) public Model createModel (Properties props, HashMap<String, Spatial> nodes)
{ {
Model model = new Model(props.getProperty("name", "model"), props); 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 // start by creating the spatials and mapping them to their names
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) { for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
Spatial spatial = spatials.get(ii).getSpatial(props); Spatial spatial = spatials.get(ii).getSpatial(props);
nodes.put(spatial.getName(), spatial); nodes.put(spatial.getName(), spatial);
} }
// then go through again, resolving any name references and attaching // then go through again, resolving any name references and attaching
// root children // root children
HashSet<Spatial> referenced = new HashSet<Spatial>(); HashSet<Spatial> referenced = new HashSet<Spatial>();
@@ -454,12 +454,12 @@ public class ModelDef
model.attachChild(sdef.getSpatial(props)); model.attachChild(sdef.getSpatial(props));
} }
} }
// create any controllers listed // create any controllers listed
String[] controllers = StringUtil.parseStringArray( String[] controllers = StringUtil.parseStringArray(
props.getProperty("controllers", "")); props.getProperty("controllers", ""));
for (int ii = 0; ii < controllers.length; ii++) { for (int ii = 0; ii < controllers.length; ii++) {
Properties subProps = Properties subProps =
PropertiesUtil.getSubProperties(props, controllers[ii]); PropertiesUtil.getSubProperties(props, controllers[ii]);
String node = subProps.getProperty("node", controllers[ii]); String node = subProps.getProperty("node", controllers[ii]);
Spatial target = node.equals(model.getName()) ? Spatial target = node.equals(model.getName()) ?
@@ -471,15 +471,24 @@ public class ModelDef
ModelController ctrl = createController(subProps, target); ModelController ctrl = createController(subProps, target);
if (ctrl != null) { if (ctrl != null) {
model.addController(ctrl); model.addController(ctrl);
referenced.add(target);
} }
} }
// in non-animated models, merge meshes with the same attributes
if (StringUtil.isBlank(props.getProperty("animations"))) {
mergeEquivalentMeshes(model, nodes, referenced);
}
// get rid of any nodes that serve no purpose // get rid of any nodes that serve no purpose
pruneUnusedNodes(model, nodes, referenced); pruneUnusedNodes(model, nodes, referenced);
// set the overall scale
model.setLocalScale(Float.parseFloat(props.getProperty("scale", "1")));
return model; return model;
} }
/** Creates, configures, and returns a model controller. */ /** Creates, configures, and returns a model controller. */
protected ModelController createController ( protected ModelController createController (
Properties props, Spatial target) Properties props, Spatial target)
@@ -497,7 +506,7 @@ public class ModelDef
ctrl.configure(props, target); ctrl.configure(props, target);
return ctrl; return ctrl;
} }
/** Recursively removes any unused nodes. */ /** Recursively removes any unused nodes. */
protected boolean pruneUnusedNodes ( protected boolean pruneUnusedNodes (
ModelNode node, HashMap<String, Spatial> nodes, ModelNode node, HashMap<String, Spatial> nodes,
@@ -516,7 +525,66 @@ public class ModelDef
} }
return referenced.contains(node) || hasValidChildren; return referenced.contains(node) || hasValidChildren;
} }
/** Merges meshes with the same attributes. */
protected void mergeEquivalentMeshes (
Model model, HashMap<String, Spatial> nodes, final HashSet<Spatial> referenced)
{
final HashMap<Object, ArrayList<ModelMesh>> meshes =
new HashMap<Object, ArrayList<ModelMesh>>();
new SpatialVisitor<ModelMesh>(ModelMesh.class) {
public void traverse (Spatial spatial) {
// cut out when we encounter referenced nodes
if (!referenced.contains(spatial)) {
spatial.updateWorldVectors();
super.traverse(spatial);
}
}
protected void visit (ModelMesh mesh) {
// make sure we don't try this with skinned meshes
if (mesh instanceof SkinMesh) {
return;
}
Object key = mesh.getAttributeKey();
ArrayList<ModelMesh> list = meshes.get(key);
if (list == null) {
meshes.put(key, list = new ArrayList<ModelMesh>());
}
list.add(mesh);
}
}.traverse(model);
for (ArrayList<ModelMesh> list : meshes.values()) {
// the first in the list becomes the base
ModelMesh base = list.get(0);
flattenTransforms(base);
model.attachChild(base);
// the rest are merged with it
for (int ii = 1, nn = list.size(); ii < nn; ii++) {
ModelMesh mesh = list.get(ii);
flattenTransforms(mesh);
mesh.getParent().detachChild(mesh);
base.merge(mesh);
}
// update the model bound and recenter
base.updateModelBound();
base.centerVertices();
}
}
/** Transforms the mesh's vertices and normals into model coordinates. */
protected static void flattenTransforms (ModelMesh mesh)
{
GeomBatch batch = mesh.getBatch(0);
batch.getWorldCoords(batch.getVertexBuffer());
batch.getWorldNormals(batch.getNormalBuffer());
mesh.getLocalTranslation().zero();
mesh.getLocalRotation().loadIdentity();
mesh.getLocalScale().set(Vector3f.UNIT_XYZ);
}
/** Converts a boxed Integer list to an unboxed int array. */ /** Converts a boxed Integer list to an unboxed int array. */
protected static int[] toArray (ArrayList<Integer> list) protected static int[] toArray (ArrayList<Integer> list)
{ {
@@ -526,7 +594,7 @@ public class ModelDef
} }
return array; return array;
} }
/** Converts a boxed Float list to an unboxed float array. */ /** Converts a boxed Float list to an unboxed float array. */
protected static float[] toArray (ArrayList<Float> list) protected static float[] toArray (ArrayList<Float> list)
{ {