Package org.jdesktop.wonderland.modules.avatarbase.client.jme.cellrenderer

Source Code of org.jdesktop.wonderland.modules.avatarbase.client.jme.cellrenderer.AvatarImiJME

/**
* Copyright (c) 2014, WonderBuilders, Inc., All Rights Reserved
*/

/**
* Open Wonderland
*
* Copyright (c) 2010 - 2012, Open Wonderland Foundation, All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* The Open Wonderland Foundation designates this particular file as
* subject to the "Classpath" exception as provided by the Open Wonderland
* Foundation in the License file that accompanied this code.
*/

/**
* Project Wonderland
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied
* this code.
*/
package org.jdesktop.wonderland.modules.avatarbase.client.jme.cellrenderer;

import org.jdesktop.wonderland.client.jme.input.AvatarCollisionEvent;
import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingBox;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.logging.Level;
import org.jdesktop.mtgame.JMECollisionSystem;
import org.jdesktop.wonderland.client.cell.MovableComponent.CellMoveSource;
import org.jdesktop.wonderland.client.jme.cellrenderer.*;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Geometry;
import com.jme.scene.Spatial.CullHint;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.ZBufferState;
import imi.character.CharacterAnimationProcessor;
import imi.character.CharacterMotionListener;
import imi.character.CharacterProcessor;
import imi.character.avatar.Avatar;
import imi.character.avatar.AvatarController;
import imi.character.avatar.AvatarCollisionListener;
import imi.character.statemachine.GameContextListener;
import imi.character.statemachine.corestates.CycleActionState;
import imi.collision.CollisionController;
import imi.input.DefaultCharacterControls;
import imi.scene.PMatrix;
import imi.scene.PTransform;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.logging.Logger;
import javolution.util.FastList;
import org.jdesktop.mtgame.CollisionComponent;
import org.jdesktop.mtgame.CollisionInfo;
import org.jdesktop.mtgame.CollisionSystem;
import org.jdesktop.mtgame.Entity;
import org.jdesktop.mtgame.JMECollisionDetails;
import org.jdesktop.mtgame.ProcessorCollectionComponent;
import org.jdesktop.mtgame.ProcessorComponent;
import org.jdesktop.mtgame.RenderComponent;
import org.jdesktop.mtgame.RenderManager;
import org.jdesktop.mtgame.RenderUpdater;
import org.jdesktop.mtgame.WorldManager;
import org.jdesktop.mtgame.processor.WorkProcessor.WorkCommit;
import org.jdesktop.wonderland.client.cell.Cell;
import org.jdesktop.wonderland.client.jme.ClientContextJME;
import org.jdesktop.wonderland.common.ExperimentalAPI;
import org.jdesktop.wonderland.common.cell.CellTransform;
import org.jdesktop.wonderland.client.ClientContext;
import org.jdesktop.wonderland.client.cell.MovableAvatarComponent;
import org.jdesktop.wonderland.client.cell.MovableComponent;
import org.jdesktop.wonderland.client.cell.MovableComponent.CellMoveListener;
import org.jdesktop.wonderland.client.cell.view.AvatarCell;
import org.jdesktop.wonderland.client.cell.view.AvatarCell.AvatarActionTrigger;
import org.jdesktop.wonderland.client.cell.view.ViewCell;
import org.jdesktop.wonderland.client.input.Event;
import org.jdesktop.wonderland.client.input.EventClassListener;
import org.jdesktop.wonderland.client.jme.SceneWorker;
import org.jdesktop.wonderland.client.jme.ViewManager;
import org.jdesktop.wonderland.client.login.LoginManager;
import org.jdesktop.wonderland.client.login.ServerSessionManager;
import org.jdesktop.wonderland.common.Math3DUtils;
import org.jdesktop.wonderland.common.cell.CellStatus;
import org.jdesktop.wonderland.modules.avatarbase.client.cell.AvatarConfigComponent;
import org.jdesktop.wonderland.modules.avatarbase.client.cell.AvatarConfigComponent.AvatarConfigChangeListener;
import org.jdesktop.wonderland.modules.avatarbase.client.jme.cellrenderer.ImiPickGeometry.PickBox;
import org.jdesktop.wonderland.modules.avatarbase.client.loader.AvatarLoaderRegistry;
import org.jdesktop.wonderland.modules.avatarbase.client.loader.spi.AvatarLoaderFactorySPI;
import org.jdesktop.wonderland.modules.avatarbase.common.cell.AvatarConfigInfo;
import org.jdesktop.wonderland.modules.avatarbase.common.cell.messages.AvatarConfigMessage;
import org.jdesktop.wonderland.modules.avatarbase.common.cell.messages.NameTagMessage;


/**
* Cell renderer for Avatars, using the IMI avatar system.
*
* @author paulby
* @author Jordan Slott <jslott@dev.java.net>
* @author Abhishek Upadhyay
*/
@ExperimentalAPI
public class AvatarImiJME extends BasicRenderer implements AvatarActionTrigger {

    private WlAvatarCharacter avatarCharacter = null;

    private boolean selectedForInput = false;

//    private AvatarRendererChangeRequestEvent.AvatarQuality quality = AvatarRendererChangeRequestEvent.AvatarQuality.High;
    private CharacterMotionListener characterMotionListener;
    private GameContextListener gameContextListener;
    private int currentTrigger = -1;
    private boolean currentPressed = false;
    private float positionMinDistanceForPull = 0.1f;
    private float positionMaxDistanceForPull = 3.0f;
    private String username;
    private DefaultCharacterControls controlScheme = null;

