/*
|
* SmoothSkin.java
|
*
|
*
|
* The Salamander Project - 2D and 3D graphics libraries in Java
|
* Copyright (C) 2004 Mark McKay
|
*
|
* 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
|
*
|
* Mark McKay can be contacted at mark@kitfox.com. Salamander and other
|
* projects can be found at http://www.kitfox.com
|
*
|
* Created on March 10, 2004, 5:39 AM
|
*/
|
|
import javax.media.j3d.*;
|
import javax.vecmath.*;
|
|
import java.util.*;
|
|
/**
|
*
|
* @author kitfox
|
*/
|
//public class SmoothSkin extends Shape3D
|
public class SmoothSkin extends TriangleStripArray
|
{
|
// TriangleStripArray targetSkin;
|
Bone skelRoot;
|
|
// int vertexFormat;
|
// int vertexCount;
|
// int[] stripVertexCounts;
|
float[] bindCoordinates;
|
float[] bindNormals;
|
float[] targetCoordinates;
|
float[] targetNormals;
|
float[] texCoords;
|
float[] colors;
|
|
//Array of weights per bone per vertex. Organized first by bones and then
|
// by index of vertex into the targetCoordinates array. Bones are in the
|
// order of a depth first search of the skelRoot skeleton.
|
float[] boneWeights;
|
Transform3D worldToSkin = new Transform3D();
|
|
private SmoothSkin(int vertexCount, int vertexFormat, int[] stripVertexCounts)
|
{
|
super(vertexCount, vertexFormat, stripVertexCounts);
|
}
|
|
private SmoothSkin(int vertexCount, int vertexFormat, int texCoordSetCount, int[] texCoordSetMap, int[] stripVertexCounts)
|
{
|
super(vertexCount, vertexFormat, texCoordSetCount, texCoordSetMap, stripVertexCounts);
|
}
|
|
/**
|
* Create a new instance of SmoothSkin. Special work needs to be done in
|
* creation, so the constructor cannot be called directly.
|
*
|
* @param skelRoot - The root node of the skeleton system that will animate
|
* this mesh
|
* @param weights - A map of HashMaps that represent the distribution of
|
* weights per bone. That is, 'weights' will contain keys of Bone that map
|
* to a hashmap describing weights for that bone. This second hashmap
|
* will contain keys of Point3f representing points in the source mesh to
|
* Float, representing the weight for this point for this bone.
|
* @param geometry - A triangle strip array that is the mesh this skeleton
|
* will deform
|
* @param skinToWorld - A transform that will map the skin mesh into world space,
|
* if it is not already there.
|
*/
|
public static SmoothSkin create(Bone skelRoot, HashMap weights, TriangleStripArray geometry, Transform3D skinToWorld)
|
{
|
//Make two copies of input geometry; one for holding bind information,
|
// and the other being the target object that we render.
|
SmoothSkin targetSkin;
|
|
int vertexFormat = geometry.getVertexFormat();
|
int vertexCount = geometry.getVertexCount();
|
int numStrips = geometry.getNumStrips();
|
int[] stripVertexCounts = new int[numStrips];
|
geometry.getStripVertexCounts(stripVertexCounts);
|
|
if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0)
|
{
|
int texCoordSetCount = geometry.getTexCoordSetCount();
|
int[] texCoordSetMap = new int[texCoordSetCount];
|
geometry.getTexCoordSetMap(texCoordSetMap);
|
|
targetSkin = new SmoothSkin(vertexCount, vertexFormat | GeometryArray.BY_REFERENCE, texCoordSetCount, texCoordSetMap, stripVertexCounts);
|
} else
|
{
|
targetSkin = new SmoothSkin(vertexCount, vertexFormat | GeometryArray.BY_REFERENCE, stripVertexCounts);
|
}
|
|
|
targetSkin.prepareGeometry(skelRoot, geometry, weights, skinToWorld);
|
|
return targetSkin;
|
}
|
|
private void prepareGeometry(Bone skelRoot, TriangleStripArray geom, HashMap weights, Transform3D skinToWorld)
|
{
|
int vertexCount = getVertexCount();
|
int vertexFormat = getVertexFormat();
|
|
bindCoordinates = new float[vertexCount * 3];
|
geom.getCoordinates(0, bindCoordinates);
|
targetCoordinates = new float[vertexCount * 3];
|
geom.getCoordinates(0, targetCoordinates);
|
setCoordRefFloat(targetCoordinates);
|
|
setCapability(GeometryArray.ALLOW_REF_DATA_READ);
|
setCapability(GeometryArray.ALLOW_REF_DATA_WRITE);
|
|
if ((vertexFormat & GeometryArray.NORMALS) != 0)
|
{
|
bindNormals = new float[vertexCount * 3];
|
geom.getNormals(0, bindNormals);
|
targetNormals = new float[vertexCount * 3];
|
geom.getNormals(0, targetNormals);
|
setNormalRefFloat(targetNormals);
|
}
|
|
if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0)
|
{
|
texCoords = new float[vertexCount * 2];
|
geom.getTextureCoordinates(0, 0, texCoords);
|
setTexCoordRefFloat(0, texCoords);
|
} else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0)
|
{
|
texCoords = new float[vertexCount * 3];
|
geom.getTextureCoordinates(0, 0, texCoords);
|
setTexCoordRefFloat(0, texCoords);
|
} else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0)
|
{
|
texCoords = new float[vertexCount * 4];
|
geom.getTextureCoordinates(0, 0, texCoords);
|
setTexCoordRefFloat(0, texCoords);
|
}
|
|
if ((vertexFormat & GeometryArray.COLOR_4) != 0)
|
{
|
colors = new float[vertexCount * 4];
|
geom.getColors(0, colors);
|
setColorRefFloat(colors);
|
} else if ((vertexFormat & GeometryArray.COLOR_3) != 0)
|
{
|
colors = new float[vertexCount * 3];
|
geom.getColors(0, colors);
|
setColorRefFloat(colors);
|
}
|
|
//Now build skeleton and bone weights array
|
this.skelRoot = skelRoot;
|
|
boneWeights = new float[weights.size() * vertexCount];
|
calcBoneWeights(skelRoot, weights, 0);
|
|
|
//Map all points to world space
|
Point3f mapPoint = new Point3f();
|
for (int i = 0; i < bindCoordinates.length; i += 3)
|
{
|
mapPoint.set(bindCoordinates[i], bindCoordinates[i + 1], bindCoordinates[i + 2]);
|
skinToWorld.transform(mapPoint);
|
bindCoordinates[i] = mapPoint.x;
|
bindCoordinates[i + 1] = mapPoint.y;
|
bindCoordinates[i + 2] = mapPoint.z;
|
}
|
|
worldToSkin.invert(skinToWorld);
|
}
|
|
public NodeComponent cloneNodeComponent(boolean forceDuplicate)
|
{
|
SmoothSkin targetSkin;
|
|
int vertexFormat = getVertexFormat();
|
int vertexCount = getVertexCount();
|
int numStrips = getNumStrips();
|
int[] stripVertexCounts = new int[numStrips];
|
getStripVertexCounts(stripVertexCounts);
|
|
if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0)
|
{
|
int texCoordSetCount = getTexCoordSetCount();
|
int[] texCoordSetMap = new int[texCoordSetCount];
|
getTexCoordSetMap(texCoordSetMap);
|
|
targetSkin = new SmoothSkin(vertexCount, vertexFormat | GeometryArray.BY_REFERENCE, texCoordSetCount, texCoordSetMap, stripVertexCounts);
|
} else
|
{
|
targetSkin = new SmoothSkin(vertexCount, vertexFormat | GeometryArray.BY_REFERENCE, stripVertexCounts);
|
}
|
|
targetSkin.duplicateNodeComponent(this, forceDuplicate);
|
return targetSkin;
|
}
|
|
/**
|
* Copies all information from the original node into this node
|
*/
|
public void duplicateNodeComponent(NodeComponent originalNodeComponent, boolean forceDuplicate)
|
{
|
super.duplicateNodeComponent(originalNodeComponent, forceDuplicate);
|
|
if (!forceDuplicate)
|
{
|
forceDuplicate = getDuplicateOnCloneTree();
|
}
|
|
|
SmoothSkin src = (SmoothSkin) originalNodeComponent;
|
|
bindCoordinates = src.bindCoordinates;
|
bindNormals = src.bindNormals;
|
boneWeights = src.boneWeights;
|
|
targetCoordinates = getCoordRefFloat();
|
targetNormals = getNormalRefFloat();
|
texCoords = getTexCoordRefFloat(0);
|
colors = getColorRefFloat();
|
|
|
if (forceDuplicate)
|
{
|
skelRoot = (Bone) src.skelRoot.cloneTree(true);
|
} else
|
{
|
skelRoot = src.skelRoot;
|
}
|
}
|
|
public Bone getSkeletonRoot()
|
{
|
return skelRoot;
|
}
|
|
/**
|
* Returns number of points weighted
|
*/
|
public int calcBoneWeights(Bone bone, HashMap weights, int offset)
|
{
|
HashMap vtxMap = (HashMap) weights.get(bone);
|
Point3f vertex = new Point3f();
|
|
int vertexCount = getVertexCount();
|
for (int i = 0; i < vertexCount; i++)
|
{
|
vertex.set(bindCoordinates[i * 3], bindCoordinates[i * 3 + 1], bindCoordinates[i * 3 + 2]);
|
Float weight = (Float) vtxMap.get(vertex);
|
|
boneWeights[offset++] = weight.floatValue();
|
}
|
|
for (Iterator it = bone.children.iterator(); it.hasNext();)
|
{
|
Bone nextBone = (Bone) it.next();
|
offset = calcBoneWeights(nextBone, weights, offset);
|
}
|
|
return offset;
|
}
|
|
/**
|
* Causes the target skin to be reevaluated. Call this after making changes
|
* to the bone hierarchy to generate the new mesh.
|
*/
|
public void updateSkin()
|
{
|
updateData(
|
new GeometryUpdater()
|
{
|
|
public void updateData(javax.media.j3d.Geometry geom)
|
{
|
evaluateSkin();
|
}
|
});
|
}
|
|
/**
|
* Recalculates target skin based on current bone animation positions.
|
*/
|
private void evaluateSkin()
|
{
|
for (int i = 0; i < bindCoordinates.length; i++)
|
{
|
targetCoordinates[i] = targetNormals[i] = 0f;
|
}
|
|
//Identity change to root bone
|
final Transform3D rootXform = new Transform3D();
|
evalBoneWeights(skelRoot, 0, rootXform, rootXform);
|
|
//Normal array needs to be converted from normal offsets to normals
|
for (int i = 0; i < bindCoordinates.length; i++)
|
{
|
targetNormals[i] -= targetCoordinates[i];
|
}
|
|
//Map all points to world space
|
Point3f mapPoint = new Point3f();
|
for (int i = 0; i < bindCoordinates.length; i += 3)
|
{
|
mapPoint.set(targetCoordinates[i], targetCoordinates[i + 1], targetCoordinates[i + 2]);
|
worldToSkin.transform(mapPoint);
|
targetCoordinates[i] = mapPoint.x;
|
targetCoordinates[i + 1] = mapPoint.y;
|
targetCoordinates[i + 2] = mapPoint.z;
|
}
|
}
|
|
// Point4f ptN = new Point4f();
|
public int evalBoneWeights(Bone bone, int offset, Transform3D parentWorldToLocal, Transform3D parentLocalToWorld)
|
{
|
Transform3D worldToLocal = new Transform3D();
|
Transform3D localToWorld = new Transform3D();
|
Transform3D sceneXform = new Transform3D();
|
|
worldToLocal.mul(bone.bindInvXform, parentWorldToLocal);
|
localToWorld.mul(parentLocalToWorld, bone.animXform);
|
sceneXform.mul(localToWorld, worldToLocal);
|
|
Point3f pt = new Point3f();
|
Point3f norm = new Point3f();
|
|
for (int i = 0; i < bindCoordinates.length; i += 3)
|
{
|
//boneWeights[offset] = 1f;
|
if (boneWeights[offset] < 0.01)
|
{
|
//Do not process transform if only a tiny amount of bone contribution
|
// is present
|
offset++;
|
continue;
|
}
|
|
//Evaluate coorinate
|
norm.x = pt.x = bindCoordinates[i];
|
norm.y = pt.y = bindCoordinates[i + 1];
|
norm.z = pt.z = bindCoordinates[i + 2];
|
|
sceneXform.transform(pt);
|
float boneWeight = boneWeights[offset];
|
pt.scale(boneWeight);
|
|
targetCoordinates[i] += pt.x;
|
targetCoordinates[i + 1] += pt.y;
|
targetCoordinates[i + 2] += pt.z;
|
|
//Evaluate normal offset
|
norm.x += bindNormals[i];
|
norm.y += bindNormals[i + 1];
|
norm.z += bindNormals[i + 2];
|
|
sceneXform.transform(norm);
|
norm.scale(boneWeights[offset++]);
|
|
targetNormals[i] += norm.x;
|
targetNormals[i + 1] += norm.y;
|
targetNormals[i + 2] += norm.z;
|
}
|
|
for (Iterator it = bone.children.iterator(); it.hasNext();)
|
{
|
Bone nextBone = (Bone) it.next();
|
offset = evalBoneWeights(nextBone, offset, worldToLocal, localToWorld);
|
}
|
|
return offset;
|
}
|
}
|