Check here an object is in relation to the player

Hi all

I need a way of finding out if an object is to the left or right of another object- (for example the player), and if they do not match apply a simple turn value so they line up .

Can anyone give any pointers on how this may be done?

Cheers

Paul

you have two vectors:

A = the vector from the player to the object
B = the vector from the player ahead (depends on the local axis the player is looking along).

Now you calculate the dot product of the two vectors (A0B0+A1B1…)

  • If the dot product is positive then the object is ahead of the player
    0 If the dot product is zero then the object is beside the player
  • If the dot product is negative then the object is behind the player

Now you know how to check if the object is ahead/beside/behind the player.

To find the left/middle/right relation, you just use another axis (the axis to left or right)

The same belongs to above/same high/below relation

I hope it helps. it is a while ago that I used this.

Cool, thanks for the explanation Monster. I have been looking at the getVectTo function but I have never done vector maths so this should be a good learning experience (or a very foolhardy venture)!

The Wiki pages contain the 2D visualization only.

Here is the 3D visualization:


You project one vector on the other.
The length of the result is the dot product.
Now you can check for -/zero/+.

The advantage:

  • it is quite simple to calculate the dot product (no need to calculate angles or roots).

Is this blend correct?

Attachments

dot_product.blend (335 KB)

It is a bit more complicated. Check this file.

Attachments

dot_product_revised.blend (281 KB)

Thanks for the revision, out of interest, what extra is your script doing in comparison to mine?

And from experimentation, I am trying to set an extra condition where the cube stops rotating when it faces the node object - is it just a matter of when Y_vec = node_vec?

I never thought maths would be fun:evilgrin:

I changed your script to be a module. Modules are much better for several reasons. The changes to convert a script to a module are quite minimal.

You approach was to have one “control” object (the cube) that searches or the two objects to check. I kept that approach as it does not really impact the workflow and has nothing to do with the requested processing.

I introduced the function side(a,b) to check two objects against each other.


a.getVectTo(br)
# is the same as 
b.worldPosition - a.worldPosition

because the positions are Vectors already. The result is A in the above diagram.

The position of the control object does not matter in out situation. It can be somewhere.


dotproduct = y_vec[2] * node_vec[2]

is not the dot product. You need to add the multiplications of ALL terms not just the Z axis.
mathutils.Vector already provides you with the dot() function doing that for you.

Now the more complicated things:
We need a vector pointing left or right of the “master” object. It must point exactly 90°.
In local space this is the vector (0,1,0)

Unfortunately as (0,1,0) there is no use to it. We have to transform it to world space. That means the orientation and Position of the master object need to be taken into account.

This is done by creating the masters transformation matrix (Local space to world space).
Now we can transform the local side vector into world space = B in the above diagram.

Now we can simply create the dot product of the vector between the two objects and the side vector (all in world space).

The value of the dot product does not really matter just the sign. This is a special situation in math. It makes the formulas really small and efficient. If you care the values goes really complicated. Just look at the Wikipedia ;).

As long as I did not something completely stupid this should be the solution.

Stopping
The naive approach would be to stop when the dot product is 0.0. It is naive as it is unlikely to ever catch this value. It is like buying a piece of beef with EXACTLY 500.0 g (not just exact g also mg and µg).

So you need a tolerance which is usually exactly the amount for the last step. I would just try it out.
(Remember the dot product is not angle. Which means if you do a 1° step when the dot product is near zero it has a different value than when not near zero.)

So you can change the check in this way:


    limit = own.get("limit",0.1)
    if abs(leftRight) <= limit:
        own["leftRight"] = "middle"
        # if you want "auto correction":
        master.alignAxisToVect(other.worldPosition-master.worldPosition, 1)
    elif leftRight > 0.0:
        master["leftRight"] = "right"
    elif leftRight < 0.0:
        master["leftRight"] = "left"

Remark: If you do auto-correction as mentioned above it is a good idea to check behind/beside/ahead. Otherwise the auto-correction would take place when the target is behind the master producing an 180° turn.

I hope it helps

A bit more generalized code:


import bge
import mathutils

X_AXIS = 0
Y_AXIS = 1
Z_AXIS = 2

