[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] API draft for vector type



One aspect of vector class design that I've become more and more a fan of over time is vector immutability.

So you can't say stuff like:
   vector.x += 2
   vector.x = 5

but instead would have to say stuff like:
   vector += (2, 0)
   vector = vector2d(5, vector.y)

their are a couple reasons to design them that way - first is no one can ever change your attributes underneath you - which is a bug I've run into every now and then with mutable vectors, and have had to work around by having stuff contruct and return new vectors instead of returning the internal one.

what I mean is code like this is pretty bad (but moderately easy to write)
-----------
  class Angel(object)
    def __init__(self, offset):
      self.offset = offset

  t = new Angel()
  halo_pos = t.offset
  halo_pos.y -= 5
  DrawHalo(halo_pos)
-----
and immutability of vectors makes that bug impossible.

the second reason for immutability of vectors is that they can because keys in dicts (something I've found useful, but have had to use 2 element tuples instead to make it work)


On Mon, Apr 27, 2009 at 2:59 PM, Lorenz Quack <don@xxxxxxxxxxxxxxxxx> wrote:
Hello,

I am interested in the inclusion of a vector and matrix types into pygame
as suggested here [4]. In this email I want to propose a API for a vector module.

I will for brevity only present the API for the types in three dimensions.
The APIs for two or four dimensions should look analog.

Also I enumerated every API for easier reference in discussions.
Alternatives are denoted by lexical items (e.g. a) or b))
At the end I put together a small comparison to existing implementations.

This is only a suggestion to spark discussion and provoke feedback. So throw
in your 2 cents.

sincerely yours
//Lorenz


PS: If this turns out to be of any value I will put something similar together for matrix types and quaternions.




******************
* API draft v1.0 *
******************

In the following I will use the notation:
  v, v1, v2, ... are vectors
  s, s1, s2, ... are objects implementing the sequences
                 protocol (list, tuple, the proposed vector)
  a, a1, a2, ... are scalars (int, float)


§ 1 Vector type
################

1.1 Class name and constructor
==============================
1.1.1  a) Vector3
      b) Vector3d
1.1.2  V(a1, a2, a3)# initialize x, y and z with a1, a2 and a3 respectivly
1.1.3  V(s)         # initialize x, y and z with s[0], s[1] and s[2] respectivly
1.1.4  V()          # initialize x, y and z with zeros


1.2 numerical behavior
======================
1.2.1.1  v1 + s -> v3
1.2.1.2  s + v1 -> v3
1.2.1.3  v += s
1.2.2.1  v1 - s -> v3
1.2.2.2  s - v1 -> v3
1.2.2.3  v -= s
1.2.3.1  v1 * a -> v3
1.2.3.2  a * v1 -> v3
1.2.3.3  v *= a
1.2.4.1  v1 / a -> v3
1.2.4.2  v /= a
1.2.5.1  v1 // a -> v3
1.2.5.2  v //= a
1.2.6.1  v1 % a -> v3
1.2.6.2  v %= a

1.2.7.1  v * s -> a      # dot/scalar/inner product
1.2.7.2  s * v -> a      # dot/scalar/inner product

1.2.8.1  +v1 -> v2       # returns a new vector
1.2.8.2  -v1 -> v2


1.3 sequence behavior
=====================
1.3.1    len(v) -> 3       # fixed length
1.3.2.1  v[0] -> a         # 0-based indexing
1.3.2.2  v[0] = a


1.4 attributes
==============
1.4.0    "x", "y", "z" (and "w" for 4th dimension)
        "_epsilon" for comparison operations
1.4.1.1  v.x -> a
1.4.1.2  v.x = a


1.5 methods
===========
1.5.1    v.dot(s) -> a     # dot/scalar/inner product
1.5.2    v.cross(s) -> v   # cross/vector product
        # in 2 dimensions this returns v.x * s[1] - v.y * s[0]
        # this is not defined in 4 dimensions
1.5.3    v.outer(s) -> m   # outer product yielding a matrix
1.5.4.1  v.isNormalized() -> bool
1.5.4.2  v.normalize() -> None    # normalizes inplace
1.5.4.3  v1.normalized() -> v2    # returns normalized vector
1.5.5.1  v1.rotate(s1[, a]) -> None
        # rotates around s1 by angle a. if a isn't given it
        # rotates around s1 by the magnitude of s1
        # this is an inplace operation
1.5.5.2  v1.rotated(s1[, a]) -> v2
        # same as 1.5.6 but returns a new vector and leaves v1 untouched
1.5.6.1  v1.rotateX(a) -> None
        # rotates v1 around the x-axis by the angle a
1.5.6.2  v1.rotatedX(a) -> v2
        # same as 1.5.6.1 but returns a new vector and leaves v1 untouched
