Skip to content

Commit cf030a9

Browse files
authored
Adds SingleLayerInfluenceMask (#2043)
Checks if a joint is currently being used by a higher layer before approving it for personal use via contains(Object target). This can help smooth some interpolation issues that may arise between layers with looped/sequenced animations
1 parent 0b40bba commit cf030a9

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* Copyright (c) 2024 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package com.jme3.anim;
33+
34+
import com.jme3.scene.Spatial;
35+
36+
/**
37+
* Mask that excludes joints from participating in the layer
38+
* if a higher layer is using those joints in an animation.
39+
*
40+
* @author codex
41+
*/
42+
public class SingleLayerInfluenceMask extends ArmatureMask {
43+
44+
private final String layer;
45+
private final AnimComposer anim;
46+
private final SkinningControl skin;
47+
private boolean checkUpperLayers = true;
48+
49+
/**
50+
* @param layer The layer this mask is targeted for. It is important
51+
* that this match the name of the layer this mask is (or will be) part of. You
52+
* can use {@link makeLayer} to ensure this.
53+
* @param spatial Spatial containing necessary controls ({@link AnimComposer} and {@link SkinningControl})
54+
*/
55+
public SingleLayerInfluenceMask(String layer, Spatial spatial) {
56+
super();
57+
this.layer = layer;
58+
anim = spatial.getControl(AnimComposer.class);
59+
skin = spatial.getControl(SkinningControl.class);
60+
}
61+
/**
62+
* @param layer The layer this mask is targeted for. It is important
63+
* that this match the name of the layer this mask is (or will be) part of. You
64+
* can use {@link makeLayer} to ensure this.
65+
* @param anim anim composer this mask is assigned to
66+
* @param skin skinning control complimenting the anim composer.
67+
*/
68+
public SingleLayerInfluenceMask(String layer, AnimComposer anim, SkinningControl skin) {
69+
super();
70+
this.layer = layer;
71+
this.anim = anim;
72+
this.skin = skin;
73+
}
74+
75+
/**
76+
* Makes a layer from this mask.
77+
*/
78+
public void makeLayer() {
79+
anim.makeLayer(layer, this);
80+
}
81+
82+
/**
83+
* Adds all joints to this mask.
84+
* @return this.instance
85+
*/
86+
public SingleLayerInfluenceMask addAll() {
87+
for (Joint j : skin.getArmature().getJointList()) {
88+
super.addBones(skin.getArmature(), j.getName());
89+
}
90+
return this;
91+
}
92+
93+
/**
94+
* Adds the given joint and all its children to this mask.
95+
* @param joint
96+
* @return this instance
97+
*/
98+
public SingleLayerInfluenceMask addFromJoint(String joint) {
99+
super.addFromJoint(skin.getArmature(), joint);
100+
return this;
101+
}
102+
103+
/**
104+
* Adds the given joints to this mask.
105+
* @param joints
106+
* @return this instance
107+
*/
108+
public SingleLayerInfluenceMask addJoints(String... joints) {
109+
super.addBones(skin.getArmature(), joints);
110+
return this;
111+
}
112+
113+
/**
114+
* Makes this mask check if each joint is being used by a higher layer
115+
* before it uses them.
116+
* <p>Not checking is more efficient, but checking can avoid some
117+
* interpolation issues between layers. Default=true
118+
* @param check
119+
* @return this instance
120+
*/
121+
public SingleLayerInfluenceMask setCheckUpperLayers(boolean check) {
122+
checkUpperLayers = check;
123+
return this;
124+
}
125+
126+
/**
127+
* Get the layer this mask is targeted for.
128+
* <p>It is extremely important that this value match the actual layer
129+
* this is included in, because checking upper layers may not work if
130+
* they are different.
131+
* @return target layer
132+
*/
133+
public String getTargetLayer() {
134+
return layer;
135+
}
136+
137+
/**
138+
* Get the {@link AnimComposer} this mask is for.
139+
* @return anim composer
140+
*/
141+
public AnimComposer getAnimComposer() {
142+
return anim;
143+
}
144+
145+
/**
146+
* Get the {@link SkinningControl} this mask is for.
147+
* @return skinning control
148+
*/
149+
public SkinningControl getSkinningControl() {
150+
return skin;
151+
}
152+
153+
/**
154+
* Returns true if this mask is checking upper layers for joint use.
155+
* @return
156+
*/
157+
public boolean isCheckUpperLayers() {
158+
return checkUpperLayers;
159+
}
160+
161+
@Override
162+
public boolean contains(Object target) {
163+
return simpleContains(target) && (!checkUpperLayers || !isAffectedByUpperLayers(target));
164+
}
165+
166+
private boolean simpleContains(Object target) {
167+
return super.contains(target);
168+
}
169+
170+
private boolean isAffectedByUpperLayers(Object target) {
171+
boolean higher = false;
172+
for (String name : anim.getLayerNames()) {
173+
if (name.equals(layer)) {
174+
higher = true;
175+
continue;
176+
}
177+
if (!higher) {
178+
continue;
179+
}
180+
AnimLayer lyr = anim.getLayer(name);
181+
// if there is no action playing, no joints are used, so we can skip
182+
if (lyr.getCurrentAction() == null) continue;
183+
if (lyr.getMask() instanceof SingleLayerInfluenceMask) {
184+
// dodge some needless recursion by calling a simpler method
185+
if (((SingleLayerInfluenceMask)lyr.getMask()).simpleContains(target)) {
186+
return true;
187+
}
188+
}
189+
else if (lyr.getMask().contains(target)) {
190+
return true;
191+
}
192+
}
193+
return false;
194+
}
195+
196+
/**
197+
* Creates an {@code SingleLayerInfluenceMask} for all joints.
198+
* @param layer layer the returned mask is, or will be, be assigned to
199+
* @param spatial spatial containing anim composer and skinning control
200+
* @return new mask
201+
*/
202+
public static SingleLayerInfluenceMask all(String layer, Spatial spatial) {
203+
return new SingleLayerInfluenceMask(layer, spatial).addAll();
204+
}
205+
206+
/**
207+
* Creates an {@code SingleLayerInfluenceMask} for all joints.
208+
* @param layer layer the returned mask is, or will be, assigned to
209+
* @param anim anim composer
210+
* @param skin skinning control
211+
* @return new mask
212+
*/
213+
public static SingleLayerInfluenceMask all(String layer, AnimComposer anim, SkinningControl skin) {
214+
return new SingleLayerInfluenceMask(layer, anim, skin).addAll();
215+
}
216+
217+
}
218+

0 commit comments

Comments
 (0)