11. API - Vector, vector_range, etc.

The vector module defines the Vector class, which is the usual method of represent coordinates or vectors when dealing with the Minecraft world. It also provides functions like vector_range() for generating sequences of vectors.

Note

All items in this module are available from the picraft namespace without having to import picraft.vector directly.

The following items are defined in the module:

11.1. Vector

class picraft.vector.Vector(x=0, y=0, z=0)[source]

Represents a 3-dimensional vector.

This tuple derivative represents a 3-dimensional vector with x, y, z components. Instances can be constructed in a number of ways. By explicitly specifying the x, y, and z components (optionally with keyword identifiers), or leaving the empty to default to 0:

>>> Vector(1, 1, 1)
Vector(x=1, y=1, z=1)
>>> Vector(x=2, y=0, z=0)
Vector(x=2, y=0, z=0)
>>> Vector()
Vector(x=0, y=0, z=0)
>>> Vector(y=10)
Vector(x=0, y=10, z=0)

Shortcuts are available for the X, Y, and Z axes:

>>> X
Vector(x=1, y=0, z=0)
>>> Y
Vector(x=0, y=1, z=0)

Note that vectors don’t much care whether their components are integers, floating point values, or None:

>>> Vector(1.0, 1, 1)
Vector(x=1.0, y=1, z=1)
>>> Vector(2, None, None)
Vector(x=2, y=None, z=None)

The class supports simple arithmetic operations with other vectors such as addition and subtraction, along with multiplication and division with scalars, raising to powers, bit-shifting, and so on. Such operations are performed element-wise [1]:

>>> v1 = Vector(1, 1, 1)
>>> v2 = Vector(2, 2, 2)
>>> v1 + v2
Vector(x=3, y=3, z=3)
>>> 2 * v2
Vector(x=4, y=4, z=4)

Simple arithmetic operations with scalars return a new vector with that operation performed on all elements of the original. For example:

>>> v = Vector()
>>> v
Vector(x=0, y=0, z=0)
>>> v + 1
Vector(x=1, y=1, z=1)
>>> 2 * (v + 2)
Vector(x=4, y=4, z=4)
>>> Vector(y=2) ** 2
Vector(x=0, y=4, z=0)

Within the Minecraft world, the X,Z plane represents the ground, while the Y vector represents height.

Note

Note that, as a derivative of tuple, instances of this class are immutable. That is, you cannot directly manipulate the x, y, and z attributes; instead you must create a new vector (for example, by adding two vectors together). The advantage of this is that vector instances can be used in sets or as dictionary keys.

[1]I realize math purists will hate this (and demand that abs() should be magnitude and * should invoke matrix multiplication), but the element wise operations are sufficiently useful to warrant the short-hand syntax.
replace(x=None, y=None, z=None)[source]

Return the vector with the x, y, or z axes replaced with the specified values. For example:

>>> Vector(1, 2, 3).replace(z=4)
Vector(x=1, y=2, z=4)
ceil()[source]

Return the vector with the ceiling of each component. This is only useful for vectors containing floating point components:

>>> Vector(0.5, -0.5, 1.2)
Vector(1.0, 0.0, 2.0)
floor()[source]

Return the vector with the floor of each component. This is only useful for vectors containing floating point components:

>>> Vector(0.5, -0.5, 1.9)
Vector(0.0, -1.0, 1.0)
dot(other)[source]

Return the dot product of the vector with the other vector. The result is a scalar value. For example:

>>> Vector(1, 2, 3).dot(Vector(2, 2, 2))
12
>>> Vector(1, 2, 3).dot(X)
1
cross(other)[source]

Return the cross product of the vector with the other vector. The result is another vector. For example:

>>> Vector(1, 2, 3).cross(Vector(2, 2, 2))
Vector(x=-2, y=4, z=-2)
>>> Vector(1, 2, 3).cross(X)
Vector(x=0, y=3, z=-2)
distance_to(other)[source]

Return the Euclidian distance between two three dimensional points (represented as vectors), calculated according to Pythagoras’ theorem. For example:

>>> Vector(1, 2, 3).distance_to(Vector(2, 2, 2))
1.4142135623730951
>>> Vector().distance_to(X)
1.0
angle_between(other)[source]

Returns the angle between this vector and the other vector on a plane that contains both vectors. The result is measured in degrees. For example:

>>> X.angle_between(Y)
90.0
>>> (X + Y).angle_between(X)
45.00000000000001
project(other)[source]

