io7m-jtensors 7.0.0-beta0006
io7m-jtensors 7.0.0-beta0006 Documentation
Package Information
Orientation
Overview
The io7m-jtensors package implements a set of efficient vector, matrix, and quaternion classes intended for use in computer graphics applications.
Efficiency
The package uses simple and efficient algorithms for all operations. The package also provides matrices that are backed by direct memory, allowing for zero-copy sharing of structures with native code.
Each vector and matrix type has an associated context type containing enough preallocated storage to execute all functions in the type without performing any allocations. All code paths are therefore allocation free (with the exception of toString methods, which require allocation by design).
Correctness
The package includes a large battery of automated tests that attempt to verify the correctness of the included implementations. As of the time of writing, the tests manage 100% coverage for all code.
Installation
Source compilation
The project can be compiled and installed with Maven:
$ mvn -C clean install
Maven
Regular releases are made to the Central Repository, so it's possible to use the io7m-jtensors package in your projects with the following Maven dependency:
<dependency>
  <groupId>com.io7m.jtensors</groupId>
  <artifactId>io7m-jtensors-core</artifactId>
  <version>7.0.0-beta0006</version>
</dependency>
All io7m.com packages use Semantic Versioning [0], which implies that it is always safe to use version ranges with an exclusive upper bound equal to the next major version - the API of the package will not change in a backwards-incompatible manner before the next major version.
Platform Specific Issues
There are currently no known platform-specific issues.
License
All files distributed with the io7m-jtensors package are placed under the following license:
Copyright © 2015 <code@io7m.com> http://io7m.com

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
        
Usage & Conventions
Vectors
Types
The io7m-jtensors package provides vectors with single-precision (float) elements, double-precision (double) elements, integer (int), and long integer (long) elements. Each vector type is available in two, three, and four element versions. Each vector type is available in mutable or immutable versions. The package unambiguously identifies the vector types by using the following naming conventions for the types (given as a simple EBNF grammar), where prefix is a type-specific prefix:
prefix       = ...
mutability   = "M" | "I" ;
size         = "2" | "3" | "4" ;
element_type = "I" | "L" | "F" | "D" ;
type         = prefix , mutability , size , element_type ;
      
Access control
If a function only needs to read from a value of a vector type, then it would be desirable to be able to statically enforce this fact. Therefore, the package provides a set of Readable interfaces that all vector types implement. By writing a function that takes a value of a type implementing a Readable interface, the programmer is implicitly stating that this function does not modify the vector.
Conversely, if a function writes to the elements of a vector without ever reading from it, it again would be desirable to statically enforce this fact. The package provides Writable interfaces that all mutable vector types implement:
Finally, a combination of the above interfaces is provided that essentially abstracts over mutable vector types. This allows the programmer to write code against pure interfaces without referring to a specific type, and to freely substitute alternative implementations at any time.
Storage
As with all matrix types, all vector types have associated Context types that allow for all methods to execute without allocating any memory. See the linked section for details.
Phantom types
The io7m-jtensors package also provides copies of the existing vector types indexed by a phantom type parameter in order to allow the programmer to make semantically distinct values type-incompatible [1].
The parameterized vector types and interfaces are provided in the com.io7m.jtensors.parameterized package.
Matrices
Types
The io7m-jtensors package provides only square matrix types with single-precision (float) elements, and double-precision (double) elements. The package unambiguously identifies the matrix types by using the following naming conventions for the types (given as a simple EBNF grammar), where prefix is a type-specific prefix:
prefix       = ...
mutability   = "M" | "I" ;
size         = "2x2" | "3x3" | "4x4" ;
element_type = "F" | "D" ;
type         = prefix , mutability , size , element_type ;
      
Storage
Several implementations of the matrix types are available, some of which store the elements of matrices data in direct memory [2]. This allows for zero-copy passing of the matrix data to native code [3]. Use of direct memory does incur an extra cost with regards to creation and deletion of matrices, and some functions in the package do require temporary matrices in order to work. Therefore, the package provides Context types that allow the user to pre-allocate storage that the functions can reuse an unlimited number of times. The package typically defines one Context type per class. See the MatrixM4x4D.ContextMM4D type for a concrete example; any function in the MatrixM4x4D class that takes a ContextMM4D as an argument can be depended upon not to allocate more memory. Note that the ContextMM4D type cannot be shared across threads and is completely opaque outside of the jtensors package: Users should allocate one ContextMM4D per thread.
MatrixM4x4D.ContextMM4D context = new MatrixM4x4D.ContextMM4D();
Matrix4x4DType mi = MatrixDirectM4x4D.newMatrix();
Matrix4x4DType mo = MatrixDirectM4x4D.newMatrix();

MatrixM4x4D.invert(context, mi, mo);
MatrixM4x4D.invert(context, mo, mi);
      
Matrix data is stored in column-major format [4], in whatever is the platform's native byte order. For an m x m square matrix, assuming that each element of the matrix uses n bytes, the first byte of the element at row r and column c (assuming 0 <= r < m and 0 <= c < m) can be found by (c * m * n) + (r * n).
As an example, a 4x4 matrix with 4 byte elements would be stored in memory as shown in the following diagram:
So, the element at row 0, column 0 would be stored in bytes [0 .. 3]. The element at row 1, column 0 would be stored in bytes [4 .. 7]. The element at row 0, column 1 would be stored in bytes [16 .. 19], and so on.
The immutable matrix types available in the package are not backed by direct memory and only offer a small fraction of the functionality of the mutable types. They are essentially provided as a way to construct immutable snapshots of a mutable matrix in order to, for example, safely transfer matrix values across threads without sharing a reference to mutable memory.
Rotations & Handedness
Any of the matrix functions that deal with rotations assume a right-handed coordinate system. This matches the system used by OpenGL (and most mathematics literature). A right-handed coordinate system assumes that if the viewer is standing at the origin and looking towards negative infinity on the Z axis, then the X axis runs horizontally (left towards negative infinity and right towards positive infinity), and the Y axis runs vertically (down towards negative infinity and up towards positive infinity). The following image demonstrates this axis configuration:
The io7m-jtensors package adheres to the convention that a positive rotation around an axis represents a counter-clockwise rotation when viewing the system along the negative direction of the axis in question.
The package uses the following matrices to define rotations around each axis:
Which results in the following matrix for rotating r radians around the axis given by (x, y, z), assuming s = sin(r) and c = cos(r) [5]:
Access control
If a function only needs to read from a value of a matrix type, then it would be desirable to be able to statically enforce this fact. Therefore, the package provides a set of Readable interfaces that all matrix types implement. By writing a function that takes a value of a type implementing a Readable interface, the programmer is implicitly stating that this function does not modify the matrix.
If a function needs to write to a value of a matrix type, then it would be desirable to be able to statically indicate this fact. Therefore, the package provides a set of Writable interfaces that all mutable matrix types implement. By writing a function that takes a value of a type implementing a Writable interface, the programmer is implicitly stating that this function may modify the matrix.
A combination of the above interfaces is provided that essentially abstracts over mutable matrix types. This allows the programmer to write code against pure interfaces without referring to a specific type, and to freely substitute alternative implementations at any time.
Finally, if matrices that are backed by direct memory are required (in order to pass them directly to native code, for example), the programmer can require matrices of a Direct type.
It is also useful for the sizes of matrices to be indicated in the types, so the package provides a set of interfaces combining the Readable and Direct types.
Interfaces that abstract over direct, mutable matrices are also provided:
Phantom Types
As with the vector types, the io7m-jtensors package provides copies of all of the existing matrix types (and interfaces) indexed by a pair of phantom type parameters.
Conceptually, a matrix can be considered as storing a transform from coordinate space T0 to space T1. For a 4x4 mutable matrix in the io7m-jtensors package, this is denoted by the type PMatrixM4x4F<T0,T1>. It then follows that when matrices are concatenated via multiplications, their type parameters are translated accordingly. For example, a matrix PMatrixM4x4F<T0,T1> multiplied by a matrix PMatrixM4x4F<T1,T2> results in a matrix of type PMatrixM4x4F<T0,T2>. Inverting a matrix results in a matrix that represents the inverse of the original transform that the matrix represented. For example, inverting a matrix of type PMatrixM4x4F<T0,T1> results in a matrix of type PMatrixM4x4F<T1,T0>.
Type parameters are also translated across multiplications by vectors. A multiplication of a vector of type PVectorI4F<T0> by a matrix of type PMatrixM4x4F<T0,T1> results in a vector of type PVectorI4F<T1>.
Being able to track the types of transforms at this level of detail is invaluable when using systems such as OpenGL, where accidentally mixing up matrices tends to result in visual anomalies that can be extremely hard to track down. By explicitly denoting coordinate spaces with empty types, it's possible to statically prevent all bugs involving accidentally mixing up matrices. It's also possible to prevent the incorrect construction of matrices [6]. Additionally, with each matrix labelled by the type of transform it represents, code becomes self-documenting.
interface WorldSpace { }
interface ViewSpace { }
interface ObjectSpace { }

