Merge master into branch
This commit is contained in:
commit
54ef6ec280
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@
|
||||
**/.classpath
|
||||
**/.settings
|
||||
**/.project
|
||||
**/out
|
||||
**/out/
|
||||
/.gradle/
|
||||
/.nb-gradle/
|
||||
/.idea/
|
||||
|
@ -6,6 +6,7 @@ branches:
|
||||
only:
|
||||
- master
|
||||
- v3.1
|
||||
- /^v3.2.0-.*$/
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
@ -8,7 +8,7 @@ jmeVersionTag = SNAPSHOT
|
||||
jmeVersionTagID = 0
|
||||
|
||||
# specify if JavaDoc should be built
|
||||
buildJavaDoc = false
|
||||
buildJavaDoc = true
|
||||
|
||||
# specify if SDK and Native libraries get built
|
||||
buildNativeProjects = false
|
||||
|
@ -67,7 +67,7 @@ public class BulletAppState implements AppState, PhysicsTickListener {
|
||||
|
||||
/**
|
||||
* Creates a new BulletAppState running a PhysicsSpace for physics
|
||||
* simulation, use getStateManager().addState(bulletAppState) to enable
|
||||
* simulation, use getStateManager().attach(bulletAppState) to enable
|
||||
* physics for an Application.
|
||||
*/
|
||||
public BulletAppState() {
|
||||
@ -75,7 +75,7 @@ public class BulletAppState implements AppState, PhysicsTickListener {
|
||||
|
||||
/**
|
||||
* Creates a new BulletAppState running a PhysicsSpace for physics
|
||||
* simulation, use getStateManager().addState(bulletAppState) to enable
|
||||
* simulation, use getStateManager().attach(bulletAppState) to enable
|
||||
* physics for an Application.
|
||||
*
|
||||
* @param broadphaseType The type of broadphase collision detection,
|
||||
@ -87,7 +87,7 @@ public class BulletAppState implements AppState, PhysicsTickListener {
|
||||
|
||||
/**
|
||||
* Creates a new BulletAppState running a PhysicsSpace for physics
|
||||
* simulation, use getStateManager().addState(bulletAppState) to enable
|
||||
* simulation, use getStateManager().attach(bulletAppState) to enable
|
||||
* physics for an Application. An AxisSweep broadphase is used.
|
||||
*
|
||||
* @param worldMin The minimum world extent
|
||||
|
@ -31,36 +31,23 @@
|
||||
*/
|
||||
package com.jme3.bullet.control;
|
||||
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.animation.SkeletonControl;
|
||||
import com.jme3.animation.*;
|
||||
import com.jme3.bullet.PhysicsSpace;
|
||||
import com.jme3.bullet.collision.PhysicsCollisionEvent;
|
||||
import com.jme3.bullet.collision.PhysicsCollisionListener;
|
||||
import com.jme3.bullet.collision.PhysicsCollisionObject;
|
||||
import com.jme3.bullet.collision.RagdollCollisionListener;
|
||||
import com.jme3.bullet.collision.*;
|
||||
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
|
||||
import com.jme3.bullet.collision.shapes.HullCollisionShape;
|
||||
import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset;
|
||||
import com.jme3.bullet.control.ragdoll.RagdollPreset;
|
||||
import com.jme3.bullet.control.ragdoll.RagdollUtils;
|
||||
import com.jme3.bullet.control.ragdoll.*;
|
||||
import com.jme3.bullet.joints.SixDofJoint;
|
||||
import com.jme3.bullet.objects.PhysicsRigidBody;
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.util.TempVars;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
@ -91,7 +78,10 @@ import java.util.logging.Logger;
|
||||
* </ul> </p>
|
||||
*
|
||||
* @author Normen Hansen and Rémy Bouquet (Nehon)
|
||||
*
|
||||
* TODO this needs to be redone with the new animation system
|
||||
*/
|
||||
@Deprecated
|
||||
public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());
|
||||
|
99
jme3-core/src/main/java/com/jme3/anim/AnimClip.java
Normal file
99
jme3-core/src/main/java/com/jme3/anim/AnimClip.java
Normal file
@ -0,0 +1,99 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.anim.tween.Tween;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 20/12/2017.
|
||||
*/
|
||||
public class AnimClip implements JmeCloneable, Savable {
|
||||
|
||||
private String name;
|
||||
private double length;
|
||||
|
||||
private AnimTrack[] tracks;
|
||||
|
||||
public AnimClip() {
|
||||
}
|
||||
|
||||
public AnimClip(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setTracks(AnimTrack[] tracks) {
|
||||
this.tracks = tracks;
|
||||
for (AnimTrack track : tracks) {
|
||||
if (track.getLength() > length) {
|
||||
length = track.getLength();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
public AnimTrack[] getTracks() {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException("Error cloning", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
AnimTrack[] newTracks = new AnimTrack[tracks.length];
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
newTracks[i] = (cloner.clone(tracks[i]));
|
||||
}
|
||||
this.tracks = newTracks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Clip " + name + ", " + length + 's';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule oc = ex.getCapsule(this);
|
||||
oc.write(name, "name", null);
|
||||
oc.write(tracks, "tracks", null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule ic = im.getCapsule(this);
|
||||
name = ic.readString("name", null);
|
||||
Savable[] arr = ic.readSavableArray("tracks", null);
|
||||
if (arr != null) {
|
||||
tracks = new AnimTrack[arr.length];
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
AnimTrack t = (AnimTrack) arr[i];
|
||||
tracks[i] = t;
|
||||
if (t.getLength() > length) {
|
||||
length = t.getLength();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
250
jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
Normal file
250
jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
Normal file
@ -0,0 +1,250 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.anim.tween.Tween;
|
||||
import com.jme3.anim.tween.Tweens;
|
||||
import com.jme3.anim.tween.action.*;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 20/12/2017.
|
||||
*/
|
||||
public class AnimComposer extends AbstractControl {
|
||||
|
||||
public static final String DEFAULT_LAYER = "Default";
|
||||
private Map<String, AnimClip> animClipMap = new HashMap<>();
|
||||
|
||||
private Map<String, Action> actions = new HashMap<>();
|
||||
private float globalSpeed = 1f;
|
||||
private Map<String, Layer> layers = new LinkedHashMap<>();
|
||||
|
||||
public AnimComposer() {
|
||||
layers.put(DEFAULT_LAYER, new Layer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an animation from the list of animations.
|
||||
*
|
||||
* @param name The name of the animation to retrieve.
|
||||
* @return The animation corresponding to the given name, or null, if no
|
||||
* such named animation exists.
|
||||
*/
|
||||
public AnimClip getAnimClip(String name) {
|
||||
return animClipMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an animation to be available for playing to this
|
||||
* <code>AnimControl</code>.
|
||||
*
|
||||
* @param anim The animation to add.
|
||||
*/
|
||||
public void addAnimClip(AnimClip anim) {
|
||||
animClipMap.put(anim.getName(), anim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an animation so that it is no longer available for playing.
|
||||
*
|
||||
* @param anim The animation to remove.
|
||||
*/
|
||||
public void removeAnimClip(AnimClip anim) {
|
||||
if (!animClipMap.containsKey(anim.getName())) {
|
||||
throw new IllegalArgumentException("Given animation does not exist "
|
||||
+ "in this AnimControl");
|
||||
}
|
||||
|
||||
animClipMap.remove(anim.getName());
|
||||
}
|
||||
|
||||
public Action setCurrentAction(String name) {
|
||||
return setCurrentAction(name, DEFAULT_LAYER);
|
||||
}
|
||||
|
||||
public Action setCurrentAction(String actionName, String layerName) {
|
||||
Layer l = layers.get(layerName);
|
||||
if (l == null) {
|
||||
throw new IllegalArgumentException("Unknown layer " + layerName);
|
||||
}
|
||||
Action currentAction = action(actionName);
|
||||
l.time = 0;
|
||||
l.currentAction = currentAction;
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
public Action action(String name) {
|
||||
Action action = actions.get(name);
|
||||
if (action == null) {
|
||||
action = makeAction(name);
|
||||
actions.put(name, action);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
public Action makeAction(String name) {
|
||||
Action action;
|
||||
AnimClip clip = animClipMap.get(name);
|
||||
if (clip == null) {
|
||||
throw new IllegalArgumentException("Cannot find clip named " + name);
|
||||
}
|
||||
action = new ClipAction(clip);
|
||||
return action;
|
||||
}
|
||||
|
||||
public void makeLayer(String name, AnimationMask mask){
|
||||
Layer l = new Layer();
|
||||
l.mask = mask;
|
||||
layers.put(name, l);
|
||||
}
|
||||
|
||||
|
||||
public BaseAction actionSequence(String name, Tween... tweens) {
|
||||
BaseAction action = new BaseAction(Tweens.sequence(tweens));
|
||||
actions.put(name, action);
|
||||
return action;
|
||||
}
|
||||
|
||||
public BlendAction actionBlended(String name, BlendSpace blendSpace, String... clips) {
|
||||
BlendableAction[] acts = new BlendableAction[clips.length];
|
||||
for (int i = 0; i < acts.length; i++) {
|
||||
BlendableAction ba = (BlendableAction) makeAction(clips[i]);
|
||||
acts[i] = ba;
|
||||
}
|
||||
BlendAction action = new BlendAction(blendSpace, acts);
|
||||
actions.put(name, action);
|
||||
return action;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
for (Layer layer : layers.values()) {
|
||||
layer.currentAction = null;
|
||||
layer.time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<AnimClip> getAnimClips() {
|
||||
return Collections.unmodifiableCollection(animClipMap.values());
|
||||
}
|
||||
|
||||
public Collection<String> getAnimClipsNames() {
|
||||
return Collections.unmodifiableCollection(animClipMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
for (Layer layer : layers.values()) {
|
||||
Action currentAction = layer.currentAction;
|
||||
if (currentAction == null) {
|
||||
continue;
|
||||
}
|
||||
layer.advance(tpf);
|
||||
|
||||
currentAction.setMask(layer.mask);
|
||||
boolean running = currentAction.interpolate(layer.time);
|
||||
currentAction.setMask(null);
|
||||
|
||||
if (!running) {
|
||||
layer.time = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
|
||||
}
|
||||
|
||||
public float getGlobalSpeed() {
|
||||
return globalSpeed;
|
||||
}
|
||||
|
||||
public void setGlobalSpeed(float globalSpeed) {
|
||||
this.globalSpeed = globalSpeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
try {
|
||||
AnimComposer clone = (AnimComposer) super.clone();
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
super.cloneFields(cloner, original);
|
||||
Map<String, AnimClip> clips = new HashMap<>();
|
||||
for (String key : animClipMap.keySet()) {
|
||||
clips.put(key, cloner.clone(animClipMap.get(key)));
|
||||
}
|
||||
Map<String, Action> act = new HashMap<>();
|
||||
for (String key : actions.keySet()) {
|
||||
act.put(key, cloner.clone(actions.get(key)));
|
||||
}
|
||||
actions = act;
|
||||
animClipMap = clips;
|
||||
|
||||
Map<String, Layer> newLayers = new LinkedHashMap<>();
|
||||
for (String key : layers.keySet()) {
|
||||
newLayers.put(key, cloner.clone(layers.get(key)));
|
||||
}
|
||||
|
||||
layers = newLayers;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
super.read(im);
|
||||
InputCapsule ic = im.getCapsule(this);
|
||||
animClipMap = (Map<String, AnimClip>) ic.readStringSavableMap("animClipMap", new HashMap<String, AnimClip>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
super.write(ex);
|
||||
OutputCapsule oc = ex.getCapsule(this);
|
||||
oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
|
||||
}
|
||||
|
||||
private class Layer implements JmeCloneable {
|
||||
private Action currentAction;
|
||||
private AnimationMask mask;
|
||||
private float weight;
|
||||
private double time;
|
||||
|
||||
public void advance(float tpf) {
|
||||
time += tpf * currentAction.getSpeed() * globalSpeed;
|
||||
// make sure negative time is in [0, length] range
|
||||
if (time < 0) {
|
||||
double length = currentAction.getLength();
|
||||
time = (time % length + length) % length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
try {
|
||||
Layer clone = (Layer) super.clone();
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
currentAction = null;
|
||||
}
|
||||
}
|
||||
}
|
12
jme3-core/src/main/java/com/jme3/anim/AnimTrack.java
Normal file
12
jme3-core/src/main/java/com/jme3/anim/AnimTrack.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
public interface AnimTrack<T> extends Savable, JmeCloneable {
|
||||
|
||||
public void getDataAtTime(double time, T store);
|
||||
public double getLength();
|
||||
|
||||
|
||||
}
|
12
jme3-core/src/main/java/com/jme3/anim/AnimationMask.java
Normal file
12
jme3-core/src/main/java/com/jme3/anim/AnimationMask.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
/**
|
||||
* Created by Nehon
|
||||
* An AnimationMask is defining a subset of elements on which an animation will be applied.
|
||||
* Most used implementation is the ArmatureMask that defines a subset of joints in an Armature.
|
||||
*/
|
||||
public interface AnimationMask {
|
||||
|
||||
boolean contains(Object target);
|
||||
|
||||
}
|
300
jme3-core/src/main/java/com/jme3/anim/Armature.java
Normal file
300
jme3-core/src/main/java/com/jme3/anim/Armature.java
Normal file
@ -0,0 +1,300 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.anim.util.JointModelTransform;
|
||||
import com.jme3.asset.AssetLoadException;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 15/12/2017.
|
||||
*/
|
||||
public class Armature implements JmeCloneable, Savable {
|
||||
|
||||
private Joint[] rootJoints;
|
||||
private Joint[] jointList;
|
||||
|
||||
/**
|
||||
* Contains the skinning matrices, multiplying it by a vertex effected by a bone
|
||||
* will cause it to go to the animated position.
|
||||
*/
|
||||
private transient Matrix4f[] skinningMatrixes;
|
||||
private Class<? extends JointModelTransform> modelTransformClass = SeparateJointModelTransform.class;
|
||||
|
||||
/**
|
||||
* Serialization only
|
||||
*/
|
||||
public Armature() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an armature from a joint list.
|
||||
* The root joints are found automatically.
|
||||
* <p>
|
||||
* Note that using this constructor will cause the joints in the list
|
||||
* to have their bind pose recomputed based on their local transforms.
|
||||
*
|
||||
* @param jointList The list of joints to manage by this Armature
|
||||
*/
|
||||
public Armature(Joint[] jointList) {
|
||||
this.jointList = jointList;
|
||||
|
||||
List<Joint> rootJointList = new ArrayList<>();
|
||||
for (int i = jointList.length - 1; i >= 0; i--) {
|
||||
Joint joint = jointList[i];
|
||||
joint.setId(i);
|
||||
instanciateJointModelTransform(joint);
|
||||
if (joint.getParent() == null) {
|
||||
rootJointList.add(joint);
|
||||
}
|
||||
}
|
||||
rootJoints = rootJointList.toArray(new Joint[rootJointList.size()]);
|
||||
|
||||
createSkinningMatrices();
|
||||
|
||||
for (int i = rootJoints.length - 1; i >= 0; i--) {
|
||||
Joint rootJoint = rootJoints[i];
|
||||
rootJoint.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all joints sin this Amature.
|
||||
*/
|
||||
public void update() {
|
||||
for (Joint rootJoint : rootJoints) {
|
||||
rootJoint.update();
|
||||
}
|
||||
}
|
||||
|
||||
private void createSkinningMatrices() {
|
||||
skinningMatrixes = new Matrix4f[jointList.length];
|
||||
for (int i = 0; i < skinningMatrixes.length; i++) {
|
||||
skinningMatrixes[i] = new Matrix4f();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JointModelTransform implementation
|
||||
* Default is {@link MatrixJointModelTransform}
|
||||
*
|
||||
* @param modelTransformClass
|
||||
* @see {@link JointModelTransform},{@link MatrixJointModelTransform},{@link SeparateJointModelTransform},
|
||||
*/
|
||||
public void setModelTransformClass(Class<? extends JointModelTransform> modelTransformClass) {
|
||||
this.modelTransformClass = modelTransformClass;
|
||||
if (jointList == null) {
|
||||
return;
|
||||
}
|
||||
for (Joint joint : jointList) {
|
||||
instanciateJointModelTransform(joint);
|
||||
}
|
||||
}
|
||||
|
||||
private void instanciateJointModelTransform(Joint joint) {
|
||||
try {
|
||||
joint.setJointModelTransform(modelTransformClass.newInstance());
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the array of all root joints of this Armature
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Joint[] getRoots() {
|
||||
return rootJoints;
|
||||
}
|
||||
|
||||
public List<Joint> getJointList() {
|
||||
return Arrays.asList(jointList);
|
||||
}
|
||||
|
||||
/**
|
||||
* return a joint for the given index
|
||||
*
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
public Joint getJoint(int index) {
|
||||
return jointList[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the joint with the given name
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public Joint getJoint(String name) {
|
||||
for (int i = 0; i < jointList.length; i++) {
|
||||
if (jointList[i].getName().equals(name)) {
|
||||
return jointList[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the bone index of the given bone
|
||||
*
|
||||
* @param joint
|
||||
* @return
|
||||
*/
|
||||
public int getJointIndex(Joint joint) {
|
||||
for (int i = 0; i < jointList.length; i++) {
|
||||
if (jointList[i] == joint) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the joint index of the joint that has the given name
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public int getJointIndex(String name) {
|
||||
for (int i = 0; i < jointList.length; i++) {
|
||||
if (jointList[i].getName().equals(name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current Armature state as its bind pose.
|
||||
* Note that the bind pose is supposed to be the one where the armature is aligned with the mesh to deform.
|
||||
* Saving this pose will affect how skinning works.
|
||||
*/
|
||||
public void saveBindPose() {
|
||||
//make sure all bones are updated
|
||||
update();
|
||||
//Save the current pose as bind pose
|
||||
for (Joint joint : jointList) {
|
||||
joint.saveBindPose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods sets this armature in its bind pose (aligned with the mesh to deform)
|
||||
* Note that this is only useful for debugging purpose.
|
||||
*/
|
||||
public void applyBindPose() {
|
||||
for (Joint joint : rootJoints) {
|
||||
joint.applyBindPose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current local transform as the initial transform.
|
||||
* Initial transform is the one applied to the armature when loaded.
|
||||
*/
|
||||
public void saveInitialPose() {
|
||||
for (Joint joint : jointList) {
|
||||
joint.saveInitialPose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the initial pose to this armature
|
||||
*/
|
||||
public void applyInitialPose() {
|
||||
for (Joint rootJoint : rootJoints) {
|
||||
rootJoint.applyInitialPose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the skinning matrices for each bone of the armature that would be used to transform vertices of associated meshes
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Matrix4f[] computeSkinningMatrices() {
|
||||
for (int i = 0; i < jointList.length; i++) {
|
||||
jointList[i].getOffsetTransform(skinningMatrixes[i]);
|
||||
}
|
||||
return skinningMatrixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the number of joints of this armature
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getJointCount() {
|
||||
return jointList.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
try {
|
||||
Armature clone = (Armature) super.clone();
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
this.rootJoints = cloner.clone(rootJoints);
|
||||
this.jointList = cloner.clone(jointList);
|
||||
this.skinningMatrixes = cloner.clone(skinningMatrixes);
|
||||
for (Joint joint : jointList) {
|
||||
instanciateJointModelTransform(joint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule input = im.getCapsule(this);
|
||||
|
||||
Savable[] jointRootsAsSavable = input.readSavableArray("rootJoints", null);
|
||||
rootJoints = new Joint[jointRootsAsSavable.length];
|
||||
System.arraycopy(jointRootsAsSavable, 0, rootJoints, 0, jointRootsAsSavable.length);
|
||||
|
||||
Savable[] jointListAsSavable = input.readSavableArray("jointList", null);
|
||||
jointList = new Joint[jointListAsSavable.length];
|
||||
System.arraycopy(jointListAsSavable, 0, jointList, 0, jointListAsSavable.length);
|
||||
|
||||
String className = input.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName());
|
||||
try {
|
||||
modelTransformClass = (Class<? extends JointModelTransform>) Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AssetLoadException("Cannnot find class for name " + className);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (Joint joint : jointList) {
|
||||
joint.setId(i++);
|
||||
instanciateJointModelTransform(joint);
|
||||
}
|
||||
createSkinningMatrices();
|
||||
|
||||
for (Joint rootJoint : rootJoints) {
|
||||
rootJoint.update();
|
||||
}
|
||||
applyInitialPose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule output = ex.getCapsule(this);
|
||||
output.write(rootJoints, "rootJoints", null);
|
||||
output.write(jointList, "jointList", null);
|
||||
output.write(modelTransformClass.getCanonicalName(), "modelTransformClass", MatrixJointModelTransform.class.getCanonicalName());
|
||||
}
|
||||
}
|
62
jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java
Normal file
62
jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java
Normal file
@ -0,0 +1,62 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public class ArmatureMask implements AnimationMask {
|
||||
|
||||
private BitSet affectedJoints = new BitSet();
|
||||
|
||||
@Override
|
||||
public boolean contains(Object target) {
|
||||
return affectedJoints.get(((Joint) target).getId());
|
||||
}
|
||||
|
||||
public static ArmatureMask createMask(Armature armature, String fromJoint) {
|
||||
ArmatureMask mask = new ArmatureMask();
|
||||
mask.addFromJoint(armature, fromJoint);
|
||||
return mask;
|
||||
}
|
||||
|
||||
public static ArmatureMask createMask(Armature armature, String... joints) {
|
||||
ArmatureMask mask = new ArmatureMask();
|
||||
mask.addBones(armature, joints);
|
||||
for (String joint : joints) {
|
||||
mask.affectedJoints.set(armature.getJoint(joint).getId());
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add joints to be influenced by this animation mask.
|
||||
*/
|
||||
public void addBones(Armature armature, String... jointNames) {
|
||||
for (String jointName : jointNames) {
|
||||
Joint joint = findJoint(armature, jointName);
|
||||
affectedJoints.set(joint.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private Joint findJoint(Armature armature, String jointName) {
|
||||
Joint joint = armature.getJoint(jointName);
|
||||
if (joint == null) {
|
||||
throw new IllegalArgumentException("Cannot find joint " + jointName);
|
||||
}
|
||||
return joint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a joint and all its sub armature joints to be influenced by this animation mask.
|
||||
*/
|
||||
public void addFromJoint(Armature armature, String jointName) {
|
||||
Joint joint = findJoint(armature, jointName);
|
||||
recurseAddJoint(joint);
|
||||
}
|
||||
|
||||
private void recurseAddJoint(Joint joint) {
|
||||
affectedJoints.set(joint.getId());
|
||||
for (Joint j : joint.getChildren()) {
|
||||
recurseAddJoint(j);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
342
jme3-core/src/main/java/com/jme3/anim/Joint.java
Normal file
342
jme3-core/src/main/java/com/jme3/anim/Joint.java
Normal file
@ -0,0 +1,342 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.anim.util.HasLocalTransform;
|
||||
import com.jme3.anim.util.JointModelTransform;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.material.MatParamOverride;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.scene.*;
|
||||
import com.jme3.shader.VarType;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Joint is the basic component of an armature designed to perform skeletal animation
|
||||
* Created by Nehon on 15/12/2017.
|
||||
*/
|
||||
public class Joint implements Savable, JmeCloneable, HasLocalTransform {
|
||||
|
||||
private String name;
|
||||
private int id;
|
||||
private Joint parent;
|
||||
private SafeArrayList<Joint> children = new SafeArrayList<>(Joint.class);
|
||||
private Geometry targetGeometry;
|
||||
|
||||
/**
|
||||
* The attachment node.
|
||||
*/
|
||||
private Node attachedNode;
|
||||
|
||||
/**
|
||||
* The transform of the joint in local space. Relative to its parent.
|
||||
* Or relative to the model's origin for the root joint.
|
||||
*/
|
||||
private Transform localTransform = new Transform();
|
||||
|
||||
/**
|
||||
* The initial transform of the joint in local space. Relative to its parent.
|
||||
* Or relative to the model's origin for the root joint.
|
||||
* this transform is the transform applied when the armature is loaded.
|
||||
*/
|
||||
private Transform initialTransform = new Transform();
|
||||
|
||||
/**
|
||||
* The transform of the joint in model space. Relative to the origin of the model.
|
||||
* this is either a MatrixJointModelTransform or a SeparateJointModelTransform
|
||||
*/
|
||||
private JointModelTransform jointModelTransform;
|
||||
|
||||
/**
|
||||
* The matrix used to transform affected vertices position into the joint model space.
|
||||
* Used for skinning.
|
||||
*/
|
||||
private Matrix4f inverseModelBindMatrix = new Matrix4f();
|
||||
|
||||
|
||||
public Joint() {
|
||||
}
|
||||
|
||||
public Joint(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates world transforms for this bone and it's children.
|
||||
*/
|
||||
public final void update() {
|
||||
this.updateModelTransforms();
|
||||
|
||||
for (Joint child : children.getArray()) {
|
||||
child.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the model transforms for this bone, and, possibly the attach node
|
||||
* if not null.
|
||||
* <p>
|
||||
* The model transform of this bone is computed by combining the parent's
|
||||
* model transform with this bones' local transform.
|
||||
*/
|
||||
public final void updateModelTransforms() {
|
||||
jointModelTransform.updateModelTransform(localTransform, parent);
|
||||
updateAttachNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the local transform of the attachments node.
|
||||
*/
|
||||
private void updateAttachNode() {
|
||||
if (attachedNode == null) {
|
||||
return;
|
||||
}
|
||||
Node attachParent = attachedNode.getParent();
|
||||
if (attachParent == null || targetGeometry == null
|
||||
|| targetGeometry.getParent() == attachParent
|
||||
&& targetGeometry.getLocalTransform().isIdentity()) {
|
||||
/*
|
||||
* The animated meshes are in the same coordinate system as the
|
||||
* attachments node: no further transforms are needed.
|
||||
*/
|
||||
attachedNode.setLocalTransform(getModelTransform());
|
||||
|
||||
} else {
|
||||
Spatial loopSpatial = targetGeometry;
|
||||
Transform combined = getModelTransform().clone();
|
||||
/*
|
||||
* Climb the scene graph applying local transforms until the
|
||||
* attachments node's parent is reached.
|
||||
*/
|
||||
while (loopSpatial != attachParent && loopSpatial != null) {
|
||||
Transform localTransform = loopSpatial.getLocalTransform();
|
||||
combined.combineWithParent(localTransform);
|
||||
loopSpatial = loopSpatial.getParent();
|
||||
}
|
||||
attachedNode.setLocalTransform(combined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the skinning transform in the specified Matrix4f.
|
||||
* The skinning transform applies the animation of the bone to a vertex.
|
||||
* <p>
|
||||
* This assumes that the world transforms for the entire bone hierarchy
|
||||
* have already been computed, otherwise this method will return undefined
|
||||
* results.
|
||||
*
|
||||
* @param outTransform
|
||||
*/
|
||||
void getOffsetTransform(Matrix4f outTransform) {
|
||||
jointModelTransform.getOffsetTransform(outTransform, inverseModelBindMatrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current localTransform as the Bind transform.
|
||||
*/
|
||||
protected void saveBindPose() {
|
||||
//Note that the whole Armature must be updated before calling this method.
|
||||
getModelTransform().toTransformMatrix(inverseModelBindMatrix);
|
||||
inverseModelBindMatrix.invertLocal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current local transforms as the initial transform.
|
||||
*/
|
||||
protected void saveInitialPose() {
|
||||
initialTransform.set(localTransform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the local transform with the bind transforms
|
||||
*/
|
||||
protected void applyBindPose() {
|
||||
jointModelTransform.applyBindPose(localTransform, inverseModelBindMatrix, parent);
|
||||
updateModelTransforms();
|
||||
|
||||
for (Joint child : children.getArray()) {
|
||||
child.applyBindPose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the local transform with the initial transform
|
||||
*/
|
||||
protected void applyInitialPose() {
|
||||
setLocalTransform(initialTransform);
|
||||
updateModelTransforms();
|
||||
|
||||
for (Joint child : children.getArray()) {
|
||||
child.applyInitialPose();
|
||||
}
|
||||
}
|
||||
|
||||
protected JointModelTransform getJointModelTransform() {
|
||||
return jointModelTransform;
|
||||
}
|
||||
|
||||
protected void setJointModelTransform(JointModelTransform jointModelTransform) {
|
||||
this.jointModelTransform = jointModelTransform;
|
||||
}
|
||||
|
||||
public Vector3f getLocalTranslation() {
|
||||
return localTransform.getTranslation();
|
||||
}
|
||||
|
||||
public Quaternion getLocalRotation() {
|
||||
return localTransform.getRotation();
|
||||
}
|
||||
|
||||
public Vector3f getLocalScale() {
|
||||
return localTransform.getScale();
|
||||
}
|
||||
|
||||
public void setLocalTranslation(Vector3f translation) {
|
||||
localTransform.setTranslation(translation);
|
||||
}
|
||||
|
||||
public void setLocalRotation(Quaternion rotation) {
|
||||
localTransform.setRotation(rotation);
|
||||
}
|
||||
|
||||
public void setLocalScale(Vector3f scale) {
|
||||
localTransform.setScale(scale);
|
||||
}
|
||||
|
||||
public void addChild(Joint child) {
|
||||
children.add(child);
|
||||
child.parent = this;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setLocalTransform(Transform localTransform) {
|
||||
this.localTransform.set(localTransform);
|
||||
}
|
||||
|
||||
public void setInverseModelBindMatrix(Matrix4f inverseModelBindMatrix) {
|
||||
this.inverseModelBindMatrix = inverseModelBindMatrix;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Joint getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public List<Joint> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the attachments node of this joint. If this joint doesn't already
|
||||
* have an attachments node, create one. Models and effects attached to the
|
||||
* attachments node will follow this bone's motions.
|
||||
*
|
||||
* @param jointIndex this bone's index in its armature (≥0)
|
||||
* @param targets a list of geometries animated by this bone's skeleton (not
|
||||
* null, unaffected)
|
||||
*/
|
||||
Node getAttachmentsNode(int jointIndex, SafeArrayList<Geometry> targets) {
|
||||
targetGeometry = null;
|
||||
/*
|
||||
* Search for a geometry animated by this particular bone.
|
||||
*/
|
||||
for (Geometry geometry : targets) {
|
||||
Mesh mesh = geometry.getMesh();
|
||||
if (mesh != null && mesh.isAnimatedByJoint(jointIndex)) {
|
||||
targetGeometry = geometry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (attachedNode == null) {
|
||||
attachedNode = new Node(name + "_attachnode");
|
||||
attachedNode.setUserData("AttachedBone", this);
|
||||
//We don't want the node to have a numBone set by a parent node so we force it to null
|
||||
attachedNode.addMatParamOverride(new MatParamOverride(VarType.Int, "NumberOfBones", null));
|
||||
}
|
||||
|
||||
return attachedNode;
|
||||
}
|
||||
|
||||
|
||||
public Transform getLocalTransform() {
|
||||
return localTransform;
|
||||
}
|
||||
|
||||
public Transform getModelTransform() {
|
||||
return jointModelTransform.getModelTransform();
|
||||
}
|
||||
|
||||
public Matrix4f getInverseModelBindMatrix() {
|
||||
return inverseModelBindMatrix;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
try {
|
||||
Joint clone = (Joint) super.clone();
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
this.children = cloner.clone(children);
|
||||
this.parent = cloner.clone(parent);
|
||||
this.attachedNode = cloner.clone(attachedNode);
|
||||
this.targetGeometry = cloner.clone(targetGeometry);
|
||||
this.localTransform = cloner.clone(localTransform);
|
||||
this.inverseModelBindMatrix = cloner.clone(inverseModelBindMatrix);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule input = im.getCapsule(this);
|
||||
|
||||
name = input.readString("name", null);
|
||||
attachedNode = (Node) input.readSavable("attachedNode", null);
|
||||
targetGeometry = (Geometry) input.readSavable("targetGeometry", null);
|
||||
initialTransform = (Transform) input.readSavable("initialTransform", new Transform());
|
||||
inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix);
|
||||
|
||||
ArrayList<Joint> childList = input.readSavableArrayList("children", null);
|
||||
for (int i = childList.size() - 1; i >= 0; i--) {
|
||||
this.addChild(childList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule output = ex.getCapsule(this);
|
||||
|
||||
output.write(name, "name", null);
|
||||
output.write(attachedNode, "attachedNode", null);
|
||||
output.write(targetGeometry, "targetGeometry", null);
|
||||
output.write(initialTransform, "initialTransform", new Transform());
|
||||
output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f());
|
||||
output.writeSavableArrayList(new ArrayList(children), "children", null);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.anim.util.JointModelTransform;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
/**
|
||||
* This JointModelTransform implementation accumulate joints transforms in a Matrix4f to properly
|
||||
* support non uniform scaling in an armature hierarchy
|
||||
*/
|
||||
public class MatrixJointModelTransform implements JointModelTransform {
|
||||
|
||||
private Matrix4f modelTransformMatrix = new Matrix4f();
|
||||
private Transform modelTransform = new Transform();
|
||||
|
||||
@Override
|
||||
public void updateModelTransform(Transform localTransform, Joint parent) {
|
||||
localTransform.toTransformMatrix(modelTransformMatrix);
|
||||
if (parent != null) {
|
||||
((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) {
|
||||
modelTransformMatrix.mult(inverseModelBindMatrix, outTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) {
|
||||
modelTransformMatrix.set(inverseModelBindMatrix).invertLocal(); // model transform = model bind
|
||||
if (parent != null) {
|
||||
((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().invert().mult(modelTransformMatrix, modelTransformMatrix);
|
||||
}
|
||||
localTransform.fromTransformMatrix(modelTransformMatrix);
|
||||
}
|
||||
|
||||
public Matrix4f getModelTransformMatrix() {
|
||||
return modelTransformMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform getModelTransform() {
|
||||
modelTransform.fromTransformMatrix(modelTransformMatrix);
|
||||
return modelTransform;
|
||||
}
|
||||
}
|
353
jme3-core/src/main/java/com/jme3/anim/MorphControl.java
Normal file
353
jme3-core/src/main/java/com/jme3/anim/MorphControl.java
Normal file
@ -0,0 +1,353 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.material.*;
|
||||
import com.jme3.renderer.*;
|
||||
import com.jme3.scene.*;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import com.jme3.scene.mesh.MorphTarget;
|
||||
import com.jme3.shader.VarType;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* A control that handle morph animation for Position, Normal and Tangent buffers.
|
||||
* All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers.
|
||||
* If you want to use other types of buffers you will need a custom MorphControl and a custom shader.
|
||||
*
|
||||
* @author Rémy Bouquet
|
||||
*/
|
||||
public class MorphControl extends AbstractControl implements Savable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MorphControl.class.getName());
|
||||
|
||||
private static final int MAX_MORPH_BUFFERS = 14;
|
||||
private final static float MIN_WEIGHT = 0.005f;
|
||||
|
||||
private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class);
|
||||
private TargetLocator targetLocator = new TargetLocator();
|
||||
|
||||
private boolean approximateTangents = true;
|
||||
private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null);
|
||||
|
||||
private float[] tmpPosArray;
|
||||
private float[] tmpNormArray;
|
||||
private float[] tmpTanArray;
|
||||
|
||||
private static final VertexBuffer.Type bufferTypes[] = VertexBuffer.Type.values();
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
// gathering geometries in the sub graph.
|
||||
// This must be done in the update phase as the gathering might add a matparam override
|
||||
targets.clear();
|
||||
this.spatial.depthFirstTraversal(targetLocator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
for (Geometry geom : targets) {
|
||||
Mesh mesh = geom.getMesh();
|
||||
if (!geom.isDirtyMorph()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Material m = geom.getMaterial();
|
||||
float weights[] = geom.getMorphState();
|
||||
MorphTarget morphTargets[] = mesh.getMorphTargets();
|
||||
float matWeights[];
|
||||
//Number of buffer to handle for each morph target
|
||||
int targetNumBuffers = getTargetNumBuffers(morphTargets[0]);
|
||||
|
||||
int maxGPUTargets = getMaxGPUTargets(rm, geom, m, targetNumBuffers);
|
||||
|
||||
MatParam param2 = m.getParam("MorphWeights");
|
||||
matWeights = (float[]) param2.getValue();
|
||||
|
||||
int nbGPUTargets = 0;
|
||||
int lastGpuTargetIndex = 0;
|
||||
int boundBufferIdx = 0;
|
||||
float cpuWeightSum = 0;
|
||||
// binding the morphTargets buffer to the mesh morph buffers
|
||||
for (int i = 0; i < morphTargets.length; i++) {
|
||||
// discard weights below the threshold
|
||||
if (weights[i] < MIN_WEIGHT) {
|
||||
continue;
|
||||
}
|
||||
if (nbGPUTargets >= maxGPUTargets) {
|
||||
// we already bound all the available gpu slots we need to merge the remaining morph targets.
|
||||
cpuWeightSum += weights[i];
|
||||
continue;
|
||||
}
|
||||
lastGpuTargetIndex = i;
|
||||
// binding the morph target's buffers to the mesh morph buffers.
|
||||
MorphTarget t = morphTargets[i];
|
||||
boundBufferIdx = bindMorphtargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t);
|
||||
// setting the weight in the mat param array
|
||||
matWeights[nbGPUTargets] = weights[i];
|
||||
nbGPUTargets++;
|
||||
}
|
||||
|
||||
if (nbGPUTargets < matWeights.length) {
|
||||
// if we have less simultaneous GPU targets than the length of the weight array, the array is padded with 0
|
||||
for (int i = nbGPUTargets; i < matWeights.length; i++) {
|
||||
matWeights[i] = 0;
|
||||
}
|
||||
} else if (cpuWeightSum > 0) {
|
||||
// we have more simultaneous morph targets than available gpu slots,
|
||||
// we merge the additional morph targets and bind them to the last gpu slot
|
||||
MorphTarget mt = geom.getFallbackMorphTarget();
|
||||
if (mt == null) {
|
||||
mt = initCpuMorphTarget(geom);
|
||||
geom.setFallbackMorphTarget(mt);
|
||||
}
|
||||
// adding the last Gpu target weight
|
||||
cpuWeightSum += matWeights[nbGPUTargets - 1];
|
||||
ensureTmpArraysCapacity(geom.getVertexCount() * 3, targetNumBuffers);
|
||||
|
||||
// merging all remaining targets in tmp arrays
|
||||
for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) {
|
||||
if (weights[i] < MIN_WEIGHT) {
|
||||
continue;
|
||||
}
|
||||
float weight = weights[i] / cpuWeightSum;
|
||||
MorphTarget t = geom.getMesh().getMorphTargets()[i];
|
||||
mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex);
|
||||
}
|
||||
|
||||
// writing the tmp arrays to the float buffer
|
||||
writeCpuBuffer(targetNumBuffers, mt);
|
||||
|
||||
// binding the merged morph target
|
||||
bindMorphtargetBuffer(mesh, targetNumBuffers, (nbGPUTargets - 1) * targetNumBuffers, mt);
|
||||
|
||||
// setting the eight of the merged targets
|
||||
matWeights[nbGPUTargets - 1] = cpuWeightSum;
|
||||
}
|
||||
geom.setDirtyMorph(false);
|
||||
}
|
||||
}
|
||||
|
||||
private int getMaxGPUTargets(RenderManager rm, Geometry geom, Material mat, int targetNumBuffers) {
|
||||
if (geom.getNbSimultaneousGPUMorph() > -1) {
|
||||
return geom.getNbSimultaneousGPUMorph();
|
||||
}
|
||||
|
||||
// Evaluate the number of CPU slots remaining for morph buffers.
|
||||
int nbMaxBuffers = getRemainingBuffers(geom.getMesh(), rm.getRenderer());
|
||||
|
||||
int realNumTargetsBuffers = geom.getMesh().getMorphTargets().length * targetNumBuffers;
|
||||
|
||||
// compute the max number of targets to send to the GPU
|
||||
int maxGPUTargets = Math.min(realNumTargetsBuffers, Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS)) / targetNumBuffers;
|
||||
|
||||
MatParam param = mat.getParam("MorphWeights");
|
||||
if (param == null) {
|
||||
// init the mat param if it doesn't exists.
|
||||
float[] wts = new float[maxGPUTargets];
|
||||
mat.setParam("MorphWeights", VarType.FloatArray, wts);
|
||||
}
|
||||
|
||||
mat.setInt("NumberOfTargetsBuffers", targetNumBuffers);
|
||||
|
||||
// test compile the shader to find the accurate number of remaining attributes slots
|
||||
boolean compilationOk = false;
|
||||
// Note that if ever the shader has an unrelated issue we want to break at some point, hence the maxGPUTargets > 0
|
||||
while (!compilationOk && maxGPUTargets > 0) {
|
||||
// setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define.
|
||||
mat.setInt("NumberOfMorphTargets", maxGPUTargets);
|
||||
try {
|
||||
// preload the spatial. this will trigger a shader compilation that will fail if the number of attributes is over the limit.
|
||||
rm.preloadScene(spatial);
|
||||
compilationOk = true;
|
||||
} catch (RendererException e) {
|
||||
logger.log(Level.FINE, geom.getName() + ": failed at " + maxGPUTargets);
|
||||
// the compilation failed let's decrement the number of targets an try again.
|
||||
maxGPUTargets--;
|
||||
}
|
||||
}
|
||||
logger.log(Level.FINE, geom.getName() + ": " + maxGPUTargets);
|
||||
// set the number of GPU morph on the geom to not have to recompute it next frame.
|
||||
geom.setNbSimultaneousGPUMorph(maxGPUTargets);
|
||||
return maxGPUTargets;
|
||||
}
|
||||
|
||||
private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) {
|
||||
int start = VertexBuffer.Type.MorphTarget0.ordinal();
|
||||
if (targetNumBuffers >= 1) {
|
||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position));
|
||||
boundBufferIdx++;
|
||||
}
|
||||
if (targetNumBuffers >= 2) {
|
||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal));
|
||||
boundBufferIdx++;
|
||||
}
|
||||
if (!approximateTangents && targetNumBuffers == 3) {
|
||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
|
||||
boundBufferIdx++;
|
||||
}
|
||||
return boundBufferIdx;
|
||||
}
|
||||
|
||||
private void writeCpuBuffer(int targetNumBuffers, MorphTarget mt) {
|
||||
if (targetNumBuffers >= 1) {
|
||||
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Position);
|
||||
dest.rewind();
|
||||
dest.put(tmpPosArray, 0, dest.capacity());
|
||||
}
|
||||
if (targetNumBuffers >= 2) {
|
||||
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Normal);
|
||||
dest.rewind();
|
||||
dest.put(tmpNormArray, 0, dest.capacity());
|
||||
}
|
||||
if (!approximateTangents && targetNumBuffers == 3) {
|
||||
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Tangent);
|
||||
dest.rewind();
|
||||
dest.put(tmpTanArray, 0, dest.capacity());
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeMorphTargets(int targetNumBuffers, float weight, MorphTarget t, boolean init) {
|
||||
if (targetNumBuffers >= 1) {
|
||||
mergeTargetBuffer(tmpPosArray, weight, t.getBuffer(VertexBuffer.Type.Position), init);
|
||||
}
|
||||
if (targetNumBuffers >= 2) {
|
||||
mergeTargetBuffer(tmpNormArray, weight, t.getBuffer(VertexBuffer.Type.Normal), init);
|
||||
}
|
||||
if (!approximateTangents && targetNumBuffers == 3) {
|
||||
mergeTargetBuffer(tmpTanArray, weight, t.getBuffer(VertexBuffer.Type.Tangent), init);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTmpArraysCapacity(int capacity, int targetNumBuffers) {
|
||||
if (targetNumBuffers >= 1) {
|
||||
tmpPosArray = ensureCapacity(tmpPosArray, capacity);
|
||||
}
|
||||
if (targetNumBuffers >= 2) {
|
||||
tmpNormArray = ensureCapacity(tmpNormArray, capacity);
|
||||
}
|
||||
if (!approximateTangents && targetNumBuffers == 3) {
|
||||
tmpTanArray = ensureCapacity(tmpTanArray, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeTargetBuffer(float[] array, float weight, FloatBuffer src, boolean init) {
|
||||
src.rewind();
|
||||
for (int j = 0; j < src.capacity(); j++) {
|
||||
if (init) {
|
||||
array[j] = 0;
|
||||
}
|
||||
array[j] += weight * src.get();
|
||||
}
|
||||
}
|
||||
|
||||
private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) {
|
||||
VertexBuffer.Type t = bufferTypes[start + idx];
|
||||
VertexBuffer vb = mesh.getBuffer(t);
|
||||
// only set the buffer if it's different
|
||||
if (vb == null || vb.getData() != b) {
|
||||
mesh.setBuffer(t, 3, b);
|
||||
}
|
||||
}
|
||||
|
||||
private float[] ensureCapacity(float[] tmpArray, int size) {
|
||||
if (tmpArray == null || tmpArray.length < size) {
|
||||
return new float[size];
|
||||
}
|
||||
return tmpArray;
|
||||
}
|
||||
|
||||
private MorphTarget initCpuMorphTarget(Geometry geom) {
|
||||
MorphTarget res = new MorphTarget();
|
||||
MorphTarget mt = geom.getMesh().getMorphTargets()[0];
|
||||
FloatBuffer b = mt.getBuffer(VertexBuffer.Type.Position);
|
||||
if (b != null) {
|
||||
res.setBuffer(VertexBuffer.Type.Position, BufferUtils.createFloatBuffer(b.capacity()));
|
||||
}
|
||||
b = mt.getBuffer(VertexBuffer.Type.Normal);
|
||||
if (b != null) {
|
||||
res.setBuffer(VertexBuffer.Type.Normal, BufferUtils.createFloatBuffer(b.capacity()));
|
||||
}
|
||||
if (!approximateTangents) {
|
||||
b = mt.getBuffer(VertexBuffer.Type.Tangent);
|
||||
if (b != null) {
|
||||
res.setBuffer(VertexBuffer.Type.Tangent, BufferUtils.createFloatBuffer(b.capacity()));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private int getTargetNumBuffers(MorphTarget morphTarget) {
|
||||
int num = 0;
|
||||
if (morphTarget.getBuffer(VertexBuffer.Type.Position) != null) num++;
|
||||
if (morphTarget.getBuffer(VertexBuffer.Type.Normal) != null) num++;
|
||||
|
||||
// if tangents are not needed we don't count the tangent buffer
|
||||
if (!approximateTangents && morphTarget.getBuffer(VertexBuffer.Type.Tangent) != null) {
|
||||
num++;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the number of remaining buffers on this mesh.
|
||||
* This is supposed to give a hint on how many attributes will be used in the material and computes the remaining available slots for the morph attributes.
|
||||
* However, the shader can declare attributes that are not used and not bound to a real buffer.
|
||||
* That's why we attempt to compile the shader later on to avoid any compilation crash.
|
||||
* This method is here to avoid too much render test iteration.
|
||||
*
|
||||
* @param mesh
|
||||
* @param renderer
|
||||
* @return
|
||||
*/
|
||||
private int getRemainingBuffers(Mesh mesh, Renderer renderer) {
|
||||
int nbUsedBuffers = 0;
|
||||
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
|
||||
boolean isMorphBuffer = vb.getBufferType().ordinal() >= VertexBuffer.Type.MorphTarget0.ordinal() && vb.getBufferType().ordinal() <= VertexBuffer.Type.MorphTarget9.ordinal();
|
||||
if (vb.getBufferType() == VertexBuffer.Type.Index || isMorphBuffer) continue;
|
||||
if (vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
|
||||
nbUsedBuffers++;
|
||||
}
|
||||
}
|
||||
return renderer.getLimits().get(Limits.VertexAttributes) - nbUsedBuffers;
|
||||
}
|
||||
|
||||
public void setApproximateTangents(boolean approximateTangents) {
|
||||
this.approximateTangents = approximateTangents;
|
||||
}
|
||||
|
||||
public boolean isApproximateTangents() {
|
||||
return approximateTangents;
|
||||
}
|
||||
|
||||
private class TargetLocator extends SceneGraphVisitorAdapter {
|
||||
@Override
|
||||
public void visit(Geometry geom) {
|
||||
MatParam p = geom.getMaterial().getMaterialDef().getMaterialParam("MorphWeights");
|
||||
if (p == null) {
|
||||
return;
|
||||
}
|
||||
Mesh mesh = geom.getMesh();
|
||||
if (mesh != null && mesh.hasMorphTargets()) {
|
||||
targets.add(geom);
|
||||
// If the mesh is in a subgraph of a node with a SkinningControl it might have hardware skinning activated through mat param override even if it's not skinned.
|
||||
// this code makes sure that if the mesh has no hardware skinning buffers hardware skinning won't be activated.
|
||||
// this is important, because if HW skinning is activated the shader will declare 2 additional useless attributes,
|
||||
// and we desperately need all the attributes we can find for Morph animation.
|
||||
if (mesh.getBuffer(VertexBuffer.Type.HWBoneIndex) == null && !geom.getLocalMatParamOverrides().contains(nullNumberOfBones)) {
|
||||
geom.addMatParamOverride(nullNumberOfBones);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
219
jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
Normal file
219
jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 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.anim;
|
||||
|
||||
import com.jme3.anim.interpolator.FrameInterpolator;
|
||||
import com.jme3.animation.*;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Contains a list of weights and times for each keyframe.
|
||||
*
|
||||
* @author Rémy Bouquet
|
||||
*/
|
||||
public class MorphTrack implements AnimTrack<float[]> {
|
||||
|
||||
private double length;
|
||||
private Geometry target;
|
||||
|
||||
/**
|
||||
* Weights and times for track.
|
||||
*/
|
||||
private float[] weights;
|
||||
private FrameInterpolator interpolator = FrameInterpolator.DEFAULT;
|
||||
private float[] times;
|
||||
private int nbMorphTargets;
|
||||
|
||||
/**
|
||||
* Serialization-only. Do not use.
|
||||
*/
|
||||
public MorphTrack() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a morph track with the given Geometry as a target
|
||||
*
|
||||
* @param times a float array with the time of each frame
|
||||
* @param weights the morphs for each frames
|
||||
*/
|
||||
public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTargets) {
|
||||
this.target = target;
|
||||
this.nbMorphTargets = nbMorphTargets;
|
||||
this.setKeyframes(times, weights);
|
||||
}
|
||||
|
||||
/**
|
||||
* return the array of weights of this track
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public float[] getWeights() {
|
||||
return weights;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the arrays of time for this track
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public float[] getTimes() {
|
||||
return times;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keyframes times for this Joint track
|
||||
*
|
||||
* @param times the keyframes times
|
||||
*/
|
||||
public void setTimes(float[] times) {
|
||||
if (times.length == 0) {
|
||||
throw new RuntimeException("TransformTrack with no keyframes!");
|
||||
}
|
||||
this.times = times;
|
||||
length = times[times.length - 1] - times[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the weight for this morph track
|
||||
*
|
||||
* @param times a float array with the time of each frame
|
||||
* @param weights the weights of the morphs for each frame
|
||||
|
||||
*/
|
||||
public void setKeyframes(float[] times, float[] weights) {
|
||||
setTimes(times);
|
||||
if (weights != null) {
|
||||
if (times == null) {
|
||||
throw new RuntimeException("MorphTrack doesn't have any time for key frames, please call setTimes first");
|
||||
}
|
||||
|
||||
this.weights = weights;
|
||||
|
||||
assert times != null && times.length == weights.length;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDataAtTime(double t, float[] store) {
|
||||
float time = (float) t;
|
||||
|
||||
int lastFrame = times.length - 1;
|
||||
if (time < 0 || lastFrame == 0) {
|
||||
if (weights != null) {
|
||||
System.arraycopy(weights,0,store,0, nbMorphTargets);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int startFrame = 0;
|
||||
int endFrame = 1;
|
||||
float blend = 0;
|
||||
if (time >= times[lastFrame]) {
|
||||
startFrame = lastFrame;
|
||||
|
||||
time = time - times[startFrame] + times[startFrame - 1];
|
||||
blend = (time - times[startFrame - 1])
|
||||
/ (times[startFrame] - times[startFrame - 1]);
|
||||
|
||||
} else {
|
||||
// use lastFrame so we never overflow the array
|
||||
int i;
|
||||
for (i = 0; i < lastFrame && times[i] < time; i++) {
|
||||
startFrame = i;
|
||||
endFrame = i + 1;
|
||||
}
|
||||
blend = (time - times[startFrame])
|
||||
/ (times[endFrame] - times[startFrame]);
|
||||
}
|
||||
|
||||
interpolator.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store);
|
||||
}
|
||||
|
||||
public void setFrameInterpolator(FrameInterpolator interpolator) {
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
public Geometry getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(Geometry target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule oc = ex.getCapsule(this);
|
||||
oc.write(weights, "weights", null);
|
||||
oc.write(times, "times", null);
|
||||
oc.write(target, "target", null);
|
||||
oc.write(nbMorphTargets, "nbMorphTargets", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule ic = im.getCapsule(this);
|
||||
weights = ic.readFloatArray("weights", null);
|
||||
times = ic.readFloatArray("times", null);
|
||||
target = (Geometry) ic.readSavable("target", null);
|
||||
nbMorphTargets = ic.readInt("nbMorphTargets", 0);
|
||||
setTimes(times);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
try {
|
||||
MorphTrack clone = (MorphTrack) super.clone();
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
this.target = cloner.clone(target);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import com.jme3.anim.util.JointModelTransform;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
/**
|
||||
* This JointModelTransform implementation accumulates model transform in a Transform class
|
||||
* This does NOT support proper non uniform scale in the armature hierarchy.
|
||||
* But the effect might be useful in some circumstances.
|
||||
* Note that this is how the old animation system was working, so you might want to use this
|
||||
* if your model has non uniform scale and was migrated from old j3o model.
|
||||
*/
|
||||
public class SeparateJointModelTransform implements JointModelTransform {
|
||||
|
||||
private Transform modelTransform = new Transform();
|
||||
|
||||
@Override
|
||||
public void updateModelTransform(Transform localTransform, Joint parent) {
|
||||
modelTransform.set(localTransform);
|
||||
if (parent != null) {
|
||||
modelTransform.combineWithParent(parent.getModelTransform());
|
||||
}
|
||||
}
|
||||
|
||||
public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) {
|
||||
modelTransform.toTransformMatrix(outTransform).mult(inverseModelBindMatrix, outTransform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) {
|
||||
localTransform.fromTransformMatrix(inverseModelBindMatrix.invert());
|
||||
if (parent != null) {
|
||||
localTransform.combineWithParent(parent.getModelTransform().invert());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform getModelTransform() {
|
||||
return modelTransform;
|
||||
}
|
||||
|
||||
}
|
744
jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
Normal file
744
jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
Normal file
@ -0,0 +1,744 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2017 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.anim;
|
||||
|
||||
import com.jme3.export.*;
|
||||
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.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.control.AbstractControl;
|
||||
import com.jme3.scene.mesh.IndexBuffer;
|
||||
import com.jme3.shader.VarType;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
import com.jme3.util.TempVars;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.logging.Level;
|
||||
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
|
||||
* <p>
|
||||
* It can perform software skinning or Hardware skinning
|
||||
*
|
||||
* @author Rémy Bouquet Based on SkeletonControl by Kirill Vainer
|
||||
*/
|
||||
public class SkinningControl extends AbstractControl implements Cloneable, JmeCloneable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SkinningControl.class.getName());
|
||||
|
||||
/**
|
||||
* The armature of the model.
|
||||
*/
|
||||
private Armature armature;
|
||||
|
||||
/**
|
||||
* List of geometries affected by this control.
|
||||
*/
|
||||
private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class);
|
||||
|
||||
/**
|
||||
* Used to track when a mesh was updated. Meshes are only updated if they
|
||||
* are visible in at least one camera.
|
||||
*/
|
||||
private boolean wasMeshUpdated = false;
|
||||
|
||||
/**
|
||||
* User wishes to use hardware skinning if available.
|
||||
*/
|
||||
private transient boolean hwSkinningDesired = true;
|
||||
|
||||
/**
|
||||
* Hardware skinning is currently being used.
|
||||
*/
|
||||
private transient boolean hwSkinningEnabled = false;
|
||||
|
||||
/**
|
||||
* Hardware skinning was tested on this GPU, results
|
||||
* are stored in {@link #hwSkinningSupported} variable.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private transient boolean hwSkinningSupported = false;
|
||||
|
||||
/**
|
||||
* Bone offset matrices, recreated each frame
|
||||
*/
|
||||
private transient Matrix4f[] offsetMatrices;
|
||||
|
||||
|
||||
private MatParamOverride numberOfJointsParam;
|
||||
private MatParamOverride jointMatricesParam;
|
||||
|
||||
/**
|
||||
* Serialization only. Do not use.
|
||||
*/
|
||||
public SkinningControl() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a armature control. The list of targets will be acquired
|
||||
* automatically when the control is attached to a node.
|
||||
*
|
||||
* @param armature the armature
|
||||
*/
|
||||
public SkinningControl(Armature armature) {
|
||||
if (armature == 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() {
|
||||
numberOfJointsParam.setEnabled(true);
|
||||
jointMatricesParam.setEnabled(true);
|
||||
|
||||
// Next full 10 bones (e.g. 30 on 24 bones)
|
||||
int numBones = ((armature.getJointCount() / 10) + 1) * 10;
|
||||
numberOfJointsParam.setValue(numBones);
|
||||
|
||||
for (Geometry geometry : targets) {
|
||||
Mesh mesh = geometry.getMesh();
|
||||
if (mesh != null && mesh.isAnimated()) {
|
||||
mesh.prepareForAnim(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void switchToSoftware() {
|
||||
numberOfJointsParam.setEnabled(false);
|
||||
jointMatricesParam.setEnabled(false);
|
||||
|
||||
for (Geometry geometry : targets) {
|
||||
Mesh mesh = geometry.getMesh();
|
||||
if (mesh != null && mesh.isAnimated()) {
|
||||
mesh.prepareForAnim(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean testHardwareSupported(RenderManager rm) {
|
||||
|
||||
//Only 255 bones max supported with hardware skinning
|
||||
if (armature.getJointCount() > 255) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switchToHardware();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies if hardware skinning is preferred. If it is preferred and
|
||||
* supported by GPU, it shall be enabled, if its not preferred, or not
|
||||
* supported by GPU, then it shall be disabled.
|
||||
*
|
||||
* @param preferred
|
||||
* @see #isHardwareSkinningUsed()
|
||||
*/
|
||||
public void setHardwareSkinningPreferred(boolean preferred) {
|
||||
hwSkinningDesired = preferred;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if hardware skinning is preferable to software skinning.
|
||||
* Set to false by default.
|
||||
* @see #setHardwareSkinningPreferred(boolean)
|
||||
*/
|
||||
public boolean isHardwareSkinningPreferred() {
|
||||
return hwSkinningDesired;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True is hardware skinning is activated and is currently used, false otherwise.
|
||||
*/
|
||||
public boolean isHardwareSkinningUsed() {
|
||||
return hwSkinningEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If specified the geometry has an animated mesh, add its mesh and material
|
||||
* to the lists of animation targets.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpatial(Spatial spatial) {
|
||||
Spatial oldSpatial = this.spatial;
|
||||
super.setSpatial(spatial);
|
||||
updateTargetsAndMaterials(spatial);
|
||||
|
||||
if (oldSpatial != null) {
|
||||
oldSpatial.removeMatParamOverride(numberOfJointsParam);
|
||||
oldSpatial.removeMatParamOverride(jointMatricesParam);
|
||||
}
|
||||
|
||||
if (spatial != null) {
|
||||
spatial.removeMatParamOverride(numberOfJointsParam);
|
||||
spatial.removeMatParamOverride(jointMatricesParam);
|
||||
spatial.addMatParamOverride(numberOfJointsParam);
|
||||
spatial.addMatParamOverride(jointMatricesParam);
|
||||
}
|
||||
}
|
||||
|
||||
private void controlRenderSoftware() {
|
||||
resetToBind(); // reset morph meshes to bind pose
|
||||
|
||||
offsetMatrices = 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);
|
||||
}
|
||||
}
|
||||
|
||||
private void controlRenderHardware() {
|
||||
offsetMatrices = armature.computeSkinningMatrices();
|
||||
jointMatricesParam.setValue(offsetMatrices);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
if (!wasMeshUpdated) {
|
||||
updateTargetsAndMaterials(spatial);
|
||||
|
||||
// Prevent illegal cases. These should never happen.
|
||||
assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled);
|
||||
assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported);
|
||||
|
||||
if (hwSkinningDesired && !hwSkinningTested) {
|
||||
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();
|
||||
}
|
||||
} else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) {
|
||||
switchToHardware();
|
||||
hwSkinningEnabled = true;
|
||||
} else if (!hwSkinningDesired && hwSkinningEnabled) {
|
||||
switchToSoftware();
|
||||
hwSkinningEnabled = false;
|
||||
}
|
||||
|
||||
if (hwSkinningEnabled) {
|
||||
controlRenderHardware();
|
||||
} else {
|
||||
controlRenderSoftware();
|
||||
}
|
||||
|
||||
wasMeshUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
wasMeshUpdated = false;
|
||||
armature.update();
|
||||
}
|
||||
|
||||
//only do this for software updates
|
||||
void resetToBind() {
|
||||
for (Geometry geometry : targets) {
|
||||
Mesh mesh = geometry.getMesh();
|
||||
if (mesh != null && mesh.isAnimated()) {
|
||||
Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
|
||||
Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
|
||||
if (!biBuff.hasArray() || !bwBuff.hasArray()) {
|
||||
mesh.prepareForAnim(true); // prepare for software animation
|
||||
}
|
||||
VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
|
||||
VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
|
||||
VertexBuffer pos = mesh.getBuffer(Type.Position);
|
||||
VertexBuffer norm = mesh.getBuffer(Type.Normal);
|
||||
FloatBuffer pb = (FloatBuffer) pos.getData();
|
||||
FloatBuffer nb = (FloatBuffer) norm.getData();
|
||||
FloatBuffer bpb = (FloatBuffer) bindPos.getData();
|
||||
FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
|
||||
pb.clear();
|
||||
nb.clear();
|
||||
bpb.clear();
|
||||
bnb.clear();
|
||||
|
||||
//reseting bind tangents if there is a bind tangent buffer
|
||||
VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
|
||||
if (bindTangents != null) {
|
||||
VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
|
||||
FloatBuffer tb = (FloatBuffer) tangents.getData();
|
||||
FloatBuffer btb = (FloatBuffer) bindTangents.getData();
|
||||
tb.clear();
|
||||
btb.clear();
|
||||
tb.put(btb).clear();
|
||||
}
|
||||
|
||||
|
||||
pb.put(bpb).clear();
|
||||
nb.put(bnb).clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
return super.jmeClone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
super.cloneFields(cloner, original);
|
||||
|
||||
this.armature = cloner.clone(armature);
|
||||
|
||||
// If the targets were cloned then this will clone them. If the targets
|
||||
// were shared then this will share them.
|
||||
this.targets = cloner.clone(targets);
|
||||
|
||||
this.numberOfJointsParam = cloner.clone(numberOfJointsParam);
|
||||
this.jointMatricesParam = cloner.clone(jointMatricesParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.");
|
||||
}
|
||||
|
||||
updateTargetsAndMaterials(spatial);
|
||||
int boneIndex = armature.getJointIndex(b);
|
||||
Node n = b.getAttachmentsNode(boneIndex, targets);
|
||||
/*
|
||||
* Select a node to parent the attachments node.
|
||||
*/
|
||||
Node parent;
|
||||
if (spatial instanceof Node) {
|
||||
parent = (Node) spatial; // the usual case
|
||||
} else {
|
||||
parent = spatial.getParent();
|
||||
}
|
||||
parent.attachChild(n);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the armature of this control
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Armature getArmature() {
|
||||
return armature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate the target meshes of this control.
|
||||
*
|
||||
* @return a new array
|
||||
*/
|
||||
public Mesh[] getTargets() {
|
||||
Mesh[] result = new Mesh[targets.size()];
|
||||
int i = 0;
|
||||
for (Geometry geometry : targets) {
|
||||
Mesh mesh = geometry.getMesh();
|
||||
result[i] = mesh;
|
||||
i++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the mesh according to the given transformation matrices
|
||||
*
|
||||
* @param mesh then mesh
|
||||
* @param offsetMatrices the transformation matrices to apply
|
||||
*/
|
||||
private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
|
||||
|
||||
VertexBuffer tb = mesh.getBuffer(Type.Tangent);
|
||||
if (tb == null) {
|
||||
//if there are no tangents use the classic skinning
|
||||
applySkinning(mesh, offsetMatrices);
|
||||
} else {
|
||||
//if there are tangents use the skinning with tangents
|
||||
applySkinningTangents(mesh, offsetMatrices, tb);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to apply skinning transforms to a mesh's buffers
|
||||
*
|
||||
* @param mesh the mesh
|
||||
* @param offsetMatrices the offset matices to apply
|
||||
*/
|
||||
private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
|
||||
int maxWeightsPerVert = mesh.getMaxNumWeights();
|
||||
if (maxWeightsPerVert <= 0) {
|
||||
throw new IllegalStateException("Max weights per vert is incorrectly set!");
|
||||
}
|
||||
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
|
||||
|
||||
// NOTE: This code assumes the vertex buffer is in bind pose
|
||||
// resetToBind() has been called this frame
|
||||
VertexBuffer vb = mesh.getBuffer(Type.Position);
|
||||
FloatBuffer fvb = (FloatBuffer) vb.getData();
|
||||
fvb.rewind();
|
||||
|
||||
VertexBuffer nb = mesh.getBuffer(Type.Normal);
|
||||
FloatBuffer fnb = (FloatBuffer) nb.getData();
|
||||
fnb.rewind();
|
||||
|
||||
// get boneIndexes and weights for mesh
|
||||
IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
|
||||
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
|
||||
|
||||
wb.rewind();
|
||||
|
||||
float[] weights = wb.array();
|
||||
int idxWeights = 0;
|
||||
|
||||
TempVars vars = TempVars.get();
|
||||
|
||||
float[] posBuf = vars.skinPositions;
|
||||
float[] normBuf = vars.skinNormals;
|
||||
|
||||
int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
|
||||
int bufLength = posBuf.length;
|
||||
for (int i = iterations - 1; i >= 0; i--) {
|
||||
// read next set of positions and normals from native buffer
|
||||
bufLength = Math.min(posBuf.length, fvb.remaining());
|
||||
fvb.get(posBuf, 0, bufLength);
|
||||
fnb.get(normBuf, 0, bufLength);
|
||||
int verts = bufLength / 3;
|
||||
int idxPositions = 0;
|
||||
|
||||
// iterate vertices and apply skinning transform for each effecting bone
|
||||
for (int vert = verts - 1; vert >= 0; vert--) {
|
||||
// Skip this vertex if the first weight is zero.
|
||||
if (weights[idxWeights] == 0) {
|
||||
idxPositions += 3;
|
||||
idxWeights += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
float nmx = normBuf[idxPositions];
|
||||
float vtx = posBuf[idxPositions++];
|
||||
float nmy = normBuf[idxPositions];
|
||||
float vty = posBuf[idxPositions++];
|
||||
float nmz = normBuf[idxPositions];
|
||||
float vtz = posBuf[idxPositions++];
|
||||
|
||||
float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
|
||||
|
||||
for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
|
||||
float weight = weights[idxWeights];
|
||||
Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
|
||||
|
||||
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
|
||||
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
|
||||
rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
|
||||
|
||||
rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
|
||||
rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
|
||||
rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
|
||||
}
|
||||
|
||||
idxWeights += fourMinusMaxWeights;
|
||||
|
||||
idxPositions -= 3;
|
||||
normBuf[idxPositions] = rnx;
|
||||
posBuf[idxPositions++] = rx;
|
||||
normBuf[idxPositions] = rny;
|
||||
posBuf[idxPositions++] = ry;
|
||||
normBuf[idxPositions] = rnz;
|
||||
posBuf[idxPositions++] = rz;
|
||||
}
|
||||
|
||||
fvb.position(fvb.position() - bufLength);
|
||||
fvb.put(posBuf, 0, bufLength);
|
||||
fnb.position(fnb.position() - bufLength);
|
||||
fnb.put(normBuf, 0, bufLength);
|
||||
}
|
||||
|
||||
vars.release();
|
||||
|
||||
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
|
||||
*
|
||||
* @param mesh the mesh
|
||||
* @param offsetMatrices the offsetMatrices to apply
|
||||
* @param tb the tangent vertexBuffer
|
||||
*/
|
||||
private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
|
||||
int maxWeightsPerVert = mesh.getMaxNumWeights();
|
||||
|
||||
if (maxWeightsPerVert <= 0) {
|
||||
throw new IllegalStateException("Max weights per vert is incorrectly set!");
|
||||
}
|
||||
|
||||
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
|
||||
|
||||
// NOTE: This code assumes the vertex buffer is in bind pose
|
||||
// resetToBind() has been called this frame
|
||||
VertexBuffer vb = mesh.getBuffer(Type.Position);
|
||||
FloatBuffer fvb = (FloatBuffer) vb.getData();
|
||||
fvb.rewind();
|
||||
|
||||
VertexBuffer nb = mesh.getBuffer(Type.Normal);
|
||||
|
||||
FloatBuffer fnb = (FloatBuffer) nb.getData();
|
||||
fnb.rewind();
|
||||
|
||||
|
||||
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();
|
||||
|
||||
wb.rewind();
|
||||
|
||||
float[] weights = wb.array();
|
||||
int idxWeights = 0;
|
||||
|
||||
TempVars vars = TempVars.get();
|
||||
|
||||
|
||||
float[] posBuf = vars.skinPositions;
|
||||
float[] normBuf = vars.skinNormals;
|
||||
float[] tanBuf = vars.skinTangents;
|
||||
|
||||
int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
|
||||
int bufLength = 0;
|
||||
int tanLength = 0;
|
||||
for (int i = iterations - 1; i >= 0; i--) {
|
||||
// read next set of positions and normals from native buffer
|
||||
bufLength = Math.min(posBuf.length, fvb.remaining());
|
||||
tanLength = Math.min(tanBuf.length, ftb.remaining());
|
||||
fvb.get(posBuf, 0, bufLength);
|
||||
fnb.get(normBuf, 0, bufLength);
|
||||
ftb.get(tanBuf, 0, tanLength);
|
||||
int verts = bufLength / 3;
|
||||
int idxPositions = 0;
|
||||
//tangents has their own index because of the 4 components
|
||||
int idxTangents = 0;
|
||||
|
||||
// iterate vertices and apply skinning transform for each effecting bone
|
||||
for (int vert = verts - 1; vert >= 0; vert--) {
|
||||
// Skip this vertex if the first weight is zero.
|
||||
if (weights[idxWeights] == 0) {
|
||||
idxTangents += 4;
|
||||
idxPositions += 3;
|
||||
idxWeights += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
float nmx = normBuf[idxPositions];
|
||||
float vtx = posBuf[idxPositions++];
|
||||
float nmy = normBuf[idxPositions];
|
||||
float vty = posBuf[idxPositions++];
|
||||
float nmz = normBuf[idxPositions];
|
||||
float vtz = posBuf[idxPositions++];
|
||||
|
||||
float tnx = tanBuf[idxTangents++];
|
||||
float tny = tanBuf[idxTangents++];
|
||||
float tnz = tanBuf[idxTangents++];
|
||||
|
||||
// skipping the 4th component of the tangent since it doesn't have to be transformed
|
||||
idxTangents++;
|
||||
|
||||
float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
|
||||
|
||||
for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
|
||||
float weight = weights[idxWeights];
|
||||
Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
|
||||
|
||||
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
|
||||
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
|
||||
rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
|
||||
|
||||
rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
|
||||
rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
|
||||
rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
|
||||
|
||||
rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
|
||||
rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
|
||||
rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
|
||||
}
|
||||
|
||||
idxWeights += fourMinusMaxWeights;
|
||||
|
||||
idxPositions -= 3;
|
||||
|
||||
normBuf[idxPositions] = rnx;
|
||||
posBuf[idxPositions++] = rx;
|
||||
normBuf[idxPositions] = rny;
|
||||
posBuf[idxPositions++] = ry;
|
||||
normBuf[idxPositions] = rnz;
|
||||
posBuf[idxPositions++] = rz;
|
||||
|
||||
idxTangents -= 4;
|
||||
|
||||
tanBuf[idxTangents++] = rtx;
|
||||
tanBuf[idxTangents++] = rty;
|
||||
tanBuf[idxTangents++] = rtz;
|
||||
|
||||
//once again skipping the 4th component of the tangent
|
||||
idxTangents++;
|
||||
}
|
||||
|
||||
fvb.position(fvb.position() - bufLength);
|
||||
fvb.put(posBuf, 0, bufLength);
|
||||
fnb.position(fnb.position() - bufLength);
|
||||
fnb.put(normBuf, 0, bufLength);
|
||||
ftb.position(ftb.position() - tanLength);
|
||||
ftb.put(tanBuf, 0, tanLength);
|
||||
}
|
||||
|
||||
vars.release();
|
||||
|
||||
vb.updateData(fvb);
|
||||
nb.updateData(fnb);
|
||||
tb.updateData(ftb);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
super.read(im);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the lists of animation targets.
|
||||
*
|
||||
* @param spatial the controlled spatial
|
||||
*/
|
||||
private void updateTargetsAndMaterials(Spatial spatial) {
|
||||
targets.clear();
|
||||
|
||||
if (spatial instanceof Node) {
|
||||
findTargets((Node) spatial);
|
||||
} else if (spatial instanceof Geometry) {
|
||||
findTargets((Geometry) spatial);
|
||||
}
|
||||
}
|
||||
}
|
315
jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
Normal file
315
jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
Normal file
@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 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.anim;
|
||||
|
||||
import com.jme3.anim.interpolator.FrameInterpolator;
|
||||
import com.jme3.anim.tween.Tween;
|
||||
import com.jme3.anim.util.HasLocalTransform;
|
||||
import com.jme3.animation.CompactQuaternionArray;
|
||||
import com.jme3.animation.CompactVector3Array;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Contains a list of transforms and times for each keyframe.
|
||||
*
|
||||
* @author Rémy Bouquet
|
||||
*/
|
||||
public class TransformTrack implements AnimTrack<Transform> {
|
||||
|
||||
private double length;
|
||||
private HasLocalTransform target;
|
||||
|
||||
/**
|
||||
* Transforms and times for track.
|
||||
*/
|
||||
private CompactVector3Array translations;
|
||||
private CompactQuaternionArray rotations;
|
||||
private CompactVector3Array scales;
|
||||
private FrameInterpolator interpolator = FrameInterpolator.DEFAULT;
|
||||
private float[] times;
|
||||
|
||||
/**
|
||||
* Serialization-only. Do not use.
|
||||
*/
|
||||
public TransformTrack() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transform track for the given bone index
|
||||
*
|
||||
* @param times a float array with the time of each frame
|
||||
* @param translations the translation of the bone for each frame
|
||||
* @param rotations the rotation of the bone for each frame
|
||||
* @param scales the scale of the bone for each frame
|
||||
*/
|
||||
public TransformTrack(HasLocalTransform target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
|
||||
this.target = target;
|
||||
this.setKeyframes(times, translations, rotations, scales);
|
||||
}
|
||||
|
||||
/**
|
||||
* return the array of rotations of this track
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Quaternion[] getRotations() {
|
||||
return rotations.toObjectArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the array of scales for this track
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Vector3f[] getScales() {
|
||||
return scales == null ? null : scales.toObjectArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the arrays of time for this track
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public float[] getTimes() {
|
||||
return times;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the array of translations of this track
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Vector3f[] getTranslations() {
|
||||
return translations.toObjectArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the keyframes times for this Joint track
|
||||
*
|
||||
* @param times the keyframes times
|
||||
*/
|
||||
public void setTimes(float[] times) {
|
||||
if (times.length == 0) {
|
||||
throw new RuntimeException("TransformTrack with no keyframes!");
|
||||
}
|
||||
this.times = times;
|
||||
length = times[times.length - 1] - times[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the translations for this joint track
|
||||
*
|
||||
* @param translations the translation of the bone for each frame
|
||||
*/
|
||||
public void setKeyframesTranslation(Vector3f[] translations) {
|
||||
if (times == null) {
|
||||
throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
|
||||
}
|
||||
if (translations.length == 0) {
|
||||
throw new RuntimeException("TransformTrack with no translation keyframes!");
|
||||
}
|
||||
this.translations = new CompactVector3Array();
|
||||
this.translations.add(translations);
|
||||
this.translations.freeze();
|
||||
|
||||
assert times != null && times.length == translations.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scales for this joint track
|
||||
*
|
||||
* @param scales the scales of the bone for each frame
|
||||
*/
|
||||
public void setKeyframesScale(Vector3f[] scales) {
|
||||
if (times == null) {
|
||||
throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
|
||||
}
|
||||
if (scales.length == 0) {
|
||||
throw new RuntimeException("TransformTrack with no scale keyframes!");
|
||||
}
|
||||
this.scales = new CompactVector3Array();
|
||||
this.scales.add(scales);
|
||||
this.scales.freeze();
|
||||
|
||||
assert times != null && times.length == scales.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rotations for this joint track
|
||||
*
|
||||
* @param rotations the rotations of the bone for each frame
|
||||
*/
|
||||
public void setKeyframesRotation(Quaternion[] rotations) {
|
||||
if (times == null) {
|
||||
throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
|
||||
}
|
||||
if (rotations.length == 0) {
|
||||
throw new RuntimeException("TransformTrack with no rotation keyframes!");
|
||||
}
|
||||
this.rotations = new CompactQuaternionArray();
|
||||
this.rotations.add(rotations);
|
||||
this.rotations.freeze();
|
||||
|
||||
assert times != null && times.length == rotations.length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the translations, rotations and scales for this bone track
|
||||
*
|
||||
* @param times a float array with the time of each frame
|
||||
* @param translations the translation of the bone for each frame
|
||||
* @param rotations the rotation of the bone for each frame
|
||||
* @param scales the scale of the bone for each frame
|
||||
*/
|
||||
public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
|
||||
setTimes(times);
|
||||
if (translations != null) {
|
||||
setKeyframesTranslation(translations);
|
||||
}
|
||||
if (rotations != null) {
|
||||
setKeyframesRotation(rotations);
|
||||
}
|
||||
if (scales != null) {
|
||||
setKeyframesScale(scales);
|
||||
}
|
||||
}
|
||||
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void getDataAtTime(double t, Transform transform) {
|
||||
float time = (float) t;
|
||||
|
||||
int lastFrame = times.length - 1;
|
||||
if (time < 0 || lastFrame == 0) {
|
||||
if (translations != null) {
|
||||
translations.get(0, transform.getTranslation());
|
||||
}
|
||||
if (rotations != null) {
|
||||
rotations.get(0, transform.getRotation());
|
||||
}
|
||||
if (scales != null) {
|
||||
scales.get(0, transform.getScale());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int startFrame = 0;
|
||||
int endFrame = 1;
|
||||
float blend = 0;
|
||||
if (time >= times[lastFrame]) {
|
||||
startFrame = lastFrame;
|
||||
|
||||
time = time - times[startFrame] + times[startFrame - 1];
|
||||
blend = (time - times[startFrame - 1])
|
||||
/ (times[startFrame] - times[startFrame - 1]);
|
||||
|
||||
} else {
|
||||
// use lastFrame so we never overflow the array
|
||||
int i;
|
||||
for (i = 0; i < lastFrame && times[i] < time; i++) {
|
||||
startFrame = i;
|
||||
endFrame = i + 1;
|
||||
}
|
||||
blend = (time - times[startFrame])
|
||||
/ (times[endFrame] - times[startFrame]);
|
||||
}
|
||||
|
||||
Transform interpolated = interpolator.interpolate(blend, startFrame, translations, rotations, scales, times);
|
||||
|
||||
if (translations != null) {
|
||||
transform.setTranslation(interpolated.getTranslation());
|
||||
}
|
||||
if (rotations != null) {
|
||||
transform.setRotation(interpolated.getRotation());
|
||||
}
|
||||
if (scales != null) {
|
||||
transform.setScale(interpolated.getScale());
|
||||
}
|
||||
}
|
||||
|
||||
public void setFrameInterpolator(FrameInterpolator interpolator) {
|
||||
this.interpolator = interpolator;
|
||||
}
|
||||
|
||||
public HasLocalTransform getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(HasLocalTransform target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule oc = ex.getCapsule(this);
|
||||
oc.write(translations, "translations", null);
|
||||
oc.write(rotations, "rotations", null);
|
||||
oc.write(times, "times", null);
|
||||
oc.write(scales, "scales", null);
|
||||
oc.write(target, "target", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule ic = im.getCapsule(this);
|
||||
translations = (CompactVector3Array) ic.readSavable("translations", null);
|
||||
rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
|
||||
times = ic.readFloatArray("times", null);
|
||||
scales = (CompactVector3Array) ic.readSavable("scales", null);
|
||||
target = (HasLocalTransform) ic.readSavable("target", null);
|
||||
setTimes(times);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jmeClone() {
|
||||
try {
|
||||
TransformTrack clone = (TransformTrack) super.clone();
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cloneFields(Cloner cloner, Object original) {
|
||||
this.target = cloner.clone(target);
|
||||
}
|
||||
}
|
95
jme3-core/src/main/java/com/jme3/anim/Weights.java
Normal file
95
jme3-core/src/main/java/com/jme3/anim/Weights.java
Normal file
@ -0,0 +1,95 @@
|
||||
package com.jme3.anim;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Weights {//} extends Savable, JmeCloneable{
|
||||
|
||||
|
||||
private final static float MIN_WEIGHT = 0.005f;
|
||||
|
||||
private int[] indices;
|
||||
private float[] data;
|
||||
private int size;
|
||||
|
||||
public Weights(float[] array, int start, int length) {
|
||||
ArrayList<Float> list = new ArrayList<>();
|
||||
ArrayList<Integer> idx = new ArrayList<>();
|
||||
|
||||
for (int i = start; i < length; i++) {
|
||||
float val = array[i];
|
||||
if (val > MIN_WEIGHT) {
|
||||
list.add(val);
|
||||
idx.add(i);
|
||||
}
|
||||
}
|
||||
size = list.size();
|
||||
data = new float[size];
|
||||
indices = new int[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
data[i] = list.get(i);
|
||||
indices[i] = idx.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
// public Weights(float[] array, int start, int length) {
|
||||
// LinkedList<Float> list = new LinkedList<>();
|
||||
// LinkedList<Integer> idx = new LinkedList<>();
|
||||
// for (int i = start; i < length; i++) {
|
||||
// float val = array[i];
|
||||
// if (val > MIN_WEIGHT) {
|
||||
// int index = insert(list, val);
|
||||
// if (idx.size() < index) {
|
||||
// idx.add(i);
|
||||
// } else {
|
||||
// idx.add(index, i);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// data = new float[list.size()];
|
||||
// for (int i = 0; i < data.length; i++) {
|
||||
// data[i] = list.get(i);
|
||||
// }
|
||||
//
|
||||
// indices = new int[idx.size()];
|
||||
// for (int i = 0; i < indices.length; i++) {
|
||||
// indices[i] = idx.get(i);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private int insert(LinkedList<Float> list, float value) {
|
||||
// for (int i = 0; i < list.size(); i++) {
|
||||
// float w = list.get(i);
|
||||
// if (value > w) {
|
||||
// list.add(i, value);
|
||||
// return i;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// list.add(value);
|
||||
// return list.size();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
b.append(indices[i]).append(",");
|
||||
}
|
||||
b.append("\n");
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
b.append(data[i]).append(",");
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
// 6 7 4 8
|
||||
float values[] = {0, 0, 0, 0, 0.5f, 0.001f, 0.7f, 0.6f, 0.2f, 0, 0, 0};
|
||||
Weights w = new Weights(values, 0, values.length);
|
||||
System.err.println(w);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.jme3.anim.interpolator;
|
||||
|
||||
import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader;
|
||||
import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader;
|
||||
|
||||
/**
|
||||
* Created by nehon on 15/04/17.
|
||||
*/
|
||||
public abstract class AnimInterpolator<T> {
|
||||
|
||||
public abstract T interpolate(float t, int currentIndex, TrackDataReader<T> data, TrackTimeReader times, T store);
|
||||
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package com.jme3.anim.interpolator;
|
||||
|
||||
import com.jme3.math.*;
|
||||
|
||||
import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader;
|
||||
import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader;
|
||||
|
||||
/**
|
||||
* Created by nehon on 15/04/17.
|
||||
*/
|
||||
public class AnimInterpolators {
|
||||
|
||||
//Rotation interpolators
|
||||
|
||||
public static final AnimInterpolator<Quaternion> NLerp = new AnimInterpolator<Quaternion>() {
|
||||
private Quaternion next = new Quaternion();
|
||||
|
||||
@Override
|
||||
public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
|
||||
data.getEntryClamp(currentIndex, store);
|
||||
data.getEntryClamp(currentIndex + 1, next);
|
||||
store.nlerp(next, t);
|
||||
return store;
|
||||
}
|
||||
};
|
||||
|
||||
public static final AnimInterpolator<Quaternion> SLerp = new AnimInterpolator<Quaternion>() {
|
||||
private Quaternion next = new Quaternion();
|
||||
|
||||
@Override
|
||||
public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
|
||||
data.getEntryClamp(currentIndex, store);
|
||||
data.getEntryClamp(currentIndex + 1, next);
|
||||
//MathUtils.slerpNoInvert(store, next, t, store);
|
||||
MathUtils.slerp(store, next, t, store);
|
||||
return store;
|
||||
}
|
||||
};
|
||||
|
||||
public static final AnimInterpolator<Quaternion> SQuad = new AnimInterpolator<Quaternion>() {
|
||||
private Quaternion a = new Quaternion();
|
||||
private Quaternion b = new Quaternion();
|
||||
|
||||
private Quaternion q0 = new Quaternion();
|
||||
private Quaternion q1 = new Quaternion();
|
||||
private Quaternion q2 = new Quaternion();
|
||||
private Quaternion q3 = new Quaternion();
|
||||
|
||||
@Override
|
||||
public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
|
||||
data.getEntryModSkip(currentIndex - 1, q0);
|
||||
data.getEntryModSkip(currentIndex, q1);
|
||||
data.getEntryModSkip(currentIndex + 1, q2);
|
||||
data.getEntryModSkip(currentIndex + 2, q3);
|
||||
MathUtils.squad(q0, q1, q2, q3, a, b, t, store);
|
||||
return store;
|
||||
}
|
||||
};
|
||||
|
||||
//Position / Scale interpolators
|
||||
public static final AnimInterpolator<Vector3f> LinearVec3f = new AnimInterpolator<Vector3f>() {
|
||||
private Vector3f next = new Vector3f();
|
||||
|
||||
@Override
|
||||
public Vector3f interpolate(float t, int currentIndex, TrackDataReader<Vector3f> data, TrackTimeReader times, Vector3f store) {
|
||||
data.getEntryClamp(currentIndex, store);
|
||||
data.getEntryClamp(currentIndex + 1, next);
|
||||
store.interpolateLocal(next, t);
|
||||
return store;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* CatmullRom interpolation
|
||||
*/
|
||||
public static final CatmullRomInterpolator CatmullRom = new CatmullRomInterpolator();
|
||||
|
||||
public static class CatmullRomInterpolator extends AnimInterpolator<Vector3f> {
|
||||
private Vector3f p0 = new Vector3f();
|
||||
private Vector3f p1 = new Vector3f();
|
||||
private Vector3f p2 = new Vector3f();
|
||||
private Vector3f p3 = new Vector3f();
|
||||
private float tension = 0.7f;
|
||||
|
||||
public CatmullRomInterpolator(float tension) {
|
||||
this.tension = tension;
|
||||
}
|
||||
|
||||
public CatmullRomInterpolator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f interpolate(float t, int currentIndex, TrackDataReader<Vector3f> data, TrackTimeReader times, Vector3f store) {
|
||||
data.getEntryModSkip(currentIndex - 1, p0);
|
||||
data.getEntryModSkip(currentIndex, p1);
|
||||
data.getEntryModSkip(currentIndex + 1, p2);
|
||||
data.getEntryModSkip(currentIndex + 2, p3);
|
||||
|
||||
FastMath.interpolateCatmullRom(t, tension, p0, p1, p2, p3, store);
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
//Time Interpolators
|
||||
|
||||
public static class TimeInterpolator extends AnimInterpolator<Float> {
|
||||
private EaseFunction ease;
|
||||
|
||||
public TimeInterpolator(EaseFunction ease) {
|
||||
this.ease = ease;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float interpolate(float t, int currentIndex, TrackDataReader<Float> data, TrackTimeReader times, Float store) {
|
||||
return ease.apply(t);
|
||||
}
|
||||
}
|
||||
|
||||
//in
|
||||
public static final TimeInterpolator easeInQuad = new TimeInterpolator(Easing.inQuad);
|
||||
public static final TimeInterpolator easeInCubic = new TimeInterpolator(Easing.inCubic);
|
||||
public static final TimeInterpolator easeInQuart = new TimeInterpolator(Easing.inQuart);
|
||||
public static final TimeInterpolator easeInQuint = new TimeInterpolator(Easing.inQuint);
|
||||
public static final TimeInterpolator easeInBounce = new TimeInterpolator(Easing.inBounce);
|
||||
public static final TimeInterpolator easeInElastic = new TimeInterpolator(Easing.inElastic);
|
||||
|
||||
//out
|
||||
public static final TimeInterpolator easeOutQuad = new TimeInterpolator(Easing.outQuad);
|
||||
public static final TimeInterpolator easeOutCubic = new TimeInterpolator(Easing.outCubic);
|
||||
public static final TimeInterpolator easeOutQuart = new TimeInterpolator(Easing.outQuart);
|
||||
public static final TimeInterpolator easeOutQuint = new TimeInterpolator(Easing.outQuint);
|
||||
public static final TimeInterpolator easeOutBounce = new TimeInterpolator(Easing.outBounce);
|
||||
public static final TimeInterpolator easeOutElastic = new TimeInterpolator(Easing.outElastic);
|
||||
|
||||
//inout
|
||||
public static final TimeInterpolator easeInOutQuad = new TimeInterpolator(Easing.inOutQuad);
|
||||
public static final TimeInterpolator easeInOutCubic = new TimeInterpolator(Easing.inOutCubic);
|
||||
public static final TimeInterpolator easeInOutQuart = new TimeInterpolator(Easing.inOutQuart);
|
||||
public static final TimeInterpolator easeInOutQuint = new TimeInterpolator(Easing.inOutQuint);
|
||||
public static final TimeInterpolator easeInOutBounce = new TimeInterpolator(Easing.inOutBounce);
|
||||
public static final TimeInterpolator easeInOutElastic = new TimeInterpolator(Easing.inOutElastic);
|
||||
|
||||
//extra
|
||||
public static final TimeInterpolator smoothStep = new TimeInterpolator(Easing.smoothStep);
|
||||
public static final TimeInterpolator smootherStep = new TimeInterpolator(Easing.smootherStep);
|
||||
|
||||
public static final TimeInterpolator constant = new TimeInterpolator(Easing.constant);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package com.jme3.anim.interpolator;
|
||||
|
||||
import com.jme3.animation.*;
|
||||
import com.jme3.math.*;
|
||||
|
||||
/**
|
||||
* Created by nehon on 15/04/17.
|
||||
*/
|
||||
public class FrameInterpolator {
|
||||
|
||||
public static final FrameInterpolator DEFAULT = new FrameInterpolator();
|
||||
|
||||
private AnimInterpolator<Float> timeInterpolator;
|
||||
private AnimInterpolator<Vector3f> translationInterpolator = AnimInterpolators.LinearVec3f;
|
||||
private AnimInterpolator<Quaternion> rotationInterpolator = AnimInterpolators.NLerp;
|
||||
private AnimInterpolator<Vector3f> scaleInterpolator = AnimInterpolators.LinearVec3f;
|
||||
|
||||
private TrackDataReader<Vector3f> translationReader = new TrackDataReader<>();
|
||||
private TrackDataReader<Quaternion> rotationReader = new TrackDataReader<>();
|
||||
private TrackDataReader<Vector3f> scaleReader = new TrackDataReader<>();
|
||||
private TrackTimeReader timesReader = new TrackTimeReader();
|
||||
|
||||
|
||||
private Transform transforms = new Transform();
|
||||
|
||||
public Transform interpolate(float t, int currentIndex, CompactVector3Array translations, CompactQuaternionArray rotations, CompactVector3Array scales, float[] times){
|
||||
timesReader.setData(times);
|
||||
if( timeInterpolator != null){
|
||||
t = timeInterpolator.interpolate(t,currentIndex, null, timesReader, null );
|
||||
}
|
||||
if(translations != null) {
|
||||
translationReader.setData(translations);
|
||||
translationInterpolator.interpolate(t, currentIndex, translationReader, timesReader, transforms.getTranslation());
|
||||
}
|
||||
if(rotations != null) {
|
||||
rotationReader.setData(rotations);
|
||||
rotationInterpolator.interpolate(t, currentIndex, rotationReader, timesReader, transforms.getRotation());
|
||||
}
|
||||
if(scales != null){
|
||||
scaleReader.setData(scales);
|
||||
scaleInterpolator.interpolate(t, currentIndex, scaleReader, timesReader, transforms.getScale());
|
||||
}
|
||||
return transforms;
|
||||
}
|
||||
|
||||
public void interpolateWeights(float t, int currentIndex, float[] weights, int nbMorphTargets, float[] store) {
|
||||
int start = currentIndex * nbMorphTargets;
|
||||
for (int i = 0; i < nbMorphTargets; i++) {
|
||||
int current = start + i;
|
||||
int next = current + nbMorphTargets;
|
||||
if (next >= weights.length) {
|
||||
next = current;
|
||||
}
|
||||
|
||||
float val = FastMath.interpolateLinear(t, weights[current], weights[next]);
|
||||
store[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTimeInterpolator(AnimInterpolator<Float> timeInterpolator) {
|
||||
this.timeInterpolator = timeInterpolator;
|
||||
}
|
||||
|
||||
public void setTranslationInterpolator(AnimInterpolator<Vector3f> translationInterpolator) {
|
||||
this.translationInterpolator = translationInterpolator;
|
||||
}
|
||||
|
||||
public void setRotationInterpolator(AnimInterpolator<Quaternion> rotationInterpolator) {
|
||||
this.rotationInterpolator = rotationInterpolator;
|
||||
}
|
||||
|
||||
public void setScaleInterpolator(AnimInterpolator<Vector3f> scaleInterpolator) {
|
||||
this.scaleInterpolator = scaleInterpolator;
|
||||
}
|
||||
|
||||
|
||||
public static class TrackTimeReader {
|
||||
private float[] data;
|
||||
|
||||
protected void setData(float[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public float getEntry(int index) {
|
||||
return data[mod(index, data.length)];
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return data.length;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TrackDataReader<T> {
|
||||
|
||||
private CompactArray<T> data;
|
||||
|
||||
protected void setData(CompactArray<T> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public T getEntryMod(int index, T store) {
|
||||
return data.get(mod(index, data.getTotalObjectSize()), store);
|
||||
}
|
||||
|
||||
public T getEntryClamp(int index, T store) {
|
||||
index = (int) FastMath.clamp(index, 0, data.getTotalObjectSize() - 1);
|
||||
return data.get(index, store);
|
||||
}
|
||||
|
||||
public T getEntryModSkip(int index, T store) {
|
||||
int total = data.getTotalObjectSize();
|
||||
if (index == -1) {
|
||||
index--;
|
||||
} else if (index >= total) {
|
||||
index++;
|
||||
}
|
||||
|
||||
index = mod(index, total);
|
||||
|
||||
|
||||
return data.get(index, store);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Euclidean modulo (cycle on 0,n instead of -n,0; 0,n)
|
||||
*
|
||||
* @param val
|
||||
* @param n
|
||||
* @return
|
||||
*/
|
||||
private static int mod(int val, int n) {
|
||||
return ((val % n) + n) % n;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2015, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder 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 HOLDER 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.anim.tween;
|
||||
|
||||
|
||||
import com.jme3.export.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Base implementation of the Tween interface that provides
|
||||
* default implementations of the getLength() and interopolate()
|
||||
* methods that provide common tween clamping and bounds checking.
|
||||
* Subclasses need only override the doInterpolate() method and
|
||||
* the rest is handled for them.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public abstract class AbstractTween implements Tween {
|
||||
|
||||
private double length;
|
||||
|
||||
protected AbstractTween(double length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(double length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation clamps the time value, converts
|
||||
* it to 0 to 1.0 based on getLength(), and calls doInterpolate().
|
||||
*/
|
||||
@Override
|
||||
public boolean interpolate(double t) {
|
||||
if (t < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scale t to be between 0 and 1 for our length
|
||||
if (length == 0) {
|
||||
t = 1;
|
||||
} else {
|
||||
t = t / length;
|
||||
}
|
||||
|
||||
boolean done = false;
|
||||
if (t >= 1.0) {
|
||||
t = 1.0;
|
||||
done = true;
|
||||
}
|
||||
doInterpolate(t);
|
||||
return !done;
|
||||
}
|
||||
|
||||
protected abstract void doInterpolate(double t);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.jme3.anim.tween;
|
||||
|
||||
public interface ContainsTweens {
|
||||
|
||||
public Tween[] getTweens();
|
||||
}
|
71
jme3-core/src/main/java/com/jme3/anim/tween/Tween.java
Normal file
71
jme3-core/src/main/java/com/jme3/anim/tween/Tween.java
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2015, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder 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 HOLDER 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.anim.tween;
|
||||
|
||||
|
||||
import com.jme3.export.Savable;
|
||||
|
||||
/**
|
||||
* Represents some action that interpolates across input between 0
|
||||
* and some length value. (For example, movement, rotation, fading.)
|
||||
* It's also possible to have zero length 'instant' tweens.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public interface Tween extends Cloneable {
|
||||
|
||||
/**
|
||||
* Returns the length of the tween. If 't' represents time in
|
||||
* seconds then this is the notional time in seconds that the tween
|
||||
* will run. Note: all of the caveats are because tweens may be
|
||||
* externally scaled in such a way that 't' no longer represents
|
||||
* actual time.
|
||||
*/
|
||||
public double getLength();
|
||||
|
||||
/**
|
||||
* Sets the implementation specific interpolation to the
|
||||
* specified 'tween' value as a value in the range from 0 to
|
||||
* getLength(). If the value is greater or equal to getLength()
|
||||
* then it is internally clamped and the method returns false.
|
||||
* If 't' is still in the tween's range then this method returns
|
||||
* true.
|
||||
*/
|
||||
public boolean interpolate(double t);
|
||||
|
||||
}
|
||||
|
619
jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java
Normal file
619
jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java
Normal file
@ -0,0 +1,619 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (c) 2015, Simsilica, LLC
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder 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 HOLDER 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.anim.tween;
|
||||
|
||||
import com.jme3.anim.util.Primitives;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Static utility methods for creating common generic Tween objects.
|
||||
*
|
||||
* @author Paul Speed
|
||||
*/
|
||||
public class Tweens {
|
||||
|
||||
static Logger log = Logger.getLogger(Tweens.class.getName());
|
||||
|
||||
private static final CurveFunction SMOOTH = new SmoothStep();
|
||||
private static final CurveFunction SINE = new Sine();
|
||||
|
||||
/**
|
||||
* Creates a tween that will interpolate over an entire sequence
|
||||
* of tweens in order.
|
||||
*/
|
||||
public static Tween sequence(Tween... delegates) {
|
||||
return new Sequence(delegates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tween that will interpolate over an entire list
|
||||
* of tweens in parallel, ie: all tweens will be run at the same
|
||||
* time.
|
||||
*/
|
||||
public static Tween parallel(Tween... delegates) {
|
||||
return new Parallel(delegates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tween that will perform a no-op until the length
|
||||
* has expired.
|
||||
*/
|
||||
public static Tween delay(double length) {
|
||||
return new Delay(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tween that scales the specified delegate tween or tweens
|
||||
* to the desired length. If more than one tween is specified then they
|
||||
* are wrapped in a sequence using the sequence() method.
|
||||
*/
|
||||
public static Tween stretch(double desiredLength, Tween... delegates) {
|
||||
if (delegates.length == 1) {
|
||||
return new Stretch(delegates[0], desiredLength);
|
||||
}
|
||||
return new Stretch(sequence(delegates), desiredLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tween that uses a sine function to smooth step the time value
|
||||
* for the specified delegate tween or tweens. These 'curved' wrappers
|
||||
* can be used to smooth the interpolation of another tween.
|
||||
*/
|
||||
public static Tween sineStep(Tween... delegates) {
|
||||
if (delegates.length == 1) {
|
||||
return new Curve(delegates[0], SINE);
|
||||
}
|
||||
return new Curve(sequence(delegates), SINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tween that uses a hermite function to smooth step the time value
|
||||
* for the specified delegate tween or tweens. This is similar to GLSL's
|
||||
* smoothstep(). These 'curved' wrappers can be used to smooth the interpolation
|
||||
* of another tween.
|
||||
*/
|
||||
public static Tween smoothStep(Tween... delegates) {
|
||||
if (delegates.length == 1) {
|
||||
return new Curve(delegates[0], SMOOTH);
|
||||
}
|
||||
return new Curve(sequence(delegates), SMOOTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tween that will call the specified method and optional arguments
|
||||
* whenever supplied a time value greater than or equal to 0. This creates
|
||||
* an "instant" tween of length 0.
|
||||
*/
|
||||
public static Tween callMethod(Object target, String method, Object... args) {
|
||||
return new CallMethod(target, method, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Tween that will call the specified method and optional arguments,
|
||||
* including the time value scaled between 0 and 1. The method must take
|
||||
* a float or double value as its first or last argument, in addition to whatever
|
||||
* optional arguments are specified.
|
||||
* <p>
|
||||
* <p>For example:</p>
|
||||
* <pre>Tweens.callTweenMethod(1, myObject, "foo", "bar")</pre>
|
||||
* <p>Would work for any of the following method signatures:</p>
|
||||
* <pre>
|
||||
* void foo( float t, String arg )
|
||||
* void foo( double t, String arg )
|
||||
* void foo( String arg, float t )
|
||||
* void foo( String arg, double t )
|
||||
* </pre>
|
||||
*/
|
||||
public static Tween callTweenMethod(double length, Object target, String method, Object... args) {
|
||||
return new CallTweenMethod(length, target, method, args);
|
||||
}
|
||||
|
||||
private static interface CurveFunction {
|
||||
public double curve(double input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Curve function for Hermite interpolation ala GLSL smoothstep().
|
||||
*/
|
||||
private static class SmoothStep implements CurveFunction {
|
||||
|
||||
@Override
|
||||
public double curve(double t) {
|
||||
if (t < 0) {
|
||||
return 0;
|
||||
} else if (t > 1) {
|
||||
return 1;
|
||||
}
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Sine implements CurveFunction {
|
||||
|
||||
@Override
|
||||
public double curve(double t) {
|
||||
if (t < 0) {
|
||||
return 0;
|
||||
} else if (t > 1) {
|
||||
return 1;
|
||||
}
|
||||
// Sine starting at -90 will go from -1 to 1 through 0
|
||||
double result = Math.sin(t * Math.PI - Math.PI * 0.5);
|
||||
return (result + 1) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Curve implements Tween {
|
||||
private final Tween delegate;
|
||||
private final CurveFunction func;
|
||||
private final double length;
|
||||
|
||||
public Curve(Tween delegate, CurveFunction func) {
|
||||
this.delegate = delegate;
|
||||
this.func = func;
|
||||
this.length = delegate.getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interpolate(double t) {
|
||||
// Sanity check the inputs
|
||||
if (t < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
// Caller did something strange but we'll allow it
|
||||
return delegate.interpolate(t);
|
||||
}
|
||||
|
||||
t = func.curve(t / length);
|
||||
return delegate.interpolate(t * length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[delegate=" + delegate + ", func=" + func + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private static class Sequence implements Tween, ContainsTweens {
|
||||
private final Tween[] delegates;
|
||||
private int current = 0;
|
||||
private double baseTime;
|
||||
private double length;
|
||||
|
||||
public Sequence(Tween... delegates) {
|
||||
this.delegates = delegates;
|
||||
for (Tween t : delegates) {
|
||||
length += t.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interpolate(double t) {
|
||||
|
||||
// Sanity check the inputs
|
||||
if (t < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t < baseTime) {
|
||||
// We've rolled back before the current sequence step
|
||||
// which means we need to reset and start forward
|
||||
// again. We have no idea how to 'roll back' and
|
||||
// this is the only way to maintain consistency.
|
||||
// The only 'normal' case where this happens is when looping
|
||||
// in which case a full rollback is appropriate.
|
||||
current = 0;
|
||||
baseTime = 0;
|
||||
}
|
||||
|
||||
if (current >= delegates.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip any that are done
|
||||
while (!delegates[current].interpolate(t - baseTime)) {
|
||||
// Time to go to the next one
|
||||
baseTime += delegates[current].getLength();
|
||||
current++;
|
||||
if (current >= delegates.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tween[] getTweens() {
|
||||
return delegates;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Parallel implements Tween, ContainsTweens {
|
||||
private final Tween[] delegates;
|
||||
private final boolean[] done;
|
||||
private double length;
|
||||
private double lastTime;
|
||||
|
||||
public Parallel(Tween... delegates) {
|
||||
this.delegates = delegates;
|
||||
done = new boolean[delegates.length];
|
||||
|
||||
for (Tween t : delegates) {
|
||||
if (t.getLength() > length) {
|
||||
length = t.getLength();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
protected void reset() {
|
||||
for (int i = 0; i < done.length; i++) {
|
||||
done[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interpolate(double t) {
|
||||
// Sanity check the inputs
|
||||
if (t < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t < lastTime) {
|
||||
// We've rolled back before the last time we were given.
|
||||
// This means we may have 'done'ed a few tasks that now
|
||||
// need to be run again. Better to just reset and start
|
||||
// over. As mentioned in the Sequence task, the only 'normal'
|
||||
// use-case for time rolling backwards is when looping. And
|
||||
// in that case, we want to start from the beginning anyway.
|
||||
reset();
|
||||
}
|
||||
lastTime = t;
|
||||
|
||||
int runningCount = delegates.length;
|
||||
for (int i = 0; i < delegates.length; i++) {
|
||||
if (!done[i]) {
|
||||
done[i] = !delegates[i].interpolate(t);
|
||||
}
|
||||
if (done[i]) {
|
||||
runningCount--;
|
||||
}
|
||||
}
|
||||
return runningCount > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tween[] getTweens() {
|
||||
return delegates;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Delay extends AbstractTween {
|
||||
|
||||
public Delay(double length) {
|
||||
super(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInterpolate(double t) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Stretch implements Tween, ContainsTweens {
|
||||
|
||||
private final Tween[] delegate = new Tween[1];
|
||||
private final double length;
|
||||
private final double scale;
|
||||
|
||||
public Stretch(Tween delegate, double length) {
|
||||
this.delegate[0] = delegate;
|
||||
|
||||
this.length = length;
|
||||
|
||||
// Caller desires delegate to be 'length' instead of
|
||||
// it's actual length so we will calculate a time scale
|
||||
// If the desired length is longer than delegate's then
|
||||
// we need to feed time in slower, ie: scale < 1
|
||||
if (length != 0) {
|
||||
this.scale = delegate.getLength() / length;
|
||||
} else {
|
||||
this.scale = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tween[] getTweens() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interpolate(double t) {
|
||||
if (t < 0) {
|
||||
return true;
|
||||
}
|
||||
if (length > 0) {
|
||||
t *= scale;
|
||||
} else {
|
||||
t = length;
|
||||
}
|
||||
return delegate[0].interpolate(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[delegate=" + delegate[0] + ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private static class CallMethod extends AbstractTween {
|
||||
|
||||
private Object target;
|
||||
private Method method;
|
||||
private Object[] args;
|
||||
|
||||
public CallMethod(Object target, String methodName, Object... args) {
|
||||
super(0);
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException("Target cannot be null.");
|
||||
}
|
||||
this.target = target;
|
||||
this.args = args;
|
||||
|
||||
// Lookup the method
|
||||
if (args == null) {
|
||||
this.method = findMethod(target.getClass(), methodName);
|
||||
} else {
|
||||
this.method = findMethod(target.getClass(), methodName, args);
|
||||
}
|
||||
if (this.method == null) {
|
||||
throw new IllegalArgumentException("Method not found for:" + methodName + " on type:" + target.getClass());
|
||||
}
|
||||
this.method.setAccessible(true);
|
||||
}
|
||||
|
||||
private static Method findMethod(Class type, String name, Object... args) {
|
||||
for (Method m : type.getDeclaredMethods()) {
|
||||
if (!Objects.equals(m.getName(), name)) {
|
||||
continue;
|
||||
}
|
||||
Class[] paramTypes = m.getParameterTypes();
|
||||
if (paramTypes.length != args.length) {
|
||||
continue;
|
||||
}
|
||||
int matches = 0;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (paramTypes[i].isInstance(args[i])
|
||||
|| Primitives.wrap(paramTypes[i]).isInstance(args[i])) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (matches == args.length) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
if (type.getSuperclass() != null) {
|
||||
return findMethod(type.getSuperclass(), name, args);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInterpolate(double t) {
|
||||
try {
|
||||
method.invoke(target, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[method=" + method + ", parms=" + Arrays.asList(args) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private static class CallTweenMethod extends AbstractTween {
|
||||
|
||||
private Object target;
|
||||
private Method method;
|
||||
private Object[] args;
|
||||
private int tIndex = -1;
|
||||
private boolean isFloat = false;
|
||||
|
||||
public CallTweenMethod(double length, Object target, String methodName, Object... args) {
|
||||
super(length);
|
||||
if (target == null) {
|
||||
throw new IllegalArgumentException("Target cannot be null.");
|
||||
}
|
||||
this.target = target;
|
||||
|
||||
// Lookup the method
|
||||
this.method = findMethod(target.getClass(), methodName, args);
|
||||
if (this.method == null) {
|
||||
throw new IllegalArgumentException("Method not found for:" + methodName + " on type:" + target.getClass());
|
||||
}
|
||||
this.method.setAccessible(true);
|
||||
|
||||
// So now setup the real args list
|
||||
this.args = new Object[args.length + 1];
|
||||
if (tIndex == 0) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
this.args[i + 1] = args[i];
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
this.args[i] = args[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFloatType(Class type) {
|
||||
return type == Float.TYPE || type == Float.class;
|
||||
}
|
||||
|
||||
private static boolean isDoubleType(Class type) {
|
||||
return type == Double.TYPE || type == Double.class;
|
||||
}
|
||||
|
||||
private Method findMethod(Class type, String name, Object... args) {
|
||||
for (Method m : type.getDeclaredMethods()) {
|
||||
if (!Objects.equals(m.getName(), name)) {
|
||||
continue;
|
||||
}
|
||||
Class[] paramTypes = m.getParameterTypes();
|
||||
if (paramTypes.length != args.length + 1) {
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.log(Level.FINE, "Param lengths of [" + m + "] differ. method arg count:" + paramTypes.length + " lookging for:" + (args.length + 1));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// We accept the 't' parameter as either first or last
|
||||
// so we'll see which one matches.
|
||||
if (isFloatType(paramTypes[0]) || isDoubleType(paramTypes[0])) {
|
||||
// Try it as the first parameter
|
||||
int matches = 0;
|
||||
|
||||
for (int i = 1; i < paramTypes.length; i++) {
|
||||
if (paramTypes[i].isInstance(args[i - 1])) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (matches == args.length) {
|
||||
// Then this is our method and this is how we are configured
|
||||
tIndex = 0;
|
||||
isFloat = isFloatType(paramTypes[0]);
|
||||
} else {
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.log(Level.FINE, m + " Leading float check failed because of type mismatches, for:" + m);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tIndex >= 0) {
|
||||
return m;
|
||||
}
|
||||
|
||||
// Else try it at the end
|
||||
int last = paramTypes.length - 1;
|
||||
if (isFloatType(paramTypes[last]) || isDoubleType(paramTypes[last])) {
|
||||
int matches = 0;
|
||||
|
||||
for (int i = 0; i < last; i++) {
|
||||
if (paramTypes[i].isInstance(args[i])) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (matches == args.length) {
|
||||
// Then this is our method and this is how we are configured
|
||||
tIndex = last;
|
||||
isFloat = isFloatType(paramTypes[last]);
|
||||
return m;
|
||||
} else {
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.log(Level.FINE, "Trailing float check failed because of type mismatches, for:" + m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type.getSuperclass() != null) {
|
||||
return findMethod(type.getSuperclass(), name, args);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInterpolate(double t) {
|
||||
try {
|
||||
if (isFloat) {
|
||||
args[tIndex] = (float) t;
|
||||
} else {
|
||||
args[tIndex] = t;
|
||||
}
|
||||
method.invoke(target, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[method=" + method + ", parms=" + Arrays.asList(args) + "]";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package com.jme3.anim.tween.action;
|
||||
|
||||
import com.jme3.anim.AnimationMask;
|
||||
import com.jme3.anim.tween.Tween;
|
||||
|
||||
public abstract class Action implements Tween {
|
||||
|
||||
protected Action[] actions;
|
||||
private double length;
|
||||
private double speed = 1;
|
||||
private AnimationMask mask;
|
||||
private boolean forward = true;
|
||||
|
||||
protected Action(Tween... tweens) {
|
||||
this.actions = new Action[tweens.length];
|
||||
for (int i = 0; i < tweens.length; i++) {
|
||||
Tween tween = tweens[i];
|
||||
if (tween instanceof Action) {
|
||||
this.actions[i] = (Action) tween;
|
||||
} else {
|
||||
this.actions[i] = new BaseAction(tween);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
protected void setLength(double length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public double getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public void setSpeed(double speed) {
|
||||
this.speed = speed;
|
||||
if( speed < 0){
|
||||
setForward(false);
|
||||
} else {
|
||||
setForward(true);
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationMask getMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
public void setMask(AnimationMask mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
protected boolean isForward() {
|
||||
return forward;
|
||||
}
|
||||
|
||||
protected void setForward(boolean forward) {
|
||||
if(this.forward == forward){
|
||||
return;
|
||||
}
|
||||
this.forward = forward;
|
||||
for (Action action : actions) {
|
||||
action.setForward(forward);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.jme3.anim.tween.action;
|
||||
|
||||
import com.jme3.anim.tween.ContainsTweens;
|
||||
import com.jme3.anim.tween.Tween;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BaseAction extends Action {
|
||||
|
||||
private Tween tween;
|
||||
|
||||
public BaseAction(Tween tween) {
|
||||
this.tween = tween;
|
||||
setLength(tween.getLength());
|
||||
List<Action> subActions = new SafeArrayList<>(Action.class);
|
||||
gatherActions(tween, subActions);
|
||||
actions = new Action[subActions.size()];
|
||||
subActions.toArray(actions);
|
||||
}
|
||||
|
||||
private void gatherActions(Tween tween, List<Action> subActions) {
|
||||
if (tween instanceof Action) {
|
||||
subActions.add((Action) tween);
|
||||
} else if (tween instanceof ContainsTweens) {
|
||||
Tween[] tweens = ((ContainsTweens) tween).getTweens();
|
||||
for (Tween t : tweens) {
|
||||
gatherActions(t, subActions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interpolate(double t) {
|
||||
return tween.interpolate(t);
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package com.jme3.anim.tween.action;
|
||||
|
||||
import com.jme3.anim.util.HasLocalTransform;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BlendAction extends BlendableAction {
|
||||
|
||||
private int firstActiveIndex;
|
||||
private int secondActiveIndex;
|
||||
private BlendSpace blendSpace;
|
||||
private float blendWeight;
|
||||
private double[] timeFactor;
|
||||
private Map<HasLocalTransform, Transform> targetMap = new HashMap<>();
|
||||
|
||||
public BlendAction(BlendSpace blendSpace, BlendableAction... actions) {
|
||||
super(actions);
|
||||
timeFactor = new double[actions.length];
|
||||
this.blendSpace = blendSpace;
|
||||
blendSpace.setBlendAction(this);
|
||||
|
||||
for (BlendableAction action : actions) {
|
||||
if (action.getLength() > getLength()) {
|
||||
setLength(action.getLength());
|
||||
}
|
||||
Collection<HasLocalTransform> targets = action.getTargets();
|
||||
for (HasLocalTransform target : targets) {
|
||||
Transform t = targetMap.get(target);
|
||||
if (t == null) {
|
||||
t = new Transform();
|
||||
targetMap.put(target, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Blending effect maybe unexpected when blended animation don't have the same length
|
||||
//Stretching any action that doesn't have the same length.
|
||||
for (int i = 0; i < this.actions.length; i++) {
|
||||
this.timeFactor[i] = 1;
|
||||
if (this.actions[i].getLength() != getLength()) {
|
||||
double actionLength = this.actions[i].getLength();
|
||||
if (actionLength > 0 && getLength() > 0) {
|
||||
this.timeFactor[i] = this.actions[i].getLength() / getLength();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void doInterpolate(double t) {
|
||||
blendWeight = blendSpace.getWeight();
|
||||
BlendableAction firstActiveAction = (BlendableAction) actions[firstActiveIndex];
|
||||
BlendableAction secondActiveAction = (BlendableAction) actions[secondActiveIndex];
|
||||
firstActiveAction.setCollectTransformDelegate(this);
|
||||
secondActiveAction.setCollectTransformDelegate(this);
|
||||
|
||||
//only interpolate the first action if the weight if below 1.
|
||||
if (blendWeight < 1f) {
|
||||
firstActiveAction.setWeight(1f);
|
||||
firstActiveAction.interpolate(t * timeFactor[firstActiveIndex]);
|
||||
if (blendWeight == 0) {
|
||||
for (HasLocalTransform target : targetMap.keySet()) {
|
||||
collect(target, targetMap.get(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Second action should be interpolated
|
||||
secondActiveAction.setWeight(blendWeight);
|
||||
secondActiveAction.interpolate(t * timeFactor[secondActiveIndex]);
|
||||
|
||||
firstActiveAction.setCollectTransformDelegate(null);
|
||||
secondActiveAction.setCollectTransformDelegate(null);
|
||||
|
||||
}
|
||||
|
||||
protected Action[] getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public BlendSpace getBlendSpace() {
|
||||
return blendSpace;
|
||||
}
|
||||
|
||||
protected void setFirstActiveIndex(int index) {
|
||||
this.firstActiveIndex = index;
|
||||
}
|
||||
|
||||
protected void setSecondActiveIndex(int index) {
|
||||
this.secondActiveIndex = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<HasLocalTransform> getTargets() {
|
||||
return targetMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) {
|
||||
|
||||
Transform tr = targetMap.get(target);
|
||||
if (weight == 1) {
|
||||
tr.set(t);
|
||||
} else if (weight > 0) {
|
||||
tr.interpolateTransforms(tr, t, weight);
|
||||
}
|
||||
|
||||
if (source == actions[secondActiveIndex]) {
|
||||
collect(target, tr);
|
||||
}
|
||||
}
|
||||
|
||||
private void collect(HasLocalTransform target, Transform tr) {
|
||||
if (collectTransformDelegate != null) {
|
||||
collectTransformDelegate.collectTransform(target, tr, this.getWeight(), this);
|
||||
} else {
|
||||
if (getTransitionWeight() == 1) {
|
||||
target.setLocalTransform(tr);
|
||||
} else {
|
||||
Transform trans = target.getLocalTransform();
|
||||
trans.interpolateTransforms(trans, tr, getTransitionWeight());
|
||||
target.setLocalTransform(trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.jme3.anim.tween.action;
|
||||
|
||||
public interface BlendSpace {
|
||||
|
||||
public void setBlendAction(BlendAction action);
|
||||
|
||||
public float getWeight();
|
||||
|
||||
public void setValue(float value);
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package com.jme3.anim.tween.action;
|
||||
|
||||
import com.jme3.anim.tween.AbstractTween;
|
||||
import com.jme3.anim.tween.Tween;
|
||||
import com.jme3.anim.util.HasLocalTransform;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class BlendableAction extends Action {
|
||||
|
||||
protected BlendableAction collectTransformDelegate;
|
||||
private float transitionWeight = 1.0f;
|
||||
private double transitionLength = 0.4f;
|
||||
private float weight = 1f;
|
||||
private TransitionTween transition = new TransitionTween(transitionLength);
|
||||
|
||||
public BlendableAction(Tween... tweens) {
|
||||
super(tweens);
|
||||
}
|
||||
|
||||
public void setCollectTransformDelegate(BlendableAction delegate) {
|
||||
this.collectTransformDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interpolate(double t) {
|
||||
// Sanity check the inputs
|
||||
if (t < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (collectTransformDelegate == null) {
|
||||
if (transition.getLength() > getLength()) {
|
||||
transition.setLength(getLength());
|
||||
}
|
||||
if(isForward()) {
|
||||
transition.interpolate(t);
|
||||
} else {
|
||||
float v = Math.max((float)(getLength() - t), 0f);
|
||||
transition.interpolate(v);
|
||||
}
|
||||
} else {
|
||||
transitionWeight = 1f;
|
||||
}
|
||||
|
||||
if (weight == 0) {
|
||||
//weight is 0 let's not interpolate
|
||||
return t < getLength();
|
||||
}
|
||||
|
||||
doInterpolate(t);
|
||||
|
||||
return t < getLength();
|
||||
}
|
||||
|
||||
public float getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(float weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
protected abstract void doInterpolate(double t);
|
||||
|
||||
public abstract Collection<HasLocalTransform> getTargets();
|
||||
|
||||
public abstract void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source);
|
||||
|
||||
public double getTransitionLength() {
|
||||
return transitionLength;
|
||||
}
|
||||
|
||||
public void setTransitionLength(double transitionLength) {
|
||||
this.transitionLength = transitionLength;
|
||||
}
|
||||
|
||||
protected float getTransitionWeight() {
|
||||
return transitionWeight;
|
||||
}
|
||||
|
||||
private class TransitionTween extends AbstractTween {
|
||||
|
||||
|
||||
public TransitionTween(double length) {
|
||||
super(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInterpolate(double t) {
|
||||
transitionWeight = (float) t;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.jme3.anim.tween.action;
|
||||
|
||||
import com.jme3.anim.*;
|
||||
import com.jme3.anim.tween.AbstractTween;
|
||||
import com.jme3.anim.util.HasLocalTransform;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.Geometry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class ClipAction extends BlendableAction {
|
||||
|
||||
private AnimClip clip;
|
||||
private Transform transform = new Transform();
|
||||
|
||||
public ClipAction(AnimClip clip) {
|
||||
this.clip = clip;
|
||||
setLength(clip.getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doInterpolate(double t) {
|
||||
AnimTrack[] tracks = clip.getTracks();
|
||||
for (AnimTrack track : tracks) {
|
||||
if (track instanceof TransformTrack) {
|
||||
TransformTrack tt = (TransformTrack) track;
|
||||
if(getMask() != null && !getMask().contains(tt.getTarget())){
|
||||
continue;
|
||||
}
|
||||
interpolateTransformTrack(t, tt);
|
||||
} else if (track instanceof MorphTrack) {
|
||||
interpolateMorphTrack(t, (MorphTrack) track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void interpolateTransformTrack(double t, TransformTrack track) {
|
||||
HasLocalTransform target = track.getTarget();
|
||||
transform.set(target.getLocalTransform());
|
||||
track.getDataAtTime(t, transform);
|
||||
|
||||
if (collectTransformDelegate != null) {
|
||||
collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
|
||||
} else {
|
||||
this.collectTransform(target, transform, getTransitionWeight(), this);
|
||||
}
|
||||
}
|
||||
private void interpolateMorphTrack(double t, MorphTrack track) {
|
||||
Geometry target = track.getTarget();
|
||||
float[] weights = target.getMorphState();
|
||||
track.getDataAtTime(t, weights);
|
||||
target.setMorphState(weights);
|
||||
|
||||
// if (collectTransformDelegate != null) {
|
||||
// collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
|
||||
// } else {
|
||||
// this.collectTransform(target, transform, getTransitionWeight(), this);
|
||||
// }
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return clip.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<HasLocalTransform> getTargets() {
|
||||
List<HasLocalTransform> targets = new ArrayList<>(clip.getTracks().length);
|
||||
for (AnimTrack track : clip.getTracks()) {
|
||||
if (track instanceof TransformTrack) {
|
||||
targets.add(((TransformTrack) track).getTarget());
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) {
|
||||
if (weight == 1f) {
|
||||
target.setLocalTransform(t);
|
||||
} else {
|
||||
Transform tr = target.getLocalTransform();
|
||||
tr.interpolateTransforms(tr, t, weight);
|
||||
target.setLocalTransform(tr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.jme3.anim.tween.action;
|
||||
|
||||
public class LinearBlendSpace implements BlendSpace {
|
||||
|
||||
private BlendAction action;
|
||||
private float value;
|
||||
private float maxValue;
|
||||
private float minValue;
|
||||
private float step;
|
||||
|
||||
public LinearBlendSpace(float minValue, float maxValue) {
|
||||
this.maxValue = maxValue;
|
||||
this.minValue = minValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlendAction(BlendAction action) {
|
||||
this.action = action;
|
||||
Action[] actions = action.getActions();
|
||||
step = (maxValue - minValue) / (float) (actions.length - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getWeight() {
|
||||
Action[] actions = action.getActions();
|
||||
float lowStep = minValue, highStep = minValue;
|
||||
int lowIndex = 0, highIndex = 0;
|
||||
for (int i = 0; i < actions.length && highStep < value; i++) {
|
||||
lowStep = highStep;
|
||||
lowIndex = i;
|
||||
highStep += step;
|
||||
}
|
||||
highIndex = lowIndex + 1;
|
||||
|
||||
action.setFirstActiveIndex(lowIndex);
|
||||
action.setSecondActiveIndex(highIndex);
|
||||
|
||||
if (highStep == lowStep) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (value - lowStep) / (highStep - lowStep);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
package com.jme3.anim.util;
|
||||
|
||||
import com.jme3.anim.*;
|
||||
import com.jme3.animation.*;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class AnimMigrationUtils {
|
||||
|
||||
private static AnimControlVisitor animControlVisitor = new AnimControlVisitor();
|
||||
private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor();
|
||||
|
||||
|
||||
public static Spatial migrate(Spatial source) {
|
||||
Map<Skeleton, Armature> skeletonArmatureMap = new HashMap<>();
|
||||
animControlVisitor.setMappings(skeletonArmatureMap);
|
||||
source.depthFirstTraversal(animControlVisitor);
|
||||
skeletonControlVisitor.setMappings(skeletonArmatureMap);
|
||||
source.depthFirstTraversal(skeletonControlVisitor);
|
||||
return source;
|
||||
}
|
||||
|
||||
private static class AnimControlVisitor implements SceneGraphVisitor {
|
||||
|
||||
Map<Skeleton, Armature> skeletonArmatureMap;
|
||||
|
||||
@Override
|
||||
public void visit(Spatial spatial) {
|
||||
AnimControl control = spatial.getControl(AnimControl.class);
|
||||
if (control != null) {
|
||||
AnimComposer composer = new AnimComposer();
|
||||
Skeleton skeleton = control.getSkeleton();
|
||||
if (skeleton == null) {
|
||||
//only bone anim for now
|
||||
return;
|
||||
}
|
||||
|
||||
Joint[] joints = new Joint[skeleton.getBoneCount()];
|
||||
for (int i = 0; i < skeleton.getBoneCount(); i++) {
|
||||
Bone b = skeleton.getBone(i);
|
||||
Joint j = joints[i];
|
||||
if (j == null) {
|
||||
j = fromBone(b);
|
||||
joints[i] = j;
|
||||
}
|
||||
for (Bone bone : b.getChildren()) {
|
||||
int index = skeleton.getBoneIndex(bone);
|
||||
Joint joint = joints[index];
|
||||
if (joint == null) {
|
||||
joint = fromBone(bone);
|
||||
}
|
||||
j.addChild(joint);
|
||||
joints[index] = joint;
|
||||
}
|
||||
}
|
||||
|
||||
Armature armature = new Armature(joints);
|
||||
armature.saveBindPose();
|
||||
skeletonArmatureMap.put(skeleton, armature);
|
||||
|
||||
List<TransformTrack> tracks = new ArrayList<>();
|
||||
|
||||
for (String animName : control.getAnimationNames()) {
|
||||
tracks.clear();
|
||||
Animation anim = control.getAnim(animName);
|
||||
AnimClip clip = new AnimClip(animName);
|
||||
Joint[] staticJoints = new Joint[joints.length];
|
||||
|
||||
System.arraycopy(joints, 0, staticJoints, 0, joints.length);
|
||||
for (Track track : anim.getTracks()) {
|
||||
if (track instanceof BoneTrack) {
|
||||
BoneTrack boneTrack = (BoneTrack) track;
|
||||
int index = boneTrack.getTargetBoneIndex();
|
||||
Bone bone = skeleton.getBone(index);
|
||||
Joint joint = joints[index];
|
||||
TransformTrack jointTrack = fromBoneTrack(boneTrack, bone, joint);
|
||||
tracks.add(jointTrack);
|
||||
//this joint is animated let's remove it from the static joints
|
||||
staticJoints[index] = null;
|
||||
}
|
||||
//TODO spatial tracks , Effect tracks, Audio tracks
|
||||
}
|
||||
|
||||
for (int i = 0; i < staticJoints.length; i++) {
|
||||
padJointTracks(tracks, staticJoints[i]);
|
||||
}
|
||||
|
||||
clip.setTracks(tracks.toArray(new TransformTrack[tracks.size()]));
|
||||
|
||||
composer.addAnimClip(clip);
|
||||
}
|
||||
spatial.removeControl(control);
|
||||
spatial.addControl(composer);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMappings(Map<Skeleton, Armature> skeletonArmatureMap) {
|
||||
this.skeletonArmatureMap = skeletonArmatureMap;
|
||||
}
|
||||
}
|
||||
|
||||
public static void padJointTracks(List<TransformTrack> tracks, Joint staticJoint) {
|
||||
Joint j = staticJoint;
|
||||
if (j != null) {
|
||||
// joint has no track , we create one with the default pose
|
||||
float[] times = new float[]{0};
|
||||
Vector3f[] translations = new Vector3f[]{j.getLocalTranslation()};
|
||||
Quaternion[] rotations = new Quaternion[]{j.getLocalRotation()};
|
||||
Vector3f[] scales = new Vector3f[]{j.getLocalScale()};
|
||||
TransformTrack track = new TransformTrack(j, times, translations, rotations, scales);
|
||||
tracks.add(track);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SkeletonControlVisitor implements SceneGraphVisitor {
|
||||
|
||||
Map<Skeleton, Armature> skeletonArmatureMap;
|
||||
|
||||
@Override
|
||||
public void visit(Spatial spatial) {
|
||||
SkeletonControl control = spatial.getControl(SkeletonControl.class);
|
||||
if (control != null) {
|
||||
Armature armature = skeletonArmatureMap.get(control.getSkeleton());
|
||||
SkinningControl skinningControl = new SkinningControl(armature);
|
||||
Map<String, List<Spatial>> attachedSpatials = new HashMap<>();
|
||||
for (int i = 0; i < control.getSkeleton().getBoneCount(); i++) {
|
||||
Bone b = control.getSkeleton().getBone(i);
|
||||
Node n = control.getAttachmentsNode(b.getName());
|
||||
n.removeFromParent();
|
||||
if (!n.getChildren().isEmpty()) {
|
||||
attachedSpatials.put(b.getName(), n.getChildren());
|
||||
}
|
||||
}
|
||||
spatial.removeControl(control);
|
||||
spatial.addControl(skinningControl);
|
||||
for (String name : attachedSpatials.keySet()) {
|
||||
List<Spatial> spatials = attachedSpatials.get(name);
|
||||
for (Spatial child : spatials) {
|
||||
skinningControl.getAttachmentsNode(name).attachChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setMappings(Map<Skeleton, Armature> skeletonArmatureMap) {
|
||||
this.skeletonArmatureMap = skeletonArmatureMap;
|
||||
}
|
||||
}
|
||||
|
||||
public static TransformTrack fromBoneTrack(BoneTrack boneTrack, Bone bone, Joint joint) {
|
||||
float[] times = new float[boneTrack.getTimes().length];
|
||||
int length = times.length;
|
||||
System.arraycopy(boneTrack.getTimes(), 0, times, 0, length);
|
||||
//translation
|
||||
Vector3f[] translations = new Vector3f[length];
|
||||
if (boneTrack.getTranslations() != null) {
|
||||
for (int i = 0; i < boneTrack.getTranslations().length; i++) {
|
||||
Vector3f oldTrans = boneTrack.getTranslations()[i];
|
||||
Vector3f newTrans = new Vector3f();
|
||||
newTrans.set(bone.getBindPosition()).addLocal(oldTrans);
|
||||
translations[i] = newTrans;
|
||||
}
|
||||
}
|
||||
//rotation
|
||||
Quaternion[] rotations = new Quaternion[length];
|
||||
if (boneTrack.getRotations() != null) {
|
||||
for (int i = 0; i < boneTrack.getRotations().length; i++) {
|
||||
Quaternion oldRot = boneTrack.getRotations()[i];
|
||||
Quaternion newRot = new Quaternion();
|
||||
newRot.set(bone.getBindRotation()).multLocal(oldRot);
|
||||
rotations[i] = newRot;
|
||||
}
|
||||
}
|
||||
//scale
|
||||
Vector3f[] scales = new Vector3f[length];
|
||||
if (boneTrack.getScales() != null) {
|
||||
for (int i = 0; i < boneTrack.getScales().length; i++) {
|
||||
Vector3f oldScale = boneTrack.getScales()[i];
|
||||
Vector3f newScale = new Vector3f();
|
||||
newScale.set(bone.getBindScale()).multLocal(oldScale);
|
||||
scales[i] = newScale;
|
||||
}
|
||||
}
|
||||
TransformTrack t = new TransformTrack(joint, times, translations, rotations, scales);
|
||||
return t;
|
||||
}
|
||||
|
||||
private static Joint fromBone(Bone b) {
|
||||
Joint j = new Joint(b.getName());
|
||||
j.setLocalTranslation(b.getBindPosition());
|
||||
j.setLocalRotation(b.getBindRotation());
|
||||
j.setLocalScale(b.getBindScale());
|
||||
return j;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.jme3.anim.util;
|
||||
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
public interface HasLocalTransform extends Savable {
|
||||
public void setLocalTransform(Transform transform);
|
||||
|
||||
public Transform getLocalTransform();
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.jme3.anim.util;
|
||||
|
||||
import com.jme3.anim.Joint;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
/**
|
||||
* Implementations of this interface holds accumulated model transform of a Joint.
|
||||
* Implementation might choose different accumulation strategy.
|
||||
*/
|
||||
public interface JointModelTransform {
|
||||
|
||||
void updateModelTransform(Transform localTransform, Joint parent);
|
||||
|
||||
void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix);
|
||||
|
||||
void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent);
|
||||
|
||||
Transform getModelTransform();
|
||||
}
|
56
jme3-core/src/main/java/com/jme3/anim/util/Primitives.java
Normal file
56
jme3-core/src/main/java/com/jme3/anim/util/Primitives.java
Normal file
@ -0,0 +1,56 @@
|
||||
package com.jme3.anim.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* This is a guava method used in {@link com.jme3.anim.tween.Tweens} class.
|
||||
* Maybe we should just add guava as a dependency in the engine...
|
||||
* //TODO do something about this.
|
||||
*/
|
||||
public class Primitives {
|
||||
|
||||
/**
|
||||
* A map from primitive types to their corresponding wrapper types.
|
||||
*/
|
||||
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
|
||||
|
||||
static {
|
||||
Map<Class<?>, Class<?>> primToWrap = new HashMap<>(16);
|
||||
|
||||
primToWrap.put(boolean.class, Boolean.class);
|
||||
primToWrap.put(byte.class, Byte.class);
|
||||
primToWrap.put(char.class, Character.class);
|
||||
primToWrap.put(double.class, Double.class);
|
||||
primToWrap.put(float.class, Float.class);
|
||||
primToWrap.put(int.class, Integer.class);
|
||||
primToWrap.put(long.class, Long.class);
|
||||
primToWrap.put(short.class, Short.class);
|
||||
primToWrap.put(void.class, Void.class);
|
||||
|
||||
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise
|
||||
* returns {@code type} itself. Idempotent.
|
||||
* <p>
|
||||
* <pre>
|
||||
* wrap(int.class) == Integer.class
|
||||
* wrap(Integer.class) == Integer.class
|
||||
* wrap(String.class) == String.class
|
||||
* </pre>
|
||||
*/
|
||||
public static <T> Class<T> wrap(Class<T> type) {
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("type is null");
|
||||
}
|
||||
|
||||
// cast is safe: long.class and Long.class are both of type Class<Long>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE.get(type);
|
||||
return (wrapped == null) ? type : wrapped;
|
||||
}
|
||||
}
|
11
jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
Normal file
11
jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.jme3.anim.util;
|
||||
|
||||
import com.jme3.anim.tween.action.Action;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
public interface Weighted {
|
||||
|
||||
// public void setWeight(float weight);
|
||||
|
||||
public void setParentAction(Action action);
|
||||
}
|
@ -33,6 +33,7 @@ package com.jme3.animation;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
@ -46,6 +47,7 @@ import java.util.BitSet;
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
@Deprecated
|
||||
public final class AnimChannel {
|
||||
|
||||
private static final float DEFAULT_BLEND_TIME = 0.15f;
|
||||
|
@ -64,7 +64,9 @@ import java.util.Map;
|
||||
* 1) Morph/Pose animation
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
* @deprecated use {@link com.jme3.anim.AnimComposer}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable {
|
||||
|
||||
/**
|
||||
|
@ -37,6 +37,7 @@ package com.jme3.animation;
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
@Deprecated
|
||||
public interface AnimEventListener {
|
||||
|
||||
/**
|
||||
|
@ -37,13 +37,16 @@ import com.jme3.util.SafeArrayList;
|
||||
import com.jme3.util.TempVars;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* The animation class updates the animation target with the tracks of a given type.
|
||||
*
|
||||
* @author Kirill Vainer, Marcin Roguski (Kaelthas)
|
||||
* @deprecated use {@link com.jme3.anim.AnimClip}
|
||||
*/
|
||||
@Deprecated
|
||||
public class Animation implements Savable, Cloneable, JmeCloneable {
|
||||
|
||||
/**
|
||||
|
@ -32,15 +32,12 @@
|
||||
package com.jme3.animation;
|
||||
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.util.TempVars;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@ -62,6 +59,7 @@ import java.util.logging.Logger;
|
||||
*
|
||||
* @author Nehon
|
||||
*/
|
||||
@Deprecated
|
||||
public class AudioTrack implements ClonableTrack {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AudioTrack.class.getName());
|
||||
|
@ -67,7 +67,9 @@ import java.util.ArrayList;
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
* @author Rémy Bouquet
|
||||
* @deprecated use {@link com.jme3.anim.Joint}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class Bone implements Savable, JmeCloneable {
|
||||
|
||||
// Version #2: Changed naming of transforms as they were misleading
|
||||
@ -533,6 +535,16 @@ public final class Bone implements Savable, JmeCloneable {
|
||||
attachNode.setLocalRotation(modelRot);
|
||||
attachNode.setLocalScale(modelScale);
|
||||
|
||||
} else if (targetGeometry.isIgnoreTransform()) {
|
||||
/*
|
||||
* The animated meshes ignore transforms: match the world transform
|
||||
* of the attachments node to the bone's transform.
|
||||
*/
|
||||
attachNode.setLocalTranslation(modelPos);
|
||||
attachNode.setLocalRotation(modelRot);
|
||||
attachNode.setLocalScale(modelScale);
|
||||
attachNode.getLocalTransform().combineWithParent(attachNode.getParent().getWorldTransform().invert());
|
||||
|
||||
} else {
|
||||
Spatial loopSpatial = targetGeometry;
|
||||
Transform combined = new Transform(modelPos, modelRot, modelScale);
|
||||
|
@ -44,7 +44,9 @@ import java.util.BitSet;
|
||||
* Contains a list of transforms and times for each keyframe.
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
* @deprecated use {@link com.jme3.anim.JointTrack}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class BoneTrack implements JmeCloneable, Track {
|
||||
|
||||
/**
|
||||
|
@ -44,6 +44,7 @@ import com.jme3.util.clone.JmeCloneable;
|
||||
*
|
||||
* @author Nehon
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ClonableTrack extends Track, JmeCloneable {
|
||||
|
||||
/**
|
||||
|
@ -44,7 +44,7 @@ import java.util.Map;
|
||||
*/
|
||||
public abstract class CompactArray<T> implements JmeCloneable {
|
||||
|
||||
private Map<T, Integer> indexPool = new HashMap<T, Integer>();
|
||||
protected Map<T, Integer> indexPool = new HashMap<T, Integer>();
|
||||
protected int[] index;
|
||||
protected float[] array;
|
||||
private boolean invalid;
|
||||
@ -114,6 +114,10 @@ public abstract class CompactArray<T> implements JmeCloneable {
|
||||
indexPool.clear();
|
||||
}
|
||||
|
||||
protected void setInvalid(boolean invalid) {
|
||||
this.invalid = invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index
|
||||
* @param value
|
||||
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 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.animation;
|
||||
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Serialize and compress Float by indexing similar values
|
||||
* @author Lim, YongHoon
|
||||
*/
|
||||
public class CompactFloatArray extends CompactArray<Float> implements Savable {
|
||||
|
||||
/**
|
||||
* Creates a compact vector array
|
||||
*/
|
||||
public CompactFloatArray() {
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a compact vector array
|
||||
* @param dataArray the data array
|
||||
* @param index the indices
|
||||
*/
|
||||
public CompactFloatArray(float[] dataArray, int[] index) {
|
||||
super(dataArray, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final int getTupleSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Class<Float> getElementClass() {
|
||||
return Float.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
serialize();
|
||||
OutputCapsule out = ex.getCapsule(this);
|
||||
out.write(array, "array", null);
|
||||
out.write(index, "index", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule in = im.getCapsule(this);
|
||||
array = in.readFloatArray("array", null);
|
||||
index = in.readIntArray("index", null);
|
||||
}
|
||||
|
||||
public void fill(int startIndex, float[] store ){
|
||||
for (int i = 0; i < store.length; i++) {
|
||||
store[i] = get(startIndex + i, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serialize(int i, Float data) {
|
||||
array[i] = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Float deserialize(int i, Float store) {
|
||||
return array[i];
|
||||
}
|
||||
}
|
@ -32,10 +32,7 @@
|
||||
package com.jme3.animation;
|
||||
|
||||
import com.jme3.effect.ParticleEmitter;
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.Node;
|
||||
@ -67,6 +64,7 @@ import java.util.logging.Logger;
|
||||
*
|
||||
* @author Nehon
|
||||
*/
|
||||
@Deprecated
|
||||
public class EffectTrack implements ClonableTrack {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(EffectTrack.class.getName());
|
||||
@ -130,15 +128,17 @@ public class EffectTrack implements ClonableTrack {
|
||||
@Override
|
||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//Anim listener that stops the Emmitter when the animation is finished or changed.
|
||||
private class OnEndListener implements AnimEventListener {
|
||||
|
||||
@Override
|
||||
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
|
||||
}
|
||||
}
|
||||
@ -188,6 +188,7 @@ public class EffectTrack implements ClonableTrack {
|
||||
* @see Track#setTime(float, float, com.jme3.animation.AnimControl,
|
||||
* com.jme3.animation.AnimChannel, com.jme3.util.TempVars)
|
||||
*/
|
||||
@Override
|
||||
public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
|
||||
|
||||
if (time >= length) {
|
||||
@ -233,6 +234,7 @@ public class EffectTrack implements ClonableTrack {
|
||||
*
|
||||
* @return length of the track
|
||||
*/
|
||||
@Override
|
||||
public float getLength() {
|
||||
return length;
|
||||
}
|
||||
@ -325,6 +327,7 @@ public class EffectTrack implements ClonableTrack {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanUp() {
|
||||
TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo");
|
||||
t.getTracks().remove(this);
|
||||
@ -413,6 +416,7 @@ public class EffectTrack implements ClonableTrack {
|
||||
* @param ex exporter
|
||||
* @throws IOException exception
|
||||
*/
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule out = ex.getCapsule(this);
|
||||
//reset the particle emission rate on the emitter before saving.
|
||||
@ -431,6 +435,7 @@ public class EffectTrack implements ClonableTrack {
|
||||
* @param im importer
|
||||
* @throws IOException Exception
|
||||
*/
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule in = im.getCapsule(this);
|
||||
this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0);
|
||||
|
@ -35,6 +35,7 @@ package com.jme3.animation;
|
||||
* <code>LoopMode</code> determines how animations repeat, or if they
|
||||
* do not repeat.
|
||||
*/
|
||||
@Deprecated
|
||||
public enum LoopMode {
|
||||
/**
|
||||
* The animation will play repeatedly, when it reaches the end
|
||||
|
@ -34,12 +34,14 @@ package com.jme3.animation;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
/**
|
||||
* A pose is a list of offsets that say where a mesh vertices should be for this pose.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class Pose implements Savable, Cloneable {
|
||||
|
||||
private String name;
|
||||
|
@ -31,11 +31,13 @@
|
||||
*/
|
||||
package com.jme3.animation;
|
||||
|
||||
import com.jme3.anim.Armature;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.util.TempVars;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -46,7 +48,9 @@ import java.util.List;
|
||||
* animated matrixes.
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
* @deprecated use {@link Armature}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class Skeleton implements Savable, JmeCloneable {
|
||||
|
||||
private Bone[] rootBones;
|
||||
|
@ -31,6 +31,7 @@
|
||||
*/
|
||||
package com.jme3.animation;
|
||||
|
||||
import com.jme3.anim.SkinningControl;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.material.MatParamOverride;
|
||||
import com.jme3.math.FastMath;
|
||||
@ -57,7 +58,9 @@ import java.util.logging.Logger;
|
||||
* the mesh
|
||||
*
|
||||
* @author Rémy Bouquet Based on AnimControl by Kirill Vainer
|
||||
* @deprecated use {@link SkinningControl}
|
||||
*/
|
||||
@Deprecated
|
||||
public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable {
|
||||
|
||||
/**
|
||||
|
@ -31,10 +31,7 @@
|
||||
*/
|
||||
package com.jme3.animation;
|
||||
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
@ -48,6 +45,7 @@ import java.io.IOException;
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
@Deprecated
|
||||
public class SpatialTrack implements JmeCloneable, Track {
|
||||
|
||||
/**
|
||||
|
@ -34,6 +34,7 @@ package com.jme3.animation;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
@Deprecated
|
||||
public interface Track extends Savable, Cloneable {
|
||||
|
||||
/**
|
||||
|
@ -31,13 +31,10 @@
|
||||
*/
|
||||
package com.jme3.animation;
|
||||
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -50,6 +47,7 @@ import java.util.ArrayList;
|
||||
*
|
||||
* @author Nehon
|
||||
*/
|
||||
@Deprecated
|
||||
public class TrackInfo implements Savable, JmeCloneable {
|
||||
|
||||
ArrayList<Track> tracks = new ArrayList<Track>();
|
||||
|
@ -34,6 +34,7 @@ package com.jme3.bounding;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Plane;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.util.TempVars;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
@ -107,6 +108,15 @@ public final class Intersection {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean intersect(Camera camera, Vector3f center,float radius){
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
if (camera.getWorldPlane(i).pseudoDistance(center) <= -radius) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, )
|
||||
// private boolean axisTestX01(float a, float b, float fa, float fb,
|
||||
// Vector3f center, Vector3f ext,
|
||||
|
@ -31,24 +31,16 @@
|
||||
*/
|
||||
package com.jme3.cinematic.events;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.LoopMode;
|
||||
import com.jme3.animation.*;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.cinematic.Cinematic;
|
||||
import com.jme3.cinematic.PlayState;
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
@ -60,6 +52,7 @@ import java.util.logging.Logger;
|
||||
*
|
||||
* @author Nehon
|
||||
*/
|
||||
@Deprecated
|
||||
public class AnimationEvent extends AbstractCinematicEvent {
|
||||
|
||||
// Version #2: directly keeping track on the model instead of trying to retrieve
|
||||
|
@ -38,14 +38,9 @@ import com.jme3.environment.util.EnvMapUtils;
|
||||
import com.jme3.light.LightProbe;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.*;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Texture2D;
|
||||
import com.jme3.texture.TextureCubeMap;
|
||||
import com.jme3.texture.*;
|
||||
import com.jme3.texture.image.ColorSpace;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.MipMapGenerator;
|
||||
@ -119,7 +114,7 @@ public class EnvironmentCamera extends BaseAppState {
|
||||
private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
|
||||
|
||||
/**
|
||||
* Creates an EnvironmentCamera with a size of 128
|
||||
* Creates an EnvironmentCamera with a size of 256
|
||||
*/
|
||||
public EnvironmentCamera() {
|
||||
}
|
||||
@ -322,7 +317,7 @@ public class EnvironmentCamera extends BaseAppState {
|
||||
final Camera offCamera = new Camera(mapSize, mapSize);
|
||||
offCamera.setLocation(worldPos);
|
||||
offCamera.setAxes(axisX, axisY, axisZ);
|
||||
offCamera.setFrustumPerspective(90f, 1f, 1, 1000);
|
||||
offCamera.setFrustumPerspective(90f, 1f, 0.1f, 1000);
|
||||
offCamera.setLocation(position);
|
||||
return offCamera;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
package com.jme3.environment;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.environment.generation.*;
|
||||
import com.jme3.environment.util.EnvMapUtils;
|
||||
import com.jme3.light.LightProbe;
|
||||
@ -204,6 +205,26 @@ public class LightProbeFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For debuging porpose only
|
||||
* Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
|
||||
*
|
||||
* @param manager the asset manager
|
||||
* @return a debug node
|
||||
*/
|
||||
public static Node getDebugGui(AssetManager manager, LightProbe probe) {
|
||||
if (!probe.isReady()) {
|
||||
throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
|
||||
}
|
||||
|
||||
Node debugNode = new Node("debug gui probe");
|
||||
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager);
|
||||
debugNode.attachChild(debugPfemCm);
|
||||
debugPfemCm.setLocalTranslation(520, 0, 0);
|
||||
|
||||
return debugNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* An inner class to keep the state of a generation process
|
||||
*/
|
||||
|
@ -34,9 +34,8 @@ package com.jme3.environment.util;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.light.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.light.LightProbe;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
@ -68,7 +67,7 @@ public class LightsDebugState extends BaseAppState {
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
debugNode = new Node("Environment debug Node");
|
||||
Sphere s = new Sphere(16, 16, 1);
|
||||
Sphere s = new Sphere(16, 16, 0.15f);
|
||||
debugGeom = new Geometry("debugEnvProbe", s);
|
||||
debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
|
||||
debugGeom.setMaterial(debugMaterial);
|
||||
@ -80,6 +79,16 @@ public class LightsDebugState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if(!isEnabled()){
|
||||
return;
|
||||
}
|
||||
updateLights(scene);
|
||||
debugNode.updateLogicalState(tpf);
|
||||
debugNode.updateGeometricState();
|
||||
cleanProbes();
|
||||
}
|
||||
|
||||
public void updateLights(Spatial scene) {
|
||||
for (Light light : scene.getWorldLightList()) {
|
||||
switch (light.getType()) {
|
||||
|
||||
@ -101,16 +110,18 @@ public class LightsDebugState extends BaseAppState {
|
||||
m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
|
||||
}
|
||||
n.setLocalTranslation(probe.getPosition());
|
||||
n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
|
||||
n.getChild(1).setLocalScale(probe.getArea().getRadius());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
debugNode.updateLogicalState(tpf);
|
||||
debugNode.updateGeometricState();
|
||||
cleanProbes();
|
||||
|
||||
if( scene instanceof Node){
|
||||
Node n = (Node)scene;
|
||||
for (Spatial spatial : n.getChildren()) {
|
||||
updateLights(spatial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,6 +149,9 @@ public class LightsDebugState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void render(RenderManager rm) {
|
||||
if(!isEnabled()){
|
||||
return;
|
||||
}
|
||||
rm.renderScene(debugNode, getApplication().getViewPort());
|
||||
}
|
||||
|
||||
|
@ -42,11 +42,11 @@ import java.util.HashSet;
|
||||
public final class DefaultLightFilter implements LightFilter {
|
||||
|
||||
private Camera camera;
|
||||
private final HashSet<Light> processedLights = new HashSet<>();
|
||||
private final LightProbeBlendingStrategy probeBlendStrat;
|
||||
private final HashSet<Light> processedLights = new HashSet<Light>();
|
||||
private LightProbeBlendingStrategy probeBlendStrat;
|
||||
|
||||
public DefaultLightFilter() {
|
||||
probeBlendStrat = new BasicProbeBlendingStrategy();
|
||||
probeBlendStrat = new WeightedProbeBlendingStrategy();
|
||||
}
|
||||
|
||||
public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) {
|
||||
@ -115,4 +115,8 @@ public final class DefaultLightFilter implements LightFilter {
|
||||
}
|
||||
}
|
||||
|
||||
public void setLightProbeBlendingStrategy(LightProbeBlendingStrategy strategy){
|
||||
probeBlendStrat = strategy;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,24 +31,16 @@
|
||||
*/
|
||||
package com.jme3.light;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.bounding.BoundingVolume;
|
||||
import com.jme3.bounding.*;
|
||||
import com.jme3.environment.EnvironmentCamera;
|
||||
import com.jme3.environment.LightProbeFactory;
|
||||
import com.jme3.environment.util.EnvMapUtils;
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.texture.TextureCubeMap;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@ -65,9 +57,9 @@ import java.util.logging.Logger;
|
||||
* To compute them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
|
||||
* and {@link EnvironmentCamera}.
|
||||
*
|
||||
* The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported).
|
||||
* The light probe has an area of effect centered on its position. It can have a Spherical area or an Oriented Box area
|
||||
*
|
||||
* A LightProbe will only be taken into account when it's marked as ready.
|
||||
* A LightProbe will only be taken into account when it's marked as ready and enabled.
|
||||
* A light probe is ready when it has valid environment map data set.
|
||||
* Note that you should never call setReady yourself.
|
||||
*
|
||||
@ -78,15 +70,20 @@ import java.util.logging.Logger;
|
||||
public class LightProbe extends Light implements Savable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(LightProbe.class.getName());
|
||||
public static final Matrix4f FALLBACK_MATRIX = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1);
|
||||
|
||||
private Vector3f[] shCoeffs;
|
||||
private TextureCubeMap prefilteredEnvMap;
|
||||
private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
|
||||
private ProbeArea area = new SphereProbeArea(Vector3f.ZERO, 1.0f);
|
||||
private boolean ready = false;
|
||||
private Vector3f position = new Vector3f();
|
||||
private Node debugNode;
|
||||
private int nbMipMaps;
|
||||
|
||||
public enum AreaType{
|
||||
Spherical,
|
||||
OrientedBox
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty constructor used for serialization.
|
||||
* You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead
|
||||
@ -111,6 +108,52 @@ public class LightProbe extends Light implements Savable {
|
||||
this.prefilteredEnvMap = prefileteredEnvMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data to send to the shader.
|
||||
* This is a column major matrix that is not a classic transform matrix, it's laid out in a particular way
|
||||
// 3x3 rot mat|
|
||||
// 0 1 2 | 3
|
||||
// 0 | ax bx cx | px | )
|
||||
// 1 | ay by cy | py | probe position
|
||||
// 2 | az bz cz | pz | )
|
||||
// --|----------|
|
||||
// 3 | sx sy sz sp | -> 1/probe radius + nbMipMaps
|
||||
// --scale--
|
||||
* <p>
|
||||
* (ax, ay, az) is the pitch rotation axis
|
||||
* (bx, by, bz) is the yaw rotation axis
|
||||
* (cx, cy, cz) is the roll rotation axis
|
||||
* Like in a standard 3x3 rotation matrix.
|
||||
* It's also the valid rotation matrix of the probe in world space.
|
||||
* Note that for the Spherical Probe area this part is a 3x3 identity matrix.
|
||||
* <p>
|
||||
* (px, py, pz) is the position of the center of the probe in world space
|
||||
* Like in a valid 4x4 transform matrix.
|
||||
* <p>
|
||||
* (sx, sy, sy) is the extent of the probe ( the scale )
|
||||
* In a standard transform matrix the scale is applied to the rotation matrix part.
|
||||
* In the shader we need the rotation and the scale to be separated, doing this avoid to extract
|
||||
* the scale from a classic transform matrix in the shader
|
||||
* <p>
|
||||
* (sp) is a special entry, it contains the packed number of mip maps of the probe and the inverse radius for the probe.
|
||||
* since the inverse radius in lower than 1, it's packed in the decimal part of the float.
|
||||
* The number of mip maps is packed in the integer part of the float.
|
||||
* (ie: for 6 mip maps and a radius of 3, sp= 6.3333333)
|
||||
* <p>
|
||||
* The radius is obvious for a SphereProbeArea,
|
||||
* but in the case of a OrientedBoxProbeArea it's the max of the extent vector's components.
|
||||
*/
|
||||
public Matrix4f getUniformMatrix(){
|
||||
|
||||
Matrix4f mat = area.getUniformMatrix();
|
||||
|
||||
// setting the (sp) entry of the matrix
|
||||
mat.m33 = nbMipMaps + 1f / area.getRadius();
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
super.write(ex);
|
||||
@ -118,7 +161,7 @@ public class LightProbe extends Light implements Savable {
|
||||
oc.write(shCoeffs, "shCoeffs", null);
|
||||
oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
|
||||
oc.write(position, "position", null);
|
||||
oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
|
||||
oc.write(area, "area", new SphereProbeArea(Vector3f.ZERO, 1.0f));
|
||||
oc.write(ready, "ready", false);
|
||||
oc.write(nbMipMaps, "nbMipMaps", 0);
|
||||
}
|
||||
@ -130,7 +173,13 @@ public class LightProbe extends Light implements Savable {
|
||||
|
||||
prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
|
||||
position = (Vector3f) ic.readSavable("position", null);
|
||||
bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
|
||||
area = (ProbeArea)ic.readSavable("area", null);
|
||||
if(area == null) {
|
||||
// retro compat
|
||||
BoundingSphere bounds = (BoundingSphere) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
|
||||
area = new SphereProbeArea(bounds.getCenter(), bounds.getRadius());
|
||||
}
|
||||
area.setCenter(position);
|
||||
nbMipMaps = ic.readInt("nbMipMaps", 0);
|
||||
ready = ic.readBoolean("ready", false);
|
||||
|
||||
@ -146,12 +195,15 @@ public class LightProbe extends Light implements Savable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns the bounding volume of this LightProbe
|
||||
* @return a bounding volume.
|
||||
* @deprecated use {@link LightProbe#getArea()}
|
||||
*/
|
||||
@Deprecated
|
||||
public BoundingVolume getBounds() {
|
||||
return bounds;
|
||||
return new BoundingSphere(((SphereProbeArea)area).getRadius(), ((SphereProbeArea)area).getCenter());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,12 +211,33 @@ public class LightProbe extends Light implements Savable {
|
||||
* Note that for now only BoundingSphere is supported and this method will
|
||||
* throw an UnsupportedOperationException with any other BoundingVolume type
|
||||
* @param bounds the bounds of the LightProbe
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public void setBounds(BoundingVolume bounds) {
|
||||
if( bounds.getType()!= BoundingVolume.Type.Sphere){
|
||||
throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe");
|
||||
}
|
||||
this.bounds = bounds;
|
||||
|
||||
public ProbeArea getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
public void setAreaType(AreaType type){
|
||||
switch (type){
|
||||
case Spherical:
|
||||
area = new SphereProbeArea(Vector3f.ZERO, 1.0f);
|
||||
break;
|
||||
case OrientedBox:
|
||||
area = new OrientedBoxProbeArea(new Transform());
|
||||
area.setCenter(position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public AreaType getAreaType(){
|
||||
if(area instanceof SphereProbeArea){
|
||||
return AreaType.Spherical;
|
||||
}
|
||||
return AreaType.OrientedBox;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,27 +259,6 @@ public class LightProbe extends Light implements Savable {
|
||||
this.ready = ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* For debuging porpose only
|
||||
* Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
|
||||
*
|
||||
* @param manager the asset manager
|
||||
* @return a debug node
|
||||
*/
|
||||
public Node getDebugGui(AssetManager manager) {
|
||||
if (!ready) {
|
||||
throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
|
||||
}
|
||||
if (debugNode == null) {
|
||||
debugNode = new Node("debug gui probe");
|
||||
Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
|
||||
debugNode.attachChild(debugPfemCm);
|
||||
debugPfemCm.setLocalTranslation(520, 0, 0);
|
||||
}
|
||||
|
||||
return debugNode;
|
||||
}
|
||||
|
||||
public Vector3f[] getShCoeffs() {
|
||||
return shCoeffs;
|
||||
}
|
||||
@ -229,7 +281,7 @@ public class LightProbe extends Light implements Savable {
|
||||
*/
|
||||
public void setPosition(Vector3f position) {
|
||||
this.position.set(position);
|
||||
getBounds().setCenter(position);
|
||||
area.setCenter(position);
|
||||
}
|
||||
|
||||
public int getNbMipMaps() {
|
||||
@ -242,12 +294,17 @@ public class LightProbe extends Light implements Savable {
|
||||
|
||||
@Override
|
||||
public boolean intersectsBox(BoundingBox box, TempVars vars) {
|
||||
return getBounds().intersectsBoundingBox(box);
|
||||
return area.intersectsBox(box, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsFrustum(Camera camera, TempVars vars) {
|
||||
return camera.contains(bounds) != Camera.FrustumIntersect.Outside;
|
||||
return area.intersectsFrustum(camera, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
|
||||
return area.intersectsSphere(sphere, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -267,14 +324,8 @@ public class LightProbe extends Light implements Savable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Light Probe : " + name + " at " + position + " / " + bounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
|
||||
return getBounds().intersectsSphere(sphere);
|
||||
return "Light Probe : " + name + " at " + position + " / " + area;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2015 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.light;
|
||||
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.post.SceneProcessor;
|
||||
import com.jme3.profile.AppProfiler;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.util.TempVars;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* this processor allows to blend several light probes maps together according to a Point of Interest.
|
||||
* This is all based on this article by Sebastien lagarde
|
||||
* https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
|
||||
* @author Nehon
|
||||
*/
|
||||
public class LightProbeBlendingProcessor implements SceneProcessor {
|
||||
|
||||
private ViewPort viewPort;
|
||||
private LightFilter prevFilter;
|
||||
private RenderManager renderManager;
|
||||
private LightProbe probe = new LightProbe();
|
||||
private Spatial poi;
|
||||
private AppProfiler prof;
|
||||
|
||||
public LightProbeBlendingProcessor(Spatial poi) {
|
||||
this.poi = poi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(RenderManager rm, ViewPort vp) {
|
||||
viewPort = vp;
|
||||
renderManager = rm;
|
||||
prevFilter = rm.getLightFilter();
|
||||
rm.setLightFilter(new PoiLightProbeLightFilter(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reshape(ViewPort vp, int w, int h) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return viewPort != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preFrame(float tpf) {
|
||||
|
||||
}
|
||||
|
||||
/** 1. For POI take a spatial in the constructor and make all calculation against its world pos
|
||||
* - Alternatively compute an arbitrary POI by casting rays from the camera
|
||||
* (one in the center and one for each corner and take the median point)
|
||||
* 2. Take the 4 most weighted probes for default. Maybe allow the user to change this
|
||||
* 3. For the inner influence radius take half of the radius for a start we'll see then how to change this.
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void postQueue(RenderQueue rq) {
|
||||
List<BlendFactor> blendFactors = new ArrayList<BlendFactor>();
|
||||
float sumBlendFactors = computeBlendFactors(blendFactors);
|
||||
|
||||
//Sort blend factors according to their weight
|
||||
Collections.sort(blendFactors);
|
||||
|
||||
//normalize blend factors;
|
||||
float normalizer = 1f / sumBlendFactors;
|
||||
for (BlendFactor blendFactor : blendFactors) {
|
||||
blendFactor.ndf *= normalizer;
|
||||
// System.err.println(blendFactor);
|
||||
}
|
||||
|
||||
|
||||
//for now just pick the first probe.
|
||||
if(!blendFactors.isEmpty()){
|
||||
probe = blendFactors.get(0).lightProbe;
|
||||
}else{
|
||||
probe = null;
|
||||
}
|
||||
}
|
||||
|
||||
private float computeBlendFactors(List<BlendFactor> blendFactors) {
|
||||
float sumBlendFactors = 0;
|
||||
for (Spatial scene : viewPort.getScenes()) {
|
||||
for (Light light : scene.getWorldLightList()) {
|
||||
if(light.getType() == Light.Type.Probe){
|
||||
LightProbe p = (LightProbe)light;
|
||||
TempVars vars = TempVars.get();
|
||||
boolean intersect = p.intersectsFrustum(viewPort.getCamera(), vars);
|
||||
vars.release();
|
||||
//check if the probe is inside the camera frustum
|
||||
if(intersect){
|
||||
|
||||
//is the poi inside the bounds of this probe
|
||||
if(poi.getWorldBound().intersects(p.getBounds())){
|
||||
|
||||
//computing the distance as we need it to check if th epoi in in the inner radius and later to compute the weight
|
||||
float outerRadius = ((BoundingSphere)p.getBounds()).getRadius();
|
||||
float innerRadius = outerRadius * 0.5f;
|
||||
float distance = p.getBounds().getCenter().distance(poi.getWorldTranslation());
|
||||
|
||||
// if the poi in inside the inner range of this probe, then this probe is the only one that matters.
|
||||
if( distance < innerRadius ){
|
||||
blendFactors.clear();
|
||||
blendFactors.add(new BlendFactor(p, 1.0f));
|
||||
return 1.0f;
|
||||
}
|
||||
//else we need to compute the weight of this probe and collect it for blending
|
||||
float ndf = (distance - innerRadius) / (outerRadius - innerRadius);
|
||||
sumBlendFactors += ndf;
|
||||
blendFactors.add(new BlendFactor(p, ndf));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sumBlendFactors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postFrame(FrameBuffer out) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
viewPort = null;
|
||||
renderManager.setLightFilter(prevFilter);
|
||||
}
|
||||
|
||||
public void populateProbe(LightList lightList){
|
||||
if(probe != null && probe.isReady()){
|
||||
lightList.add(probe);
|
||||
}
|
||||
}
|
||||
|
||||
public Spatial getPoi() {
|
||||
return poi;
|
||||
}
|
||||
|
||||
public void setPoi(Spatial poi) {
|
||||
this.poi = poi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProfiler(AppProfiler profiler) {
|
||||
this.prof = profiler;
|
||||
}
|
||||
|
||||
private class BlendFactor implements Comparable<BlendFactor>{
|
||||
|
||||
LightProbe lightProbe;
|
||||
float ndf;
|
||||
|
||||
public BlendFactor(LightProbe lightProbe, float ndf) {
|
||||
this.lightProbe = lightProbe;
|
||||
this.ndf = ndf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlendFactor{" + "lightProbe=" + lightProbe + ", ndf=" + ndf + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(BlendFactor o) {
|
||||
if(o.ndf > ndf){
|
||||
return -1;
|
||||
}else if(o.ndf < ndf){
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
254
jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java
Normal file
254
jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java
Normal file
@ -0,0 +1,254 @@
|
||||
package com.jme3.light;
|
||||
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class OrientedBoxProbeArea implements ProbeArea {
|
||||
private Transform transform = new Transform();
|
||||
|
||||
/**
|
||||
* @see LightProbe#getUniformMatrix()
|
||||
* for this Area type, the matrix is updated when the probe is transformed,
|
||||
* and its data is used for bound checks in the light culling process.
|
||||
*/
|
||||
private Matrix4f uniformMatrix = new Matrix4f();
|
||||
|
||||
public OrientedBoxProbeArea() {
|
||||
}
|
||||
|
||||
public OrientedBoxProbeArea(Transform transform) {
|
||||
transform.set(transform);
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsBox(BoundingBox box, TempVars vars) {
|
||||
|
||||
Vector3f axis1 = getScaledAxis(0, vars.vect1);
|
||||
Vector3f axis2 = getScaledAxis(1, vars.vect2);
|
||||
Vector3f axis3 = getScaledAxis(2, vars.vect3);
|
||||
|
||||
Vector3f tn = vars.vect4;
|
||||
Plane p = vars.plane;
|
||||
Vector3f c = box.getCenter();
|
||||
|
||||
p.setNormal(0, 0, -1);
|
||||
p.setConstant(-(c.z + box.getZExtent()));
|
||||
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
|
||||
|
||||
p.setNormal(0, 0, 1);
|
||||
p.setConstant(c.z - box.getZExtent());
|
||||
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
|
||||
|
||||
|
||||
p.setNormal(0, -1, 0);
|
||||
p.setConstant(-(c.y + box.getYExtent()));
|
||||
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
|
||||
|
||||
p.setNormal(0, 1, 0);
|
||||
p.setConstant(c.y - box.getYExtent());
|
||||
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
|
||||
|
||||
p.setNormal(-1, 0, 0);
|
||||
p.setConstant(-(c.x + box.getXExtent()));
|
||||
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
|
||||
|
||||
p.setNormal(1, 0, 0);
|
||||
p.setConstant(c.x - box.getXExtent());
|
||||
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getRadius() {
|
||||
return Math.max(Math.max(transform.getScale().x, transform.getScale().y), transform.getScale().z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadius(float radius) {
|
||||
transform.setScale(radius, radius, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
|
||||
|
||||
Vector3f closestPoint = getClosestPoint(vars, sphere.getCenter());
|
||||
// check if the point intersects with the sphere bound
|
||||
if (sphere.intersects(closestPoint)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsFrustum(Camera camera, TempVars vars) {
|
||||
|
||||
// extract the scaled axis
|
||||
// this allows a small optimization.
|
||||
Vector3f axis1 = getScaledAxis(0, vars.vect1);
|
||||
Vector3f axis2 = getScaledAxis(1, vars.vect2);
|
||||
Vector3f axis3 = getScaledAxis(2, vars.vect3);
|
||||
|
||||
Vector3f tn = vars.vect4;
|
||||
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
Plane p = camera.getWorldPlane(i);
|
||||
if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Vector3f getScaledAxis(int index, Vector3f store) {
|
||||
Matrix4f u = uniformMatrix;
|
||||
float x = 0, y = 0, z = 0, s = 1;
|
||||
switch (index) {
|
||||
case 0:
|
||||
x = u.m00;
|
||||
y = u.m10;
|
||||
z = u.m20;
|
||||
s = u.m30;
|
||||
break;
|
||||
case 1:
|
||||
x = u.m01;
|
||||
y = u.m11;
|
||||
z = u.m21;
|
||||
s = u.m31;
|
||||
break;
|
||||
case 2:
|
||||
x = u.m02;
|
||||
y = u.m12;
|
||||
z = u.m22;
|
||||
s = u.m32;
|
||||
}
|
||||
return store.set(x, y, z).multLocal(s);
|
||||
}
|
||||
|
||||
private boolean insidePlane(Plane p, Vector3f axis1, Vector3f axis2, Vector3f axis3, Vector3f tn) {
|
||||
// transform the plane normal in the box local space.
|
||||
tn.set(axis1.dot(p.getNormal()), axis2.dot(p.getNormal()), axis3.dot(p.getNormal()));
|
||||
|
||||
// distance check
|
||||
float radius = FastMath.abs(tn.x) +
|
||||
FastMath.abs(tn.y) +
|
||||
FastMath.abs(tn.z);
|
||||
|
||||
float distance = p.pseudoDistance(transform.getTranslation());
|
||||
|
||||
if (distance < -radius) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Vector3f getClosestPoint(TempVars vars, Vector3f point) {
|
||||
// non normalized direction
|
||||
Vector3f dir = vars.vect2.set(point).subtractLocal(transform.getTranslation());
|
||||
// initialize the closest point with box center
|
||||
Vector3f closestPoint = vars.vect3.set(transform.getTranslation());
|
||||
|
||||
//store extent in an array
|
||||
float[] r = vars.fWdU;
|
||||
r[0] = transform.getScale().x;
|
||||
r[1] = transform.getScale().y;
|
||||
r[2] = transform.getScale().z;
|
||||
|
||||
// computing closest point to sphere center
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// extract the axis from the 3x3 matrix
|
||||
Vector3f axis = getScaledAxis(i, vars.vect1);
|
||||
// nomalize (here we just divide by the extent
|
||||
axis.divideLocal(r[i]);
|
||||
// distance to the closest point on this axis.
|
||||
float d = FastMath.clamp(dir.dot(axis), -r[i], r[i]);
|
||||
closestPoint.addLocal(vars.vect4.set(axis).multLocal(d));
|
||||
}
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
private void updateMatrix() {
|
||||
TempVars vars = TempVars.get();
|
||||
Matrix3f r = vars.tempMat3;
|
||||
Matrix4f u = uniformMatrix;
|
||||
transform.getRotation().toRotationMatrix(r);
|
||||
|
||||
u.m00 = r.get(0,0);
|
||||
u.m10 = r.get(1,0);
|
||||
u.m20 = r.get(2,0);
|
||||
u.m01 = r.get(0,1);
|
||||
u.m11 = r.get(1,1);
|
||||
u.m21 = r.get(2,1);
|
||||
u.m02 = r.get(0,2);
|
||||
u.m12 = r.get(1,2);
|
||||
u.m22 = r.get(2,2);
|
||||
|
||||
//scale
|
||||
u.m30 = transform.getScale().x;
|
||||
u.m31 = transform.getScale().y;
|
||||
u.m32 = transform.getScale().z;
|
||||
|
||||
//position
|
||||
u.m03 = transform.getTranslation().x;
|
||||
u.m13 = transform.getTranslation().y;
|
||||
u.m23 = transform.getTranslation().z;
|
||||
|
||||
vars.release();
|
||||
}
|
||||
|
||||
public Matrix4f getUniformMatrix() {
|
||||
return uniformMatrix;
|
||||
}
|
||||
|
||||
public Vector3f getExtent() {
|
||||
return transform.getScale();
|
||||
}
|
||||
|
||||
public void setExtent(Vector3f extent) {
|
||||
transform.setScale(extent);
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
public Vector3f getCenter() {
|
||||
return transform.getTranslation();
|
||||
}
|
||||
|
||||
public void setCenter(Vector3f center) {
|
||||
transform.setTranslation(center);
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
public Quaternion getRotation() {
|
||||
return transform.getRotation();
|
||||
}
|
||||
|
||||
public void setRotation(Quaternion rotation) {
|
||||
transform.setRotation(rotation);
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OrientedBoxProbeArea clone() throws CloneNotSupportedException {
|
||||
return new OrientedBoxProbeArea(transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
OutputCapsule oc = e.getCapsule(this);
|
||||
oc.write(transform, "transform", new Transform());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter i) throws IOException {
|
||||
InputCapsule ic = i.getCapsule(this);
|
||||
transform = (Transform) ic.readSavable("transform", new Transform());
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2015 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.light;
|
||||
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.bounding.BoundingVolume;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.util.TempVars;
|
||||
import java.util.HashSet;
|
||||
|
||||
public final class PoiLightProbeLightFilter implements LightFilter {
|
||||
|
||||
private Camera camera;
|
||||
private final HashSet<Light> processedLights = new HashSet<Light>();
|
||||
private final LightProbeBlendingProcessor processor;
|
||||
|
||||
public PoiLightProbeLightFilter(LightProbeBlendingProcessor processor) {
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCamera(Camera camera) {
|
||||
this.camera = camera;
|
||||
for (Light light : processedLights) {
|
||||
light.frustumCheckNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filterLights(Geometry geometry, LightList filteredLightList) {
|
||||
TempVars vars = TempVars.get();
|
||||
try {
|
||||
LightList worldLights = geometry.getWorldLightList();
|
||||
|
||||
for (int i = 0; i < worldLights.size(); i++) {
|
||||
Light light = worldLights.get(i);
|
||||
|
||||
if (light.getType() == Light.Type.Probe) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (light.frustumCheckNeeded) {
|
||||
processedLights.add(light);
|
||||
light.frustumCheckNeeded = false;
|
||||
light.intersectsFrustum = light.intersectsFrustum(camera, vars);
|
||||
}
|
||||
|
||||
if (!light.intersectsFrustum) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BoundingVolume bv = geometry.getWorldBound();
|
||||
|
||||
if (bv instanceof BoundingBox) {
|
||||
if (!light.intersectsBox((BoundingBox) bv, vars)) {
|
||||
continue;
|
||||
}
|
||||
} else if (bv instanceof BoundingSphere) {
|
||||
if (!Float.isInfinite(((BoundingSphere) bv).getRadius())) {
|
||||
if (!light.intersectsSphere((BoundingSphere) bv, vars)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filteredLightList.add(light);
|
||||
}
|
||||
|
||||
processor.populateProbe(filteredLightList);
|
||||
|
||||
} finally {
|
||||
vars.release();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -212,12 +212,7 @@ public class PointLight extends Light {
|
||||
if (this.radius == 0) {
|
||||
return true;
|
||||
} else {
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
if (camera.getWorldPlane(i).pseudoDistance(position) <= -radius) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return Intersection.intersect(camera, position, radius);
|
||||
}
|
||||
}
|
||||
|
||||
|
35
jme3-core/src/main/java/com/jme3/light/ProbeArea.java
Normal file
35
jme3-core/src/main/java/com/jme3/light/ProbeArea.java
Normal file
@ -0,0 +1,35 @@
|
||||
package com.jme3.light;
|
||||
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
public interface ProbeArea extends Savable, Cloneable{
|
||||
|
||||
public void setCenter(Vector3f center);
|
||||
|
||||
public float getRadius();
|
||||
|
||||
public void setRadius(float radius);
|
||||
|
||||
public Matrix4f getUniformMatrix();
|
||||
|
||||
/**
|
||||
* @see Light#intersectsBox(BoundingBox, TempVars)
|
||||
*/
|
||||
public boolean intersectsBox(BoundingBox box, TempVars vars);
|
||||
|
||||
/**
|
||||
* @see Light#intersectsSphere(BoundingSphere, TempVars)
|
||||
*/
|
||||
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars);
|
||||
|
||||
/**
|
||||
* @see Light#intersectsFrustum(Camera, TempVars)
|
||||
*/
|
||||
public abstract boolean intersectsFrustum(Camera camera, TempVars vars);
|
||||
}
|
103
jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java
Normal file
103
jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java
Normal file
@ -0,0 +1,103 @@
|
||||
package com.jme3.light;
|
||||
|
||||
import com.jme3.bounding.*;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class SphereProbeArea implements ProbeArea {
|
||||
|
||||
private Vector3f center = new Vector3f();
|
||||
private float radius = 1;
|
||||
private Matrix4f uniformMatrix = new Matrix4f();
|
||||
|
||||
public SphereProbeArea() {
|
||||
}
|
||||
|
||||
public SphereProbeArea(Vector3f center, float radius) {
|
||||
this.center.set(center);
|
||||
this.radius = radius;
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
public Vector3f getCenter() {
|
||||
return center;
|
||||
}
|
||||
|
||||
public void setCenter(Vector3f center) {
|
||||
this.center.set(center);
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
public float getRadius() {
|
||||
return radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix4f getUniformMatrix() {
|
||||
return uniformMatrix;
|
||||
}
|
||||
|
||||
private void updateMatrix(){
|
||||
//position
|
||||
uniformMatrix.m03 = center.x;
|
||||
uniformMatrix.m13 = center.y;
|
||||
uniformMatrix.m23 = center.z;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsBox(BoundingBox box, TempVars vars) {
|
||||
return Intersection.intersect(box, center, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
|
||||
return Intersection.intersect(sphere, center, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectsFrustum(Camera camera, TempVars vars) {
|
||||
return Intersection.intersect(camera, center, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SphereProbeArea{" +
|
||||
"center=" + center +
|
||||
", radius=" + radius +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SphereProbeArea clone() throws CloneNotSupportedException {
|
||||
return new SphereProbeArea(center, radius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
OutputCapsule oc = e.getCapsule(this);
|
||||
oc.write(center, "center", new Vector3f());
|
||||
oc.write(radius, "radius", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter i) throws IOException {
|
||||
InputCapsule ic = i.getCapsule(this);
|
||||
center = (Vector3f) ic.readSavable("center", new Vector3f());
|
||||
radius = ic.readFloat("radius", 1);
|
||||
updateMatrix();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2015 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.light;
|
||||
|
||||
import com.jme3.scene.Geometry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This strategy returns the 3 closest probe from the rendered object.
|
||||
* <p>
|
||||
* Image based lighting will be blended between those probes in the shader according to their distance and range.
|
||||
*
|
||||
* @author Nehon
|
||||
*/
|
||||
public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy {
|
||||
|
||||
private final static int MAX_PROBES = 3;
|
||||
List<LightProbe> lightProbes = new ArrayList<LightProbe>();
|
||||
|
||||
@Override
|
||||
public void registerProbe(LightProbe probe) {
|
||||
lightProbes.add(probe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateProbes(Geometry g, LightList lightList) {
|
||||
if (!lightProbes.isEmpty()) {
|
||||
//The 3 first probes are the closest to the geometry since the
|
||||
//light list is sorted according to the distance to the geom.
|
||||
int addedProbes = 0;
|
||||
for (LightProbe p : lightProbes) {
|
||||
if (p.isReady() && p.isEnabled()) {
|
||||
lightList.add(p);
|
||||
addedProbes ++;
|
||||
}
|
||||
if (addedProbes == MAX_PROBES) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//clearing the list for next pass.
|
||||
lightProbes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -140,6 +140,9 @@ public final class MatParamOverride extends MatParam {
|
||||
super.write(ex);
|
||||
OutputCapsule oc = ex.getCapsule(this);
|
||||
oc.write(enabled, "enabled", true);
|
||||
if (value == null) {
|
||||
oc.write(true, "isNull", false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -147,5 +150,9 @@ public final class MatParamOverride extends MatParam {
|
||||
super.read(im);
|
||||
InputCapsule ic = im.getCapsule(this);
|
||||
enabled = ic.readBoolean("enabled", true);
|
||||
boolean isNull = ic.readBoolean("isNull", false);
|
||||
if (isNull) {
|
||||
setValue(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,10 +46,7 @@ import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.Renderer;
|
||||
import com.jme3.renderer.queue.RenderQueue.Bucket;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.shader.Shader;
|
||||
import com.jme3.shader.Uniform;
|
||||
import com.jme3.shader.UniformBindingManager;
|
||||
import com.jme3.shader.VarType;
|
||||
import com.jme3.shader.*;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.texture.image.ColorSpace;
|
||||
@ -414,6 +411,17 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
||||
return paramValues.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current parameter's value.
|
||||
*
|
||||
* @param name the parameter name to look up.
|
||||
* @return current value or null if the parameter wasn't set.
|
||||
*/
|
||||
public <T> T getParamValue(final String name) {
|
||||
final MatParam param = paramValues.get(name);
|
||||
return param == null ? null : (T) param.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the texture parameter set on this material with the given name,
|
||||
* returns <code>null</code> if the parameter is not set.
|
||||
@ -662,6 +670,28 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
||||
setParam(name, VarType.Vector4, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass an uniform buffer object to the material shader.
|
||||
*
|
||||
* @param name the name of the buffer object defined in the material definition (j3md).
|
||||
* @param value the buffer object.
|
||||
*/
|
||||
public void setUniformBufferObject(final String name, final BufferObject value) {
|
||||
value.setBufferType(BufferObject.BufferType.UniformBufferObject);
|
||||
setParam(name, VarType.BufferObject, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a shader storage buffer object to the material shader.
|
||||
*
|
||||
* @param name the name of the buffer object defined in the material definition (j3md).
|
||||
* @param value the buffer object.
|
||||
*/
|
||||
public void setShaderStorageBufferObject(final String name, final BufferObject value) {
|
||||
value.setBufferType(BufferObject.BufferType.ShaderStorageBufferObject);
|
||||
setParam(name, VarType.BufferObject, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a Vector2f to the material shader.
|
||||
*
|
||||
@ -796,10 +826,18 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
||||
}
|
||||
|
||||
for (int i = 0; i < paramValues.size(); i++) {
|
||||
|
||||
MatParam param = paramValues.getValue(i);
|
||||
VarType type = param.getVarType();
|
||||
Uniform uniform = shader.getUniform(param.getPrefixedName());
|
||||
|
||||
if (isBO(type)) {
|
||||
|
||||
final ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName());
|
||||
bufferBlock.setBufferObject((BufferObject) param.getValue());
|
||||
|
||||
} else {
|
||||
|
||||
Uniform uniform = shader.getUniform(param.getPrefixedName());
|
||||
if (uniform.isSetByCurrentMaterial()) {
|
||||
continue;
|
||||
}
|
||||
@ -812,11 +850,22 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
||||
uniform.setValue(type, param.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO HACKY HACK remove this when texture unit is handled by the uniform.
|
||||
return unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the type is Buffer Object's type.
|
||||
*
|
||||
* @param type the material parameter type.
|
||||
* @return true if the type is Buffer Object's type.
|
||||
*/
|
||||
private boolean isBO(final VarType type) {
|
||||
return type == VarType.BufferObject;
|
||||
}
|
||||
|
||||
private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
|
||||
if (renderManager.getForcedRenderState() != null) {
|
||||
if (techniqueDef.getForcedRenderState() != null) {
|
||||
@ -845,7 +894,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
||||
*
|
||||
* @param renderManager The render manager to preload for
|
||||
*/
|
||||
public void preload(RenderManager renderManager) {
|
||||
public void preload(RenderManager renderManager, Geometry geometry) {
|
||||
if (technique == null) {
|
||||
selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
|
||||
}
|
||||
@ -856,9 +905,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
|
||||
if (techniqueDef.isNoRender()) {
|
||||
return;
|
||||
}
|
||||
// Get world overrides
|
||||
SafeArrayList<MatParamOverride> overrides = geometry.getWorldMatParamOverrides();
|
||||
|
||||
Shader shader = technique.makeCurrent(renderManager, null, null, null, rendererCaps);
|
||||
updateShaderMaterialParameters(renderer, shader, null, null);
|
||||
Shader shader = technique.makeCurrent(renderManager, geometry, overrides, renderManager.getForcedMatParams(), rendererCaps);
|
||||
updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
|
||||
renderManager.getRenderer().setShader(shader);
|
||||
}
|
||||
|
||||
|
@ -331,9 +331,17 @@ public class RenderState implements Cloneable, Savable {
|
||||
*/
|
||||
Exclusion,
|
||||
/**
|
||||
* Allows for custom blending by using glBlendFuncSeparate.
|
||||
* Uses the blend equations and blend factors defined by the render state.
|
||||
* <p>
|
||||
*
|
||||
* These attributes can be set by using the following methods:
|
||||
* <ul>
|
||||
* <li>{@link RenderState#setBlendEquation(BlendEquation)}<br/>
|
||||
* <li>{@link RenderState#setBlendEquationAlpha(BlendEquationAlpha)}<br/>
|
||||
* <li>{@link RenderState#setCustomBlendFactors(BlendFunc, BlendFunc, BlendFunc, BlendFunc)}<br/>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Result.RGB = BlendEquation( sfactorRGB * Source.RGB , dfactorRGB * Destination.RGB )<br/>
|
||||
* Result.A = BlendEquationAlpha( sfactorAlpha * Source.A , dfactorAlpha * Destination.A )
|
||||
*/
|
||||
Custom
|
||||
}
|
||||
@ -425,8 +433,6 @@ public class RenderState implements Cloneable, Savable {
|
||||
ADDITIONAL.applyDepthWrite = false;
|
||||
ADDITIONAL.applyDepthTest = false;
|
||||
ADDITIONAL.applyColorWrite = false;
|
||||
ADDITIONAL.applyBlendEquation = false;
|
||||
ADDITIONAL.applyBlendEquationAlpha = false;
|
||||
ADDITIONAL.applyBlendMode = false;
|
||||
ADDITIONAL.applyPolyOffset = false;
|
||||
ADDITIONAL.applyStencilTest = false;
|
||||
@ -444,9 +450,7 @@ public class RenderState implements Cloneable, Savable {
|
||||
boolean colorWrite = true;
|
||||
boolean applyColorWrite = true;
|
||||
BlendEquation blendEquation = BlendEquation.Add;
|
||||
boolean applyBlendEquation = true;
|
||||
BlendEquationAlpha blendEquationAlpha = BlendEquationAlpha.InheritColor;
|
||||
boolean applyBlendEquationAlpha = true;
|
||||
BlendMode blendMode = BlendMode.Off;
|
||||
boolean applyBlendMode = true;
|
||||
float offsetFactor = 0;
|
||||
@ -470,9 +474,9 @@ public class RenderState implements Cloneable, Savable {
|
||||
TestFunction backStencilFunction = TestFunction.Always;
|
||||
int cachedHashCode = -1;
|
||||
BlendFunc sfactorRGB = BlendFunc.One;
|
||||
BlendFunc dfactorRGB=BlendFunc.Zero;
|
||||
BlendFunc dfactorRGB = BlendFunc.One;
|
||||
BlendFunc sfactorAlpha = BlendFunc.One;
|
||||
BlendFunc dfactorAlpha=BlendFunc.Zero;
|
||||
BlendFunc dfactorAlpha = BlendFunc.One;
|
||||
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule oc = ex.getCapsule(this);
|
||||
@ -510,8 +514,6 @@ public class RenderState implements Cloneable, Savable {
|
||||
oc.write(applyDepthWrite, "applyDepthWrite", true);
|
||||
oc.write(applyDepthTest, "applyDepthTest", true);
|
||||
oc.write(applyColorWrite, "applyColorWrite", true);
|
||||
oc.write(applyBlendEquation, "applyBlendEquation", true);
|
||||
oc.write(applyBlendEquationAlpha, "applyBlendEquationAlpha", true);
|
||||
oc.write(applyBlendMode, "applyBlendMode", true);
|
||||
oc.write(applyPolyOffset, "applyPolyOffset", true);
|
||||
oc.write(applyDepthFunc, "applyDepthFunc", true);
|
||||
@ -544,9 +546,9 @@ public class RenderState implements Cloneable, Savable {
|
||||
depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual);
|
||||
lineWidth = ic.readFloat("lineWidth", 1);
|
||||
sfactorRGB = ic.readEnum("sfactorRGB", BlendFunc.class, BlendFunc.One);
|
||||
dfactorAlpha = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.Zero);
|
||||
dfactorAlpha = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.One);
|
||||
sfactorRGB = ic.readEnum("sfactorAlpha", BlendFunc.class, BlendFunc.One);
|
||||
dfactorAlpha = ic.readEnum("dfactorAlpha", BlendFunc.class, BlendFunc.Zero);
|
||||
dfactorAlpha = ic.readEnum("dfactorAlpha", BlendFunc.class, BlendFunc.One);
|
||||
|
||||
|
||||
applyWireFrame = ic.readBoolean("applyWireFrame", true);
|
||||
@ -554,14 +556,11 @@ public class RenderState implements Cloneable, Savable {
|
||||
applyDepthWrite = ic.readBoolean("applyDepthWrite", true);
|
||||
applyDepthTest = ic.readBoolean("applyDepthTest", true);
|
||||
applyColorWrite = ic.readBoolean("applyColorWrite", true);
|
||||
applyBlendEquation = ic.readBoolean("applyBlendEquation", true);
|
||||
applyBlendEquationAlpha = ic.readBoolean("applyBlendEquationAlpha", true);
|
||||
applyBlendMode = ic.readBoolean("applyBlendMode", true);
|
||||
applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
|
||||
applyDepthFunc = ic.readBoolean("applyDepthFunc", true);
|
||||
applyLineWidth = ic.readBoolean("applyLineWidth", true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -618,18 +617,31 @@ public class RenderState implements Cloneable, Savable {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blendEquation != rs.blendEquation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blendEquationAlpha != rs.blendEquationAlpha) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blendMode != rs.blendMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blendMode == BlendMode.Custom) {
|
||||
if (blendEquation != rs.blendEquation) {
|
||||
return false;
|
||||
}
|
||||
if (blendEquationAlpha != rs.blendEquationAlpha) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sfactorRGB != rs.sfactorRGB) {
|
||||
return false;
|
||||
}
|
||||
if (dfactorRGB != rs.dfactorRGB) {
|
||||
return false;
|
||||
}
|
||||
if (sfactorAlpha != rs.sfactorAlpha) {
|
||||
return false;
|
||||
}
|
||||
if (dfactorAlpha != rs.dfactorAlpha) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (offsetEnabled != rs.offsetEnabled) {
|
||||
return false;
|
||||
@ -679,14 +691,6 @@ public class RenderState implements Cloneable, Savable {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blendMode.equals(BlendMode.Custom)) {
|
||||
return sfactorRGB==rs.getCustomSfactorRGB()
|
||||
&& dfactorRGB==rs.getCustomDfactorRGB()
|
||||
&& sfactorAlpha==rs.getCustomSfactorAlpha()
|
||||
&& dfactorAlpha==rs.getCustomDfactorAlpha();
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -771,27 +775,21 @@ public class RenderState implements Cloneable, Savable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the blending equation.
|
||||
* Set the blending equation for the color component (RGB).
|
||||
* <p>
|
||||
* When blending is enabled, (<code>blendMode</code> is not
|
||||
* {@link BlendMode#Off}) the input pixel will be blended with the pixel
|
||||
* already in the color buffer. The blending equation is determined by the
|
||||
* {@link BlendEquation}. For example, the mode {@link BlendMode#Additive}
|
||||
* and {@link BlendEquation#Add} will add the input pixel's color to the
|
||||
* color already in the color buffer:
|
||||
* The blending equation determines, how the RGB values of the input pixel
|
||||
* will be blended with the RGB values of the pixel already in the color buffer.<br/>
|
||||
* For example, {@link BlendEquation#Add} will add the input pixel's color
|
||||
* to the color already in the color buffer:
|
||||
* <br/>
|
||||
* <code>Result = Source Color + Destination Color</code>
|
||||
* <br/>
|
||||
* However, the mode {@link BlendMode#Additive}
|
||||
* and {@link BlendEquation#Subtract} will subtract the input pixel's color to the
|
||||
* color already in the color buffer:
|
||||
* <br/>
|
||||
* <code>Result = Source Color - Destination Color</code>
|
||||
* <p>
|
||||
* <b>Note:</b> This gets only used in {@link BlendMode#Custom} mode.
|
||||
* All other blend modes will ignore this setting.
|
||||
*
|
||||
* @param blendEquation The blend equation to use.
|
||||
* @param blendEquation The {@link BlendEquation} to use.
|
||||
*/
|
||||
public void setBlendEquation(BlendEquation blendEquation) {
|
||||
applyBlendEquation = true;
|
||||
this.blendEquation = blendEquation;
|
||||
cachedHashCode = -1;
|
||||
}
|
||||
@ -799,44 +797,38 @@ public class RenderState implements Cloneable, Savable {
|
||||
/**
|
||||
* Set the blending equation for the alpha component.
|
||||
* <p>
|
||||
* When blending is enabled, (<code>blendMode</code> is not
|
||||
* {@link BlendMode#Off}) the input pixel will be blended with the pixel
|
||||
* already in the color buffer. The blending equation is determined by the
|
||||
* {@link BlendEquation} and can be overrode for the alpha component using
|
||||
* the {@link BlendEquationAlpha} . For example, the mode
|
||||
* {@link BlendMode#Additive} and {@link BlendEquationAlpha#Add} will add
|
||||
* the input pixel's alpha to the alpha component already in the color
|
||||
* buffer:
|
||||
* The alpha blending equation determines, how the alpha values of the input pixel
|
||||
* will be blended with the alpha values of the pixel already in the color buffer.<br/>
|
||||
* For example, {@link BlendEquationAlpha#Add} will add the input pixel's color
|
||||
* to the color already in the color buffer:
|
||||
* <br/>
|
||||
* <code>Result = Source Alpha + Destination Alpha</code>
|
||||
* <br/>
|
||||
* However, the mode {@link BlendMode#Additive} and
|
||||
* {@link BlendEquationAlpha#Subtract} will subtract the input pixel's alpha
|
||||
* to the alpha component already in the color buffer:
|
||||
* <br/>
|
||||
* <code>Result = Source Alpha - Destination Alpha</code>
|
||||
* <code>Result = Source Color + Destination Color</code>
|
||||
* <p>
|
||||
* <b>Note:</b> This gets only used in {@link BlendMode#Custom} mode.
|
||||
* All other blend modes will ignore this setting.
|
||||
*
|
||||
* @param blendEquationAlpha The blend equation to use for the alpha
|
||||
* component.
|
||||
* @param blendEquationAlpha The {@link BlendEquationAlpha} to use.
|
||||
*/
|
||||
public void setBlendEquationAlpha(BlendEquationAlpha blendEquationAlpha) {
|
||||
applyBlendEquationAlpha = true;
|
||||
this.blendEquationAlpha = blendEquationAlpha;
|
||||
cachedHashCode = -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the custom blend factors for <code>BlendMode.Custom</code> as
|
||||
* defined by the appropriate <code>BlendFunc</code>.
|
||||
* Sets the blend factors used for the source and destination color.
|
||||
* <p>
|
||||
* These factors will be multiplied with the color values of the input pixel
|
||||
* and the pixel already in the color buffer, before both colors gets combined by the {@link BlendEquation}.
|
||||
* <p>
|
||||
* <b>Note:</b> This gets only used in {@link BlendMode#Custom} mode.
|
||||
* All other blend modes will ignore this setting.
|
||||
*
|
||||
* @param sfactorRGB The source blend factor for RGB components.
|
||||
* @param dfactorRGB The destination blend factor for RGB components.
|
||||
* @param sfactorAlpha The source blend factor for the alpha component.
|
||||
* @param dfactorAlpha The destination blend factor for the alpha component.
|
||||
*/
|
||||
public void setCustomBlendFactors(BlendFunc sfactorRGB, BlendFunc dfactorRGB, BlendFunc sfactorAlpha, BlendFunc dfactorAlpha)
|
||||
{
|
||||
public void setCustomBlendFactors(BlendFunc sfactorRGB, BlendFunc dfactorRGB, BlendFunc sfactorAlpha, BlendFunc dfactorAlpha) {
|
||||
this.sfactorRGB = sfactorRGB;
|
||||
this.dfactorRGB = dfactorRGB;
|
||||
this.sfactorAlpha = sfactorAlpha;
|
||||
@ -1377,14 +1369,6 @@ public class RenderState implements Cloneable, Savable {
|
||||
return applyBlendMode;
|
||||
}
|
||||
|
||||
public boolean isApplyBlendEquation() {
|
||||
return applyBlendEquation;
|
||||
}
|
||||
|
||||
public boolean isApplyBlendEquationAlpha() {
|
||||
return applyBlendEquationAlpha;
|
||||
}
|
||||
|
||||
public boolean isApplyColorWrite() {
|
||||
return applyColorWrite;
|
||||
}
|
||||
@ -1514,27 +1498,26 @@ public class RenderState implements Cloneable, Savable {
|
||||
} else {
|
||||
state.colorWrite = colorWrite;
|
||||
}
|
||||
if (additionalState.applyBlendEquation) {
|
||||
state.blendEquation = additionalState.blendEquation;
|
||||
} else {
|
||||
state.blendEquation = blendEquation;
|
||||
}
|
||||
if (additionalState.applyBlendEquationAlpha) {
|
||||
state.blendEquationAlpha = additionalState.blendEquationAlpha;
|
||||
} else {
|
||||
state.blendEquationAlpha = blendEquationAlpha;
|
||||
}
|
||||
if (additionalState.applyBlendMode) {
|
||||
state.blendMode = additionalState.blendMode;
|
||||
if (additionalState.getBlendMode().equals(BlendMode.Custom)) {
|
||||
state.setCustomBlendFactors(
|
||||
additionalState.getCustomSfactorRGB(),
|
||||
additionalState.getCustomDfactorRGB(),
|
||||
additionalState.getCustomSfactorAlpha(),
|
||||
additionalState.getCustomDfactorAlpha());
|
||||
if (additionalState.blendMode == BlendMode.Custom) {
|
||||
state.blendEquation = additionalState.blendEquation;
|
||||
state.blendEquationAlpha = additionalState.blendEquationAlpha;
|
||||
state.sfactorRGB = additionalState.sfactorRGB;
|
||||
state.dfactorRGB = additionalState.dfactorRGB;
|
||||
state.sfactorAlpha = additionalState.sfactorAlpha;
|
||||
state.dfactorAlpha = additionalState.dfactorAlpha;
|
||||
}
|
||||
} else {
|
||||
state.blendMode = blendMode;
|
||||
if (blendMode == BlendMode.Custom) {
|
||||
state.blendEquation = blendEquation;
|
||||
state.blendEquationAlpha = blendEquationAlpha;
|
||||
state.sfactorRGB = sfactorRGB;
|
||||
state.dfactorRGB = dfactorRGB;
|
||||
state.sfactorAlpha = sfactorAlpha;
|
||||
state.dfactorAlpha = dfactorAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalState.applyPolyOffset) {
|
||||
@ -1611,8 +1594,6 @@ public class RenderState implements Cloneable, Savable {
|
||||
applyDepthWrite = true;
|
||||
applyDepthTest = true;
|
||||
applyColorWrite = true;
|
||||
applyBlendEquation = true;
|
||||
applyBlendEquationAlpha = true;
|
||||
applyBlendMode = true;
|
||||
applyPolyOffset = true;
|
||||
applyDepthFunc = true;
|
||||
@ -1639,8 +1620,6 @@ public class RenderState implements Cloneable, Savable {
|
||||
+ "\ncolorWrite=" + colorWrite
|
||||
+ "\napplyColorWrite=" + applyColorWrite
|
||||
+ "\nblendEquation=" + blendEquation
|
||||
+ "\napplyBlendEquation=" + applyBlendEquation
|
||||
+ "\napplyBlendEquationAlpha=" + applyBlendEquationAlpha
|
||||
+ "\nblendMode=" + blendMode
|
||||
+ "\napplyBlendMode=" + applyBlendMode
|
||||
+ "\noffsetEnabled=" + offsetEnabled
|
||||
|
@ -82,7 +82,7 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
|
||||
|
||||
for (int i = 0; i < lights.size(); i++) {
|
||||
Light l = lights.get(i);
|
||||
if (l instanceof AmbientLight) {
|
||||
if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -152,8 +152,6 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
|
||||
|
||||
lightDir.setValue(VarType.Vector4, tmpLightDirection);
|
||||
|
||||
break;
|
||||
case Probe:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
|
||||
|
@ -48,7 +48,7 @@ import com.jme3.shadow.next.array.DirectionalArrayShadowMap;
|
||||
import com.jme3.texture.TextureArray;
|
||||
import java.util.Comparator;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.*;
|
||||
|
||||
public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic {
|
||||
|
||||
@ -60,10 +60,11 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
private static final RenderState ADDITIVE_LIGHT = new RenderState();
|
||||
|
||||
private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
|
||||
private LightProbe lightProbe;
|
||||
private TextureArray shadowMapArray;
|
||||
private Vector3f pssmSplitsPositions;
|
||||
private int numPssmSplits;
|
||||
private static final String DEFINE_NB_PROBES = "NB_PROBES";
|
||||
private List<LightProbe> lightProbes = new ArrayList<>(3);
|
||||
|
||||
static {
|
||||
ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
|
||||
@ -73,16 +74,16 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
private final int singlePassLightingDefineId;
|
||||
private final int inPassShadowsDefineId;
|
||||
private final int nbLightsDefineId;
|
||||
private final int indirectLightingDefineId;
|
||||
private final int numPssmSplitsDefineId;
|
||||
private final int nbProbesDefineId;
|
||||
|
||||
public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) {
|
||||
super(techniqueDef);
|
||||
numPssmSplitsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_PSSM_SPLITS, VarType.Int);
|
||||
singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
|
||||
nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
|
||||
indirectLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_INDIRECT_LIGHTING, VarType.Boolean);
|
||||
inPassShadowsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_IN_PASS_SHADOWS, VarType.Boolean);
|
||||
nbProbesDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_PROBES, VarType.Int);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,7 +101,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
getFilteredLightList(renderManager, geometry);
|
||||
|
||||
ambientLightColor.set(0, 0, 0, 1);
|
||||
lightProbe = null;
|
||||
lightProbes.clear();
|
||||
pssmSplitsPositions = null;
|
||||
numPssmSplits = 0;
|
||||
|
||||
@ -110,7 +111,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
ambientLightColor.addLocal(light.getColor());
|
||||
filteredLightList.remove(i--);
|
||||
} else if (light instanceof LightProbe) {
|
||||
lightProbe = (LightProbe) light;
|
||||
lightProbes.add((LightProbe) light);
|
||||
filteredLightList.remove(i--);
|
||||
} else if (light.getShadowMap() != null) {
|
||||
ArrayShadowMap shadowMap = (ArrayShadowMap) light.getShadowMap();
|
||||
@ -121,6 +122,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
}
|
||||
}
|
||||
}
|
||||
defines.set(nbProbesDefineId, lightProbes.size());
|
||||
ambientLightColor.a = 1.0f;
|
||||
|
||||
filteredLightList.sort(new Comparator<Light>() {
|
||||
@ -139,7 +141,6 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
});
|
||||
|
||||
defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
|
||||
defines.set(indirectLightingDefineId, lightProbe != null);
|
||||
defines.set(inPassShadowsDefineId, shadowMapArray != null);
|
||||
defines.set(numPssmSplitsDefineId, numPssmSplits);
|
||||
|
||||
@ -167,12 +168,18 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
Uniform lightData = shader.getUniform("g_LightData");
|
||||
lightData.setVector4Length(numLights * 3);//8 lights * max 3
|
||||
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
|
||||
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
|
||||
lightProbeData.setVector4Length(1);
|
||||
|
||||
//TODO These 2 uniforms should be packed in an array, to be able to have several probes and blend between them.
|
||||
// Matrix4f
|
||||
Uniform lightProbeData = shader.getUniform("g_LightProbeData");
|
||||
Uniform lightProbeData2 = shader.getUniform("g_LightProbeData2");
|
||||
Uniform lightProbeData3 = shader.getUniform("g_LightProbeData3");
|
||||
|
||||
Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
|
||||
Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
|
||||
Uniform shCoeffs2 = shader.getUniform("g_ShCoeffs2");
|
||||
Uniform lightProbePemMap2 = shader.getUniform("g_PrefEnvMap2");
|
||||
Uniform shCoeffs3 = shader.getUniform("g_ShCoeffs3");
|
||||
Uniform lightProbePemMap3 = shader.getUniform("g_PrefEnvMap3");
|
||||
|
||||
if (startIndex != 0) {
|
||||
// apply additive blending for 2nd and future passes
|
||||
@ -183,17 +190,20 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
}
|
||||
|
||||
//If there is a lightProbe in the list we force its render on the first pass
|
||||
if(lightProbe != null){
|
||||
BoundingSphere s = (BoundingSphere)lightProbe.getBounds();
|
||||
lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / s.getRadius() + lightProbe.getNbMipMaps(), 0);
|
||||
shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
|
||||
//assigning new texture indexes
|
||||
int pemUnit = lastTexUnit++;
|
||||
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
|
||||
lightProbePemMap.setValue(VarType.Int, pemUnit);
|
||||
if (!lightProbes.isEmpty()) {
|
||||
LightProbe lightProbe = lightProbes.get(0);
|
||||
lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData, shCoeffs, lightProbePemMap, lightProbe);
|
||||
if (lightProbes.size() > 1) {
|
||||
lightProbe = lightProbes.get(1);
|
||||
lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData2, shCoeffs2, lightProbePemMap2, lightProbe);
|
||||
}
|
||||
if (lightProbes.size() > 2) {
|
||||
lightProbe = lightProbes.get(2);
|
||||
setProbeData(rm, lastTexUnit, lightProbeData3, shCoeffs3, lightProbePemMap3, lightProbe);
|
||||
}
|
||||
} else {
|
||||
//Disable IBL for this pass
|
||||
lightProbeData.setVector4InArray(0,0,0,-1, 0);
|
||||
lightProbeData.setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX);
|
||||
}
|
||||
|
||||
Uniform shadowMatricesUniform = shader.getUniform("g_ShadowMatrices");
|
||||
@ -290,6 +300,17 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
|
||||
return curIndex;
|
||||
}
|
||||
|
||||
private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, Uniform lightProbePemMap, LightProbe lightProbe) {
|
||||
|
||||
lightProbeData.setValue(VarType.Matrix4, lightProbe.getUniformMatrix());
|
||||
shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
|
||||
//assigning new texture indexes
|
||||
int pemUnit = lastTexUnit++;
|
||||
rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
|
||||
lightProbePemMap.setValue(VarType.Int, pemUnit);
|
||||
return lastTexUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderManager renderManager, Shader shader, Geometry geometry, int lastTexUnit) {
|
||||
int nbRenderedLights = 0;
|
||||
|
@ -112,7 +112,6 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
|
||||
lightData.setVector4Length(numLights * 3);//8 lights * max 3
|
||||
Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
|
||||
|
||||
|
||||
if (startIndex != 0) {
|
||||
// apply additive blending for 2nd and future passes
|
||||
rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
|
||||
@ -129,7 +128,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
|
||||
for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
|
||||
|
||||
Light l = lightList.get(curIndex);
|
||||
if (l.getType() == Light.Type.Ambient) {
|
||||
if (l.getType() == Light.Type.Ambient || l.getType() == Light.Type.Probe) {
|
||||
endIndex++;
|
||||
continue;
|
||||
}
|
||||
@ -191,8 +190,6 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
|
||||
lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
|
||||
lightDataIndex++;
|
||||
break;
|
||||
case Probe:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
|
||||
}
|
||||
|
@ -558,6 +558,19 @@ public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable
|
||||
a = ((byte) (color) & 0xFF) / 255f;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the RGBA values of this <code>ColorRGBA</code> with the given combined ABGR value
|
||||
* Bits 24-31 are alpha, bits 16-23 are blue, bits 8-15 are green, bits 0-7 are red.
|
||||
* @param color The integer ABGR value used to set this object.
|
||||
* @return this
|
||||
*/
|
||||
public ColorRGBA fromIntABGR(int color) {
|
||||
a = ((byte) (color >> 24) & 0xFF) / 255f;
|
||||
b = ((byte) (color >> 16) & 0xFF) / 255f;
|
||||
g = ((byte) (color >> 8) & 0xFF) / 255f;
|
||||
r = ((byte) (color) & 0xFF) / 255f;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform this <code>ColorRGBA</code> to a <code>Vector3f</code> using
|
||||
|
13
jme3-core/src/main/java/com/jme3/math/EaseFunction.java
Normal file
13
jme3-core/src/main/java/com/jme3/math/EaseFunction.java
Normal file
@ -0,0 +1,13 @@
|
||||
package com.jme3.math;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 26/03/2017.
|
||||
*/
|
||||
public interface EaseFunction {
|
||||
|
||||
/**
|
||||
* @param value a value from 0 to 1. Passing a value out of this range will have unexpected behavior.
|
||||
* @return
|
||||
*/
|
||||
float apply(float value);
|
||||
}
|
163
jme3-core/src/main/java/com/jme3/math/Easing.java
Normal file
163
jme3-core/src/main/java/com/jme3/math/Easing.java
Normal file
@ -0,0 +1,163 @@
|
||||
package com.jme3.math;
|
||||
|
||||
/**
|
||||
* Expose several Easing function from Robert Penner
|
||||
* Created by Nehon on 26/03/2017.
|
||||
*/
|
||||
public class Easing {
|
||||
|
||||
|
||||
public static EaseFunction constant = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* In
|
||||
*/
|
||||
public static EaseFunction linear = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
public static EaseFunction inQuad = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return value * value;
|
||||
}
|
||||
};
|
||||
|
||||
public static EaseFunction inCubic = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return value * value * value;
|
||||
}
|
||||
};
|
||||
|
||||
public static EaseFunction inQuart = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return value * value * value * value;
|
||||
}
|
||||
};
|
||||
|
||||
public static EaseFunction inQuint = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return value * value * value * value * value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Out Elastic and bounce
|
||||
*/
|
||||
public static EaseFunction outElastic = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return FastMath.pow(2f, -10f * value) * FastMath.sin((value - 0.3f / 4f) * (2f * FastMath.PI) / 0.3f) + 1f;
|
||||
}
|
||||
};
|
||||
|
||||
public static EaseFunction outBounce = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
if (value < (1f / 2.75f)) {
|
||||
return (7.5625f * value * value);
|
||||
} else if (value < (2f / 2.75f)) {
|
||||
return (7.5625f * (value -= (1.5f / 2.75f)) * value + 0.75f);
|
||||
} else if (value < (2.5 / 2.75)) {
|
||||
return (7.5625f * (value -= (2.25f / 2.75f)) * value + 0.9375f);
|
||||
} else {
|
||||
return (7.5625f * (value -= (2.625f / 2.75f)) * value + 0.984375f);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* In Elastic and bounce
|
||||
*/
|
||||
public static EaseFunction inElastic = new Invert(outElastic);
|
||||
public static EaseFunction inBounce = new Invert(outBounce);
|
||||
|
||||
/**
|
||||
* Out
|
||||
*/
|
||||
public static EaseFunction outQuad = new Invert(inQuad);
|
||||
public static EaseFunction outCubic = new Invert(inCubic);
|
||||
public static EaseFunction outQuart = new Invert(inQuart);
|
||||
public static EaseFunction outQuint = new Invert(inQuint);
|
||||
|
||||
/**
|
||||
* inOut
|
||||
*/
|
||||
public static EaseFunction inOutQuad = new InOut(inQuad, outQuad);
|
||||
public static EaseFunction inOutCubic = new InOut(inCubic, outCubic);
|
||||
public static EaseFunction inOutQuart = new InOut(inQuart, outQuart);
|
||||
public static EaseFunction inOutQuint = new InOut(inQuint, outQuint);
|
||||
public static EaseFunction inOutElastic = new InOut(inElastic, outElastic);
|
||||
public static EaseFunction inOutBounce = new InOut(inBounce, outBounce);
|
||||
|
||||
|
||||
/**
|
||||
* Extra functions
|
||||
*/
|
||||
|
||||
public static EaseFunction smoothStep = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float t) {
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
};
|
||||
|
||||
public static EaseFunction smootherStep = new EaseFunction() {
|
||||
@Override
|
||||
public float apply(float t) {
|
||||
return t * t * t * (t * (t * 6f - 15f) + 10f);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An Ease function composed of 2 sb function for custom in and out easing
|
||||
*/
|
||||
public static class InOut implements EaseFunction {
|
||||
|
||||
private EaseFunction in;
|
||||
private EaseFunction out;
|
||||
|
||||
public InOut(EaseFunction in, EaseFunction out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
if (value < 0.5) {
|
||||
value = value * 2;
|
||||
return inQuad.apply(value) / 2;
|
||||
} else {
|
||||
value = (value - 0.5f) * 2;
|
||||
return outQuad.apply(value) / 2 + 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Invert implements EaseFunction {
|
||||
|
||||
private EaseFunction func;
|
||||
|
||||
public Invert(EaseFunction func) {
|
||||
this.func = func;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float apply(float value) {
|
||||
return 1f - func.apply(1f - value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
247
jme3-core/src/main/java/com/jme3/math/MathUtils.java
Normal file
247
jme3-core/src/main/java/com/jme3/math/MathUtils.java
Normal file
@ -0,0 +1,247 @@
|
||||
package com.jme3.math;
|
||||
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
/**
|
||||
* Created by Nehon on 23/04/2017.
|
||||
*/
|
||||
public class MathUtils {
|
||||
|
||||
public static Quaternion log(Quaternion q, Quaternion store) {
|
||||
float a = FastMath.acos(q.w);
|
||||
float sina = FastMath.sin(a);
|
||||
|
||||
store.w = 0;
|
||||
if (sina > 0) {
|
||||
store.x = a * q.x / sina;
|
||||
store.y = a * q.y / sina;
|
||||
store.z = a * q.z / sina;
|
||||
} else {
|
||||
store.x = 0;
|
||||
store.y = 0;
|
||||
store.z = 0;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
public static Quaternion exp(Quaternion q, Quaternion store) {
|
||||
|
||||
float len = FastMath.sqrt(q.x * q.x + q.y * q.y + q.z * q.z);
|
||||
float sinLen = FastMath.sin(len);
|
||||
float cosLen = FastMath.cos(len);
|
||||
|
||||
store.w = cosLen;
|
||||
if (len > 0) {
|
||||
store.x = sinLen * q.x / len;
|
||||
store.y = sinLen * q.y / len;
|
||||
store.z = sinLen * q.z / len;
|
||||
} else {
|
||||
store.x = 0;
|
||||
store.y = 0;
|
||||
store.z = 0;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
//! This version of slerp, used by squad, does not check for theta > 90.
|
||||
public static Quaternion slerpNoInvert(Quaternion q1, Quaternion q2, float t, Quaternion store) {
|
||||
float dot = q1.dot(q2);
|
||||
|
||||
if (dot > -0.95f && dot < 0.95f) {
|
||||
float angle = FastMath.acos(dot);
|
||||
float sin1 = FastMath.sin(angle * (1 - t));
|
||||
float sin2 = FastMath.sin(angle * t);
|
||||
float sin3 = FastMath.sin(angle);
|
||||
store.x = (q1.x * sin1 + q2.x * sin2) / sin3;
|
||||
store.y = (q1.y * sin1 + q2.y * sin2) / sin3;
|
||||
store.z = (q1.z * sin1 + q2.z * sin2) / sin3;
|
||||
store.w = (q1.w * sin1 + q2.w * sin2) / sin3;
|
||||
System.err.println("real slerp");
|
||||
} else {
|
||||
// if the angle is small, use linear interpolation
|
||||
store.set(q1).nlerp(q2, t);
|
||||
System.err.println("nlerp");
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
public static Quaternion slerp(Quaternion q1, Quaternion q2, float t, Quaternion store) {
|
||||
|
||||
float dot = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z)
|
||||
+ (q1.w * q2.w);
|
||||
|
||||
if (dot < 0.0f) {
|
||||
// Negate the second quaternion and the result of the dot product
|
||||
q2.x = -q2.x;
|
||||
q2.y = -q2.y;
|
||||
q2.z = -q2.z;
|
||||
q2.w = -q2.w;
|
||||
dot = -dot;
|
||||
}
|
||||
|
||||
// Set the first and second scale for the interpolation
|
||||
float scale0 = 1 - t;
|
||||
float scale1 = t;
|
||||
|
||||
// Check if the angle between the 2 quaternions was big enough to
|
||||
// warrant such calculations
|
||||
if (dot < 0.9f) {// Get the angle between the 2 quaternions,
|
||||
// and then store the sin() of that angle
|
||||
float theta = FastMath.acos(dot);
|
||||
float invSinTheta = 1f / FastMath.sin(theta);
|
||||
|
||||
// Calculate the scale for q1 and q2, according to the angle and
|
||||
// it's sine value
|
||||
scale0 = FastMath.sin((1 - t) * theta) * invSinTheta;
|
||||
scale1 = FastMath.sin((t * theta)) * invSinTheta;
|
||||
|
||||
// Calculate the x, y, z and w values for the quaternion by using a
|
||||
// special
|
||||
// form of linear interpolation for quaternions.
|
||||
store.x = (scale0 * q1.x) + (scale1 * q2.x);
|
||||
store.y = (scale0 * q1.y) + (scale1 * q2.y);
|
||||
store.z = (scale0 * q1.z) + (scale1 * q2.z);
|
||||
store.w = (scale0 * q1.w) + (scale1 * q2.w);
|
||||
} else {
|
||||
store.x = (scale0 * q1.x) + (scale1 * q2.x);
|
||||
store.y = (scale0 * q1.y) + (scale1 * q2.y);
|
||||
store.z = (scale0 * q1.z) + (scale1 * q2.z);
|
||||
store.w = (scale0 * q1.w) + (scale1 * q2.w);
|
||||
store.normalizeLocal();
|
||||
}
|
||||
// Return the interpolated quaternion
|
||||
return store;
|
||||
}
|
||||
|
||||
// //! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation
|
||||
// private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) {
|
||||
// store.set(-qn.x, -qn.y, -qn.z, qn.w);
|
||||
// //store.set(qn).inverseLocal();
|
||||
// tmp.set(store);
|
||||
//
|
||||
// log(store.multLocal(qnm1), store);
|
||||
// log(tmp.multLocal(qnp1), tmp);
|
||||
// store.addLocal(tmp).multLocal(1f / -4f);
|
||||
// exp(store, tmp);
|
||||
// store.set(tmp).multLocal(qn);
|
||||
//
|
||||
// return store.normalizeLocal();
|
||||
// //return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp();
|
||||
// }
|
||||
|
||||
//! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation
|
||||
private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) {
|
||||
Quaternion invQn = new Quaternion(-qn.x, -qn.y, -qn.z, qn.w);
|
||||
|
||||
|
||||
log(invQn.mult(qnp1), tmp);
|
||||
log(invQn.mult(qnm1), store);
|
||||
store.addLocal(tmp).multLocal(-1f / 4f);
|
||||
exp(store, tmp);
|
||||
store.set(qn).multLocal(tmp);
|
||||
|
||||
return store.normalizeLocal();
|
||||
//return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp();
|
||||
}
|
||||
|
||||
|
||||
//! spherical cubic interpolation
|
||||
public static Quaternion squad(Quaternion q0, Quaternion q1, Quaternion q2, Quaternion q3, Quaternion a, Quaternion b, float t, Quaternion store) {
|
||||
|
||||
spline(q0, q1, q2, a, store);
|
||||
spline(q1, q2, q3, b, store);
|
||||
|
||||
slerp(a, b, t, store);
|
||||
slerp(q1, q2, t, a);
|
||||
return slerp(a, store, 2 * t * (1 - t), b);
|
||||
//slerpNoInvert(a, b, t, store);
|
||||
//slerpNoInvert(q1, q2, t, a);
|
||||
//return slerpNoInvert(a, store, 2 * t * (1 - t), b);
|
||||
|
||||
// quaternion c = slerpNoInvert(q1, q2, t),
|
||||
// d = slerpNoInvert(a, b, t);
|
||||
// return slerpNoInvert(c, d, 2 * t * (1 - t));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the shortest distance between a Ray and a segment.
|
||||
* The segment is defined by a start position and an end position in world space
|
||||
* The distance returned will be in world space (world units).
|
||||
* If the camera parameter is not null the distance will be returned in screen space (pixels)
|
||||
*
|
||||
* @param ray The ray
|
||||
* @param segStart The start position of the segment in world space
|
||||
* @param segEnd The end position of the segment in world space
|
||||
* @param camera The renderer camera if the distance is required in screen space. Null if the distance is required in world space
|
||||
* @return the shortest distance between the ray and the segment or -1 if no solution is found.
|
||||
*/
|
||||
public static float raySegmentShortestDistance(Ray ray, Vector3f segStart, Vector3f segEnd, Camera camera) {
|
||||
// Algorithm is ported from the C algorithm of
|
||||
// Paul Bourke at http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/
|
||||
TempVars vars = TempVars.get();
|
||||
Vector3f resultSegmentPoint1 = vars.vect1;
|
||||
Vector3f resultSegmentPoint2 = vars.vect2;
|
||||
|
||||
Vector3f p1 = segStart;
|
||||
Vector3f p2 = segEnd;
|
||||
Vector3f p3 = ray.origin;
|
||||
Vector3f p4 = vars.vect3.set(ray.getDirection()).multLocal(Math.min(ray.getLimit(), 1000)).addLocal(ray.getOrigin());
|
||||
Vector3f p13 = vars.vect4.set(p1).subtractLocal(p3);
|
||||
Vector3f p43 = vars.vect5.set(p4).subtractLocal(p3);
|
||||
|
||||
if (p43.lengthSquared() < 0.0001) {
|
||||
vars.release();
|
||||
return -1;
|
||||
}
|
||||
Vector3f p21 = vars.vect6.set(p2).subtractLocal(p1);
|
||||
if (p21.lengthSquared() < 0.0001) {
|
||||
vars.release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
double d1343 = p13.x * (double) p43.x + (double) p13.y * p43.y + (double) p13.z * p43.z;
|
||||
double d4321 = p43.x * (double) p21.x + (double) p43.y * p21.y + (double) p43.z * p21.z;
|
||||
double d1321 = p13.x * (double) p21.x + (double) p13.y * p21.y + (double) p13.z * p21.z;
|
||||
double d4343 = p43.x * (double) p43.x + (double) p43.y * p43.y + (double) p43.z * p43.z;
|
||||
double d2121 = p21.x * (double) p21.x + (double) p21.y * p21.y + (double) p21.z * p21.z;
|
||||
|
||||
double denom = d2121 * d4343 - d4321 * d4321;
|
||||
if (Math.abs(denom) < 0.0001) {
|
||||
vars.release();
|
||||
return -1;
|
||||
}
|
||||
double numer = d1343 * d4321 - d1321 * d4343;
|
||||
|
||||
double mua = numer / denom;
|
||||
double mub = (d1343 + d4321 * (mua)) / d4343;
|
||||
|
||||
resultSegmentPoint1.x = (float) (p1.x + mua * p21.x);
|
||||
resultSegmentPoint1.y = (float) (p1.y + mua * p21.y);
|
||||
resultSegmentPoint1.z = (float) (p1.z + mua * p21.z);
|
||||
resultSegmentPoint2.x = (float) (p3.x + mub * p43.x);
|
||||
resultSegmentPoint2.y = (float) (p3.y + mub * p43.y);
|
||||
resultSegmentPoint2.z = (float) (p3.z + mub * p43.z);
|
||||
|
||||
//check if result 1 is in the segment section.
|
||||
float startToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segStart).lengthSquared();
|
||||
float endToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segEnd).lengthSquared();
|
||||
float segLength = vars.vect3.set(segEnd).subtractLocal(segStart).lengthSquared();
|
||||
if (startToPoint > segLength || endToPoint > segLength) {
|
||||
vars.release();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (camera != null) {
|
||||
//camera is not null let's convert the points in screen space
|
||||
camera.getScreenCoordinates(resultSegmentPoint1, resultSegmentPoint1);
|
||||
camera.getScreenCoordinates(resultSegmentPoint2, resultSegmentPoint2);
|
||||
}
|
||||
|
||||
float length = resultSegmentPoint1.subtractLocal(resultSegmentPoint2).length();
|
||||
vars.release();
|
||||
return length;
|
||||
}
|
||||
|
||||
}
|
@ -34,6 +34,7 @@ package com.jme3.math;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.logging.Logger;
|
||||
@ -1022,96 +1023,95 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
|
||||
store = new Matrix4f();
|
||||
}
|
||||
|
||||
float temp00, temp01, temp02, temp03;
|
||||
float temp10, temp11, temp12, temp13;
|
||||
float temp20, temp21, temp22, temp23;
|
||||
float temp30, temp31, temp32, temp33;
|
||||
TempVars v = TempVars.get();
|
||||
float[] m = v.matrixWrite;
|
||||
|
||||
temp00 = m00 * in2.m00
|
||||
m[0] = m00 * in2.m00
|
||||
+ m01 * in2.m10
|
||||
+ m02 * in2.m20
|
||||
+ m03 * in2.m30;
|
||||
temp01 = m00 * in2.m01
|
||||
m[1] = m00 * in2.m01
|
||||
+ m01 * in2.m11
|
||||
+ m02 * in2.m21
|
||||
+ m03 * in2.m31;
|
||||
temp02 = m00 * in2.m02
|
||||
m[2] = m00 * in2.m02
|
||||
+ m01 * in2.m12
|
||||
+ m02 * in2.m22
|
||||
+ m03 * in2.m32;
|
||||
temp03 = m00 * in2.m03
|
||||
m[3] = m00 * in2.m03
|
||||
+ m01 * in2.m13
|
||||
+ m02 * in2.m23
|
||||
+ m03 * in2.m33;
|
||||
|
||||
temp10 = m10 * in2.m00
|
||||
m[4] = m10 * in2.m00
|
||||
+ m11 * in2.m10
|
||||
+ m12 * in2.m20
|
||||
+ m13 * in2.m30;
|
||||
temp11 = m10 * in2.m01
|
||||
m[5] = m10 * in2.m01
|
||||
+ m11 * in2.m11
|
||||
+ m12 * in2.m21
|
||||
+ m13 * in2.m31;
|
||||
temp12 = m10 * in2.m02
|
||||
m[6] = m10 * in2.m02
|
||||
+ m11 * in2.m12
|
||||
+ m12 * in2.m22
|
||||
+ m13 * in2.m32;
|
||||
temp13 = m10 * in2.m03
|
||||
m[7] = m10 * in2.m03
|
||||
+ m11 * in2.m13
|
||||
+ m12 * in2.m23
|
||||
+ m13 * in2.m33;
|
||||
|
||||
temp20 = m20 * in2.m00
|
||||
m[8] = m20 * in2.m00
|
||||
+ m21 * in2.m10
|
||||
+ m22 * in2.m20
|
||||
+ m23 * in2.m30;
|
||||
temp21 = m20 * in2.m01
|
||||
m[9] = m20 * in2.m01
|
||||
+ m21 * in2.m11
|
||||
+ m22 * in2.m21
|
||||
+ m23 * in2.m31;
|
||||
temp22 = m20 * in2.m02
|
||||
m[10] = m20 * in2.m02
|
||||
+ m21 * in2.m12
|
||||
+ m22 * in2.m22
|
||||
+ m23 * in2.m32;
|
||||
temp23 = m20 * in2.m03
|
||||
m[11] = m20 * in2.m03
|
||||
+ m21 * in2.m13
|
||||
+ m22 * in2.m23
|
||||
+ m23 * in2.m33;
|
||||
|
||||
temp30 = m30 * in2.m00
|
||||
m[12] = m30 * in2.m00
|
||||
+ m31 * in2.m10
|
||||
+ m32 * in2.m20
|
||||
+ m33 * in2.m30;
|
||||
temp31 = m30 * in2.m01
|
||||
m[13] = m30 * in2.m01
|
||||
+ m31 * in2.m11
|
||||
+ m32 * in2.m21
|
||||
+ m33 * in2.m31;
|
||||
temp32 = m30 * in2.m02
|
||||
m[14] = m30 * in2.m02
|
||||
+ m31 * in2.m12
|
||||
+ m32 * in2.m22
|
||||
+ m33 * in2.m32;
|
||||
temp33 = m30 * in2.m03
|
||||
m[15] = m30 * in2.m03
|
||||
+ m31 * in2.m13
|
||||
+ m32 * in2.m23
|
||||
+ m33 * in2.m33;
|
||||
|
||||
store.m00 = temp00;
|
||||
store.m01 = temp01;
|
||||
store.m02 = temp02;
|
||||
store.m03 = temp03;
|
||||
store.m10 = temp10;
|
||||
store.m11 = temp11;
|
||||
store.m12 = temp12;
|
||||
store.m13 = temp13;
|
||||
store.m20 = temp20;
|
||||
store.m21 = temp21;
|
||||
store.m22 = temp22;
|
||||
store.m23 = temp23;
|
||||
store.m30 = temp30;
|
||||
store.m31 = temp31;
|
||||
store.m32 = temp32;
|
||||
store.m33 = temp33;
|
||||
|
||||
store.m00 = m[0];
|
||||
store.m01 = m[1];
|
||||
store.m02 = m[2];
|
||||
store.m03 = m[3];
|
||||
store.m10 = m[4];
|
||||
store.m11 = m[5];
|
||||
store.m12 = m[6];
|
||||
store.m13 = m[7];
|
||||
store.m20 = m[8];
|
||||
store.m21 = m[9];
|
||||
store.m22 = m[10];
|
||||
store.m23 = m[11];
|
||||
store.m30 = m[12];
|
||||
store.m31 = m[13];
|
||||
store.m32 = m[14];
|
||||
store.m33 = m[15];
|
||||
v.release();
|
||||
return store;
|
||||
}
|
||||
|
||||
@ -1708,8 +1708,8 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
|
||||
return new Vector3f(m03, m13, m23);
|
||||
}
|
||||
|
||||
public void toTranslationVector(Vector3f vector) {
|
||||
vector.set(m03, m13, m23);
|
||||
public Vector3f toTranslationVector(Vector3f vector) {
|
||||
return vector.set(m03, m13, m23);
|
||||
}
|
||||
|
||||
public Quaternion toRotationQuat() {
|
||||
@ -1718,8 +1718,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
|
||||
return quat;
|
||||
}
|
||||
|
||||
public void toRotationQuat(Quaternion q) {
|
||||
q.fromRotationMatrix(toRotationMatrix());
|
||||
public Quaternion toRotationQuat(Quaternion q) {
|
||||
return q.fromRotationMatrix(m00, m01, m02, m10,
|
||||
m11, m12, m20, m21, m22);
|
||||
}
|
||||
|
||||
public Matrix3f toRotationMatrix() {
|
||||
@ -1753,14 +1754,15 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
|
||||
* Retreives the scale vector from the matrix and stores it into a given
|
||||
* vector.
|
||||
*
|
||||
* @param the
|
||||
* vector where the scale will be stored
|
||||
* @param store the vector where the scale will be stored
|
||||
* @return the store vector
|
||||
*/
|
||||
public void toScaleVector(Vector3f vector) {
|
||||
public Vector3f toScaleVector(Vector3f store) {
|
||||
float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20);
|
||||
float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21);
|
||||
float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22);
|
||||
vector.set(scaleX, scaleY, scaleZ);
|
||||
store.set(scaleX, scaleY, scaleZ);
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1774,25 +1776,30 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
|
||||
* the Z scale
|
||||
*/
|
||||
public void setScale(float x, float y, float z) {
|
||||
TempVars vars = TempVars.get();
|
||||
vars.vect1.set(m00, m10, m20);
|
||||
vars.vect1.normalizeLocal().multLocal(x);
|
||||
m00 = vars.vect1.x;
|
||||
m10 = vars.vect1.y;
|
||||
m20 = vars.vect1.z;
|
||||
|
||||
vars.vect1.set(m01, m11, m21);
|
||||
vars.vect1.normalizeLocal().multLocal(y);
|
||||
m01 = vars.vect1.x;
|
||||
m11 = vars.vect1.y;
|
||||
m21 = vars.vect1.z;
|
||||
float length = m00 * m00 + m10 * m10 + m20 * m20;
|
||||
if (length != 0f) {
|
||||
length = length == 1 ? x : (x / FastMath.sqrt(length));
|
||||
m00 *= length;
|
||||
m10 *= length;
|
||||
m20 *= length;
|
||||
}
|
||||
|
||||
vars.vect1.set(m02, m12, m22);
|
||||
vars.vect1.normalizeLocal().multLocal(z);
|
||||
m02 = vars.vect1.x;
|
||||
m12 = vars.vect1.y;
|
||||
m22 = vars.vect1.z;
|
||||
vars.release();
|
||||
length = m01 * m01 + m11 * m11 + m21 * m21;
|
||||
if (length != 0f) {
|
||||
length = length == 1 ? y : (y / FastMath.sqrt(length));
|
||||
m01 *= length;
|
||||
m11 *= length;
|
||||
m21 *= length;
|
||||
}
|
||||
|
||||
length = m02 * m02 + m12 * m12 + m22 * m22;
|
||||
if (length != 0f) {
|
||||
length = length == 1 ? z : (z / FastMath.sqrt(length));
|
||||
m02 *= length;
|
||||
m12 *= length;
|
||||
m22 *= length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,10 +33,8 @@ package com.jme3.math;
|
||||
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.util.TempVars;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
@ -452,10 +450,55 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>toTransformMatrix</code> converts this quaternion to a transform
|
||||
* matrix. The result is stored in result.
|
||||
* Note this method won't preserve the scale of the given matrix.
|
||||
*
|
||||
* @param store The Matrix3f to store the result in.
|
||||
* @return the transform matrix with the rotation representation of this quaternion.
|
||||
*/
|
||||
public Matrix4f toTransformMatrix(Matrix4f store) {
|
||||
|
||||
float norm = norm();
|
||||
// we explicitly test norm against one here, saving a division
|
||||
// at the cost of a test and branch. Is it worth it?
|
||||
float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;
|
||||
|
||||
// compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
|
||||
// will be used 2-4 times each.
|
||||
float xs = x * s;
|
||||
float ys = y * s;
|
||||
float zs = z * s;
|
||||
float xx = x * xs;
|
||||
float xy = x * ys;
|
||||
float xz = x * zs;
|
||||
float xw = w * xs;
|
||||
float yy = y * ys;
|
||||
float yz = y * zs;
|
||||
float yw = w * ys;
|
||||
float zz = z * zs;
|
||||
float zw = w * zs;
|
||||
|
||||
// using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
|
||||
store.m00 = 1 - (yy + zz);
|
||||
store.m01 = (xy - zw);
|
||||
store.m02 = (xz + yw);
|
||||
store.m10 = (xy + zw);
|
||||
store.m11 = 1 - (xx + zz);
|
||||
store.m12 = (yz - xw);
|
||||
store.m20 = (xz - yw);
|
||||
store.m21 = (yz + xw);
|
||||
store.m22 = 1 - (xx + yy);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>toRotationMatrix</code> converts this quaternion to a rotational
|
||||
* matrix. The result is stored in result. 4th row and 4th column values are
|
||||
* untouched. Note: the result is created from a normalized version of this quat.
|
||||
* Note that this method will preserve the scale of the given matrix
|
||||
*
|
||||
* @param result
|
||||
* The Matrix4f to store the result in.
|
||||
|
@ -32,6 +32,8 @@
|
||||
package com.jme3.math;
|
||||
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@ -174,13 +176,14 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this matrix to the interpolation between the first matrix and the second by delta amount.
|
||||
* Sets this transform to the interpolation between the first transform and the second by delta amount.
|
||||
* @param t1 The beginning transform.
|
||||
* @param t2 The ending transform.
|
||||
* @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2.
|
||||
*/
|
||||
public void interpolateTransforms(Transform t1, Transform t2, float delta) {
|
||||
this.rot.slerp(t1.rot,t2.rot,delta);
|
||||
t1.rot.nlerp(t2.rot, delta);
|
||||
this.rot.set(t1.rot);
|
||||
this.translation.interpolateLocal(t1.translation,t2.translation,delta);
|
||||
this.scale.interpolateLocal(t1.scale,t2.scale,delta);
|
||||
}
|
||||
@ -257,17 +260,25 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
|
||||
}
|
||||
|
||||
public Matrix4f toTransformMatrix() {
|
||||
Matrix4f trans = new Matrix4f();
|
||||
trans.setTranslation(translation);
|
||||
trans.setRotationQuaternion(rot);
|
||||
trans.setScale(scale);
|
||||
return trans;
|
||||
return toTransformMatrix(null);
|
||||
}
|
||||
|
||||
public Matrix4f toTransformMatrix(Matrix4f store) {
|
||||
if (store == null) {
|
||||
store = new Matrix4f();
|
||||
}
|
||||
store.setTranslation(translation);
|
||||
rot.toTransformMatrix(store);
|
||||
store.setScale(scale);
|
||||
return store;
|
||||
}
|
||||
|
||||
public void fromTransformMatrix(Matrix4f mat) {
|
||||
translation.set(mat.toTranslationVector());
|
||||
rot.set(mat.toRotationQuat());
|
||||
scale.set(mat.toScaleVector());
|
||||
TempVars vars = TempVars.get();
|
||||
translation.set(mat.toTranslationVector(vars.vect1));
|
||||
rot.set(mat.toRotationQuat(vars.quat1));
|
||||
scale.set(mat.toScaleVector(vars.vect2));
|
||||
vars.release();
|
||||
}
|
||||
|
||||
public Transform invert() {
|
||||
|
@ -394,7 +394,15 @@ public enum Caps {
|
||||
/**
|
||||
* GPU can provide and accept binary shaders.
|
||||
*/
|
||||
BinaryShader;
|
||||
BinaryShader,
|
||||
/**
|
||||
* Supporting working with UniformBufferObject.
|
||||
*/
|
||||
UniformBufferObject,
|
||||
/**
|
||||
* Supporting working with ShaderStorageBufferObjects.
|
||||
*/
|
||||
ShaderStorageBufferObject;
|
||||
|
||||
/**
|
||||
* Returns true if given the renderer capabilities, the texture
|
||||
|
@ -62,4 +62,20 @@ public enum Limits {
|
||||
ColorTextureSamples,
|
||||
DepthTextureSamples,
|
||||
TextureAnisotropy,
|
||||
|
||||
// UBO
|
||||
UniformBufferObjectMaxVertexBlocks,
|
||||
UniformBufferObjectMaxFragmentBlocks,
|
||||
UniformBufferObjectMaxGeometryBlocks,
|
||||
UniformBufferObjectMaxBlockSize,
|
||||
|
||||
// SSBO
|
||||
ShaderStorageBufferObjectMaxBlockSize,
|
||||
ShaderStorageBufferObjectMaxVertexBlocks,
|
||||
ShaderStorageBufferObjectMaxFragmentBlocks,
|
||||
ShaderStorageBufferObjectMaxGeometryBlocks,
|
||||
ShaderStorageBufferObjectMaxTessControlBlocks,
|
||||
ShaderStorageBufferObjectMaxTessEvaluationBlocks,
|
||||
ShaderStorageBufferObjectMaxComputeBlocks,
|
||||
ShaderStorageBufferObjectMaxCombineBlocks,
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
package com.jme3.renderer;
|
||||
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.material.RenderState.BlendFunc;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
@ -110,6 +111,30 @@ public class RenderContext {
|
||||
*/
|
||||
public RenderState.BlendEquationAlpha blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor;
|
||||
|
||||
/**
|
||||
* @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
|
||||
* com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
|
||||
*/
|
||||
public RenderState.BlendFunc sfactorRGB = RenderState.BlendFunc.One;
|
||||
|
||||
/**
|
||||
* @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
|
||||
* com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
|
||||
*/
|
||||
public RenderState.BlendFunc dfactorRGB = RenderState.BlendFunc.One;
|
||||
|
||||
/**
|
||||
* @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
|
||||
* com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
|
||||
*/
|
||||
public RenderState.BlendFunc sfactorAlpha = RenderState.BlendFunc.One;
|
||||
|
||||
/**
|
||||
* @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
|
||||
* com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
|
||||
*/
|
||||
public RenderState.BlendFunc dfactorAlpha = RenderState.BlendFunc.One;
|
||||
|
||||
/**
|
||||
* @see RenderState#setWireframe(boolean)
|
||||
*/
|
||||
@ -266,6 +291,10 @@ public class RenderContext {
|
||||
blendMode = RenderState.BlendMode.Off;
|
||||
blendEquation = RenderState.BlendEquation.Add;
|
||||
blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor;
|
||||
sfactorRGB = BlendFunc.One;
|
||||
dfactorRGB = BlendFunc.One;
|
||||
sfactorAlpha = BlendFunc.One;
|
||||
dfactorAlpha = BlendFunc.One;
|
||||
wireframe = false;
|
||||
boundShaderProgram = 0;
|
||||
boundShader = null;
|
||||
|
@ -642,7 +642,7 @@ public class RenderManager {
|
||||
throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
|
||||
}
|
||||
|
||||
gm.getMaterial().preload(this);
|
||||
gm.getMaterial().preload(this, gm);
|
||||
Mesh mesh = gm.getMesh();
|
||||
if (mesh != null
|
||||
&& mesh.getVertexCount() != 0
|
||||
|
@ -35,6 +35,7 @@ import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.shader.BufferObject;
|
||||
import com.jme3.shader.Shader;
|
||||
import com.jme3.shader.Shader.ShaderSource;
|
||||
import com.jme3.system.AppSettings;
|
||||
@ -267,12 +268,26 @@ public interface Renderer {
|
||||
*/
|
||||
public void updateBufferData(VertexBuffer vb);
|
||||
|
||||
/**
|
||||
* Uploads data of the buffer object on the GPU.
|
||||
*
|
||||
* @param bo the buffer object to upload.
|
||||
*/
|
||||
public void updateBufferData(BufferObject bo);
|
||||
|
||||
/**
|
||||
* Deletes a vertex buffer from the GPU.
|
||||
* @param vb The vertex buffer to delete
|
||||
*/
|
||||
public void deleteBuffer(VertexBuffer vb);
|
||||
|
||||
/**
|
||||
* Deletes the buffer object from the GPU.
|
||||
*
|
||||
* @param bo the buffer object to delete.
|
||||
*/
|
||||
public void deleteBuffer(BufferObject bo);
|
||||
|
||||
/**
|
||||
* Renders <code>count</code> meshes, with the geometry data supplied and
|
||||
* per-instance data supplied.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -83,6 +83,46 @@ public interface GL3 extends GL2 {
|
||||
public static final int GL_RGB_INTEGER = 36248;
|
||||
public static final int GL_RGBA_INTEGER = 36249;
|
||||
|
||||
public static final int GL_UNIFORM_OFFSET = 0x8A3B;
|
||||
|
||||
/**
|
||||
* Accepted by the {@code target} parameters of BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, and GetBufferPointerv.
|
||||
*/
|
||||
public static final int GL_UNIFORM_BUFFER = 0x8A11;
|
||||
|
||||
/**
|
||||
* Accepted by the {@code pname} parameter of GetActiveUniformBlockiv.
|
||||
*/
|
||||
public static final int GL_UNIFORM_BLOCK_BINDING = 0x8A3F;
|
||||
public static final int GL_UNIFORM_BLOCK_DATA_SIZE = 0x8A40;
|
||||
public static final int GL_UNIFORM_BLOCK_NAME_LENGTH = 0x8A41;
|
||||
public static final int GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS = 0x8A42;
|
||||
public static final int GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES = 0x8A43;
|
||||
public static final int GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = 0x8A44;
|
||||
public static final int GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER = 0x8A45;
|
||||
public static final int GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER = 0x8A46;
|
||||
|
||||
/**
|
||||
* Accepted by the <pname> parameter of GetBooleanv, GetIntegerv,
|
||||
* GetFloatv, and GetDoublev:
|
||||
*/
|
||||
public static final int GL_MAX_VERTEX_UNIFORM_BLOCKS = 0x8A2B;
|
||||
public static final int GL_MAX_GEOMETRY_UNIFORM_BLOCKS = 0x8A2C;
|
||||
public static final int GL_MAX_FRAGMENT_UNIFORM_BLOCKS = 0x8A2D;
|
||||
public static final int GL_MAX_COMBINED_UNIFORM_BLOCKS = 0x8A2E;
|
||||
public static final int GL_MAX_UNIFORM_BUFFER_BINDINGS = 0x8A2F;
|
||||
public static final int GL_MAX_UNIFORM_BLOCK_SIZE = 0x8A30;
|
||||
public static final int GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS = 0x8A31;
|
||||
public static final int GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS = 0x8A32;
|
||||
public static final int GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS = 0x8A33;
|
||||
public static final int GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0x8A34;
|
||||
|
||||
/**
|
||||
* Accepted by the {@code target} parameters of BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, GetBufferPointerv,
|
||||
* BindBufferRange, BindBufferOffset and BindBufferBase.
|
||||
*/
|
||||
public static final int GL_TRANSFORM_FEEDBACK_BUFFER = 0x8C8E;
|
||||
|
||||
/**
|
||||
* <p><a target="_blank" href="http://docs.gl/gl4/glBindFragDataLocation">Reference Page</a></p>
|
||||
* <p>
|
||||
@ -128,4 +168,47 @@ public interface GL3 extends GL2 {
|
||||
* @param index the index of the particular element being queried.
|
||||
*/
|
||||
public String glGetString(int name, int index); /// GL3+
|
||||
|
||||
|
||||
/**
|
||||
* <p><a target="_blank" href="http://docs.gl/gl4/glGetUniformBlockIndex">Reference Page</a></p>
|
||||
*
|
||||
* Retrieves the index of a named uniform block.
|
||||
*
|
||||
* @param program the name of a program containing the uniform block.
|
||||
* @param uniformBlockName an array of characters to containing the name of the uniform block whose index to retrieve.
|
||||
* @return the block index.
|
||||
*/
|
||||
public int glGetUniformBlockIndex(int program, String uniformBlockName);
|
||||
|
||||
/**
|
||||
* <p><a target="_blank" href="http://docs.gl/gl4/glBindBufferBase">Reference Page</a></p>
|
||||
*
|
||||
* Binds a buffer object to an indexed buffer target.
|
||||
*
|
||||
* @param target the target of the bind operation. One of:<br><table><tr><td>{@link #GL_TRANSFORM_FEEDBACK_BUFFER TRANSFORM_FEEDBACK_BUFFER}</td><td>{@link #GL_UNIFORM_BUFFER UNIFORM_BUFFER}</td><td>{@link GL4#GL_ATOMIC_COUNTER_BUFFER ATOMIC_COUNTER_BUFFER}</td><td>{@link GL4#GL_SHADER_STORAGE_BUFFER SHADER_STORAGE_BUFFER}</td></tr></table>
|
||||
* @param index the index of the binding point within the array specified by {@code target}
|
||||
* @param buffer a buffer object to bind to the specified binding point
|
||||
*/
|
||||
public void glBindBufferBase(int target, int index, int buffer);
|
||||
|
||||
/**
|
||||
* Binding points for active uniform blocks are assigned using glUniformBlockBinding. Each of a program's active
|
||||
* uniform blocks has a corresponding uniform buffer binding point. program is the name of a program object for
|
||||
* which the command glLinkProgram has been issued in the past.
|
||||
* <p>
|
||||
* If successful, glUniformBlockBinding specifies that program will use the data store of the buffer object bound
|
||||
* to the binding point uniformBlockBinding to extract the values of the uniforms in the uniform block identified
|
||||
* by uniformBlockIndex.
|
||||
* <p>
|
||||
* When a program object is linked or re-linked, the uniform buffer object binding point assigned to each of its
|
||||
* active uniform blocks is reset to zero.
|
||||
*
|
||||
* @param program The name of a program object containing the active uniform block whose binding to
|
||||
* assign.
|
||||
* @param uniformBlockIndex The index of the active uniform block within program whose binding to assign.
|
||||
* @param uniformBlockBinding Specifies the binding point to which to bind the uniform block with index
|
||||
* uniformBlockIndex within program.
|
||||
*/
|
||||
public void glUniformBlockBinding(int program, int uniformBlockIndex, int uniformBlockBinding);
|
||||
}
|
||||
|
@ -42,6 +42,32 @@ public interface GL4 extends GL3 {
|
||||
public static final int GL_TESS_EVALUATION_SHADER = 0x8E87;
|
||||
public static final int GL_PATCHES = 0xE;
|
||||
|
||||
/**
|
||||
* Accepted by the {@code target} parameter of BindBufferBase and BindBufferRange.
|
||||
*/
|
||||
public static final int GL_ATOMIC_COUNTER_BUFFER = 0x92C0;
|
||||
|
||||
/**
|
||||
* Accepted by the {@code target} parameters of BindBuffer, BufferData, BufferSubData, MapBuffer, UnmapBuffer, GetBufferSubData, and GetBufferPointerv.
|
||||
*/
|
||||
public static final int GL_SHADER_STORAGE_BUFFER = 0x90D2;
|
||||
public static final int GL_SHADER_STORAGE_BLOCK = 0x92E6;
|
||||
|
||||
/**
|
||||
* Accepted by the <pname> parameter of GetIntegerv, GetBooleanv,
|
||||
* GetInteger64v, GetFloatv, and GetDoublev:
|
||||
*/
|
||||
public static final int GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = 0x90D6;
|
||||
public static final int GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS = 0x90D7;
|
||||
public static final int GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS = 0x90D8;
|
||||
public static final int GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS = 0x90D9;
|
||||
public static final int GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS = 0x90DA;
|
||||
public static final int GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS = 0x90DB;
|
||||
public static final int GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS = 0x90DC;
|
||||
public static final int GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 0x90DD;
|
||||
public static final int GL_MAX_SHADER_STORAGE_BLOCK_SIZE = 0x90DE;
|
||||
public static final int GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT = 0x90DF;
|
||||
|
||||
/**
|
||||
* <p><a target="_blank" href="http://docs.gl/gl4/glPatchParameteri">Reference Page</a></p>
|
||||
* <p>
|
||||
@ -50,4 +76,28 @@ public interface GL4 extends GL3 {
|
||||
* @param count the new value for the parameter given by {@code pname}
|
||||
*/
|
||||
public void glPatchParameter(int count);
|
||||
|
||||
/**
|
||||
* Returns the unsigned integer index assigned to a resource named name in the interface type programInterface of
|
||||
* program object program.
|
||||
*
|
||||
* @param program the name of a program object whose resources to query.
|
||||
* @param programInterface a token identifying the interface within program containing the resource named name.
|
||||
* @param name the name of the resource to query the index of.
|
||||
* @return the index of a named resource within a program.
|
||||
*/
|
||||
public int glGetProgramResourceIndex(int program, int programInterface, String name);
|
||||
|
||||
/**
|
||||
* Cchanges the active shader storage block with an assigned index of storageBlockIndex in program object program.
|
||||
* storageBlockIndex must be an active shader storage block index in program. storageBlockBinding must be less
|
||||
* than the value of {@code #GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS}. If successful, glShaderStorageBlockBinding specifies
|
||||
* that program will use the data store of the buffer object bound to the binding point storageBlockBinding to
|
||||
* read and write the values of the buffer variables in the shader storage block identified by storageBlockIndex.
|
||||
*
|
||||
* @param program the name of a program object whose resources to query.
|
||||
* @param storageBlockIndex The index storage block within the program.
|
||||
* @param storageBlockBinding The index storage block binding to associate with the specified storage block.
|
||||
*/
|
||||
public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding);
|
||||
}
|
||||
|
@ -83,6 +83,19 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetUniformBlockIndex(final int program, final String uniformBlockName) {
|
||||
final int result = gl3.glGetUniformBlockIndex(program, uniformBlockName);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindBufferBase(final int target, final int index, final int buffer) {
|
||||
gl3.glBindBufferBase(target, index, buffer);
|
||||
checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteVertexArrays(IntBuffer arrays) {
|
||||
gl3.glDeleteVertexArrays(arrays);
|
||||
@ -95,8 +108,27 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
|
||||
checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetProgramResourceIndex(int program, int programInterface, String name) {
|
||||
final int result = gl4.glGetProgramResourceIndex(program, programInterface, name);
|
||||
checkError();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding) {
|
||||
gl4.glShaderStorageBlockBinding(program, storageBlockIndex, storageBlockBinding);
|
||||
checkError();
|
||||
}
|
||||
|
||||
public void glBlendEquationSeparate(int colorMode, int alphaMode) {
|
||||
gl.glBlendEquationSeparate(colorMode, alphaMode);
|
||||
checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) {
|
||||
gl3.glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding);
|
||||
checkError();
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +196,10 @@ public final class GLImageFormats {
|
||||
format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT);
|
||||
format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, halfFloatFormat);
|
||||
}
|
||||
format(formatToGL, Format.R16F, GL3.GL_R16F, GL3.GL_RED, halfFloatFormat);
|
||||
format(formatToGL, Format.R32F, GL3.GL_R32F, GL3.GL_RED, GL.GL_FLOAT);
|
||||
format(formatToGL, Format.RG16F, GL3.GL_RG16F, GL3.GL_RG, halfFloatFormat);
|
||||
format(formatToGL, Format.RG32F, GL3.GL_RG32F, GL3.GL_RG, GL.GL_FLOAT);
|
||||
format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, halfFloatFormat);
|
||||
format(formatToGL, Format.RGB32F, GLExt.GL_RGB32F_ARB, GL.GL_RGB, GL.GL_FLOAT);
|
||||
format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, halfFloatFormat);
|
||||
@ -220,7 +224,7 @@ public final class GLImageFormats {
|
||||
|
||||
// NOTE: OpenGL ES 2.0 does not support DEPTH_COMPONENT as internal format -- fallback to 16-bit depth.
|
||||
if (caps.contains(Caps.OpenGLES20)) {
|
||||
format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE);
|
||||
format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT);
|
||||
} else {
|
||||
format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE);
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ package com.jme3.renderer.opengl;
|
||||
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.material.RenderState.BlendFunc;
|
||||
import com.jme3.material.RenderState.BlendMode;
|
||||
import com.jme3.material.RenderState.StencilOperation;
|
||||
import com.jme3.material.RenderState.TestFunction;
|
||||
import com.jme3.math.*;
|
||||
@ -44,11 +45,9 @@ import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Format;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
import com.jme3.shader.Attribute;
|
||||
import com.jme3.shader.Shader;
|
||||
import com.jme3.shader.*;
|
||||
import com.jme3.shader.Shader.ShaderSource;
|
||||
import com.jme3.shader.Shader.ShaderType;
|
||||
import com.jme3.shader.Uniform;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.FrameBuffer.RenderBuffer;
|
||||
import com.jme3.texture.Image;
|
||||
@ -60,17 +59,17 @@ import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.ListMap;
|
||||
import com.jme3.util.MipMapGenerator;
|
||||
import com.jme3.util.NativeObjectManager;
|
||||
import java.nio.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import jme3tools.shader.ShaderDebug;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import jme3tools.shader.ShaderDebug;
|
||||
|
||||
public final class GLRenderer implements Renderer {
|
||||
|
||||
@ -479,6 +478,26 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasExtension("GL_ARB_shader_storage_buffer_object")) {
|
||||
caps.add(Caps.ShaderStorageBufferObject);
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxBlockSize, getInteger(GL4.GL_MAX_SHADER_STORAGE_BLOCK_SIZE));
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxComputeBlocks, getInteger(GL4.GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS));
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxGeometryBlocks, getInteger(GL4.GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS));
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxFragmentBlocks, getInteger(GL4.GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS));
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxVertexBlocks, getInteger(GL4.GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS));
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxTessControlBlocks, getInteger(GL4.GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS));
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxTessEvaluationBlocks, getInteger(GL4.GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS));
|
||||
limits.put(Limits.ShaderStorageBufferObjectMaxCombineBlocks, getInteger(GL4.GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS));
|
||||
}
|
||||
|
||||
if (hasExtension("GL_ARB_uniform_buffer_object")) {
|
||||
caps.add(Caps.UniformBufferObject);
|
||||
limits.put(Limits.UniformBufferObjectMaxBlockSize, getInteger(GL3.GL_MAX_UNIFORM_BLOCK_SIZE));
|
||||
limits.put(Limits.UniformBufferObjectMaxGeometryBlocks, getInteger(GL3.GL_MAX_GEOMETRY_UNIFORM_BLOCKS));
|
||||
limits.put(Limits.UniformBufferObjectMaxFragmentBlocks, getInteger(GL3.GL_MAX_FRAGMENT_UNIFORM_BLOCKS));
|
||||
limits.put(Limits.UniformBufferObjectMaxVertexBlocks, getInteger(GL3.GL_MAX_VERTEX_UNIFORM_BLOCKS));
|
||||
}
|
||||
|
||||
// Print context information
|
||||
logger.log(Level.INFO, "OpenGL Renderer Information\n" +
|
||||
" * Vendor: {0}\n" +
|
||||
@ -743,68 +762,57 @@ public final class GLRenderer implements Renderer {
|
||||
context.cullMode = state.getFaceCullMode();
|
||||
}
|
||||
|
||||
if (state.getBlendMode() != context.blendMode) {
|
||||
if (state.getBlendMode() == RenderState.BlendMode.Off) {
|
||||
gl.glDisable(GL.GL_BLEND);
|
||||
} else {
|
||||
if (context.blendMode == RenderState.BlendMode.Off) {
|
||||
gl.glEnable(GL.GL_BLEND);
|
||||
}
|
||||
// Always update the blend equations and factors when using custom blend mode.
|
||||
if (state.getBlendMode() == BlendMode.Custom) {
|
||||
changeBlendMode(BlendMode.Custom);
|
||||
|
||||
blendFuncSeparate(
|
||||
state.getCustomSfactorRGB(),
|
||||
state.getCustomDfactorRGB(),
|
||||
state.getCustomSfactorAlpha(),
|
||||
state.getCustomDfactorAlpha());
|
||||
blendEquationSeparate(state.getBlendEquation(), state.getBlendEquationAlpha());
|
||||
|
||||
// Update the blend equations and factors only on a mode change for all the other (common) blend modes.
|
||||
} else if (state.getBlendMode() != context.blendMode) {
|
||||
changeBlendMode(state.getBlendMode());
|
||||
|
||||
switch (state.getBlendMode()) {
|
||||
case Off:
|
||||
break;
|
||||
case Additive:
|
||||
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE);
|
||||
blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One);
|
||||
break;
|
||||
case AlphaAdditive:
|
||||
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
|
||||
blendFunc(RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One);
|
||||
break;
|
||||
case Alpha:
|
||||
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
|
||||
blendFunc(RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One_Minus_Src_Alpha);
|
||||
break;
|
||||
case PremultAlpha:
|
||||
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
|
||||
blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One_Minus_Src_Alpha);
|
||||
break;
|
||||
case Modulate:
|
||||
gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_ZERO);
|
||||
blendFunc(RenderState.BlendFunc.Dst_Color, RenderState.BlendFunc.Zero);
|
||||
break;
|
||||
case ModulateX2:
|
||||
gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR);
|
||||
blendFunc(RenderState.BlendFunc.Dst_Color, RenderState.BlendFunc.Src_Color);
|
||||
break;
|
||||
case Color:
|
||||
case Screen:
|
||||
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
|
||||
blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One_Minus_Src_Color);
|
||||
break;
|
||||
case Exclusion:
|
||||
gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE_MINUS_SRC_COLOR);
|
||||
break;
|
||||
case Custom:
|
||||
gl.glBlendFuncSeparate(
|
||||
convertBlendFunc(state.getCustomSfactorRGB()),
|
||||
convertBlendFunc(state.getCustomDfactorRGB()),
|
||||
convertBlendFunc(state.getCustomSfactorAlpha()),
|
||||
convertBlendFunc(state.getCustomDfactorAlpha()));
|
||||
blendFunc(RenderState.BlendFunc.One_Minus_Dst_Color, RenderState.BlendFunc.One_Minus_Src_Color);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unrecognized blend mode: "
|
||||
+ state.getBlendMode());
|
||||
}
|
||||
|
||||
if (state.getBlendEquation() != context.blendEquation || state.getBlendEquationAlpha() != context.blendEquationAlpha) {
|
||||
int colorMode = convertBlendEquation(state.getBlendEquation());
|
||||
int alphaMode;
|
||||
if (state.getBlendEquationAlpha() == RenderState.BlendEquationAlpha.InheritColor) {
|
||||
alphaMode = colorMode;
|
||||
} else {
|
||||
alphaMode = convertBlendEquationAlpha(state.getBlendEquationAlpha());
|
||||
}
|
||||
gl.glBlendEquationSeparate(colorMode, alphaMode);
|
||||
context.blendEquation = state.getBlendEquation();
|
||||
context.blendEquationAlpha = state.getBlendEquationAlpha();
|
||||
}
|
||||
}
|
||||
|
||||
context.blendMode = state.getBlendMode();
|
||||
// All of the common modes requires the ADD equation.
|
||||
// (This might change in the future?)
|
||||
blendEquationSeparate(RenderState.BlendEquation.Add, RenderState.BlendEquationAlpha.InheritColor);
|
||||
}
|
||||
|
||||
if (context.stencilTest != state.isStencilTest()
|
||||
@ -852,6 +860,65 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
private void changeBlendMode(RenderState.BlendMode blendMode) {
|
||||
if (blendMode != context.blendMode) {
|
||||
if (blendMode == RenderState.BlendMode.Off) {
|
||||
gl.glDisable(GL.GL_BLEND);
|
||||
} else if (context.blendMode == RenderState.BlendMode.Off) {
|
||||
gl.glEnable(GL.GL_BLEND);
|
||||
}
|
||||
|
||||
context.blendMode = blendMode;
|
||||
}
|
||||
}
|
||||
|
||||
private void blendEquationSeparate(RenderState.BlendEquation blendEquation, RenderState.BlendEquationAlpha blendEquationAlpha) {
|
||||
if (blendEquation != context.blendEquation || blendEquationAlpha != context.blendEquationAlpha) {
|
||||
int glBlendEquation = convertBlendEquation(blendEquation);
|
||||
int glBlendEquationAlpha = blendEquationAlpha == RenderState.BlendEquationAlpha.InheritColor
|
||||
? glBlendEquation
|
||||
: convertBlendEquationAlpha(blendEquationAlpha);
|
||||
gl.glBlendEquationSeparate(glBlendEquation, glBlendEquationAlpha);
|
||||
context.blendEquation = blendEquation;
|
||||
context.blendEquationAlpha = blendEquationAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
private void blendFunc(RenderState.BlendFunc sfactor, RenderState.BlendFunc dfactor) {
|
||||
if (sfactor != context.sfactorRGB
|
||||
|| dfactor != context.dfactorRGB
|
||||
|| sfactor != context.sfactorAlpha
|
||||
|| dfactor != context.dfactorAlpha) {
|
||||
|
||||
gl.glBlendFunc(
|
||||
convertBlendFunc(sfactor),
|
||||
convertBlendFunc(dfactor));
|
||||
context.sfactorRGB = sfactor;
|
||||
context.dfactorRGB = dfactor;
|
||||
context.sfactorAlpha = sfactor;
|
||||
context.dfactorAlpha = dfactor;
|
||||
}
|
||||
}
|
||||
|
||||
private void blendFuncSeparate(RenderState.BlendFunc sfactorRGB, RenderState.BlendFunc dfactorRGB,
|
||||
RenderState.BlendFunc sfactorAlpha, RenderState.BlendFunc dfactorAlpha) {
|
||||
if (sfactorRGB != context.sfactorRGB
|
||||
|| dfactorRGB != context.dfactorRGB
|
||||
|| sfactorAlpha != context.sfactorAlpha
|
||||
|| dfactorAlpha != context.dfactorAlpha) {
|
||||
|
||||
gl.glBlendFuncSeparate(
|
||||
convertBlendFunc(sfactorRGB),
|
||||
convertBlendFunc(dfactorRGB),
|
||||
convertBlendFunc(sfactorAlpha),
|
||||
convertBlendFunc(dfactorAlpha));
|
||||
context.sfactorRGB = sfactorRGB;
|
||||
context.dfactorRGB = dfactorRGB;
|
||||
context.sfactorAlpha = sfactorAlpha;
|
||||
context.dfactorAlpha = dfactorAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
private int convertBlendEquation(RenderState.BlendEquation blendEquation) {
|
||||
switch (blendEquation) {
|
||||
case Add:
|
||||
@ -1001,12 +1068,25 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postFrame() {
|
||||
objManager.deleteUnused(this);
|
||||
OpenCLObjectManager.getInstance().deleteUnusedObjects();
|
||||
gl.resetStats();
|
||||
}
|
||||
|
||||
protected void bindProgram(Shader shader) {
|
||||
int shaderId = shader.getId();
|
||||
if (context.boundShaderProgram != shaderId) {
|
||||
gl.glUseProgram(shaderId);
|
||||
statistics.onShaderUse(shader, true);
|
||||
context.boundShader = shader;
|
||||
context.boundShaderProgram = shaderId;
|
||||
} else {
|
||||
statistics.onShaderUse(shader, false);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************\
|
||||
|* Shaders *|
|
||||
\*********************************************************************/
|
||||
@ -1021,18 +1101,6 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
protected void bindProgram(Shader shader) {
|
||||
int shaderId = shader.getId();
|
||||
if (context.boundShaderProgram != shaderId) {
|
||||
gl.glUseProgram(shaderId);
|
||||
statistics.onShaderUse(shader, true);
|
||||
context.boundShader = shader;
|
||||
context.boundShaderProgram = shaderId;
|
||||
} else {
|
||||
statistics.onShaderUse(shader, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateUniform(Shader shader, Uniform uniform) {
|
||||
int shaderId = shader.getId();
|
||||
|
||||
@ -1138,6 +1206,58 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the buffer block for the shader.
|
||||
*
|
||||
* @param shader the shader.
|
||||
* @param bufferBlock the storage block.
|
||||
*/
|
||||
protected void updateShaderBufferBlock(final Shader shader, final ShaderBufferBlock bufferBlock) {
|
||||
|
||||
assert bufferBlock.getName() != null;
|
||||
assert shader.getId() > 0;
|
||||
|
||||
final BufferObject bufferObject = bufferBlock.getBufferObject();
|
||||
if (bufferObject.getUniqueId() == -1 || bufferObject.isUpdateNeeded()) {
|
||||
updateBufferData(bufferObject);
|
||||
}
|
||||
|
||||
if (!bufferBlock.isUpdateNeeded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bindProgram(shader);
|
||||
|
||||
final int shaderId = shader.getId();
|
||||
final BufferObject.BufferType bufferType = bufferObject.getBufferType();
|
||||
|
||||
bindBuffer(bufferBlock, bufferObject, shaderId, bufferType);
|
||||
|
||||
bufferBlock.clearUpdateNeeded();
|
||||
}
|
||||
|
||||
private void bindBuffer(final ShaderBufferBlock bufferBlock, final BufferObject bufferObject, final int shaderId,
|
||||
final BufferObject.BufferType bufferType) {
|
||||
|
||||
switch (bufferType) {
|
||||
case UniformBufferObject: {
|
||||
final int blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName());
|
||||
gl3.glBindBufferBase(GL3.GL_UNIFORM_BUFFER, bufferObject.getBinding(), bufferObject.getId());
|
||||
gl3.glUniformBlockBinding(GL3.GL_UNIFORM_BUFFER, blockIndex, bufferObject.getBinding());
|
||||
break;
|
||||
}
|
||||
case ShaderStorageBufferObject: {
|
||||
final int blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName());
|
||||
gl4.glShaderStorageBlockBinding(shaderId, blockIndex, bufferObject.getBinding());
|
||||
gl4.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, bufferObject.getBinding(), bufferObject.getId());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Doesn't support binding of " + bufferType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateShaderUniforms(Shader shader) {
|
||||
ListMap<String, Uniform> uniforms = shader.getUniformMap();
|
||||
for (int i = 0; i < uniforms.size(); i++) {
|
||||
@ -1148,6 +1268,18 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all shader's buffer blocks.
|
||||
*
|
||||
* @param shader the shader.
|
||||
*/
|
||||
protected void updateShaderBufferBlocks(final Shader shader) {
|
||||
final ListMap<String, ShaderBufferBlock> bufferBlocks = shader.getBufferBlockMap();
|
||||
for (int i = 0; i < bufferBlocks.size(); i++) {
|
||||
updateShaderBufferBlock(shader, bufferBlocks.getValue(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected void resetUniformLocations(Shader shader) {
|
||||
ListMap<String, Uniform> uniforms = shader.getUniformMap();
|
||||
for (int i = 0; i < uniforms.size(); i++) {
|
||||
@ -1195,6 +1327,7 @@ public final class GLRenderer implements Renderer {
|
||||
+ "Only GLSL 1.00 shaders are supported.");
|
||||
}
|
||||
|
||||
boolean insertPrecision = false;
|
||||
// Upload shader source.
|
||||
// Merge the defines and source code.
|
||||
stringBuf.setLength(0);
|
||||
@ -1214,7 +1347,7 @@ public final class GLRenderer implements Renderer {
|
||||
|
||||
if (source.getType() == ShaderType.Fragment) {
|
||||
// GLES2 requires precision qualifier.
|
||||
stringBuf.append("precision mediump float;\n");
|
||||
insertPrecision = true;
|
||||
}
|
||||
} else {
|
||||
// version 100 does not exist in desktop GLSL.
|
||||
@ -1233,6 +1366,14 @@ public final class GLRenderer implements Renderer {
|
||||
stringBuf.append(source.getDefines());
|
||||
stringBuf.append(source.getSource());
|
||||
|
||||
if(insertPrecision){
|
||||
// precision token is not a preprocessor dirrective therefore it must be placed after #extension tokens to avoid
|
||||
// Error P0001: Extension directive must occur before any non-preprocessor tokens
|
||||
int idx = stringBuf.lastIndexOf("#extension");
|
||||
idx = stringBuf.indexOf("\n", idx);
|
||||
stringBuf.insert(idx + 1, "precision mediump float;\n");
|
||||
}
|
||||
|
||||
intBuf1.clear();
|
||||
intBuf1.put(0, stringBuf.length());
|
||||
gl.glShaderSource(id, new String[]{ stringBuf.toString() }, intBuf1);
|
||||
@ -1366,6 +1507,7 @@ public final class GLRenderer implements Renderer {
|
||||
assert shader.getId() > 0;
|
||||
|
||||
updateShaderUniforms(shader);
|
||||
updateShaderBufferBlocks(shader);
|
||||
bindProgram(shader);
|
||||
}
|
||||
}
|
||||
@ -2454,6 +2596,58 @@ public final class GLRenderer implements Renderer {
|
||||
vb.clearUpdateNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBufferData(final BufferObject bo) {
|
||||
|
||||
int maxSize = Integer.MAX_VALUE;
|
||||
|
||||
final BufferObject.BufferType bufferType = bo.getBufferType();
|
||||
|
||||
if (!caps.contains(bufferType.getRequiredCaps())) {
|
||||
throw new IllegalArgumentException("The current video hardware doesn't support " + bufferType);
|
||||
}
|
||||
|
||||
final ByteBuffer data = bo.computeData(maxSize);
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("Can't upload BO without data.");
|
||||
}
|
||||
|
||||
int bufferId = bo.getId();
|
||||
if (bufferId == -1) {
|
||||
|
||||
// create buffer
|
||||
intBuf1.clear();
|
||||
gl.glGenBuffers(intBuf1);
|
||||
bufferId = intBuf1.get(0);
|
||||
|
||||
bo.setId(bufferId);
|
||||
|
||||
objManager.registerObject(bo);
|
||||
}
|
||||
|
||||
data.rewind();
|
||||
|
||||
switch (bufferType) {
|
||||
case UniformBufferObject: {
|
||||
gl3.glBindBuffer(GL3.GL_UNIFORM_BUFFER, bufferId);
|
||||
gl3.glBufferData(GL4.GL_UNIFORM_BUFFER, data, GL3.GL_DYNAMIC_DRAW);
|
||||
gl3.glBindBuffer(GL4.GL_UNIFORM_BUFFER, 0);
|
||||
break;
|
||||
}
|
||||
case ShaderStorageBufferObject: {
|
||||
gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, bufferId);
|
||||
gl4.glBufferData(GL4.GL_SHADER_STORAGE_BUFFER, data, GL4.GL_DYNAMIC_COPY);
|
||||
gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, 0);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Doesn't support binding of " + bufferType);
|
||||
}
|
||||
}
|
||||
|
||||
bo.clearUpdateNeeded();
|
||||
}
|
||||
|
||||
public void deleteBuffer(VertexBuffer vb) {
|
||||
int bufId = vb.getId();
|
||||
if (bufId != -1) {
|
||||
@ -2467,6 +2661,23 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBuffer(final BufferObject bo) {
|
||||
|
||||
int bufferId = bo.getId();
|
||||
if (bufferId == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
intBuf1.clear();
|
||||
intBuf1.put(bufferId);
|
||||
intBuf1.flip();
|
||||
|
||||
gl.glDeleteBuffers(intBuf1);
|
||||
|
||||
bo.resetObject();
|
||||
}
|
||||
|
||||
public void clearVertexAttribs() {
|
||||
IDList attribList = context.attribIndexList;
|
||||
for (int i = 0; i < attribList.oldLen; i++) {
|
||||
|
@ -43,6 +43,7 @@ import com.jme3.material.Material;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.mesh.MorphTarget;
|
||||
import com.jme3.util.TempVars;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.IdentityCloneFunction;
|
||||
@ -86,6 +87,16 @@ public class Geometry extends Spatial {
|
||||
*/
|
||||
protected int startIndex = -1;
|
||||
|
||||
/**
|
||||
* Morph state variable for morph animation
|
||||
*/
|
||||
private float[] morphState;
|
||||
private boolean dirtyMorph = true;
|
||||
// a Morph target that will be used to merge all targets that
|
||||
// can't be handled on the cpu on each frame.
|
||||
private MorphTarget fallbackMorphTarget;
|
||||
private int nbSimultaneousGPUMorph = -1;
|
||||
|
||||
/**
|
||||
* Serialization only. Do not use.
|
||||
*/
|
||||
@ -248,7 +259,7 @@ public class Geometry extends Spatial {
|
||||
@Override
|
||||
public void setMaterial(Material material) {
|
||||
this.material = material;
|
||||
|
||||
nbSimultaneousGPUMorph = -1;
|
||||
if (isGrouped()) {
|
||||
groupNode.onMaterialChange(this);
|
||||
}
|
||||
@ -576,6 +587,80 @@ public class Geometry extends Spatial {
|
||||
this.material = cloner.clone(material);
|
||||
}
|
||||
|
||||
public void setMorphState(float[] state) {
|
||||
if (mesh == null || mesh.getMorphTargets().length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
int nbMorphTargets = mesh.getMorphTargets().length;
|
||||
|
||||
if (morphState == null) {
|
||||
morphState = new float[nbMorphTargets];
|
||||
}
|
||||
System.arraycopy(state, 0, morphState, 0, morphState.length);
|
||||
this.dirtyMorph = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the morph state has changed on the last frame.
|
||||
* @return
|
||||
*/
|
||||
public boolean isDirtyMorph() {
|
||||
return dirtyMorph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seting this to true will stop this geometry morph buffer to be updated,
|
||||
* unless the morph state changes
|
||||
* @param dirtyMorph
|
||||
*/
|
||||
public void setDirtyMorph(boolean dirtyMorph) {
|
||||
this.dirtyMorph = dirtyMorph;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the morph state of this Geometry.
|
||||
* Used internally by the MorphControl.
|
||||
* @return
|
||||
*/
|
||||
public float[] getMorphState() {
|
||||
if (morphState == null) {
|
||||
morphState = new float[mesh.getMorphTargets().length];
|
||||
}
|
||||
return morphState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of morph targets that can be handled on the GPU simultaneously for this geometry.
|
||||
* Note that it depends on the material set on this geometry.
|
||||
* This number is computed and set by the MorphControl, so it might be available only after the first frame.
|
||||
* Else it's set to -1.
|
||||
* @return the number of simultaneous morph targets handled on the GPU
|
||||
*/
|
||||
public int getNbSimultaneousGPUMorph() {
|
||||
return nbSimultaneousGPUMorph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of morph targets that can be handled on the GPU simultaneously for this geometry.
|
||||
* Note that it depends on the material set on this geometry.
|
||||
* This number is computed and set by the MorphControl, so it might be available only after the first frame.
|
||||
* Else it's set to -1.
|
||||
* WARNING: setting this manually might crash the shader compilation if set too high. Do it at your own risk.
|
||||
* @param nbSimultaneousGPUMorph the number of simultaneous morph targets to be handled on the GPU.
|
||||
*/
|
||||
public void setNbSimultaneousGPUMorph(int nbSimultaneousGPUMorph) {
|
||||
this.nbSimultaneousGPUMorph = nbSimultaneousGPUMorph;
|
||||
}
|
||||
|
||||
public MorphTarget getFallbackMorphTarget() {
|
||||
return fallbackMorphTarget;
|
||||
}
|
||||
|
||||
public void setFallbackMorphTarget(MorphTarget fallbackMorphTarget) {
|
||||
this.fallbackMorphTarget = fallbackMorphTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
super.write(ex);
|
||||
|
@ -39,24 +39,18 @@ import com.jme3.collision.bih.BIHTree;
|
||||
import com.jme3.export.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Triangle;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.VertexBuffer.Format;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.scene.VertexBuffer.*;
|
||||
import com.jme3.scene.mesh.*;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.IntMap;
|
||||
import com.jme3.util.*;
|
||||
import com.jme3.util.IntMap.Entry;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* <code>Mesh</code> is used to store rendering data.
|
||||
@ -171,8 +165,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
|
||||
private CollisionData collisionTree = null;
|
||||
|
||||
private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
|
||||
private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
|
||||
private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<>(VertexBuffer.class);
|
||||
private IntMap<VertexBuffer> buffers = new IntMap<>();
|
||||
private VertexBuffer[] lodLevels;
|
||||
private float pointSize = 1;
|
||||
private float lineWidth = 1;
|
||||
@ -190,6 +184,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
|
||||
private Mode mode = Mode.Triangles;
|
||||
|
||||
private SafeArrayList<MorphTarget> morphTargets;
|
||||
|
||||
/**
|
||||
* Creates a new mesh with no {@link VertexBuffer vertex buffers}.
|
||||
*/
|
||||
@ -210,7 +206,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
clone.meshBound = meshBound.clone();
|
||||
clone.collisionTree = collisionTree != null ? collisionTree : null;
|
||||
clone.buffers = buffers.clone();
|
||||
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
|
||||
clone.buffersList = new SafeArrayList<>(VertexBuffer.class, buffersList);
|
||||
clone.vertexArrayID = -1;
|
||||
if (elementLengths != null) {
|
||||
clone.elementLengths = elementLengths.clone();
|
||||
@ -240,8 +236,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
//clone.collisionTree = collisionTree != null ? collisionTree : null;
|
||||
clone.collisionTree = null; // it will get re-generated in any case
|
||||
|
||||
clone.buffers = new IntMap<VertexBuffer>();
|
||||
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
|
||||
clone.buffers = new IntMap<>();
|
||||
clone.buffersList = new SafeArrayList<>(VertexBuffer.class);
|
||||
for (VertexBuffer vb : buffersList.getArray()){
|
||||
VertexBuffer bufClone = vb.clone();
|
||||
clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
|
||||
@ -704,7 +700,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
@Deprecated
|
||||
public void setInterleaved(){
|
||||
ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
|
||||
ArrayList<VertexBuffer> vbs = new ArrayList<>();
|
||||
vbs.addAll(buffersList);
|
||||
|
||||
// ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
|
||||
@ -827,8 +823,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* {@link #setInterleaved() interleaved} format.
|
||||
*/
|
||||
public void updateCounts(){
|
||||
if (getBuffer(Type.InterleavedData) != null)
|
||||
if (getBuffer(Type.InterleavedData) != null) {
|
||||
throw new IllegalStateException("Should update counts before interleave");
|
||||
}
|
||||
|
||||
VertexBuffer pb = getBuffer(Type.Position);
|
||||
VertexBuffer ib = getBuffer(Type.Index);
|
||||
@ -851,11 +848,13 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
public int getTriangleCount(int lod){
|
||||
if (lodLevels != null){
|
||||
if (lod < 0)
|
||||
if (lod < 0) {
|
||||
throw new IllegalArgumentException("LOD level cannot be < 0");
|
||||
}
|
||||
|
||||
if (lod >= lodLevels.length)
|
||||
if (lod >= lodLevels.length) {
|
||||
throw new IllegalArgumentException("LOD level " + lod + " does not exist!");
|
||||
}
|
||||
|
||||
return computeNumElements(lodLevels[lod].getData().limit());
|
||||
}else if (lod == 0){
|
||||
@ -975,8 +974,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* Sets the mesh's VAO ID. Internal use only.
|
||||
*/
|
||||
public void setId(int id){
|
||||
if (vertexArrayID != -1)
|
||||
if (vertexArrayID != -1) {
|
||||
throw new IllegalStateException("ID has already been set.");
|
||||
}
|
||||
|
||||
vertexArrayID = id;
|
||||
}
|
||||
@ -1044,8 +1044,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* @throws IllegalArgumentException If the buffer type is already set
|
||||
*/
|
||||
public void setBuffer(VertexBuffer vb){
|
||||
if (buffers.containsKey(vb.getBufferType().ordinal()))
|
||||
if (buffers.containsKey(vb.getBufferType().ordinal())) {
|
||||
throw new IllegalArgumentException("Buffer type already set: " + vb.getBufferType());
|
||||
}
|
||||
|
||||
buffers.put(vb.getBufferType().ordinal(), vb);
|
||||
buffersList.add(vb);
|
||||
@ -1158,8 +1159,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
public FloatBuffer getFloatBuffer(Type type) {
|
||||
VertexBuffer vb = getBuffer(type);
|
||||
if (vb == null)
|
||||
if (vb == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (FloatBuffer) vb.getData();
|
||||
}
|
||||
@ -1173,8 +1175,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
public ShortBuffer getShortBuffer(Type type) {
|
||||
VertexBuffer vb = getBuffer(type);
|
||||
if (vb == null)
|
||||
if (vb == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (ShortBuffer) vb.getData();
|
||||
}
|
||||
@ -1186,8 +1189,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* @return A virtual or wrapped index buffer to read the data as a list
|
||||
*/
|
||||
public IndexBuffer getIndicesAsList(){
|
||||
if (mode == Mode.Hybrid)
|
||||
if (mode == Mode.Hybrid) {
|
||||
throw new UnsupportedOperationException("Hybrid mode not supported");
|
||||
}
|
||||
|
||||
IndexBuffer ib = getIndexBuffer();
|
||||
if (ib != null){
|
||||
@ -1216,8 +1220,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
public IndexBuffer getIndexBuffer() {
|
||||
VertexBuffer vb = getBuffer(Type.Index);
|
||||
if (vb == null)
|
||||
if (vb == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return IndexBuffer.wrapIndexBuffer(vb.getData());
|
||||
}
|
||||
@ -1240,8 +1245,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
IndexBuffer indexBuf = getIndexBuffer();
|
||||
int numIndices = indexBuf.size();
|
||||
|
||||
IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices);
|
||||
ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>();
|
||||
IntMap<Integer> oldIndicesToNewIndices = new IntMap<>(numIndices);
|
||||
ArrayList<Integer> newIndicesToOldIndices = new ArrayList<>();
|
||||
int newIndex = 0;
|
||||
|
||||
for (int i = 0; i < numIndices; i++) {
|
||||
@ -1352,14 +1357,17 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
public void scaleTextureCoordinates(Vector2f scaleFactor){
|
||||
VertexBuffer tc = getBuffer(Type.TexCoord);
|
||||
if (tc == null)
|
||||
if (tc == null) {
|
||||
throw new IllegalStateException("The mesh has no texture coordinates");
|
||||
}
|
||||
|
||||
if (tc.getFormat() != VertexBuffer.Format.Float)
|
||||
if (tc.getFormat() != VertexBuffer.Format.Float) {
|
||||
throw new UnsupportedOperationException("Only float texture coord format is supported");
|
||||
}
|
||||
|
||||
if (tc.getNumComponents() != 2)
|
||||
if (tc.getNumComponents() != 2) {
|
||||
throw new UnsupportedOperationException("Only 2D texture coords are supported");
|
||||
}
|
||||
|
||||
FloatBuffer fb = (FloatBuffer) tc.getData();
|
||||
fb.clear();
|
||||
@ -1446,13 +1454,23 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
getBuffer(Type.HWBoneIndex) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use isAnimatedByJoint
|
||||
* @param boneIndex
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isAnimatedByBone(int boneIndex) {
|
||||
return isAnimatedByJoint(boneIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the specified bone animates this mesh.
|
||||
*
|
||||
* @param boneIndex the bone's index in its skeleton
|
||||
* @param jointIndex the bone's index in its skeleton
|
||||
* @return true if the specified bone animates this mesh, otherwise false
|
||||
*/
|
||||
public boolean isAnimatedByBone(int boneIndex) {
|
||||
public boolean isAnimatedByJoint(int jointIndex) {
|
||||
VertexBuffer biBuf = getBuffer(VertexBuffer.Type.BoneIndex);
|
||||
VertexBuffer wBuf = getBuffer(VertexBuffer.Type.BoneWeight);
|
||||
if (biBuf == null || wBuf == null) {
|
||||
@ -1472,7 +1490,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
/*
|
||||
* Test each vertex to determine whether the bone affects it.
|
||||
*/
|
||||
int biByte = boneIndex;
|
||||
int biByte = jointIndex;
|
||||
for (int vIndex = 0; vIndex < numVertices; vIndex++) {
|
||||
for (int wIndex = 0; wIndex < 4; wIndex++) {
|
||||
int bIndex = boneIndexBuffer.get();
|
||||
@ -1501,16 +1519,26 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
return patchVertexCount;
|
||||
}
|
||||
|
||||
|
||||
public void addMorphTarget(MorphTarget target) {
|
||||
if (morphTargets == null) {
|
||||
morphTargets = new SafeArrayList<>(MorphTarget.class);
|
||||
}
|
||||
morphTargets.add(target);
|
||||
}
|
||||
|
||||
public MorphTarget[] getMorphTargets() {
|
||||
return morphTargets.getArray();
|
||||
}
|
||||
|
||||
public boolean hasMorphTargets() {
|
||||
return morphTargets != null && !morphTargets.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule out = ex.getCapsule(this);
|
||||
|
||||
// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
|
||||
// for (Entry<VertexBuffer> buf : buffers){
|
||||
// if (buf.getValue() != null)
|
||||
// map.put(buf.getKey()+"a", buf.getValue());
|
||||
// }
|
||||
// out.writeStringSavableMap(map, "buffers", null);
|
||||
|
||||
out.write(meshBound, "modelBound", null);
|
||||
out.write(vertCount, "vertCount", -1);
|
||||
out.write(elementCount, "elementCount", -1);
|
||||
@ -1545,8 +1573,12 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
}
|
||||
|
||||
out.write(lodLevels, "lodLevels", null);
|
||||
if (morphTargets != null) {
|
||||
out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter im) throws IOException {
|
||||
InputCapsule in = im.getCapsule(this);
|
||||
meshBound = (BoundingVolume) in.readSavable("modelBound", null);
|
||||
@ -1583,6 +1615,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
lodLevels = new VertexBuffer[lodLevelsSavable.length];
|
||||
System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
|
||||
}
|
||||
|
||||
ArrayList<Savable> l = in.readSavableArrayList("morphTargets", null);
|
||||
if (l != null) {
|
||||
morphTargets = new SafeArrayList(MorphTarget.class, l);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
*/
|
||||
package com.jme3.scene;
|
||||
|
||||
import com.jme3.anim.util.HasLocalTransform;
|
||||
import com.jme3.asset.AssetKey;
|
||||
import com.jme3.asset.CloneableSmartAsset;
|
||||
import com.jme3.bounding.BoundingVolume;
|
||||
@ -67,7 +68,7 @@ import java.util.logging.Logger;
|
||||
* @author Joshua Slack
|
||||
* @version $Revision: 4075 $, $Data$
|
||||
*/
|
||||
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable {
|
||||
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable, HasLocalTransform {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Spatial.class.getName());
|
||||
|
||||
|
@ -212,7 +212,42 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* Format should be {@link Format#Float} and number of components
|
||||
* should be 16.
|
||||
*/
|
||||
InstanceData
|
||||
InstanceData,
|
||||
|
||||
/**
|
||||
* Morph animations targets.
|
||||
* Supports up tp 14 morph target buffers at the same time
|
||||
* Limited due to the limited number of attributes you can bind to a vertex shader usually 16
|
||||
* <p>
|
||||
* MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers.
|
||||
* So we can support up to
|
||||
* 14 simultaneous POSITION targets
|
||||
* 7 simultaneous POSITION and NORMAL targets
|
||||
* 4 simultaneous POSTION, NORMAL and TANGENT targets.
|
||||
* <p>
|
||||
* Note that the MorphControl will find how many buffers can be supported for each mesh/material combination.
|
||||
* Note that all buffers have 3 components (Vector3f) even the Tangent buffer that
|
||||
* does not contain the w (handedness) component that will not be interpolated for morph animation.
|
||||
* <p>
|
||||
* Note that those buffers contain the difference between the base buffer (POSITION, NORMAL or TANGENT) and the target value
|
||||
* So that you can interpolate with a MADD operation in the vertex shader
|
||||
* position = weight * diffPosition + basePosition;
|
||||
*/
|
||||
MorphTarget0,
|
||||
MorphTarget1,
|
||||
MorphTarget2,
|
||||
MorphTarget3,
|
||||
MorphTarget4,
|
||||
MorphTarget5,
|
||||
MorphTarget6,
|
||||
MorphTarget7,
|
||||
MorphTarget8,
|
||||
MorphTarget9,
|
||||
MorphTarget10,
|
||||
MorphTarget11,
|
||||
MorphTarget12,
|
||||
MorphTarget13,
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,7 +276,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* Mesh data is <em>not</em> sent to GPU at all. It is only
|
||||
* used by the CPU.
|
||||
*/
|
||||
CpuOnly;
|
||||
CpuOnly
|
||||
}
|
||||
|
||||
/**
|
||||
@ -606,9 +641,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* @return The total number of data elements in the data buffer.
|
||||
*/
|
||||
public int getNumElements(){
|
||||
if( data == null ) {
|
||||
return 0;
|
||||
}
|
||||
int elements = data.limit() / components;
|
||||
if (format == Format.Half)
|
||||
if (format == Format.Half) {
|
||||
elements /= 2;
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
@ -639,14 +678,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* argument.
|
||||
*/
|
||||
public void setupData(Usage usage, int components, Format format, Buffer data){
|
||||
if (id != -1)
|
||||
if (id != -1) {
|
||||
throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
|
||||
}
|
||||
|
||||
if (usage == null || format == null || data == null)
|
||||
if (usage == null || format == null || data == null) {
|
||||
throw new IllegalArgumentException("None of the arguments can be null");
|
||||
}
|
||||
|
||||
if (data.isReadOnly())
|
||||
if (data.isReadOnly()) {
|
||||
throw new IllegalArgumentException("VertexBuffer data cannot be read-only.");
|
||||
}
|
||||
|
||||
if (bufType != Type.InstanceData) {
|
||||
if (components < 1 || components > 4) {
|
||||
@ -717,11 +759,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* Converts single floating-point data to {@link Format#Half half} floating-point data.
|
||||
*/
|
||||
public void convertToHalf(){
|
||||
if (id != -1)
|
||||
if (id != -1) {
|
||||
throw new UnsupportedOperationException("Data has already been sent.");
|
||||
}
|
||||
|
||||
if (format != Format.Float)
|
||||
if (format != Format.Float) {
|
||||
throw new IllegalStateException("Format must be float!");
|
||||
}
|
||||
|
||||
int numElements = data.limit() / components;
|
||||
format = Format.Half;
|
||||
@ -910,8 +954,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* match.
|
||||
*/
|
||||
public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){
|
||||
if (outVb.format != format || outVb.components != components)
|
||||
if (outVb.format != format || outVb.components != components) {
|
||||
throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
|
||||
}
|
||||
|
||||
int inPos = inIndex * components;
|
||||
int outPos = outIndex * components;
|
||||
@ -978,8 +1023,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* of elements with the given number of components in each element.
|
||||
*/
|
||||
public static Buffer createBuffer(Format format, int components, int numElements){
|
||||
if (components < 1 || components > 4)
|
||||
if (components < 1 || components > 4) {
|
||||
throw new IllegalArgumentException("Num components must be between 1 and 4");
|
||||
}
|
||||
|
||||
int total = numElements * components;
|
||||
|
||||
|
@ -42,10 +42,20 @@ import java.nio.FloatBuffer;
|
||||
public class WireFrustum extends Mesh {
|
||||
|
||||
public WireFrustum(Vector3f[] points){
|
||||
if (points != null)
|
||||
setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
|
||||
initGeom(this, points);
|
||||
}
|
||||
|
||||
setBuffer(Type.Index, 2,
|
||||
public static Mesh makeFrustum(Vector3f[] points){
|
||||
Mesh m = new Mesh();
|
||||
initGeom(m, points);
|
||||
return m;
|
||||
}
|
||||
|
||||
private static void initGeom(Mesh m, Vector3f[] points) {
|
||||
if (points != null)
|
||||
m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
|
||||
|
||||
m.setBuffer(Type.Index, 2,
|
||||
new short[]{
|
||||
0, 1,
|
||||
1, 2,
|
||||
@ -63,8 +73,8 @@ public class WireFrustum extends Mesh {
|
||||
3, 7,
|
||||
}
|
||||
);
|
||||
getBuffer(Type.Index).setUsage(Usage.Static);
|
||||
setMode(Mode.Lines);
|
||||
m.getBuffer(Type.Index).setUsage(Usage.Static);
|
||||
m.setMode(Mode.Lines);
|
||||
}
|
||||
|
||||
public void update(Vector3f[] points){
|
||||
|
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.jme3.scene.debug.custom;
|
||||
|
||||
import com.jme3.anim.*;
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.collision.CollisionResults;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.*;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.scene.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Nehon
|
||||
*/
|
||||
public class ArmatureDebugAppState extends BaseAppState {
|
||||
|
||||
public static final float CLICK_MAX_DELAY = 0.2f;
|
||||
private Node debugNode = new Node("debugNode");
|
||||
private Map<Armature, ArmatureDebugger> armatures = new HashMap<>();
|
||||
private Map<Armature, Joint> selectedBones = new HashMap<>();
|
||||
private Application app;
|
||||
private boolean displayAllJoints = false;
|
||||
private float clickDelay = -1;
|
||||
Vector3f tmp = new Vector3f();
|
||||
Vector3f tmp2 = new Vector3f();
|
||||
ViewPort vp;
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
vp = app.getRenderManager().createMainView("debug", app.getCamera());
|
||||
vp.attachScene(debugNode);
|
||||
vp.setClearDepth(true);
|
||||
this.app = app;
|
||||
for (ArmatureDebugger armatureDebugger : armatures.values()) {
|
||||
armatureDebugger.initialize(app.getAssetManager(), app.getCamera());
|
||||
}
|
||||
app.getInputManager().addListener(actionListener, "shoot", "toggleJoints");
|
||||
app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
||||
app.getInputManager().addMapping("toggleJoints", new KeyTrigger(KeyInput.KEY_F10));
|
||||
|
||||
debugNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal()));
|
||||
|
||||
debugNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)));
|
||||
vp.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
vp.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
vp.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (clickDelay > -1) {
|
||||
clickDelay += tpf;
|
||||
}
|
||||
debugNode.updateLogicalState(tpf);
|
||||
debugNode.updateGeometricState();
|
||||
|
||||
}
|
||||
|
||||
public ArmatureDebugger addArmatureFrom(SkinningControl skinningControl) {
|
||||
Armature armature = skinningControl.getArmature();
|
||||
Spatial forSpatial = skinningControl.getSpatial();
|
||||
return addArmatureFrom(armature, forSpatial);
|
||||
}
|
||||
|
||||
public ArmatureDebugger addArmatureFrom(Armature armature, Spatial forSpatial) {
|
||||
|
||||
ArmatureDebugger ad = armatures.get(armature);
|
||||
if(ad != null){
|
||||
return ad;
|
||||
}
|
||||
|
||||
JointInfoVisitor visitor = new JointInfoVisitor(armature);
|
||||
forSpatial.depthFirstTraversal(visitor);
|
||||
|
||||
ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature, visitor.deformingJoints);
|
||||
ad.setLocalTransform(forSpatial.getWorldTransform());
|
||||
if (forSpatial instanceof Node) {
|
||||
List<Geometry> geoms = new ArrayList<>();
|
||||
findGeoms((Node) forSpatial, geoms);
|
||||
if (geoms.size() == 1) {
|
||||
ad.setLocalTransform(geoms.get(0).getWorldTransform());
|
||||
}
|
||||
}
|
||||
armatures.put(armature, ad);
|
||||
debugNode.attachChild(ad);
|
||||
if (isInitialized()) {
|
||||
ad.initialize(app.getAssetManager(), app.getCamera());
|
||||
}
|
||||
return ad;
|
||||
}
|
||||
|
||||
private void findGeoms(Node node, List<Geometry> geoms) {
|
||||
for (Spatial spatial : node.getChildren()) {
|
||||
if (spatial instanceof Geometry) {
|
||||
geoms.add((Geometry) spatial);
|
||||
} else if (spatial instanceof Node) {
|
||||
findGeoms((Node) spatial, geoms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ActionListener actionListener = new ActionListener() {
|
||||
public void onAction(String name, boolean isPressed, float tpf) {
|
||||
if (name.equals("shoot") && isPressed) {
|
||||
clickDelay = 0;
|
||||
}
|
||||
if (name.equals("shoot") && !isPressed && clickDelay < CLICK_MAX_DELAY) {
|
||||
Vector2f click2d = app.getInputManager().getCursorPosition();
|
||||
CollisionResults results = new CollisionResults();
|
||||
|
||||
Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f, tmp);
|
||||
Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f, tmp2).subtractLocal(click3d);
|
||||
Ray ray = new Ray(click3d, dir);
|
||||
debugNode.collideWith(ray, results);
|
||||
|
||||
if (results.size() == 0) {
|
||||
for (ArmatureDebugger ad : armatures.values()) {
|
||||
ad.select(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The closest result is the target that the player picked:
|
||||
Geometry target = results.getClosestCollision().getGeometry();
|
||||
for (ArmatureDebugger ad : armatures.values()) {
|
||||
Joint selectedjoint = ad.select(target);
|
||||
if (selectedjoint != null) {
|
||||
selectedBones.put(ad.getArmature(), selectedjoint);
|
||||
System.err.println("-----------------------");
|
||||
System.err.println("Selected Joint : " + selectedjoint.getName() + " in armature " + ad.getName());
|
||||
System.err.println("Root Bone : " + (selectedjoint.getParent() == null));
|
||||
System.err.println("-----------------------");
|
||||
System.err.println("Local translation: " + selectedjoint.getLocalTranslation());
|
||||
System.err.println("Local rotation: " + selectedjoint.getLocalRotation());
|
||||
System.err.println("Local scale: " + selectedjoint.getLocalScale());
|
||||
System.err.println("---");
|
||||
System.err.println("Model translation: " + selectedjoint.getModelTransform().getTranslation());
|
||||
System.err.println("Model rotation: " + selectedjoint.getModelTransform().getRotation());
|
||||
System.err.println("Model scale: " + selectedjoint.getModelTransform().getScale());
|
||||
System.err.println("---");
|
||||
System.err.println("Bind inverse Transform: ");
|
||||
System.err.println(selectedjoint.getInverseModelBindMatrix());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name.equals("toggleJoints") && isPressed) {
|
||||
displayAllJoints = !displayAllJoints;
|
||||
for (ArmatureDebugger ad : armatures.values()) {
|
||||
ad.displayNonDeformingJoint(displayAllJoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// public Map<Skeleton, Bone> getSelectedBones() {
|
||||
// return selectedBones;
|
||||
// }
|
||||
|
||||
public Node getDebugNode() {
|
||||
return debugNode;
|
||||
}
|
||||
|
||||
public void setDebugNode(Node debugNode) {
|
||||
this.debugNode = debugNode;
|
||||
}
|
||||
|
||||
private class JointInfoVisitor extends SceneGraphVisitorAdapter {
|
||||
|
||||
List<Joint> deformingJoints = new ArrayList<>();
|
||||
Armature armature;
|
||||
|
||||
public JointInfoVisitor(Armature armature) {
|
||||
this.armature = armature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Geometry g) {
|
||||
for (Joint joint : armature.getJointList()) {
|
||||
if (g.getMesh().isAnimatedByJoint(armature.getJointIndex(joint))) {
|
||||
deformingJoints.add(joint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user