def printSide(cont):
    own = cont.owner
    scene = bge.logic.getCurrentScene()
    gameObjects = scene.objects
    
    master = gameObjects["Node"]
    other = gameObjects["Y_vector"]
    
    leftRight = side(master, other, X_AXIS)
    
    limit = master.get("limit", 0.1)

    if abs(leftRight) <= limit:
        own["leftRight"] = "middle"
        behindAhead = side(master, other, Y_AXIS)
        if behindAhead > 0.0:
            master.alignAxisToVect(other.worldPosition-master.worldPosition, 1)
     
    elif leftRight > 0.0:
        master["leftRight"] = "right"
    elif leftRight < 0.0:
        master["leftRight"] = "left"

def side(master, other, axis):
    
    toOther = master.worldPosition - other.worldPosition
    
    rotation = master.worldOrientation
    rotation = rotation.to_4x4()
    
    sideVector = mathutils.Vector([0,0,0])
    sideVector[axis] = 1.0
    toSideMatrix = mathutils.Matrix.Translation(sideVector)
    toMasterMatrix = mathutils.Matrix.Translation(master.worldPosition)
    transformation = toMasterMatrix * rotation * toSideMatrix
    
    toSide = master.worldPosition - transformation.to_translation() 
    return toOther.dot(toSide)


You cold simply do getVectTo(other), the third returned value is the normalized vector in local coordinates - so just checking if the sideways axis in that vector is greater or lesser then 0 give the answer.

As the vector is normalized You can just multiply that axis with a factor of choice and set it as rotation speed - it will automatically slow down and stop getting close to target (unless the factor is to big - then it will start to oscillate - less then 60 with some margin is OK (< logic tick rate).

If the object may by facing away from the target You need a few extra checks for smooth operation.

Thanks again Monster and LaH!

When I did this:


dotproduct = y_vec[2] * node_vec[2]

I assumed the returned values were [0] for the distance, [1] for world and [2] for local, I never realised it was the z values:o!

How exactly do you extract only the local values then? If I do something like


print(node_vec[2])

I (assumed) I get a tuple of local vectors.

Monster: thanks for explaining that, you really should be a tutor in a college or school somewhere. You have probably guessed this is an attempt to replace the tracking actuator / alignAxisToVect, as I need a way to orient an object by drot only (so I can get the object to bank and turn based on those values.

I don’t want to resurrect an oldish thread, but I am almost finished on this one!

First off, for anyone who reads this I found the answer to my question in my last post: to access the (nested?) tuple vector I think this works:


#blah = (y_vec[2][0]) # get list/ dict 2 entry 1

Now apologies to Monster (sorry!:(), I had some trouble modifying your code for 3d tracking and I have fallen back onto a version of my first script. I have managed to get tracking in one axis working (i.e I can track in x, y or z reliably) but not in two at the same time. When the object the tracking script controls gets close to the target (or the target moves erratically) the script ceases to work. I can only assume they interfere in some way.

In the blend the node target is controlled by cursor keys (UP/DOWN etc), and each axis has its own script (so I could easily disable one or the other for testing).

Thanks for helping!

Paul

Attachments

3DNav.blend (355 KB)

This script (module) do it for me:


import bge

turnSpeed = 0.03

def rot(cont):
    own = cont.owner
    
    sce = bge.logic.getCurrentScene()
    
    objList = sce.objects
    
    node = objList["Node"]
    
    (dist, glob, loc) = own.getVectTo(node)
    
    if loc.y &gt; 0:
        x = loc.z * turnSpeed
        z = loc.x * -turnSpeed
    else:
        if loc.z &gt; 1:
            x = turnSpeed / 3
        else:
            x = -turnSpeed / 3
        if loc.x &gt; 1:
            z = -turnSpeed / 3
        else:
            z = turnSpeed / 3
    
    own.applyRotation([x, 0.0, z], True)

update: fixed blend 3DNav.blend (462 KB)

LaH,

If we ever meet, (Monster too) you shall have free beer on me!

Cheers!:smiley:

Paul

I think the local vector method, that LaH suggest is better. You do not need to perform the transformations by yourself. (fyi: the above code is 3D already :P. )

Taunton, Somerset?
Not that unlikely.
My nearest visit was Southhampton. This was a holiday trip with my father a long time ago. We took the first “Wartburg” (an east german car) on the ferry from Calais to Dover. How we know? They had to measure it first ;).
My other (business-) trips just reached Watford and Farnborough. But this was with my previous job.

Don’t worry no holiday plans for UK yet :smiley: