Snap to ground orientation (vector?)

So this has been giving me a headache for the past few hours (I swear I’ve tried everything!) I’m trying to make a similar effect to if you were on a roller coaster and it did a barrel roll or a loop, obviously in real life they work off of centrifugal force, I’d like to snap my object so it could sit still upside down.

I thought vectors were my solution so I used a solution like this:


track = ray.hitObject
    pos = ray.hitPosition
    vect = own.getVectTo(track)
    own.alignAxisToVect(vect[2])

I also tried:


track = ray.hitObject
    pos = ray.hitPosition
    vect = own.getVectTo(pos)
    own.alignAxisToVect(vect[2])

trying to get the vector to the object aswell as the object point, this was of course using a ray that’s shot under the object, but for some reason it was just flipping the object upside down?

Is there an easier solution that I’m missing? I’m very python savvy as many of you know, so you don’t have to give me a crazy in depth answer, just tell me what I need to do and I can figure out the rest.

Thanks a ton guys :slight_smile:

Your vector is pointing down, so your object will align with the vector- and point down. It should work fine if you invert the vector on the second script segment. (the first one aligns toward the center of the ground object, rather than toward the ground where the ray hits)


vect=[vect[0]*-1, vect[1]*-1, vect[2]*-1]

I understand where you’re going with this, but it still isn’t working. It’s working kinda, but not the way I want. Look at the blend file and please try and give some assistance :slight_smile: I’ve tried using both the pos and track variables with no success. :frowning:

Attachments

game.blend (454 KB)

I’m not sure, exacly, but here are a couple of thoughts…

First:
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.

Second:
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. :wink:

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:


#!BPY

"""
Name: 'Path 2 IPO'
Blender: 248
Group: 'Object'
Tooltip: 'Bezier curve to IPO'
"""

# By haramanai, FunkyWurm, Sanne
# http://blenderartists.org/forum/showthread.php?t=83942&highlight=path+to+ipo

# 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
#case.
# - 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

import Blender
from Blender import *

def GetObjects():
    """ 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")
        return
    
    # 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")
        return
    
    # 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
    
    # 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]
    if speedipolist:
        speedipo = speedipolist[0]
        speedcu = speedipo[Ipo.CU_SPEED]
        if speedcu:
          lastbeztr = speedcu.bezierPoints[-1]
          end = int(lastbeztr.pt[0])
          print 'Using endframe %d of speed ipo "%s".' % (end, speediponame)
        else:
          end = Curve.data.pathlen
          print 'No speed curve in speed ipo "%s" found, using pathlen %d of curve.' % (speediponame, end)
    else:
        end = Curve.data.pathlen
        print 'No speed ipo "%s" found, using pathlen %d of curve.' % (speediponame, end)
    return 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
    try:
        ipo = Ipo.Get("IpoFrom"+Curve.data.name)
    except:
        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.append(Constraint.Type.FOLLOWPATH)
    empty.constraints[0][Constraint.Settings.TARGET] = Curve
    empty.constraints[0][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):
        Blender.Set("curframe", i)
        Mesh.setMatrix(empty.matrix)
        Mesh.insertIpoKey(Object.IpoKeyTypes.LOCROT)
        if i == end:
            hasendkey = True
        
    if not hasendkey:
        Blender.Set("curframe", end)
        Mesh.insertIpoKey(Object.IpoKeyTypes.LOCROT)
        
    Blender.Set("curframe", 1) # Change current frame to 1 again to be tidy
    
    # Clear up the empty. Nice and tidy ;-)
    #Scene.GetCurrent().unlink(empty)
    Scene.GetCurrent().objects.unlink(empty)
    
    return ipo


def main():
    """ The main function """
    try:
        Curve, Mesh = GetObjects()
    except:
        retval = Blender.Draw.PupMenu("Script aborting")
        return
    
    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):
        return
    
    bakedIpo = BakeIpoCurves(Curve, Mesh, PREF_STEP.val, PREF_SPEEDIPO.val)


# ------------------------
if __name__ == "__main__":
    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. :slight_smile:

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.

You should use the hitNormal attribute of the ray sensor to return the normal of the face the ray intercepts with. Aligning the z axis to this will solve the rotation, to make the object ‘stick’ to the track use the hitPosition atrribute, all up, something like this:

# align the object to the track
own.alignAxisToVect(ray.hitNormal, 2, 1)

# position the object on the track
hit_pos = ray.hitPosition
base = own.getAxisVect([0.0,0.0, -0.8]) # -.8 was the approximate base of the object

pos = [
    hit_pos[0]-base[0],
    hit_pos[1]-base[1],
    hit_pos[2]-base[2]]
own.worldPosition = pos

Andrew thank you sooo much :slight_smile: if I’m a python guru you’d definitely the god of blender python :slight_smile: I was unaware that hitNormal existed! It works perfectly, this will be extremely useful. Thanks again man. +karma

Actually I have another problem now :o it appears that when I look at it through a camera I’m seeing that it’s really sketchy (or jumpy) when aligning the vectors. The only solution I’ve found is slow parenting it, which is a bad method for the styled game I’m putting it in. Is there a way to align it smoothly? Or does it depend on how high poly the track it?

Here’s the blend so you can see for yourself… also have any idea why dRot does nothing but move it left and right?

Attachments

game.blend (470 KB)

Firstly you need to change the rotation from the local y axis to the local z axis.

To make it less jumpy, remove the slow parent and change the third argument of alignAxisToVect to something like 0.1. The third argument controls how quickly it takes to align the object, 1 being instantly.

Hmm may I ask why that’s the local z now? Normally with objects you rotate on the local y axis, because the Z axis is the depth?

From what I have read, and my experience with ogre, is that a lot of programs will have the z axis going into the screen, Blender on the other hand has the y axis going into the screen. As I understand you have recently been using Unity? This is most likely the source of confusion.

http://www.rab3d.com/rab3d/tutorial/cartesian.png
The axis that Blender uses.

Ah my bad, yeah just confusion. Unity and Maya both use Z as depth (that’s the proper way though?) Because X and Y are in 2d games indicating up and side, and Z is usually the depth… wonder why blender’s different?