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 */
32package com.jme3.cinematic;
33
34import com.jme3.asset.AssetManager;
35import com.jme3.cinematic.events.MotionTrack;
36import com.jme3.export.*;
37import com.jme3.material.Material;
38import com.jme3.math.ColorRGBA;
39import com.jme3.math.Spline;
40import com.jme3.math.Spline.SplineType;
41import com.jme3.math.Vector2f;
42import com.jme3.math.Vector3f;
43import com.jme3.scene.Geometry;
44import com.jme3.scene.Node;
45import com.jme3.scene.shape.Box;
46import com.jme3.scene.shape.Curve;
47import com.jme3.util.TempVars;
48import java.io.IOException;
49import java.util.ArrayList;
50import java.util.Iterator;
51import java.util.List;
52
53/**
54 * Motion path is used to create a path between way points.
55 * @author Nehon
56 */
57public 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     */
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     */
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
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
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
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
153    public void write(JmeExporter ex) throws IOException {
154        OutputCapsule oc = ex.getCapsule(this);
155        oc.write(spline, "spline", null);
156    }
157
158    @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
303    public int getNbWayPoints() {
304        return spline.getControlPoints().size();
305    }
306
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     */
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     */
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
339    public void clearWayPoints() {
340        spline.clearControlPoints();
341    }
342
343    /**
344     * Sets the path to be a cycle
345     * @param cycle
346     */
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     */
364    public boolean isCycle() {
365        return spline.isCycle();
366    }
367
368    public Spline getSpline() {
369        return spline;
370    }
371}
372