Using villares PVector (python implementation) in py5

@villares has created a proposed new python implementation of PVector for use with py5. I’m not a fan of camel case in python (or ruby) so I changed the signature of PVector.fromAngle and also enclosed it as a module. But here’s my translation of morph.rb to morph.py making use of numpy along the way:-

import py5
from vector.pvector import PVector
import numpy as np

ALPHA = 45
OMEGA = 405
THETA = 9
state = False

def settings():
    py5.size(200, 200)

def setup():
    global circle, square, morph
    circle = []
    square = []
    morph = []
    angles = range(ALPHA, OMEGA, THETA)
    py5.frame_rate(15)
    for angle in angles:
        circle.append(PVector.from_angle(py5.radians(angle), 50))
        morph.append(PVector(0, 0))
    nvalues = -np.arange(-50, 51)
    pvalues = np.arange(-50, 51)
    for x in np.nditer(pvalues[::10]): # top
        square.append(PVector(x, -50))
    for y in np.nditer(pvalues[::10]): # right side
        square.append(PVector(50, y))
    for x in np.nditer(nvalues[::-10]): # bottom
        square.append(PVector(x, 50))
    for y in np.nditer(nvalues[::-10]): # left side
        square.append(PVector(-50, y))

def draw():
    global state
    py5.background(51)
    total_distance = 0
    for i in range(len(circle)):
        v1 = circle[i] if state else square[i]
        v2 = morph[i]
        v2 = v2.lerp(v1, 0.1)
        total_distance += v1.dist(v2)
    if (total_distance < 0.08):
        state = not(state)
    py5.translate(py5.width / 2.0, py5.height / 2.0)
    py5.no_fill()
    py5.stroke(255)
    py5.stroke_weight(4)
    py5.begin_shape()
    for vec in morph:
        py5.vertex(vec.x, vec.y)
    py5.end_shape(py5.CLOSE)
py5.run_sketch()

It took longer than expected with some of my usual grievances with python syntax bugging me again, including ternary operator and unexpected indexing of numpy arrays using nditer however numpy scores on performance ground, and gets round limitations of iterating regular python ranges (things I take for granted in ruby). One for @hx2a and @tabreturn to look at.

3 Likes

Thanks for sharing this. I never used np.nditer before but just read the documentation. Apparently I’ve been missing out.

It’s too bad you can’t use py5.vertices(morph) at the end of the draw() function to draw all of the vertices without that for loop. I do think py5.vertices([[v.x, v.y] for v in morph]) might work though.

Your example is interesting and helps inform my thinking.

1 Like

Turns out nditer actually can do what I wanted in a more elegant way (which also avoids crazy indexing):-

    nvalues = -np.arange(-50, 50, 10)
    pvalues = np.arange(-50, 50, 10)
    for x in np.nditer(pvalues): # top
        square.append(PVector(x, -50))
    for y in np.nditer(pvalues): # right side
        square.append(PVector(50, y))
    for x in np.nditer(nvalues): # bottom
        square.append(PVector(x, 50))
    for y in np.nditer(nvalues): # left side
        square.append(PVector(-50, y))

But unfortunately I cannot use py5.vertices as suggested, however that might be a consideration when creating a vector class (for my ruby processing Vec2D and Vec3D classes I have created to vertex methods for PShape and and custom renderer).

Interesting. In my opinion it would be better if you could use it that way, and I will certainly think about this when creating a vector class.

@hx2A If you create a Vec2D class (along lines of @villares PVector) and add a method:-

def to_array(self):
        return [self.x, self.y]

Then the following works just dandy:-

py5.vertices([v.to_array() for v in morph])

Which as you say avoids looping through morph…

If you just add to_array method to PVector then you get the following error

vertex() with x, y, and z coordinates can only be used with a renderer that supports 3D, such as P3D. Use a version without a z-coordinate instead.

Another strong argument for a separate Vec2D class, along with cross product error (returns vector instead of scalar) etc etc.

1 Like