mirror of
				https://github.com/TES3MP/openmw-tes3mp.git
				synced 2025-11-03 23:56:47 +00:00 
			
		
		
		
	Improve actor movement collision handling
This commit is contained in:
		
							parent
							
								
									8d925b7fd6
								
							
						
					
					
						commit
						76b812f75f
					
				
					 1 changed files with 56 additions and 71 deletions
				
			
		| 
						 | 
					@ -31,17 +31,22 @@ namespace MWWorld
 | 
				
			||||||
    static const float sMaxSlope = 60.0f;
 | 
					    static const float sMaxSlope = 60.0f;
 | 
				
			||||||
    static const float sStepSize = 30.0f;
 | 
					    static const float sStepSize = 30.0f;
 | 
				
			||||||
    // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
 | 
					    // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
 | 
				
			||||||
    static const int sMaxIterations = 50;
 | 
					    static const int sMaxIterations = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class MovementSolver
 | 
					    class MovementSolver
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
        static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient, const Ogre::Vector3 &velocity, float remainingTime,
 | 
					        static float getSlope(const Ogre::Vector3 &normal)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient,
 | 
				
			||||||
 | 
					                             const Ogre::Vector3 &velocity, float &remainingTime,
 | 
				
			||||||
                             const Ogre::Vector3 &halfExtents, bool isInterior,
 | 
					                             const Ogre::Vector3 &halfExtents, bool isInterior,
 | 
				
			||||||
                             OEngine::Physic::PhysicEngine *engine)
 | 
					                             OEngine::Physic::PhysicEngine *engine)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            traceResults trace; // no initialization needed
 | 
					            traceResults trace;
 | 
				
			||||||
 | 
					 | 
				
			||||||
            newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize),
 | 
					            newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize),
 | 
				
			||||||
                     halfExtents, isInterior, engine);
 | 
					                     halfExtents, isInterior, engine);
 | 
				
			||||||
            if(trace.fraction == 0.0f)
 | 
					            if(trace.fraction == 0.0f)
 | 
				
			||||||
| 
						 | 
					@ -51,44 +56,33 @@ namespace MWWorld
 | 
				
			||||||
                     halfExtents, isInterior, engine);
 | 
					                     halfExtents, isInterior, engine);
 | 
				
			||||||
            if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope))
 | 
					            if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope))
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
 | 
					            float movefrac = trace.fraction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine);
 | 
					            newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine);
 | 
				
			||||||
            if(getSlope(trace.planenormal) <= sMaxSlope)
 | 
					            if(getSlope(trace.planenormal) <= sMaxSlope)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
 | 
					                // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
 | 
				
			||||||
                position = trace.endpos;
 | 
					                position = trace.endpos;
 | 
				
			||||||
 | 
					                remainingTime *= (1.0f-movefrac);
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static void clipVelocity(Ogre::Vector3& inout, const Ogre::Vector3& normal, float overbounce=1.0f)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            //Math stuff. Basically just project the velocity vector onto the plane represented by the normal.
 | 
					 | 
				
			||||||
            //More specifically, it projects velocity onto the normal, takes that result, multiplies it by overbounce and then subtracts it from velocity.
 | 
					 | 
				
			||||||
            float backoff = inout.dotProduct(normal);
 | 
					 | 
				
			||||||
            if(backoff < 0.0f)
 | 
					 | 
				
			||||||
                backoff *= overbounce;
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
                backoff /= overbounce;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            inout -= normal*backoff;
 | 
					        ///Project a vector u on another vector v
 | 
				
			||||||
 | 
					        static inline Ogre::Vector3 project(const Ogre::Vector3 u, const Ogre::Vector3 &v)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return v * u.dotProduct(v);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static void projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction)
 | 
					        ///Helper for computing the character sliding
 | 
				
			||||||
 | 
					        static inline Ogre::Vector3 slide(Ogre::Vector3 direction, const Ogre::Vector3 &planeNormal)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Ogre::Vector3 normalizedDirection(direction);
 | 
					            return direction - project(direction, planeNormal);
 | 
				
			||||||
            normalizedDirection.normalise();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // no divide by normalizedDirection.length necessary because it's normalized
 | 
					 | 
				
			||||||
            velocity = normalizedDirection * velocity.dotProduct(normalizedDirection);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static float getSlope(const Ogre::Vector3 &normal)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public:
 | 
					    public:
 | 
				
			||||||
        static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine)
 | 
					        static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine)
 | 
				
			||||||
