Skip to main content

src/transform3d.erl

%%%-------------------------------------------------------------------
%%% erlrithmetician - linear algebra library for Erlang
%%% Copyright (C) 2026 E. G. Bland
%%%
%%% This program is free software: you can redistribute it and/or modify
%%% it under the terms of the GNU General Public License as published by
%%% the Free Software Foundation, either version 3 of the License, or
%%% (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%%% GNU General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program.  If not, see <https://www.gnu.org/licenses/>.
%%%-------------------------------------------------------------------

-module(transform3d).

-export([rotate_xy/1, rotate_xz/1, rotate_yz/1, rotate_axis/2, scale/3, translate/3]).

-spec rotate_xy(number()) -> mat:mat(float()).
-spec rotate_xz(number()) -> mat:mat(float()).
-spec rotate_yz(number()) -> mat:mat(float()).
-spec rotate_axis(vec:vec(number()), number()) -> mat:mat(float()).
-spec scale(number(), number(), number()) -> mat:mat(number()).
-spec translate(number(), number(), number()) -> mat:mat(number()).

rotate_xy(Theta) ->
  Cos = math:cos(Theta),
  Sin = math:sin(Theta),
  mat:new(array:from_list([vec:new3(Cos, Sin, 0), vec:new3(-Sin, Cos, 0), vec:new3(0, 0, 1)])).

rotate_xz(Theta) ->
  Cos = math:cos(Theta),
  Sin = math:sin(Theta),
  mat:new(array:from_list([vec:new3(Cos, 0, -Sin), vec:new3(0, 1, 0), vec:new3(Sin, 0, Cos)])).

rotate_yz(Theta) ->
  Cos = math:cos(Theta),
  Sin = math:sin(Theta),
  mat:new(array:from_list([vec:new3(1, 0, 0), vec:new3(0, Cos, Sin), vec:new3(0, -Sin, Cos)])).

rotate_axis(Axis, Theta) ->
  Dim = vec:dim(Axis),
  case Dim of
    3 ->
      AxisNorm = vec:normalise(Axis),
      X = vec:x(AxisNorm),
      Y = vec:y(AxisNorm),
      Z = vec:z(AxisNorm),
      Cos = math:cos(Theta),
      Sin = math:sin(Theta),
      mat:new(array:from_list([
        vec:new3(
          Cos + (1 - Cos) * X * X,
          (1 - Cos) * X * Y + Sin * Z,
          (1 - Cos) * X * Z - Sin * Y
        ),
        vec:new3(
          (1 - Cos) * X * Y - Sin * Z,
          Cos + (1 - Cos) * Y * Y,
          (1 - Cos) * Y * Z + Sin * X
        ),
        vec:new3(
          (1 - Cos) * X * Z + Sin * Y,
          (1 - Cos) * Y * Z - Sin * X,
          Cos + (1 - Cos) * Z * Z
        )
      ]));
    _ -> error({ axis_not_3d, { axis_dim, Dim } })
  end.

scale(Sx, Sy, Sz) ->
  mat:new(array:from_list([vec:new3(Sx, 0, 0), vec:new3(0, Sy, 0), vec:new3(0, 0, Sz)])).

translate(Tx, Ty, Tz) ->
  mat:new(array:from_list([vec:new4(1, 0, 0, 0), vec:new4(0, 1, 0, 0), vec:new4(0, 0, 1, 0), vec:new4(Tx, Ty, Tz, 1)])).