sokol-luajit/lua/opensteer/os-simplevehicle.lua
2025-10-17 09:43:15 +10:30

262 lines
12 KiB
Lua

local veclib = require("opensteer.os-vec")
local osmath, osvec, Vec3 = veclib.osmath, veclib.osvec, veclib.vec3
local SteerLibrary = require("opensteer.os-library")
local LocalSpace = require("opensteer.os-localspace")
local serialNumberCounter = 0
-- -- // SimpleVehicle adds concrete vehicle methods to SimpleVehicle_3
local SimpleVehicle = function()
local self = {}
-- -- // maintain unique serial numbers
self.serialNumber = serialNumberCounter
serialNumberCounter = serialNumberCounter + 1
self._mass = 0.0 -- -- // mass (defaults to unity so acceleration=force)
self._radius = 0.0 -- -- // size of bounding sphere, for obstacle avoidance, etc.
self._speed = 0.0 -- -- // speed along Forward direction. Because local space
-- -- // is velocity-aligned, velocity = Forward * Speed
self._maxForce = 0.0 -- -- // the maximum steering force this vehicle can apply
-- -- // (steering force is clipped to this magnitude)
self._maxSpeed = 0.0 -- -- // the maximum speed this vehicle is allowed to move
-- -- // (velocity is clipped to this magnitude)
self._curvature = 0.0
self._lastForward = Vec3()
self._lastPosition = Vec3()
self._smoothedPosition = Vec3()
self._smoothedCurvature = 0.0
self._smoothedAcceleration = Vec3()
SteerLibrary(self)
LocalSpace(self)
-- -- // reset vehicle state
self.reset = function()
-- -- // reset LocalSpace state
self.resetLocalSpace(self.localspace)
self.setMass(1.0) -- -- // mass (defaults to 1 so acceleration=force)
self.setSpeed(0.0) -- -- // speed along Forward direction.
self.setRadius(0.5) -- -- // size of bounding sphere
self.setMaxForce(0.1) -- -- // steering force is clipped to this magnitude
self.setMaxSpeed(1.0) -- -- // velocity is clipped to this magnitude
-- -- // reset bookkeeping to do running averages of these quanities
self.resetSmoothedPosition()
self.resetSmoothedCurvature()
self.resetSmoothedAcceleration()
end
-- -- // get/set mass
self.mass = function() return self._mass end
self.setMass = function(m) self._mass = m; return m end
-- -- // get velocity of vehicle
self.velocity = function() return self.forward().mult(self._speed) end
-- -- // get/set speed of vehicle (may be faster than taking mag of velocity)
self.speed = function() return self._speed end
self.setSpeed = function(s) self._speed = s; return s end
-- -- // size of bounding sphere, for obstacle avoidance, etc.
self.radius = function() return self._radius end
self.setRadius = function(m) self._radius = m; return m end
-- -- // get/set maxForce
self.maxForce = function() return self._maxForce end
self.setMaxForce = function(mf) self._maxForce = mf; return mf end
-- -- // get/set maxSpeed
self.maxSpeed = function() return self._maxSpeed end
self.setMaxSpeed = function(ms) self._maxSpeed = ms; return ms end
-- -- // ratio of speed to max possible speed (0 slowest, 1 fastest)
self.relativeSpeed = function() return self.speed() / self.maxSpeed() end
-- -- // apply a given steering force to our momentum,
-- -- // adjusting our orientation to maintain velocity-alignment.
self.applySteeringForce = function( force, elapsedTime )
local adjustedForce = self.adjustRawSteeringForce(force, elapsedTime)
-- -- // enforce limit on magnitude of steering force
local clippedForce = adjustedForce.truncateLength( self.maxForce() )
-- -- // compute acceleration and velocity
local newAcceleration = clippedForce.div( self.mass() )
local newVelocity = self.velocity()
-- -- // damp out abrupt changes and oscillations in steering acceleration
-- -- // (rate is proportional to time step, then clipped into useful range)
if (elapsedTime > 0.0) then
local smoothRate = osmath.clip(9.0 * elapsedTime, 0.15, 0.4)
self._smoothedAcceleration = osmath.blendIntoAccumulatorV(smoothRate, newAcceleration, self._smoothedAcceleration)
end
-- -- // Euler integrate (per frame) acceleration into velocity
local accel = self._smoothedAcceleration.mult( elapsedTime )
newVelocity = newVelocity.add( accel )
-- -- // enforce speed limit
newVelocity = newVelocity.truncateLength( self.maxSpeed ())
-- -- // update Speed
self.setSpeed(newVelocity.length());
-- -- // Euler integrate (per frame) velocity into position
self.setPosition(self.position().add(newVelocity.mult(elapsedTime)))
-- -- // regenerate local space (by default: align vehicle's forward axis with
-- -- // new velocity, but this behavior may be overridden by derived classes.)
self.regenerateLocalSpace(newVelocity, elapsedTime)
-- -- // maintain path curvature information
self.measurePathCurvature(elapsedTime)
-- -- // running average of recent positions
self._smoothedPosition = osmath.blendIntoAccumulatorV(elapsedTime * 0.06, self.position(), self._smoothedPosition)
end
-- -- // the default version: keep FORWARD parallel to velocity, change
-- -- // UP as little as possible.
self.regenerateLocalSpace = function( newVelocity, elapsedTime)
-- -- // adjust orthonormal basis vectors to be aligned with new velocity
if (self.speed() > 0.0) then self.regenerateOrthonormalBasisUF(newVelocity.div(self.speed())) end
end
-- // alternate version: keep FORWARD parallel to velocity, adjust UP
-- // according to a no-basis-in-reality "banking" behavior, something
-- // like what birds and airplanes do. (XXX experimental cwr 6-5-03)
self.regenerateLocalSpaceForBanking = function( newVelocity, elapsedTime )
-- // the length of this global-upward-pointing vector controls the vehicle's
-- // tendency to right itself as it is rolled over from turning acceleration
local globalUp = osvec.Vec3Set(0, 0.2, 0);
-- // acceleration points toward the center of local path curvature, the
-- // length determines how much the vehicle will roll while turning
local accelUp = self._smoothedAcceleration.mult( 0.05 );
-- // combined banking, sum of UP due to turning and global UP
local bankUp = accelUp.add( globalUp );
-- // blend bankUp into vehicle's UP basis vector
local smoothRate = elapsedTime * 3;
local tempUp = self.up();
tempUp = osmath.blendIntoAccumulatorV(smoothRate, bankUp, tempUp);
self.localspace.setUp(tempUp.normalize());
-- // annotationLine (position(), position() + (globalUp * 4), gWhite); -- // XXX
-- // annotationLine (position(), position() + (bankUp * 4), gOrange); -- // XXX
-- // annotationLine (position(), position() + (accelUp * 4), gRed); -- // XXX
-- // annotationLine (position(), position() + (up () * 1), gYellow); -- // XXX
-- // adjust orthonormal basis vectors to be aligned with new velocity
if (self.speed() > 0) then regenerateOrthonormalBasisUF (newVelocity.div(self.speed())) end
end
-- // adjust the steering force passed to applySteeringForce.
-- // allows a specific vehicle class to redefine this adjustment.
-- // default is to disallow backward-facing steering at low speed.
-- // xxx experimental 8-20-02
self.adjustRawSteeringForce = function( force, deltaTime )
local maxAdjustedSpeed = 0.2 * self.maxSpeed();
if ((self.speed() > maxAdjustedSpeed) or force.eq(osvec.Vec3_zero) ) then
return force
else
local range = self.speed() / maxAdjustedSpeed
-- // const float cosine = osmath.interpolate (pow (range, 6), 1.0f, -1.0f)
-- // const float cosine = osmath.interpolate (pow (range, 10), 1.0f, -1.0f)
-- // const float cosine = osmath.interpolate (pow (range, 20), 1.0f, -1.0f)
-- // const float cosine = osmath.interpolate (pow (range, 100), 1.0f, -1.0f)
-- // const float cosine = osmath.interpolate (pow (range, 50), 1.0f, -1.0f)
local cosine = osmath.interpolate(math.pow (range, 20), 1.0, -1.0)
return osvec.limitMaxDeviationAngle (force, cosine, self.forward())
end
end
-- // apply a given braking force (for a given dt) to our momentum.
-- // xxx experimental 9-6-02
self.applyBrakingForce = function( rate, deltaTime )
local rawBraking = self.speed () * rate
local clipBraking = self.maxForce()
if (rawBraking < self.maxForce ()) then clipBraking = rawBraking end
self.setSpeed (self.speed () - (clipBraking * deltaTime))
end
-- // predict position of this vehicle at some time in the future
-- // (assumes velocity remains constant)
self.predictFuturePosition = function( predictionTime )
return self.position().add( self.velocity().mult(predictionTime) )
end
-- // get instantaneous curvature (since last update)
self.curvature = function() return self._curvature end
-- // get/reset smoothedCurvature, smoothedAcceleration and smoothedPosition
self.smoothedCurvature = function() return self._smoothedCurvature end
self.resetSmoothedCurvature = function( value )
value = value or 0
self._lastForward.setV( osvec.Vec3_zero )
self._lastPosition.setV( osvec.Vec3_zero )
self._smoothedCurvature, self._curvature = value
return value
end
self.smoothedAcceleration = function() return self._smoothedAcceleration end
self.resetSmoothedAcceleration = function(value)
if(not value) then value = osvec.Vec3_zero end
self._smoothedAcceleration.setV(value)
return self._smoothedAcceleration
end
self.smoothedPosition = function() return self._smoothedPosition end
self.resetSmoothedPosition = function( value )
if(value == nil) then value = osvec.Vec3_zero end
self._smoothedPosition.setV(value)
return self._smoothedPosition
end
-- // give each vehicle a unique number
self.serialNumber = 0.0
-- // set a random "2D" heading: set local Up to global Y, then effectively
-- // rotate about it by a random angle (pick random forward, derive side).
self.randomizeHeadingOnXZPlane = function()
self.setUp(osvec.Vec3_up)
self.setForward(osvec.RandomUnitVectorOnXZPlane())
self.setSide(self.localRotateForwardToSide(self.forward()))
end
self.setHeading = function(heading)
self.setUp(Vec3.up)
self.setForward( osvec.Vec3Set( math.cos(heading), 0.0, math.sin(heading) ))
self.setSide(self.localRotateForwardToSide(self.forward()))
end
-- // measure path curvature (1/turning-radius), maintain smoothed version
self.measurePathCurvature = function( elapsedTime )
if (elapsedTime > 0) then
local dP = self._lastPosition.sub( self.position() )
local dF = (self._lastForward.sub( self.forward() )).div( dP.length() )
local lateral = dF.perpendicularComponent( self.forward() )
local sign = -1.0
if (lateral.dot(self.side()) < 0) then sign = 1.0 end
self._curvature = lateral.length() * sign
self._smoothedCurvature = osmath.blendIntoAccumulator(elapsedTime * 4.0, self._curvature, self._smoothedCurvature)
self._lastForward = self.forward()
self._lastPosition = self.position()
end
end
return self
end
return SimpleVehicle