The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Math::3Space - 3D Coordinate math with an intuitive cross-space mapping API

SYNOPSIS

  use Math::3Space 'vec3', 'space';
  
  my $boat= space;
  my $sailor= space($boat);
  my $dock= space;
  
  # boat moves, carrying sailor with it
  $boat->translate(0,0,1)->rotate(.001, [0,1,0]);
  
  # sailor walks onto the dock
  $sailor->translate(10,0,0);
  $sailor->reparent($dock);
  
  # The boat and dock are both floating
  for ($dock, $boat) {
    $_->translate(rand(.1), rand(.1), rand(.1))
      ->rotate(rand(.001), [1,0,0])
      ->rotate(rand(.001), [0,0,1]);
  }
  
  # Sailor is holding a rope at 1,1,1 relative to themself.
  # Where is the end of the rope in boat-space?
  my $rope_end= vec3(1,1,1);
  $sailor->unproject_inplace($rope_end);
  $dock->unproject_inplace($rope_end);
  $boat->project_inplace($rope_end);
  
  # Do the same thing in bulk with fewer calculations
  my $sailor_to_boat= space($boat)->reparent($sailor);
  @boat_points= $sailor_to_boat->project(@sailor_points);
  
  # Interoperate with OpenGL
  @float16= $boat->get_gl_matrix;

DESCRIPTION

This module implements the sort of 3D coordinate space math that would typically be done using a 4x4 matrix, but instead uses a 3x4 matrix composed of axis vectors xv, yv, zv (i.e. vectors that point along the axes of the coordinate space) plus an origin point. This results in significantly fewer math operations needed to project points, and gives you a more useful mental model to work with, like being able to see which direction the coordinate space is "facing", or which way is "up".

The coordinate spaces track their "parent" coordinate space, so you can perform advanced projections from a space inside a space out to a different space inside a space inside a space without thinking about the details.

The coordinate spaces can be exported as 4x4 matrices for use with OpenGL or other common 3D systems.

CONSTRUCTOR

space

  $space= Math::3Space::space();
  $space= Math::3Space::space($parent);
  $space= $parent->space;

Construct a space (optionally within $parent) initialized to an identity:

  origin => [0,0,0],
  xv     => [1,0,0],
  yv     => [0,1,0],
  zv     => [0,0,1],

new

  $space= Math::3Space->new(%attributes)

Initialize a space from raw attributes.

ATTRIBUTES

parent

Optional reference to parent coordinate space. The origin and each of the axis vectors are described in terms of the parent coordinate space. A parent of undef means the space is described in terms of global absolute coordinates.

parent_count

Number of parent coordinate spaces above this one, i.e. the "depth" of this node in the hierarchy.

origin

The [x,y,z] vector (point) of this space's origin in terms of the parent space.

xv

The [x,y,z] vector of the X axis (often named "I" in math text)

yv

The [x,y,z] vector of the Y axis (often named "J" in math text)

zv

The [x,y,z] vector of the Z axis (often named "K" in math text)

is_normal

Returns true if all axis vectors are unit-length and orthagonal to eachother.

METHODS

clone

Return a new space with the same values, including same parent.

space

Return a new space describing an identity, with the current object as its parent.

reparent

Project this coordinate space into a different parent coordinate space. After the projection, this space still refers to the same absolute global coordinates as it did before, but it is described in terms of a different parent coordinate space.

For example, in a 3D game where a player is riding in a vehicle, the parent of the player's 3D space is the vehicle, and the parent space of the vehicle is the ground. If the player jumps off the vehicle, you would call $player->reparent($ground); to keep the player at their current position, but begin describing them in terms of the ground.

Setting $parent to undef means "global coordinates".

project

  @local_points= $space->project( @parent_points );
  @parent_points= $space->unproject( @local_points );
  @local_vectors= $space->project_vector( @parent_vectors );
  $space->project_inplace( @points );
  $space->project_vector_inplace( @vectors );

Project one or more points (or vectors) into (or out of) this coordinate space.

The project and unproject methods operate on points, meaning that they subtract or add the Space's origin to the result in addition to (un)projecting along each of the (xv, yv, zv) axes.

The _vector variants do not add/subtract "origin", so vectors that were acting as directional indicators will still be indicating that direction afterward regardless of this space's origin.

The _inplace variants modify the points or vectors and return $self for method chaining.

Each parameter is another vector to process. The projected vectors are returned in a list the same length and format as the list passed to this function, e.g. if you supply Math::3Space::Vector objects you get back Vector objects. If you supply [x,y,z] arrayrefs you get back arrayrefs.

Variants:

project
project_inplace
project_vector
project_vector_inplace
unproject
unproject_inplace
unproject_vector
unproject_vector_inplace

normalize

Ensure that the xv, yv, and zv axis vectors are unit length and orthagonal to eachother, like proper eigenvectors. The algorithm is:

  * make zv a unit vector
  * xv = yv cross zv, and make it a unit vector
  * yv = xv cross zv, and make it a unit vector

translate

  $space->translate($x, $y, $z);
  $space->translate([$x, $y, $z]);
  $space->translate($vec);
  # alias 'tr'
  $space->tr(...);

Translate the origin of the coordinate space, in terms of parent coordinates.

travel

  $space->travel($x, $y, $z);
  $space->travel([$x, $y, $z]);
  $space->travel($vec);
  # alias 'go'
  $space->go(...);

Translate the origin of the coordinate space in terms of its own coordinates. e.g. if your "zv" vector is being used as "forward", you can make an object travel forward with $space->travel(0,0,1).

set_scale

  $space->set_scale($uniform);
  $space->set_scale($x, $y, $z);
  $space->set_scale([$x, $y, $z]);
  $space->set_scale($vector);

Reset the scale of the axes of this space. For instance, ->set_scale(1) normalizes the vectors so that the scale is identical to the parent coordinate space.

scale

  $space->scale($uniform);
  $space->scale($x, $y, $z);
  $space->scale([$x, $y, $z]);
  $space->scale($vector);

Scale the axes of this space by a multiplier to the existing scale.

rotate

  $space->rotate($revolutions, $x, $y, $z);
  $space->rotate($revolutions, [$x, $y, $z]);
  $space->rotate($revolutions, $vec);
  
  $space->rot($revolutions => ...); # alias for 'rotate'
  
  $space->rot_x($revolutions);      # optimized for specific vectors
  $space->rot_xv($revolutions);

This rotates the xv, yv, and zv axes by an angle around some other vector. The angle is measured in revolutions rather than degrees or radians, so 1 is a full rotation back to where you started, and .25 is a quarter rotation. The vector is defined in terms of the parent coordinate space. If you want to rotate around an arbitrary vector defined in *local* coordinates, just unproject it out to the parent coordinate space first.

The following (more efficient) variants are available for rotating about the parent's axes or this space's own axes:

rot_x
rot_y
rot_z
rot_xv
rot_yv
rot_zv

get_gl_matrix

  @float16= $space->get_gl_matrix();
  $space->get_gl_matrix($buffer);

Get an OpenGL-compatible 16-element array representing a 4x4 matrix that would perform the same projection as this space. This can either be returned as 16 perl floats, or written into a packed buffer of 16 doubles.

AUTHOR

Michael Conrad <mike@nrdvana.net>

VERSION

version 0.003

COPYRIGHT AND LICENSE

This software is copyright (c) 2023 by Michael Conrad.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.