mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-02 18:03:56 -04:00
added ProjectileInterval
This commit is contained in:
parent
0ff53d1ec1
commit
613dea6208
@ -9,5 +9,6 @@ from MopathInterval import *
|
||||
from ParticleInterval import *
|
||||
from SoundInterval import *
|
||||
from WaitInterval import *
|
||||
from ProjectileInterval import *
|
||||
from MetaInterval import *
|
||||
from IntervalManager import *
|
||||
|
216
direct/src/interval/ProjectileInterval.py
Executable file
216
direct/src/interval/ProjectileInterval.py
Executable file
@ -0,0 +1,216 @@
|
||||
"""ProjectileInterval module: contains the ProjectileInterval class"""
|
||||
|
||||
from DirectObject import *
|
||||
from PandaModules import *
|
||||
from Interval import Interval
|
||||
from PythonUtil import lerp
|
||||
import PythonUtil
|
||||
|
||||
class ProjectileInterval(Interval):
|
||||
"""ProjectileInterval class: moves a nodepath through the trajectory
|
||||
of a projectile under the influence of gravity"""
|
||||
|
||||
# create ProjectileInterval DirectNotify category
|
||||
notify = directNotify.newCategory('ProjectileInterval')
|
||||
|
||||
# serial num for unnamed intervals
|
||||
projectileIntervalNum = 1
|
||||
|
||||
# g ~ 9.8 m/s^2 ~ 32 ft/s^2
|
||||
gravity = 32.
|
||||
|
||||
# the projectile's velocity is constant in the X and Y directions.
|
||||
# the projectile's motion in the Z (up) direction is parabolic
|
||||
# due to the constant force of gravity, which acts in the -Z direction
|
||||
|
||||
def __init__(self, node, startPos = None,
|
||||
endPos = None, duration = None,
|
||||
startVel = None, endZ = None,
|
||||
gravityMult = None, name = None):
|
||||
"""
|
||||
You may specify several different sets of input parameters.
|
||||
(If startPos is not provided, it will be obtained from the node.)
|
||||
|
||||
# go from startPos to endPos in duration seconds
|
||||
startPos, endPos, duration
|
||||
# given a starting velocity, go for a specific time period
|
||||
startPos, startVel, duration
|
||||
# given a starting velocity, go until you hit a given Z plane (TODO)
|
||||
startPos, startVel, endZ
|
||||
|
||||
You may alter gravity by providing a multiplier in 'gravityMult'.
|
||||
'2.' will make gravity twice as strong, '.5' twice as weak.
|
||||
'-1.' will reverse gravity
|
||||
"""
|
||||
self.node = node
|
||||
|
||||
if name == None:
|
||||
name = '%s-%s' % (self.__class__.__name__,
|
||||
self.projectileIntervalNum)
|
||||
ProjectileInterval.projectileIntervalNum += 1
|
||||
|
||||
"""
|
||||
# attempt to add info about the caller
|
||||
file, line, func = PythonUtil.callerInfo()
|
||||
if file is not None:
|
||||
name += '-%s:%s:%s' % (file, line, func)
|
||||
"""
|
||||
|
||||
args = (startPos, endPos, duration,
|
||||
startVel, endZ, gravityMult)
|
||||
self.needToCalcTraj = 0
|
||||
if startPos is None:
|
||||
assert duration is not None
|
||||
# we can't calc the trajectory until we know our starting
|
||||
# position; delay until the interval is actually started
|
||||
self.trajectoryArgs = args
|
||||
self.needToCalcTraj = 1
|
||||
else:
|
||||
self.__calcTrajectory(*args)
|
||||
|
||||
Interval.__init__(self, name, duration)
|
||||
|
||||
def __calcTrajectory(self, startPos = None,
|
||||
endPos = None, duration = None,
|
||||
startVel = None, endZ = None,
|
||||
gravityMult = None):
|
||||
self.needToCalcTraj = 0
|
||||
if not startPos:
|
||||
startPos = self.node.getPos()
|
||||
|
||||
def doIndirections(*items):
|
||||
result = []
|
||||
for item in items:
|
||||
if callable(item):
|
||||
item = item()
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
startPos, endPos, startVel, endZ, gravityMult = \
|
||||
doIndirections(startPos, endPos, startVel, endZ, gravityMult)
|
||||
|
||||
# we're guaranteed to know the starting position at this point
|
||||
self.startPos = startPos
|
||||
|
||||
# gravity is applied in the -Z direction
|
||||
self.zAcc = -self.gravity
|
||||
if gravityMult:
|
||||
self.zAcc *= gravityMult
|
||||
|
||||
def calcStartVel(startPos, endPos, duration, zAccel):
|
||||
# p(t) = p_0 + t*v_0 + .5*a*t^2
|
||||
# v_0 = [p(t) - p_0 - .5*a*t^2] / t
|
||||
t = duration
|
||||
return Point3((endPos[0] - startPos[0]) / duration,
|
||||
(endPos[1] - startPos[1]) / duration,
|
||||
(endPos[2] - startPos[2] - (.5*zAccel*t*t)) / t)
|
||||
|
||||
def calcTimeOfImpactOnPlane(startHeight, endHeight, startVel, accel):
|
||||
return PythonUtil.solveQuadratic(accel * .5, startVel,
|
||||
startHeight-endHeight)
|
||||
|
||||
# which set of input parameters do we have?
|
||||
if (None not in (endPos, duration)):
|
||||
assert not startVel
|
||||
assert not endZ
|
||||
self.duration = duration
|
||||
self.endPos = endPos
|
||||
self.startVel = calcStartVel(self.startPos, self.endPos,
|
||||
self.duration, self.zAcc)
|
||||
elif (None not in (startVel, duration)):
|
||||
assert not endPos
|
||||
assert not endZ
|
||||
self.duration = duration
|
||||
self.startVel = startVel
|
||||
self.endPos = self.__calcPos(self.duration)
|
||||
elif (None not in (startVel, endZ)):
|
||||
assert not endPos
|
||||
assert not duration
|
||||
self.startVel = startVel
|
||||
time = calcTimeOfImpactOnPlane(self.startPos[2], endZ,
|
||||
self.startVel[2], self.zAcc)
|
||||
if not time:
|
||||
self.notify.error(
|
||||
'projectile never reaches plane Z=%s' % endZ)
|
||||
if type(time) == type([]):
|
||||
# projectile hits plane once going up, once going down
|
||||
# assume they want the one on the way down
|
||||
self.notify.debug('projectile hits plane twice at times: %s' %
|
||||
time)
|
||||
time = max(*time)
|
||||
else:
|
||||
self.notify.debug('projectile hits plane once at time: %s' %
|
||||
time)
|
||||
self.duration = time
|
||||
self.endPos = self.__calcPos(self.duration)
|
||||
else:
|
||||
self.notify.error('invalid set of inputs')
|
||||
|
||||
self.notify.debug('startPos: %s' % `self.startPos`)
|
||||
self.notify.debug('endPos: %s' % `self.endPos`)
|
||||
self.notify.debug('duration: %s' % self.duration)
|
||||
self.notify.debug('startVel: %s' % `self.startVel`)
|
||||
self.notify.debug('z-accel: %s' % self.zAcc)
|
||||
|
||||
def __initialize(self):
|
||||
if self.needToCalcTraj:
|
||||
self.__calcTrajectory(*self.trajectoryArgs)
|
||||
|
||||
def privInitialize(self, t):
|
||||
self.__initialize()
|
||||
Interval.privInitialize(self, t)
|
||||
|
||||
def privInstant(self):
|
||||
self.__initialize()
|
||||
Interval.privInstant(self)
|
||||
|
||||
def __calcPos(self, t):
|
||||
return Point3(
|
||||
self.startPos[0] + (self.startVel[0] * t),
|
||||
self.startPos[1] + (self.startVel[1] * t),
|
||||
(self.startPos[2] + (self.startVel[2] * t) +
|
||||
(.5 * self.zAcc * t * t)))
|
||||
|
||||
def privStep(self, t):
|
||||
self.node.setPos(self.__calcPos(t))
|
||||
Interval.privStep(self, t)
|
||||
|
||||
"""
|
||||
##################################################################
|
||||
TODO: support arbitrary sets of inputs
|
||||
##################################################################
|
||||
You must provide a few of the parameters, and the others will be
|
||||
computed. The input parameters in question are:
|
||||
duration, endZ, endPos, startVel, gravityMult
|
||||
|
||||
Valid sets of input parameters (AA),
|
||||
(trivially computed/default parameters) (BB),
|
||||
non-trivial computed parameters (CC):
|
||||
AA && BB => CC
|
||||
|
||||
# one parameter
|
||||
duration && (startVel, gravityMult) => endZ, endPos
|
||||
endZ && (startVel, gravityMult) => duration, endPos
|
||||
endPos && (endZ, gravityMult ) => duration, startVel
|
||||
|
||||
# two parameters
|
||||
duration, endZ && (endPos, gravityMult) => startVel
|
||||
duration, endPos && (endZ, gravityMult ) => startVel
|
||||
duration, startVel && (gravityMult ) => endZ, endPos
|
||||
duration, gravityMult && (startVel ) => endZ, endPos
|
||||
endZ, startVel && (gravityMult ) => duration, endPos
|
||||
endZ, gravityMult && (endPos, startVel ) => duration
|
||||
endPos, gravityMult && (endZ ) => duration, startVel
|
||||
|
||||
# three parameters
|
||||
duration, endZ, startVel && ( ) => endPos, gravityMult
|
||||
duration, endZ, gravityMult && (endPos) => startVel
|
||||
duration, endPos, gravityMult && (endZ ) => startVel
|
||||
duration, startVel, gravityMult && ( ) => endZ, endPos
|
||||
endZ, startVel, gravityMult && ( ) => duration, endPos
|
||||
|
||||
# four parameters
|
||||
duration, endZ, startVel, gravityMult && () => endPos
|
||||
##################################################################
|
||||
##################################################################
|
||||
"""
|
@ -562,3 +562,29 @@ def formatElapsedSeconds(seconds):
|
||||
return "%s%d:%02d:%02d" % (sign, hours, minutes, seconds)
|
||||
else:
|
||||
return "%s%d:%02d" % (sign, minutes, seconds)
|
||||
|
||||
def solveQuadratic(a, b, c):
|
||||
# quadratic equation: ax^2 + bx + c = 0
|
||||
# quadratic formula: x = [-b +/- sqrt(b^2 - 4ac)] / 2a
|
||||
# returns None, root, or [root1, root2]
|
||||
|
||||
# a cannot be zero.
|
||||
if a == 0.:
|
||||
return None
|
||||
|
||||
# calculate the determinant (b^2 - 4ac)
|
||||
D = (b * b) - (4. * a * c)
|
||||
|
||||
if D < 0:
|
||||
# there are no solutions (sqrt(negative number) is undefined)
|
||||
return None
|
||||
elif D == 0:
|
||||
# only one root
|
||||
return (-b) / (2. * a)
|
||||
else:
|
||||
# OK, there are two roots
|
||||
sqrtD = math.sqrt(D)
|
||||
twoA = 2. * a
|
||||
root1 = ((-b) - sqrtD) / twoA
|
||||
root2 = ((-b) + sqrtD) / twoA
|
||||
return [root1, root2]
|
||||
|
Loading…
x
Reference in New Issue
Block a user