| 
						 | 
					@ -104,7 +98,6 @@ namespace MWWorld
 | 
				
			||||||
                return position;
 | 
					                return position;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            bool wasCollisionMode = physicActor->getCollisionMode();
 | 
					            bool wasCollisionMode = physicActor->getCollisionMode();
 | 
				
			||||||
 | 
					 | 
				
			||||||
            physicActor->enableCollisions(false);
 | 
					            physicActor->enableCollisions(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
 | 
					            Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
 | 
				
			||||||
| 
						 | 
					@ -124,10 +117,9 @@ namespace MWWorld
 | 
				
			||||||
            if (wasCollisionMode)
 | 
					            if (wasCollisionMode)
 | 
				
			||||||
                physicActor->enableCollisions(true);
 | 
					                physicActor->enableCollisions(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (hit)
 | 
					            if(!hit)
 | 
				
			||||||
                return newPosition+Ogre::Vector3(0,0,4);
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
                return position;
 | 
					                return position;
 | 
				
			||||||
 | 
					            return newPosition+Ogre::Vector3(0,0,4);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
 | 
					        static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time,
 | 
				
			||||||
| 
						 | 
					@ -147,14 +139,13 @@ namespace MWWorld
 | 
				
			||||||
                                  movement;
 | 
					                                  movement;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            traceResults trace; //no initialization needed
 | 
					 | 
				
			||||||
            bool onground = false;
 | 
					 | 
				
			||||||
            float remainingTime = time;
 | 
					 | 
				
			||||||
            bool isInterior = !ptr.getCell()->isExterior();
 | 
					            bool isInterior = !ptr.getCell()->isExterior();
 | 
				
			||||||
            Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
 | 
					            Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1);
 | 
				
			||||||
            bool wasCollisionMode = physicActor->getCollisionMode();
 | 
					 | 
				
			||||||
            physicActor->enableCollisions(false);
 | 
					            physicActor->enableCollisions(false);
 | 
				
			||||||
            Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::UNIT_Z);
 | 
					
 | 
				
			||||||
 | 
					            traceResults trace;
 | 
				
			||||||
 | 
					            bool onground = false;
 | 
				
			||||||
 | 
					            const Ogre::Quaternion orient; // Don't rotate actor collision boxes
 | 
				
			||||||
            Ogre::Vector3 velocity;
 | 
					            Ogre::Vector3 velocity;
 | 
				
			||||||
            if(!gravity)
 | 
					            if(!gravity)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -175,53 +166,46 @@ namespace MWWorld
 | 
				
			||||||
                velocity.z += physicActor->getVerticalForce();
 | 
					                velocity.z += physicActor->getVerticalForce();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ogre::Vector3 clippedVelocity(velocity);
 | 
					 | 
				
			||||||
            if(onground)
 | 
					            if(onground)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // if we're on the ground, force velocity to track it
 | 
					                // if we're on the ground, don't try to fall
 | 
				
			||||||
                clippedVelocity.z = velocity.z = std::max(0.0f, velocity.z);
 | 
					                velocity.z = std::max(0.0f, velocity.z);
 | 
				
			||||||
                clipVelocity(clippedVelocity, trace.planenormal);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const Ogre::Vector3 up(0.0f, 0.0f, 1.0f);
 | 
					            const Ogre::Vector3 up(0.0f, 0.0f, 1.0f);
 | 
				
			||||||
            Ogre::Vector3 newPosition = position;
 | 
					            Ogre::Vector3 newPosition = position;
 | 
				
			||||||
            int iterations = 0;
 | 
					            float remainingTime = time;
 | 
				
			||||||
            do {
 | 
					            for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                // trace to where character would go if there were no obstructions
 | 
					                // trace to where character would go if there were no obstructions
 | 
				
			||||||
                newtrace(&trace, orient, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, isInterior, engine);
 | 
					                newtrace(&trace, orient, newPosition, newPosition+velocity*remainingTime, halfExtents, isInterior, engine);
 | 
				
			||||||
                newPosition = trace.endpos;
 | 
					 | 
				
			||||||
                remainingTime = remainingTime * (1.0f-trace.fraction);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // check for obstructions
 | 
					                // check for obstructions
 | 
				
			||||||
                if(trace.fraction < 1.0f)
 | 
					                if(trace.fraction >= 1.0f)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    //std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n";
 | 
					                    newPosition = trace.endpos;
 | 
				
			||||||
                    if(getSlope(trace.planenormal) <= sMaxSlope)
 | 
					                    remainingTime *= (1.0f-trace.fraction);
 | 
				
			||||||
                    {
 | 
					                    break;
 | 
				
			||||||
                        // We hit a slope we can walk on. Update velocity accordingly.
 | 
					 | 
				
			||||||
                        clipVelocity(clippedVelocity, trace.planenormal);
 | 
					 | 
				
			||||||
                        // We're only on the ground if gravity is affecting us
 | 
					 | 
				
			||||||
                        onground = gravity;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        // Can't walk on this. Try to step up onto it.
 | 
					 | 
				
			||||||
                        if((gravity && !onground) ||
 | 
					 | 
				
			||||||
                           !stepMove(newPosition, orient, velocity, remainingTime, halfExtents, isInterior, engine))
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            Ogre::Vector3 resultantDirection = trace.planenormal.crossProduct(up);
 | 
					 | 
				
			||||||
                            resultantDirection.normalise();
 | 
					 | 
				
			||||||
                            clippedVelocity = velocity;
 | 
					 | 
				
			||||||
                            projectVelocity(clippedVelocity, resultantDirection);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            // just this isn't enough sometimes. It's the same problem that causes steps to be necessary on even uphill terrain.
 | 
					 | 
				
			||||||
                            clippedVelocity += trace.planenormal*clippedVelocity.length()/50.0f;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                iterations++;
 | 
					                //std::cout<<"angle: "<<getSlope(trace.planenormal)<<"\n";
 | 
				
			||||||
            } while(iterations < sMaxIterations && remainingTime > 0.0f);
 | 
					                // We hit something. Try to step up onto it.
 | 
				
			||||||
 | 
					                if(stepMove(newPosition, orient, velocity, remainingTime, halfExtents, isInterior, engine))
 | 
				
			||||||
 | 
					                    onground = gravity;
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Can't move this way, try to find another spot along the plane
 | 
				
			||||||
 | 
					                    Ogre::Real movelen = velocity.normalise();
 | 
				
			||||||
 | 
					                    Ogre::Vector3 reflectdir = velocity.reflect(trace.planenormal);
 | 
				
			||||||
 | 
					                    reflectdir.normalise();
 | 
				
			||||||
 | 
					                    velocity = slide(reflectdir, trace.planenormal)*movelen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Do not allow sliding upward if there is gravity. Stepping will have taken
 | 
				
			||||||
 | 
					                    // care of that.
 | 
				
			||||||
 | 
					                    if(gravity)
 | 
				
			||||||
 | 
					                        velocity.z = std::min(velocity.z, 0.0f);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(onground)
 | 
					            if(onground)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -231,10 +215,11 @@ namespace MWWorld
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                    onground = false;
 | 
					                    onground = false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            physicActor->setOnGround(onground);
 | 
					            physicActor->setOnGround(onground);
 | 
				
			||||||
            physicActor->setVerticalForce(!onground ? clippedVelocity.z - time*627.2f : 0.0f);
 | 
					            physicActor->setVerticalForce(!onground ? velocity.z - time*627.2f : 0.0f);
 | 
				
			||||||
            if (wasCollisionMode)
 | 
					            physicActor->enableCollisions(true);
 | 
				
			||||||
                physicActor->enableCollisions(true);
 | 
					
 | 
				
			||||||
            return newPosition;
 | 
					            return newPosition;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue