1 /*
2  * Copyright (c) 2009-2010 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package com.jme3.cinematic;
33 
34 import com.jme3.asset.AssetManager;
35 import com.jme3.cinematic.events.MotionTrack;
36 import com.jme3.export.*;
37 import com.jme3.material.Material;
38 import com.jme3.math.ColorRGBA;
39 import com.jme3.math.Spline;
40 import com.jme3.math.Spline.SplineType;
41 import com.jme3.math.Vector2f;
42 import com.jme3.math.Vector3f;
43 import com.jme3.scene.Geometry;
44 import com.jme3.scene.Node;
45 import com.jme3.scene.shape.Box;
46 import com.jme3.scene.shape.Curve;
47 import com.jme3.util.TempVars;
48 import java.io.IOException;
49 import java.util.ArrayList;
50 import java.util.Iterator;
51 import java.util.List;
52 
53 /**
54  * Motion path is used to create a path between way points.
55  * @author Nehon
56  */
57 public class MotionPath implements Savable {
58 
59     private Node debugNode;
60     private AssetManager assetManager;
61     private List<MotionPathListener> listeners;
62     private Spline spline = new Spline();
63     private float eps = 0.0001f;
64 
65     /**
66      * Create a motion Path
67      */
MotionPath()68     public MotionPath() {
69     }
70 
71     /**
72      * interpolate the path giving the time since the beginnin and the motionControl
73      * this methods sets the new localTranslation to the spatial of the motionTrack control.
74      * @param time the time since the animation started
75      * @param control the ocntrol over the moving spatial
76      */
interpolatePath(float time, MotionTrack control)77     public float interpolatePath(float time, MotionTrack control) {
78 
79         float traveledDistance = 0;
80         TempVars vars = TempVars.get();
81         Vector3f temp = vars.vect1;
82         Vector3f tmpVector = vars.vect2;
83         //computing traveled distance according to new time
84         traveledDistance = time * (getLength() / control.getInitialDuration());
85 
86         //getting waypoint index and current value from new traveled distance
87         Vector2f v = getWayPointIndexForDistance(traveledDistance);
88 
89         //setting values
90         control.setCurrentWayPoint((int) v.x);
91         control.setCurrentValue(v.y);
92 
93         //interpolating new position
94         getSpline().interpolate(control.getCurrentValue(), control.getCurrentWayPoint(), temp);
95         if (control.needsDirection()) {
96             tmpVector.set(temp);
97             control.setDirection(tmpVector.subtractLocal(control.getSpatial().getLocalTranslation()).normalizeLocal());
98         }
99 
100         control.getSpatial().setLocalTranslation(temp);
101         vars.release();
102         return traveledDistance;
103     }
104 
attachDebugNode(Node root)105     private void attachDebugNode(Node root) {
106         if (debugNode == null) {
107             debugNode = new Node();
108             Material m = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
109             for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
110                 Vector3f cp = it.next();
111                 Geometry geo = new Geometry("box", new Box(cp, 0.3f, 0.3f, 0.3f));
112                 geo.setMaterial(m);
113                 debugNode.attachChild(geo);
114 
115             }
116             switch (spline.getType()) {
117                 case CatmullRom:
118                     debugNode.attachChild(CreateCatmullRomPath());
119                     break;
120                 case Linear:
121                     debugNode.attachChild(CreateLinearPath());
122                     break;
123                 default:
124                     debugNode.attachChild(CreateLinearPath());
125                     break;
126             }
127 
128             root.attachChild(debugNode);
129         }
130     }
131 
CreateLinearPath()132     private Geometry CreateLinearPath() {
133 
134         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
135         mat.getAdditionalRenderState().setWireframe(true);
136         mat.setColor("Color", ColorRGBA.Blue);
137         Geometry lineGeometry = new Geometry("line", new Curve(spline, 0));
138         lineGeometry.setMaterial(mat);
139         return lineGeometry;
140     }
141 
CreateCatmullRomPath()142     private Geometry CreateCatmullRomPath() {
143 
144         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
145         mat.getAdditionalRenderState().setWireframe(true);
146         mat.setColor("Color", ColorRGBA.Blue);
147         Geometry lineGeometry = new Geometry("line", new Curve(spline, 10));
148         lineGeometry.setMaterial(mat);
149         return lineGeometry;
150     }
151 
152     @Override
write(JmeExporter ex)153     public void write(JmeExporter ex) throws IOException {
154         OutputCapsule oc = ex.getCapsule(this);
155         oc.write(spline, "spline", null);
156     }
157 
158     @Override
read(JmeImporter im)159     public void read(JmeImporter im) throws IOException {
160         InputCapsule in = im.getCapsule(this);
161         spline = (Spline) in.readSavable("spline", null);
162 
163     }
164 
165     /**
166      * compute the index of the waypoint and the interpolation value according to a distance
167      * returns a vector 2 containing the index in the x field and the interpolation value in the y field
168      * @param distance the distance traveled on this path
169      * @return the waypoint index and the interpolation value in a vector2
170      */
getWayPointIndexForDistance(float distance)171     public Vector2f getWayPointIndexForDistance(float distance) {
172         float sum = 0;
173         distance = distance % spline.getTotalLength();
174         int i = 0;
175         for (Float len : spline.getSegmentsLength()) {
176             if (sum + len >= distance) {
177                 return new Vector2f((float) i, (distance - sum) / len);
178             }
179             sum += len;
180             i++;
181         }
182         return new Vector2f((float) spline.getControlPoints().size() - 1, 1.0f);
183     }
184 
185     /**
186      * Addsa waypoint to the path
187      * @param wayPoint a position in world space
188      */
addWayPoint(Vector3f wayPoint)189     public void addWayPoint(Vector3f wayPoint) {
190         spline.addControlPoint(wayPoint);
191     }
192 
193     /**
194      * retruns the length of the path in world units
195      * @return the length
196      */
getLength()197     public float getLength() {
198         return spline.getTotalLength();
199     }
200 
201     /**
202      * returns the waypoint at the given index
203      * @param i the index
204      * @return returns the waypoint position
205      */
getWayPoint(int i)206     public Vector3f getWayPoint(int i) {
207         return spline.getControlPoints().get(i);
208     }
209 
210     /**
211      * remove the waypoint from the path
212      * @param wayPoint the waypoint to remove
213      */
removeWayPoint(Vector3f wayPoint)214     public void removeWayPoint(Vector3f wayPoint) {
215         spline.removeControlPoint(wayPoint);
216     }
217 
218     /**
219      * remove the waypoint at the given index from the path
220      * @param i the index of the waypoint to remove
221      */
removeWayPoint(int i)222     public void removeWayPoint(int i) {
223         removeWayPoint(spline.getControlPoints().get(i));
224     }
225 
226     /**
227      * returns an iterator on the waypoints collection
228      * @return
229      */
iterator()230     public Iterator<Vector3f> iterator() {
231         return spline.getControlPoints().iterator();
232     }
233 
234     /**
235      * return the type of spline used for the path interpolation for this path
236      * @return the path interpolation spline type
237      */
getPathSplineType()238     public SplineType getPathSplineType() {
239         return spline.getType();
240     }
241 
242     /**
243      * sets the type of spline used for the path interpolation for this path
244      * @param pathSplineType
245      */
setPathSplineType(SplineType pathSplineType)246     public void setPathSplineType(SplineType pathSplineType) {
247         spline.setType(pathSplineType);
248         if (debugNode != null) {
249             Node parent = debugNode.getParent();
250             debugNode.removeFromParent();
251             debugNode.detachAllChildren();
252             debugNode = null;
253             attachDebugNode(parent);
254         }
255     }
256 
257     /**
258      * disable the display of the path and the waypoints
259      */
disableDebugShape()260     public void disableDebugShape() {
261 
262         debugNode.detachAllChildren();
263         debugNode = null;
264         assetManager = null;
265     }
266 
267     /**
268      * enable the display of the path and the waypoints
269      * @param manager the assetManager
270      * @param rootNode the node where the debug shapes must be attached
271      */
enableDebugShape(AssetManager manager, Node rootNode)272     public void enableDebugShape(AssetManager manager, Node rootNode) {
273         assetManager = manager;
274         // computeTotalLentgh();
275         attachDebugNode(rootNode);
276     }
277 
278     /**
279      * Adds a motion pathListener to the path
280      * @param listener the MotionPathListener to attach
281      */
addListener(MotionPathListener listener)282     public void addListener(MotionPathListener listener) {
283         if (listeners == null) {
284             listeners = new ArrayList<MotionPathListener>();
285         }
286         listeners.add(listener);
287     }
288 
289     /**
290      * remove the given listener
291      * @param listener the listener to remove
292      */
removeListener(MotionPathListener listener)293     public void removeListener(MotionPathListener listener) {
294         if (listeners != null) {
295             listeners.remove(listener);
296         }
297     }
298 
299     /**
300      * return the number of waypoints of this path
301      * @return
302      */
getNbWayPoints()303     public int getNbWayPoints() {
304         return spline.getControlPoints().size();
305     }
306 
triggerWayPointReach(int wayPointIndex, MotionTrack control)307     public void triggerWayPointReach(int wayPointIndex, MotionTrack control) {
308         if (listeners != null) {
309             for (Iterator<MotionPathListener> it = listeners.iterator(); it.hasNext();) {
310                 MotionPathListener listener = it.next();
311                 listener.onWayPointReach(control, wayPointIndex);
312             }
313         }
314     }
315 
316     /**
317      * Returns the curve tension
318      * @return
319      */
getCurveTension()320     public float getCurveTension() {
321         return spline.getCurveTension();
322     }
323 
324     /**
325      * sets the tension of the curve (only for catmull rom) 0.0 will give a linear curve, 1.0 a round curve
326      * @param curveTension
327      */
setCurveTension(float curveTension)328     public void setCurveTension(float curveTension) {
329         spline.setCurveTension(curveTension);
330         if (debugNode != null) {
331             Node parent = debugNode.getParent();
332             debugNode.removeFromParent();
333             debugNode.detachAllChildren();
334             debugNode = null;
335             attachDebugNode(parent);
336         }
337     }
338 
clearWayPoints()339     public void clearWayPoints() {
340         spline.clearControlPoints();
341     }
342 
343     /**
344      * Sets the path to be a cycle
345      * @param cycle
346      */
setCycle(boolean cycle)347     public void setCycle(boolean cycle) {
348 
349         spline.setCycle(cycle);
350         if (debugNode != null) {
351             Node parent = debugNode.getParent();
352             debugNode.removeFromParent();
353             debugNode.detachAllChildren();
354             debugNode = null;
355             attachDebugNode(parent);
356         }
357 
358     }
359 
360     /**
361      * returns true if the path is a cycle
362      * @return
363      */
isCycle()364     public boolean isCycle() {
365         return spline.isCycle();
366     }
367 
getSpline()368     public Spline getSpline() {
369         return spline;
370     }
371 }
372