1%% 2%% wings_shapes.erl -- 3%% 4%% This module contains definition of all primitive 5%% shapes that can be created, such as Cube, Sphere, 6%% and Grid. 7%% 8%% Copyright (c) 2001-2011 Bjorn Gustavsson 9%% 10%% See the file "license.terms" for information on usage and redistribution 11%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. 12%% 13%% 14 15-module(wings_shapes). 16-export([menu/2,command/2]). 17-export([transform_obj_dlg/0, transform_obj/4, transform_obj/2]). 18-export([tri_sphere/1, tri_cube/1, tri_disc/1]). 19-include("wings.hrl"). 20 21-import(lists, [map/2,seq/2,seq/3]). 22-import(math, [sqrt/1,cos/1,sin/1,pi/0]). 23 24menu(Parent, Pos) -> 25 wings_menu:popup_menu(Parent, Pos, shape, menu()). 26 27menu() -> 28 Opt = [option], 29 Menu0 = [ 30 {sphere,Opt}, 31 {cone,Opt}, 32 separator, 33 {tetrahedron,Opt}, 34 {octahedron,Opt}, 35 {octotoad, Opt}, 36 {dodecahedron,Opt}, 37 {icosahedron,Opt}, 38 separator, 39 {grid,Opt}, 40 separator, 41 {prim_name(light),{light,wings_light:light_types()}, 42 prim_help(light)}, 43 material, 44 image], 45 [prim_trans(Item) || Item <- Menu0]. 46 47 48prim_trans(separator) -> 49 separator; 50prim_trans({K,Opts}) when is_atom(K) -> 51 {prim_name(K),K,prim_help(K),Opts}; 52prim_trans(K) when is_atom(K) -> 53 {prim_name(K),K,prim_help(K)}; 54prim_trans(Other) -> Other. 55 56prim_name(tetrahedron) -> ?STR(prim_name,tetrahedron,"Tetrahedron"); 57prim_name(octahedron) -> ?STR(prim_name,octahedron,"Octahedron"); 58prim_name(octotoad) -> ?STR(prim_name,octotoad,"Octotoad"); 59prim_name(dodecahedron) -> ?STR(prim_name,dodecahedron,"Dodecahedron"); 60prim_name(icosahedron) -> ?STR(prim_name,icosahedron,"Icosahedron"); 61prim_name(cone) -> ?STR(prim_name,cone,"Cone"); 62prim_name(sphere) -> ?STR(prim_name,sphere,"Sphere"); 63prim_name(grid) -> ?STR(prim_name,grid,"Grid"); 64prim_name(light) -> ?STR(prim_name,light,"Light"); 65prim_name(material) -> ?STR(prim_name,material,"Material..."); 66prim_name(image) -> ?STR(prim_name,image,"Image..."). 67 68prim_help(tetrahedron) -> 69 ?STR(prim_help,tetrahedron,"Create a tetrahedron"); 70prim_help(octahedron) -> 71 ?STR(prim_help,octahedron,"Create an octahedron"); 72prim_help(octotoad) -> 73 ?STR(prim_help,octotoad,"Create an octotoad"); 74prim_help(dodecahedron) -> 75 ?STR(prim_help,dodecahedron,"Create a dodecahedron"); 76prim_help(icosahedron) -> 77 ?STR(prim_help,icosahedron,"Create an icosahedron"); 78prim_help(cone) -> 79 ?STR(prim_help,cone,"Create a cone"); 80prim_help(sphere) -> 81 ?STR(prim_help,sphere,"Create a sphere"); 82prim_help(grid) -> 83 ?STR(prim_help,grid,"Create a grid"); 84prim_help(light) -> 85 ?STR(prim_help,light,"Create a light"); 86prim_help(material) -> 87 ?STR(prim_help,material,"Create a material..."); 88prim_help(image) -> 89 ?STR(prim_help,image,"Create an image..."). 90 91 92command({tetrahedron,Ask}, St) -> tetrahedron(Ask, St); 93command({octahedron,Ask}, St) -> octahedron(Ask, St); 94command({octotoad,Ask}, St) -> octotoad(Ask, St); 95command({dodecahedron,Ask}, St) -> dodecahedron(Ask, St); 96command({icosahedron,Ask}, St) -> icosahedron(Ask, St); 97command({cone,Ask}, St) -> cone(Ask, St); 98command({sphere,Ask}, St) -> sphere(Ask, St); 99command({grid,Ask}, St) -> grid(Ask, St); 100command({light,Type}, St) -> wings_light:create(Type, St); 101command(material, St) -> wings_material:new(St); 102command(image, St) -> wings_image:create(St). 103 104build_shape(Prefix, Fs, Vs, #st{onext=Oid}=St) -> 105 We = wings_we:build(Fs, Vs), 106 Name = Prefix++integer_to_list(Oid), 107 wings_obj:new(Name, We, St). 108 109tetrahedron(Ask, St) when is_atom(Ask) -> 110 Q = [{label_column, [ 111 {?STR(tetrahedron,1,"Edge Length"), {text, 2.0,[{range,{0.0,infinity}}]}}] 112 }, 113 transform_obj_dlg()], 114 ask(tetrahedron, Ask, Q, St); 115tetrahedron([L|Transf], St) -> 116 Xi = L/2.0, 117 Hp = sqrt(3.0), 118 Li = Xi*Hp, 119 Zi = Xi/Hp, 120 Yi = L * sqrt(2.0/3.0), 121 Yf = Yi / 3.0, 122 123 Fs = [[2,1,0],[1,2,3],[1,3,0],[3,2,0]], 124 Vs0 = [{ 0.0, Yi-Yf, 0.0}, 125 { 0.0, -Yf, Li-Zi}, 126 { -Xi, -Yf, -Zi}, 127 { Xi, -Yf, -Zi}], 128 %% The string below is intentionally not translated. 129 Vs = transform_obj(Transf, Vs0), 130 build_shape("tetrahedron", Fs, Vs, St). 131 132octahedron(Ask, St) when is_atom(Ask) -> 133 Q = [{label_column, [ 134 {?STR(octahedron,1,"Height"), {text, 2.0,[{range,{0.0,infinity}}]}}] 135 }, 136 transform_obj_dlg()], 137 ask(octahedron, Ask, Q, St); 138octahedron([L|Transf], St) -> 139 Fs = [[2,4,0],[4,2,1],[4,3,0],[3,4,1],[5,2,0],[2,5,1],[3,5,0],[5,3,1]], 140 Vs0 = [{L,0.0,0.0},{-L,0.0,0.0},{0.0,L,0.0}, 141 {0.0,-L,0.0},{0.0,0.0,L},{0.0,0.0,-L}], 142 Vs = transform_obj(Transf, Vs0), 143 %% The string below is intentionally not translated. 144 build_shape("octahedron", Fs, Vs, St). 145 146octotoad(Ask, St) when is_atom(Ask) -> 147 Q = [{label_column, [ 148 {?STR(octotoad,1,"Height"), {text, 2.0,[{range,{0.0,infinity}}]}}] 149 }, 150 transform_obj_dlg()], 151 ask(octotoad, Ask, Q, St); 152octotoad([L|Transf], St) -> 153 Fs = [[2,3,1,0],[7,6,4,5],[9,8,0,1],[10,11,3,2], 154 [12,0,8],[12,14,2,0],[13,9,1],[14,10,2], 155 [15,3,11],[15,13,1,3],[16,17,5,4],[16,20,12,8], 156 [17,16,8,9],[18,19,11,10],[19,18,6,7],[19,23,15,11], 157 [20,16,4],[20,22,14,12],[21,5,17],[21,17,9,13], 158 [21,23,7,5],[22,6,18],[22,18,10,14],[22,20,4,6], 159 [23,19,7],[23,21,13,15]], 160 Third = 1/3, 161 Vs1 = [{1.0,Third,Third},{1.0,Third,-Third}, 162 {1.0,-Third,Third},{1.0,-Third,-Third}, 163 {-1.0,Third,Third},{-1.0,Third,-Third}, 164 {-1.0,-Third,Third},{-1.0,-Third,-Third}, 165 {Third,1.0,Third},{Third,1.0,-Third}, 166 {Third,-1.0,Third},{Third,-1.0,-Third}, 167 {Third,Third,1.0},{Third,Third,-1.0}, 168 {Third,-Third,1.0},{Third,-Third,-1.0}, 169 {-Third,1.0,Third},{-Third,1.0,-Third}, 170 {-Third,-1.0,Third},{-Third,-1.0,-Third}, 171 {-Third,Third,1.0},{-Third,Third,-1.0}, 172 {-Third,-Third,1.0},{-Third,-Third,-1.0}], 173 Scale = (L/2.0), 174 Vs0 = [{X*Scale,Y*Scale,Z*Scale} || {X,Y,Z} <- Vs1], 175 Vs = transform_obj(Transf, Vs0), 176 %% The string below is intentionally not translated. 177 build_shape("octotoad", Fs, Vs, St). 178 179dodecahedron(Ask, St) when is_atom(Ask) -> 180 Q = [{label_column, [ 181 {?STR(dodecahedron,1,"Edge Length"), {text, 1.0,[{range,{0.0,infinity}}]}} 182 ]}, 183 transform_obj_dlg()], 184 ask(dodecahedron, Ask, Q, St); 185dodecahedron([L|Transf], St) -> 186 Pn = sqrt(5.0), 187 Phi = (1.0 + Pn)/2.0, 188 Li = L/2.0 * Phi, 189 Ap = sqrt(2.0 / (3.0 + Pn)), 190 Alpha = Li*Ap, 191 Beta = Li*(1.0 + sqrt(6.0 / (3.0 + Pn) - 2.0 + 2.0 * Ap)), 192 Fs = [[0,1,9,16,5],[1,0,3,18,7],[1,7,11,10,9],[11,7,18,19,6], 193 [8,17,16,9,10],[2,14,15,6,19],[2,13,12,4,14],[2,19,18,3,13], 194 [3,0,5,12,13],[6,15,8,10,11],[4,17,8,15,14],[4,12,5,16,17]], 195 Vs0 = [{-Alpha,0.0,Beta},{Alpha,0.0,Beta},{-Li,-Li,-Li},{-Li,-Li,Li}, 196 {-Li,Li,-Li},{-Li,Li,Li},{Li,-Li,-Li}, 197 {Li,-Li,Li},{Li,Li,-Li}, 198 {Li,Li,Li},{Beta,Alpha,0.0},{Beta,-Alpha,0.0},{-Beta,Alpha,0.0}, 199 {-Beta,-Alpha,0.0},{-Alpha,0.0,-Beta},{Alpha,0.0,-Beta}, 200 {0.0,Beta,Alpha},{0.0,Beta,-Alpha},{0.0,-Beta,Alpha}, 201 {0.0,-Beta,-Alpha}], 202 Vs = transform_obj(Transf, Vs0), 203 %% The string below is intentionally not translated. 204 build_shape("dodecahedron", Fs, Vs, St). 205 206icosahedron(Ask, St) when is_atom(Ask) -> 207 Q = [{label_column, [ 208 {?STR(icosahedron,1,"Edge Length"), {text,2.0,[{range,{0.0,infinity}}]}} 209 ]}, 210 transform_obj_dlg()], 211 ask(icosahedron, Ask, Q, St); 212icosahedron([S|Transf], St) -> 213 T2 = pi()/10.0, 214 T4 = 2.0 * T2, 215 CT4 = cos(T4), 216 R = (S/2.0) / sin(T4), 217 H = CT4 * R, 218 Cx = R * cos(T2), 219 Cy = R * sin(T2), 220 H1 = sqrt(S * S - R * R), 221 H2 = abs(R) * sqrt(1.0 + 2.0*CT4), 222 Z2 = (H2 - H1) / 2.0, 223 Z1 = Z2 + H1, 224 225 Fs = [[0,2,1],[0,3,2],[0,4,3],[0,5,4],[0,1,5], 226 [4,7,6],[2,9,1],[5,8,7],[3,10,2],[9,8,1], 227 [4,6,3],[10,9,2],[7,4,5],[6,10,3],[1,8,5], 228 [11,10,6],[11,6,7],[11,7,8],[11,8,9],[11,9,10]], 229 230 Vs0 =[{0.0, Z1, 0.0}, 231 {R, Z2, 0.0 }, 232 {Cy, Z2, Cx }, 233 {-H, Z2, S/2.0 }, 234 {-H, Z2, -S/2.0 }, 235 {Cy, Z2, -Cx }, 236 {-R, -Z2, 0.0 }, 237 {-Cy, -Z2, -Cx }, 238 { H, -Z2, -S/2.0 }, 239 { H, -Z2, S/2.0 }, 240 {-Cy, -Z2, Cx }, 241 {0.0, -Z1, 0.0 }], 242 Vs = transform_obj(Transf, Vs0), 243 %% The string below is intentionally not translated. 244 build_shape("icosahedron", Fs, Vs, St). 245 246circle(N, Y, R) -> 247 Delta = pi()*2 / N, 248 [{R*cos(I*Delta), Y, R*sin(I*Delta)} || I <- lists:seq(0, N-1)]. 249 250ellipse(N, Y, {R1, R2}) -> 251 Delta = pi()*2 / N, 252 [{R1*cos(I*Delta), Y, R2*sin(I*Delta)} || I <- lists:seq(0, N-1)]. 253 254cone(Ask, St) when is_atom(Ask) -> 255 Q = [{label_column, [ 256 {?STR(cone,1,"Sections"), {text,16,[{range,{3,infinity}}]}}, 257 {?STR(cone,2,"Height"), {text,2.0,[{range,{0.0,infinity}}]}}, 258 {?STR(cone,3,"X Diameter"), {text,2.0,[{range,{0.0,infinity}}]}}, 259 {?STR(cone,5,"Z Diameter"), {text,2.0,[{range,{0.0,infinity}}]}} 260 ]}, 261 transform_obj_dlg()], 262 ask(cone, Ask, Q, St); 263cone([N,H,Dx,Dy|Transf], St) -> 264 Hi = H/2.0, 265 Di = Dx/2.0, 266 Dj = Dy/2.0, 267 Ns = lists:seq(0, N-1), 268 Lower = lists:seq(0, N-1), 269 C = ellipse(N, -Hi, {Di, Dj}), 270 Vs0 = C ++ [{0.0,Hi,0.0}], 271 Vs = transform_obj(Transf, Vs0), 272 Sides = [[N, (I+1) rem N, I] || I <- Ns], 273 Fs = [Lower | Sides], 274 %% The string below is intentionally not translated. 275 build_shape("cone", Fs, Vs, St). 276 277sphere_circles(Ns, Nl, Xi, Yi) -> 278 Delta = pi() / Nl, 279 PosAndRads= [{cos(I*Delta), sin(I*Delta)} || I <- lists:seq(1, Nl-1)], 280 Circles = [circle(Ns, Pos*Xi, Rad*Yi) || {Pos, Rad} <- PosAndRads], 281 lists:flatten(Circles). 282 283sphere_faces(Ns, Nl) -> 284 Lasti= (Nl-1)*Ns, 285 Topi= Lasti, 286 Boti= Topi+1, 287 Topf= [[Topi, (I+1) rem Ns, I] 288 || I <- lists:seq(0, Ns-1)], 289 Botf= [[Boti, Lasti - Ns + I, Lasti - Ns + (I+1) rem Ns] 290 || I <- lists:seq(0, Ns-1)], 291 Slices= [ [ [(I+1) rem Ns +J*Ns, 292 (I+1) rem Ns +J*Ns +Ns, 293 I +J*Ns +Ns, 294 I +J*Ns] 295 || I <- lists:seq(0, Ns-1)] 296 || J <- lists:seq(0, Nl-3)], 297 Topf ++ Botf ++ lists:append(Slices). 298 299sphere(Ask, St) when is_atom(Ask) -> 300 Q = [{label_column, [ 301 {?STR(sphere,1,"Sections"), {text,16,[{range,{3,infinity}}]}}, 302 {?STR(sphere,2,"Slices"), {text,8,[{range,{3,infinity}}]}}, 303 {?STR(sphere,3,"X Radial"), {text,2.0,[{range,{0.0,infinity}}]}}, 304 {?STR(sphere,4,"Y Radial"), {text,2.0,[{range,{0.0,infinity}}]}} 305 ]}, 306 transform_obj_dlg()], 307 ask(sphere, Ask, Q, St); 308sphere([Ns,Nl,Xr,Yr|Transf], St) -> 309 Xi = Xr/2.0, 310 Yi = Yr/2.0, 311 Fs = sphere_faces(Ns, Nl), 312 Vs0 = sphere_circles(Ns, Nl, Xi, Yi) ++ [{0.0, Xi, 0.0}, {0.0, -Xi, 0.0}], 313 Vs = transform_obj(Transf, Vs0), 314 %% The string below is intentionally not translated. 315 build_shape("sphere", Fs, Vs, St). 316 317grid(Ask, St) when is_atom(Ask) -> 318 Q = [{label_column, [ 319 {?STR(grid,1,"Rows/Cols"), {text,10,[{range,{1,infinity}}]}}, 320 {?STR(grid,2,"X Spacing"), {text,0.5,[{range,{0.0,infinity}}]}}, 321 {?STR(grid,3,"Z Spacing"), {text,0.5,[{range,{0.0,infinity}}]}}, 322 {?STR(grid,4,"Height"), {text,0.1,[{range,{0.0,infinity}}]}} 323 ]}, 324 transform_obj_dlg()], 325 ask(grid, Ask, Q, St); 326grid([Rc,Sp,Zp,Ht|Transf], St) -> 327 Vs0 = grid_vertices(Rc,Sp,Zp,Ht), 328 Vs = transform_obj(Transf, Vs0), 329 Fs = grid_faces(Rc), 330 %% The string below is intentionally not translated. 331 build_shape("grid", Fs, Vs, St). 332 333grid_vertices(Rc,Sp,Zp,Ht) -> 334 {Low,High} = case Rc rem 2 of 335 0 -> {-Rc,Rc}; 336 1 -> {-Rc,Rc} 337 end, 338 TopSeq = seq(Low, High, 2), 339 BotSeq = [Low,High], 340 VsBot = [{I*Sp/2,-Ht,J*Zp/2} || J <- BotSeq, I <- BotSeq], 341 [{I*Sp/2,Ht,J*Zp/2} || J <- TopSeq, I <- TopSeq] ++ VsBot. 342 343grid_faces(Rc) -> 344 TopSeq = seq(0, Rc-1), 345 Rsz = Rc+1, 346 TopFs = [grid_face(I, J, Rsz) || I <- TopSeq, J <- TopSeq], 347 ULv = Rsz*Rsz, 348 Fs0 = [[ULv,ULv+1,ULv+3,ULv+2]|TopFs], %Add bottom. 349 Fs1 = [[ULv+1,ULv|seq(0, Rsz-1)]|Fs0], %North 350 Fs2 = [[ULv,ULv+2|seq(Rsz*Rsz-Rsz, 0, -Rsz)]|Fs1], %West 351 Fs = [[ULv+2,ULv+3|seq(Rsz*Rsz-1, Rsz*Rsz-Rsz, -1)]|Fs2], % South. 352 [[ULv+3,ULv+1|seq(Rsz-1, Rsz*Rsz-1, Rsz)]|Fs]. %East 353 354grid_face(I, J, Rsz) -> 355 [Rsz*J+I+1, Rsz*J+I, 356 Rsz*(J+1)+I, Rsz*(J+1)+I+1]. 357 358ask(Shape, Bool, Qs, St) -> 359 Title = prim_help(Shape), 360 wings_dialog:dialog_preview({shape,Shape}, Bool, Title, Qs, St). 361 362transform_obj_dlg() -> 363 Hook = fun(Var, Val, Sto) -> 364 case Var of 365 ground -> 366 wings_dialog:enable(mov_y, Val=:=false, Sto); 367 _ -> ok 368 end 369 end, 370 {vframe,[ 371 {hframe,[ 372 {label_column, 373 [{wings_util:stringify(rotate), 374 {label_column, [ 375 {wings_util:stringify(x),{text, 0.0,[{key,rot_x},{range,{-360.0,360.0}}]}}, 376 {wings_util:stringify(y),{text, 0.0,[{key,rot_y},{range,{-360.0,360.0}}]}}, 377 {wings_util:stringify(z),{text, 0.0,[{key,rot_z},{range,{-360.0,360.0}}]}} 378 ]} 379 } 380 ]}, 381 {label_column, 382 [{wings_util:stringify(move), 383 {label_column, [ 384 {wings_util:stringify(x),{text, 0.0,[{key,mov_x},{range,{-360.0,360.0}}]}}, 385 {wings_util:stringify(y),{text, 0.0,[{key,mov_y},{range,{-360.0,360.0}}]}}, 386 {wings_util:stringify(z),{text, 0.0,[{key,mov_z},{range,{-360.0,360.0}}]}} 387 ]} 388 }]} 389 ],[{margin,false}]}, 390 {wings_util:stringify(put_on_ground), false, [{key,ground},{hook, Hook}]} 391 ],[{title,""},{margin,false}]}. 392 393transform_obj([{_,Rot_X},{_,Rot_Y},{_,Rot_Z},{_,Mov_X},{_,Mov_Y},{_,Mov_Z},{_,Ground}], Vs) -> 394 transform_obj({Rot_X,Rot_Y,Rot_Z}, {Mov_X,Mov_Y,Mov_Z}, Ground, Vs). 395transform_obj({0.0,0.0,0.0},{0.0,0.0,0.0},false, Vs) -> Vs; 396transform_obj({Rot_X,Rot_Y,Rot_Z}, {Mov_X,Mov_Y,Mov_Z}, Ground, Vs) -> 397 MrX = e3d_mat:rotate(Rot_X, {1.0,0.0,0.0}), 398 MrY = e3d_mat:rotate(Rot_Y, {0.0,1.0,0.0}), 399 MrZ = e3d_mat:rotate(Rot_Z, {0.0,0.0,1.0}), 400 Mr = e3d_mat:mul(MrZ, e3d_mat:mul(MrY, MrX)), 401 Y = case Ground of 402 true -> 403 {{_,Y1,_},{_,Y2,_}} = e3d_bv:box(Vs), 404 min(Y1,Y2)*-1.0; 405 false -> Mov_Y 406 end, 407 Mt = e3d_mat:translate(Mov_X,Y,Mov_Z), 408 Mat = e3d_mat:mul(Mt,Mr), 409 [e3d_mat:mul_point(Mat, V) || V <- Vs]. 410 411 412%%% Other shapes for internal rendering 413%% Replaces glu quadric functionality 414%% Returns the number of triangles and the triangles in a list 415%% or in a binary if option binary is true. 416%% Extra contents depends on Options. 417%% Options: 418%% subd Subdivision level default 1 419%% binary All output is binary default false 420%% ccw Winding order counter clockwise default true 421%% scale Scale output default 1, 422%% normals Add normals to the default false 423%% tgs Add tangent-normals to the extra list default false 424 425-type out() :: binary() | list(). 426 427-define(LLF, {-1.0, -1.0, 1.0}). %% Lower left front 428-define(LLB, {-1.0, -1.0, -1.0}). %% Lower left back 429-define(LRF, { 1.0, -1.0, 1.0}). %% Lower Right front 430-define(LRB, { 1.0, -1.0, -1.0}). 431-define(ULF, {-1.0, 1.0, 1.0}). %% Upper Left Front 432-define(ULB, {-1.0, 1.0, -1.0}). 433-define(URF, { 1.0, 1.0, 1.0}). 434-define(URB, { 1.0, 1.0, -1.0}). 435 436%% +Y 437%% ULB _____ URB 438%% /| -Z /| 439%% ULF /_____/ URF 440%% -X LLB|->|__ |_| LRB +X 441%% | / | / 442%% LLF |/_____|/ LRF 443%% +Z 444-define(cube, 445 [{?LLF, ?LRF, ?URF}, {?URF, ?ULF, ?LLF}, % Front 446 {?LRB, ?LLB, ?ULB}, {?ULB, ?URB, ?LRB}, % Back 447 {?ULF, ?URF, ?URB}, {?URB, ?ULB, ?ULF}, % Top 448 {?LLB, ?LRB, ?LRF}, {?LRF, ?LLF, ?LLB}, % Bottom 449 {?LLB, ?LLF, ?ULF}, {?ULF, ?ULB, ?LLB}, % Left 450 {?LRF, ?LRB, ?URB}, {?URB, ?URF, ?LRF} % Right 451 ]). 452 453-spec tri_cube(Opts::map()) -> 454 #{size=>NoOfFs::integer(), tris := Tris::out(), ns => Normals::out(), uvs := UVs::out(), tgs := Tgs::out()}. 455tri_cube(Opts) -> 456 Binary = maps:get(binary, Opts, false), 457 CCW = maps:get(ccw, Opts, true), 458 Scale = maps:get(scale, Opts, 1), 459 (maps:get(subd, Opts, 1) > 1) andalso error(not_yet_implented), 460 maps:get(normals, Opts, false) andalso error(not_yet_implented), 461 maps:get(uvs, Opts, false) andalso error(not_yet_implented), 462 maps:get(tgs, Opts, false) andalso error(not_yet_implented), 463 464 Tris = ?cube, 465 convert(Binary, Tris, CCW, Scale, false, false, false). 466 467-define(XPLUS, {1.0,0.0,0.0}). 468-define(XMIN, {-1.0,0.0,0.0}). 469-define(YPLUS, {0.0,1.0,0.0}). 470-define(YMIN, {0.0,-1.0,0.0}). 471-define(ZPLUS, {0.0,0.0,1.0}). 472-define(ZMIN, {0.0,0.0,-1.0}). 473-define(ZERO, {0.0,0.0,0.0}). 474-define(octahedron, 475 [{?ZPLUS, ?XPLUS, ?YPLUS}, 476 {?XMIN, ?ZPLUS, ?YPLUS}, 477 {?ZPLUS, ?XMIN, ?YMIN }, 478 {?ZPLUS, ?YMIN, ?XPLUS}, 479 {?YPLUS, ?XPLUS, ?ZMIN }, 480 {?XMIN, ?YPLUS, ?ZMIN }, 481 {?YMIN, ?XMIN, ?ZMIN }, 482 {?XPLUS, ?YMIN, ?ZMIN }]). 483 484-spec tri_sphere(Opts :: map()) -> 485 #{size := integer(), tris := out(), ns := out(), uvs := out(), tgs := out()}. 486tri_sphere(Opts) when is_map(Opts) -> 487 Subd = maps:get(subd, Opts, 1), 488 Binary = maps:get(binary, Opts, false), 489 CCW = maps:get(ccw, Opts, true), 490 Scale = maps:get(scale, Opts, 1), 491 Normal = maps:get(normals, Opts, false), 492 UV = maps:get(uvs, Opts, false), 493 Tg = maps:get(tgs, Opts, false), 494 %% Do the work 495 Tris = subd_sphere(1, Subd, ?octahedron), 496 convert(Binary, Tris, CCW, Scale, Normal, UV, Tg). 497 498-define(diamond, 499 [{?ZERO, ?YPLUS, ?XMIN}, 500 {?ZERO, ?XMIN, ?YMIN}, 501 {?ZERO, ?YMIN, ?XPLUS}, 502 {?ZERO, ?XPLUS, ?YPLUS}]). 503-spec tri_disc(Opts :: map()) -> 504 #{size := integer(), tris := out(), ns := out(), uvs := out(), tgs := out()}. 505tri_disc(Opts) -> 506 Binary = maps:get(binary, Opts, false), 507 CCW = maps:get(ccw, Opts, true), 508 Scale = maps:get(scale, Opts, 1), 509 Subd = maps:get(subd, Opts, 1), 510 511 maps:get(normals, Opts, false) andalso error(not_yet_implented), 512 maps:get(uvs, Opts, false) andalso error(not_yet_implented), 513 maps:get(tgs, Opts, false) andalso error(not_yet_implented), 514 Tris = subd_disc(1, Subd, ?diamond), 515 convert(Binary, Tris, CCW, Scale, false, false, false). 516 517subd_sphere(Level, MaxLevel, Sphere0) when Level < MaxLevel -> 518 Sphere = subd_sphere(Sphere0, []), 519 subd_sphere(Level+1, MaxLevel, Sphere); 520subd_sphere(_,_, Sphere) -> Sphere. 521 522%% 2 create a, b, c in the middle 523%% /\ Normalize a, b, c 524%% / \ 525%% c/____\ b Construct new triangles 526%% /\ /\ [0,b,a] 527%% / \ / \ [a,b,c] 528%% /____\/____\ [a,c,2] 529%% 0 a 1 [b,1,c] 530%% 531 532subd_sphere([{V0,V1,V2}|Rest], Acc) -> 533 A = e3d_vec:norm(midpoint(V0,V1)), 534 B = e3d_vec:norm(midpoint(V1,V2)), 535 C = e3d_vec:norm(midpoint(V0,V2)), 536 T1 = {V0,A,C}, 537 T2 = {A,B,C}, 538 T3 = {A,V1,B}, 539 T4 = {C,B,V2}, 540 subd_sphere(Rest, [T1,T2,T3,T4|Acc]); 541subd_sphere([],Acc) -> 542 Acc. 543 544midpoint({X1,Y1,Z1}, {X2,Y2,Z2}) -> 545 {(X1+X2)*0.5, (Y1+Y2)*0.5, (Z1+Z2)*0.5}. 546 547 548subd_disc(Level, Max, Tris0) when Level < Max -> 549 subd_disc(Level+1, Max, subd_disc(Tris0, [])); 550subd_disc(_, _, Tris) -> 551 Tris. 552 553subd_disc([{C,V1,V2}|Rest], Acc) -> 554 M = e3d_vec:norm(midpoint(V1,V2)), 555 subd_disc(Rest, [{C,V1,M}, {C,M,V2}|Acc]); 556subd_disc([], Acc) -> 557 Acc. 558 559convert(true, Tris, CCW, Scale, Normal, UV, Tg) -> 560 BinTris = list_to_bin(Tris, CCW, Scale), 561 Ns = if not Normal -> <<>>; 562 Scale =:= 1 -> [BinTris]; 563 true -> [list_to_bin(Tris, CCW, 1)] 564 end, 565 UVs = if not UV -> <<>>; 566 true -> list_to_bin(prepare_uvs(Tris), CCW, 1) 567 end, 568 Tgs = if not Tg -> <<>>; 569 true -> list_to_bin(prepare_tgs(Tris), CCW, 1) 570 end, 571 #{size => size(BinTris) div (9*4), tris => BinTris, ns => Ns, uvs => UVs, tgs => Tgs}; 572convert(false, Tris, CCW, Scale, Normal, UV, Tg) -> 573 Scaled = convert_list(Tris, CCW, Scale), 574 Ns = if not Normal -> []; 575 Scale =:= 1 -> Scaled; 576 true -> convert_list(Tris, CCW, 1) 577 end, 578 UVs = if not UV -> []; 579 true -> convert_list(prepare_uvs(Tris), CCW, 1) 580 end, 581 Tgs = if not Tg -> []; 582 true -> convert_list(prepare_tgs(Tris), CCW, 1) 583 end, 584 #{size => length(Tris), tris => Scaled, ns => Ns, uvs => UVs, tgs => Tgs}. 585 586 587%%%%%%%%%%%%%% Compute tangents for tri_sphere %%%%%%%%%%% 588prepare_tgs(Tris) -> 589 lists:foldr(fun({A,B,C}, Acc) -> 590 [{calc_tg(A),calc_tg(B),calc_tg(C)}|Acc] 591 end, [], Tris). 592 593calc_tg(?YPLUS) -> {0.0,0.0,-1.0,-1.0}; 594calc_tg(?YMIN) -> {0.0,0.0,-1.0,-1.0}; 595calc_tg(N) -> 596 Bi = e3d_vec:cross(N,{0.0,1.0,0.0}), 597 T = {X,Y,Z} = e3d_vec:norm(e3d_vec:cross(N, Bi)), 598 case e3d_vec:dot(e3d_vec:cross(N, T), Bi) < 0.0 of 599 true -> {X,Y,Z,1.0}; 600 false -> {X,Y,Z,-1.0} 601 end. 602 603%%%%%%%%%%%%%% Compute the UVs for tri_sphere %%%%%%%%%%% 604prepare_uvs(Tris) -> 605 lists:foldr(fun({A,B,C}, Acc) -> 606 {UA1,VA} = calc_uv(A), 607 {UB1,VB} = calc_uv(B), 608 {UC1,VC} = calc_uv(C), 609 UA0 = close_uv_loop(UA1,UB1,UC1), 610 UB0 = close_uv_loop(UB1,UC1,UA1), 611 UC0 = close_uv_loop(UC1,UA1,UB1), 612 UA = fix_top_issue(A, UA0, UB0, UC0), 613 UB = fix_top_issue(B, UB0, UC0, UA0), 614 UC = fix_top_issue(C, UC0, UA0, UB0), 615 [{{UA,VA},{UB,VB},{UC,VC}}|Acc] 616 end, [], Tris). 617 618calc_uv({X,Y,Z}) -> 619 U = 0.5 + math:atan2(X, Z) / (2 * math:pi()), 620 V = 0.5 + math:asin(Y) / math:pi(), 621 {U,V}. 622 623fix_top_issue(V, _, UB, UC) when V=:=?YPLUS; V=:=?YMIN -> (UB+UC)/2.0; 624fix_top_issue(_, UA, _, _) -> UA. 625 626close_uv_loop(1.0, UVuB, UVuC) when (UVuB < 0.5); (UVuC < 0.5) -> 0.0; 627close_uv_loop(UVuA, _, _) -> UVuA. 628 629%%%%%%%%%%%%%% Converters %%%%%%%%%%% 630list_to_bin([{{_,_},_,_}|_]=Tris, CCW, Scale) -> 631 << <<(U):?F32,(V):?F32>> || Fs <- Tris, {U,V} <- conv_tuple_bin(Fs,CCW,Scale) >>; 632list_to_bin(Tris, CCW, Scale) -> 633 << <<(X):?F32,(Y):?F32,(Z):?F32>> || Fs <- Tris, {X,Y,Z} <- conv_tuple_bin(Fs,CCW,Scale) >>. 634 635conv_tuple_bin(Fs, CCW, Size) -> 636 [scale(V, Size) || V <- conv_tuple_list(Fs,CCW)]. 637 638convert_list(List, CCW, 1) -> 639 [V || Fs <- List, V <- conv_tuple_list(Fs,CCW)]; 640convert_list(List, CCW, Size) -> 641 [scale(V, Size) || Fs <- List, V <- conv_tuple_list(Fs,CCW)]. 642 643conv_tuple_list({V1,V2,V3}, true) -> [V1,V2,V3]; 644conv_tuple_list({V1,V2,V3}, false) -> [V1,V3,V2]. 645 646scale({X,Y,Z},Size) -> {(X*Size),(Y*Size),(Z*Size)}; 647scale({_,_}=UV,_) -> UV. 648