public static MotionResult PhysicsStepHorizontal(ref CharacterPhysicsInfo cpi, ref Position position, ref ThreeDVelocity velocity, bool onGround, int coefficientOfRestitution256, int groundFriction256, WorldPhysics physics) { MotionResult result = MotionResult.None; int newPositionX = velocity.X.Update(position.X); int newPositionZ = velocity.Z.Update(position.Z); int deltaX = newPositionX - position.X; int deltaZ = newPositionZ - position.Z; if (deltaX != 0) { if (CharacterPhysics.TryMoveHorizontalDidHitWall(ref cpi, physics, deltaX, 0, onGround, ref position)) { // Hit wall, bounce: velocity.X.Scale256(-coefficientOfRestitution256); result = MotionResult.HitWall; } } // Z-motion just stops if it hits anything if (deltaZ != 0 && CharacterPhysics.TryMoveHorizontalDidHitWall(ref cpi, physics, 0, deltaZ, onGround, ref position)) { velocity.Z.Reset(); // NOTE: Currently not even bothering to give a hit result, just carry on... } // TODO: Slope handling for rolling objects if (onGround) { // Friction: velocity.X.Scale256(groundFriction256); velocity.Z.Scale256(groundFriction256); } return(result); }
/// <summary>Get the Y position of the ground for this actor.</summary> public int GroundHeight(WorldPhysics physics) { var cpi = new CharacterPhysicsInfo(animationSet, this); return(CharacterPhysics.GroundHeight(ref cpi, physics, position)); }
public static MotionResult PhysicsStepVertical(ref CharacterPhysicsInfo cpi, ref Position position, ref ThreeDVelocity velocity, ref bool onGround, int coefficientOfRestitution256, int gravityUp256, int gravityDown256, bool lockVertical, WorldPhysics physics, bool groundIsAPit) { if (lockVertical) { velocity.Y.Reset(); } else { if (velocity.Y.Velocity256 > 0) // Moving upwards { onGround = false; int startY = position.Y + cpi.height; position.Y = velocity.Y.Update(position.Y, gravityUp256); int endY = position.Y + cpi.height; if (endY > startY) { int ceiling = physics.GetCeilingHeightInXRange(position.X + cpi.startX, position.X + cpi.endX, position.Z, startY, endY, cpi.owner); if (ceiling < endY) // Hit ceiling { position.Y = ceiling - cpi.height; velocity.Y.Velocity256 = -((velocity.Y.Velocity256 * coefficientOfRestitution256) >> 8); // <- Bounce (fixed-point multiply) return(MotionResult.HitCeiling); } } } else // Moving downwards { int groundHeight = CharacterPhysics.GroundHeight(ref cpi, physics, position); if (position.Y > groundHeight) { onGround = false; // we came off the ground } else if (position.Y < groundHeight) // TODO: This needs some rethinking (added to let separation do its thing) { onGround = true; // we are embedded in the ground (we may start physics in this state, if we are put here by teleport/animation) } else if (position.Y == groundHeight && velocity.Y.Velocity256 == 0) { onGround = true; // we were gently placed on the ground (don't trigger OnHitGround) } if (!onGround) { position.Y = velocity.Y.Update(position.Y, gravityDown256); if (position.Y <= groundHeight) // Hit the ground { position.Y = groundHeight; if (groundHeight == 0 && groundIsAPit) { velocity = new ThreeDVelocity(); return(MotionResult.HitBottomOfPit); } else if (velocity.Y.Velocity256 > -128) // <- Kill velocity of we're moving too slowly downwards (start rolling) { onGround = true; velocity.Y.Reset(); } else { onGround = false; velocity.Y.Scale256(-coefficientOfRestitution256); velocity.X.Scale256(coefficientOfRestitution256); velocity.Z.Scale256(coefficientOfRestitution256); } return(MotionResult.HitGround); } } } } return(MotionResult.None); }
/// <summary> /// Add a moving object to the physics system. A moving object may have a heightmap that will be considered for collision (like passing to <see cref="AddCollider"/>), /// but the moving object itself is a flat plane at the object's origin with the object's physics width and height (depth of one pixel). /// The moving object will be moved in response to various physics conditions (separation out of walls, movement when stacked, movement on conveyers, etc). /// </summary> /// <param name="owner">The object that the mover instance is associated with. Only one mover per actor.</param> /// <param name="physicsSource">The animation set that provides physics data for the mover.</param> /// <param name="doNotMove"> /// The mover will not be moved by the physics engine for any reason (eg: separation, stacking, conveyers). /// Typically used for objects that are moved by the engine on a fixed path, but we still want them to interact with other game objects (eg: things stacked on them will move). /// </param> /// <param name="noAutoIgnore"> /// If two movers interpenetrate, then they will automatically ignore each other for collisions, allowing them to move through each other. /// This disables that behaviour, which will cause interpenetrating objects to get stuck, because every possible movement results in a collision. /// Typically combined with <see cref="pretendToBeStatic"/>, which causes separation to resolve the interpenetration. /// </param> /// <param name="pretendToBeStatic"> /// Makes the mover act like a part of the static scenery. Affects query results that specifically ask for static collisions, /// but is mostly used to make the mover object considered for separation. Should typically only be set on objects that have /// <see cref="doNotMove"/> set, otherwise the separation system can get into messy looping situations. /// </param> /// <param name="updateBeforeSeparation"> /// The state of the world for collisions is based on what it was at the beginning of the frame. This causes the mover's entry to be /// updated immediately before separation is processed, so that separation from moving objects happens based on their position at the /// end of the frame, rather than the beginning. Combine with <see cref="pretendToBeStatic"/> for meaningful results. /// </param> public void AddMover(Actor owner, AnimationSet physicsSource, bool doNotMove = false, bool noAutoIgnore = false, bool pretendToBeStatic = false, bool updateBeforeSeparation = false) { // // Add collider: if (physicsSource.Heightmap != null) { if (physicsSource.Heightmap.DefaultHeight != 0) { return; // Refuse to add objects with non-zero default heights (only valid for levels and shadow-receivers) } AddCollider(owner, new HeightmapView(physicsSource.Heightmap, owner.position, owner.facingLeft), pretendToBeStatic); if (updateBeforeSeparation) { collidersToUpdateBeforeSeparation.Add(colliderCount - 1); } } // // Add mover: EnsureMoverCapacity(); actorToMoverIndex.Add(owner, moverCount); // <- Ensure movers are unique moverWeDoNotMove[moverCount] = doNotMove; moverNoAutoIgnore[moverCount] = noAutoIgnore; moverActors[moverCount] = owner; Position position = moverInitialPositions[moverCount] = owner.position; bool facingLeft = moverInitialFacingLeft[moverCount] = owner.facingLeft; moverBoundsX[moverCount] = new Range(physicsSource.physicsStartX, physicsSource.physicsEndX).MaybeFlip(facingLeft) + position.X; moverBoundsZ[moverCount] = ((physicsSource.Heightmap == null) ? new Range(0, 1) : new Range(physicsSource.physicsStartZ, physicsSource.physicsEndZ)) + position.Z; // Flat for characters // NOTE: We assume that heightmap'd objects have their auto-generated physics sizes here: if (physicsSource.Heightmap != null && physicsSource.physicsHeight >= Heightmap.Infinity) { moverBoundsY[moverCount] = new Range(0, MaximumHeight); } else { moverBoundsY[moverCount] = new Range(position.Y, position.Y + physicsSource.physicsHeight); } moverHeightmaps[moverCount] = physicsSource.Heightmap; // May be null // TODO: Make this a parameter of the animation set moverCharacterPhysics[moverCount] = new CharacterPhysicsInfo(physicsSource, owner); Debug.Assert(physicsSource.Heightmap == null || physicsSource.Heightmap.DefaultHeight == 0); // If we have a heightmap, it must be a standard object heightmap // // Stacking and intersection-ignores: moverParentIndex[moverCount] = -1; moverFirstChildIndex[moverCount] = -1; moverNextSiblingIndex[moverCount] = -1; for (int i = 0; i < moverCount; i++) { if (!Range.Overlaps(moverBoundsX[moverCount], moverBoundsX[i]) || !Range.Overlaps(moverBoundsZ[moverCount], moverBoundsZ[i])) { continue; } // PERF: We're touching each heightmap twice for stacking and intersection. Can probably reduce down to one check. // (Then again, once we cache it, is spinning on it longer such a big deal?) // Handle stacking: if (moverBoundsY[moverCount].end < MaximumHeight && moverBoundsY[i].end < MaximumHeight) // <- infinite height things, or things above the world, do not stack { if (moverHeightmaps[i] != null && moverParentIndex[moverCount] == -1 && moverBoundsY[moverCount].start > moverBoundsY[i].start && moverBoundsY[moverCount].start <= moverBoundsY[i].end) // added can rest on existing { TryAddRestingOn(moverCount, i); } else if (moverHeightmaps[moverCount] != null && moverParentIndex[i] == -1 && moverBoundsY[i].start > moverBoundsY[moverCount].start && moverBoundsY[i].start <= moverBoundsY[moverCount].end) // existing can rest on added { TryAddRestingOn(i, moverCount); } } // Handle intersection: if (!noAutoIgnore && !moverNoAutoIgnore[i] && Range.Overlaps(moverBoundsY[moverCount], moverBoundsY[i])) { bool hasIntersection = false; if (moverHeightmaps[i] != null) { if (moverHeightmaps[moverCount] != null) { hasIntersection = SolidVsSolidIntersection(i, moverCount); } else { hasIntersection = SolidVsCharacterIntersection(i, moverCount); } } else if (moverHeightmaps[moverCount] != null) { hasIntersection = SolidVsCharacterIntersection(moverCount, i); } if (hasIntersection) { AddIgnoredPair(moverActors[i], moverActors[moverCount]); } } } moverCount++; }