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

Re: [pygame] API draft for vector type



Hi,

as I already wrote in my other mail. thanks for the feedback!


Marcus von Appen wrote:
On, Mon Apr 27, 2009, Lorenz Quack wrote:

[...]

Some general notes in short, which the other thread repliers already
pointed out:

Vector2d is needed
Vector3d is needed, too (at least for me), to have 2d/3d transformations.

Vector4d can be realised using the 'w' property easily in your Vector3d class.
Vector2d is basically Vector3d without considering a z component.

I believe 3d is the most work and 2d is a trivial dumbing down and 4d is a trivial expansion while leaving out some special methods like v.cross().


Daniel Jo <ostsol@xxxxxxxxx>:
Vector classes work well for convenience and code readability, but
from a performance standpoint they aren't very useful.

Right, that's what batch operations are suitable for. Lorenz' proposal
mentions e.g. creating vectors from sequence types. If we add a set of
helper functions to the module (pygame.math.do_stuff (many_vectors)), we
can easily get around that issue. This only requires us to keep the
internally optimised methods separated well enough.

seems like you already have this worked out in your head. but module-level batch functions sound like a good idea (even though I don't know how to actually implement that, yet).

Casey Duncan <casey@xxxxxxxxxxx>:
array of C structs, but you can iterate them and access the individual
vectors for position, velocity, etc. from python.

The proposed math helper functions thus should be suitable to operate on
array() objects or buffers, where the type and object size has to be
explicitily stated. We then can get equal results easily.

just for clarity: do you mean that the helper functions should operate on arrays (numpy arrays?) of vectors of regular numbers. something like this:
  v1 = Vector3d((1, 2, 3))
  v2 = Vector3d((-4, 5.6, 7))
  a = array(v1, v2)
  axis = Vector3d((1,0,0))
  angle = 90 #degrees
  pygame.math.rotateBatch(axis, angle, a)
?

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

Seconded.

interesting. I need to see more code using immutable vectors to be totally convinced.

Casey Duncan <casey@xxxxxxxxxxx>:
PEP-8 for method and property naming!

Seconded.

+1. I'm for standard compliance. I just used my own style while writing the draft without thinking. but you're totally right.

Rene Dudfield <renesd@xxxxxxxxx>
What number types are used?  eg, can you have a float vector, a long vector,
an int vector?  Any python number?  A uint8 ?

I'd vote for two major types here: long/int and float. Depending on what
the passed values are. If we argue to use 'any python number', we'll
have to refer to the number protocol anytime an arithmetic function
occurs. This makes anything painfully slow.

If the passed values are all int or long, the vector might be handled as
long/int vector with matching optimised functions. if the numbers are
all float, other matching optimised functions might be used.

However, those considerations should be handled later on. To implement
such hooks is a bit complex due to the behaviour specification. For now
I'd vote for using either float (double) as internal representation (for
being exact enough) and/or two vector types:

FVector (floating point)
LVector (long/int)

+1 on floating point vectors (though using double precision).
+0 on int/long. I would like to see some use cases of int vectors.
-1 on the names. I think floating point is the thing users normally want so I would call that Vector, Vector3d or whatever. The int vector is a special case and therefore wait in second line with a name like IVector (in python3 we only have int so I would go for the "I" instead of the "L")

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

In my opinion normalize() and normalized() are far too ambiguous. We
might should use something like ip_XXX() for inplace operations:

  v.ip_normalize () -> None
  v.normalize () -> v2

We do that naming already for Rect objects and should take care of doing
that anywhere where inplace operations happen. The same then applies to
the rest of your inplace methods.

hm. ok. but the ip is after the name: v.normalize_ip()

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

The setter might be risky due to rounding issues.


true, but I think this can be very usefull. and I can live with the fact that
v.length = 1.
v.length == 1.  # -> false
when dealing with floating point variables you have to expect weird behavior like this.

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

Same as above.

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

How am I as user supposed to manipulate _epsilon? It should be made public.

well. I thought you rarely want to twiddle with this so I marked it private. I thought the normal user doesn't want to be bothered with this and the pro can access it.

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>"

Most objects have the same output for str() and repr(). Why do you
differ here?

with repr I wanted to make explicitly clear that this is not a list but I thought that the normal str() would look nicer that way. what would you suggest? "[x, y, z]" for both (or "(x, y, z)" if we choose to make vectors immutable) or "Vector3d<x, y, z>"?


1.10 open questions (in no particular order)
============================================
1.10.1  a) use radians for all angles
         b) use degrees for all angles

Degrees, I'd say. The api should be easy to use and letting users
calculate the rad values before is not that intuitive.

I just checked: unfortunately pygame seems to be inconsistent: transform.rotate use degrees and draw.arc uses radians.

1.10.2  what about slicing?

Slicing in a fixed 1-column matrix sounds useless to me.

the thinking was that we might want to provide as much of the python sequence interfaces as possible so that vectors can be used anywhere list/tuples are used/expected and let the user figure out if it makes any sense. I don't see a reason to prohibit it.

1.10.4  do we need int or complex vectors?

A specialised int vector class would be nice for even more optimised
code, but (in my opinion) that can be considered in a later stage. Most
of the time it's just about replacing C floating point functions with
the matching integer functions.

1.10.5  what about negative indices in the sequence protocol?

Required as supported by the Python standard. The sequence protocol is
pretty easy to implement and usually you receive a nicely converted
positive Py_ssize_t, if it is within the range. Take a look at the
PixelArray and Color classes, which implement sequence access.

If I understand you correctly python always converts a negative index to a positive one (probably by asking for the sequence's length). in that case the question is obsolete.

1.10.6  is there need for explicit row- and column-vectors?

Only in conjunction with matrix types later on, I'd say.

Yes, I was thinking of that case. But me can defer this discussion until we talk about matrices.

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:

[...]

    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"

Which is intuitive as well. We have a dot() method proposed, so it might
make sense to perform an element-wise multiplication as they do.

I'm -1 on implicit element-wise multiplication. when I instantiate two vector and multiply them I expect a scalar product to be carried out. But I do realize that element-wise multiplication is useful in some cases. therefor I would suggest to have a method v.elementwise() which returns a proxy performing the following op elementwise. that could also include division, modulo and abs.

  * __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

I'd expect abs() to get the magnitude/length, too.

I don't so to avoid confusion I wouldn't implement it at all. how do other people feel about this?

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.

For batch operations (to be speedy enough) we'd require both.

Regards
Marcus


thanks again for the quite thorough feedback.

greetings
//Lorenz