1.5.6.3  # implement 1.5.6.1 and 2 also for Y and Z
1.5.7    v1.reflect(s) -> v2
        # reflects the vector of a surface with surface normal s
1.5.8    a) v1.interpolate(s, a) -> generator of vectors
        b) v1.slerp(s, a) -> generator of vectors
        # the distance between "v1" and "s" divided in "a" steps
1.5.9    v.getAngleTo(s) -> a
        # returns the angle between v and s
1.5.10.1 v.getDistanceTo(s) -> a
        # returns the distance between v and s
1.5.10.2 v.getDistance2To(s) -> a
        # returns the squared distance between v and s


1.6 properties
==============
1.6.1.1  v.length -> a # gets the magnitude/length of the vector
1.6.1.2  v.length = a
        # sets the length of the vector while preserving its direction
1.6.2.1  a) v.lengthSquared -> a
        b) v.length2 -> a
        # gets the squared length of the vector. same as v.dot(v) or v * v
1.6.2.1  a) v.lengthSquared = a
        b) v.length2 = a
        # sets the squared length of the vector. preserving its direction
# the following only have meaning in 3 dimensions
1.6.3.1  v.r -> a  # returns the "r" coordiante of sherical coordinates
                  # this is the same as the "length" property
1.6.3.2  v.r = a
1.6.4.1  v.phi -> a # returns the "phi" coordiante of spherical coordiantes
1.6.4.2  v.phi = a
1.6.5.1  v.theta -> a # returns the "theta" coordiante of spherical coordiantes
1.6.5.2  v.theta = a


1.7 comparison operations
=========================
1.7.0    the "==" and "!=" and "bool" operater compare the differences against
        the attribute v._epsilon. this way the user can adjust the accuracy.
1.7.1.1  v == s -> bool
        # true if all component differ at most by v._epsilon
1.7.1.2  s == v -> bool
1.7.2.1  v != s -> bool
        # true unless all component differ at most by v._epsilon
1.7.2.2  s != v -> bool
1.7.3    bool(v) -> bool
        # returns true if any component is larger than v._epsilon
        # formerly known as v.__nonzero__


1.8 misc
======================
1.8.1  support iter protocol
1.8.2  str(v) -> "[x, y, z]"
1.8.3  repr(v) -> "Vec<x, y, z>"
1.8.4  support pickle protocol


1.10 open questions (in no particular order)
============================================
1.10.1  a) use radians for all angles
       b) use degrees for all angles
1.10.2  what about slicing?
1.10.3  what about swizzling?
1.10.4  do we need int or complex vectors?
1.10.5  what about negative indices in the sequence protocol?
1.10.6  is there need for explicit row- and column-vectors?





Contrast to existing implementations
####################################

There are of course already existing implementations of vector types.  In
particular I want to take a look at pyeuclid [1], vectypes [2] and
3DVectorClass [3].  In this chapter I want to compare them and point out
their similarities and differences.  This isn't a full review but I tried to
find out and describe the most important differences.  If your favorite
implementation is missing from this comparison feel free to contribute your
own analysis.  Disclaimer: I never used any of these.

numerical behaviour:
 * __add__ and __sub__:
  vectypes doesn't interact with other sequence types.
    e.g. "vec2() + [3, 4]" would not work.
 * __mul__ with other vectors
  pyeuclid doesn't support multiplication with anything but numbers
    e.g. "Vector2() * Vector2()" would not work
  vectypes and 3DVectorClass do elementwise multiplication
    e.g. "vec2(1,2) * vec2(3,4) == vec(1*3, 2*4)"
  this proposal preforms a dot product
    e.g. "V(1,2) * V(3,4) == 1*3 + 2*4"
 * __div__
  pyeuclid and this proposal only support division by (int, long, float)
  vectypes and 3DVectorClass also do implicit elementwise division
  same for __floordiv__ and __mod__
 * __abs__
  pyeuclid returns the magnitude.
  3DVectorType returns vec3d(abs(x), abs(y), abs(z))
  vectypes and this proposal don't implement __abs__ to avoid confusion

other differences:
 * from the mentioned packages only pyeuclid optionally supports swizzleing
 * pyeuclid has a method "magnitude" instead of "length"
 * vectypes uses functions at a module level rather than instance methods.
 * vectypes has a method refract
 * pyeuclid has seperate geometry classes like "point", "line", "ray" and
  "lineSegment"
 * only 3DVectorClass and this proposal have built-in methods of
  the "rotate"-family


#################################################
[1] http://partiallydisassembled.net/euclid.html
[2] http://code.google.com/p/vectypes/
[3] http://pygame.org/wiki/3DVectorClass
[4] http://pygame.org/wiki/gsoc2009ideas#Math%20for%20pygame%20(vectors,%20matrix,%20etc.)