From 248c89687d7f27bbf35616231abe9a476531cf23 Mon Sep 17 00:00:00 2001 From: rickard Date: Wed, 27 Nov 2024 19:31:16 +0100 Subject: [PATCH 1/2] merge animations action --- .../assets/actions/MergeAnimationsAction.java | 208 ++++++++++++++++++ .../jme3/gde/scenecomposer/Bundle.properties | 1 + .../src/com/jme3/gde/scenecomposer/layer.xml | 15 +- 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 jme3-core/src/com/jme3/gde/core/assets/actions/MergeAnimationsAction.java diff --git a/jme3-core/src/com/jme3/gde/core/assets/actions/MergeAnimationsAction.java b/jme3-core/src/com/jme3/gde/core/assets/actions/MergeAnimationsAction.java new file mode 100644 index 000000000..338ca065b --- /dev/null +++ b/jme3-core/src/com/jme3/gde/core/assets/actions/MergeAnimationsAction.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.gde.core.assets.actions; + +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.AnimTrack; +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.TransformTrack; +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.gde.core.assets.SpatialAssetDataObject; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.bvh.SkeletonMapping; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import jme3utilities.MyAnimation; +import jme3utilities.wes.AnimationEdit; +import org.bushe.swing.event.Logger; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.NotifyDescriptor.Confirmation; +import org.openide.util.Exceptions; + +/** + * Action for merging one or more spatials' animation to another. + * Same rig required. + * @author rickard + */ +public class MergeAnimationsAction implements ActionListener { + + private static final String CANCEL = "Cancel"; + private final List spatials; + private final Logger logger; + + public MergeAnimationsAction(List context) { + this.spatials = context; + logger = Logger.getLogger(MergeAnimationsAction.class.getName()); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (spatials.size() == 1) { + DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( + "Must select more than one spatial", + NotifyDescriptor.ERROR_MESSAGE)); + return; + } + + final Object selectedSpatial = createSelector().getValue(); + + if (selectedSpatial == null || selectedSpatial.equals(CANCEL)) { + logger.log(Logger.Level.INFO, "Operation cancelled by user."); + return; + } + + final SpatialAssetDataObject targetAsset = findSpatial(selectedSpatial.toString()); + + if (targetAsset == null) { + logger.log(Logger.Level.INFO, "Operation failed. No spatial."); + return; + } + + final Spatial targetSpatial = targetAsset.loadAsset(); + final AnimComposer targetAnimComposer = findAnimComposer(targetSpatial, null); + + if (targetAnimComposer == null) { + DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( + String.format("%s has no AnimComposer.", targetSpatial), + NotifyDescriptor.ERROR_MESSAGE)); + return; + } + final Spatial targetAnimComposerSpatial = targetAnimComposer.getSpatial(); + for (final Iterator it = spatials.iterator(); it.hasNext();) { + final SpatialAssetDataObject spatialAssetDataObject = it.next(); + if(spatialAssetDataObject.getName().equals(selectedSpatial)) { + continue; + } + Spatial sourceSpatial = spatialAssetDataObject.loadAsset(); + final AnimComposer sourceAnimComposer = findAnimComposer(sourceSpatial, null); + if (sourceAnimComposer == null) { + DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( + String.format("%s has no AnimComposer.", sourceSpatial), + NotifyDescriptor.ERROR_MESSAGE)); + return; + } + copyClips(sourceAnimComposer, targetAnimComposer, targetAnimComposerSpatial.getControl(SkinningControl.class).getArmature()); + } + logger.log(Logger.Level.INFO, "Merging animations done. Saving."); + try { + targetAsset.saveAsset(); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + private static AnimClip retargetClip( + AnimClip clip, Armature armature, String clipName) { + AnimTrack[] animTracks = clip.getTracks(); + AnimClip result = new AnimClip(clipName); + for (AnimTrack animTrack : animTracks) { + if (MyAnimation.isJointTrack(animTrack)) { + TransformTrack transformTrack = (TransformTrack) animTrack; + HasLocalTransform animTarget = transformTrack.getTarget(); + Joint animJoint = (Joint) animTarget; + String jointName = animJoint.getName(); + Joint charaJoint = armature.getJoint(jointName); + if (charaJoint != null) { + transformTrack.setTarget(charaJoint); + AnimationEdit.addTrack(result, transformTrack); + } + } + } + return result; + } + + + private void copyClips(final AnimComposer from, final AnimComposer to, Armature toArmature) { + final Collection animClips = from.getAnimClips(); + for(AnimClip animClip: animClips) { + to.addAnimClip(retargetClip(animClip, toArmature, animClip.getName())); + logger.log(Logger.Level.DEBUG, String.format("Added anim clip %s", animClip.getName())); + } + } + + private Confirmation createSelector() { + final Object[] spatials = new Object[this.spatials.size() + 1]; + int index = 0; + for (Iterator it = this.spatials.iterator(); it.hasNext();) { + final SpatialAssetDataObject spatialAssetDataObject = it.next(); + spatials[index++] = spatialAssetDataObject.getName(); + } + spatials[index++] = CANCEL; + final NotifyDescriptor.Confirmation message = + new NotifyDescriptor.Confirmation( + "Select spatial to copy animations to."); + message.setOptions(spatials); + + DialogDisplayer.getDefault().notify(message); + + return message; + } + + private AnimComposer findAnimComposer(Spatial spatial, AnimComposer animComposer) { + if (spatial.getControl(AnimComposer.class) != null) { + animComposer = spatial.getControl(AnimComposer.class); + } + if (animComposer == null && spatial instanceof Node node) { + for (Spatial child: node.getChildren()) { + animComposer = findAnimComposer(child, animComposer); + if (animComposer != null) { + return animComposer; + } + } + } + return animComposer; + } + + private SpatialAssetDataObject findSpatial(String selected) { + for (Iterator it = spatials.iterator(); it.hasNext();) { + final SpatialAssetDataObject spatialAssetDataObject = it.next(); + if(spatialAssetDataObject.getName().equals(selected)) { + return spatialAssetDataObject; + } + } + NotifyDescriptor.Message msg = new NotifyDescriptor.Message( + "Main asset to copy to not found. This is likely an issue with the tool itself.", + NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(msg); + return null; + } + +} diff --git a/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties b/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties index 98860b313..b0e223570 100644 --- a/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties +++ b/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/Bundle.properties @@ -4,6 +4,7 @@ CTL_OpenSceneComposer=Edit in SceneComposer CTL_SceneComposerAction=SceneComposer CTL_SceneComposerTopComponent=SceneComposer Window CTL_SomeAction=SomeAction +CTL_MergeAnimationsAction=Merge Animations HINT_SceneComposerTopComponent=This is a SceneComposer window OpenIDE-Module-Display-Category=jMonkeyEngine OpenIDE-Module-Long-Description=\ diff --git a/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml b/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml index 6e623878b..f198adcec 100644 --- a/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml +++ b/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/layer.xml @@ -33,6 +33,15 @@ + + + + + + + + + @@ -59,9 +68,13 @@ + + + + - + From 75c0eb2789d785b1f4c9085b09986cf4a44d8aa9 Mon Sep 17 00:00:00 2001 From: rickard Date: Wed, 27 Nov 2024 20:28:23 +0100 Subject: [PATCH 2/2] include config --- build.gradle | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 9aeca489c..94853d57f 100644 --- a/build.gradle +++ b/build.gradle @@ -42,8 +42,9 @@ dependencies { corelibs dep("org.jmonkeyengine:jme3-plugins-json:$jmeVersion-$jmeVersionTag", true, true) corelibs dep("org.jmonkeyengine:jme3-lwjgl:$jmeVersion-$jmeVersionTag", true, true) corelibs dep("org.jmonkeyengine:jme3-effects:$jmeVersion-$jmeVersionTag", true, true) - corelibs dep("com.github.stephengold:Minie:8.0.0", false, false) // replacement for bullet-native - corelibs dep("com.github.stephengold:Heart:9.0.0", true, true) // requirement for Minie + corelibs dep("com.github.stephengold:Minie:8.2.0", false, false) // replacement for bullet-native + corelibs dep("com.github.stephengold:Heart:9.1.0", true, true) // requirement for Minie + corelibs dep("com.github.stephengold:Wes:0.8.1", true, true) // Animation toolkit corelibs dep(fileTree("lib"), false, false) corelibs dep("org.jmonkeyengine:jme3-jogg:$jmeVersion-$jmeVersionTag", true, true) @@ -63,7 +64,6 @@ dependencies { optlibs dep("org.jmonkeyengine:jme3-ios:$jmeVersion-$jmeVersionTag", true, true) optlibs dep("org.jmonkeyengine:jme3-android-native:$jmeVersion-$jmeVersionTag", true, true) optlibs dep("org.jmonkeyengine:jme3-lwjgl3:$jmeVersion-$jmeVersionTag", true, true) - optlibs dep("com.github.stephengold:Wes:0.7.5", true, true) testdatalibs dep("org.jmonkeyengine:jme3-testdata:$jmeVersion-$jmeVersionTag", false, false) examplelibs dep("org.jmonkeyengine:jme3-examples:$jmeVersion-$jmeVersionTag", false, true) } @@ -132,7 +132,13 @@ task copyBaseLibs(dependsOn:configurations.corelibs) { into "jme3-core-baselibs/release/modules/ext/" } } else if( file.name.contains("Heart") && !isSourceOrJavadoc(file.name)) { - // Special handling of Minie, since it doesn't follow the name convention + // Special handling of Heart, since it doesn't follow the name convention + copy { + from file + into "jme3-core-baselibs/release/modules/ext/" + } + } else if( file.name.contains("Wes") && !isSourceOrJavadoc(file.name)) { + // Special handling of Wes, since it doesn't follow the name convention copy { from file into "jme3-core-baselibs/release/modules/ext/"