PVectors in Class Objects

I’m hoping someone can explain this to me, as I’m pretty new to this stuff. Here’s a program that shows the problem I’m having:

targ_pos = PVector(100, 300, 0)
mis_pos = PVector(400, 600, 0)
l_o_s = PVector()

class Foo:
def init(self, targ_pos, mis_pos):
self.targ_pos = targ_pos
self.mis_pos = mis_pos
self.l_o_s = PVector()
def sub_vec(self, target_pos):
# temp_vec = mis_pos.copy() # uncomment to clear error
# self.l_o_s = temp_vec.sub(self.targ_pos) # uncomment to clear error
self.l_o_s = self.mis_pos.sub(self.targ_pos) # comment to clear error
return self.l_o_s

foo = Foo(targ_pos, mis_pos)
i = 1

def draw():
global i
vec = foo.sub_vec(targ_pos)
print('targ_pos ', targ_pos)
print('mis_pos ', mis_pos)
print('vec ', vec)
if i == 2:
noLoop()
i += 1

The output looks like this:
('targ_pos ', [ 100.0, 300.0, 0.0 ])
('mis_pos ', [ 300.0, 300.0, 0.0 ])
('vec ', [ 300.0, 300.0, 0.0 ])
('targ_pos ', [ 100.0, 300.0, 0.0 ])
('mis_pos ', [ 200.0, 0.0, 0.0 ])
('vec ', [ 200.0, 0.0, 0.0 ])

The PVector ‘mis_pos’ is changing, though there is no instruction that would cause it to do so. If I make a copy of that vector and use the copy for the subtraction, it works as intended.

Any help appreciated, thanks, Mike

My apologies, I just learned that I should have formatted the code to make it easier to run.

targ_pos = PVector(100, 300, 0)
mis_pos = PVector(400, 600, 0)
l_o_s = PVector()

class Foo:
    def __init__(self, targ_pos, mis_pos):
        self.targ_pos = targ_pos
        self.mis_pos = mis_pos
        self.l_o_s = PVector()
    def sub_vec(self, targ_pos):
        # temp_vec = mis_pos.copy() # uncomment to clear error
        # self.l_o_s = temp_vec.sub(self.targ_pos) # uncomment to clear error
        self.l_o_s = self.mis_pos.sub(self.targ_pos) # comment to clear error
        return self.l_o_s

foo = Foo(targ_pos, mis_pos)
i = 1    

def draw():
    global i
    vec = foo.sub_vec(targ_pos)
    print('targ_pos ', targ_pos)
    print('mis_pos ', mis_pos)
    print('vec ', vec)
    if i == 2:
        noLoop()
    i += 1

Mike

2 Likes

When we have a variable assignment, for example this 1: targ_pos = PVector(100, 300, 0)

What is actually stored in the variable is the memory address (a.K.a. reference or pointer) of an object.

If we have more than 1 variable, property or container assigned to the same reference value from another, they all become alias to the same object.

In order to avoid aliased variables, make sure to clone an object before assigning it.

For PVector, its methods copy() or get() will create a clone of it:

For convenience, do it right there inside the constructor:

class Foo:
    def __init__(self, targ_pos, mis_pos):
        self.targ_pos = targ_pos.get()
        self.mis_pos = mis_pos.get()
        self.l_o_s = PVector()
2 Likes

Thanks for the reply, I thought it might be something to that effect. If you look at the code I posted, you’ll see ‘temp_vec’ making a copy, which, when uncommented,.made it work fine. I’ll try the method you recommended and see how it goes.
As I understand your reply, when I referenced a subtraction from the original vector, the result of the subtraction was stored in the original vector. I’ll have to chew on that for a bit, but I think I see what you mean.

1 Like

Method sub() mutates the object invoking it.

As an alternative, use its static version PVector.sub() w/ 2 or 3 parameters.

3 Likes

Thanks, that did it! As the ‘sub_vec’ method is called once per frame, that’s where I put the static method, rather than ‘init’. Here’s the code that works as intended:

