Beispiel #1
0
        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);
        }
Beispiel #2
0
        /// <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));
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        /// <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++;
        }