All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cogchar.render.scene.goody.PathMgr Maven / Gradle / Ivy

/*
 *  Copyright 2012 by The Cogchar Project (www.cogchar.org).
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.cogchar.render.scene.goody;

//import org.cogchar.render.sys.goody.SpatialGrabber;
import com.jme3.animation.LoopMode;
import com.jme3.cinematic.MotionPath;
import com.jme3.cinematic.events.MotionEvent;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.appdapter.core.name.Ident;
import org.cogchar.api.cinema.AnimWaypointsConfig;
import org.cogchar.api.cinema.PathInstanceConfig;
import org.cogchar.api.cinema.SpatialActionConfig;
import org.cogchar.api.cinema.WaypointConfig;

/**
 *
 * @author Ryan Biggs 
 */
public class PathMgr extends AbstractThingCinematicMgr {

    private Map myPathsByUri = new HashMap();

    @Override
    public void buildAnimation(SpatialActionConfig sac) {
        myLogger.info("Building Path from RDF: {}", sac);
        if (sac.getClass() != PathInstanceConfig.class) {
            myLogger.warn("buildAnimation was passed the wrong class of configuration object! Aborting.");
            return;
        }
        PathInstanceConfig pic = (PathInstanceConfig) sac;
        Map boundCameras = new HashMap(); // To keep track of cameras bound to this cinematic
        SpatialGrabber grabber = new SpatialGrabber(myCRC);
        Spatial attachedSpatial = grabber.getSpatialForSpecifiedType(pic, boundCameras);
        //staticLogger.info("The attached spatial is {} with requested uri {}", attachedSpatial, pic.attachedItem); // TEST ONLY
        if (attachedSpatial != null) {
            MotionEvent event = getMotionEvent(pic, attachedSpatial);
            if (event != null) {
                myPathsByUri.put(pic.myUri, event);
            }
        }
    }

    // A class to extend MotionEvent to detach MotionEvent's spatial from the root node when the animation completes
    // This allows the flycam to operate normally after an animation if we animate the default camera
    class DetachingMotionEvent extends MotionEvent {

        Node myRootNode;

        DetachingMotionEvent(Spatial spatial, MotionPath path, float initialDuration, Node rootNode) {
            super(spatial, path, initialDuration);
            myRootNode = rootNode;
        }

        @Override
        public void onStop() {
            //super.onStop(); //maybe? -Matt (from MotionEvent: currentWaypoint = 0;)
            myRootNode.detachChild(this.getSpatial());
        }
    }

