@Override public void generate( AxionElementGenerationCallback callback, Vector3f position, Matrix4f rotation, String axionParameter) { Vector3f workVector = new Vector3f(); callback.setMainBlock(position, baseBlock); int rangeInt = (int) range; for (int x = -rangeInt; x <= rangeInt; x++) { for (int y = -rangeInt; y <= rangeInt; y++) { for (int z = -rangeInt; z <= Math.min(rangeInt, maxZ); z++) { double distanceSquare = x * x + y * y + z * z; if (distanceSquare < innerRangeSquare) { workVector.set(x, y, z); rotation.transformVector(workVector); workVector.add(position); callback.setAdditionalBlock(workVector, baseBlock); } else if (distanceSquare < rangeSquare) { workVector.set(x, y, z); rotation.transformVector(workVector); workVector.add(position); callback.setAdditionalBlock(workVector, surroundBlock); } } } } callback.advance(advance); }
private MoveResult move( final Vector3f startPosition, final Vector3f moveDelta, final float stepHeight, final float slopeFactor, final CharacterCollider collider) { steppedUpDist = 0; stepped = false; Vector3f position = new Vector3f(startPosition); boolean hitTop = false; boolean hitBottom = false; boolean hitSide; // Actual upwards movement if (moveDelta.y > 0) { hitTop = moveDelta.y - moveUp(moveDelta.y, collider, position) > physics.getEpsilon(); } hitSide = moveHorizontal( new Vector3f(moveDelta.x, 0, moveDelta.z), collider, position, slopeFactor, stepHeight); if (moveDelta.y < 0 || steppedUpDist > 0) { float dist = (moveDelta.y < 0) ? moveDelta.y : 0; dist -= steppedUpDist; hitBottom = moveDown(dist, slopeFactor, collider, position); } if (!hitBottom && stepHeight > 0) { Vector3f tempPos = new Vector3f(position); hitBottom = moveDown(-stepHeight, slopeFactor, collider, tempPos); // Don't apply step down if nothing to step onto if (hitBottom) { position.set(tempPos); } } return new MoveResult(position, hitSide, hitBottom, hitTop); }
private void followToParent(final CharacterStateEvent state, EntityRef entity) { LocationComponent locationComponent = entity.getComponent(LocationComponent.class); if (!locationComponent.getParent().equals(EntityRef.NULL)) { Vector3f velocity = new Vector3f(locationComponent.getWorldPosition()); velocity.sub(state.getPosition()); state.getVelocity().set(velocity); state.getPosition().set(locationComponent.getWorldPosition()); } }
private void climb( final CharacterStateEvent state, CharacterMoveInputEvent input, Vector3f desiredVelocity) { if (state.getClimbDirection() == null) { return; } Vector3f tmp; Vector3i climbDir3i = state.getClimbDirection(); Vector3f climbDir3f = climbDir3i.toVector3f(); Quat4f rotation = new Quat4f(TeraMath.DEG_TO_RAD * state.getYaw(), 0, 0); tmp = new Vector3f(0.0f, 0.0f, -1.0f); QuaternionUtil.quatRotate(rotation, tmp, tmp); float angleToClimbDirection = tmp.angle(climbDir3f); boolean clearMovementToDirection = !state.isGrounded(); // facing the ladder or looking down or up if (angleToClimbDirection < Math.PI / 4.0 || Math.abs(input.getPitch()) > 60f) { float pitchAmount = state.isGrounded() ? 45f : 90f; float pitch = input.getPitch() > 30f ? pitchAmount : -pitchAmount; rotation = new Quat4f(TeraMath.DEG_TO_RAD * state.getYaw(), TeraMath.DEG_TO_RAD * pitch, 0); QuaternionUtil.quatRotate(rotation, desiredVelocity, desiredVelocity); // looking sidewards from ladder } else if (angleToClimbDirection < Math.PI * 3.0 / 4.0) { float rollAmount = state.isGrounded() ? 45f : 90f; tmp = new Vector3f(); QuaternionUtil.quatRotate(rotation, climbDir3f, tmp); float leftOrRight = tmp.x; float plusOrMinus = (leftOrRight < 0f ? -1.0f : 1.0f) * (climbDir3i.x != 0 ? -1.0f : 1.0f); rotation = new Quat4f( TeraMath.DEG_TO_RAD * input.getYaw(), 0f, TeraMath.DEG_TO_RAD * rollAmount * plusOrMinus); QuaternionUtil.quatRotate(rotation, desiredVelocity, desiredVelocity); // facing away from ladder } else { rotation = new Quat4f(TeraMath.DEG_TO_RAD * state.getYaw(), 0, 0); QuaternionUtil.quatRotate(rotation, desiredVelocity, desiredVelocity); clearMovementToDirection = false; } // clear out movement towards or away from the ladder if (clearMovementToDirection) { if (climbDir3i.x != 0) { desiredVelocity.x = 0f; } if (climbDir3i.z != 0) { desiredVelocity.z = 0f; } } }
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); extrapolateCharacterComponent(entity, state); setPhysicsLocation(entity, newPos); }
private void updateFocalDistance(HitResult hitInfo, float delta) { float focusRate = 4.0f; // how fast the focus distance is updated // if the hit result from a trace has a recorded a hit if (hitInfo.isHit()) { Vector3f playerToTargetRay = new Vector3f(); // calculate the distance from the player to the hit point playerToTargetRay.sub(hitInfo.getHitPoint(), localPlayer.getPosition()); // gradually adjust focalDistance from it's current value to the hit point distance focalDistance = TeraMath.lerp(focalDistance, playerToTargetRay.length(), delta * focusRate); // if nothing was hit, gradually adjust the focusDistance to the maximum length of the update // function trace } else { focalDistance = TeraMath.lerp(focalDistance, targetDistance, delta * focusRate); } }
private float moveUp(float riseAmount, CharacterCollider collider, Vector3f position) { Vector3f to = new Vector3f(position.x, position.y + riseAmount + VERTICAL_PENETRATION_LEEWAY, position.z); if (collider != null) { SweepCallback callback = collider.sweep(position, to, VERTICAL_PENETRATION_LEEWAY, -1f); if (callback.hasHit()) { float actualDist = Math.max( 0, ((riseAmount + VERTICAL_PENETRATION_LEEWAY) * callback.getClosestHitFraction()) - VERTICAL_PENETRATION_LEEWAY); position.y += actualDist; return actualDist; } } position.y += riseAmount; return riseAmount; }
@ReceiveEvent(components = {HealthComponent.class}) public void onCrash(HorizontalCollisionEvent event, EntityRef entity) { HealthComponent health = entity.getComponent(HealthComponent.class); Vector3f vel = new Vector3f(event.getVelocity()); vel.y = 0; float speed = vel.length(); if (speed > health.horizontalDamageSpeedThreshold) { int damage = (int) ((speed - health.horizontalDamageSpeedThreshold) * health.excessSpeedDamageMultiplier); if (damage > 0) { checkDamage( entity, damage, EngineDamageTypes.PHYSICAL.get(), EntityRef.NULL, EntityRef.NULL); } } }
@ReceiveEvent public void onCrash( HorizontalCollisionEvent event, EntityRef entity, CharacterSoundComponent characterSounds, HealthComponent healthComponent) { Vector3f horizVelocity = new Vector3f(event.getVelocity()); horizVelocity.y = 0; float velocity = horizVelocity.length(); if (velocity > healthComponent.horizontalDamageSpeedThreshold) { if (characterSounds.lastSoundTime + CharacterSoundSystem.MIN_TIME < time.getGameTimeInMs()) { StaticSound sound = random.nextItem(characterSounds.landingSounds); if (sound != null) { entity.send(new PlaySoundEvent(sound, characterSounds.landingVolume)); characterSounds.lastSoundTime = time.getGameTimeInMs(); entity.saveComponent(characterSounds); } } } }
private Vector3f extractResidualMovement( Vector3f hitNormal, Vector3f direction, float normalMag) { float movementLength = direction.length(); if (movementLength > physics.getEpsilon()) { direction.normalize(); Vector3f reflectDir = Vector3fUtil.reflect(direction, hitNormal, new Vector3f()); reflectDir.normalize(); Vector3f perpendicularDir = Vector3fUtil.getPerpendicularComponent(reflectDir, hitNormal, new Vector3f()); if (normalMag != 0.0f) { Vector3f perpComponent = new Vector3f(perpendicularDir); perpComponent.scale(normalMag * movementLength); direction.set(perpComponent); } } return direction; }
@ReceiveEvent public void onActivate( ActivateEvent event, EntityRef entity, TunnelActionComponent tunnelActionComponent) { Vector3f dir = new Vector3f(event.getDirection()); dir.scale(4.0f); Vector3f origin = new Vector3f(event.getOrigin()); origin.add(dir); Vector3i blockPos = new Vector3i(); int particleEffects = 0; int blockCounter = tunnelActionComponent.maxDestroyedBlocks; for (int s = 0; s <= tunnelActionComponent.maxTunnelDepth; s++) { origin.add(dir); if (!worldProvider.isBlockRelevant(origin)) { break; } for (int i = 0; i < tunnelActionComponent.maxRaysCast; i++) { Vector3f direction = random.nextVector3f(); Vector3f impulse = new Vector3f(direction); impulse.scale(tunnelActionComponent.explosiveForce); for (int j = 0; j < 3; j++) { Vector3f target = new Vector3f(origin); target.x += direction.x * j; target.y += direction.y * j; target.z += direction.z * j; blockPos.set((int) target.x, (int) target.y, (int) target.z); Block currentBlock = worldProvider.getBlock(blockPos); if (currentBlock.isDestructible()) { if (particleEffects < tunnelActionComponent.maxParticalEffects) { EntityBuilder builder = entityManager.newBuilder("engine:smokeExplosion"); builder.getComponent(LocationComponent.class).setWorldPosition(target); builder.build(); particleEffects++; } if (random.nextFloat() < tunnelActionComponent.thoroughness) { EntityRef blockEntity = blockEntityRegistry.getEntityAt(blockPos); blockEntity.send( new DoDamageEvent( tunnelActionComponent.damageAmount, tunnelActionComponent.damageType)); } blockCounter--; } if (blockCounter <= 0) { return; } } } } // No blocks were destroyed, so cancel the event if (blockCounter == tunnelActionComponent.maxDestroyedBlocks) { event.consume(); } }
@Override public String toString(Vector3f value) { return value != null ? value.toString() : ""; }
private void walk( final CharacterMovementComponent movementComp, final CharacterStateEvent state, CharacterMoveInputEvent input, EntityRef entity) { Vector3f desiredVelocity = new Vector3f(input.getMovementDirection()); float lengthSquared = desiredVelocity.lengthSquared(); // If the length of desired movement is > 1, normalise it to prevent movement being faster than // allowed. // (Desired velocity < 1 is allowed, as the character may wish to walk/crawl/otherwise move // slowly) if (lengthSquared > 1) { desiredVelocity.normalize(); } desiredVelocity.scale(movementComp.speedMultiplier); float maxSpeed = getMaxSpeed(entity, movementComp); if (input.isRunning()) { maxSpeed *= movementComp.runFactor; } // As we can't use it, remove the y component of desired movement while maintaining speed. if (movementComp.grounded && desiredVelocity.y != 0) { float speed = desiredVelocity.length(); desiredVelocity.y = 0; if (desiredVelocity.x != 0 || desiredVelocity.z != 0) { desiredVelocity.normalize(); desiredVelocity.scale(speed); } } desiredVelocity.scale(maxSpeed); if (movementComp.mode == MovementMode.CLIMBING) { climb(state, input, desiredVelocity); } // Modify velocity towards desired, up to the maximum rate determined by friction Vector3f velocityDiff = new Vector3f(desiredVelocity); velocityDiff.sub(state.getVelocity()); velocityDiff.scale(Math.min(movementComp.mode.scaleInertia * input.getDelta(), 1.0f)); Vector3f endVelocity = new Vector3f(state.getVelocity()); endVelocity.x += velocityDiff.x; endVelocity.z += velocityDiff.z; if (movementComp.mode.scaleGravity == 0) { // apply the velocity without gravity endVelocity.y += velocityDiff.y; } else if (movementComp.mode.applyInertiaToVertical) { endVelocity.y += Math.max( -TERMINAL_VELOCITY, velocityDiff.y - (GRAVITY * movementComp.mode.scaleGravity) * input.getDelta()); } else { endVelocity.y = Math.max( -TERMINAL_VELOCITY, state.getVelocity().y - (GRAVITY * movementComp.mode.scaleGravity) * input.getDelta()); } Vector3f moveDelta = new Vector3f(endVelocity); moveDelta.scale(input.getDelta()); CharacterCollider collider = movementComp.mode.useCollision ? physics.getCharacterCollider(entity) : null; MoveResult moveResult = move( state.getPosition(), moveDelta, (state.getMode() != MovementMode.CLIMBING && state.isGrounded() && movementComp.mode.canBeGrounded) ? movementComp.stepHeight : 0, movementComp.slopeFactor, collider); Vector3f distanceMoved = new Vector3f(moveResult.getFinalPosition()); distanceMoved.sub(state.getPosition()); state.getPosition().set(moveResult.getFinalPosition()); if (input.isFirstRun() && distanceMoved.length() > 0) { entity.send(new MovedEvent(distanceMoved, state.getPosition())); } if (moveResult.isBottomHit()) { if (!state.isGrounded() && movementComp.mode.canBeGrounded) { if (input.isFirstRun()) { Vector3f landVelocity = new Vector3f(state.getVelocity()); landVelocity.y += (distanceMoved.y / moveDelta.y) * (endVelocity.y - state.getVelocity().y); logger.debug("Landed at " + landVelocity); entity.send(new VerticalCollisionEvent(state.getPosition(), landVelocity)); } state.setGrounded(true); } endVelocity.y = 0; // Jumping is only possible, if the entity is standing on ground if (input.isJumpRequested()) { state.setGrounded(false); endVelocity.y += movementComp.jumpSpeed; if (input.isFirstRun()) { entity.send(new JumpEvent()); } } } else { if (moveResult.isTopHit() && endVelocity.y > 0) { endVelocity.y = -0.5f * endVelocity.y; } state.setGrounded(false); } state.getVelocity().set(endVelocity); if (input.isFirstRun() && moveResult.isHorizontalHit()) { entity.send(new HorizontalCollisionEvent(state.getPosition(), state.getVelocity())); } if (state.isGrounded() || movementComp.mode == MovementMode.SWIMMING || movementComp.mode == MovementMode.DIVING) { state.setFootstepDelta( state.getFootstepDelta() + distanceMoved.length() / movementComp.distanceBetweenFootsteps); if (state.getFootstepDelta() > 1) { state.setFootstepDelta(state.getFootstepDelta() - 1); if (input.isFirstRun()) { switch (movementComp.mode) { case WALKING: entity.send(new FootstepEvent()); break; case DIVING: case SWIMMING: entity.send(new SwimStrokeEvent(worldProvider.getBlock(state.getPosition()))); break; } } } } }
private boolean moveHorizontal( Vector3f horizMove, CharacterCollider collider, Vector3f position, float slopeFactor, float stepHeight) { float remainingFraction = 1.0f; float dist = horizMove.length(); if (dist < physics.getEpsilon()) { return false; } boolean horizontalHit = false; Vector3f normalizedDir = Vector3fUtil.safeNormalize(horizMove, new Vector3f()); if (collider == null) { // ignore collision normalizedDir.scale(dist); position.add(normalizedDir); return false; } Vector3f targetPos = new Vector3f(normalizedDir); targetPos.scale(dist + HORIZONTAL_PENETRATION_LEEWAY); targetPos.add(position); int iteration = 0; Vector3f lastHitNormal = new Vector3f(0, 1, 0); while (remainingFraction >= 0.01f && iteration++ < 10) { SweepCallback callback = collider.sweep(position, targetPos, HORIZONTAL_PENETRATION, slopeFactor); /* Note: this isn't quite correct (after the first iteration the closestHitFraction is only for part of the moment) but probably close enough */ float actualDist = Math.max( 0, (dist + HORIZONTAL_PENETRATION_LEEWAY) * callback.getClosestHitFraction() - HORIZONTAL_PENETRATION_LEEWAY); if (actualDist != 0) { remainingFraction -= actualDist / dist; } if (callback.hasHit()) { if (actualDist > physics.getEpsilon()) { Vector3f actualMove = new Vector3f(normalizedDir); actualMove.scale(actualDist); position.add(actualMove); } dist -= actualDist; Vector3f newDir = new Vector3f(normalizedDir); newDir.scale(dist); float slope = callback.getHitNormalWorld().dot(new Vector3f(0, 1, 0)); // We step up if we're hitting a big slope, or if we're grazing // the ground, otherwise we move up a shallow slope. if (slope < slopeFactor || 1 - slope < physics.getEpsilon()) { boolean stepping = checkStep(collider, position, newDir, callback, slopeFactor, stepHeight); if (!stepping) { horizontalHit = true; Vector3f newHorizDir = new Vector3f(newDir.x, 0, newDir.z); Vector3f horizNormal = new Vector3f(callback.getHitNormalWorld().x, 0, callback.getHitNormalWorld().z); if (horizNormal.lengthSquared() > physics.getEpsilon()) { horizNormal.normalize(); if (lastHitNormal.dot(horizNormal) > physics.getEpsilon()) { break; } lastHitNormal.set(horizNormal); extractResidualMovement(horizNormal, newHorizDir); } newDir.set(newHorizDir); } } else { // Hitting a shallow slope, move up it Vector3f newHorizDir = new Vector3f(newDir.x, 0, newDir.z); extractResidualMovement(callback.getHitNormalWorld(), newDir); Vector3f modHorizDir = new Vector3f(newDir); modHorizDir.y = 0; newDir.scale(newHorizDir.length() / modHorizDir.length()); } float sqrDist = newDir.lengthSquared(); if (sqrDist > physics.getEpsilon()) { newDir.normalize(); if (newDir.dot(normalizedDir) <= 0.0f) { break; } } else { break; } dist = (float) Math.sqrt(sqrDist); normalizedDir.set(newDir); targetPos.set(normalizedDir); targetPos.scale(dist + HORIZONTAL_PENETRATION_LEEWAY); targetPos.add(position); } else { normalizedDir.scale(dist); position.add(normalizedDir); break; } } return horizontalHit; }
private boolean moveDown( float dist, float slopeFactor, CharacterCollider collider, Vector3f position) { if (collider == null) { position.y += dist; return false; } float remainingDist = -dist; Vector3f targetPos = new Vector3f(position); targetPos.y -= remainingDist + VERTICAL_PENETRATION_LEEWAY; Vector3f normalizedDir = new Vector3f(0, -1, 0); boolean hit = false; int iteration = 0; while (remainingDist > physics.getEpsilon() && iteration++ < 10) { SweepCallback callback = collider.sweep(position, targetPos, VERTICAL_PENETRATION, -1.0f); float actualDist = Math.max( 0, (remainingDist + VERTICAL_PENETRATION_LEEWAY) * callback.getClosestHitFraction() - VERTICAL_PENETRATION_LEEWAY); Vector3f expectedMove = new Vector3f(targetPos); expectedMove.sub(position); if (expectedMove.lengthSquared() > physics.getEpsilon()) { expectedMove.normalize(); expectedMove.scale(actualDist); position.add(expectedMove); } remainingDist -= actualDist; if (remainingDist < physics.getEpsilon()) { break; } if (callback.hasHit()) { float originalSlope = callback.getHitNormalWorld().dot(new Vector3f(0, 1, 0)); if (originalSlope < slopeFactor) { float slope = callback.calculateAverageSlope(originalSlope, CHECK_FORWARD_DIST); if (slope < slopeFactor) { remainingDist -= actualDist; expectedMove.set(targetPos); expectedMove.sub(position); extractResidualMovement(callback.getHitNormalWorld(), expectedMove); float sqrDist = expectedMove.lengthSquared(); if (sqrDist > physics.getEpsilon()) { expectedMove.normalize(); if (expectedMove.dot(normalizedDir) <= 0.0f) { hit = true; break; } } else { hit = true; break; } if (expectedMove.y > -physics.getEpsilon()) { hit = true; break; } normalizedDir.set(expectedMove); expectedMove.scale(-remainingDist / expectedMove.y + HORIZONTAL_PENETRATION_LEEWAY); targetPos.set(position); targetPos.add(expectedMove); } else { hit = true; break; } } else { hit = true; break; } } else { break; } } if (iteration >= 10) { hit = true; } return hit; }
/** * Checks whether a character should change movement mode (from being underwater or in a ladder). * A higher and lower point of the character is tested for being in water, only if both points are * in water does the character count as swimming. <br> * <br> * Sends the OnEnterLiquidEvent and OnLeaveLiquidEvent events. * * @param movementComp The movement component of the character. * @param state The current state of the character. */ private void checkMode( final CharacterMovementComponent movementComp, final CharacterStateEvent state, final CharacterStateEvent oldState, EntityRef entity, boolean firstRun) { // If we are ghosting or we can't move, the mode cannot be changed. if (!state.getMode().respondToEnvironment) { return; } Vector3f worldPos = state.getPosition(); Vector3f top = new Vector3f(worldPos); Vector3f bottom = new Vector3f(worldPos); top.y += 0.5f * movementComp.height; bottom.y -= 0.5f * movementComp.height; final boolean topUnderwater = worldProvider.getBlock(top).isLiquid(); final boolean bottomUnderwater = worldProvider.getBlock(bottom).isLiquid(); final boolean newSwimming = !topUnderwater && bottomUnderwater; final boolean newDiving = topUnderwater && bottomUnderwater; boolean newClimbing = false; // TODO: refactor this knot of if-else statements into something easy to read. Some sub-methods // and switch statements would be nice. if (!newSwimming && !newDiving) { // TODO: generalize to isClimbingAllowed() or similar Vector3f[] sides = { new Vector3f(worldPos), new Vector3f(worldPos), new Vector3f(worldPos), new Vector3f(worldPos), new Vector3f(worldPos) }; float factor = 1.0f; sides[0].x += factor * movementComp.radius; sides[1].x -= factor * movementComp.radius; sides[2].z += factor * movementComp.radius; sides[3].z -= factor * movementComp.radius; sides[4].y -= movementComp.height; float distance = 100f; for (Vector3f side : sides) { Block block = worldProvider.getBlock(side); if (block.isClimbable()) { // If any of our sides are near a climbable block, check if we are near to the side Vector3i myPos = new Vector3i(worldPos, 0.5f); Vector3i climbBlockPos = new Vector3i(side, 0.5f); Vector3i dir = new Vector3i(block.getDirection().getVector3i()); float currentDistance = 10f; if (dir.x != 0 && Math.abs(worldPos.x - (float) climbBlockPos.x + (float) dir.x * .5f) < movementComp.radius + 0.1f) { newClimbing = true; if (myPos.x < climbBlockPos.x) { dir.x = -dir.x; } currentDistance = Math.abs(climbBlockPos.z - worldPos.z); } else if (dir.z != 0 && Math.abs(worldPos.z - (float) climbBlockPos.z + (float) dir.z * .5f) < movementComp.radius + 0.1f) { newClimbing = true; if (myPos.z < climbBlockPos.z) { dir.z = -dir.z; } currentDistance = Math.abs(climbBlockPos.z - worldPos.z); } // if there are multiple climb blocks, choose the nearest one. This can happen when there // are two // adjacent ledges around a corner. if (currentDistance < distance) { distance = currentDistance; state.setClimbDirection(dir); } } } } if (newDiving) { if (state.getMode() != MovementMode.DIVING) { state.setMode(MovementMode.DIVING); } } else if (newSwimming) { if (state.getMode() != MovementMode.SWIMMING) { state.setMode(MovementMode.SWIMMING); } state.getVelocity().y += 0.02; } else if (state.getMode() == MovementMode.SWIMMING) { if (newClimbing) { state.setMode(MovementMode.CLIMBING); state.getVelocity().y = 0; } else { if (state.getVelocity().y > 0) { state.getVelocity().y += 4; } state.setMode(MovementMode.WALKING); } } else if (newClimbing != (state.getMode() == MovementMode.CLIMBING)) { // We need to toggle the climbing mode state.getVelocity().y = 0; state.setMode((newClimbing) ? MovementMode.CLIMBING : MovementMode.WALKING); } }