PMatrixM4x4F<ObjectSpace, WorldSpace> matrix_model;
PMatrixM4x4F<WorldSpace, ViewSpace> matrix_view;
PMatrixM4x4F<ObjectSpace, ViewSpace> matrix_modelview;

PMatrixM4x4F.multiply (matrix_view, matrix_model, matrix_modelview);

// Compilation error: The resulting matrix would be of type PMatrixM4x4F<ViewSpace, ObjectSpace>
// PMatrixM4x4F.multiply (matrix_model, matrix_view, matrix_modelview);

PMatrixM4x4F<ViewSpace, WorldSpace> matrix_view_inverse;
PMatrixM4x4F.invert(context, matrix_view, matrix_view_inverse);
Of course, because the type system is unsound, and because the matrices are mutable, it is obviously possible to deliberately construct matrices that claim to be of a particular type of transform but nevertheless contain nonsense values. It is, however, difficult to do this accidentally. The io7m-jtensors package strives to provide a means to prevent accidental errors, and doesn't attempt to address deliberate sabotage.
The parameterized matrix types and interfaces are provided in the com.io7m.jtensors.parameterized package.
Quaternions
Types
The io7m-jtensors package provides quaternions with single-precision (float) elements, and double-precision (double) elements. Each quaternion type is available in mutable or immutable versions. The package unambiguously identifies the quaternion types by using the following naming conventions for the types (given as a simple EBNF grammar):
mutability   = "M" | "I" ;
size         = "4" ;
element_type = "F" | "D" ;
type         = "Quaternion" , mutability , size , element_type ;
      
A comprehensive list of the available quaternion types is as follows:
Rotations & Handedness
The included quaternion types and functions have consistent semantics and assumptions with regards to rotation and coordinate systems as the Matrix types, and functions are provided to convert between matrices and quaternions.
Access control
If a function only needs to read from a value of a quaternion type, then it would be desirable to be able to statically enforce this fact. Therefore, the package provides a set of Readable interfaces that all quaternion types implement. By writing a function that takes a value of a type implementing a Readable interface, the programmer is implicitly stating that this function does not modify the quaternion.
Writable interfaces are provided:
Interfaces that abstract over mutable quaternions are provided:

[3]
Typically, the OpenGL API.
[4]
The convention used by most programs using the OpenGL API.
[5]
See Mathematics for 3D Game Programming and Computer Graphics 3rd Edition, section 4.3.1 for the derivation.
[6]
It is common for people to make mistakes with matrix multiplication: The order of matrices is effectively the reverse of the order in which the transforms will be applied.