/* * Copyright 2015 MovingBlocks * * 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.terasology.logic.characters; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.logic.location.LocationComponent; import org.terasology.math.TeraMath; import org.terasology.math.geom.BaseQuat4f; import org.terasology.math.geom.BaseVector3f; import org.terasology.math.geom.Quat4f; import org.terasology.math.geom.Vector3f; import org.terasology.physics.engine.CharacterCollider; import org.terasology.physics.engine.PhysicsEngine; /** * Contains logic used by both {@link ClientCharacterPredictionSystem} and {@link ServerCharacterPredictionSystem}. */ public final class CharacterMovementSystemUtility { private final PhysicsEngine physics; public CharacterMovementSystemUtility(PhysicsEngine physicsEngine) { this.physics = physicsEngine; } /** * Sets the state of the given entity to the state represented by the * CharacterStateEvent. The state of the entity is determined by its * LocationComponent (location and orientation of physics body), * CharacterMovementComponent (velocity and various movement state * variables) and CharacterComponent for pitch and yaw (used for the camera). * * @param entity * @param state */ public void setToState(EntityRef entity, CharacterStateEvent state) { LocationComponent location = entity.getComponent(LocationComponent.class); CharacterMovementComponent movementComp = entity.getComponent(CharacterMovementComponent.class); if (location == null || movementComp == null) { return; } location.setWorldPosition(state.getPosition()); location.setWorldRotation(state.getRotation()); entity.saveComponent(location); movementComp.mode = state.getMode(); movementComp.setVelocity(state.getVelocity()); movementComp.grounded = state.isGrounded(); movementComp.footstepDelta = state.getFootstepDelta(); entity.saveComponent(movementComp); setPhysicsLocation(entity, state.getPosition()); // set the pitch to the character's gaze entity Quat4f rotation = new Quat4f(0f, TeraMath.DEG_TO_RAD * state.getPitch(), 0f); EntityRef gazeEntity = GazeAuthoritySystem.getGazeEntityForCharacter(entity); if (!gazeEntity.equals(entity)) { // Only set the gaze entity rotation if it is not the same as the main entity. // The character is assumed to only rotate side to side, introducing pitch makes things act strangely LocationComponent gazeLocation = gazeEntity.getComponent(LocationComponent.class); gazeLocation.setLocalRotation(rotation); gazeEntity.saveComponent(gazeLocation); } } public void setToInterpolateState(EntityRef entity, CharacterStateEvent a, CharacterStateEvent b, long time) { float t = (float) (time - a.getTime()) / (b.getTime() - a.getTime()); Vector3f newPos = BaseVector3f.lerp(a.getPosition(), b.getPosition(), t); Quat4f newRot = BaseQuat4f.interpolate(a.getRotation(), b.getRotation(), t); LocationComponent location = entity.getComponent(LocationComponent.class); location.setWorldPosition(newPos); location.setWorldRotation(newRot); entity.saveComponent(location); CharacterMovementComponent movementComponent = entity.getComponent(CharacterMovementComponent.class); movementComponent.mode = a.getMode(); movementComponent.setVelocity(a.getVelocity()); movementComponent.grounded = a.isGrounded(); if (b.getFootstepDelta() < a.getFootstepDelta()) { movementComponent.footstepDelta = t * (1 + b.getFootstepDelta() - a.getFootstepDelta()) + a.getFootstepDelta(); if (movementComponent.footstepDelta > 1) { movementComponent.footstepDelta -= 1; } } else { movementComponent.footstepDelta = t * (b.getFootstepDelta() - a.getFootstepDelta()) + a.getFootstepDelta(); } entity.saveComponent(movementComponent); setPhysicsLocation(entity, newPos); } public void setToExtrapolateState(EntityRef entity, CharacterStateEvent state, long time) { float t = (time - state.getTime()) * 0.0001f; Vector3f newPos = new Vector3f(state.getVelocity()); newPos.scale(t); newPos.add(state.getPosition()); extrapolateLocationComponent(entity, state, newPos); extrapolateCharacterMovementComponent(entity, state); setPhysicsLocation(entity, newPos); } private void extrapolateLocationComponent(EntityRef entity, CharacterStateEvent state, Vector3f newPos) { LocationComponent location = entity.getComponent(LocationComponent.class); location.setWorldPosition(newPos); location.setWorldRotation(state.getRotation()); entity.saveComponent(location); } private void extrapolateCharacterMovementComponent(EntityRef entity, CharacterStateEvent state) { CharacterMovementComponent movementComponent = entity.getComponent(CharacterMovementComponent.class); movementComponent.mode = state.getMode(); movementComponent.setVelocity(state.getVelocity()); movementComponent.grounded = state.isGrounded(); entity.saveComponent(movementComponent); } /** * Sets the location in the physics engine. * * @param entity The entity to set the location of. * @param newPos The new position of the entity. */ private void setPhysicsLocation(EntityRef entity, Vector3f newPos) { CharacterCollider collider = physics.getCharacterCollider(entity); collider.setLocation(newPos); } }