124af1e0b1
I tracked the ATI slowdown down to using a vertex shader in combination with fixed function fog. Also cleaned up ShaderConfig a little. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@244 ed5b42cb-e716-0410-a449-f6a68f950b19
771 lines
26 KiB
Java
771 lines
26 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.ObjectInputStream;
|
|
import java.io.Serializable;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.FloatBuffer;
|
|
import java.nio.IntBuffer;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
|
|
import org.lwjgl.opengl.GLContext;
|
|
|
|
import com.jme.bounding.BoundingVolume;
|
|
import com.jme.math.Matrix4f;
|
|
import com.jme.math.Vector3f;
|
|
import com.jme.renderer.Renderer;
|
|
import com.jme.scene.Spatial;
|
|
import com.jme.scene.TriMesh;
|
|
import com.jme.scene.VBOInfo;
|
|
import com.jme.scene.batch.SharedBatch;
|
|
import com.jme.scene.batch.TriangleBatch;
|
|
import com.jme.scene.state.GLSLShaderObjectsState;
|
|
import com.jme.scene.state.RenderState;
|
|
import com.jme.system.DisplaySystem;
|
|
import com.jme.util.ShaderAttribute;
|
|
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.export.Savable;
|
|
import com.jme.util.geom.BufferUtils;
|
|
|
|
import com.samskivert.util.ArrayUtil;
|
|
import com.samskivert.util.HashIntMap;
|
|
import com.samskivert.util.ListUtil;
|
|
|
|
import com.threerings.jme.Log;
|
|
import com.threerings.jme.util.JmeUtil;
|
|
import com.threerings.jme.util.ShaderCache;
|
|
import com.threerings.jme.util.ShaderConfig;
|
|
|
|
/**
|
|
* A triangle mesh that deforms according to a bone hierarchy.
|
|
*/
|
|
public class SkinMesh extends ModelMesh
|
|
{
|
|
/** The maximum number of bone matrices that we can use for hardware skinning. */
|
|
public static final int MAX_SHADER_BONE_COUNT = 31;
|
|
|
|
/** The maximum number of bones influencing a single vertex for hardware skinning. */
|
|
public static final int MAX_SHADER_BONES_PER_VERTEX = 4;
|
|
|
|
/** Represents the vertex weights of a group of vertices influenced by the
|
|
* same set of bones. */
|
|
public static class WeightGroup
|
|
implements Savable
|
|
{
|
|
/** The number of vertices in this weight group. */
|
|
public int vertexCount;
|
|
|
|
/** The bones influencing this group. */
|
|
public Bone[] bones;
|
|
|
|
/** The array of interleaved weights (of length <code>vertexCount *
|
|
* boneIndices.length</code>): weights for first vertex, weights for
|
|
* second, etc. */
|
|
public float[] weights;
|
|
|
|
/**
|
|
* Rebinds this weight group for a prototype instance.
|
|
*
|
|
* @param bmap the mapping from prototype to instance bones
|
|
*/
|
|
public WeightGroup rebind (HashMap<Bone, Bone> bmap)
|
|
{
|
|
WeightGroup wgroup = new WeightGroup();
|
|
wgroup.vertexCount = vertexCount;
|
|
wgroup.bones = new Bone[bones.length];
|
|
for (int ii = 0; ii < bones.length; ii++) {
|
|
wgroup.bones[ii] = bmap.get(bones[ii]);
|
|
}
|
|
wgroup.weights = weights;
|
|
return wgroup;
|
|
}
|
|
|
|
// documentation inherited
|
|
public Class getClassTag ()
|
|
{
|
|
return getClass();
|
|
}
|
|
|
|
// documentation inherited
|
|
public void read (JMEImporter im)
|
|
throws IOException
|
|
{
|
|
InputCapsule capsule = im.getCapsule(this);
|
|
vertexCount = capsule.readInt("vertexCount", 0);
|
|
bones = ArrayUtil.copy(capsule.readSavableArray("bones", null),
|
|
new Bone[0]);
|
|
weights = capsule.readFloatArray("weights", null);
|
|
}
|
|
|
|
// documentation inherited
|
|
public void write (JMEExporter ex)
|
|
throws IOException
|
|
{
|
|
OutputCapsule capsule = ex.getCapsule(this);
|
|
capsule.write(vertexCount, "vertexCount", 0);
|
|
capsule.write(bones, "bones", null);
|
|
capsule.write(weights, "weights", null);
|
|
}
|
|
|
|
private static final long serialVersionUID = 1;
|
|
}
|
|
|
|
/** Represents a bone that influences the mesh. */
|
|
public static class Bone
|
|
implements Savable
|
|
{
|
|
/** The node that defines the bone's position. */
|
|
public ModelNode node;
|
|
|
|
/** The inverse of the bone's model space reference transform. */
|
|
public transient Matrix4f invRefTransform;
|
|
|
|
/** The bone's current transform in model space. */
|
|
public transient Matrix4f transform;
|
|
|
|
public Bone (ModelNode node)
|
|
{
|
|
this();
|
|
this.node = node;
|
|
}
|
|
|
|
public Bone ()
|
|
{
|
|
transform = new Matrix4f();
|
|
}
|
|
|
|
/**
|
|
* Rebinds this bone for a prototype instance.
|
|
*
|
|
* @param pnodes a mapping from prototype nodes to instance nodes
|
|
*/
|
|
public Bone rebind (HashMap pnodes)
|
|
{
|
|
Bone bone = new Bone((ModelNode)pnodes.get(node));
|
|
bone.invRefTransform = invRefTransform;
|
|
bone.transform = new Matrix4f();
|
|
return bone;
|
|
}
|
|
|
|
// documentation inherited
|
|
public Class getClassTag ()
|
|
{
|
|
return getClass();
|
|
}
|
|
|
|
// documentation inherited
|
|
public void read (JMEImporter im)
|
|
throws IOException
|
|
{
|
|
InputCapsule capsule = im.getCapsule(this);
|
|
node = (ModelNode)capsule.readSavable("node", null);
|
|
}
|
|
|
|
// documentation inherited
|
|
public void write (JMEExporter ex)
|
|
throws IOException
|
|
{
|
|
OutputCapsule capsule = ex.getCapsule(this);
|
|
capsule.write(node, "node", null);
|
|
}
|
|
|
|
private static final long serialVersionUID = 1;
|
|
}
|
|
|
|
/**
|
|
* No-arg constructor for deserialization.
|
|
*/
|
|
public SkinMesh ()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Creates an empty mesh.
|
|
*/
|
|
public SkinMesh (String name)
|
|
{
|
|
super(name);
|
|
}
|
|
|
|
/**
|
|
* Sets the array of weight groups that determine how bones affect
|
|
* each vertex.
|
|
*/
|
|
public void setWeightGroups (WeightGroup[] weightGroups)
|
|
{
|
|
_weightGroups = weightGroups;
|
|
|
|
// compile a list of all referenced bones
|
|
HashSet<Bone> bones = new HashSet<Bone>();
|
|
for (WeightGroup group : weightGroups) {
|
|
Collections.addAll(bones, group.bones);
|
|
}
|
|
_bones = bones.toArray(new Bone[bones.size()]);
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void addOverlay (RenderState[] overlay)
|
|
{
|
|
// add a cloned state config (with same uniforms) for the overlay
|
|
super.addOverlay(overlay);
|
|
if (_sconfig == null) {
|
|
return;
|
|
}
|
|
if (_osconfigs == null) {
|
|
_osconfigs = new ArrayList<SkinShaderConfig>(1);
|
|
}
|
|
SkinShaderConfig osconfig = (SkinShaderConfig)_sconfig.clone();
|
|
osconfig.getState().uniforms = _sconfig.getState().uniforms;
|
|
_osconfigs.add(osconfig);
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void removeOverlay (RenderState[] overlay)
|
|
{
|
|
// remove the corresponding state config
|
|
int idx = (_overlays == null) ? -1 : _overlays.indexOf(overlay);
|
|
super.removeOverlay(overlay);
|
|
if (_osconfigs != null && idx >= 0) {
|
|
_osconfigs.remove(idx);
|
|
if (_osconfigs.isEmpty()) {
|
|
_osconfigs = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void reconstruct (
|
|
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
|
|
FloatBuffer textures, IntBuffer indices)
|
|
{
|
|
super.reconstruct(vertices, normals, colors, textures, indices);
|
|
|
|
// initialize the quantized frame table
|
|
_frames = new HashIntMap<Object>();
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public Spatial putClone (Spatial store, Model.CloneCreator properties)
|
|
{
|
|
SkinMesh mstore = (SkinMesh)properties.originalToCopy.get(this);
|
|
if (mstore != null) {
|
|
return mstore;
|
|
} else if (store == null) {
|
|
mstore = new SkinMesh(getName());
|
|
} else {
|
|
mstore = (SkinMesh)store;
|
|
}
|
|
GLSLShaderObjectsState sstate = (GLSLShaderObjectsState)getRenderState(
|
|
RenderState.RS_GLSL_SHADER_OBJECTS);
|
|
if (sstate == null) {
|
|
// vertices and normals must be cloned if not using a shader
|
|
properties.removeProperty("vertices");
|
|
properties.removeProperty("normals");
|
|
}
|
|
properties.removeProperty("displaylistid");
|
|
super.putClone(mstore, properties);
|
|
if (sstate == null) {
|
|
properties.addProperty("vertices");
|
|
properties.addProperty("normals");
|
|
}
|
|
properties.addProperty("displaylistid");
|
|
mstore._frames = _frames;
|
|
mstore._useDisplayLists = _useDisplayLists;
|
|
mstore._invRefTransform = _invRefTransform;
|
|
mstore._bones = new Bone[_bones.length];
|
|
HashMap<Bone, Bone> bmap = new HashMap<Bone, Bone>();
|
|
for (int ii = 0; ii < _bones.length; ii++) {
|
|
bmap.put(_bones[ii], mstore._bones[ii] =
|
|
_bones[ii].rebind(properties.originalToCopy));
|
|
}
|
|
mstore._weightGroups = new WeightGroup[_weightGroups.length];
|
|
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
|
mstore._weightGroups[ii] = _weightGroups[ii].rebind(bmap);
|
|
}
|
|
mstore._ovbuf = _ovbuf;
|
|
mstore._onbuf = _onbuf;
|
|
mstore._vbuf = (sstate == null) ? new float[_vbuf.length] : _vbuf;
|
|
mstore._nbuf = (sstate == null) ? new float[_nbuf.length] : _nbuf;
|
|
if (_sconfig != null) {
|
|
mstore._sconfig = (SkinShaderConfig)_sconfig.clone();
|
|
mstore.setRenderState(mstore._sconfig.getState());
|
|
}
|
|
return mstore;
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void read (JMEImporter im)
|
|
throws IOException
|
|
{
|
|
super.read(im);
|
|
InputCapsule capsule = im.getCapsule(this);
|
|
setWeightGroups(ArrayUtil.copy(capsule.readSavableArray(
|
|
"weightGroups", null), new WeightGroup[0]));
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void write (JMEExporter ex)
|
|
throws IOException
|
|
{
|
|
super.write(ex);
|
|
OutputCapsule capsule = ex.getCapsule(this);
|
|
capsule.write(_weightGroups, "weightGroups", null);
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void expandModelBounds ()
|
|
{
|
|
BoundingVolume obound =
|
|
(BoundingVolume)getBatch(0).getModelBound().clone(null);
|
|
updateModelBound();
|
|
getBatch(0).getModelBound().mergeLocal(obound);
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void setReferenceTransforms ()
|
|
{
|
|
_invRefTransform = new Matrix4f();
|
|
if (parent instanceof ModelNode) {
|
|
Matrix4f transform = new Matrix4f();
|
|
JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
|
|
getLocalScale(), transform);
|
|
((ModelNode)parent).getModelTransform().mult(transform,
|
|
_invRefTransform);
|
|
_invRefTransform.invertLocal();
|
|
}
|
|
for (Bone bone : _bones) {
|
|
bone.invRefTransform =
|
|
_invRefTransform.mult(bone.node.getModelTransform()).invert();
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void lockStaticMeshes (
|
|
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
|
|
{
|
|
// we can use VBOs for color, texture, and indices if not using shaders
|
|
if (useVBOs && renderer.supportsVBO()) {
|
|
// use VBOs for shader attributes
|
|
GLSLShaderObjectsState sstate = (GLSLShaderObjectsState)getRenderState(
|
|
RenderState.RS_GLSL_SHADER_OBJECTS);
|
|
if (sstate != null) {
|
|
for (ShaderAttribute attrib : sstate.attribs.values()) {
|
|
attrib.useVBO = true;
|
|
}
|
|
}
|
|
|
|
VBOInfo vboinfo = new VBOInfo(true);
|
|
if (sstate == null) {
|
|
vboinfo.setVBOVertexEnabled(false);
|
|
vboinfo.setVBONormalEnabled(false);
|
|
}
|
|
vboinfo.setVBOIndexEnabled(!_translucent);
|
|
setVBOInfo(vboinfo);
|
|
}
|
|
_useDisplayLists = useDisplayLists && !_translucent;
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void configureShaders (ShaderCache scache)
|
|
{
|
|
if (_disableShaders || !GLContext.getCapabilities().GL_ARB_vertex_shader ||
|
|
_bones.length > MAX_SHADER_BONE_COUNT) {
|
|
return;
|
|
}
|
|
int bonesPerVertex = 0;
|
|
for (WeightGroup group : _weightGroups) {
|
|
bonesPerVertex = Math.max(group.bones.length, bonesPerVertex);
|
|
}
|
|
if (bonesPerVertex > MAX_SHADER_BONES_PER_VERTEX) {
|
|
return;
|
|
}
|
|
_sconfig = new SkinShaderConfig(scache, bonesPerVertex, _emissiveMap != null);
|
|
if (_sconfig.update(getBatch(0).states)) {
|
|
setShaderAttributes();
|
|
setRenderState(_sconfig.getState());
|
|
} else {
|
|
_sconfig = null;
|
|
_disableShaders = true;
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void storeMeshFrame (int frameId, boolean blend)
|
|
{
|
|
_storeFrameId = frameId;
|
|
_storeBlend = blend;
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void setMeshFrame (int frameId)
|
|
{
|
|
TriangleBatch batch = getBatch(0),
|
|
tbatch = (TriangleBatch)_frames.get(frameId);
|
|
if (batch instanceof SharedBatch) {
|
|
((SharedBatch)batch).setTarget(tbatch);
|
|
} else {
|
|
clearBatches();
|
|
addBatch(new SharedBatch(tbatch));
|
|
getBatch(0).updateRenderState();
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
|
|
{
|
|
BlendFrame frame1 = (BlendFrame)_frames.get(frameId1),
|
|
frame2 = (BlendFrame)_frames.get(frameId2);
|
|
frame1.blend(frame2, alpha, _vbuf, _nbuf);
|
|
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
|
|
vbuf.rewind();
|
|
vbuf.put(_vbuf);
|
|
nbuf.rewind();
|
|
nbuf.put(_nbuf);
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
public void updateWorldData (float time)
|
|
{
|
|
super.updateWorldData(time);
|
|
if (_weightGroups == null || _storeFrameId == -1 ||
|
|
(_storeFrameId == 0 && getCullMode() == CULL_ALWAYS)) {
|
|
return;
|
|
}
|
|
// update the bone transforms
|
|
for (Bone bone : _bones) {
|
|
_invRefTransform.mult(bone.node.getModelTransform(),
|
|
bone.transform);
|
|
bone.transform.multLocal(bone.invRefTransform);
|
|
}
|
|
|
|
// if we're using shaders, initialize the uniform variables with the bone transforms
|
|
GLSLShaderObjectsState sstate = (GLSLShaderObjectsState)getRenderState(
|
|
RenderState.RS_GLSL_SHADER_OBJECTS);
|
|
if (sstate != null) {
|
|
for (int ii = 0; ii < _bones.length; ii++) {
|
|
sstate.setUniform("boneTransforms[" + ii + "]", _bones[ii].transform, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// deform the mesh according to the positions of the bones (this code
|
|
// is ugly as sin because it's optimized at a low level)
|
|
Bone[] bones;
|
|
int vertexCount, jj, kk, ww;
|
|
float[] weights;
|
|
Matrix4f m;
|
|
float weight, ovx, ovy, ovz, onx, ony, onz, vx, vy, vz, nx, ny, nz;
|
|
for (int ii = 0, bidx = 0; ii < _weightGroups.length; ii++) {
|
|
vertexCount = _weightGroups[ii].vertexCount;
|
|
bones = _weightGroups[ii].bones;
|
|
weights = _weightGroups[ii].weights;
|
|
for (jj = 0, ww = 0; jj < vertexCount; jj++) {
|
|
ovx = _ovbuf[bidx];
|
|
ovy = _ovbuf[bidx + 1];
|
|
ovz = _ovbuf[bidx + 2];
|
|
onx = _onbuf[bidx];
|
|
ony = _onbuf[bidx + 1];
|
|
onz = _onbuf[bidx + 2];
|
|
vx = vy = vz = 0f;
|
|
nx = ny = nz = 0f;
|
|
for (kk = 0; kk < bones.length; kk++) {
|
|
m = bones[kk].transform;
|
|
weight = weights[ww++];
|
|
|
|
vx += (ovx*m.m00 + ovy*m.m01 + ovz*m.m02 + m.m03) * weight;
|
|
vy += (ovx*m.m10 + ovy*m.m11 + ovz*m.m12 + m.m13) * weight;
|
|
vz += (ovx*m.m20 + ovy*m.m21 + ovz*m.m22 + m.m23) * weight;
|
|
|
|
nx += (onx*m.m00 + ony*m.m01 + onz*m.m02) * weight;
|
|
ny += (onx*m.m10 + ony*m.m11 + onz*m.m12) * weight;
|
|
nz += (onx*m.m20 + ony*m.m21 + onz*m.m22) * weight;
|
|
}
|
|
_vbuf[bidx] = vx;
|
|
_vbuf[bidx + 1] = vy;
|
|
_vbuf[bidx + 2] = vz;
|
|
_nbuf[bidx++] = nx;
|
|
_nbuf[bidx++] = ny;
|
|
_nbuf[bidx++] = nz;
|
|
}
|
|
}
|
|
|
|
// if skinning in real time, copy the data from arrays to buffers;
|
|
// otherwise, store the mesh as an animation frame
|
|
if (_storeFrameId == 0) {
|
|
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
|
|
vbuf.rewind();
|
|
vbuf.put(_vbuf);
|
|
nbuf.rewind();
|
|
nbuf.put(_nbuf);
|
|
} else {
|
|
storeFrame();
|
|
_storeFrameId = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stores the current frame data for later use.
|
|
*/
|
|
protected void storeFrame ()
|
|
{
|
|
if (_storeBlend) {
|
|
_frames.put(_storeFrameId, new BlendFrame(
|
|
(float[])_vbuf.clone(), (float[])_nbuf.clone()));
|
|
} else {
|
|
TriangleBatch batch = getBatch(0), tbatch = new TriangleBatch();
|
|
tbatch.setParentGeom(DUMMY_MESH);
|
|
tbatch.setColorBuffer(batch.getColorBuffer());
|
|
int nunits = batch.getNumberOfUnits();
|
|
for (int ii = 0; ii < nunits; ii++) {
|
|
tbatch.setTextureBuffer(batch.getTextureBuffer(ii), ii);
|
|
}
|
|
tbatch.setIndexBuffer(batch.getIndexBuffer());
|
|
tbatch.setVertexBuffer(BufferUtils.createFloatBuffer(_vbuf));
|
|
tbatch.setNormalBuffer(BufferUtils.createFloatBuffer(_nbuf));
|
|
VBOInfo ovboinfo = batch.getVBOInfo();
|
|
if (ovboinfo != null) {
|
|
VBOInfo vboinfo = new VBOInfo(true);
|
|
vboinfo.setVBOIndexEnabled(!_translucent);
|
|
vboinfo.setVBOColorID(ovboinfo.getVBOColorID());
|
|
for (int ii = 0; ii < nunits; ii++) {
|
|
vboinfo.setVBOTextureID(ii, ovboinfo.getVBOTextureID(ii));
|
|
}
|
|
vboinfo.setVBOIndexID(ovboinfo.getVBOIndexID());
|
|
tbatch.setVBOInfo(vboinfo);
|
|
} else if (_useDisplayLists) {
|
|
tbatch.lockMeshes(
|
|
DisplaySystem.getDisplaySystem().getRenderer());
|
|
}
|
|
_frames.put(_storeFrameId, tbatch);
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
protected ModelBatch createModelBatch ()
|
|
{
|
|
// update the shader configs immediately before drawing
|
|
return new ModelBatch() {
|
|
protected void preDraw () {
|
|
if (_sconfig != null) {
|
|
_sconfig.update(states);
|
|
}
|
|
}
|
|
protected void preDrawOverlay (int oidx) {
|
|
super.preDrawOverlay(oidx);
|
|
if (_osconfigs != null) {
|
|
_ostates[RenderState.RS_GLSL_SHADER_OBJECTS] =
|
|
states[RenderState.RS_GLSL_SHADER_OBJECTS];
|
|
SkinShaderConfig osconfig = _osconfigs.get(oidx);
|
|
states[RenderState.RS_GLSL_SHADER_OBJECTS] = osconfig.getState();
|
|
_osconfigs.get(oidx).update(states);
|
|
}
|
|
}
|
|
protected void postDrawOverlay (int oidx) {
|
|
super.postDrawOverlay(oidx);
|
|
if (_osconfigs != null) {
|
|
states[RenderState.RS_GLSL_SHADER_OBJECTS] =
|
|
_ostates[RenderState.RS_GLSL_SHADER_OBJECTS];
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
protected void storeOriginalBuffers ()
|
|
{
|
|
super.storeOriginalBuffers();
|
|
|
|
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
|
|
vbuf.rewind();
|
|
nbuf.rewind();
|
|
FloatBuffer.wrap(_ovbuf = new float[vbuf.capacity()]).put(vbuf);
|
|
FloatBuffer.wrap(_onbuf = new float[nbuf.capacity()]).put(nbuf);
|
|
_vbuf = new float[_ovbuf.length];
|
|
_nbuf = new float[_onbuf.length];
|
|
}
|
|
|
|
/**
|
|
* Initializes the skin shader attributes (bone indices and weights) in the supplied state.
|
|
*/
|
|
protected void setShaderAttributes ()
|
|
{
|
|
int bonesPerVertex = _sconfig.getBonesPerVertex();
|
|
int size = getBatch(0).getVertexCount() * bonesPerVertex;
|
|
ByteBuffer bibuf = BufferUtils.createByteBuffer(size);
|
|
FloatBuffer bwbuf = BufferUtils.createFloatBuffer(size);
|
|
|
|
for (WeightGroup group : _weightGroups) {
|
|
byte[] indices = new byte[bonesPerVertex];
|
|
for (int ii = 0; ii < indices.length; ii++) {
|
|
indices[ii] = (byte)((ii < group.bones.length) ?
|
|
ListUtil.indexOf(_bones, group.bones[ii]) : 0);
|
|
}
|
|
for (int ii = 0, widx = 0; ii < group.vertexCount; ii++) {
|
|
bibuf.put(indices);
|
|
for (int jj = 0; jj < bonesPerVertex; jj++) {
|
|
bwbuf.put((jj < group.bones.length) ? group.weights[widx++] : 0f);
|
|
}
|
|
}
|
|
}
|
|
bibuf.rewind();
|
|
bwbuf.rewind();
|
|
|
|
GLSLShaderObjectsState sstate = _sconfig.getState();
|
|
sstate.setAttributePointer("boneIndices", bonesPerVertex, false, false, 0, bibuf);
|
|
sstate.setAttributePointer("boneWeights", bonesPerVertex, false, 0, bwbuf);
|
|
}
|
|
|
|
/** Tracks the configuration of a skin shader. */
|
|
protected static class SkinShaderConfig extends ShaderConfig
|
|
{
|
|
public SkinShaderConfig (ShaderCache scache, int bonesPerVertex, boolean emissiveMapped)
|
|
{
|
|
super(scache);
|
|
_bonesPerVertex = bonesPerVertex;
|
|
_emissiveMapped = emissiveMapped;
|
|
|
|
// set bindings from texture units to samplers
|
|
if (emissiveMapped) {
|
|
_state.setUniform("diffuseMap", 1);
|
|
_state.setUniform("emissiveMap", 0);
|
|
} else {
|
|
_state.setUniform("diffuseMap", 0);
|
|
}
|
|
}
|
|
|
|
public int getBonesPerVertex ()
|
|
{
|
|
return _bonesPerVertex;
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
protected String getVertexShader ()
|
|
{
|
|
return "media/jme/skin.vert";
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
protected String getFragmentShader ()
|
|
{
|
|
return "media/jme/skin.frag";
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
protected void getDefinitions (ArrayList<String> defs)
|
|
{
|
|
super.getDefinitions(defs);
|
|
defs.add("BONES_PER_VERTEX " + _bonesPerVertex);
|
|
if (_emissiveMapped) {
|
|
defs.add("EMISSIVE_MAPPED");
|
|
}
|
|
}
|
|
|
|
@Override // documentation inherited
|
|
protected void getDerivedDefinitions (ArrayList<String> ddefs)
|
|
{
|
|
super.getDerivedDefinitions(ddefs);
|
|
ddefs.add("MAX_BONE_COUNT " + MAX_SHADER_BONE_COUNT);
|
|
}
|
|
|
|
protected int _bonesPerVertex;
|
|
protected boolean _emissiveMapped;
|
|
}
|
|
|
|
/** A stored frame used for linear blending. */
|
|
protected static class BlendFrame
|
|
{
|
|
/** The skinned vertex and normal values. */
|
|
public float[] vbuf, nbuf;
|
|
|
|
public BlendFrame (float[] vbuf, float[] nbuf)
|
|
{
|
|
this.vbuf = vbuf;
|
|
this.nbuf = nbuf;
|
|
}
|
|
|
|
public void blend (
|
|
BlendFrame next, float alpha, float[] rvbuf, float[] rnbuf)
|
|
{
|
|
float[] nvbuf = next.vbuf, nnbuf = next.nbuf;
|
|
float ialpha = 1f - alpha;
|
|
for (int ii = 0, nn = vbuf.length; ii < nn; ii++) {
|
|
rvbuf[ii] = vbuf[ii] * ialpha + nvbuf[ii] * alpha;
|
|
rnbuf[ii] = nbuf[ii] * ialpha + nnbuf[ii] * alpha;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Pre-skinned {@link TriangleBatch}es or {@link BlendFrame}s shared
|
|
* between all instances corresponding to frame ids from
|
|
* {@link #storeAnimationFrame}. */
|
|
protected HashIntMap<Object> _frames;
|
|
|
|
/** Whether or to use display lists if VBOs are unavailable for quantized
|
|
* meshes. */
|
|
protected boolean _useDisplayLists;
|
|
|
|
/** The inverse of the model space reference transform. */
|
|
protected Matrix4f _invRefTransform;
|
|
|
|
/** The groups of vertices influenced by different sets of bones. */
|
|
protected WeightGroup[] _weightGroups;
|
|
|
|
/** The bones referenced by the weight groups. */
|
|
protected Bone[] _bones;
|
|
|
|
/** The original (undeformed) vertex and normal buffers and the deformed
|
|
* versions. */
|
|
protected float[] _onbuf, _ovbuf, _nbuf;
|
|
|
|
/** The primary skin shader configuration. */
|
|
protected SkinShaderConfig _sconfig;
|
|
|
|
/** Skin shader configurations for each overlay. */
|
|
protected ArrayList<SkinShaderConfig> _osconfigs;
|
|
|
|
/** The frame id to store on the next update. If 0, don't store any frame
|
|
* and skin the mesh as normal. If -1, a frame has been stored and thus
|
|
* skinning should only take place when further frames are requested. */
|
|
protected int _storeFrameId;
|
|
|
|
/** Whether or not the stored frame id will be used for blending. */
|
|
protected boolean _storeBlend;
|
|
|
|
/** Set if we determine that our shaders don't compile to prevent us from trying again. */
|
|
protected static boolean _disableShaders;
|
|
|
|
/** A dummy mesh that simply hold transformation values. */
|
|
protected static final TriMesh DUMMY_MESH = new TriMesh();
|
|
|
|
private static final long serialVersionUID = 1;
|
|
}
|