diff --git a/rsrc/i18n/jme/viewer.properties b/rsrc/i18n/jme/viewer.properties index b5363738..bc5b2643 100644 --- a/rsrc/i18n/jme/viewer.properties +++ b/rsrc/i18n/jme/viewer.properties @@ -23,6 +23,9 @@ m.mode_flipbook = Flipbook m.mode_morph = Morph m.mode_skin = Skin +m.model_variant = Model Variant +m.variant_default = Default + m.load_title = Select Model to Load m.load_filter = Model Files (*.properties, *.dat) diff --git a/src/java/com/threerings/jme/model/Model.java b/src/java/com/threerings/jme/model/Model.java index 9be7d687..300bcfdb 100644 --- a/src/java/com/threerings/jme/model/Model.java +++ b/src/java/com/threerings/jme/model/Model.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Properties; import com.jme.bounding.BoundingVolume; @@ -53,9 +54,12 @@ import com.jme.scene.Node; import com.jme.scene.Spatial; import com.samskivert.util.ObserverList; +import com.samskivert.util.PropertiesUtil; import com.samskivert.util.RandomUtil; +import com.samskivert.util.StringUtil; import com.threerings.jme.Log; +import com.threerings.jme.util.SpatialVisitor; /** * The base node for models. @@ -400,6 +404,14 @@ public class Model extends ModelNode initInstance(); } + /** + * Returns the names of the model's variant configurations. + */ + public String[] getVariantNames () + { + return StringUtil.parseStringArray(_props.getProperty("variants", "")); + } + /** * Adds an animation to the model's library. This should only be called by * the model compiler. @@ -716,6 +728,39 @@ public class Model extends ModelNode } } + /** + * Creates a new prototype using the given variant configuration. + * Use {@link #createInstance} on the returned prototype to create + * additional instances of the variant. + */ + public Model createPrototype (String variant) + { + if (_prototype != null) { + return _prototype.createPrototype(variant); + } + // create an instance and rebind all animations + final Model prototype = createInstance(); + for (Map.Entry entry : _anims.entrySet()) { + prototype._anims.put(entry.getKey(), + entry.getValue().rebind(prototype._pnodes)); + } + prototype._prototype = null; + prototype._pnodes = null; + + // reconfigure meshes with new variant type + if (variant != null) { + prototype._props = PropertiesUtil.getFilteredProperties( + _props, variant); + new SpatialVisitor(ModelMesh.class) { + public void visit (ModelMesh mesh) { + mesh.reconfigure(PropertiesUtil.getFilteredProperties( + prototype._props, mesh.getParent().getName())); + } + }.traverse(prototype); + } + return prototype; + } + /** * Creates and returns a new instance of this model. */ diff --git a/src/java/com/threerings/jme/model/ModelMesh.java b/src/java/com/threerings/jme/model/ModelMesh.java index d72918a9..b91b16b5 100644 --- a/src/java/com/threerings/jme/model/ModelMesh.java +++ b/src/java/com/threerings/jme/model/ModelMesh.java @@ -60,6 +60,7 @@ import com.jme.scene.state.ZBufferState; import com.jme.system.DisplaySystem; import com.jme.util.geom.BufferUtils; +import com.samskivert.util.PropertiesUtil; import com.samskivert.util.StringUtil; import com.threerings.jme.Log; @@ -86,6 +87,16 @@ public class ModelMesh extends TriMesh super(name); } + /** + * Reconfigures this model with a new set of (sub-)properties. Textures + * must be (re-)resolved after calling this method. + */ + public void reconfigure (Properties props) + { + configure(_solid, _textureKey, _transparent, props); + setRenderStates(); + } + /** * Configures this mesh based on the given parameters and (sub-)properties. * @@ -97,23 +108,23 @@ public class ModelMesh extends TriMesh public void configure ( boolean solid, String texture, boolean transparent, Properties props) { + _textureKey = texture; _textures = (texture == null) ? null : StringUtil.parseStringArray( props.getProperty(texture, texture)); - _sphereMapped = Boolean.parseBoolean( - getSubProperty(props, texture, "sphere_map")); - _filterMode = "nearest".equals( - getSubProperty(props, texture, "filter")) ? + Properties tprops = PropertiesUtil.getFilteredProperties( + props, texture); + _sphereMapped = Boolean.parseBoolean(tprops.getProperty("sphere_map")); + _filterMode = "nearest".equals(tprops.getProperty("filter")) ? Texture.FM_NEAREST : Texture.FM_LINEAR; - _mipMapMode = getMipMapMode( - getSubProperty(props, texture, "mipmap")); - _emissiveMap = getSubProperty(props, texture, "emissive"); + _mipMapMode = getMipMapMode(tprops.getProperty("mipmap")); + _emissiveMap = tprops.getProperty("emissive"); _solid = solid; _transparent = transparent; - String threshold = getSubProperty(props, texture, "alpha_threshold"); + String threshold = tprops.getProperty("alpha_threshold"); _alphaThreshold = (threshold == null) ? DEFAULT_ALPHA_THRESHOLD : Float.parseFloat(threshold); - _translucent = _transparent && Boolean.parseBoolean( - getSubProperty(props, texture, "translucent")); + _translucent = _transparent && + Boolean.parseBoolean(tprops.getProperty("translucent")); } /** @@ -144,26 +155,7 @@ public class ModelMesh extends TriMesh storeOriginalBuffers(); // 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); - if (_translucent) { - setRenderState(_blendAlpha); - setRenderState(_overlayZBuffer); - } else if (_alphaThreshold == DEFAULT_ALPHA_THRESHOLD) { - setRenderState(_defaultTestAlpha); - } else { - setRenderState(createTestAlpha(_alphaThreshold)); - } - } + setRenderStates(); } /** @@ -308,6 +300,7 @@ public class ModelMesh extends TriMesh mbatch.setModelBound(properties.isSet("bound") ? batch.getModelBound() : batch.getModelBound().clone(null)); } + mstore._textureKey = _textureKey; if (_textures != null && _textures.length > 1) { int tidx = properties.random % _textures.length; mstore._textures = new String[] { _textures[tidx] }; @@ -352,6 +345,7 @@ public class ModelMesh extends TriMesh out.writeInt(_colorBufferSize); out.writeInt(_textureBufferSize); out.writeInt(_indexBufferSize); + out.writeObject(_textureKey); out.writeObject(_textures); out.writeBoolean(_sphereMapped); out.writeInt(_filterMode); @@ -377,6 +371,7 @@ public class ModelMesh extends TriMesh _colorBufferSize = in.readInt(); _textureBufferSize = in.readInt(); _indexBufferSize = in.readInt(); + _textureKey = (String)in.readObject(); _textures = (String[])in.readObject(); _sphereMapped = in.readBoolean(); _filterMode = in.readInt(); @@ -445,6 +440,8 @@ public class ModelMesh extends TriMesh } if (_tstates[0] != null) { setRenderState(_tstates[0]); + } else { + clearRenderState(RenderState.RS_TEXTURE); } } @@ -617,6 +614,34 @@ public class ModelMesh extends TriMesh FloatBuffer.wrap(_vbuf = new float[vbuf.capacity()]).put(vbuf); } + /** + * Sets the model's render states (excluding the texture state, which is + * set by {@link #resolveTextures}) according to its configuration. + */ + protected void setRenderStates () + { + if (DisplaySystem.getDisplaySystem() == null) { + return; + } + if (_backCull == null) { + initSharedStates(); + } + if (_solid) { + setRenderState(_backCull); + } + if (_transparent) { + setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); + if (_translucent) { + setRenderState(_blendAlpha); + setRenderState(_overlayZBuffer); + } else if (_alphaThreshold == DEFAULT_ALPHA_THRESHOLD) { + setRenderState(_defaultTestAlpha); + } else { + setRenderState(createTestAlpha(_alphaThreshold)); + } + } + } + /** * Locks the transform and bounds of this mesh on the assumption that its * position will not change. @@ -627,17 +652,6 @@ public class ModelMesh extends TriMesh _transformLocked = true; } - /** - * Gets a sub-property from the given set, falling back on the main - * property if the sub-property is not specified. - */ - protected static String getSubProperty ( - Properties props, String prefix, String property) - { - return props.getProperty(prefix + "." + property, - props.getProperty(property)); - } - /** * Returns the mip-map mode corresponding to the given string * (defaulting to {@link Texture#MM_LINEAR_LINEAR}). @@ -876,6 +890,10 @@ public class ModelMesh extends TriMesh /** The type of bounding volume that this mesh should use. */ protected int _boundingType; + /** The name of the texture specified in the model file, which acts as a + * property key and a default value. */ + protected String _textureKey; + /** The name of this model's textures, or null for none. */ protected String[] _textures; diff --git a/src/java/com/threerings/jme/tools/ModelDef.java b/src/java/com/threerings/jme/tools/ModelDef.java index 2c7cf30b..713640a9 100644 --- a/src/java/com/threerings/jme/tools/ModelDef.java +++ b/src/java/com/threerings/jme/tools/ModelDef.java @@ -76,7 +76,8 @@ public class ModelDef public Spatial getSpatial (Properties props) { if (_spatial == null) { - _spatial = createSpatial(new NodeProperties(props, name)); + _spatial = createSpatial( + PropertiesUtil.getFilteredProperties(props, name)); setTransform(); } return _spatial; @@ -541,34 +542,4 @@ public class ModelDef } 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; - } } diff --git a/src/java/com/threerings/jme/tools/ModelViewer.java b/src/java/com/threerings/jme/tools/ModelViewer.java index 44af1f24..7fb14d4f 100644 --- a/src/java/com/threerings/jme/tools/ModelViewer.java +++ b/src/java/com/threerings/jme/tools/ModelViewer.java @@ -100,6 +100,7 @@ import com.jmex.effects.particles.ParticleGeometry; import com.samskivert.swing.GroupLayout; import com.samskivert.swing.Spacer; import com.samskivert.util.Config; +import com.samskivert.util.ObjectUtil; import com.samskivert.util.StringUtil; import com.threerings.resource.ResourceManager; @@ -230,6 +231,9 @@ public class ModelViewer extends JmeCanvasApp view.add(_normals); view.addSeparator(); + _vmenu = new JMenu(_msg.get("m.model_variant")); + view.add(_vmenu); + JMenu amode = new JMenu(_msg.get("m.animation_mode")); final JRadioButtonMenuItem flipbook = new JRadioButtonMenuItem(_msg.get("m.mode_flipbook")), @@ -252,6 +256,7 @@ public class ModelViewer extends JmeCanvasApp } } }; + mgroup.add(flipbook); mgroup.add(morph); mgroup.add(skin); @@ -591,11 +596,82 @@ public class ModelViewer extends JmeCanvasApp if (_model != null) { _ctx.getGeometry().detachChild(_model); } - _ctx.getGeometry().attachChild(_model = model); + _ctx.getGeometry().attachChild(_model = _omodel = model); _model.setAnimationMode(_animMode); _model.lockStaticMeshes(_ctx.getRenderer(), true, true); - // resolve the textures from the file's directory + // load the model's textures + resolveModelTextures(file); + + // recenter the camera + _model.updateGeometricState(0f, true); + ((OrbitCameraHandler)_camhand).recenter(); + + // configure the variant menu + _variant = null; + _vmenu.removeAll(); + ButtonGroup vgroup = new ButtonGroup() { + public void setSelected (ButtonModel model, boolean b) { + super.setSelected(model, b); + String variant = model.getActionCommand(); + if (b && !ObjectUtil.equals(variant, _variant)) { + setVariant(model.getActionCommand()); + } + } + }; + JRadioButtonMenuItem def = new JRadioButtonMenuItem( + _msg.get("m.variant_default")); + def.setActionCommand(null); + vgroup.add(def); + _vmenu.add(def); + for (String variant : _model.getVariantNames()) { + JRadioButtonMenuItem vitem = new JRadioButtonMenuItem(variant); + vitem.setActionCommand(variant); + vgroup.add(vitem); + _vmenu.add(vitem); + } + vgroup.setSelected(def.getModel(), true); + + // configure the animation panel + String[] anims = _model.getAnimationNames(); + if (anims.length == 0) { + _animctrls.setVisible(false); + return; + } + _model.addAnimationObserver(_animobs); + _animctrls.setVisible(true); + DefaultComboBoxModel abmodel = new DefaultComboBoxModel(anims); + _animbox.setModel(abmodel); + updateAnimationSpeed(); + + // if there are any sequences, add those as well + String[] seqs = StringUtil.parseStringArray( + _model.getProperties().getProperty("sequences", "")); + for (String seq : seqs) { + abmodel.addElement(seq); + } + } + + /** + * Switches to the named variant. + */ + protected void setVariant (String variant) + { + _model.stopAnimation(); + _ctx.getGeometry().detachChild(_model); + _ctx.getGeometry().attachChild( + _model = _omodel.createPrototype(variant)); + _model.addAnimationObserver(_animobs); + updateAnimationSpeed(); + resolveModelTextures(_loaded); + _variant = variant; + } + + /** + * Resolve the textures from the file's directory. + */ + protected void resolveModelTextures (File file) + { final File dir = file.getParentFile(); _model.resolveTextures(new TextureProvider() { public TextureState getTexture (String name) { @@ -625,29 +701,6 @@ public class ModelViewer extends JmeCanvasApp new HashMap(); }); _model.updateRenderState(); - - // recenter the camera - _model.updateGeometricState(0f, true); - ((OrbitCameraHandler)_camhand).recenter(); - - // configure the animation panel - String[] anims = _model.getAnimationNames(); - if (anims.length == 0) { - _animctrls.setVisible(false); - return; - } - _model.addAnimationObserver(_animobs); - _animctrls.setVisible(true); - DefaultComboBoxModel abmodel = new DefaultComboBoxModel(anims); - _animbox.setModel(abmodel); - updateAnimationSpeed(); - - // if there are any sequences, add those as well - String[] seqs = StringUtil.parseStringArray( - _model.getProperties().getProperty("sequences", "")); - for (String seq : seqs) { - abmodel.addElement(seq); - } } /** @@ -731,6 +784,9 @@ public class ModelViewer extends JmeCanvasApp /** The viewer frame. */ protected JFrame _frame; + /** The variant menu. */ + protected JMenu _vmenu; + /** Debug view switches. */ protected JCheckBoxMenuItem _pivots, _bounds, _normals; @@ -758,6 +814,9 @@ public class ModelViewer extends JmeCanvasApp /** The desired animation mode. */ protected Model.AnimationMode _animMode; + /** The desired variant. */ + protected String _variant; + /** The light rotation dialog. */ protected RotateLightDialog _rldialog; @@ -770,6 +829,9 @@ public class ModelViewer extends JmeCanvasApp /** The currently loaded model. */ protected Model _model; + /** The original model (before switching to a variant). */ + protected Model _omodel; + /** Reused to draw pivot axes. */ protected Line _axes;