    private boolean pickable = true;
    private PickGeometry pickGeometry;

    private ProcessorComponent cameraChainedProcessor = null// The processor to which the camera is chained

    private CellMoveListener cellMoveListener = null;
    private CellTransform delayedMove = null;
   
    private Entity rootEntity = null;

    private CollisionChangeRequestListener collisionChangeRequestListener;
    private AvatarCollisionListener collisionListener = null;
   
    /** Collection of listeners **/
    private final List<WeakReference<AvatarChangedListener>> avatarChangedListeners = new FastList();

    private AvatarUIEventListener avatarUIEventListener;
    private static final Logger LOGGER = Logger.getLogger(AvatarImiJME.class.getName());

    public AvatarImiJME(Cell cell) {
        super(cell);
        assert (cell != null);
        final Cell c = cell;

        // Listen for avatar configuration changes.
        AvatarConfigComponent comp = cell.getComponent(AvatarConfigComponent.class);
        comp.addAvatarConfigChangeListener(new AvatarChangeListener());

        // XXX NPC HACK XXX
        if (cell instanceof AvatarCell)
            username = ((AvatarCell) cell).getIdentity().getUsername();
        else
            username = "npc"; // HACK !

        characterMotionListener = new CharacterMotionListener() {
            Vector3f prevTrans;
            PMatrix prevRot;
            float prevHeight;
            boolean prevCollision;
           
            public void transformUpdate(Vector3f translation, PMatrix rotation) {
                if (logger.isLoggable(Level.FINEST)) {
                    logger.finest("Transform update: translation: prev: " +
                            prevTrans + " cur: " + translation +
                            " rotation: prev: " + prevRot + " cur: " +
                            rotation);
                }
               
                float height = avatarCharacter.getController().getHeight();
                boolean collision = avatarCharacter.getController().isColliding();
            
                if (prevTrans == null || !Math3DUtils.epsilonEquals(prevTrans, translation, 0.001f) ||
                    prevRot == null || !prevRot.epsilonEquals(rotation, 0.001f) ||
                    !Math3DUtils.epsilonEquals(prevHeight, height, 0.001f) ||
                    prevCollision != collision)
                {                   
                    MovableAvatarComponent mac = ((MovableAvatarComponent) c.getComponent(MovableComponent.class));
                    mac.localMoveRequest(new CellTransform(rotation.getRotation(), translation), height, collision);

                    prevTrans = translation.clone();
                    prevRot = new PMatrix(rotation);
                    prevHeight = height;
                    prevCollision = collision;
                }
            };  
        };

        // This info will be sent to the other clients to animate the avatar
        gameContextListener = new GameContextListener() {

            public void trigger(boolean pressed, int trigger, Vector3f translation, Quaternion rotation) {
                synchronized (this) {
                    currentTrigger = trigger;
                    currentPressed = pressed;
                }
               
                String animationName = null;
               
                // OWL issue #237 - regardless of the current state, send the
                // animation that is currently set in CycleActionState. This
                // is consistent with the behavior of
                // WlAvatarContext.setMiscAnimation() used in the trigger()
                // method below
                CycleActionState cas = (CycleActionState)
                        avatarCharacter.getContext().getState(CycleActionState.class);
                if (cas != null) {
                    animationName = cas.getAnimationName();
                }
                               
                float height = avatarCharacter.getController().getHeight();
                boolean collision = avatarCharacter.getController().isColliding();
               
                if (c.getComponent(MovableComponent.class)==null) {
                    logger.warning("!!!! NULL MovableComponent");
                } else {
                    MovableAvatarComponent mac = ((MovableAvatarComponent) c.getComponent(MovableComponent.class));
                    mac.localMoveRequest(new CellTransform(rotation, translation),
                                         trigger, pressed, animationName,
                                         height, collision, null);
               
                }
            }
        };



    }




