diff --git a/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java index 7cc5f3c58b..9ceb4b3638 100644 --- a/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java +++ b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2023 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,12 +31,21 @@ */ package com.jme3.anim; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.material.MatParamOverride; import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; -import com.jme3.renderer.*; -import com.jme3.scene.*; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.mesh.IndexBuffer; @@ -53,64 +62,77 @@ import java.util.logging.Logger; /** - * The Skinning control deforms a model according to an armature, It handles the - * computation of the deformation matrices and performs the transformations on - * the mesh + * The `SkinningControl` deforms a 3D model according to an {@link Armature}. It manages the + * computation of deformation matrices and applies these transformations to the mesh, + * supporting both software and hardware-accelerated skinning. + * + *

+ * **Software Skinning:** Performed on the CPU, offering broader compatibility but + * potentially lower performance for complex models. *

- * It can perform software skinning or Hardware skinning + * **Hardware Skinning:** Utilizes the GPU for deformation, providing significantly + * better performance but requiring shader support and having a limit on the number + * of bones (typically 255 in common shaders). * - * @author Rémy Bouquet Based on SkeletonControl by Kirill Vainer + * @author Nehon */ -public class SkinningControl extends AbstractControl implements Cloneable, JmeCloneable { +public class SkinningControl extends AbstractControl implements JmeCloneable { private static final Logger logger = Logger.getLogger(SkinningControl.class.getName()); + /** + * The maximum number of bones supported for hardware skinning in common shaders. + */ + private static final int MAX_BONES_HW_SKINNING_SUPPORT = 255; + /** * The armature of the model. */ private Armature armature; /** - * List of geometries affected by this control. + * A list of geometries that this control will deform. */ private SafeArrayList targets = new SafeArrayList<>(Geometry.class); /** - * Used to track when a mesh was updated. Meshes are only updated if they + * Used to track when a mesh needs to be updated. Meshes are only updated if they * are visible in at least one camera. */ - private boolean wasMeshUpdated = false; + private boolean meshUpdateRequired = true; /** - * User wishes to use hardware skinning if available. + * Indicates whether hardware skinning is preferred. If `true` and the GPU + * supports it, hardware skinning will be enabled. */ - private transient boolean hwSkinningDesired = true; + private transient boolean hwSkinningPreferred = true; /** - * Hardware skinning is currently being used. + * Indicates if hardware skinning is currently active and being used. */ private transient boolean hwSkinningEnabled = false; /** - * Hardware skinning was tested on this GPU, results - * are stored in {@link #hwSkinningSupported} variable. + * Flag indicating whether hardware skinning compatibility has been tested + * on the current GPU. Results are stored in {@link #hwSkinningSupported}. */ private transient boolean hwSkinningTested = false; /** - * If hardware skinning was {@link #hwSkinningTested tested}, then - * this variable will be set to true if supported, and false if otherwise. + * Stores the result of the hardware skinning compatibility test. `true` if + * supported, `false` otherwise. This is only valid after + * {@link #hwSkinningTested} is `true`. */ private transient boolean hwSkinningSupported = false; /** - * Bone offset matrices, recreated each frame + * Bone offset matrices, computed each frame to deform the mesh based on + * the armature's current pose. */ - private transient Matrix4f[] offsetMatrices; - + private transient Matrix4f[] boneOffsetMatrices; - private MatParamOverride numberOfJointsParam; - private MatParamOverride jointMatricesParam; + private MatParamOverride numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); + private MatParamOverride jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); /** * Serialization only. Do not use. @@ -119,26 +141,26 @@ protected SkinningControl() { } /** - * Creates an armature control. The list of targets will be acquired - * automatically when the control is attached to a node. + * Creates a new `SkinningControl` for the given armature. * - * @param armature the armature + * @param armature The armature that drives the deformation (not null). */ public SkinningControl(Armature armature) { if (armature == null) { - throw new IllegalArgumentException("armature cannot be null"); + throw new IllegalArgumentException("armature cannot be null."); } this.armature = armature; - this.numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); - this.jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); } - - private void switchToHardware() { + /** + * Configures the material parameters and meshes for hardware skinning. + */ + private void enableHardwareSkinning() { numberOfJointsParam.setEnabled(true); jointMatricesParam.setEnabled(true); - // Next full 10 bones (e.g. 30 on 24 bones) + // Calculate the number of bones rounded up to the nearest multiple of 10. + // This is often required by shaders for array uniform declarations. int numBones = ((armature.getJointCount() / 10) + 1) * 10; numberOfJointsParam.setValue(numBones); @@ -150,7 +172,10 @@ private void switchToHardware() { } } - private void switchToSoftware() { + /** + * Configures the material parameters and meshes for software skinning. + */ + private void enableSoftwareSkinning() { numberOfJointsParam.setEnabled(false); jointMatricesParam.setEnabled(false); @@ -162,22 +187,34 @@ private void switchToSoftware() { } } - private boolean testHardwareSupported(RenderManager rm) { - - //Only 255 bones max supported with hardware skinning - if (armature.getJointCount() > 255) { + /** + * Tests if hardware skinning is supported by the GPU for the current spatial. + * + * @param renderManager the RenderManager instance + * @return true if hardware skinning is supported, false otherwise + */ + private boolean testHardwareSupported(RenderManager renderManager) { + // Only 255 bones max supported with hardware skinning in common shaders. + if (armature.getJointCount() > MAX_BONES_HW_SKINNING_SUPPORT) { + logger.log(Level.INFO, "Hardware skinning not supported for {0}: Too many bones ({1} > 255).", + new Object[]{spatial, armature.getJointCount()}); return false; } - switchToHardware(); + // Temporarily enable hardware skinning to test shader compilation. + enableHardwareSkinning(); + boolean hwSkinningEngaged = false; try { - rm.preloadScene(spatial); - return true; - } catch (RendererException e) { - logger.log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e); - return false; + renderManager.preloadScene(spatial); + logger.log(Level.INFO, "Hardware skinning engaged for {0}", spatial); + hwSkinningEngaged = true; + + } catch (RendererException ex) { + logger.log(Level.WARNING, "Could not enable HW skinning due to shader compile error: ", ex); } + + return hwSkinningEngaged; } /** @@ -190,7 +227,7 @@ private boolean testHardwareSupported(RenderManager rm) { * @see #isHardwareSkinningUsed() */ public void setHardwareSkinningPreferred(boolean preferred) { - hwSkinningDesired = preferred; + hwSkinningPreferred = preferred; } /** @@ -199,7 +236,7 @@ public void setHardwareSkinningPreferred(boolean preferred) { * @see #setHardwareSkinningPreferred(boolean) */ public boolean isHardwareSkinningPreferred() { - return hwSkinningDesired; + return hwSkinningPreferred; } /** @@ -209,25 +246,21 @@ public boolean isHardwareSkinningUsed() { return hwSkinningEnabled; } - /** - * If specified the geometry has an animated mesh, add its mesh and material - * to the lists of animation targets. + * Recursively finds and adds animated geometries to the targets list. + * + * @param sp The spatial to search within. */ - private void findTargets(Geometry geometry) { - Mesh mesh = geometry.getMesh(); - if (mesh != null && mesh.isAnimated()) { - targets.add(geometry); - } - - } - - private void findTargets(Node node) { - for (Spatial child : node.getChildren()) { - if (child instanceof Geometry) { - findTargets((Geometry) child); - } else if (child instanceof Node) { - findTargets((Node) child); + private void collectAnimatedGeometries(Spatial sp) { + if (sp instanceof Geometry) { + Geometry geo = (Geometry) sp; + Mesh mesh = geo.getMesh(); + if (mesh != null && mesh.isAnimated()) { + targets.add(geo); + } + } else if (sp instanceof Node) { + for (Spatial child : ((Node) sp).getChildren()) { + collectAnimatedGeometries(child); } } } @@ -236,65 +269,72 @@ private void findTargets(Node node) { public void setSpatial(Spatial spatial) { Spatial oldSpatial = this.spatial; super.setSpatial(spatial); - updateTargetsAndMaterials(spatial); + updateAnimationTargets(spatial); if (oldSpatial != null) { + // Ensure parameters are removed from the old spatial to prevent memory leaks oldSpatial.removeMatParamOverride(numberOfJointsParam); oldSpatial.removeMatParamOverride(jointMatricesParam); } if (spatial != null) { - spatial.removeMatParamOverride(numberOfJointsParam); - spatial.removeMatParamOverride(jointMatricesParam); + // Add parameters to the new spatial. No need to remove first if they are not already present. spatial.addMatParamOverride(numberOfJointsParam); spatial.addMatParamOverride(jointMatricesParam); } } + /** + * Performs software skinning updates. + */ private void controlRenderSoftware() { resetToBind(); // reset morph meshes to bind pose - offsetMatrices = armature.computeSkinningMatrices(); + boneOffsetMatrices = armature.computeSkinningMatrices(); for (Geometry geometry : targets) { Mesh mesh = geometry.getMesh(); - // NOTE: This assumes code higher up has - // already ensured this mesh is animated. - // Otherwise a crash will happen in skin update. - softwareSkinUpdate(mesh, offsetMatrices); + // NOTE: This assumes code higher up has already ensured this mesh is animated. + // Otherwise, a crash will happen in skin update. + applySoftwareSkinning(mesh, boneOffsetMatrices); } } + /** + * Prepares parameters for hardware skinning. + */ private void controlRenderHardware() { - offsetMatrices = armature.computeSkinningMatrices(); - jointMatricesParam.setValue(offsetMatrices); + boneOffsetMatrices = armature.computeSkinningMatrices(); + jointMatricesParam.setValue(boneOffsetMatrices); } @Override protected void controlRender(RenderManager rm, ViewPort vp) { - if (!wasMeshUpdated) { - updateTargetsAndMaterials(spatial); + if (meshUpdateRequired) { + updateAnimationTargets(spatial); // Prevent illegal cases. These should never happen. - assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled); - assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported); + assert hwSkinningTested || (!hwSkinningSupported && !hwSkinningEnabled); + assert !hwSkinningEnabled || (hwSkinningTested && hwSkinningSupported); - if (hwSkinningDesired && !hwSkinningTested) { + if (hwSkinningPreferred && !hwSkinningTested) { + // If hardware skinning is preferred and hasn't been tested yet, test it. hwSkinningTested = true; hwSkinningSupported = testHardwareSupported(rm); if (hwSkinningSupported) { hwSkinningEnabled = true; - - Logger.getLogger(SkinningControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial); } else { - switchToSoftware(); + enableSoftwareSkinning(); } - } else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) { - switchToHardware(); + } else if (hwSkinningPreferred && hwSkinningSupported && !hwSkinningEnabled) { + // If hardware skinning is preferred, supported, but not yet enabled, enable it. + enableHardwareSkinning(); hwSkinningEnabled = true; - } else if (!hwSkinningDesired && hwSkinningEnabled) { - switchToSoftware(); + + } else if (!hwSkinningPreferred && hwSkinningEnabled) { + // If hardware skinning is no longer preferred but is enabled, switch to software. + enableSoftwareSkinning(); hwSkinningEnabled = false; } @@ -304,17 +344,22 @@ protected void controlRender(RenderManager rm, ViewPort vp) { controlRenderSoftware(); } - wasMeshUpdated = true; + meshUpdateRequired = false; // Reset flag after update } } @Override protected void controlUpdate(float tpf) { - wasMeshUpdated = false; + meshUpdateRequired = true; // Mark for mesh update on next render pass armature.update(); } - //only do this for software updates + /** + * Resets the vertex, normal, and tangent buffers of animated meshes to their + * original bind pose. This is crucial for software skinning to ensure + * transformations are applied from a consistent base. + * This method is only applied when performing software updates. + */ void resetToBind() { for (Geometry geometry : targets) { Mesh mesh = geometry.getMesh(); @@ -378,51 +423,51 @@ public void cloneFields(Cloner cloner, Object original) { } /** - * Access the attachments node of the named bone. If the bone doesn't - * already have an attachments node, create one and attach it to the scene - * graph. Models and effects attached to the attachments node will follow - * the bone's motions. + * Provides access to the attachment node for a specific joint in the armature. + * If an attachment node does not already exist for the named joint, one will be + * created and attached to the scene graph. Models or effects attached to this + * node will follow the motion of the corresponding bone. * * @param jointName the name of the joint * @return the attachments node of the joint */ public Node getAttachmentsNode(String jointName) { - Joint b = armature.getJoint(jointName); - if (b == null) { - throw new IllegalArgumentException("Given bone name does not exist " - + "in the armature."); + Joint joint = armature.getJoint(jointName); + if (joint == null) { + throw new IllegalArgumentException( + "Given joint name '" + jointName + "' does not exist in the armature."); } - updateTargetsAndMaterials(spatial); - int boneIndex = armature.getJointIndex(b); - Node n = b.getAttachmentsNode(boneIndex, targets); - /* - * Select a node to parent the attachments node. - */ + updateAnimationTargets(spatial); + int jointIndex = armature.getJointIndex(joint); + Node attachNode = joint.getAttachmentsNode(jointIndex, targets); + + // Determine the appropriate parent for the attachment node. Node parent; if (spatial instanceof Node) { parent = (Node) spatial; // the usual case } else { parent = spatial.getParent(); } - parent.attachChild(n); + parent.attachChild(attachNode); - return n; + return attachNode; } /** - * returns the armature of this control + * Returns the armature associated with this skinning control. * - * @return the pre-existing instance + * @return The pre-existing `Armature` instance. */ public Armature getArmature() { return armature; } /** - * Enumerate the target meshes of this control. + * Returns an array containing all the target meshes that this control + * is currently affecting. * - * @return a new array + * @return A new array of `Mesh` objects. */ public Mesh[] getTargets() { Mesh[] result = new Mesh[targets.size()]; @@ -437,30 +482,31 @@ public Mesh[] getTargets() { } /** - * Update the mesh according to the given transformation matrices + * Applies software skinning transformations to the given mesh using the + * provided bone offset matrices. * - * @param mesh then mesh - * @param offsetMatrices the transformation matrices to apply + * @param mesh The mesh to deform. + * @param offsetMatrices The array of transformation matrices for each bone. */ - private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { + private void applySoftwareSkinning(Mesh mesh, Matrix4f[] offsetMatrices) { VertexBuffer tb = mesh.getBuffer(Type.Tangent); if (tb == null) { - //if there are no tangents use the classic skinning + // if there are no tangents use the classic skinning applySkinning(mesh, offsetMatrices); } else { - //if there are tangents use the skinning with tangents + // if there are tangents use the skinning with tangents applySkinningTangents(mesh, offsetMatrices, tb); } - - } /** - * Method to apply skinning transforms to a mesh's buffers + * Applies skinning transformations to a mesh's position and normal buffers. + * This method iterates through each vertex, applies the weighted sum of + * bone transformations, and updates the vertex buffers. * - * @param mesh the mesh - * @param offsetMatrices the offset matrices to apply + * @param mesh The mesh to apply skinning to. + * @param offsetMatrices The bone offset matrices to use for transformation. */ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { int maxWeightsPerVert = mesh.getMaxNumWeights(); @@ -555,19 +601,16 @@ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { vb.updateData(fvb); nb.updateData(fnb); - } /** - * Specific method for skinning with tangents to avoid cluttering the - * classic skinning calculation with null checks that would slow down the - * process even if tangents don't have to be computed. Also the iteration - * has additional indexes since tangent has 4 components instead of 3 for - * pos and norm + * Applies skinning transformations to a mesh's position, normal, and tangent buffers. + * This method is specifically designed for meshes that include tangent data, + * ensuring proper deformation of tangents alongside positions and normals. * - * @param mesh the mesh - * @param offsetMatrices the offsetMatrices to apply - * @param tb the tangent vertexBuffer + * @param mesh The mesh to apply skinning to. + * @param offsetMatrices The bone offset matrices to use for transformation. + * @param tb The tangent `VertexBuffer`. */ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { int maxWeightsPerVert = mesh.getMaxNumWeights(); @@ -594,7 +637,6 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB FloatBuffer ftb = (FloatBuffer) tb.getData(); ftb.rewind(); - // get boneIndexes and weights for mesh IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData()); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); @@ -605,8 +647,6 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB int idxWeights = 0; TempVars vars = TempVars.get(); - - float[] posBuf = vars.skinPositions; float[] normBuf = vars.skinNormals; float[] tanBuf = vars.skinTangents; @@ -723,9 +763,6 @@ public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(armature, "armature", null); - - oc.write(numberOfJointsParam, "numberOfBonesParam", null); - oc.write(jointMatricesParam, "boneMatricesParam", null); } /** @@ -741,15 +778,13 @@ public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); armature = (Armature) in.readSavable("armature", null); - numberOfJointsParam = (MatParamOverride) in.readSavable("numberOfBonesParam", null); - jointMatricesParam = (MatParamOverride) in.readSavable("boneMatricesParam", null); - - if (numberOfJointsParam == null) { - numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); - jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); - getSpatial().addMatParamOverride(numberOfJointsParam); - getSpatial().addMatParamOverride(jointMatricesParam); + for (MatParamOverride mpo : spatial.getLocalMatParamOverrides().getArray()) { + if (mpo.getName().equals("NumberOfBones") || mpo.getName().equals("BoneMatrices")) { + spatial.removeMatParamOverride(mpo); + } } + spatial.addMatParamOverride(numberOfJointsParam); + spatial.addMatParamOverride(jointMatricesParam); } /** @@ -757,13 +792,9 @@ public void read(JmeImporter im) throws IOException { * * @param spatial the controlled spatial */ - private void updateTargetsAndMaterials(Spatial spatial) { + private void updateAnimationTargets(Spatial spatial) { targets.clear(); - - if (spatial instanceof Node) { - findTargets((Node) spatial); - } else if (spatial instanceof Geometry) { - findTargets((Geometry) spatial); - } + collectAnimatedGeometries(spatial); } + } diff --git a/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java index 57dba2fb54..2c1449f5d5 100644 --- a/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java +++ b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,53 +29,109 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - package jme3test.export; import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; import com.jme3.app.SimpleApplication; import com.jme3.export.binary.BinaryExporter; -import com.jme3.export.binary.BinaryImporter; +import com.jme3.font.BitmapText; +import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; +import com.jme3.material.MatParamOverride; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import java.io.*; - +/** + * This class is a jMonkeyEngine 3 (jME3) test application designed to verify + * the import, export, and runtime behavior of 3D models, particularly those + * in or compatible with the Ogre3D format (.mesh.xml). + * It loads an Ogre model, saves and reloads it using jME3's binary exporter, + * plays an animation, and displays debugging information about its skinning + * and material parameters. + * + * @author capdevon + */ public class TestOgreConvert extends SimpleApplication { - public static void main(String[] args){ + public static void main(String[] args) { TestOgreConvert app = new TestOgreConvert(); + app.setPauseOnLostFocus(false); app.start(); } + private final StringBuilder sb = new StringBuilder(); + private int frameCount = 0; + private BitmapText bmp; + private Spatial spCopy; + private SkinningControl skinningControl; + @Override public void simpleInitApp() { - Spatial ogreModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + configureCamera(); + setupLights(); + + bmp = createLabelText(10, 20, ""); + + // Load the Ogre model (Oto.mesh.xml) from the assets + Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + // Save the loaded model to jME3's binary format and then reload it. + // This tests the binary serialization/deserialization process. + spCopy = BinaryExporter.saveAndLoad(assetManager, model); + spCopy.setName("Oto-Copy"); + rootNode.attachChild(spCopy); + + AnimComposer animComposer = spCopy.getControl(AnimComposer.class); + animComposer.setCurrentAction("Walk"); + + // Get the SkinningControl from the model to inspect skinning properties + skinningControl = spCopy.getControl(SkinningControl.class); + } + + private void setupLights() { + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); DirectionalLight dl = new DirectionalLight(); - dl.setColor(ColorRGBA.White); - dl.setDirection(new Vector3f(0,-1,-1).normalizeLocal()); + dl.setDirection(new Vector3f(0, -1, -1).normalizeLocal()); rootNode.addLight(dl); + } - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BinaryExporter exp = new BinaryExporter(); - exp.save(ogreModel, baos); + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(15f); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - BinaryImporter imp = new BinaryImporter(); - imp.setAssetManager(assetManager); - Node ogreModelReloaded = (Node) imp.load(bais, null, null); + cam.setLocation(new Vector3f(0, 0, 20)); + } - AnimComposer composer = ogreModelReloaded.getControl(AnimComposer.class); - composer.setCurrentAction("Walk"); + @Override + public void simpleUpdate(float tpf) { + frameCount++; + if (frameCount == 10) { + frameCount = 0; - rootNode.attachChild(ogreModelReloaded); - } catch (IOException ex){ - ex.printStackTrace(); + sb.append("HW Skinning Preferred: ").append(skinningControl.isHardwareSkinningPreferred()).append("\n"); + sb.append("HW Skinning Enabled: ").append(skinningControl.isHardwareSkinningUsed()).append("\n"); + sb.append("Mesh Targets: ").append(skinningControl.getTargets().length).append("\n"); + + for (MatParamOverride mpo : spCopy.getLocalMatParamOverrides()) { + sb.append(mpo.getVarType()).append(" "); + sb.append(mpo.getName()).append(": "); + sb.append(mpo.getValue()).append("\n"); + } + + bmp.setText(sb.toString()); + sb.setLength(0); } } + + private BitmapText createLabelText(int x, int y, String text) { + BitmapText bmp = new BitmapText(guiFont); + bmp.setText(text); + bmp.setLocalTranslation(x, settings.getHeight() - y, 0); + bmp.setColor(ColorRGBA.Red); + guiNode.attachChild(bmp); + return bmp; + } }