/* * Copyright (c) 2009-2021 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.cinematic.events; import com.jme3.animation.AnimationUtils; import com.jme3.animation.LoopMode; import com.jme3.app.Application; import com.jme3.cinematic.Cinematic; import com.jme3.cinematic.MotionPath; 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.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** * A MotionEvent is a control over the spatial that manages the position and direction of the spatial while following a motion Path. * * You must first create a MotionPath and then create a MotionEvent to associate a spatial and the path. * * @author Nehon */ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable { protected Spatial spatial; protected int currentWayPoint; protected float currentValue; protected Vector3f direction = new Vector3f(); protected Vector3f lookAt = null; protected Vector3f upVector = Vector3f.UNIT_Y; protected Quaternion rotation = null; protected Direction directionType = Direction.None; protected MotionPath path; private boolean isControl = true; /** * the distance traveled by the spatial on the path */ protected float traveledDistance = 0; /** * Enum for the different type of target direction behavior. */ public enum Direction { /** * The target stays in the starting direction. */ None, /** * The target rotates with the direction of the path. */ Path, /** * The target rotates with the direction of the path but with the addition of a rotation. * You need to use the setRotation method when using this Direction. */ PathAndRotation, /** * The target rotates with the given rotation. */ Rotation, /** * The target looks at a point. * You need to use the setLookAt method when using this direction. */ LookAt } /** * Create MotionEvent, * when using this constructor don't forget to assign spatial and path. */ public MotionEvent() { super(); } /** * Creates a MotionPath for the given spatial on the given motion path. * * @param spatial the Spatial to move (not null) * @param path the path to be taken (alias created) */ public MotionEvent(Spatial spatial, MotionPath path) { super(); spatial.addControl(this); this.path = path; } /** * Creates a MotionPath for the given spatial on the given motion path. * * @param spatial the Spatial to move (not null) * @param path the path to be taken (alias created) * @param initialDuration the desired duration (in seconds, default=10) */ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) { super(initialDuration); spatial.addControl(this); this.path = path; } /** * Creates a MotionPath for the given spatial on the given motion path. * * @param spatial the Spatial to move (not null) * @param path the path to be taken (alias created) * @param loopMode (default=DontLoop) */ public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) { super(); spatial.addControl(this); this.path = path; this.loopMode = loopMode; } /** * Creates a MotionPath for the given spatial on the given motion path. * * @param spatial the Spatial to move (not null) * @param path the path to be taken (alias created) * @param initialDuration the desired duration (in seconds, default=10) * @param loopMode (default=DontLoop) */ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) { super(initialDuration); spatial.addControl(this); this.path = path; this.loopMode = loopMode; } @Override public void update(float tpf) { if (isControl) { internalUpdate(tpf); } } @Override public void internalUpdate(float tpf) { if (playState == PlayState.Playing) { time = time + (tpf * speed); if (loopMode == LoopMode.Loop && time < 0) { time = initialDuration; } if ((time >= initialDuration || time < 0) && loopMode == LoopMode.DontLoop) { if (time >= initialDuration) { path.triggerWayPointReach(path.getNbWayPoints() - 1, this); } stop(); } else { time = AnimationUtils.clampWrapTime(time, initialDuration, loopMode); if (time < 0) { speed = -speed; time = -time; } onUpdate(tpf); } } } @Override public void initEvent(Application app, Cinematic cinematic) { super.initEvent(app, cinematic); isControl = false; } @Override public void setTime(float time) { super.setTime(time); onUpdate(0); } @Override public void onUpdate(float tpf) { traveledDistance = path.interpolatePath(time, this, tpf); computeTargetDirection(); } @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(lookAt, "lookAt", null); oc.write(upVector, "upVector", Vector3f.UNIT_Y); oc.write(rotation, "rotation", null); oc.write(directionType, "directionType", Direction.None); oc.write(path, "path", null); oc.write(spatial, "spatial", null); } @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule in = im.getCapsule(this); lookAt = (Vector3f) in.readSavable("lookAt", null); upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y); rotation = (Quaternion) in.readSavable("rotation", null); directionType = in.readEnum("directionType", Direction.class, Direction.None); path = (MotionPath) in.readSavable("path", null); spatial = (Spatial) in.readSavable("spatial", null); } /** * This method is meant to be called by the motion path only. * @return true if needed, otherwise false */ public boolean needsDirection() { return directionType == Direction.Path || directionType == Direction.PathAndRotation; } private void computeTargetDirection() { switch (directionType) { case Path: Quaternion q = new Quaternion(); q.lookAt(direction, upVector); spatial.setLocalRotation(q); break; case LookAt: if (lookAt != null) { spatial.lookAt(lookAt, upVector); } break; case PathAndRotation: if (rotation != null) { Quaternion q2 = new Quaternion(); q2.lookAt(direction, upVector); q2.multLocal(rotation); spatial.setLocalRotation(q2); } break; case Rotation: if (rotation != null) { spatial.setLocalRotation(rotation); } break; case None: break; default: break; } } /** * Clone this control for the given spatial. * * @param spatial ignored * @return never */ @Deprecated @Override public Control cloneForSpatial(Spatial spatial) { throw new UnsupportedOperationException(); } @Override public Object jmeClone() { MotionEvent control = new MotionEvent(); control.path = path; control.playState = playState; control.currentWayPoint = currentWayPoint; control.currentValue = currentValue; control.direction = direction.clone(); control.lookAt = lookAt; control.upVector = upVector.clone(); control.rotation = rotation; control.initialDuration = initialDuration; control.speed = speed; control.loopMode = loopMode; control.directionType = directionType; control.spatial = spatial; return control; } @Override public void cloneFields(Cloner cloner, Object original) { this.spatial = cloner.clone(spatial); } @Override public void onPlay() { traveledDistance = 0; } @Override public void onStop() { currentWayPoint = 0; } @Override public void onPause() { } /** * This method is meant to be called by the motion path only. * @return the value */ public float getCurrentValue() { return currentValue; } /** * This method is meant to be called by the motion path only. * * @param currentValue the desired value */ public void setCurrentValue(float currentValue) { this.currentValue = currentValue; } /** * This method is meant to be called by the motion path only. * @return the waypoint index */ public int getCurrentWayPoint() { return currentWayPoint; } /** * This method is meant to be called by the motion path only. * * @param currentWayPoint the desired waypoint index */ public void setCurrentWayPoint(int currentWayPoint) { this.currentWayPoint = currentWayPoint; } /** * Returns the direction the spatial is moving. * @return the pre-existing vector */ public Vector3f getDirection() { return direction; } /** * Sets the direction of the spatial, using the Y axis as the up vector. * Use MotionEvent#setDirection((Vector3f direction,Vector3f upVector) if * you want a custom up vector. * This method is used by the motion path. * * @param direction the desired forward direction (not null, unaffected) */ public void setDirection(Vector3f direction) { setDirection(direction, Vector3f.UNIT_Y); } /** * Sets the direction of the spatial with the given up vector. * This method is used by the motion path. * * @param direction the desired forward direction (not null, unaffected) * @param upVector the up vector to consider for this direction. */ public void setDirection(Vector3f direction, Vector3f upVector) { this.direction.set(direction); this.upVector.set(upVector); } /** * Returns the direction type of the target. * @return the direction type. */ public Direction getDirectionType() { return directionType; } /** * Sets the direction type of the target. * On each update the direction given to the target can have different behavior. * See the Direction Enum for explanations. * @param directionType the direction type. */ public void setDirectionType(Direction directionType) { this.directionType = directionType; } /** * Set the lookAt for the target. * This can be used only if direction Type is Direction.LookAt. * @param lookAt the position to look at. * @param upVector the up vector. */ public void setLookAt(Vector3f lookAt, Vector3f upVector) { this.lookAt = lookAt; this.upVector = upVector; } /** * Returns the rotation of the target. * @return the rotation quaternion. */ public Quaternion getRotation() { return rotation; } /** * Sets the rotation of the target. * This can be used only if direction Type is Direction.PathAndRotation or Direction.Rotation. * With PathAndRotation the target will face the direction of the path multiplied by the given Quaternion. * With Rotation the rotation of the target will be set with the given Quaternion. * @param rotation the rotation quaternion. */ public void setRotation(Quaternion rotation) { this.rotation = rotation; } /** * Return the motion path this control follows. * @return the pre-existing instance */ public MotionPath getPath() { return path; } /** * Sets the motion path to follow. * * @param path the desired path (alias created) */ public void setPath(MotionPath path) { this.path = path; } public void setEnabled(boolean enabled) { if (enabled) { play(); } else { pause(); } } public boolean isEnabled() { return playState != PlayState.Stopped; } @Override public void render(RenderManager rm, ViewPort vp) { } @Override public void setSpatial(Spatial spatial) { this.spatial = spatial; } public Spatial getSpatial() { return spatial; } /** * Return the distance traveled by the spatial on the path. * @return the distance */ public float getTraveledDistance() { return traveledDistance; } }