I’m not sure, exacly, but here are a couple of thoughts…
I think that when you send a ray cast to an object that it aims the ray directly at the object centre. The hit point will be the point on the object that lies at the closest point of the object on that line to the object centre. For an object with an unusual shape such as a track this might not work too well. I think it works better on closed objects such as balls, cubes, models etc.
Aligning an axis to the vector that you find will align only that axis. ie. The axis that is following the direction of travel is undefined so even if the “down” axis of your carriage were to correctly align to the track, the other axes would be free to spin about that axis. This would give the effect that the carriage is attached to the track on a bearing, spinning in the plane of the track.
Sorry if my explanations are unclear.
I was messing about wih trying to make a physics object go down a tunnel. Maybe my technique could be modified to suit your purposes…
Note: Blender 2.49 only.
oops, the .blend with the full implementation is too large to upload here. I’ll provide the tests so you can see the principle of a physics object following a second object that tracks a path…
Controls_FollowPath.blend (165 KB) demonstrates the pinciple of following a path using ipo curves.
Controls_SetPathFollower.blend (205 KB) demonstrates setting the path follower based on the tracking object position.
Controls_RelativeForce.blend (221 KB) is an attempt to bring the two aspects together, applying a force to push the physics object towards the path follower whilst at the same time moving the path follower to the closest point to the physics object on the curve.
All the above is very much in the pre-alpha tech demo phase and should probably be extensively modified before usage in a game.
The curve was tanslated into ipos using the following script:
Name: 'Path 2 IPO'
Tooltip: 'Bezier curve to IPO'
# By haramanai, FunkyWurm, Sanne
# Changes by Sanne, 29. Nov 2008:
# - put it in "Object" menu for easy access from the 3D menu header, acces via
#Object-> Script -> Path 2 IPO.
# - added a GUI with a Step value for not recording keys every frame. Value 10 for
#example means: record every 10th frame. First and last frames get recorded in any
# - also in GUI, "Speed IPO Name". If the path has a speed ipo, set the name here. The
#script then uses the speed ipo for path length, otherwise it uses the PathLen setting in
#curve edit buttons.
# - made IPO clearing work better
# - some small general cleanups
from Blender import *
""" Get selected objects
Make sure that a curve and a mesh have been selected
Return the curve and mesh objects
# Initialise the variables
Curve = None
Mesh = None
ObjList = Blender.Object.GetSelected()
# Abort if too many objects are selected
if len(ObjList) > 2:
retval = Blender.Draw.PupMenu("Select 1 curve and 1 object only")
# Assign objects to their variables
for object in ObjList:
if object.type == "Curve":
Curve = object
elif object.type == "Mesh":
Mesh = object
elif object.type == "Empty":
Mesh = object
# Abort if required objects are not selected
if Curve == None or Mesh == None:
retval = Blender.Draw.PupMenu("You must select 1 curve and 1 object")
# Abort if curve not set to curve path
bitMask = 0x08 #the flag for curve path in Curve.data
if not Curve.data.getFlag() & bitMask:
retval = Blender.Draw.PupMenu("Curve must be set to a curve path")
# Return objects if all is well
return Curve, Mesh
def getEndFrame(Curve, speediponame):
""" Get the end frame of the path animation
If the curve has a speed IPO, get the speed IPO's last frame
number (truncted to int), else use the curve's PathLen value.
Appearantly Curve.getIpo() doesn't return a present speed ipo,
so we need to pass the name of the speed ipo in order to access it.
speedipolist = [ipo for ipo in Ipo.Get() if ipo.name == speediponame]
speedipo = speedipolist
speedcu = speedipo[Ipo.CU_SPEED]
lastbeztr = speedcu.bezierPoints[-1]
end = int(lastbeztr.pt)
print 'Using endframe %d of speed ipo "%s".' % (end, speediponame)
end = Curve.data.pathlen
print 'No speed curve in speed ipo "%s" found, using pathlen %d of curve.' % (speediponame, end)
end = Curve.data.pathlen
print 'No speed ipo "%s" found, using pathlen %d of curve.' % (speediponame, end)
def BakeIpoCurves(Curve, Mesh, step, speediponame):
""" Bake path animation to IPO curves
Thanks to haramanai for the concept of this function.
It seems to be necessary to copy the LocRot matrix from
one object to another in order to add the Ipo keys to a
second object. In this instance, I created an empty.
# Check for existing Ipo block that could be used
# in order to avoid unnecessary duplication
ipo = Ipo.Get("IpoFrom"+Curve.data.name)
ipo = Ipo.New("Object","IpoFrom"+Curve.data.name)
# Delete IPO curves if present (Mesh.clearIpo() just unlinks,
# gets relinked later, so doesn't delete anything)
if Mesh.ipo and Mesh.ipo.name == ipo.name:
cc = dict(ipo.curveConsts.items())
for icu in ipo.curves:
ipo[cc["OB_"+icu.name.upper()]] = None
if not Mesh.ipo:
Mesh.ipo = ipo
# Create an empty and make it follow the curve path
empty = None
empty = Object.New('Empty','tempEmpty')
empty = Scene.GetCurrent().objects.new('Empty')
empty.constraints[Constraint.Settings.TARGET] = Curve
empty.constraints[Constraint.Settings.FOLLOW] = True
# Animate the empty along the curve path, copying loc/rot at
# each frame to the object and inserting Ipo keys for the object
start = 1
end = getEndFrame(Curve, speediponame)
hasendkey = False
for i in xrange(start, end+1, step):
if i == end:
hasendkey = True
if not hasendkey:
Blender.Set("curframe", 1) # Change current frame to 1 again to be tidy
# Clear up the empty. Nice and tidy ;-)
""" The main function """
Curve, Mesh = GetObjects()
retval = Blender.Draw.PupMenu("Script aborting")
PREF_STEP = Blender.Draw.Create(1)
PREF_SPEEDIPO = Blender.Draw.Create("speed")
block = 
# inputs: title, button, min/max values, tooltip
block.append(("Step ", PREF_STEP, 1, 10000, "Insert key step value"))
block.append(("Speed IPO name: ", PREF_SPEEDIPO, 0, 21, "Name of the path's speed IPO, if present"))
if not Blender.Draw.PupBlock("Path 2 IPO", block):
bakedIpo = BakeIpoCurves(Curve, Mesh, PREF_STEP.val, PREF_SPEEDIPO.val)
if __name__ == "__main__":
Edit: Sorry for the long post. The information may be more for interest than use, I’m not sure if it would be relevant, and rewriting for 2.5 would be necessary. Hope it provides an alternative perspective though.
Edit 2: (Last one ;)) My thinking with the path follower is that you could make a path follower that follows your track and stays at the closest point to your carriage. The carriage could then use the path follower as a “track to” reference rather than directly trying to find the correct tracking point of a complex (track) shape.