Thursday, July 25, 2013

Using the new vector_math_lists library

The latest vector_math release (v 1.3.5) includes a new library for managing lists of vectors. Introducing:

import 'package:vector_math/vector_math_lists.dart';

The library introduces three lists (one for each of Vector2, Vector3, and Vector4) that have the following interface:

class VectorNList {
  VectorNList(int length, [int offset = 0, int stride = 0]);
  VectorNList.fromList(List<VectorN> list, [int offset = 0, int stride = 0]);
  VectorNList.view(Float32List buffer, [int offset = 0, int stride = 0]);

  /// Copy element at index into vector. Zero allocations.
  void load(int index, VectorN vector);
  // Replace element at index with vector. Zero allocations.
  void store(int index, VectorN vector);

  // Read element at index. Allocates a new VectorN.
  VectorN operator[](int index); 
  // Replace element at index with vector. Zero allocations.
  void operator[]=(int index, VectorN vector);
}

Under the hood each VectorNList is backed by a Float32List. If you need to, you can pass in your own storage by using the view constructor

When constructing a vector list you can specify an offset and stride. Offset specifies the underlying Float32List that the vector list begins at. The stride specifies the number of floats in the Float32List between each Vector stored. By default the stride is the length of the vector but you can specify a larger value.

The following are three examples showing how to use a vector list:

Example #1: Construct a Vector2 list of 10 elements storing them offset by 1 in the Float32List

/// Construct a new Vector2 list with 10 items. Index 0 is offset by 1 float.
Vector2List list = new Vector2List(10, 1);
// Store vector (1.0, 2.0) into index 0.
list[0] = new Vector2(1.0, 2.0);
// Verify that list[0] is the vector we just stored.
relativeTest(list[0].x, 1.0);
relativeTest(list[0].y, 2.0);
// Verify that the vector list starts at offset 1.
relativeTest(list.buffer[0], 0.0);  // unset
relativeTest(list.buffer[1], 1.0);
relativeTest(list.buffer[2], 2.0);
relativeTest(list.buffer[3], 0.0);  // unset

Example #2: Construct a Vector2 list view on top of buffer. The view starts offset by 1 and has a stride of 3, meaning there will be a gap of 1 float between each Vector2.

Float32List buffer = new Float32List(8);
Vector2List list = new Vector2List.view(buffer, 1, 3);
// The list length should be (8 - 1) ~/ 3 == 2.
expect(list.length, 2);
list[0] = new Vector2(1.0, 2.0);
list[1] = new Vector2(3.0, 4.0);
expect(buffer[0], 0.0);
expect(buffer[1], 1.0);
expect(buffer[2], 2.0);
expect(buffer[3], 0.0);
expect(buffer[4], 3.0);
expect(buffer[5], 4.0);
expect(buffer[6], 0.0);
expect(buffer[7], 0.0);

Example #3: Construct a Vector2 list from an existing list of Vector2. The interesting bit here is that the offset and stride are specified in the list constructor control how the data is copied from the existing list.

List input = new List(3);
input[0] = new Vector2(1.0, 2.0);
input[1] = new Vector2(3.0, 4.0);
input[2] = new Vector2(5.0, 6.0);
Vector2List list = new Vector2List.fromList(input, 2, 5);
expect(list.buffer.length, 17);
expect(list.buffer[0], 0.0);
expect(list.buffer[1], 0.0);
expect(list.buffer[2], 1.0);
expect(list.buffer[3], 2.0);
expect(list.buffer[4], 0.0);
expect(list.buffer[5], 0.0);
expect(list.buffer[6], 0.0);
expect(list.buffer[7], 3.0);
expect(list.buffer[8], 4.0);
expect(list.buffer[9], 0.0);
expect(list.buffer[10], 0.0);
expect(list.buffer[11], 0.0);
expect(list.buffer[12], 5.0);
expect(list.buffer[13], 6.0);
expect(list.buffer[14], 0.0);
expect(list.buffer[15], 0.0);
expect(list.buffer[16], 0.0);

It is possible to use the lists without allocating any memory by using the load method instead of the index operator.