1%% 2%% e3d_transf.erl -- 3%% 4%% More transformation matrix utilities 5%% 6%% Copyright (c) 2010-2011 Dan Gudmundsson 7%% 8%% See the file "license.terms" for information on usage and redistribution 9%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. 10%% 11%% $Id$ 12%%% 13%%% @doc More transformation matrix utilities 14%%% 15%%% All of the matrix operations operates in the following order 16%%% I = identity(), 17%%% T = translate(I, Vec), 18%%% R = rotate(T, Vec), 19%%% Matrix = Rotate(Translate()) 20%%% 21%%% Also using opengl right handed coordinate system 22%%% @end 23-module(e3d_transform). 24 25-export([%% Initializes matrices 26 identity/0, init/1, 27 lookat/3, ortho/2, ortho/6, perspective/3, perspective/4, pick/5, frustum/6, 28 %% Get the actual matrices 29 matrix/1, inv_matrix/1, 30 %% Transform the matrices 31 inverse/1, translate/2, rotate/2, rotate/3, scale/2, mul/1, mul/2, 32 %% (un)Projection 33 project/4, unproject/4 34 ]). 35 36-include("e3d.hrl"). 37 38-type transform() :: #e3d_transf{}. 39 40-type matrix() :: e3d_mat:matrix(). 41-type vector() :: e3d_vec:vector(). 42-type point() :: e3d_vec:point(). 43 44 45%%%------------------------------------------------------------------- 46%%-------------------------------------------------------------------- 47%% @doc Returns the identity transform 48%% @end 49%%-------------------------------------------------------------------- 50-spec identity() -> transform(). 51identity() -> 52 #e3d_transf{}. 53 54%%-------------------------------------------------------------------- 55%% @doc Initializes transform from matrix mat 56%% @end 57%%-------------------------------------------------------------------- 58-spec init(matrix()) -> transform(). 59init(Mat) when tuple_size(Mat) =:= 12 -> 60 init(e3d_mat:expand(Mat)); 61init(Mat) -> 62 #e3d_transf{mat=Mat, inv=e3d_mat:invert(Mat)}. 63 64%%-------------------------------------------------------------------- 65%% @doc Returns the matrix 66%% @end 67%%-------------------------------------------------------------------- 68-spec matrix(transform()) -> matrix(). 69matrix(#e3d_transf{mat=M}) -> e3d_mat:expand(M). 70 71%%-------------------------------------------------------------------- 72%% @doc Returns the inverse matrix 73%% @end 74%%-------------------------------------------------------------------- 75-spec inv_matrix(transform()) -> matrix(). 76inv_matrix(#e3d_transf{inv=I}) -> e3d_mat:expand(I). 77 78%%%------------------------------------------------------------------- 79 80 81%%-------------------------------------------------------------------- 82%% @doc Inverses the transform 83%% @end 84%%-------------------------------------------------------------------- 85-spec inverse(transform()) -> transform(). 86inverse(#e3d_transf{mat=M, inv=I}) -> 87 #e3d_transf{mat=I, inv=M}. 88 89%%-------------------------------------------------------------------- 90%% @doc Translates the matrix with vector 91%% @end 92%%-------------------------------------------------------------------- 93-spec translate(transform(), vector()) -> transform(). 94translate(#e3d_transf{mat=M,inv=I}, {Dx,Dy,Dz}) -> 95 #e3d_transf{mat = e3d_mat:mul(M, e3d_mat:translate(Dx,Dy,Dz)), 96 inv = e3d_mat:mul(e3d_mat:translate(-Dx,-Dy,-Dz), I)}. 97 98%%-------------------------------------------------------------------- 99%% @doc Rotates the matrix with rotation matrix 100%% @end 101%%-------------------------------------------------------------------- 102-spec rotate(transform(), matrix()) -> transform(). 103rotate(#e3d_transf{mat=M,inv=I}, Rot) 104 when tuple_size(Rot) =:= 12; tuple_size(Rot) =:= 16 -> 105 #e3d_transf{mat = e3d_mat:mul(M, Rot), 106 inv = e3d_mat:mul(e3d_mat:transpose(Rot), I)}. 107 108%%-------------------------------------------------------------------- 109%% @doc Rotates the matrix with angle (in degrees) and direction 110%% @end 111%%-------------------------------------------------------------------- 112-spec rotate(transform(), number(), vector()) -> transform(). 113rotate(Mat = #e3d_transf{}, A, Vec) -> 114 rotate(Mat, e3d_mat:rotate(A,Vec)). 115 116%%-------------------------------------------------------------------- 117%% @doc Scales the matrix with {ScaleX, ScaleY, ScaleZ} 118%% @end 119%%-------------------------------------------------------------------- 120-spec scale(transform(), vector()) -> transform(). 121scale(#e3d_transf{mat=M,inv=I}, {X,Y,Z}) -> 122 #e3d_transf{mat = e3d_mat:mul(M, e3d_mat:scale(X,Y,Z)), 123 inv = e3d_mat:mul(e3d_mat:scale(1/X,1/Y,1/Z), I)}. 124 125%%---------------------------------------------------------------------- 126%% @doc Multiplies the current matrix (at right) with new Mat (at left) 127%% Trans(Vec) = Mat(Current(Vec)) 128%% @end 129%%---------------------------------------------------------------------- 130-spec mul(transform(), transform()) -> transform(). 131mul(#e3d_transf{mat=M1,inv=I1}, #e3d_transf{mat=M2,inv=I2}) -> 132 #e3d_transf{mat = e3d_mat:mul(M1, M2), inv = e3d_mat:mul(I2, I1)}. 133 134%%-------------------------------------------------------------- 135%% mul([Rx,Ry,Rz]) = mul([mul(Ry,Rx),Rz]) 136%%-------------------------------------------------------------- 137-spec mul([transform()]) -> transform(). 138mul([#e3d_transf{}=A,#e3d_transf{}=B | T ]) -> mul([mul(B,A) | T]); 139mul([#e3d_transf{}=A]) -> A. 140 141%%---------------------------------------------------------------------- 142%% @doc Transforms point to window coordinates 143%% 144%% @end 145%%---------------------------------------------------------------------- 146-spec project(Point::point(), 147 ModelView::transform(), 148 Projection::transform(), ViewPort::{integer(), integer(), integer(), integer()}) -> 149 point(). 150project(Point, #e3d_transf{mat=MV}, #e3d_transf{mat=Pr}, {V0,V1,V2,V3}) -> 151 P0 = e3d_mat:mul_point(MV, Point), 152 P1 = e3d_mat:mul_point(Pr, P0), 153 {Px,Py,Pz} = e3d_vec:add_prod({0.5,0.5,0.5}, P1, 0.5), 154 {Px*V2+V0, Py*V3+V1, Pz}. 155 156%%---------------------------------------------------------------------- 157%% @doc Maps windows coordinates to object coordinates 158%% 159%% @end 160%%---------------------------------------------------------------------- 161-spec unproject(WinPoint::e3d_vec:point(), 162 ModelView::transform(), 163 Projection::transform(), ViewPort::{integer(), integer(), integer(), integer()}) -> 164 e3d_vec:point(). 165unproject({WinX,WinY,WinZ}, #e3d_transf{inv=InvMV}, #e3d_transf{inv=InvPr}, {V0,V1,V2,V3}) -> 166 %% From windows coords to 0.0-1.0 167 Point0 = {(WinX-V0)/V2, (WinY-V1)/V3, WinZ}, 168 %% From 0.0-1.0 -> -1, 1 169 Point = e3d_vec:add_prod({-1.0,-1.0,-1.0}, Point0, 2.0), 170 %% ModelView Projection matrix 171 MVPM = e3d_mat:mul(InvMV,InvPr), 172 e3d_mat:mul_point(MVPM, Point). 173 174%%%------------------------------------------------------------------- 175 176%%-------------------------------------------------------------------- 177%% @doc Generates a world to camera transformation 178%% @end 179%%-------------------------------------------------------------------- 180-spec lookat(point(), vector(), vector()) -> transform(). 181lookat(Pos, Look, Up) -> 182 Dir = e3d_vec:norm_sub(Look, Pos), 183 Right = e3d_vec:norm(e3d_vec:cross(Dir, e3d_vec:norm(Up))), 184 NewUp = e3d_vec:norm(e3d_vec:cross(Right, Dir)), 185 AsList = [tuple_to_list(Right), 0.0, 186 tuple_to_list(NewUp), 0.0, 187 tuple_to_list(e3d_vec:neg(Dir)), 0.0, 188 0.0, 0.0, 0.0, 1.0], 189 CamToWorld = list_to_tuple(lists:flatten(AsList)), 190 WorldToCam = e3d_mat:invert(CamToWorld), 191 translate(#e3d_transf{mat=WorldToCam,inv=CamToWorld}, e3d_vec:neg(Pos)). 192 193%%-------------------------------------------------------------------- 194%% @doc Generates a ortho transformation 195%% @end 196%%-------------------------------------------------------------------- 197-spec ortho(float(), float()) -> transform(). 198ortho(Near, Far) -> 199 ortho(-1.0, 1.0, -1.0, 1.0, Near, Far). 200 201-spec ortho(float(), float(), float(), float(), float(), float()) -> transform(). 202ortho(Left, Right, Bottom, Top, Near, Far) -> 203 O = 0.0, 204 IDx = 1/(Right-Left), 205 IDy = 1/(Top-Bottom), 206 IDz = 1/(Far-Near), 207 208 Mat0 = {2.0, O, O, 209 O, 2.0, O, 210 O, O, -2.0, 211 O, O, O}, 212 %% Do this in 3 steps to avoid inverse calculation problems 213 Mat1 = scale(init(Mat0), {IDx,IDy,IDz}), 214 Tv = {-(Right+Left)*IDx, -(Top+Bottom)*IDy, -(Far+Near)*IDz}, 215 Trans = translate(identity(), Tv), 216 mul(Trans, Mat1). 217 218%%-------------------------------------------------------------------- 219%% @doc Generates a perspective transformation 220%% Fov = Field Of View (in degrees) 221%% Projects from camera space: Z = {-near, -far} 222%% to screen space: Z' = {0.0, 1.0} 223%% @end 224%%-------------------------------------------------------------------- 225-spec perspective(Fov::float(), 226 Near::float(), Far::float()) -> transform(). 227perspective(Fov, Near, Far) -> 228 perspective(Fov, 1.0, Near, Far). 229 230-spec perspective(Fov::float(), Aspect::float(), 231 Near::float(), Far::float()) -> transform(). 232perspective(Fov, Aspect, Near, Far) -> 233 T = 1.0 / math:tan((Fov*math:pi()/180)/2.0), 234 %% Perform projective divide 235 D = 1.0 / (Far-Near), %% Inverted Denom 236 I = 1.0, O = 0.0, 237 Persp = {I/Aspect, O, O, O, 238 O, I, O, O, 239 O, O, -(Near+Far)*D, -I, 240 O, O, -2.0*Far*Near*D, O}, 241 InvPersp = e3d_mat:invert(Persp), 242 scale(#e3d_transf{mat=Persp, inv=InvPersp}, {T,T,1.0}). 243 244%%-------------------------------------------------------------------- 245%% @doc Generates a perspective projection matrix 246%% @end 247%%-------------------------------------------------------------------- 248 249-spec frustum(Left::float(), Right::float(), 250 Bottom::float(), Top::float(), 251 Near::float(), Far::float()) -> 252 transform(). 253frustum(Left, Right, Bottom, Top, Near, Far) -> 254 InvX = 1/(Right-Left), 255 InvY = 1/(Top-Bottom), 256 InvZ = 1/(Far-Near), 257 A = (Right+Left)*InvX, 258 B = (Top+Bottom)*InvY, 259 C = -(Far+Near)*InvZ, 260 D = -(2*Far*Near)*InvZ, 261 I = 1.0, O = 0.0, 262 E = 2*Near*InvX, 263 F = 2*Near*InvY, 264 265 Persp = {E, O, O, O, 266 O, F, O, O, 267 A, B, C, -I, 268 O, O, D, O}, 269 InvPersp = e3d_mat:invert(Persp), 270 #e3d_transf{mat=Persp, inv=InvPersp}. 271 272%%-------------------------------------------------------------------- 273%% @doc Generates a pick matrix transformation 274%% Equiv to glu:pickMatrix/5 275%% @end 276%%-------------------------------------------------------------------- 277-spec pick(X::float(), Y::float(), 278 Width::float(), Height::float(), 279 Viewport::{integer(),integer(),integer(),integer()} 280 ) -> transform(). 281pick(X, Y, W, H, {X0,Y0,X1,Y1}) -> 282 Sx = X1 / W, 283 Sy = Y1 / H, 284 Tx = (X1+2.0*(X0-X)) / W, 285 Ty = (Y1+2.0*(Y0-Y)) / H, 286 I = 1.0, O = 0.0, 287 Pick = {Sx, O, O, O, 288 O, Sy, O, O, 289 O, O, I, O, 290 Tx,Ty, O, I}, 291 init(Pick). 292