|
@@ -1,5 +1,5 @@
|
|
|
/*
|
|
|
- * Copyright (c) 2024 jMonkeyEngine
|
|
|
+ * Copyright (c) 2025 jMonkeyEngine
|
|
|
* All rights reserved.
|
|
|
*
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
@@ -32,42 +32,47 @@
|
|
|
package jme3test.model.anim;
|
|
|
|
|
|
import com.jme3.anim.AnimComposer;
|
|
|
+import com.jme3.anim.AnimLayer;
|
|
|
+import com.jme3.anim.Armature;
|
|
|
import com.jme3.anim.ArmatureMask;
|
|
|
import com.jme3.anim.SingleLayerInfluenceMask;
|
|
|
import com.jme3.anim.SkinningControl;
|
|
|
import com.jme3.anim.tween.action.ClipAction;
|
|
|
import com.jme3.anim.util.AnimMigrationUtils;
|
|
|
import com.jme3.app.SimpleApplication;
|
|
|
+import com.jme3.export.binary.BinaryExporter;
|
|
|
import com.jme3.font.BitmapFont;
|
|
|
import com.jme3.font.BitmapText;
|
|
|
import com.jme3.input.KeyInput;
|
|
|
import com.jme3.input.controls.ActionListener;
|
|
|
import com.jme3.input.controls.KeyTrigger;
|
|
|
import com.jme3.light.DirectionalLight;
|
|
|
-import com.jme3.math.ColorRGBA;
|
|
|
import com.jme3.math.Vector3f;
|
|
|
import com.jme3.scene.Spatial;
|
|
|
|
|
|
/**
|
|
|
* Tests {@link SingleLayerInfluenceMask}.
|
|
|
- *
|
|
|
- * The test runs two simultaneous looping actions on seperate layers.
|
|
|
+ *
|
|
|
+ * The test runs two simultaneous looping actions on separate layers.
|
|
|
* <p>
|
|
|
* The test <strong>fails</strong> if the visible dancing action does <em>not</em>
|
|
|
- * loop seamlessly when using SingleLayerInfluenceMasks. Note that the action is not
|
|
|
- * expected to loop seamlessly when <em>not</em> using SingleLayerArmatureMasks.
|
|
|
+ * loop seamlessly when using SingleLayerInfluenceMasks. Note that the action is
|
|
|
+ * not expected to loop seamlessly when <em>not</em> using
|
|
|
+ * SingleLayerArmatureMasks.
|
|
|
* <p>
|
|
|
- * Press the spacebar to switch between using SingleLayerInfluenceMasks and masks
|
|
|
- * provided by {@link ArmatureMask}.
|
|
|
- *
|
|
|
+ * Press the spacebar to switch between using SingleLayerInfluenceMasks and
|
|
|
+ * masks provided by {@link ArmatureMask}.
|
|
|
+ *
|
|
|
* @author codex
|
|
|
*/
|
|
|
public class TestSingleLayerInfluenceMask extends SimpleApplication implements ActionListener {
|
|
|
-
|
|
|
+
|
|
|
private Spatial model;
|
|
|
- private AnimComposer anim;
|
|
|
- private SkinningControl skin;
|
|
|
- private boolean useSLIMask = true;
|
|
|
+ private AnimComposer animComposer;
|
|
|
+ private SkinningControl skinningControl;
|
|
|
+ private final String idleLayer = "idleLayer";
|
|
|
+ private final String danceLayer = "danceLayer";
|
|
|
+ private boolean useSingleLayerInfMask = true;
|
|
|
private BitmapText display;
|
|
|
|
|
|
public static void main(String[] args) {
|
|
@@ -77,74 +82,123 @@ public class TestSingleLayerInfluenceMask extends SimpleApplication implements A
|
|
|
|
|
|
@Override
|
|
|
public void simpleInitApp() {
|
|
|
-
|
|
|
- flyCam.setMoveSpeed(30f);
|
|
|
|
|
|
+ flyCam.setMoveSpeed(30f);
|
|
|
+
|
|
|
DirectionalLight dl = new DirectionalLight();
|
|
|
dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
|
|
|
- dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
|
|
|
rootNode.addLight(dl);
|
|
|
-
|
|
|
+
|
|
|
BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt");
|
|
|
display = new BitmapText(font);
|
|
|
display.setSize(font.getCharSet().getRenderedSize());
|
|
|
display.setText("");
|
|
|
- display.setLocalTranslation(5, context.getSettings().getHeight()-5, 0);
|
|
|
+ display.setLocalTranslation(5, context.getSettings().getHeight() - 5, 0);
|
|
|
guiNode.attachChild(display);
|
|
|
-
|
|
|
- inputManager.addMapping("reset", new KeyTrigger(KeyInput.KEY_SPACE));
|
|
|
- inputManager.addListener(this, "reset");
|
|
|
-
|
|
|
+
|
|
|
+ inputManager.addMapping("SWITCH_MASKS", new KeyTrigger(KeyInput.KEY_SPACE));
|
|
|
+ inputManager.addListener(this, "SWITCH_MASKS");
|
|
|
+
|
|
|
setupModel();
|
|
|
-
|
|
|
+ createAnimMasks();
|
|
|
+ testSerialization();
|
|
|
+ playAnimations();
|
|
|
+ updateUI();
|
|
|
}
|
|
|
+
|
|
|
@Override
|
|
|
public void simpleUpdate(float tpf) {
|
|
|
cam.lookAt(model.getWorldTranslation(), Vector3f.UNIT_Y);
|
|
|
}
|
|
|
+
|
|
|
@Override
|
|
|
public void onAction(String name, boolean isPressed, float tpf) {
|
|
|
- if (name.equals("reset") && isPressed) {
|
|
|
- model.removeFromParent();
|
|
|
- setupModel();
|
|
|
+ if (name.equals("SWITCH_MASKS") && isPressed) {
|
|
|
+ useSingleLayerInfMask = !useSingleLayerInfMask;
|
|
|
+ animComposer.removeLayer(idleLayer);
|
|
|
+ animComposer.removeLayer(danceLayer);
|
|
|
+
|
|
|
+ createAnimMasks();
|
|
|
+ playAnimations();
|
|
|
+ updateUI();
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets up the model by loading it, migrating animations, and attaching it to
|
|
|
+ * the root node.
|
|
|
+ */
|
|
|
private void setupModel() {
|
|
|
-
|
|
|
model = assetManager.loadModel("Models/Sinbad/SinbadOldAnim.j3o");
|
|
|
+ // Migrate the model's animations to the new system
|
|
|
AnimMigrationUtils.migrate(model);
|
|
|
- anim = model.getControl(AnimComposer.class);
|
|
|
- skin = model.getControl(SkinningControl.class);
|
|
|
-
|
|
|
- if (useSLIMask) {
|
|
|
- SingleLayerInfluenceMask walkLayer = new SingleLayerInfluenceMask("idleLayer", anim, skin);
|
|
|
- walkLayer.addAll();
|
|
|
- walkLayer.makeLayer();
|
|
|
- SingleLayerInfluenceMask danceLayer = new SingleLayerInfluenceMask("danceLayer", anim, skin);
|
|
|
- danceLayer.addAll();
|
|
|
- danceLayer.makeLayer();
|
|
|
- } else {
|
|
|
- anim.makeLayer("idleLayer", ArmatureMask.createMask(skin.getArmature(), "Root"));
|
|
|
- anim.makeLayer("danceLayer", ArmatureMask.createMask(skin.getArmature(), "Root"));
|
|
|
- }
|
|
|
-
|
|
|
- setSLIMaskInfo();
|
|
|
- useSLIMask = !useSLIMask;
|
|
|
-
|
|
|
- ClipAction clip = (ClipAction)anim.action("Dance");
|
|
|
+ rootNode.attachChild(model);
|
|
|
+
|
|
|
+ animComposer = model.getControl(AnimComposer.class);
|
|
|
+ skinningControl = model.getControl(SkinningControl.class);
|
|
|
+
|
|
|
+ ClipAction clip = (ClipAction) animComposer.action("Dance");
|
|
|
clip.setMaxTransitionWeight(.9f);
|
|
|
- ClipAction clip2 = (ClipAction)anim.action("IdleTop");
|
|
|
+
|
|
|
+ ClipAction clip2 = (ClipAction) animComposer.action("IdleTop");
|
|
|
clip2.setMaxTransitionWeight(.8f);
|
|
|
-
|
|
|
- anim.setCurrentAction("Dance", "danceLayer");
|
|
|
- anim.setCurrentAction("IdleTop", "idleLayer");
|
|
|
+ }
|
|
|
|
|
|
- rootNode.attachChild(model);
|
|
|
-
|
|
|
+ /**
|
|
|
+ * Creates animation masks for the idle and dance layers.
|
|
|
+ */
|
|
|
+ private void createAnimMasks() {
|
|
|
+ Armature armature = skinningControl.getArmature();
|
|
|
+ ArmatureMask idleMask;
|
|
|
+ ArmatureMask danceMask;
|
|
|
+
|
|
|
+ if (useSingleLayerInfMask) {
|
|
|
+ // Create single layer influence masks for idle and dance layers
|
|
|
+ idleMask = new SingleLayerInfluenceMask(idleLayer, animComposer, armature);
|
|
|
+ danceMask = new SingleLayerInfluenceMask(danceLayer, animComposer, armature);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // Create default armature masks for idle and dance layers
|
|
|
+ idleMask = ArmatureMask.createMask(armature, "Root");
|
|
|
+ danceMask = ArmatureMask.createMask(armature, "Root");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Assign the masks to the respective animation layers
|
|
|
+ animComposer.makeLayer(idleLayer, idleMask);
|
|
|
+ animComposer.makeLayer(danceLayer, danceMask);
|
|
|
}
|
|
|
- private void setSLIMaskInfo() {
|
|
|
- display.setText("Using SingleLayerInfluenceMasks: "+useSLIMask+"\nPress Spacebar to switch masks");
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Plays the "Dance" and "IdleTop" animations on their respective layers.
|
|
|
+ */
|
|
|
+ private void playAnimations() {
|
|
|
+ animComposer.setCurrentAction("Dance", danceLayer);
|
|
|
+ animComposer.setCurrentAction("IdleTop", idleLayer);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tests the serialization of animation masks.
|
|
|
+ */
|
|
|
+ private void testSerialization() {
|
|
|
+ AnimComposer aComposer = model.getControl(AnimComposer.class);
|
|
|
+ for (String layerName : aComposer.getLayerNames()) {
|
|
|
+
|
|
|
+ System.out.println("layerName: " + layerName);
|
|
|
+ AnimLayer layer = aComposer.getLayer(layerName);
|
|
|
+
|
|
|
+ if (layer.getMask() instanceof SingleLayerInfluenceMask) {
|
|
|
+ SingleLayerInfluenceMask mask = (SingleLayerInfluenceMask) layer.getMask();
|
|
|
+ // Serialize and deserialize the mask
|
|
|
+ mask = BinaryExporter.saveAndLoad(assetManager, mask);
|
|
|
+ // Reassign the AnimComposer to the mask and remake the layer
|
|
|
+ mask.setAnimComposer(aComposer);
|
|
|
+ aComposer.makeLayer(layerName, mask);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateUI() {
|
|
|
+ display.setText("Using SingleLayerInfluenceMasks: " + useSingleLayerInfMask + "\nPress Spacebar to switch masks");
|
|
|
+ }
|
|
|
+
|
|
|
}
|