Return the scalar projection of this vector onto the other vector. This is a scalar indicating the length of this vector in the direction of the other vector. For example:

>>> Vector(1, 2, 3).project(2 * Y)
2.0
>>> Vector(3, 4, 5).project(Vector(3, 4, 0))
5.0
rotate(angle, about, origin=None)[source]

Return this vector after rotation of angle degrees about the line passing through origin in the direction about. Origin defaults to the vector 0, 0, 0. Hence, if this parameter is omitted this method calculates rotation about the axis (through the origin) defined by about. For example:

>>> Y.rotate(90, about=X)
Vector(x=0, y=6.123233995736766e-17, z=1.0)
>>> Vector(3, 4, 5).rotate(30, about=X, origin=10 * Y)
Vector(x=3.0, y=2.3038475772933684, z=1.330127018922194)

Information about rotation around arbitrary lines was obtained from Glenn Murray’s informative site.

x

The position or length of the vector along the X-axis. In the Minecraft world this can be considered to run left-to-right.

y

The position or length of the vector along the Y-axis. In the Minecraft world this can be considered to run vertically up and down.

z

The position or length of the vector along the Z-axis. In the Minecraft world this can be considered as depth (in or out of the screen).

magnitude

Returns the magnitude of the vector. This could also be considered the distance of the vector from the origin, i.e. v.magnitude is equivalent to Vector().distance_to(v). For example:

>>> Vector(2, 4, 4).magnitude
6.0
>>> Vector().distance_to(Vector(2, 4, 4))
6.0
unit

Return a unit vector (a vector with a magnitude of one) with the same direction as this vector:

>>> X.unit
Vector(x=1.0, y=0.0, z=0.0)
>>> (2 * Y).unit
Vector(x=0.0, y=1.0, z=0.0)

Note

If the vector’s magnitude is zero, this property returns the original vector.

11.2. Short-hand variants

The Vector class is used sufficiently often to justify the inclusion of some shortcuts. The class itself is also available as V, and vectors representing the three axes are each available as X, Y, and Z. Finally, a vector representing the origin is available as O:

>>> from picraft import V, O, X, Y, Z
>>> O
Vector(x=0, y=0, z=0)
>>> 2 * X
Vector(x=2, y=0, z=0)
>>> X + Y
Vector(x=1, y=1, z=0)
>>> (X + Y).angle_between(X)
45.00000000000001
>>> V(3, 4, 5).projection(X)
3.0
>>> X.rotate(90, about=Y)
Vector(x=0.0, y=0.0, z=1.0)

11.3. vector_range

class picraft.vector.vector_range(start, stop=None, step=None, order=u'zxy')[source]

Like range(), vector_range is actually a type which efficiently represents a range of vectors. The arguments to the constructor must be Vector instances (or objects which have integer x, y, and z attributes).

If step is omitted, it defaults to Vector(1, 1, 1). If the start argument is omitted, it defaults to Vector(0, 0, 0). If any element of the step vector is zero, ValueError is raised.

The contents of the range are largely determined by the step and order which specifies the order in which the axes of the range will be incremented. For example, with the order 'xyz', the X-axis will be incremented first, followed by the Y-axis, and finally the Z-axis. So, for a range with the default start, step, and stop set to Vector(3, 3, 3), the contents of the range will be:

>>> list(vector_range(Vector(3, 3, 3), order='xyz'))
[Vector(0, 0, 0), Vector(1, 0, 0), Vector(2, 0, 0),
 Vector(0, 1, 0), Vector(1, 1, 0), Vector(2, 1, 0),
 Vector(0, 2, 0), Vector(1, 2, 0), Vector(2, 2, 0),
 Vector(0, 0, 1), Vector(1, 0, 1), Vector(2, 0, 1),
 Vector(0, 1, 1), Vector(1, 1, 1), Vector(2, 1, 1),
 Vector(0, 2, 1), Vector(1, 2, 1), Vector(2, 2, 1),
 Vector(0, 0, 2), Vector(1, 0, 2), Vector(2, 0, 2),
 Vector(0, 1, 2), Vector(1, 1, 2), Vector(2, 1, 2),
 Vector(0, 2, 2), Vector(1, 2, 2), Vector(2, 2, 2)]

Vector ranges implement all common sequence operations except concatenation and repetition (due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation usually cause the resulting sequence to violate that pattern).