    private MotionEvent getMotionEvent(PathInstanceConfig track, Spatial attachedSpatial) {
        MotionPath path = new MotionPath();
        path.setCycle(track.cycle);
        AnimWaypointsConfig waypointInfo = AnimWaypointsConfig.getMainConfig();
        for (WaypointConfig waypoint : track.waypoints) {
            if (noPosition(waypoint.myCoordinates)) { // If we don't have coordinates for this waypoint...
                // First check to see if this waypoint refers to a stored waypoint previously defined
                Ident waypointReference = waypoint.myUri;
                if (waypointReference != null) {
                    waypoint = waypointInfo.myWCs.get(waypointReference); // Reset waypoint to the WaypointConfig declared separately by name
                    if (waypoint == null) { // If so, track is calling for a waypoint we don't know about
                        myLogger.error("Path has requested undefined waypoint: {}; track is {}", waypointReference, track);
                        break;
                    }
                } else {
                    myLogger.error("No coordinates or waypointName in waypoint contained in path: {}", track);
                    break; // If no coordinates and no waypointName, we don't really have a waypoint!
                }
            }
            //staticLogger.info("Making new waypoint: " + new Vector3f(waypoint.myCoordinates[0], waypoint.myCoordinates[1], waypoint.myCoordinates[2])); // TEST ONLY
            path.addWayPoint(new Vector3f(waypoint.myCoordinates[0], waypoint.myCoordinates[1], waypoint.myCoordinates[2]));
        }

        MotionEvent motionTrack = null;

        if (path.getNbWayPoints() < 2) {
            myLogger.warn("Less than two waypoints found in path {}; aborting build.", track.myUri);
        } else {
            path.setCurveTension(track.tension);

            MotionEvent.Direction directionJmeType = null;
            for (MotionEvent.Direction testType : MotionEvent.Direction.values()) {
                if (track.directionType.equals(testType.toString())) {
                    directionJmeType = testType;
                }
            }
            if (directionJmeType == null) {
                myLogger.error("Specified MotionEvent direction type not in MotionEvent.Direction: {}", track.directionType);
                return null;
            }
            LoopMode loopJmeType = setLoopMode(track.loopMode);
            if (loopJmeType == null) {
                myLogger.error("Specified MotionEvent loop mode not in com.jme3.animation.LoopMode: {}", track.loopMode);
                return null;
            }
            motionTrack = new DetachingMotionEvent(attachedSpatial, path, track.duration,
                    myCRC.getRenderRegistryClient().getJme3RootDeepNode(null));
            
            // For move rotation to work, we need to look at a specific location, prior to bens changes we were attempting to use a rotation as our location...
            motionTrack.setDirectionType(MotionEvent.Direction.LookAt);
            motionTrack.setLookAt(new Vector3f(track.lookAtLocation[0], track.lookAtLocation[1], track.lookAtLocation[2]), Vector3f.UNIT_Y);
            
            motionTrack.setLoopMode(loopJmeType);
            motionTrack.setInitialDuration(track.duration);

        }
        return motionTrack;
    }

    @Override
    public boolean controlAnimationByName(final Ident uri, ControlAction action) { // Soon switching to controlPathByUri
        boolean validAction = true;
        final MotionEvent path = myPathsByUri.get(uri);
        if (path != null) {
            if (action.equals(PathMgr.ControlAction.PLAY)) {
                myLogger.info("Playing cinematic {}", uri);
                // Stu added time reset so that anims with "DontLoop" can be played more than once.
                Callable c = new Callable() { // Do this on main render thread
                    @Override
                    public Void call() {
                        try {
                            // Ben is specifically stopping null pointer exception as lwjgl exception can be thrown.
                            if (path != null) { 
                                path.setTime(0.0f);
                                path.play();
                            }else{
                                myLogger.error("Trouble setting camera path, path is null {}", path);
                            }
                        } catch (Exception e) {
                            myLogger.error("Trouble setting camera path {}", e);
                        }
                        return null;
                    }
                };
                myCRC.enqueueCallable(c);

            } else if (action.equals(PathMgr.ControlAction.STOP)) {
                // Wouldn't you know, this has to be done on main thread
                Future waitForThis = myCRC.enqueueCallable(new Callable() {
                    @Override
                    public Boolean call() throws Exception {
                        path.stop();
                        myLogger.info("Stopping cinematic {}", uri);
                        return true;
                    }
                });
                try {
                    // We call waitForThis.get (and discard the result) so that this method doesn't return until STOP is actially executed
                    waitForThis.get(3, java.util.concurrent.TimeUnit.SECONDS);
                } catch (Exception e) {
                    myLogger.error("Exception stopping cinematic: {}", e.toString());
                }
            } else if (action.equals(PathMgr.ControlAction.PAUSE)) { // NEEDS TO BE TESTED, MAY NEED TO BE EXECUTED ON MAIN jME RENDERING THREAD
                myLogger.info("Pausing cinematic {}", uri);
                myLogger.info("Pause has not been tested. If it throws an exception, we probably just need to add some code to cause it to be run on the main jME rendering thread");
                path.pause();
            } else {
                validAction = false;
            }
        } else {
            myLogger.error("No path found by URI {}", uri);
            validAction = false;
        }
        return validAction;
    }

    @Override
    public void clearAnimations() {
        myPathsByUri.clear();
        // Disable CameraNodes?
        myLogger.info("Paths cleared.");
    }
}