/* * 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; } }