/* * Copyright 2016 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.physics.engine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.engine.Time; import org.terasology.entitySystem.entity.EntityManager; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.entitySystem.entity.lifecycleEvents.BeforeDeactivateComponent; import org.terasology.entitySystem.entity.lifecycleEvents.OnActivatedComponent; import org.terasology.entitySystem.entity.lifecycleEvents.OnChangedComponent; import org.terasology.entitySystem.event.EventPriority; import org.terasology.entitySystem.event.ReceiveEvent; import org.terasology.entitySystem.systems.BaseComponentSystem; import org.terasology.entitySystem.systems.RegisterMode; import org.terasology.entitySystem.systems.RegisterSystem; import org.terasology.entitySystem.systems.UpdateSubscriberSystem; import org.terasology.logic.location.LocationComponent; import org.terasology.logic.location.LocationResynchEvent; import org.terasology.math.geom.Quat4f; import org.terasology.math.geom.Vector3f; import org.terasology.monitoring.PerformanceMonitor; import org.terasology.network.NetworkComponent; import org.terasology.network.NetworkSystem; import org.terasology.physics.CollisionGroup; import org.terasology.physics.HitResult; import org.terasology.physics.StandardCollisionGroup; import org.terasology.physics.components.RigidBodyComponent; import org.terasology.physics.components.TriggerComponent; import org.terasology.physics.events.ChangeVelocityEvent; import org.terasology.physics.events.CollideEvent; import org.terasology.physics.events.ForceEvent; import org.terasology.physics.events.ImpulseEvent; import org.terasology.physics.events.PhysicsResynchEvent; import org.terasology.physics.events.ImpactEvent; import org.terasology.physics.events.EntityImpactEvent; import org.terasology.physics.events.BlockImpactEvent; import org.terasology.registry.In; import org.terasology.world.OnChangedBlock; import org.terasology.world.WorldProvider; import org.terasology.world.block.Block; import org.terasology.world.block.BlockComponent; import java.util.Iterator; import java.util.List; /** * The PhysicsSystem is a bridging class between the event system and the * physics engine. It translates events into changes to the physics engine and * translates output of the physics engine into events. It also calls the update * method of the PhysicsEngine every frame. * */ @RegisterSystem public class PhysicsSystem extends BaseComponentSystem implements UpdateSubscriberSystem { private static final Logger logger = LoggerFactory.getLogger(PhysicsSystem.class); private static final long TIME_BETWEEN_NETSYNCS = 500; private static final CollisionGroup[] DEFAULT_COLLISION_GROUP = {StandardCollisionGroup.WORLD, StandardCollisionGroup.CHARACTER, StandardCollisionGroup.DEFAULT}; private static final float COLLISION_DAMPENING_MULTIPLIER = 0.5f; @In private Time time; @In private NetworkSystem networkSystem; @In private EntityManager entityManager; @In private PhysicsEngine physics; @In private WorldProvider worldProvider; private long lastNetsync; @Override public void initialise() { lastNetsync = 0; } @ReceiveEvent(components = {RigidBodyComponent.class, LocationComponent.class}, priority = EventPriority.PRIORITY_NORMAL) public void newRigidBody(OnActivatedComponent event, EntityRef entity) { //getter also creates the rigid body physics.getRigidBody(entity); } @ReceiveEvent(components = {TriggerComponent.class, LocationComponent.class}) //update also creates the trigger public void newTrigger(OnActivatedComponent event, EntityRef entity) { physics.updateTrigger(entity); } @ReceiveEvent(components = {RigidBodyComponent.class}) public void onImpulse(ImpulseEvent event, EntityRef entity) { physics.getRigidBody(entity).applyImpulse(event.getImpulse()); } @ReceiveEvent(components = {RigidBodyComponent.class}) public void onForce(ForceEvent event, EntityRef entity) { physics.getRigidBody(entity).applyForce(event.getForce()); } @ReceiveEvent(components = {RigidBodyComponent.class}) public void onChangeVelocity(ChangeVelocityEvent event, EntityRef entity) { if (event.getAngularVelocity() != null) { physics.getRigidBody(entity).setAngularVelocity(event.getAngularVelocity()); } if (event.getLinearVelocity() != null) { physics.getRigidBody(entity).setLinearVelocity(event.getLinearVelocity()); } } @ReceiveEvent(components = {RigidBodyComponent.class, LocationComponent.class}) public void removeRigidBody(BeforeDeactivateComponent event, EntityRef entity) { physics.removeRigidBody(entity); } @ReceiveEvent(components = {TriggerComponent.class, LocationComponent.class}) public void removeTrigger(BeforeDeactivateComponent event, EntityRef entity) { physics.removeTrigger(entity); } @ReceiveEvent(components = {TriggerComponent.class, LocationComponent.class}) public void updateTrigger(OnChangedComponent event, EntityRef entity) { physics.updateTrigger(entity); } @ReceiveEvent(components = {RigidBodyComponent.class, LocationComponent.class}) public void updateRigidBody(OnChangedComponent event, EntityRef entity) { physics.updateRigidBody(entity); } @ReceiveEvent(components = {BlockComponent.class}) public void onBlockAltered(OnChangedBlock event, EntityRef entity) { physics.awakenArea(event.getBlockPosition().toVector3f(), 0.6f); } @ReceiveEvent public void onItemImpact(ImpactEvent event, EntityRef entity) { RigidBody rigidBody = physics.getRigidBody(entity); if (rigidBody != null) { Vector3f vImpactNormal = new Vector3f(event.getImpactNormal()); Vector3f vImpactPoint = new Vector3f(event.getImpactPoint()); Vector3f vImpactSpeed = new Vector3f(event.getImpactSpeed()); float speedFactor = vImpactSpeed.length(); vImpactNormal.normalize(); vImpactSpeed.normalize(); float dotImpactNormal = vImpactSpeed.dot(vImpactNormal); Vector3f impactResult = vImpactNormal.mul(dotImpactNormal); impactResult = vImpactSpeed.sub(impactResult.mul(2.0f)); impactResult.normalize(); Vector3f vNewLocationVector = (new Vector3f(impactResult)).mul(event.getTravelDistance()); Vector3f vNewPosition = (new Vector3f(vImpactPoint)).add(vNewLocationVector); Vector3f vNewVelocity = (new Vector3f(impactResult)).mul(speedFactor * COLLISION_DAMPENING_MULTIPLIER); rigidBody.setLocation(vNewPosition); rigidBody.setLinearVelocity(vNewVelocity); rigidBody.setAngularVelocity(vNewVelocity); } } @Override public void update(float delta) { PerformanceMonitor.startActivity("Physics Renderer"); physics.update(time.getGameDelta()); PerformanceMonitor.endActivity(); //Update the velocity from physics engine bodies to Components: Iterator<EntityRef> iter = physics.physicsEntitiesIterator(); while (iter.hasNext()) { EntityRef entity = iter.next(); RigidBodyComponent comp = entity.getComponent(RigidBodyComponent.class); RigidBody body = physics.getRigidBody(entity); if (body.isActive()) { body.getLinearVelocity(comp.velocity); body.getAngularVelocity(comp.angularVelocity); Vector3f vLocation = Vector3f.zero(); body.getLocation(vLocation); Vector3f vDirection = new Vector3f(comp.velocity); float fDistanceThisFrame = vDirection.length(); vDirection.normalize(); fDistanceThisFrame = fDistanceThisFrame * delta; while (true) { HitResult hitInfo = physics.rayTrace(vLocation, vDirection, fDistanceThisFrame + 0.5f, DEFAULT_COLLISION_GROUP); if (hitInfo.isHit()) { Block hitBlock = worldProvider.getBlock(hitInfo.getBlockPosition()); if (hitBlock != null) { Vector3f vTravelledDistance = vLocation.sub(hitInfo.getHitPoint()); float fTravelledDistance = vTravelledDistance.length(); if (fTravelledDistance > fDistanceThisFrame) { break; } if (hitBlock.isPenetrable()) { if (!hitInfo.getEntity().hasComponent(BlockComponent.class)) { entity.send(new EntityImpactEvent(hitInfo.getHitPoint(), hitInfo.getHitNormal(), comp.velocity, fDistanceThisFrame, hitInfo.getEntity())); break; } fDistanceThisFrame = fDistanceThisFrame - fTravelledDistance; // decrease the remaining distance to check if we hit a block vLocation = hitInfo.getHitPoint(); } else { entity.send(new BlockImpactEvent(hitInfo.getHitPoint(), hitInfo.getHitNormal(), comp.velocity, fDistanceThisFrame, hitInfo.getEntity())); break; } } else { break; } } else { break; } } } } if (networkSystem.getMode().isServer() && time.getGameTimeInMs() - TIME_BETWEEN_NETSYNCS > lastNetsync) { sendSyncMessages(); lastNetsync = time.getGameTimeInMs(); } List<CollisionPair> collisionPairs = physics.getCollisionPairs(); for (CollisionPair pair : collisionPairs) { if (pair.b.exists()) { pair.a.send(new CollideEvent(pair.b)); } if (pair.a.exists()) { pair.b.send(new CollideEvent(pair.a)); } } } private void sendSyncMessages() { Iterator<EntityRef> iter = physics.physicsEntitiesIterator(); while (iter.hasNext()) { EntityRef entity = iter.next(); if (entity.hasComponent(NetworkComponent.class)) { //TODO after implementing rigidbody interface RigidBody body = physics.getRigidBody(entity); if (body.isActive()) { entity.send(new LocationResynchEvent(body.getLocation(new Vector3f()), body.getOrientation(new Quat4f()))); entity.send(new PhysicsResynchEvent(body.getLinearVelocity(new Vector3f()), body.getAngularVelocity(new Vector3f()))); } } } } @ReceiveEvent(components = {RigidBodyComponent.class, LocationComponent.class}, netFilter = RegisterMode.REMOTE_CLIENT) public void resynchPhysics(PhysicsResynchEvent event, EntityRef entity) { logger.debug("Received resynch event"); RigidBody body = physics.getRigidBody(entity); body.setVelocity(event.getVelocity(), event.getAngularVelocity()); } @ReceiveEvent(components = {RigidBodyComponent.class, LocationComponent.class}, netFilter = RegisterMode.REMOTE_CLIENT) public void resynchLocation(LocationResynchEvent event, EntityRef entity) { logger.debug("Received location resynch event"); RigidBody body = physics.getRigidBody(entity); body.setTransform(event.getPosition(), event.getRotation()); } public static class CollisionPair { EntityRef a; EntityRef b; public CollisionPair(EntityRef a, EntityRef b) { this.a = a; this.b = b; } } }