Vector ranges are extremely efficient compared to an equivalent list() or tuple() as they take a small (fixed) amount of memory, storing only the arguments passed in its construction and calculating individual items and sub-ranges as requested.

Vector range objects implement the collections.Sequence ABC, and provide features such as containment tests, element index lookup, slicing and support for negative indices.

The default order ('zxy') may seem an odd choice. This is primarily used as it’s the order used by the Raspberry Juice server when returning results from the world.getBlocks call. In turn, Raspberry Juice probably uses this order as it results in returning a horizontal layer of vectors at a time (given the Y-axis is used for height in the Minecraft world).

Warning

Bear in mind that the ordering of a vector range may have a bearing on tests for its ordering and equality. Two ranges with different orders are unlikely to test equal even though they may have the same start, stop, and step attributes (and thus contain the same vectors, but in a different order).

Vector ranges can be accessed by integer index, by Vector index, or by a slice of vectors. For example:

>>> v = vector_range(Vector() + 1, Vector() + 3)
>>> list(v)
[Vector(x=1, y=1, z=1),
 Vector(x=1, y=1, z=2),
 Vector(x=2, y=1, z=1),
 Vector(x=2, y=1, z=2),
 Vector(x=1, y=2, z=1),
 Vector(x=1, y=2, z=2),
 Vector(x=2, y=2, z=1),
 Vector(x=2, y=2, z=2)]
>>> v[0]
Vector(x=1, y=1, z=1)
>>> v[Vector(0, 0, 0)]
Vector(x=1, y=1, z=1)
>>> v[Vector(1, 0, 0)]
Vector(x=2, y=1, z=1)
>>> v[-1]
Vector(x=2, y=2, z=2)
>>> v[Vector() - 1]
Vector(x=2, y=2, z=2)
>>> v[Vector(x=1):]
vector_range(Vector(x=2, y=1, z=1), Vector(x=3, y=3, z=3),
        Vector(x=1, y=1, z=1), order='zxy')
>>> list(v[Vector(x=1):])
[Vector(x=2, y=1, z=1),
 Vector(x=2, y=1, z=2),
 Vector(x=2, y=2, z=1),
 Vector(x=2, y=2, z=2)]

However, integer slices are not currently permitted.

count(value)[source]

Return the count of instances of value within the range (note this can only be 0 or 1 in the case of a range, and thus is equivalent to testing membership with in).

index(value)[source]

Return the zero-based index of value within the range, or raise ValueError if value does not exist in the range.

11.4. line

picraft.vector.line(start, end)[source]

A three-dimensional implementation of Bresenham’s line algorithm, derived largely from Bob Pendelton’s implementation (public domain). Given the end points of the line as the start and end vectors, this generator function yields the coordinate of each block (inclusive of the start and end vectors) that should be filled in to render the line.

For example:

>>> list(line(O, V(10, 5, 0)))
[Vector(x=0, y=0, z=0),
 Vector(x=1, y=1, z=0),
 Vector(x=2, y=1, z=0),
 Vector(x=3, y=2, z=0),
 Vector(x=4, y=2, z=0),
 Vector(x=5, y=3, z=0),
 Vector(x=6, y=3, z=0),
 Vector(x=7, y=4, z=0),
 Vector(x=8, y=4, z=0),
 Vector(x=9, y=5, z=0),
 Vector(x=10, y=5, z=0)]

11.5. lines

picraft.vector.lines(points, closed=True)[source]

Extension of the line() function which returns all vectors necessary to render the lines connecting the specified points (which is an iterable of Vector instances).

If the optional closed parameter is True (the default) the last point in the points sequence will be connected to the first point. Otherwise, the lines will be left disconnected (assuming the last point is not coincident with the first). For example:

>>> points = [O, 4*X, 4*Z]
>>> list(lines(points))
[Vector(x=0, y=0, z=0),
 Vector(x=1, y=0, z=0),
 Vector(x=2, y=0, z=0),
 Vector(x=3, y=0, z=0),
 Vector(x=4, y=0, z=0),
 Vector(x=3, y=0, z=1),
 Vector(x=2, y=0, z=2),
 Vector(x=1, y=0, z=3),
 Vector(x=0, y=0, z=4),
 Vector(x=0, y=0, z=3),
 Vector(x=0, y=0, z=2),
 Vector(x=0, y=0, z=1),
 Vector(x=0, y=0, z=0)]