class Foo:
    def __init__(self, targ_pos, mis_pos):
        self.targ_pos = targ_pos
        self.mis_pos = mis_pos
        self.l_o_s = PVector()
    def sub_vec(self, target_pos):
        # temp_vec = mis_pos.copy() # uncomment to clear error
        # self.l_o_s = temp_vec.sub(self.targ_pos) # uncomment to clear error
        # self.l_o_s = self.mis_pos.sub(self.targ_pos) # commented to try static PVector
        self.l_o_s = PVector.sub(self.mis_pos,(self.targ_pos)) 
        return self.l_o_s

foo = Foo(targ_pos, mis_pos)

def draw():
    global i
    vec = foo.sub_vec(targ_pos)
    print('targ_pos ', targ_pos)
    print('mis_pos ', mis_pos)
    print('vec ', vec)

Your reply led me to Prof Schiffman’s excellent tutorial, which helped me a lot.

I believe you didn’t grasp the reason I’ve advised you about cloning the passed PVector arguments inside __init__().

The idea is once we’ve got a clone of each passed object argument we don’t have to worry about inadvertently modifying them anymore.

Take a look at this sketch example based on yours:

class Foo:
    def __init__(self, targ_pos, mis_pos):
        self.targ_pos = targ_pos.get()
        self.mis_pos  = mis_pos.copy()


    def sub_vec(self):
        self.mis_pos.sub(self.targ_pos)
        return self


    def __str__(self, INFO = 'Foo { targ_pos: %s, mis_pos: %s }'):
        return INFO % (self.targ_pos, self.mis_pos)


targ_pos = PVector(100, 300, 0)
mis_pos = PVector(400, 600, 0)

foo = Foo(targ_pos, mis_pos)

def setup(FPS = .5): frameRate(FPS);

def draw(LOOPS = 4, INFO = 'Ori { targ_pos: %s, mis_pos: %s }\n'):
    background(int(random(PImage.ALPHA_MASK)))

    print foo.sub_vec()
    print INFO % (targ_pos, mis_pos)

    frameCount is LOOPS and noLoop()

And this is its log output:


Foo { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 300.0, 300.0, 0.0 ] }
Ori { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 400.0, 600.0, 0.0 ] }

Foo { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 200.0, 0.0, 0.0 ] }
Ori { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 400.0, 600.0, 0.0 ] }

Foo { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 100.0, -300.0, 0.0 ] }
Ori { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 400.0, 600.0, 0.0 ] }

Foo { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 0.0, -600.0, 0.0 ] }
Ori { targ_pos: [ 100.0, 300.0, 0.0 ], mis_pos: [ 400.0, 600.0, 0.0 ] }

Notice that even though property mis_pos is being mutated inside method sub_vec(): self.mis_pos.sub(self.targ_pos)

The original global mis_pos keeps its original initial value of: [ 400.0, 600.0, 0.0 ]

In short, both properties targ_pos & mis_pos can be mutated w/o modifying the original passed parameters, b/c the former are clones of the latter, via methods get() or copy():

def __init__(self, targ_pos, mis_pos):
    self.targ_pos = targ_pos.get()
    self.mis_pos  = mis_pos.copy()

So, as I understand it, the first two statements in ‘init’ do essentially the same thing, make a clone of the vectors involved. I’m unable to find a difference between ‘get’ and ‘copy’.

I assumed that the subtraction here would decrement ‘mis_pos’, as it did before. Do the clones become immutable?
BTW, your example showed me some unfamiliar programming features which I’ve figured out and no doubt will find useful in the future. Thanks for that.
I appreciate your patience.
Mike

Methods get() & copy() from class PVector do the same clone action when invoked w/ no argument.

Nope! Clones are different objects initialized w/ the same content as the original object.

B/c a cloned object is separated from the original, we can mutate the content of either independently w/o affecting the content of the other.

Take notice that each object has its own memory address (a.K.a. pointer or reference), even if they happen to belong to the same class w/ exact same content.

The example bellow explains alias & clone w/ more detail:

a = PVector.random2D(this) # new PVector object w/ random content
b = a # variable b is an alias of a, b/c they both refer to the same object
c = a.get() # variable c is a clone of a w/ exact same initial content

print a, b, c

print a == b, a == c, b == c # True True True
print a is b, a is c, b is c # True False False

b.div(2) # mutating b also mutates its alias a w/o affecting c
print a, b, c

c.sub(b) # mutating c won't affect either a or b
print a, b, c

exit()

Notice operator == checks content equality while is checks identity (memory address).