    /**
     * Returns the avatar renderer for the primary view cell, or null if none
     * exists.
     *
     * @return An instance of this class that is the avatar cell renderer
     */
    public static AvatarImiJME getPrimaryAvatarRenderer() {
        // Fetch the primary view cell, make sure it is an avatar and then get
        // its cell renderer.
        ViewCell cell = ClientContextJME.getViewManager().getPrimaryViewCell();
        if (cell instanceof AvatarCell) {
            AvatarCell avatarCell = (AvatarCell) cell;
            return (AvatarImiJME) avatarCell.getCellRenderer(ClientContext.getRendererType());
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setStatus(CellStatus status, boolean increasing) {
        super.setStatus(status, increasing);
        WlAvatarCharacter pendingAvatar = null;

        logger.info("AVATAR RENDERER STATUS " + status + " DIR " + increasing);
       
        // If we are increasing to the ACTIVE state, then turn everything on.
        // Add the listeners to the avatar Cell and set the avatar character
        if (status == CellStatus.ACTIVE && increasing == true) {
            BoundsDebugger.getInstance().add(this);
            if (cellMoveListener != null) {
                // mc should not be null, but sometimes it seems to be
                MovableComponent mc = cell.getComponent(MovableComponent.class);
                if (mc==null) {
                    logger.severe("NULL MovableComponent in avatar "+((AvatarCell)cell).getName());
                } else {
                    mc.removeServerCellMoveListener(cellMoveListener);
                }
                cellMoveListener = null;
            }

            // If we have not creating the avatar yet, then look for the config
            // component on the cell. Fetch the avatar configuration.
            if (avatarCharacter == null) {
                AvatarConfigComponent configComp = cell.getComponent(AvatarConfigComponent.class);
                AvatarConfigInfo avatarConfigInfo = null;
                if (configComp != null) {
                    avatarConfigInfo = configComp.getAvatarConfigInfo();
                }
                logger.info("LOADING AVATAR FOR " + avatarConfigInfo);
                pendingAvatar = loadAvatar(avatarConfigInfo);
            }
            else {
                // Otherwise remove the existing avatar from the world
                ClientContextJME.getWorldManager().removeEntity(avatarCharacter);
                pendingAvatar = null;
            }

            // Go ahead and change the avatar
            logger.info("CHANGING AVATAR IN SET STATUS");
            changeAvatar(pendingAvatar);

            if (cellMoveListener == null) {
                cellMoveListener = new CellMoveListener() {
                    public void cellMoved(CellTransform transform, CellMoveSource source) {
                        if (source == CellMoveSource.REMOTE) {
                            //                            System.err.println("REMOTE MOVE "+transform.getTranslation(null));
                            if (avatarCharacter != null) {
                                if (avatarCharacter.getModelInst() == null) {  // Extra debug check
                                    logger.severe("MODEL INST IS NULL !");
                                    Thread.dumpStack();
                                    return;
                                }
                               
                                if (delayedMove != null) {
                                    delayedMove = null;
                                    logger.fine(cell.getCellID() + ": remove delayed move for: " + transform.toString());
                                }
                                avatarCharacter.getModelInst().setTransform(new PTransform(transform.getRotation(null), transform.getTranslation(null), new Vector3f(1, 1, 1)));
                            } else {
                                logger.fine(cell.getCellID() + ": delaying move: " + transform.toString());
                                // we tried to move before loading the avatar.
                                // record the new position to apply when we
                                // actually load
                                delayedMove = transform;
                            }
                        }
                    }
                };
            }
            cell.getComponent(MovableComponent.class).addServerCellMoveListener(cellMoveListener);

            avatarUIEventListener = new AvatarUIEventListener();
            ClientContext.getInputManager().addGlobalEventListener(avatarUIEventListener);

            collisionChangeRequestListener = new CollisionChangeRequestListener();
            ClientContext.getInputManager().addGlobalEventListener(collisionChangeRequestListener);
        } else if (status==CellStatus.DISK && !increasing) {
            BoundsDebugger.getInstance().remove(this);
            ClientContext.getInputManager().removeGlobalEventListener(avatarUIEventListener);
            ClientContext.getInputManager().removeGlobalEventListener(collisionChangeRequestListener);
            cell.getComponent(MovableComponent.class).removeServerCellMoveListener(cellMoveListener);
            avatarUIEventListener=null;
            cellMoveListener=null;
            collisionChangeRequestListener=null;
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Entity createEntity() {
        assert (rootEntity == null);
        rootEntity = new Entity("AvatarRoot");
        return rootEntity;
    }

    private void handleAvatarRendererChangeRequest(AvatarRendererChangeRequestEvent event) {
        switch (event.getQuality()) {
            case High :
                // Fetch the avatar configuration information and change to the
                // current avatar
                AvatarConfigComponent comp = cell.getComponent(AvatarConfigComponent.class);
                AvatarConfigInfo avatarConfigInfo = comp.getAvatarConfigInfo();
                changeAvatar(loadAvatar(avatarConfigInfo));
                break;
            case Medium :
                changeAvatar(loadAvatar(null));
                break;
            case Low :
                changeAvatar(loadAvatar(null));
                break;
        }
    }

    /**
     * Changes the avatar to the given avatar on the MT Game Render Thread
     */
    public void changeAvatar(final WlAvatarCharacter avatar) {
        RenderUpdater updater = new RenderUpdater() {
            public void update(Object arg0) {
                changeAvatarInternal(avatar);
            }
        };
        WorldManager wm = ClientContextJME.getWorldManager();
        wm.addRenderUpdater(updater, null);
    }

    /**
     * Change the current avatar to the given avatar.
     *
     * NOTE: This method must be called in the MT Game Render Thread. As such,
     * we assume only one of these methods is called at a time.
     *
     * @param newAvatar The new avatar to change to.
     */
    private void changeAvatarInternal(WlAvatarCharacter newAvatar) {

        int flg=0;
        if (newAvatar==null)
            return;

        // Turn on an indication that the avatar is being loaded
        LoadingInfo.startedLoading(cell.getCellID(), newAvatar.getName());

        // Fetch the name tag node. There should be only one of these in the
        // system.
        Node nameTagNode = getNameTagNode();

        // If there is an existing avatar character, then remove it, but store
        // away its position. Remove the name tag, turn off input and destroy
        // the avatar character.
        PMatrix currentLocation = null;
        if (avatarCharacter != null) {
            currentLocation = avatarCharacter.getModelInst().getTransform().getWorldMatrix(true);
            rootEntity.removeEntity(avatarCharacter);
            avatarCharacter.getJScene().getExternalKidsRoot().detachChild(nameTagNode);
            selectForInput(false);
            avatarCharacter.destroy();
            flg=1;
        }

        // Set the new avatar character. If there is none (when would that happen?)
        // then just return.
        avatarCharacter = newAvatar;
        if (newAvatar == null) {
            return;
        }

        // Add all of the default components to the renderer, but remove the
        // collision component, since we use our own collision graph
        RenderComponent rc = (RenderComponent) avatarCharacter.getComponent(RenderComponent.class);
        addDefaultComponents(avatarCharacter, rc.getSceneRoot());
        avatarCharacter.removeComponent(CollisionComponent.class);

        // Set the initial location of the avatar if there is one
        if (currentLocation != null && avatarCharacter.getModelInst() != null) {
            logger.fine(cell.getCellID() + " Using current location: " + currentLocation);
            avatarCharacter.getModelInst().setTransform(new PTransform(currentLocation));
        } else if (delayedMove != null && avatarCharacter.getModelInst() != null) {
            // there was no previous avatar, but there was a move that
            // happened while the avatar was null. Apply the move now
            logger.fine(cell.getCellID() + " using delayed move: " + delayedMove.toString());
            PTransform trans = new PTransform(delayedMove.getRotation(null),
                                              delayedMove.getTranslation(null),
                                              new Vector3f(1, 1, 1));
            avatarCharacter.getModelInst().setTransform(trans);
        }

        // Attach the name tag to the new avatar and add the avatar entity to
        // the cell renderer root entity and turn on input.
        Node externalRoot = avatarCharacter.getJScene().getExternalKidsRoot();
        if (nameTagNode!=null) {
            externalRoot.attachChild(nameTagNode);
            externalRoot.setModelBound(new BoundingSphere());
            externalRoot.updateModelBound();
            externalRoot.updateGeometricState(0, true);
        }
        rootEntity.addEntity(avatarCharacter);

        // Turn on input handle for the renderer, if we wish. Check for AvatarCell
        // to allow NPC's to work
        if (cell instanceof AvatarCell) {
            selectForInput(((AvatarCell) cell).isSelectedForInput());
        }

        // Notify listeners that the avatar has changed.
        for (WeakReference<AvatarChangedListener> listenerRef : avatarChangedListeners) {
            AvatarChangedListener listener = listenerRef.get();
            if (listener != null) {
                listener.avatarChanged(avatarCharacter);
            }
            else {
                avatarChangedListeners.remove(listenerRef);
            }
        }

        // update the bounds if necessary
        if (avatarCharacter.getJScene() != null) {
            avatarCharacter.getPScene().submitTransformsAndGeometry(true);
            avatarCharacter.getJScene().setModelBound(new BoundingSphere());
            avatarCharacter.getJScene().updateModelBound();
            avatarCharacter.getJScene().updateWorldBound();
        }

        // Update pick geometry
        updatePickGeometry();

        // Turn off the indication that we have finished loading
        LoadingInfo.finishedLoading(cell.getCellID(), newAvatar.getName());
       
        //--added for sitting problem when user logs in--//
        //check If there is an existing avatar character
        if(flg==0) {
            AvatarCell acell = (AvatarCell) cell;
            MovableAvatarComponent mac = (MovableAvatarComponent) acell.getComponent(MovableComponent.class);
            //check if avatar has trigger value of sitting
            if(mac.getServerTrigger()==15) {
                avatarCharacter.getContext().triggerPressed(mac.getServerTrigger());
    }
        }
        //--added for sitting problem when user logs in--//
    }

    public boolean isPickable() {
        return pickable;
    }

    public void setPickable(boolean pickable) {
        this.pickable = pickable;

        // schedule a task to update the pick geometry
        SceneWorker.addWorker(new WorkCommit() {
            public void commit() {
                updatePickGeometry();
            }
        });
    }

    public PickGeometry getPickGeometry() {
        return pickGeometry;
    }

    protected void updatePickGeometry() {
        // clean up any existing pick geometry
        if (pickGeometry != null) {
            pickGeometry.detach();
            pickGeometry = null;
        }

        // make sure we are pickable and ready to add geometry
        if (!isPickable() || avatarCharacter.getJScene() == null) {
            return;
        }

        if (avatarCharacter.getSkeleton() != null) {
            // use pick geometry for an IMI avatar
            boolean isMale = avatarCharacter.getCharacterParams().isMale();
            PickBox[] pickBoxes = getPickBoxes(isMale);

            pickGeometry = new ImiPickGeometry(cell.getName(), cell,
                                               AvatarImiJME.this, pickBoxes);
        } else if (avatarCharacter.getSimpleStaticGeometry() != null) {
            pickGeometry = new BasicPickGeometry(cell.getName(), cell, this,
                                                 avatarCharacter.getSimpleStaticGeometry());
        }
    }

    protected PickBox[] getPickBoxes(boolean isMale) {
        return ImiPickGeometry.getDefaultGeometry(isMale);
    }

    @Override
    protected void addRenderState(Node node) {
        // Nothing to do
    }

    @Override
    public void cellTransformUpdate(CellTransform transform) {
        // Don't call super, we don't use a MoveProcessor for avatars
//
//        if (!selectedForInput && avatarCharacter != null && avatarCharacter.getContext().getController().getModelInstance()!=null ) {
//            // If the user is being steered by AI, do not mess it up
//            // (objects that the AI is dealing with gota be synced)
////            System.err.println("Steering "+avatarCharacter.getContext().getSteering().isEnabled()+"  "+avatarCharacter.getContext().getSteering().getCurrentTask());
//            if (avatarCharacter.getContext().getBehaviorManager().isEnabled()
//                    && avatarCharacter.getContext().getBehaviorManager().getCurrentTask() != null) {
//            } else {
//                Vector3f pos = transform.getTranslation(null);
//                Vector3f dir = new Vector3f(0, 0, -1);
//                transform.getRotation(null).multLocal(dir);
////                System.err.println("Setting pos "+pos);
//                PMatrix local = avatarCharacter.getContext().getController().getModelInstance().getTransform().getLocalMatrix(true);
//                final Vector3f currentPosition = local.getTranslation();
//                float currentDistance = currentPosition.distance(pos);
//                if (currentDistance < positionMaxDistanceForPull) {
//                    pos.set(currentPosition);
//                }
//
//            }
//        }
    }

    public void loadAndChangeAvatar(final AvatarConfigInfo avatarConfigInfo) {
        logger.info("Loading avatar info");
        WlAvatarCharacter avatar = loadAvatar(avatarConfigInfo);
        logger.info("Changing avatar character");
        changeAvatar(avatar);
        logger.info("Done changing avatar character");
    }

    /**
     * Load and return an avatar given its configuration information.
     *
     * @param avatarConfigInfo The avatar configuration info
     * @return The Avatar character
     */
    private WlAvatarCharacter loadAvatar(AvatarConfigInfo avatarConfigInfo) {
        // Load the avatar configuration information, placing a loading
        // message until it is finished
        LoadingInfo.startedLoading(cell.getCellID(), username);
        try {
            if (avatarConfigInfo != null) {
                logger.info("Loading avatar with config info url " +
                        avatarConfigInfo.getAvatarConfigURL() + " with loader " +
                        avatarConfigInfo.getLoaderFactoryClassName());
            }
            else {
                logger.info("Loading default avatar.");
            }

            return loadAvatarInternal(avatarConfigInfo);
        } catch (Throwable t) {
           
            // make sure to catch *all* errors here -- anything that gets
            // thrown can cause a stuck message in Darkstar. See
            // OWL issue #263 for details.
           
            // Log an error and return null
            String url = avatarConfigInfo == null ? "null" : avatarConfigInfo.getAvatarConfigURL();
            logger.log(Level.WARNING, "Failed to load avatar character for " +
                       "url " + url, t);
            return null;
        } finally {
            LoadingInfo.finishedLoading(cell.getCellID(), username);
        }
    }

    /**
     * Load and return the avatar. To make this the current avatar changeAvatar()
     * must be called
     *
     * @param avatarConfigURL
     * @return
     */
    private WlAvatarCharacter loadAvatarInternal(AvatarConfigInfo avatarConfigInfo)
            throws MalformedURLException, IOException {
       
        WlAvatarCharacter ret = null;
        PMatrix origin = new PMatrix();
        CellTransform transform = cell.getLocalTransform();
        origin.setTranslation(transform.getTranslation(null));
        origin.setRotation(transform.getRotation(null));

        // Create the character
        String avatarDetail = System.getProperty("avatar.detail", "high");
       
        // check if we support high-quality avatars
        if (!supportsHighQualityAvatars()) {
            logger.warning("Forcing low detail.");
            avatarDetail = "low";
        }

        // Check to see if there is no avatar configuration info and/or if we
        // have the avatar details set to "low". If so, then use the default
        AvatarLoaderRegistry registry = AvatarLoaderRegistry.getAvatarLoaderRegistry();
        if (avatarConfigInfo == null || avatarDetail.equalsIgnoreCase("low")) {

            // Find the "default" factory to generate an avatar. Ask it to
            // loader the avatar. If it does not exist (it should), simply
            // log an error andr return.
            AvatarLoaderFactorySPI factory = registry.getDefaultAvatarLoaderFactory();
            if (factory == null) {
                logger.warning("No default avatar factory is registered.");
                return null;
            }

            // We need to rewrite the AvatarConfigInfo object here a bit,
            // otherwise, the loader may still loader the wrong avatar. If
            // we set the URL in the AvatarConfigInfo to null, that should do
            // the trick. (Note that since we manually obtained the
            // AvatarLoaderFactorySPI, we don't need to update the factory
            // class name in the AvatarConfigInfo object, but we do anyway).
            String defaultClassName = factory.getClass().getName();
            AvatarConfigInfo defaultInfo =
                    new AvatarConfigInfo(null, defaultClassName);

            // Go ahead and load the default avatar
            ret = factory.getAvatarLoader().getAvatarCharacter(cell, username,
                    defaultInfo);
        }
        else {
            // If the avatar has a non-null configuration information, then
            // ask the loader factory to generate a new loader for this avatar
            String className = avatarConfigInfo.getLoaderFactoryClassName();
            if (className == null) {
                logger.warning("No class name given for avatar configuration" +
                        " with url " + avatarConfigInfo.getAvatarConfigURL());
                return null;
            }

            // Find the avatar factory, if it does not exist, return an error
            AvatarLoaderFactorySPI factory = registry.getAvatarLoaderFactory(className);
            if (factory == null) {
                logger.warning("No avatar loader factory for the class name " +
                        className + " with url " + avatarConfigInfo.getAvatarConfigURL());
                return null;
            }

            // Ask the avatar loader to create and return an avatar character
            ret = factory.getAvatarLoader().getAvatarCharacter(cell, username, avatarConfigInfo);
        }

        ret.getModelInst().getTransform().getLocalMatrix(true).set(origin);

        // XXX NPC HACK XXX
        // TODO - remove hardcoded npc support
//        if (username.equals("npc") && avatarConfigURL != null) {
//            String u = avatarConfigURL.getFile();
//            username = u.substring(u.lastIndexOf('/') + 1, u.lastIndexOf('.'));
//        }

        // Sets the Z-buffer state on the external kids root
        Node external = ret.getJScene().getExternalKidsRoot();
        setZBufferState(external);

        // JSCENE HAS NOT CHILDREN, so this does nothing
//        ret.getJScene().updateGeometricState(0, true);
//        GraphicsUtils.printGraphBounds(ret.getJScene());

        //        JScene jscene = avatar.getJScene();
        //        jscene.renderToggle();      // both renderers
        //        jscene.renderToggle();      // jme renderer only
        //        jscene.setRenderPRendererMesh(true);  // Force pRenderer to be instantiated
        //        jscene.toggleRenderPRendererMesh();   // turn off mesh
        //        jscene.toggleRenderBoundingVolume();  // turn off bounds


        return ret;
    }

    /**
     * Return true if we support high-quality avatars
     */
    public static boolean supportsHighQualityAvatars() {
        String shaderCheck = System.getProperty("avatar.shaderCheck");
        boolean shaderPass = true;

        // Check to see if the system supports OpenGL 2.0. If not, then
        // always use the low-detail avatar character
        RenderManager rm = ClientContextJME.getWorldManager().getRenderManager();
        if (shaderCheck != null && shaderCheck.equals("true")) {
            shaderPass = rm.getContextCaps().GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB >= 512;
        }

        // Issue 1114: make sure the system is telling the truth about what
        // it supports by trying a mock shader program
        boolean uniformsPass = shaderPass && ShaderTest.getInstance().testShaders();

        logger.warning("Checking avatar detail level.  OpenGL20: " +
                       rm.supportsOpenGL20() + " ShaderCheck: " + shaderPass +
                       " UniformsCheck: " + uniformsPass);

        // OWL issue #110 -- ignore the value of supportsOpenGL20() here. This
        // is known to report false negatives on at least one graphics card.
        // Our shader test should do an adequate job determining whether a
        // graphics card supports the OpenGL 2.0 features we use.
       
        // Update: fixed version of supportsOpenGL20 should properly detect
        // version.
       
        return rm.supportsOpenGL20() && shaderPass && uniformsPass;
    }

    /**
     * Returns the name tag node, creating it if it does not exist. There is a
     * single name tag node attached to the avatar Cell.
     *
     * NOTE: This method assumes it is being called in an MT-Safe manner.
     */
    private NameTagNode getNameTagNode() {
        NameTagComponent nameTagComp = cell.getComponent(NameTagComponent.class);
        if (nameTagComp == null) {
            return null;
        }
        return nameTagComp.getNameTagNode();
    }

    /**
     * Given the Avatar character, create and set it's collision controller.
     */
    private void setCollisionController(WlAvatarCharacter avatar) {
        // Create a spatial that represents the bounds of the avatar to use
        // for collision. These are hardcoded values for now.
        Vector3f origin = new Vector3f(0f, 0.57f, 0.0f);
        float radius = 0.25f;

        if (selectedForInput) {      
            Spatial collisionGraph = new Sphere("AvatarCollision", origin, 10, 10, radius);
           
            collisionGraph.setModelBound(new BoundingBox());
            collisionGraph.updateModelBound();

            // Fetch the JME Collision system using the server manager of the Cell
            // to which this renderer is attached.
            ServerSessionManager manager =
                    cell.getCellCache().getSession().getSessionManager();
            CollisionSystem collisionSystem =
                    ClientContextJME.getCollisionSystem(manager, "Default");

            // Create a new collision controller, and set on the avatar
            CollisionController controller = new CollisionController(collisionGraph,
                    (JMECollisionSystem) collisionSystem);
            collisionChangeRequestListener.setCollisionController(controller);
            AvatarController ac = (AvatarController)avatar.getContext().getController();
            ac.setCollisionController(controller);
            if (collisionListener==null) {
                collisionListener = new WLCollisionListener();
            }
            ac.addCollisionListener(collisionListener);
        } else {
            AvatarController ac = (AvatarController)avatar.getContext().getController();
            ac.setCollisionController(null);
            ac.removeCollisionListener(collisionListener);
         }
    }

    /**
     * Sets the Z-buffer state on the given node.
     *
     * NOTE: This method assumes it is being called in a MT-Safe manner.
     */
    private void setZBufferState(Node node) {
        RenderManager rm = ClientContextJME.getWorldManager().getRenderManager();
        ZBufferState zbuf = (ZBufferState)rm.createRendererState(RenderState.StateType.ZBuffer);
        zbuf.setEnabled(true);
        zbuf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
        node.setRenderState(zbuf);
    }

    void checkBounds(Spatial placeHolder) {
        traverseGraph(placeHolder, 0);
    }

    void traverseGraph(Spatial s, int level) {
        //for (int i=0; i<level; i++) {
        //    System.out.print("\t");
        //}

        if (s instanceof Geometry) {
            Geometry g = (Geometry)s;
            g.lockBounds();
            //System.out.println("Bounds for " + g + " is : " + g.getWorldBound());
        }
        if (s instanceof Node) {
            Node n = (Node)s;
            //n.setModelBound(new BoundingSphere());
            for (int i=0; i<n.getQuantity(); i++) {
                traverseGraph(n.getChild(i), level+1);
            }
            //n.updateWorldBound();
            //System.out.println("Bounds for " + n + " is : " + n.getWorldBound());
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Node createSceneGraph(Entity entity) {
        // Nothing to do here
        return null;
    }

    /**
     * Returns the WlAvatarCharacter object for this renderer. The WlAvatarCharacter
     * provides the control points in the avatar system.
     * @return
     */
    public WlAvatarCharacter getAvatarCharacter() {
        return avatarCharacter;
    }

    public void selectForInput(boolean selected) {
        if (selectedForInput==selected)
            return;

        if (avatarCharacter==null) {
            logger.warning("selectForInput called with null avatarCharacter");
            Thread.dumpStack();
            return;
        }

        logger.info("selectedForInput "+selected);
        selectedForInput = selected;

        if (avatarCharacter!=null) {
             WorldManager wm = ClientContextJME.getWorldManager();

            ((WlAvatarContext) avatarCharacter.getContext()).getBehaviorManager().setEnable(false);

            if (controlScheme == null && selectedForInput) {
                controlScheme = new DefaultCharacterControls(ClientContextJME.getWorldManager());
                ((AvatarControls)wm.getUserData(AvatarControls.class)).setDefault(controlScheme);
            }
            setCollisionController(avatarCharacter);
            if (selectedForInput) {

                // Listen for avatar movement and update the cell
                avatarCharacter.getContext().getController().addCharacterMotionListener(characterMotionListener);

                // Listen for game context changes
                avatarCharacter.getContext().addGameContextListener(gameContextListener);
                avatarCharacter.selectForInput();
                controlScheme.addCharacterToTeam(avatarCharacter);
                controlScheme.setCharacter(avatarCharacter);

                // Chain the camera processor to the avatar motion processor for
                // smooth animation. For animated avatars we use CharacterAnimationProcessor for the simple
                // avatar CharacterProcessor
                ProcessorCollectionComponent pcc = avatarCharacter.getComponent(ProcessorCollectionComponent.class);
                ProcessorComponent characterProcessor = null;
                ProcessorComponent characterAnimationProcessor = null;
                for(ProcessorComponent pc : pcc.getProcessors()) {
                    if (pc instanceof CharacterProcessor)
                        characterProcessor = pc;
                    else if (pc instanceof CharacterAnimationProcessor) {
                        characterAnimationProcessor = pc;
                        break;
                    }
                }

                cameraChainedProcessor=null;
                if (characterAnimationProcessor!=null) {
                    cameraChainedProcessor = characterAnimationProcessor;
                } else if (characterProcessor!=null)
                    cameraChainedProcessor = characterProcessor;

                if (cameraChainedProcessor!=null) {
                    cameraChainedProcessor.addToChain(ViewManager.getViewManager().getCameraProcessor());
                    cameraChainedProcessor.setRunInRenderer(true);
                }

                // Disable culling for local avatar, fix for issue 799
                avatarCharacter.getJScene().setCullHint(CullHint.Never);
            } else {
                avatarCharacter.getContext().getController().removeCharacterMotionListener(characterMotionListener);
                avatarCharacter.getContext().removeGameContextListener(gameContextListener);
                if (controlScheme!=null) {
                    controlScheme.clearCharacterTeam();
                }

                if (cameraChainedProcessor!=null) {
                    cameraChainedProcessor.removeFromChain(ViewManager.getViewManager().getCameraProcessor());
                    cameraChainedProcessor = null;
                }
                //Reenable culling for local avatar, fix for issue 799
                avatarCharacter.getJScene().setCullHint(CullHint.Dynamic);
            }
        } else {
            logger.severe("The avatar was null during enableInputListeners().");
        }
    }

    public void trigger(int trigger, boolean pressed, String animationName) {
        if (!selectedForInput && avatarCharacter != null) {
            // Sync to avoid concurrent updates of currentTrigger and currentPressed
            synchronized (this) {
                if (currentTrigger == trigger && currentPressed == pressed) {
                    return;
                }
               
                try {
                    if (pressed) {
                        if (animationName != null) {
                            ((WlAvatarContext) avatarCharacter.getContext()).setMiscAnimation(animationName);
                        }

                        avatarCharacter.getContext().triggerPressed(trigger);
                    } else {
                        avatarCharacter.getContext().triggerReleased(trigger);
                    }
                } catch (Exception e) {
                    // We can get this if a user is viewing a female avatar but
                    // has not yet set female as the default.
                }

                currentTrigger = trigger;
                currentPressed = pressed;
            }
        }
    }
   
    public void triggerCollision(float height, boolean collision) {
        if (!selectedForInput && avatarCharacter != null) {
            ((WlAvatarController) avatarCharacter.getController()).setHeight(height);
            ((WlAvatarController) avatarCharacter.getController()).setColliding(collision);
        }
    }

    public void triggerGoto(final Vector3f position, final Quaternion look) {

        if (avatarCharacter != null) {
            SceneWorker.addWorker(new WorkCommit() {
                public void commit() {
                    PTransform xform = new PTransform(look, position,
                                                      new Vector3f(1, 1, 1));
                    avatarCharacter.getModelInst().setTransform(xform);
                }
            });
        } else {
            CellTransform transform = new CellTransform();
            transform.setRotation(look);
            transform.setTranslation(position);
       
            cell.getComponent(MovableComponent.class).localMoveRequest(transform);
        }
    }

    /**
     * Avatar model configuration listener, re-loads the avatar whenever a
     * reconfiguration happens
     */
    private class AvatarChangeListener implements AvatarConfigChangeListener {
        public void avatarConfigChanged(AvatarConfigMessage message) {
            // Fetch the new config info and try to load it. The null case is
            // handled by loadAvatar, and loads the system default avatar.
            loadAndChangeAvatar(message.getAvatarConfigInfo());
//            changeAvatar(loadAvatar(message.getAvatarConfigInfo()));
        }
    }

    /**
     * Add an avatar changed listener to the list. Duplicate checking is not
     * performed. This method is thread-safe.
     * @param listener A non-null listener
     * @throws NullPointerException If listener == null
     */
    public synchronized void addAvatarChangedListener(AvatarChangedListener listener) {
        if (listener == null)
            throw new NullPointerException("Null listener provided!");
        avatarChangedListeners.add(new WeakReference<AvatarChangedListener>(listener));
    }
   
    /**
     * Remove an avatar changed listener from the list. This method will remove
     * erroneously added duplicates if any exist.
     * @param remove A non-null listener to remove
     * @throws NullPointerException If (remove == null)
     */
    public synchronized void removeAvatarChangedListener(AvatarChangedListener remove) {
        if (remove == null)
            throw new NullPointerException("Null listener provided!");
        for (WeakReference<AvatarChangedListener> listenerRef : avatarChangedListeners) {
            AvatarChangedListener listener = listenerRef.get();
            if (listener == null || listener == remove)
                avatarChangedListeners.remove(listenerRef);
        }
    }

    /**
     * This interface is used to receive call-backs whenever the underlying avatar
     * is changed
     */
    public static interface AvatarChangedListener {
        /**
         * The avatar has changed.
         * @param newAvatar The newly assigned avatar.
         */
        public void avatarChanged(Avatar newAvatar);
    }

    class DebugNode extends Node {
        @Override
        public void draw(Renderer r) {
            System.err.println("START**********************************");
            super.draw(r);
            System.err.println("END ***********************************");
        }
    }

    class CollisionChangeRequestListener extends EventClassListener {

        private CollisionController collisionController;
        private AvatarCollisionChangeRequestEvent evt=null;

        private Class[] consumeClasses = new Class[]{
            AvatarCollisionChangeRequestEvent.class,
        };

        public void setCollisionController(CollisionController collisionController) {
            synchronized(this) {
                this.collisionController = collisionController;
                if (collisionController!=null) {
                    if (evt!=null) {
                        collisionController.setCollisionResponseEnabled(evt.isCollisionResponseEnabled());
                        collisionController.setGravityEnabled(evt.isGravityEnabled());
                    } else {
                        collisionController.setCollisionResponseEnabled(true);
                        collisionController.setGravityEnabled(true);
                    }
                }
            }
        }

        @Override
        public Class[] eventClassesToConsume() {
            return consumeClasses;
        }

        @Override
        public void commitEvent(Event event) {
        }

        @Override
        public void computeEvent(Event evtIn) {
            synchronized(this) {
                evt = (AvatarCollisionChangeRequestEvent) evtIn;
                if (collisionController!=null) {
                    collisionController.setCollisionResponseEnabled(evt.isCollisionResponseEnabled());
                    collisionController.setGravityEnabled(evt.isGravityEnabled());
                }
            }
        }
    }

    class WLCollisionListener implements AvatarCollisionListener {

        public void processCollision(CollisionInfo collisionInfo) {
            JMECollisionDetails cd = (JMECollisionDetails)collisionInfo.get(0);
            if (logger.isLoggable(Level.INFO))
                logger.info("Collided with: " + cd.getReportedNode() + " on entity: " + cd.getEntity());
            ClientContextJME.getInputManager().postEvent(new AvatarCollisionEvent(collisionInfo), cd.getEntity());
        }

    }
   
    class AvatarUIEventListener extends EventClassListener {
            private Class[] consumeClasses = new Class[]{
                AvatarRendererChangeRequestEvent.class,
                AvatarNameEvent.class
            };

            @Override
            public Class[] eventClassesToConsume() {
                return consumeClasses;
            }

            @Override
            public void commitEvent(Event event) {
                if (event instanceof AvatarNameEvent) {
                    AvatarNameEvent e = (AvatarNameEvent) event;

                    // Fetch the name tag node, there should only be one of
                    // these in the system and set the name.
                    NameTagNode nameTagNode = getNameTagNode();
                    if (nameTagNode!=null && e.getUsername().equals(username) == true) {
                        nameTagNode.setNameTag(e.getEventType(), username,
                                           e.getUsernameAlias());
                    }
                   
                    //Issue#172 & 286 : Avatar name out of sync after Mute.
                    //update server state for mute & unmute events
                    //send message to server that mute/unmute event is fired
                    String currUsername = LoginManager.getPrimary().getUsername();
                    if(e.getUsername().equals(currUsername)) {
                        if(e.getUsername().equals(username)) {
                            if(e.getEventType().equals(AvatarNameEvent.EventType.MUTE)
                                    || e.getEventType().equals(AvatarNameEvent.EventType.UNMUTE)) {
                                NameTagMessage msg = new NameTagMessage();
                                msg.setUsername(currUsername);
                                msg.setIsMute(e.getEventType().equals(AvatarNameEvent.EventType.MUTE)?true:false);
                                cell.sendCellMessage(msg);
                            }
                        }
                    }
                   
                } else if (event instanceof AvatarRendererChangeRequestEvent) {
                    handleAvatarRendererChangeRequest((AvatarRendererChangeRequestEvent)event);
                }
            }

            @Override
            public void computeEvent(Event evtIn) {
            }
    }
}
TOP

Related Classes of org.jdesktop.wonderland.modules.avatarbase.client.jme.cellrenderer.AvatarImiJME

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.