% Copyright (C) 2003-2019 Olivier Boudeville
%
% This file is part of the Ceylan-Myriad library.
%
% This library is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License or
% the GNU General Public License, as they are published by the Free Software
% Foundation, either version 3 of these Licenses, or (at your option)
% any later version.
% You can also redistribute it and/or modify it under the terms of the
% Mozilla Public License, version 1.1 or later.
%
% This library 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 Lesser General Public License and the GNU General Public License
% for more details.
%
% You should have received a copy of the GNU Lesser General Public
% License, of the GNU General Public License and of the Mozilla Public License
% along with this library.
% If not, see and
% .
%
% Author: Olivier Boudeville [olivier (dot) boudeville (at) esperide (dot) com]
% Creation date: Monday, February 15, 2010.
% Gathering of various three dimensional linear facilities.
%
% See linear_3D_test.erl for the corresponding test.
%
-module(linear_3D).
% Relatively aggressive inlining for basic operations:
-compile( inline ).
-compile( { inline_size, 48 } ).
% For the mat3 record:
-include("linear_3D.hrl").
% Operations on points:
%
-export([ get_origin/0, are_close/2, is_within/3, is_within_square/3,
square_distance/2, distance/2, cross_product/2, roundify/1,
get_integer_center/2, get_center/2, translate/2 ]).
% Operations on vectors:
%
-export([ vectorize/2, square_magnitude/1, magnitude/1, scale/2, make_unit/1,
dot_product/2 ]).
% Operations common to points and vectors:
%
-export([ add/1, add/2 ]).
% Textual conversions:
%
-export([ to_string/1 ]).
% For epsilon:
-include("math_utils.hrl").
% Shorthands:
-type coordinate() :: linear:coordinate().
-type factor() :: linear:factor().
% Section about points and vectors.
% A 3D vector, with floating-point coordinates.
%
% They are typically referenced as [ X, Y, Z ].
%
-type point() :: { coordinate(), coordinate(), coordinate() }.
% A 3D point, with integer coordinates.
%
-type integer_point() :: { linear:integer_coordinate(),
linear:integer_coordinate(),
linear:integer_coordinate() }.
% Vectors could/should be aliased to points.
% A 3D vector, with floating-point coordinates:
%
-type vector() :: { coordinate(), coordinate(), coordinate() }.
% A 3D unit vector, i.e. a vector of magnitude 1.0.
%
% For documentation purpose.
%
-type unit_vector() :: vector().
% A 3D vector normal to a plane.
%
% For documentation purpose.
%
-type normal() :: vector().
% A 3D unit vector normal to a plane.
%
% For documentation purpose.
%
-type unit_normal() :: unit_vector().
% 3D vector, with integer coordinates:
%
-type integer_vector() :: { linear:integer_coordinate(),
linear:integer_coordinate(),
linear:integer_coordinate() }.
-export_type([ point/0, integer_point/0,
vector/0, unit_vector/0, normal/0, unit_normal/0,
integer_vector/0 ]).
% Section about lines and planes.
% A 3D line, whose equation A.x+B.y+C.z+D=0, can be defined from these four
% factors {A,B,C,D}.
%
-type line() :: { factor(), factor(), factor(), factor() }.
% A plane, whose general equation is: A.x + B.y + C.z + D = 0, where:
%
% - P=(x,y,z) is a point belonging to this plane
%
% - N=(A,B,C) is a (non-necessarily unit) normal vector to this plane
%
% - P0=(x0,y0,z0) is a point of that plane
%
% - D= -A.x0 - B.y0 - C.z0
%
% See http://mathworld.wolfram.com/Plane.html
%
% So a plane may be described as (N,D):
%
-type plane() :: { normal(), factor() }.
% A plane in Hessian normal form.
%
% See http://mathworld.wolfram.com/HessianNormalForm.html
%
-type hessian_plane() :: { unit_normal(), factor() }.
-export_type([ line/0, plane/0, hessian_plane/0 ]).
% Section about matrices.
% Alias for 3x3 canonical matrices:
-type mat3() :: #mat3{}.
-type canonical_matrix() :: mat3().
% Aliases for 3x3 compact matrices:
-type cpt_mat3() :: #cpt_mat3{}.
-type compact_matrix() :: cpt_mat3().
-type matrix() :: 'identity_3' | canonical_matrix() | compact_matrix().
-export_type([ mat3/0, canonical_matrix/0,
cpt_mat3/0, compact_matrix/0,
matrix/0 ]).
% Section about shapes.
% Various types of known 3D shapes (basic geometries):
%
-type shape() :: 'sphere' | 'right_cuboid'.
-export_type([ shape/0 ]).
% Implementations now.
% Point section.
% Returns the origin of this referential.
%
-spec get_origin() -> point().
get_origin() ->
{ 0, 0, 0 }.
% Returns whether the two specified points are close, i.e. if they could be
% considered as representing the same point (equality operator on points).
%
-spec are_close( point(), point() ) -> boolean().
are_close( _P1={X1,Y1,Z1}, _P2={X2,Y2,Z2} ) ->
math_utils:are_close( X1, X2 ) andalso math_utils:are_close( Y1, Y2 )
andalso math_utils:are_close( Z1, Z2 ).
% Tells whether point P is within a distance D from point C, using some margin
% to overcome numerical errors.
%
-spec is_within( point(), point(), linear:distance() ) -> boolean().
is_within( P, C, D ) ->
% "Taylor series", square(epsilon) is negligible here:
square_distance( P, C ) < D * ( D + ?epsilon ).
% Tells whether point P is within a square distance SquareD from point C.
%
-spec is_within_square( point(), point(),
linear:square_distance() ) -> boolean().
is_within_square( P, C, SquareD ) ->
square_distance( P, C ) < SquareD.
% Returns the square of the distance between the two specified points.
%
% For comparison purposes, computing the square root is useless.
%
% Could rely on vectorize and square_magnitude as well.
%
-spec square_distance( point(), point() ) -> linear:square_distance().
square_distance( {X1,Y1,Z1}, {X2,Y2,Z2} ) ->
XDiff = X2 - X1,
YDiff = Y2 - Y1,
ZDiff = Z2 - Z1,
XDiff*XDiff + YDiff*YDiff + ZDiff*ZDiff.
% Returns the distance between the two specified points.
%
% Note: just for comparison purposes, computing the square root is useless.
%
% Could rely on vectorize and magnitude as well.
%
-spec distance( point(), point() ) -> linear:distance().
distance( P1, P2 ) ->
math:sqrt( square_distance( P1, P2 ) ).
% Returns the cross-product of the two specified vectors, i.e. the results of a
% regular 3D cross product of the input vectors.
%
-spec cross_product( vector(), vector() ) -> vector().
cross_product( {X1,Y1,Z1}, {X2,Y2,Z2} ) ->
{ Y1*Z2 - Z1*Y2, Z1*X2 - X1*Z2, X1*Y2 - Y1*X2 }.
% Returns a point (or vector) whose coordinates have been rounded to the
% respective nearest integers.
%
-spec roundify( point() ) -> integer_point().
roundify( {X,Y,Z} ) ->
{ erlang:round(X), erlang:round(Y), erlang:round(Z) }.
% Returns a vertex corresponding the middle of the two specified vertices.
%
-spec get_center( point(), point() ) -> point().
get_center( {X1,Y1,Z1}, {X2,Y2,Z2} ) ->
{ (X1+X2)/2, (Y1+Y2)/2, (Z1+Z2)/2 }.
% Returns a vertex corresponding the middle of the two specified vertices,
% returned with integer coordinates.
%
-spec get_integer_center( point(), point() ) -> integer_point().
get_integer_center( P1, P2 ) ->
roundify( get_center( P1, P2 ) ).
% Returns a point corresponding to the specified point P translated by the
% specified vector V.
%
-spec translate( point(), vector() ) -> point().
translate( _P={X,Y,Z}, _V={Vx,Vy,Vz} ) ->
{ X+Vx, Y+Vy, Z+Vz }.
% Section for sets of points.
% Vector section.
% Returns a vector V made from the specified two points: V=P2-P1.
%
-spec vectorize( point(), point() ) -> vector().
vectorize( _P1={X1,Y1,Z1}, _P2={X2,Y2,Z2} ) ->
{ X2-X1, Y2-Y1, Z2-Z1 }.
% Returns the square of the magnitude of the specified vector.
%
-spec square_magnitude( vector() ) -> linear:square_distance().
square_magnitude( _V={X,Y,Z} ) ->
X*X + Y*Y + Z*Z.
% Returns the magnitude of the specified vector.
%
-spec magnitude( vector() ) -> linear:distance().
magnitude( V ) ->
math:sqrt( square_magnitude( V ) ).
% Scales specified vector of specified factor.
%
-spec scale( vector(), factor() ) -> vector().
scale( _V={X,Y,Z}, Factor ) ->
{ Factor*X, Factor*Y, Factor*Z }.
% Returns the specified vector with an unit length (whose magnitude is thus 1).
%
-spec make_unit( vector() ) -> vector().
make_unit( V ) ->
case magnitude( V ) of
M when M < ?epsilon ->
throw( cannot_make_null_vector_unit );
M ->
scale( V, 1.0 / M )
end.
% Returns the dot product of the two specified vectors: D = V1.V2.
%
-spec dot_product( vector(), vector() ) -> float().
dot_product( _V1={X1,Y1,Z1}, _V2={X2,Y2,Z2} ) ->
X1*X2 + Y1*Y2 + Z1*Z2.
% Returns the sum of the two specified vectors: C = A + B.
%
-spec add( vector(), vector() ) -> vector().
add( { X1, Y1, Z1 }, { X2, Y2, Z2 } ) ->
{ X1 + X2, Y1 + Y2, Z1 + Z2 }.
% Returns the sum of all vectors in the specified list.
%
-spec add( [ vector() ] ) -> vector().
add( Vectors ) ->
lists:foldl( fun( { X, Y, Z }, _AccVec={ Xa, Ya, Za } ) ->
{ X + Xa, Y + Ya, Z + Za }
end,
_InitialAcc={ 0, 0, 0 },
_List=Vectors ).
% Textual conversion section.
% Returns a stringified representation of specified point or vector.
%
-spec to_string( point() | vector() ) -> string().
to_string( { X, Y, Z } ) ->
io_lib:format( "{ ~w, ~w, ~w }", [ X, Y, Z ] ).