51c01493f2
git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@490 ed5b42cb-e716-0410-a449-f6a68f950b19
911 lines
32 KiB
Java
911 lines
32 KiB
Java
//
|
|
// $Id$
|
|
//
|
|
// Nenya library - tools for developing networked games
|
|
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
|
// http://www.threerings.net/code/nenya/
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License as published
|
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package com.threerings.jme.model;
|
|
|
|
import java.io.IOException;
|
|
import java.io.ObjectInput;
|
|
import java.io.ObjectOutput;
|
|
|
|
import java.nio.FloatBuffer;
|
|
import java.nio.IntBuffer;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Properties;
|
|
|
|
import com.jme.bounding.BoundingBox;
|
|
import com.jme.bounding.BoundingSphere;
|
|
import com.jme.bounding.BoundingVolume;
|
|
import com.jme.image.Image;
|
|
import com.jme.image.Texture;
|
|
import com.jme.math.FastMath;
|
|
import com.jme.math.Quaternion;
|
|
import com.jme.math.Vector3f;
|
|
import com.jme.renderer.ColorRGBA;
|
|
import com.jme.renderer.Renderer;
|
|
import com.jme.scene.Controller;
|
|
import com.jme.scene.SharedMesh;
|
|
import com.jme.scene.Spatial;
|
|
import com.jme.scene.TriMesh;
|
|
import com.jme.scene.VBOInfo;
|
|
import com.jme.scene.batch.GeomBatch;
|
|
import com.jme.scene.batch.TriangleBatch;
|
|
import com.jme.scene.state.AlphaState;
|
|
import com.jme.scene.state.CullState;
|
|
import com.jme.scene.state.FogState;
|
|
import com.jme.scene.state.LightState;
|
|
import com.jme.scene.state.RenderState;
|
|
import com.jme.scene.state.TextureState;
|
|
import com.jme.scene.state.ZBufferState;
|
|
import com.jme.system.DisplaySystem;
|
|
import com.jme.util.export.JMEExporter;
|
|
import com.jme.util.export.JMEImporter;
|
|
import com.jme.util.export.InputCapsule;
|
|
import com.jme.util.export.OutputCapsule;
|
|
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.util.ShaderCache;
|
|
|
|
/**
|
|
* A {@link TriMesh} with a serialization mechanism tailored to stored models.
|
|
*/
|
|
public class ModelMesh extends TriMesh
|
|
implements ModelSpatial
|
|
{
|
|
/**
|
|
* No-arg constructor for deserialization.
|
|
*/
|
|
public ModelMesh ()
|
|
{
|
|
super("mesh");
|
|
}
|
|
|
|
/**
|
|
* Creates a mesh with no vertex data.
|
|
*/
|
|
public ModelMesh (String name)
|
|
{
|
|
super(name);
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public int hashCode ()
|
|
{
|
|
// hash on the name rather than the identity for consistent ordering
|
|
return getName().hashCode();
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @param texture the texture specified in the model export, if any (can be
|
|
* overridden by textures specified in the properties)
|
|
* @param solid whether or not the mesh allows back face culling
|
|
* @param transparent whether or not the mesh is (partially) transparent
|
|
*/
|
|
public void configure (
|
|
boolean solid, String texture, boolean transparent, Properties props)
|
|
{
|
|
_textureKey = texture;
|
|
_textures = (texture == null) ? null : StringUtil.parseStringArray(
|
|
props.getProperty(texture, texture));
|
|
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(tprops.getProperty("mipmap"));
|
|
_compress = Boolean.parseBoolean(tprops.getProperty("compress", "true"));
|
|
String emissive = tprops.getProperty("emissive");
|
|
if (Boolean.parseBoolean(emissive)) {
|
|
_emissive = true;
|
|
} else {
|
|
_emissiveMap = emissive;
|
|
}
|
|
_additive = Boolean.parseBoolean(tprops.getProperty("additive"));
|
|
_solid = solid;
|
|
_transparent = transparent;
|
|
String threshold = tprops.getProperty("alpha_threshold");
|
|
_alphaThreshold = (threshold == null) ?
|
|
DEFAULT_ALPHA_THRESHOLD : Float.parseFloat(threshold);
|
|
_translucent = _transparent &&
|
|
Boolean.parseBoolean(tprops.getProperty("translucent"));
|
|
}
|
|
|
|
/**
|
|
* Adjusts the vertices and the transform of the mesh so that the mesh's
|
|
* position lies at the center of its bounding volume.
|
|
*/
|
|
public void centerVertices ()
|
|
{
|
|
Vector3f offset = getBatch(0).getModelBound().getCenter().negate();
|
|
if (!offset.equals(Vector3f.ZERO)) {
|
|
getLocalTranslation().subtractLocal(offset);
|
|
getBatch(0).getModelBound().getCenter().set(Vector3f.ZERO);
|
|
getBatch(0).translatePoints(offset);
|
|
}
|
|
storeOriginalBuffers();
|
|
}
|
|
|
|
/**
|
|
* Adds an overlay layer to this mesh. After the mesh is rendered with
|
|
* its configured states, these states will be applied and the mesh will
|
|
* be rendered again.
|
|
*/
|
|
public void addOverlay (RenderState[] overlay)
|
|
{
|
|
if (_overlays == null) {
|
|
_overlays = new ArrayList<RenderState[]>(1);
|
|
}
|
|
_overlays.add(overlay);
|
|
}
|
|
|
|
/**
|
|
* Removes a layer from this mesh.
|
|
*/
|
|
public void removeOverlay (RenderState[] overlay)
|
|
{
|
|
if (_overlays != null) {
|
|
_overlays.remove(overlay);
|
|
if (_overlays.isEmpty()) {
|
|
_overlays = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void reconstruct (
|
|
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
|
|
FloatBuffer textures, IntBuffer indices)
|
|
{
|
|
super.reconstruct(vertices, normals, colors, textures, indices);
|
|
for (int ii = 1, nn = getTextureCount(); ii < nn; ii++) {
|
|
setTextureBuffer(0, getTextureBuffer(0, 0), ii);
|
|
}
|
|
|
|
// store any buffers that will be manipulated on a per-instance basis
|
|
storeOriginalBuffers();
|
|
|
|
// initialize the model if we're displaying
|
|
setRenderStates();
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public Spatial putClone (Spatial store, Model.CloneCreator properties)
|
|
{
|
|
ModelMesh mstore = (ModelMesh)properties.originalToCopy.get(this);
|
|
if (mstore != null) {
|
|
return mstore;
|
|
} else if (store == null) {
|
|
mstore = new ModelMesh(getName());
|
|
} else {
|
|
mstore = (ModelMesh)store;
|
|
}
|
|
properties.originalToCopy.put(this, mstore);
|
|
mstore.normalsMode = normalsMode;
|
|
mstore.cullMode = cullMode;
|
|
for (int ii = 0; ii < RenderState.RS_MAX_STATE; ii++) {
|
|
RenderState rstate = getRenderState(ii);
|
|
if (rstate != null) {
|
|
mstore.setRenderState(rstate);
|
|
}
|
|
}
|
|
mstore.renderQueueMode = renderQueueMode;
|
|
mstore.lockedMode = lockedMode;
|
|
mstore.lightCombineMode = lightCombineMode;
|
|
mstore.textureCombineMode = textureCombineMode;
|
|
mstore.name = name;
|
|
mstore.isCollidable = isCollidable;
|
|
mstore.localRotation.set(localRotation);
|
|
mstore.localTranslation.set(localTranslation);
|
|
mstore.localScale.set(localScale);
|
|
for (Object controller : getControllers()) {
|
|
if (controller instanceof ModelController) {
|
|
mstore.addController(
|
|
((ModelController)controller).putClone(null, properties));
|
|
}
|
|
}
|
|
TriangleBatch batch = getBatch(0), mbatch = mstore.getBatch(0);
|
|
mbatch.setVertexBuffer(properties.isSet("vertices") ?
|
|
batch.getVertexBuffer() :
|
|
BufferUtils.clone(batch.getVertexBuffer()));
|
|
mbatch.setColorBuffer(properties.isSet("colors") ?
|
|
batch.getColorBuffer() :
|
|
BufferUtils.clone(batch.getColorBuffer()));
|
|
mbatch.setNormalBuffer(properties.isSet("normals") ?
|
|
batch.getNormalBuffer() :
|
|
BufferUtils.clone(batch.getNormalBuffer()));
|
|
FloatBuffer texcoords;
|
|
for (int ii = 0; (texcoords = batch.getTextureBuffer(ii)) != null;
|
|
ii++) {
|
|
mbatch.setTextureBuffer(properties.isSet("texcoords") ?
|
|
texcoords : BufferUtils.clone(texcoords), ii);
|
|
}
|
|
mbatch.setIndexBuffer((properties.isSet("indices") && !_translucent) ?
|
|
batch.getIndexBuffer() :
|
|
BufferUtils.clone(batch.getIndexBuffer()));
|
|
if (properties.isSet("vboinfo")) {
|
|
mbatch.setVBOInfo(batch.getVBOInfo());
|
|
}
|
|
if (properties.isSet("obbtree")) {
|
|
mbatch.setCollisionTree(batch.getCollisionTree());
|
|
}
|
|
if (properties.isSet("displaylistid")) {
|
|
mbatch.setDisplayListID(batch.getDisplayListID());
|
|
}
|
|
if (batch.getModelBound() != null) {
|
|
mbatch.setModelBound(properties.isSet("bound") ?
|
|
batch.getModelBound() : batch.getModelBound().clone(null));
|
|
}
|
|
mstore._textureKey = _textureKey;
|
|
if (_textures != null && _textures.length > 1) {
|
|
int tidx = properties.random % _textures.length;
|
|
mstore._textures = new String[] { _textures[tidx] };
|
|
mstore._tstates = new TextureState[] { _tstates[tidx] };
|
|
mstore.setRenderState(_tstates[tidx]);
|
|
} else {
|
|
mstore._textures = _textures;
|
|
mstore._tstates = _tstates;
|
|
}
|
|
mstore._sphereMapped = _sphereMapped;
|
|
mstore._filterMode = _filterMode;
|
|
mstore._mipMapMode = _mipMapMode;
|
|
mstore._compress = _compress;
|
|
mstore._emissiveMap = _emissiveMap;
|
|
mstore._emissive = _emissive;
|
|
mstore._additive = _additive;
|
|
mstore._solid = _solid;
|
|
mstore._transparent = _transparent;
|
|
mstore._alphaThreshold = _alphaThreshold;
|
|
mstore._translucent = _translucent;
|
|
mstore._oibuf = _oibuf;
|
|
mstore._vbuf = _vbuf;
|
|
return mstore;
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void updateWorldVectors ()
|
|
{
|
|
if (!_transformLocked) {
|
|
super.updateWorldVectors();
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void read (JMEImporter im)
|
|
throws IOException
|
|
{
|
|
InputCapsule capsule = im.getCapsule(this);
|
|
setName(capsule.readString("name", null));
|
|
setLocalTranslation((Vector3f)capsule.readSavable(
|
|
"localTranslation", null));
|
|
setLocalRotation((Quaternion)capsule.readSavable(
|
|
"localRotation", null));
|
|
setLocalScale((Vector3f)capsule.readSavable(
|
|
"localScale", null));
|
|
TriangleBatch batch = getBatch(0);
|
|
batch.setModelBound((BoundingVolume)capsule.readSavable(
|
|
"modelBound", null));
|
|
_textureKey = capsule.readString("textureKey", null);
|
|
_textures = capsule.readStringArray("textures", null);
|
|
_sphereMapped = capsule.readBoolean("sphereMapped", false);
|
|
_filterMode = capsule.readInt("filterMode", Texture.FM_LINEAR);
|
|
_mipMapMode = capsule.readInt("mipMapMode", Texture.MM_LINEAR_LINEAR);
|
|
_compress = capsule.readBoolean("compress", true);
|
|
_emissiveMap = capsule.readString("emissiveMap", null);
|
|
_emissive = capsule.readBoolean("emissive", false);
|
|
_additive = capsule.readBoolean("additive", false);
|
|
_solid = capsule.readBoolean("solid", true);
|
|
_transparent = capsule.readBoolean("transparent", false);
|
|
_alphaThreshold = capsule.readFloat("alphaThreshold",
|
|
DEFAULT_ALPHA_THRESHOLD);
|
|
_translucent = capsule.readBoolean("translucent", false);
|
|
|
|
reconstruct(capsule.readFloatBuffer("vertexBuffer", null),
|
|
capsule.readFloatBuffer("normalBuffer", null), null,
|
|
capsule.readFloatBuffer("textureBuffer", null),
|
|
capsule.readIntBuffer("indexBuffer", null));
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void write (JMEExporter ex)
|
|
throws IOException
|
|
{
|
|
OutputCapsule capsule = ex.getCapsule(this);
|
|
capsule.write(getName(), "name", null);
|
|
capsule.write(getLocalTranslation(), "localTranslation", null);
|
|
capsule.write(getLocalRotation(), "localRotation", null);
|
|
capsule.write(getLocalScale(), "localScale", null);
|
|
capsule.write(getBatch(0).getModelBound(), "modelBound", null);
|
|
capsule.write(getVertexBuffer(0), "vertexBuffer", null);
|
|
capsule.write(getNormalBuffer(0), "normalBuffer", null);
|
|
capsule.write(getTextureBuffer(0, 0), "textureBuffer", null);
|
|
capsule.write(getIndexBuffer(0), "indexBuffer", null);
|
|
capsule.write(_textureKey, "textureKey", null);
|
|
capsule.write(_textures, "textures", null);
|
|
capsule.write(_sphereMapped, "sphereMapped", false);
|
|
capsule.write(_filterMode, "filterMode", Texture.FM_LINEAR);
|
|
capsule.write(_mipMapMode, "mipMapMode", Texture.MM_LINEAR_LINEAR);
|
|
capsule.write(_compress, "compress", true);
|
|
capsule.write(_emissiveMap, "emissiveMap", null);
|
|
capsule.write(_emissive, "emissive", false);
|
|
capsule.write(_additive, "additive", false);
|
|
capsule.write(_solid, "solid", true);
|
|
capsule.write(_transparent, "transparent", false);
|
|
capsule.write(_alphaThreshold, "alphaThreshold",
|
|
DEFAULT_ALPHA_THRESHOLD);
|
|
capsule.write(_translucent, "translucent", false);
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void expandModelBounds ()
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void setReferenceTransforms ()
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void lockStaticMeshes (
|
|
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
|
|
{
|
|
if (useVBOs && renderer.supportsVBO()) {
|
|
VBOInfo vboinfo = new VBOInfo(true);
|
|
vboinfo.setVBOIndexEnabled(!_translucent);
|
|
setVBOInfo(vboinfo);
|
|
|
|
} else if (useDisplayLists && !_translucent) {
|
|
lockMeshes(renderer);
|
|
}
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void resolveTextures (TextureProvider tprov)
|
|
{
|
|
if (_textures == null) {
|
|
return;
|
|
}
|
|
Texture emissiveTex = null;
|
|
if (_emissiveMap != null && TextureState.getNumberOfFixedUnits() >= 2) {
|
|
TextureState tstate = tprov.getTexture(_emissiveMap);
|
|
emissiveTex = tstate.getTexture();
|
|
emissiveTex.setApply(Texture.AM_BLEND);
|
|
emissiveTex.getBlendColor().set(ColorRGBA.white);
|
|
}
|
|
_tstates = new TextureState[_textures.length];
|
|
for (int ii = 0; ii < _textures.length; ii++) {
|
|
_tstates[ii] = tprov.getTexture(_textures[ii]);
|
|
Texture tex = _tstates[ii].getTexture();
|
|
if (_sphereMapped) {
|
|
tex.setEnvironmentalMapMode(Texture.EM_SPHERE);
|
|
}
|
|
tex.setFilter(_filterMode);
|
|
tex.setMipmapState(_mipMapMode);
|
|
if (_compress && _tstates[ii].isS3TCAvailable()) {
|
|
Image image = tex.getImage();
|
|
int type = image.getType();
|
|
if (type == Image.RGB888) {
|
|
image.setType(Image.RGB888_DXT1);
|
|
} else if (type == Image.RGBA8888) {
|
|
image.setType(Image.RGBA8888_DXT5);
|
|
}
|
|
}
|
|
if (emissiveTex != null) {
|
|
_tstates[ii] = DisplaySystem.getDisplaySystem().
|
|
getRenderer().createTextureState();
|
|
_tstates[ii].setTexture(emissiveTex, 0);
|
|
_tstates[ii].setTexture(tex, 1);
|
|
}
|
|
}
|
|
if (_tstates[0] != null) {
|
|
setRenderState(_tstates[0]);
|
|
} else {
|
|
clearRenderState(RenderState.RS_TEXTURE);
|
|
}
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void configureShaders (ShaderCache scache)
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void storeMeshFrame (int frameId, boolean blend)
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void setMeshFrame (int frameId)
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
// documentation inherited from interface ModelSpatial
|
|
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
|
|
{
|
|
// no-op
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
protected void setupBatchList ()
|
|
{
|
|
batchList = new ArrayList<GeomBatch>(1);
|
|
TriangleBatch batch = createModelBatch();
|
|
batch.setParentGeom(this);
|
|
batchList.add(batch);
|
|
}
|
|
|
|
/**
|
|
* Creates a batch for this mesh.
|
|
*/
|
|
protected ModelBatch createModelBatch ()
|
|
{
|
|
return new ModelBatch();
|
|
}
|
|
|
|
/**
|
|
* Returns the number of textures this mesh uses (they must all share the
|
|
* same texture coordinates).
|
|
*/
|
|
protected int getTextureCount ()
|
|
{
|
|
return (_emissiveMap == null || TextureState.getNumberOfFixedUnits() < 2) ? 1 : 2;
|
|
}
|
|
|
|
/**
|
|
* For buffers that must be manipulated in some fashion, this method stores
|
|
* the originals.
|
|
*/
|
|
protected void storeOriginalBuffers ()
|
|
{
|
|
if (!_translucent) {
|
|
return;
|
|
}
|
|
IntBuffer ibuf = getIndexBuffer(0);
|
|
ibuf.rewind();
|
|
IntBuffer.wrap(_oibuf = new int[ibuf.capacity()]).put(ibuf);
|
|
|
|
FloatBuffer vbuf = getVertexBuffer(0);
|
|
vbuf.rewind();
|
|
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 (_emissive) {
|
|
setRenderState(_emissiveLight);
|
|
}
|
|
if (_solid) {
|
|
setRenderState(_backCull);
|
|
}
|
|
if (_additive) {
|
|
setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
|
|
setRenderState(_addAlpha);
|
|
setRenderState(_overlayZBuffer);
|
|
setRenderState(_noFog);
|
|
} else 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.
|
|
*/
|
|
protected void lockInstance ()
|
|
{
|
|
lockBounds();
|
|
_transformLocked = true;
|
|
}
|
|
|
|
/**
|
|
* Returns the mip-map mode corresponding to the given string
|
|
* (defaulting to {@link Texture#MM_LINEAR_LINEAR}).
|
|
*/
|
|
protected static int getMipMapMode (String mmode)
|
|
{
|
|
if ("none".equals(mmode)) {
|
|
return Texture.MM_NONE;
|
|
} else if ("nearest".equals(mmode)) {
|
|
return Texture.MM_NEAREST;
|
|
} else if ("linear".equals(mmode)) {
|
|
return Texture.MM_LINEAR;
|
|
} else if ("nearest_nearest".equals(mmode)) {
|
|
return Texture.MM_NEAREST_NEAREST;
|
|
} else if ("nearest_linear".equals(mmode)) {
|
|
return Texture.MM_NEAREST_LINEAR;
|
|
} else if ("linear_nearest".equals(mmode)) {
|
|
return Texture.MM_LINEAR_NEAREST;
|
|
} else {
|
|
return Texture.MM_LINEAR_LINEAR;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the states shared between all models. Requires an active
|
|
* display.
|
|
*/
|
|
protected static void initSharedStates ()
|
|
{
|
|
Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer();
|
|
_backCull = renderer.createCullState();
|
|
_backCull.setCullMode(CullState.CS_BACK);
|
|
_blendAlpha = renderer.createAlphaState();
|
|
_blendAlpha.setBlendEnabled(true);
|
|
_addAlpha = renderer.createAlphaState();
|
|
_addAlpha.setBlendEnabled(true);
|
|
_addAlpha.setDstFunction(AlphaState.DB_ONE);
|
|
_defaultTestAlpha = createTestAlpha(DEFAULT_ALPHA_THRESHOLD);
|
|
_overlayZBuffer = renderer.createZBufferState();
|
|
_overlayZBuffer.setFunction(ZBufferState.CF_LEQUAL);
|
|
_overlayZBuffer.setWritable(false);
|
|
_emissiveLight = renderer.createLightState();
|
|
_emissiveLight.setGlobalAmbient(ColorRGBA.white);
|
|
_noFog = renderer.createFogState();
|
|
_noFog.setEnabled(false);
|
|
}
|
|
|
|
/**
|
|
* Creates an alpha state what will throw away fragments with alpha
|
|
* values less than or equal to the given threshold.
|
|
*/
|
|
protected static AlphaState createTestAlpha (float threshold)
|
|
{
|
|
AlphaState astate = DisplaySystem.getDisplaySystem().
|
|
getRenderer().createAlphaState();
|
|
astate.setBlendEnabled(true);
|
|
astate.setTestEnabled(true);
|
|
astate.setTestFunction(AlphaState.TF_GREATER);
|
|
astate.setReference(threshold);
|
|
return astate;
|
|
}
|
|
|
|
/**
|
|
* Sorts the encoded triangle index/distance pairs in {@link #_tcodes}
|
|
* using a two-pass (16 bit) radix sort (as described by
|
|
* <a href="http://codercorner.com/RadixSortRevisited.htm">Pierre
|
|
* Terdiman</a>. {@link #_bcounts} is assumed to be initialized to the
|
|
* counts for the first radix.
|
|
*/
|
|
protected static void sortTriangleCodes (int tcount)
|
|
{
|
|
// initialize the offsets for the first radix (LSB) and clear
|
|
// the counts
|
|
initByteOffsets();
|
|
|
|
// sort by the first radix and get the counts for the second
|
|
// (swapping directions in the hope of using the cache more
|
|
// effectively)
|
|
if (_stcodes == null || _stcodes.length < tcount) {
|
|
_stcodes = new int[tcount];
|
|
}
|
|
int tcode;
|
|
for (int ii = tcount - 1; ii >= 0; ii--) {
|
|
tcode = _tcodes[ii];
|
|
_stcodes[_boffsets[tcode & 0xFF]++] = tcode;
|
|
_bcounts[(tcode >> 8) & 0xFF]++;
|
|
}
|
|
|
|
// initialize offsets for the second radix, clear counts, and
|
|
// sort by the second radix
|
|
initByteOffsets();
|
|
for (int ii = 0; ii < tcount; ii++) {
|
|
tcode = _stcodes[ii];
|
|
_tcodes[_boffsets[(tcode >> 8) & 0xFF]++] = tcode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the initial byte offsets used to place bytes within the sorted
|
|
* array using the byte counts, clearing the counts in the process.
|
|
*/
|
|
protected static void initByteOffsets ()
|
|
{
|
|
_boffsets[0] = 0;
|
|
for (int ii = 1; ii < 256; ii++) {
|
|
_boffsets[ii] = _boffsets[ii - 1] + _bcounts[ii - 1];
|
|
_bcounts[ii - 1] = 0;
|
|
}
|
|
_bcounts[255] = 0;
|
|
}
|
|
|
|
/** Sorts triangles for transparent meshes and renders overlays as well as
|
|
* the base layer. */
|
|
protected class ModelBatch extends TriangleBatch
|
|
{
|
|
@Override // documentation inherited
|
|
public void draw (Renderer r)
|
|
{
|
|
boolean drawing = (isEnabled() && r.isProcessingQueue());
|
|
if (drawing) {
|
|
if (_translucent) {
|
|
sortTriangles(r);
|
|
}
|
|
preDraw();
|
|
}
|
|
super.draw(r);
|
|
if (_overlays != null && drawing) {
|
|
for (int ii = 0, nn = _overlays.size(); ii < nn; ii++) {
|
|
preDrawOverlay(ii);
|
|
r.draw(this);
|
|
postDrawOverlay(ii);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gives derived classes a chance to update states immediately before drawing.
|
|
*/
|
|
protected void preDraw ()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Updates the batch's states with those of the identified overlay.
|
|
*/
|
|
protected void preDrawOverlay (int oidx)
|
|
{
|
|
RenderState[] overlay = _overlays.get(oidx);
|
|
for (RenderState rstate : overlay) {
|
|
int idx = rstate.getType();
|
|
_ostates[idx] = states[idx];
|
|
states[idx] = rstate;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restores the batch's original states after drawing an overlay.
|
|
*/
|
|
protected void postDrawOverlay (int oidx)
|
|
{
|
|
RenderState[] overlay = _overlays.get(oidx);
|
|
for (RenderState rstate : overlay) {
|
|
int idx = rstate.getType();
|
|
states[idx] = _ostates[idx];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sorts the batch's triangles by their distance to the camera.
|
|
*/
|
|
protected void sortTriangles (Renderer r)
|
|
{
|
|
// using the camera's direction in model space and the position
|
|
// and size of the model bound, find a set of plane parameters
|
|
// that determine the distance to a camera-aligned plane
|
|
// that touches the near edge of the bounding volume, as well
|
|
// as a scaling factor that brings the distance into a 16-bit
|
|
// integer range
|
|
getParentGeom().getWorldRotation().inverse().mult(
|
|
r.getCamera().getDirection(), _cdir);
|
|
BoundingVolume mbound = getModelBound();
|
|
Vector3f mc = mbound.getCenter();
|
|
float radius;
|
|
if (mbound instanceof BoundingSphere) {
|
|
radius = ((BoundingSphere)mbound).getRadius();
|
|
} else { // mbound instanceof BoundingBox
|
|
BoundingBox bbox = (BoundingBox)mbound;
|
|
radius = FastMath.sqrt(3f) * Math.max(bbox.xExtent,
|
|
Math.max(bbox.yExtent, bbox.zExtent));
|
|
}
|
|
float a = _cdir.x, b = _cdir.y, c = _cdir.z,
|
|
d = radius - a*mc.x - b*mc.y - c*mc.z,
|
|
dscale = 65535f / (radius * 2);
|
|
|
|
// premultiply the scale and averaging factor
|
|
a *= dscale / 3f;
|
|
b *= dscale / 3f;
|
|
c *= dscale / 3f;
|
|
d *= dscale;
|
|
|
|
// encode the model's triangles into integers such that the
|
|
// high 16 bits represent the original triangle index and the
|
|
// low 16 bits represent the distance to the plane. also
|
|
// increment the byte counts used for radix sorting
|
|
int tcount = getTriangleCount(), idist;
|
|
if (_tcodes == null || _tcodes.length < tcount) {
|
|
_tcodes = new int[tcount];
|
|
}
|
|
int i1, i2, i3;
|
|
for (int ii = 0, idx = 0; ii < tcount; ii++) {
|
|
i1 = _oibuf[idx++] * 3;
|
|
i2 = _oibuf[idx++] * 3;
|
|
i3 = _oibuf[idx++] * 3;
|
|
idist = (int)(
|
|
a * (_vbuf[i1++] + _vbuf[i2++] + _vbuf[i3++]) +
|
|
b * (_vbuf[i1++] + _vbuf[i2++] + _vbuf[i3++]) +
|
|
c * (_vbuf[i1++] + _vbuf[i2++] + _vbuf[i3++]) + d);
|
|
_tcodes[ii] = (ii << 16) | idist;
|
|
_bcounts[idist & 0xFF]++;
|
|
}
|
|
|
|
// sort the encoded triangles by increasing distance
|
|
sortTriangleCodes(tcount);
|
|
|
|
// reorder the triangles as dictated by the sorted codes, furthest
|
|
// triangles first
|
|
int icount = tcount * 3, idx;
|
|
if (_sibuf == null || _sibuf.length < icount) {
|
|
_sibuf = new int[icount];
|
|
}
|
|
for (int ii = tcount - 1, sidx = 0; ii >= 0; ii--) {
|
|
idx = ((_tcodes[ii] >> 16) & 0xFFFF) * 3;
|
|
_sibuf[sidx++] = _oibuf[idx++];
|
|
_sibuf[sidx++] = _oibuf[idx++];
|
|
_sibuf[sidx++] = _oibuf[idx];
|
|
}
|
|
|
|
// copy the indices to the buffer
|
|
IntBuffer ibuf = getIndexBuffer();
|
|
ibuf.rewind();
|
|
ibuf.put(_sibuf, 0, icount);
|
|
}
|
|
|
|
/**
|
|
* Gives derived classes a chance to update the states immediately before drawing.
|
|
*
|
|
* @param oidx the index of the overlay being rendered, or -1 for the base layer.
|
|
*/
|
|
protected void processStates (int oidx)
|
|
{
|
|
}
|
|
|
|
/** Temporarily stores the original states. */
|
|
protected RenderState[] _ostates =
|
|
new RenderState[RenderState.RS_MAX_STATE];
|
|
}
|
|
|
|
/** 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 <code>null</code> for none. */
|
|
protected String[] _textures;
|
|
|
|
/** Whether or not to use sphere mapping on this model's textures. */
|
|
protected boolean _sphereMapped;
|
|
|
|
/** The filter mode to use on magnification. */
|
|
protected int _filterMode;
|
|
|
|
/** The mipmap mode to use on minification. */
|
|
protected int _mipMapMode;
|
|
|
|
/** Whether or not to use compressed textures, if available. */
|
|
protected boolean _compress;
|
|
|
|
/** The emissive map, if specified. */
|
|
protected String _emissiveMap;
|
|
|
|
/** Whether or not this mesh is completely emissive. */
|
|
protected boolean _emissive;
|
|
|
|
/** Whether or not this mesh should be rendered with additive blending. */
|
|
protected boolean _additive;
|
|
|
|
/** Whether or not this mesh can enable back-face culling. */
|
|
protected boolean _solid;
|
|
|
|
/** Whether or not this mesh must be rendered as transparent. */
|
|
protected boolean _transparent;
|
|
|
|
/** The alpha threshold below which fragments are discarded. */
|
|
protected float _alphaThreshold;
|
|
|
|
/** Whether or not the triangles of this mesh should be depth-sorted before
|
|
* rendering. */
|
|
protected boolean _translucent;
|
|
|
|
/** If non-null, additional layers to render over the base layer. */
|
|
protected ArrayList<RenderState[]> _overlays;
|
|
|
|
/** For prototype meshes, the resolved texture states. */
|
|
protected TextureState[] _tstates;
|
|
|
|
/** Whether or not the transform has been locked. This operates in a
|
|
* slightly different way than JME's locking, in that it allows applying
|
|
* transformations to display lists. */
|
|
protected boolean _transformLocked;
|
|
|
|
/** For depth-sorted and skinned meshes, the array of vertices. */
|
|
protected float[] _vbuf;
|
|
|
|
/** For depth-sorted meshes, the original array of indices. */
|
|
protected int[] _oibuf;
|
|
|
|
/** The shared state for back face culling. */
|
|
protected static CullState _backCull;
|
|
|
|
/** The shared state for alpha blending. */
|
|
protected static AlphaState _blendAlpha;
|
|
|
|
/** The shared state for additive blending. */
|
|
protected static AlphaState _addAlpha;
|
|
|
|
/** The shared state for alpha testing with the default threshold. */
|
|
protected static AlphaState _defaultTestAlpha;
|
|
|
|
/** The shared state for checking, but not writing to, the z buffer. */
|
|
protected static ZBufferState _overlayZBuffer;
|
|
|
|
/** A light state that simulates emissivity. */
|
|
protected static LightState _emissiveLight;
|
|
|
|
/** A fog state that disables fog. */
|
|
protected static FogState _noFog;
|
|
|
|
/** Work vector to store the camera direction. */
|
|
protected static Vector3f _cdir = new Vector3f();
|
|
|
|
/** Work arrays used to sort triangles. */
|
|
protected static int[] _tcodes, _stcodes;
|
|
|
|
/** Holds counts of each byte and array offsets for radix sorting. */
|
|
protected static int[] _bcounts = new int[256], _boffsets = new int[256];
|
|
|
|
/** Work array used to hold indices of sorted triangles. */
|
|
protected static int[] _sibuf;
|
|
|
|
/** The default alpha threshold. */
|
|
protected static final float DEFAULT_ALPHA_THRESHOLD = 0.5f;
|
|
|
|
private static final long serialVersionUID = 1;
|
|
}
|