Last active
September 15, 2022 09:42
-
-
Save villares/5c476cbc44c1153fed159eae36fc016b to your computer and use it in GitHub Desktop.
A Python PVector replacement - made for pyp5js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# REFERENCE: https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js | |
# I had a test suit from JDF (Processing.Py's mantainer) here: | |
# https://gist.github.com/villares/e639a34eef756beba2f79a55203cd51e | |
import math | |
from numbers import Number | |
# TWO_PI = math.pi * 2 # add this if you are not using pyp5js | |
class PVector: | |
def __init__(self, x=0, y=0, z=0): | |
self.x = x | |
self.y = y | |
self.z = z | |
self.add = self.__instance_add__ | |
self.sub = self.__instance_sub__ | |
self.mult = self.__instance_mult__ | |
self.div = self.__instance_div__ | |
self.cross = self.__instance_cross__ | |
self.dist = self.__instance_dist__ | |
self.dot = self.__instance_dot__ | |
self.lerp = self.__instance_lerp__ | |
def mag(self): | |
return math.sqrt(self.magSq()) | |
def magSq(self): | |
return self.x * self.x + self.y * self.y + self.z * self.z | |
def setMag(self, mag): | |
return self.normalize().mult(n) | |
def normalize(self): | |
mag = self.mag() | |
if mag != 0: | |
self.mult(1 / mag) | |
return self | |
def limit(self, max_mag): | |
mSq = self.magSq() | |
if mSq > max_mag * max_mag: | |
self.div(math.sqrt(mSq)).mult(max_mag) | |
return self | |
def heading(self): | |
return math.atan2(self.y, self.x) | |
def rotate(self, angle): | |
new_h = self.heading() + angle | |
mag = self.mag() | |
self.x = math.cos(new_h) * mag; | |
self.y = math.sin(new_h) * mag; | |
return self | |
def __instance_add__(self, *args): | |
if len(args) == 1: | |
return PVector.add(self, args[0], self) | |
else: | |
return PVector.add(self, PVector(*args), self) | |
def __instance_sub__(self, *args): | |
if len(args) == 1: | |
return PVector.sub(self, args[0], self) | |
else: | |
return PVector.sub(self, PVector(*args), self) | |
def __instance_mult__(self, o): | |
return PVector.mult(self, o, self) | |
def __instance_div__(self, f): | |
return PVector.div(self, f, self) | |
def __instance_cross__(self, o): | |
return PVector.cross(self, o, self) | |
def __instance_dist__(self, o): | |
return PVector.dist(self, o) | |
def __instance_dot__(self, *args): | |
if len(args) == 1: | |
v = args[0] | |
else: | |
v = args | |
return self.x * v[0] + self.y * v[1] + self.z * v[2] | |
def __instance_lerp__(self, *args): | |
if len(args) == 2: | |
return PVector.lerp(self, args[0], args[1], self) | |
else: | |
vx, vy, vz, f = args | |
return PVector.lerp(self, PVector(vx, vy, vz), f, self) | |
def get(self): | |
return PVector(self.x, self.y, self.z) | |
def copy(self): | |
return PVector(self.x, self.y, self.z) | |
def __getitem__(self, k): | |
return getattr(self, ('x', 'y', 'z')[k]) | |
def __setitem__(self, k, v): | |
setattr(self, ('x', 'y', 'z')[k], v) | |
def __copy__(self): | |
return PVector(self.x, self.y, self.z) | |
def __deepcopy__(self, memo): | |
return PVector(self.x, self.y, self.z) | |
def __repr__(self): # PROVISÓRIO | |
return f'PVector({self.x}, {self.y}, {self.z})' | |
def set(self, *args): | |
if len(args) == 3: | |
self.x, self.y, self.z = args | |
elif len(args) == 2: | |
self.x, self.y = args | |
elif len(args) == 1: | |
self.x, self.y, self.z = *args | |
@classmethod | |
def add(cls, a, b, dest=None): | |
if dest is None: | |
return PVector(a.x + b[0], a.y + b[1], a.z + b[2]) | |
dest.set(a.x + b[0], a.y + b[1], a.z + b[2]) | |
return dest | |
@classmethod | |
def sub(cls, a, b, dest=None): | |
if dest is None: | |
return PVector(a.x - b[0], a.y - b[1], a.z - b[2]) | |
dest.set(a.x - b[0], a.y - b[1], a.z - b[2]) | |
return dest | |
@classmethod | |
def mult(cls, a, b, dest=None): | |
if dest is None: | |
return PVector(a.x * b, a.y * b, a.z * b) | |
dest.set(a.x * b, a.y * b, a.z * b) | |
return dest | |
@classmethod | |
def div(cls, a, b, dest=None): | |
if dest is None: | |
return PVector(a.x / b, a.y / b, a.z / b) | |
dest.set(a.x / b, a.y / b, a.z / b) | |
return dest | |
@classmethod | |
def dist(cls, a, b): | |
return a.dist(b) | |
@classmethod | |
def dot(cls, a, b): | |
return a.dot(b) | |
def __add__(a, b): | |
return PVector.add(a, b, None) | |
def __sub__(a, b): | |
return PVector.sub(a, b, None) | |
def __isub__(a, b): | |
a.sub(b) | |
return a | |
def __iadd__(a, b): | |
a.add(b) | |
return a | |
def __mul__(a, b): | |
if not isinstance(b, Number): | |
raise TypeError( | |
"The * operator can only be used to multiply a PVector by a number") | |
return PVector.mult(a, float(b), None) | |
def __rmul__(a, b): | |
if not isinstance(b, Number): | |
raise TypeError( | |
"The * operator can only be used to multiply a PVector by a number") | |
return PVector.mult(a, float(b), None) | |
def __imul__(a, b): | |
if not isinstance(b, Number): | |
raise TypeError( | |
"The *= operator can only be used to multiply a PVector by a number") | |
a.mult(float(b)) | |
return a | |
def __truediv__(a, b): | |
if not isinstance(b, Number): | |
raise TypeError( | |
"The * operator can only be used to multiply a PVector by a number") | |
return PVector(a.x / float(b), a.y / float(b), a.z / float(b)) | |
def __itruediv__(a, b): | |
if not isinstance(b, Number): | |
raise TypeError( | |
"The /= operator can only be used to multiply a PVector by a number") | |
a.set(a.x / float(b), a.y / float(b), a.z / float(b)) | |
return a | |
def __eq__(a, b): | |
return a.x == b[0] and a.y == b[1] and a.z == b[2] | |
def __lt__(a, b): | |
return a.magSq() < b.magSq() | |
def __le__(a, b): | |
return a.magSq() <= b.magSq() | |
def __gt__(a, b): | |
return a.magSq() > b.magSq() | |
def __ge__(a, b): | |
return a.magSq() >= b.magSq() | |
@classmethod | |
def lerp(cls, a, b, f, dest=None): | |
v = PVector(a.x, a.y, a.z) | |
v.lerp(b, f) | |
if dest is None: | |
return PVector(v.x, v.y, v.z) | |
dest.set(v.x, v.y, v.z) | |
return dest | |
@classmethod | |
def cross(cls, a, b, dest=None): | |
x = a.y * b[2] - b[1] * a.z | |
y = a.z * b[0] - b[2] * a.x | |
z = a.x * b[1] - b[0] * a.y | |
if dest is None: | |
return PVector(x, y, z) | |
dest.set(x, y, z) | |
return dest | |
@classmethod | |
def fromAngle(cls, angle, length=1): | |
# https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js | |
return PVector(length * cos(angle), length * sin(angle), 0) | |
@classmethod | |
def fromAngles(theta, phi, length=1): | |
# https://github.com/processing/p5.js/blob/3f0b2f0fe575dc81c724474154f5b23a517b7233/src/math/p5.Vector.js | |
cosPhi = cos(phi) | |
sinPhi = sin(phi) | |
cosTheta = cos(theta) | |
sinTheta = sin(theta) | |
return PVector(length * sinTheta * sinPhi, | |
-length * cosTheta, | |
length * sinTheta * cosPhi) | |
@classmethod | |
def random2D(cls): # using pyp5js' random() I should replace it... | |
return PVector.fromAngle(random(TWO_PI)) | |
@classmethod | |
def random3D(cls, dest=None): # using pyp5js' random() I should replace it... | |
angle = random(TWO_PI) | |
vz = random(2) - 1 | |
mult = sqrt(1 - vz * vz) | |
vx = mult * cos(angle) | |
vy = mult * sin(angle) | |
if dest is None: | |
return PVector(vx, vy, vz) | |
dest.set(vx, vy, vz) | |
return dest | |
@classmethod | |
def angleBetween(cls, a, b): | |
return acos(a.dot(b) / sqrt(a.magSq() * b.magSq())) | |
# Other harmless p5js methods | |
def equals(self, v): | |
return self == v | |
def toString(self): | |
# Returns a string representation of a vector v by calling String(v) or v.toString(). | |
# return self.__vector.toString() would be something like "p5.vector | |
# Object […, …, …]" | |
return str(self) | |
# Other things I saw in p5js PVectors... | |
# def heading2D(self): | |
# return self.__vector.heading() | |
# | |
# def reflect(self, *args): | |
# # Reflect the incoming vector about a normal to a line in 2D, or about | |
# # a normal to a plane in 3D This method acts on the vector directly | |
# p5.Vector.prototype.reflect = function reflect(surfaceNormal) { | |
# surfaceNormal.normalize(); | |
# return this.sub(surfaceNormal.mult(2 * this.dot(surfaceNormal))); | |
# }; | |
# | |
# def array(self): | |
# # Return a representation of this vector as a float array. This is only | |
# # for temporary use. If used in any w fashion, the contents should be | |
# # copied by using the p5.Vector.copy() method to copy into your own | |
# # array. | |
# return self.__vector.array() | |
# | |
# def rem(self, *args): | |
# # Gives remainder of a vector when it is divided by anw vector. See | |
# # examples for more context. | |
# self.__vector.rem(*args) | |
# return self | |
Hi @GedToon, see if this is better: https://gist.github.com/villares/334959be52f66781fe23d1860c90ebd6
Hey thanks for sharing this @villares , I am not using it directly but a really good reference nevertheless !
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cheers @GedToon !
This code was made to be used within the pyp5js (https://github.com/berinhard/pyp5js) environment that has some stuff from https://p5js.org like the
TWO_PI
constant and arandom()
function that is different from Python'srandom.random()
.You might adapt this for your use perhaps like this:
I'd have to think about how to replace Processing's random... maybe something like this ( I have not tested it):