From 5e24166ac839940fb074d879d36c6b9ced8fd6a1 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Mon, 16 Mar 2026 22:48:54 -0500 Subject: [PATCH] collada: render materials/textures --- Makefile | 1 + data/scenes/ship20.cpp | 427 -------------------------------- data/scenes/ship20/ship20.cpp | 2 +- data/scenes/ship20/shipple2.dds | Bin 0 -> 524416 bytes include/collada/scene.h | 8 + include/dds.h | 127 ++++++++++ include/dds_validate.h | 10 + include/glad/gl.h | 14 +- include/opengl.h | 2 + shader/collada/generic.frag | 61 ++++- shader/collada/static.vert | 8 +- src/collada/scene.cpp | 139 ++++++++++- src/dds_validate.cpp | 69 ++++++ src/gl.c | 3 +- src/{opengl.c => opengl.cpp} | 49 +++- src/test.cpp | 8 +- 16 files changed, 475 insertions(+), 453 deletions(-) delete mode 100644 data/scenes/ship20.cpp create mode 100644 data/scenes/ship20/shipple2.dds create mode 100644 include/dds.h create mode 100644 include/dds_validate.h create mode 100644 src/dds_validate.cpp rename src/{opengl.c => opengl.cpp} (71%) diff --git a/Makefile b/Makefile index 6cc7f0e..7a95fae 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,7 @@ OBJS = \ src/line_art.o \ src/boids.o \ src/boids_scene.o \ + src/dds_validate.o \ src/collada/scene.o \ src/collada/effect.o \ src/collada/node_state.o \ diff --git a/data/scenes/ship20.cpp b/data/scenes/ship20.cpp deleted file mode 100644 index 9297e2f..0000000 --- a/data/scenes/ship20.cpp +++ /dev/null @@ -1,427 +0,0 @@ -#include "collada/types.h" - -namespace ship20 { - - using namespace collada::types; - -light const light_environmentambientlight = { - .type = light_type::AMBIENT, - .color = { 0.0f, 0.0f, 0.0f }, -}; - -light const light_omni002_light = { - .type = light_type::POINT, - .color = { 1.0f, 1.0f, 1.0f }, -}; - -light const light_omni003_light = { - .type = light_type::POINT, - .color = { 1.0f, 1.0f, 1.0f }, -}; - -// shipple2_png -image const image_shipple2_png = { - .resource_name = "_0_SHIPPLE2_PNG", -}; - -image const * const images[] = { - &image_shipple2_png, -}; - -effect const effect_diffusetexture = { - .type = effect_type::BLINN, - .blinn = { - .emission = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .ambient = { - .type = color_or_texture_type::COLOR, - .color = {0.588f, 0.588f, 0.588f, 1.0f}, - }, - .diffuse = { - .type = color_or_texture_type::TEXTURE, - .texture = { .image_index = 0 }, // shipple2_png - }, - .specular = { - .type = color_or_texture_type::COLOR, - .color = {0.5f, 0.5f, 0.5f, 1.0f}, - }, - .shininess = 10.0f, - .reflective = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .reflectivity = 0.0f, - .transparent = { - .type = color_or_texture_type::COLOR, - .color = {1.0f, 1.0f, 1.0f, 1.0f}, - }, - .transparency = 1.0f, - .index_of_refraction = 0.0f, - } -}; - -effect const effect_cyanengine = { - .type = effect_type::BLINN, - .blinn = { - .emission = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.9647059f, 1.0f, 1.0f}, - }, - .ambient = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .diffuse = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .specular = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .shininess = 10.0f, - .reflective = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .reflectivity = 0.0f, - .transparent = { - .type = color_or_texture_type::COLOR, - .color = {1.0f, 1.0f, 1.0f, 1.0f}, - }, - .transparency = 1.0f, - .index_of_refraction = 0.0f, - } -}; - -effect const effect_emissivetexture = { - .type = effect_type::BLINN, - .blinn = { - .emission = { - .type = color_or_texture_type::TEXTURE, - .texture = { .image_index = 0 }, // shipple2_png - }, - .ambient = { - .type = color_or_texture_type::COLOR, - .color = {0.588f, 0.588f, 0.588f, 1.0f}, - }, - .diffuse = { - .type = color_or_texture_type::TEXTURE, - .texture = { .image_index = 0 }, // shipple2_png - }, - .specular = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .shininess = 10.0f, - .reflective = { - .type = color_or_texture_type::COLOR, - .color = {0.0f, 0.0f, 0.0f, 1.0f}, - }, - .reflectivity = 0.0f, - .transparent = { - .type = color_or_texture_type::COLOR, - .color = {1.0f, 1.0f, 1.0f, 1.0f}, - }, - .transparency = 1.0f, - .index_of_refraction = 0.0f, - } -}; - -material const material_diffusetexture_material = { - .effect = &effect_diffusetexture, -}; - -material const material_cyanengine_material = { - .effect = &effect_cyanengine, -}; - -material const material_emissivetexture_material = { - .effect = &effect_emissivetexture, -}; - -input_element const input_elements_position_0_3_normal_0_3_texcoord_0_3[] = { - { - .semantic = "POSITION", - .semantic_index = 0, - .format = input_format::FLOAT3, - }, - { - .semantic = "NORMAL", - .semantic_index = 0, - .format = input_format::FLOAT3, - }, - { - .semantic = "TEXCOORD", - .semantic_index = 0, - .format = input_format::FLOAT3, - }, -}; - -triangles const triangles_geom_ship[] = { - { - .count = 2949, // triangles - .index_offset = 0, // indices - .inputs_index = 0, // index into inputs_list - }, - { - .count = 60, // triangles - .index_offset = 8847, // indices - .inputs_index = 0, // index into inputs_list - }, - { - .count = 239, // triangles - .index_offset = 9027, // indices - .inputs_index = 0, // index into inputs_list - }, -}; - -geometry const geometry_geom_ship = { - .mesh = { - .triangles = triangles_geom_ship, - .triangles_count = 3, - - .vertex_buffer_offset = 0, - .vertex_buffer_size = 133272, - - .index_buffer_offset = 0, - .index_buffer_size = 38976, - } -}; - -geometry const * const geometries[] = { - &geometry_geom_ship, -}; - -transform const transforms_node_environmentambientlight[] = { -}; - -instance_geometry const instance_geometries_node_environmentambientlight[] = { -}; - -instance_controller const instance_controllers_node_environmentambientlight[] = { -}; - -instance_light const instance_lights_node_environmentambientlight[] = { - { - .light = &light_environmentambientlight, - } -}; - -channel const * const node_channels_node_environmentambientlight[] = {}; - -node const node_node_environmentambientlight = { - .parent_index = -1, - - .type = node_type::NODE, - - .transforms = transforms_node_environmentambientlight, - .transforms_count = 0, - - .instance_geometries = instance_geometries_node_environmentambientlight, - .instance_geometries_count = 0, - - .instance_controllers = instance_controllers_node_environmentambientlight, - .instance_controllers_count = 0, - - .instance_lights = instance_lights_node_environmentambientlight, - .instance_lights_count = 1, - - .channels = node_channels_node_environmentambientlight, - .channels_count = 0, -}; - -transform const transforms_node_ship[] = { - { - .type = transform_type::ROTATE, - .rotate = {0.0f, 0.0f, 1.0f, -180.0f}, - }, -}; - -instance_material const instance_geometry_instance_materials_node_ship_0[] = { - { - .element_index = 1, // an index into mesh.triangles - .material = &material_cyanengine_material, - - .emission = { .input_set = -1 }, - .ambient = { .input_set = -1 }, - .diffuse = { .input_set = -1 }, - .specular = { .input_set = -1 }, - }, - { - .element_index = 0, // an index into mesh.triangles - .material = &material_diffusetexture_material, - - .emission = { .input_set = -1 }, - .ambient = { .input_set = -1 }, - .diffuse = { .input_set = 0 }, - .specular = { .input_set = -1 }, - }, - { - .element_index = 2, // an index into mesh.triangles - .material = &material_emissivetexture_material, - - .emission = { .input_set = 0 }, - .ambient = { .input_set = -1 }, - .diffuse = { .input_set = 0 }, - .specular = { .input_set = -1 }, - }, -}; - -instance_geometry const instance_geometries_node_ship[] = { - { - .geometry = &geometry_geom_ship, - - .instance_materials = instance_geometry_instance_materials_node_ship_0, - .instance_materials_count = 3, - }, -}; - -instance_controller const instance_controllers_node_ship[] = { -}; - -instance_light const instance_lights_node_ship[] = { -}; - -channel const * const node_channels_node_ship[] = { -}; - -node const node_node_ship = { - .parent_index = -1, - - .type = node_type::NODE, - - .transforms = transforms_node_ship, - .transforms_count = 1, - - .instance_geometries = instance_geometries_node_ship, - .instance_geometries_count = 1, - - .instance_controllers = instance_controllers_node_ship, - .instance_controllers_count = 0, - - .instance_lights = instance_lights_node_ship, - .instance_lights_count = 0, - - .channels = node_channels_node_ship, - .channels_count = 0, -}; - -transform const transforms_node_omni002[] = { - { - .type = transform_type::TRANSLATE, - .translate = {-286.5521f, 395.7583f, 161.5579f}, - }, -}; - -instance_geometry const instance_geometries_node_omni002[] = { -}; - -instance_controller const instance_controllers_node_omni002[] = { -}; - -instance_light const instance_lights_node_omni002[] = { - { - .light = &light_omni002_light, - } -}; - -channel const * const node_channels_node_omni002[] = { -}; - -node const node_node_omni002 = { - .parent_index = -1, - - .type = node_type::NODE, - - .transforms = transforms_node_omni002, - .transforms_count = 1, - - .instance_geometries = instance_geometries_node_omni002, - .instance_geometries_count = 0, - - .instance_controllers = instance_controllers_node_omni002, - .instance_controllers_count = 0, - - .instance_lights = instance_lights_node_omni002, - .instance_lights_count = 1, - - .channels = node_channels_node_omni002, - .channels_count = 0, -}; - -transform const transforms_node_omni003[] = { - { - .type = transform_type::TRANSLATE, - .translate = {333.2103f, -314.4593f, 161.5578f}, - }, -}; - -instance_geometry const instance_geometries_node_omni003[] = { -}; - -instance_controller const instance_controllers_node_omni003[] = { -}; - -instance_light const instance_lights_node_omni003[] = { - { - .light = &light_omni003_light, - } -}; - -channel const * const node_channels_node_omni003[] = { -}; - -node const node_node_omni003 = { - .parent_index = -1, - - .type = node_type::NODE, - - .transforms = transforms_node_omni003, - .transforms_count = 1, - - .instance_geometries = instance_geometries_node_omni003, - .instance_geometries_count = 0, - - .instance_controllers = instance_controllers_node_omni003, - .instance_controllers_count = 0, - - .instance_lights = instance_lights_node_omni003, - .instance_lights_count = 1, - - .channels = node_channels_node_omni003, - .channels_count = 0, -}; - -node const * const nodes[] = { - &node_node_environmentambientlight, // 0 - &node_node_ship, // 1 - &node_node_omni002, // 2 - &node_node_omni003, // 3 -}; - -inputs const inputs_list[] = { - { - .elements = input_elements_position_0_3_normal_0_3_texcoord_0_3, - .elements_count = 3, - }, -}; - -types::descriptor const descriptor = { - .nodes = nodes, - .nodes_count = (sizeof (nodes)) / (sizeof (nodes[0])), - - .inputs_list = inputs_list, - .inputs_list_count = (sizeof (inputs_list)) / (sizeof (inputs_list[0])), - - .images = images, - .images_count = (sizeof (images)) / (sizeof (images[0])), - - .position_normal_texture_buffer = "RES_SCENES_SHIP20_VTX", - .joint_weight_buffer = "RES_SCENES_SHIP20_VJW", - .index_buffer = "RES_SCENES_SHIP20_IDX", -}; - -} diff --git a/data/scenes/ship20/ship20.cpp b/data/scenes/ship20/ship20.cpp index 203f876..0203f61 100644 --- a/data/scenes/ship20/ship20.cpp +++ b/data/scenes/ship20/ship20.cpp @@ -23,7 +23,7 @@ light const light_omni003_light = { // shipple2_png image const image_shipple2_png = { - .resource_name = "_0_SHIPPLE2_PNG", + .resource_name = "data/scenes/ship20/shipple2.dds", }; image const * const images[] = { diff --git a/data/scenes/ship20/shipple2.dds b/data/scenes/ship20/shipple2.dds new file mode 100644 index 0000000000000000000000000000000000000000..e389313b028086d8eba62886d5908b0cd1ca46bd GIT binary patch literal 524416 zcmeFaf2<|Pb>G>SGuNhQYG;gn9SPY#A~Mp3q$0&yW3W+@@${qkyyn7D?zM08nG~&; z6<3JN>~3TvQUaUs5T+g5vfu=UIEIB}!O&tfe6%b_hC(mp5h>9Fk7p#%h%}(BUo>n= zqDV@lLp{)(nKyU8-*f8p?OT2O_WjZK*L(E<@=SGCS69{TuFpBAPF3G@(~rOIt|*GW z_wbcbbP)gGe-ZwTzXtyM-){cLtvB6t(~tk;s}9JqVgGv_PV(}bexmc1f%y&>kDmK) z=gxNV7i8#%@EnBqAdCZH90=n;7ze^Q5XONp4uo+aj00gD2;)E)2f{cI#(^*ngmEB@ z17REp<3Jb(!Z;AdfiMn)aUhHXVH^nKKo|$YI1t8xFb;%qAdCZH90=n;7ze^Q5XONp z4uo+aj00gD2;)E)2f{cI#(^*ngmEB@17REp<3Jb(!Z;AdfiMn)aUhHXVH^nKKo|$Y zI1t8xFb;%qAdCZH90=n;7ze^Q5XONp4uo+aj00gD2;)E)2f{co=W*cdo9@AGk*#wo zQPw(4|!|y-m-#_dN8ump6c*A=a-n*cG?Sc0| zIo?s8cKSKqQhr0ElX833*Gat_jYi$$(Jwszk*E252RT_keB|lRexZdw$|nB&e&q24 zf0Qx){PULn>=!=!1?r8K_9d_H;ExiIL1WPQVfFCySEWDl^trRFciqohC@=6d@N^IG zbXn}9KCbuM<;%?TQS}9KA8JWV*}w-e>K;Ft_XFm+Yp{Z%#KqY;-XpHg=iz=yllyS@&+YvQd>_vDs{RT5pYiwx?sNWRXKQDR@ex5TcRx^1 zW1LIi{*2#?RpWl%u6O=>V1D0nzD%DX|C`PF z&-i^(0}!8svoi@bx>M zX8iy6LUR3Ip&xOa{%fA6U$k)E_5U5u-y!QT%g*z9uK%Ot`oBUx93NVbaewEpFUk79 z!2iJi!2jjfe{%l)!2NPNf0*B&<9gu#gd851Z*}Fb;x5hy{!d}Q1^%x#|IcCXWb--Edy z?EMk#ebWOL<@dMurC>+a#l`(nPT>D+#uXc{1OHc^|I~lUYv6mG5=oJb^>s}}B9W4|=O@V7yr1Mg41MERkdy8EyC}bZ;D1eihQR;X zj4w8B2mbE`{zv3F@GX4KN#Q!ust-7>8_yx-JoE+e3m{v==@A`DoR_ragOqaQxII5X z*7JS_|Nd0_`j_cP4*Xwj{@d?LUc+TC+;%c? za@|R6Z#wXN>YeX+>BOb`%CY+fIo#Edo*tU3iowhKdjrUkNc(%h}}QOb^d|>f&XKz^9b?(E5+Sq*%v9S z|DST*+_;>sJAdH*O#J+L?qi*|e$R(~tGa)`G0O}7|0(r?VE@n7ZVC4PO6ehi|0~M> z!2OwWKhOUd8~iPr`*#1p|G@u&et}^BuM~HK{lB8z9n)U3al1AdxIY)}XZ&CE@9o_` z@IUZB@PCot6*%&CxB*)`TaZ}jCK3+9Be$eK#NTCH$?IcWFXiALA|sOxq-Xp6fMEZx zh#nHSKVR;nzp?$>-1qmZ(i6k}{=@h8uj-$$|No4~yFKUs7vAH0Mvw@o9+&Ua=I4Ez zrXMs+FNm6T=>>uRGkzaD`>kqxFPsnDpDXt>yC40I?O~V3?{CjV72m@7;rc<~|BT1G zJ;VPV_qmSBINt3Zd3%~}H2Tx+#M1MUqK`D1Q~L)3|7ZL@Ov-=ZKKEgK(>I7TIaFEdQ(}^?*h*jwkg81pd$XeVCL3;7<$SpBL}KeRMxSavyI0rStm- z`+qv_n_dw3KjZOk#rRL&r|ks(eVu1#8r#}||FHA1Q4M9&@%|tt{_$y>F6a4q9Q)R- zQQ|vJ!he1*O4CW?bDzgCtTZW8d>6_;KW$q40obwE+A*pO{GajrFfRYWpYT1N`q%G| zs^5s+&wVY&{oUr2-#8ZJWconh|BT1GCFj5J9>7<*0nYDi0rTK`Ptxz#zU2%2pYi)JDi^^0C`nqQaw_DTZ2$hU zek1o0X0tTw{LA!@!2QX%Z~YedKjZOk1^BQ2dfNHJ`0ew)DlPE>2YdPe<^@EL5Iq2T z0_P1HBJ+BI&+$pqf&Vjpu1Dqgr1`*~76MJm_!N%U%X_~+o!@_H?S9h-KJqko~`u($C7=C|VfAZwX zl;vG<#TA8es_J3)8ApO^sXZqPlS|5fwfE_thn){QtHcmU`ZW#{cU;stR3GLBDrT=^g7|5s-GCin4aW!jzLJ3Wh??@tl8 zvog<3;L&(I3jH#{6!zoH2wID|1(~fN#<+8k3hMB>k;4Q;{R}1 zj&t3<%=bIl*q9CX`CT$=*SIpw|1ZdIAlwiBe%}tCcbfVa!e*A%mM*B#r(4R3>fXy4mYYL!FTG`TYXy{H&z2`=heie zpYyl^-eCHvGjGm?`wfmCApaQ$6!<#l_aJa}Jg%-vf2}zG$m~iP5YI{Z@TBBH*?;@$y8Q{sX^PgWuEs zPJ4#`Jv|`pv@`U$kigo>%U%+^2+^$Oo{?B;)qusv;*Y%UTz_#)oAHtHf z<9ueX$I5p+wxxW3|0%J_H z+)vAqaQ$lP3ww_LJv{)!3-}Aad)jg`op>J~$8{^mlTA%G>*u|%Hw6CAcpL=(S24c7 zpYJW@I}CUp&+K@|cev#Xe0Mz0_^VuR3cnKs*fLFhmNH6|>k&4xmUPrfRi+{nESQn> z2=gYaAIew1LZ?_S(;Jlk>EhQ*2hOb~=fdwc_;=g0{5L(IOhPZfxFcLIk-$05mtDV@ z~xf$j_(i!zFT|9WlW&Rusmx}DPlH)En(b9hZC+I0Pzps0h z!;5NH;QtoC4>T>8=XHJHf6e2G*!}a5^K;xwR2uiIgfM@wD9(iYE~j51@IUY$dnfHXJ4NjNB&p&`UFRMDbY33dV;XjSgR`gQLaY^#eN6(h35!!k#y|1k8KMyU%O~WTY1w;*Dj~WM6P|=y1>kyFXQ{D$K^ha zW^>y5`qxuGXqdiW5)KB{8%#z${Xw3a;BVM>;J~BcUgYz@9Fgk(GyH>K!2;=5SzXb<|XzjH}^`r z0{;X5_Y?osew%t9lR;y z+N8((+vD_?Ir65YtNT8)?w3&h@Og*8|G@u!#{Ws;{2fnP#to*iT&-ZwD=*S) zy}P90kXLz7WCI_ZV7eE7=lu81kH3!Zg4%tUuTI{(cBr2ofqqOg-mi+kvp&yyAY4)X zBxxtH`^j-EJhc88Rr0zH;vOh(N^jENDRuh{#BRUBD02P`o3|*Bhv4s!U;m)LpYyNu z=3ks%Q1tJJUjQF0Ebz20K7q^FcBCm^xyAEE$^92Kt;g+rvsuP<|GHd0;XeHv)H1ZZ zR!il;^6F|=C}%jor|qA>|G@u!#{WtD;mV=nxNbZm53pUHjQ>&6ar@vPzW(%yQ|R8V z@WSkSOgJk))ZU+@K6YPUvYt>&5|#7!(;K(Tr1T>|@1*b-Zhg1Tu$kHSJRf62Pi;rT z)+;)Dyft;)hTI`5$>|l@IUZ> zKk%P^{}J}})A3!oiFx~sM{Yb{SLt|#4W=X=A9mcgeR0%ZA4La+^NHDq0KM^GTB-Fk z&a1yZSpqwLDEH&ud^_upeo?zTj%EGf2I|lLL^oyr6Xx^vfO)J>l>S8pDN(JK-!2~K zK2em7^>$k%Kx@A%+X38*zS8NCqu4(Q1&JR38yJ-Gchd14TyEic%*sW&kITmGvhwYE z$)|pUf#vz0n@4H263NeXemsxm3Wfe-dDs@n+1rhbv(xskUC;8Nj+i~K^CLhgk3$Vme+4EK zT)R}ZTbW+KSjJFpe+OPJH!t_v?-TeR_`jF=PrDz6zm3Pf ztUQfc5HWt{nm<# zas12&GdZEOeTAVlSbOq0WuwEHY=WTG#AUBpoEH2eet+^h&fi;me97~SXRzxezw3`U*8V`b>vo;S1I}<=;Jn5W znP-b6&JjPa#W~Jj&a1uQKGTiQq5nev%~J2hcm(%;X4l$y%kenDpN`8t|E!%algtmb zLH)Xz@MoIy{x;69ie$Y}?U>x#J$mer-(Jjzw2!~{uG^UpycFKT4Ayo#yLv5KuV?X* z8ZQangQsWOoz~mBydKySzY4G?dH}-bGXFQv=k)-lX}?ns=yZ~dulal^4sitEy7d_5 z4|t!)UsAuypx=Sl;kM!r;CG9){7CA9!!dckdAxh+#OaT=QNK+&kBt`WcVxPOi3dxY z|CXga@$W zvT@nQL7vArD*6G;!v&&V&M@$C~=P6Lyuc~$s@+uk*nSACD^|w+qZ9~=b4}O zeZD_hh?BJA`Rh4;TG}M@!3=i&ta^a?1AxWM{>|qH-o-C-Xm{s){8C25-!-tn=52@_rt1c*c>sq;kq}9&tR9#(qSU0@_v78;V*lFy#d*Iz zxOwaG%|pjTZ@Bd0rLz7kJI?#(*7u1YpaC9AeRBF4^P!sZdmzkjWW7tZIqc>t4g(D#J=_A?){XK@O&f4A`c!4s!2Z@=@8 zGW~#DYD5j?bJCmN_sbhM&HHyy9wp`lgsa$gL1co4R`oBhL$+9@?h~AEH}#Zb$Bt~# zufKKtt}QE%`MECN(&I|0FDgvQ^@(;p)9~wO^Au(M!F)E~gz!aeHzqpR&zwh~gr6#* zL^ZScgtGF+J+9@pv-iQ>NB5^he_DBZe8bK6_{ZB)zm0}|e+qsP&4>M*^tz7DtK{a3 zkqfj7CPRQ37MV=?)>&Ai6nvj?x?PEhAcoieU|araTMRrRjwLe=O+8@b(rn7bHHyu$2;1alze_D z^L8iG8#wP@H*T8!i{_}G1vg-Nn16-maa)bgx9!3G@L;;qMlFxypQLeRH^F`~FjGK^rYlqkJ`)KF)@sjHs zS}%lANO^!vk;knYEPr zr5$Yt^!91%?_dA?yZAfBF(vJsZ{PWS@8It=tz>!l8C(ASEY0(bZ?^kT58!*T?9X{t zKfITC|4ZLn|2+=rdi|;793EHYF?eRYG$|a!gDo<*{*LqXyWQ>+_Iq%?q1)B@dX$&Y zkN@OTmtNHJHGXrL9%1*JR_5yqvt9H<^3KY&`c_GngU2$VL|8$k-=M5K^3M1K4k|yR zb&>h?s>08>r`Iu^-&b+adFp#Sp4a=BWY!$N7f_k ze(wHb^PksY_-Il>eHr_{`TJ83W;y5%?T-oiC0`!YnX%hr{pd^KevZS&<7p+!rT*;S z&(iL`vE64C`Jdpq;Cl(bzLVV7LhPpOeYf{M$a?rsuEC7d`TKOeX?*mh@Du}(^*hJi z_IiwXJnRc}tos%24}`G%OU#E4&8=BJ#wGsMdG#L*iv#rUH)>ky)thfo{lxiyFuV!3 zBXHI)&+0d-_uEx*jr={FA(>MAh@SFJ&{Q8xpSst)Bv7ImVfgh^w z@>wqXr>xv@kCVgH(h|9c;Q!mS%rJ)-5}1J==Q|GH_@yf5ttm7O(83}>RhthshX z1L6=hzgt%b|HUNzgzk`jLO6#=+A-sn6Y|%~@oD>$;{eR*rSk*WxIU^R$LOE(IX-S$ zIh*MVrMwxnUY3vLVtS@jPpPV>a2kPMR-P}l{Q3Da{QFm>MIXR;C~^Y-0Iy>_GbM`2 z`~WVk^9>k~OuyK({2x_M7&RaD3H+6fBjg6$*cyK~z%On*+u71MHz%Wh*>Gt@xJW)< zre6NyckApvtPk~nsh7!yuQ!-J(p$G*);>GV^6+?>9-#C7P7mnC(HGa#@j47Uv0M-R z-0C^0WI1S-e3uVG>jOhxj`!*(?ZgDIWuX-EUec^~4KK$@nFeqw6iqPd%Ix)A#DnfIdD> zySP3)fB#AR8I$${c@4i)YMdTd^7;h7i*Vmb{_cJ`f&UfjTtx40$$qZDN9NZiH;ngi z^JZLeJa39bpr1(e`_9(RjK4zx|GeA>Tr`P)Xnk6hdV{nlHvOQPwX9oveCKf#ujhy8M^YXpc2s8eyGw60^5wxElk)Hx=B!*^H_V^l%2j;<^)G8* zI3D<)Ux!Hk^Z7Nu`?APU`Qmd=xfUgPzTtH!^$TdemhkW(IzrQpLVg}cSmnt6fKPmg zd~P;WMv=;%-D>S)x#XeWj#;I7Km6Pyx5Mh`>K{94?J1p)uJ7y~^@L7l zm$SW%Y`&q|KfwI9{ts$^_(2dZQyM3b=Q!itcset+FJAl=Y%@$&@U-xIi==eoV$RZ^eBC|R2E$@wkIxZxis&k*LF@tSGIM_N9n zvECu}dCKGTds;qU@;uysP9IQxVjMe}oJ74E=YRXZe30?)Ob@!B)DMA(e*I>Z=KU}Z zIsZZVe7fHstUuh4k+sL9=mSuU{QOmE@*m3{8q;#V?D}B;d-nf8?yFy)+)+-ZYTwU< z_eb6iH3Rp7#0@DKKX&An^oKZZjVpP5L+Xw4@DC{$fz~P`w8PgYz;P%BuARc)7}q-v zSH`a~pUne64G?{4kl%d}x7TqW|G1xae0S>y9pCXg@c1h7UHjGI4^_XRzf+F~xsZOq z3n%w=&pqG2b7!en#w+drkI@d{HR6qY?g(E?`CrWMUq5bIc!>U_q+dGENo08FRg#-{1$0u-&;F;$^2Yj;jVN30rPhh^$YRagZq?&{Ptx$ANTma z=fC*(q1Ti9;H}vK^8UmAH2x?L)z0VdQRJr2(NWXnebSkh^W*9TmS6M$3=FCdG$F0t z>fd*M0pU2u4;_z3^9Lm8C+6pTgvc~;()K%2KLLt$JdfRZmmeL?>sFEL_8j?A`!6Z@ zk?c7O^32WDb7$2L%JH<}B;tF?^`?`I_hs5iJb04E^GnJ1epT)NDn1MU@gh=UUQO2D zVgIgDyT7VD|9n3$m3s|zsLx4DGe7MRy)KH{AN-fgyzU2@6=Y9g7 zpWo+x+^muJ##eA3_c0E4yGP!R*DqGtX{n5x?s%5Z^3O_A4}kwZ=O2I`fCtpZWAcmp zqWN44>EV8L_8ZspTR+w%SuUO%U$2^bdertPU(=?_hVs?pEcL}acR4S#Y}{Rz`uLn3 zH7&e?qgo{1Z;`op@lneeHJ|a$m%K0bt4Ty+T=CCan&-cWi2m56EX(sew|a=5zqdb#v&*viZ?Su! z=k@Gir)u%ED>L0=psy4A%eI6nv8d*Qf~v@=(|-`F0;qvqSi z{Wq7@j-K1NGwOPy5%U@MMwR3Y=lOkp@i_g{qn0ynK9-NiVW8cd;Dpo*(yy1Lc^<={ z*y|jZg!_-8)`?&F1FjYsf*qZ^l`sG5i+hA9#PROSJmTO13}v^y^N3 z}{L z0O-@~57txrrz3FzIGpvL_2aTg{+_6Awojy_55|S{n|?m4f7x+6KdNLoe9kC2e@wX- z_&#d;{d^O@uOsqszm;#}KKX>l%AIo7}yOnEFj$=ZU za-29q>j#t61MvQr#tU8z{=eomuPODPlam;>JJR0NtxvdD^%TCpR^Pg%-mfF;_F&Gd zJr8N^8ddt-xAeZ}Kka_}*|;R*JksXBB;U9{`ncS8_C4%%@;+UoY&H*q?>k$V77_{E zLmKDHB=mtq^#VR;#r!$_0S~D0-t-1b*DZVf0X+!{vh|nrA6{hZPm_8)>hI?$5ql8R zi?N>9d2r4*O8T(s704#>0LYf<7n=WIHt#Sv9j)ncm9`I0BewMjR<5Sey>g!{U$dtl zupBIFl=|l9JG6YO_h@kt*iKE$!40i{_N$b2^!w4?n34WMUcgN`(5}XGa%SflHzfZE z{CDM+a*pTem$Y(C3OB(^k$B!UU95-YXE_)?vidV!haci<@cjq|&>r```Btt^;_>5K z=^E=5V}nCG@5`V5;K6HdiQ^k4<-4>C3XI4J{06*kn*E0T_E?(jhC4FT5BHt@uYUhN zyO7VD+O;X{+{yj>>aX9~T3b7Ku*mH)-d7!Gd|;Fm%fq|?PLTU?ye2a9E9CToy7Yiv zzo;J|&wuXgei1$?y15!c2(!(r&#V z<|D)pK|O)zG2Tf!wfnjK1u+~6e}Q+Y5B4pT@}L&T{h$U&8v7Auehrq#c{VAxt^4{% z>utHu1`6bHR71|=XDH}Det(3t#zzGHd+Uv+)CDSM1=cMKM<(8$(+Qcl*(_asJ|KLIR0jjt@>UsJDbUpyD5z4Q+PU8jY;=YyR_e-At>^JLAlkC^dVfVcN ztT)Q%ho0nkt=|dSz<1?@vz=6U+{X9kn2sISf%TZ_X2V_A((7SKKFPW&zo`+em3DHI+haQ436i>lsN^8?s*Rjvnz%Ht=lRZI zm6-pRG;p1j%kzw*u>1IFJ8qKafqAv}o&EvyU-SUD@B7NJeE$8A-EqD@KdP5qZ}%&c zQV;wtB4Lk|<=ZdE9sk36zA63w7U$@X^QH$>amn#U;_UD^kIXgl5W`JGd;PXMw{>12 zy+!o_%r6+neMt^;{+)6V_btEV%lW7M_En{MJ@*q9iH1>fzQf8dlgx)~9VZL=g0y?a z>;D*URMoz^&+j|_+k1yN0P3$c?s9y_XA=|hpFCARK%RS(=>a|dGyY#WjqQHGYvVmR zuj~7wgUwjDjtLs$uSwxQ91ND8wRCoWZht_v``ItlOW9vsuW0?Z>{99r1!CUyOVU_ZTrW)en0acZvTu29H*h4irtA}8vF5S z9GdaFKFRs{miqH?qgiu&-13$CjDy7f19*{o?dE*9n`g&_0Q-yY*|KnDS?j@m;(B6> ze=y18aPK(3fG@M}IlJ@OcNz=nWofsx2Xg;zFy(v{K5$#CO$w?L71I{iH;H999{tp8^A%Sxaaol7-K;#7LR$h4#4Lf^Rs{D zI+G2vi#6^a)ie8YW%UKN4?nx=0eIe+Msu@xO21!1diLJ~V0@S8AHFQ;CClfxC>z(w zC8*Wex;mdj-e5f;%CYH6n^AXPi zd-kFSV0=)0pdP;f_eXX5z{LCbx$B{y$oB-SJWuL<5U%6#K1!rLcutKw%BNnO{7Bq) zadwEWZ_4$EXRz{o>G&6`TsI!-b$IXn{FY{Zj@y(G{vi_oP#KM^r&P6PUgueF##K{t z{dJHF><9c1(*O7&RJNl3=l4!hKfQC=<}GX-H>vh~8dGxqZd}fnWR@vi$CPeD_bvsf-uk^;iy&T6yD2;bfzHpC7B&r1JT9eepYR;dMY**6RZ`|LWWOW*&M?w2 z@L7M_`FXyk7(lGuWs?1YY9%6V9ll#XhiQXKKR2&~ec-rlQvC*a>_rd2FedSH7?x$d z9)6Cjo>pI9O1Wr{)GMbSl(i!q?_Z}E`eSf^EPPxtf2ET)uA3zHxh}PgRc?tG@7 z7)Ralgx>{<9{cVtxs3H&sh#&F&r?sS;(Xb8;XXD-@yoR|>%;g;N-SH>^aDHZOUZ}t zt*T6=oiN7={jl%9|L46=h4&l$r(LFcK>vDy1jor2&KrmXn2bZB4?r;xiTN>=k;%B{ zXRqRQ+4(%jtz7+n0bV1$kJC%IUeN00OIL5@D&AM7see|D|I7#ME0;U^JsV(OuD9a_ zeFF1Uaz8xBem(p&^P&5M-!}iSk!uIfV;pHqd5G(G<+|Tty%29_<>$-A=QEw&vG?N- zs(#OKelYK?U-bQtmo9G6xJd4Ed^fK8lINr7uc43{KwSQ9S_+N-maQttP-_#GRKSq_jj{1XNFJGVV z%TxWL+|S_m_q6GAXRo{Vz<~ox_gs(tp5gt(W7FY#3W<0g%8rwa?_qjODeIN+y&&+R zY+M-kxbVmE5?EF)B~BWr%cQffv+LaT>yn74<%G z>X}=gc>KLzdGhmT+M7GOmv|ie?dg7fX}P^##{H_}a$m=F^B+L1midGzT8nwU=`))u_pUBqptz2BfsO{dX`8aOiF^Vbqo{c>&%~%gGzL1Z2 zToHG6@ETN8CG>GS?@QyVN!t65J*M_9uFvdU7-*cXqmmgqKh(P9UA0|Em|7#`Hkt{D6Poano{N zv_m8u2bAl0Fa2_?9aU1wK|3f9J3WK*piD!*5Xt$Gs&b^Cz?-Uk;rwtr-t}V^w=-#f zah!=U4lf(`tv-|T&*=%7zkq4Hc53h9dmhFo^-Gt>&+|OF{DuE;<&8&ocXw4ksP8)j zQm+ZPPrf0{&CP$e3h{H}_8U1)e{7$ez%O9^YLfkjVzFN#WsL6=Zd`x-c)7pc`0Y!! z7sFQ}F4NCvY3-j5>}B;E+4HpYlHAANr0;XMZuq!4YX1n=FfXTkOAG#d9`D(|j zf*gl^aRPe4+pq_s-S6SgpSktYQ%`nz{CHRO0_e?IJcZBYy3?!|^>Rwi^H}*-zbeV= z_*_!LO+4}XL~@Jkr6{>i6y-YBaS6{5*d-GC33};{9&aaBPj_CgLqF>I4VN~V%6-t! zT3(vA`>r3NhiG}}_p59rDtq_$%JIuxch|f7r)!dqqezeU;%r<#|N2s{Ge2-G?c{f= z`|D9`x1BGO%WC)AI6W@MEI+y0^B1V!fPOOajq`NICzIq8Ufax1&NS48Y(D!7|KXYS zot>Qq`49EK;J+W&AK>&6o&U%BSgUuHB-fy?4V=&C`&4$l^3dYqtM4cMgoaxGj4RoH zjMFq8k1K`u7;h-)pSE@}&EHkd;|D7%m*@HYSo;4Pm;kWnY3Zf#zxQ63y|;Fp*vq@VjVzV&h1S@;c5M-_RE_zE#<-9 z(fjxO4ou^4~?~O?Lhz3>lqn+n<{C)%_-)G7-*g=%^+v)LG z)8HJ_{N5KO;<+u^|(+Q!S<+^&~_mK8b&$52< zCG$7%JxRF^{b%R(cw>W->rE)xk9OXdYWK(SvX6&0?vE?UFZkoep4av{$M^u0llc*< zIM4b1D0%NC^8GL#2uiri3UPsDW9~$w0>p%;Fq&<@qgeye#T6% z_upGf%eV{$l@i!rpOg>tx{mckn5uHnWX8p8U3;ltnBy78SC&J)4)%0APPfF*{*QLj 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%0ABn2t}D&^@w<3s^_S-z-@}~w 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 O@5e0vE&ucI|NjHtys4o8 literal 0 HcmV?d00001 diff --git a/include/collada/scene.h b/include/collada/scene.h index 9032a5d..b31869b 100644 --- a/include/collada/scene.h +++ b/include/collada/scene.h @@ -18,9 +18,17 @@ namespace collada::scene { static_skinned * vertex_arrays; int * vertex_buffer_strides; + unsigned int * textures; + void load_layouts(); + void load_images(); void load_scene(types::descriptor const * const descriptor); + void set_color_or_texture(types::color_or_texture const& color_or_texture, + unsigned int color_uniform, + unsigned int texture_unit); + void set_instance_material(types::instance_material const& instance_material); + void draw_geometry(types::geometry const & geometry, types::instance_material const * const instance_materials, int const instance_materials_count); diff --git a/include/dds.h b/include/dds.h new file mode 100644 index 0000000..50d6431 --- /dev/null +++ b/include/dds.h @@ -0,0 +1,127 @@ +//-------------------------------------------------------------------------------------- +// dds.h +// +// This header defines constants and structures that are useful when parsing +// DDS files. DDS files were originally designed to use several structures +// and constants that are native to DirectDraw and are defined in ddraw.h, +// such as DDSURFACEDESC2 and DDSCAPS2. This file defines similar +// (compatible) constants and structures so that one can use DDS files +// without needing to include ddraw.h. +//-------------------------------------------------------------------------------------- + +#ifndef _DDS_H_ +#define _DDS_H_ + +#pragma pack(push,1) + +#define DDS_MAGIC 0x20534444 // "DDS " + +#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ + ((unsigned int)(unsigned char)(ch0) | ((unsigned int)(unsigned char)(ch1) << 8) | \ + ((unsigned int)(unsigned char)(ch2) << 16) | ((unsigned int)(unsigned char)(ch3) << 24 )) + +struct DDS_PIXELFORMAT +{ + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwABitMask; +}; + +#define DDS_FOURCC 0x00000004 // DDPF_FOURCC +#define DDS_RGB 0x00000040 // DDPF_RGB +#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS +#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE +#define DDS_ALPHA 0x00000002 // DDPF_ALPHA + +const DDS_PIXELFORMAT DDSPF_DXT1 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','1'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT2 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','2'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT3 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','3'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT4 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','4'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_DXT5 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','5'), 0, 0, 0, 0, 0 }; + +const DDS_PIXELFORMAT DDSPF_A8R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }; + +const DDS_PIXELFORMAT DDSPF_A1R5G5B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000 }; + +const DDS_PIXELFORMAT DDSPF_A4R4G4B4 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000 }; + +const DDS_PIXELFORMAT DDSPF_R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + +const DDS_PIXELFORMAT DDSPF_R5G6B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 16, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000 }; + +// This indicates the DDS_HEADER_DXT10 extension is present (the format is in dxgiFormat) +const DDS_PIXELFORMAT DDSPF_DX10 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','1','0'), 0, 0, 0, 0, 0 }; + +#define DDS_HEADER_FLAGS_TEXTURE 0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT +#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT +#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH +#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH +#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE + +#define DDS_SURFACE_FLAGS_TEXTURE 0x00001000 // DDSCAPS_TEXTURE +#define DDS_SURFACE_FLAGS_MIPMAP 0x00400008 // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP +#define DDS_SURFACE_FLAGS_CUBEMAP 0x00000008 // DDSCAPS_COMPLEX + +#define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX +#define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX +#define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY +#define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY +#define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ +#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ + +#define DDS_CUBEMAP_ALLFACES ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX |\ + DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\ + DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ ) + +#define DDS_FLAGS_VOLUME 0x00200000 // DDSCAPS2_VOLUME + +typedef struct +{ + unsigned int dwSize; + unsigned int dwHeaderFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwHeaderFlags + unsigned int dwMipMapCount; + unsigned int dwReserved1[11]; + DDS_PIXELFORMAT ddspf; + unsigned int dwSurfaceFlags; + unsigned int dwCubemapFlags; + unsigned int dwReserved2[3]; +} DDS_HEADER; + +/* +typedef struct +{ + DXGI_FORMAT dxgiFormat; + D3D10_RESOURCE_DIMENSION resourceDimension; + unsigned int miscFlag; + unsigned int arraySize; + unsigned int reserved; +} DDS_HEADER_DXT10; +*/ + +#pragma pack(pop) + +#endif // _DDS_H diff --git a/include/dds_validate.h b/include/dds_validate.h new file mode 100644 index 0000000..1aa733e --- /dev/null +++ b/include/dds_validate.h @@ -0,0 +1,10 @@ +#pragma once + +#include "dds.h" + +struct DDS_FILE { + unsigned int dwMagic; + DDS_HEADER header; +}; + +DDS_FILE const * dds_validate(void * data, unsigned int size, void ** out_data, int * out_size); diff --git a/include/glad/gl.h b/include/glad/gl.h index a9fb7c7..c34be4b 100644 --- a/include/glad/gl.h +++ b/include/glad/gl.h @@ -1,11 +1,11 @@ /** - * Loader generated by glad 2.0.8 on Thu Mar 5 16:16:42 2026 + * Loader generated by glad 2.0.8 on Tue Mar 17 02:15:55 2026 * * SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0 * * Generator: C/C++ * Specification: gl - * Extensions: 0 + * Extensions: 1 * * APIs: * - gl:core=4.3 @@ -19,10 +19,10 @@ * - ON_DEMAND = False * * Commandline: - * --api='gl:core=4.3' --extensions='' c + * --api='gl:core=4.3' --extensions='GL_EXT_texture_compression_s3tc' c * * Online: - * http://glad.sh/#api=gl%3Acore%3D4.3&extensions=&generator=c&options= + * http://glad.sh/#api=gl%3Acore%3D4.3&extensions=GL_EXT_texture_compression_s3tc&generator=c&options= * */ @@ -332,8 +332,12 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro #define GL_COMPRESSED_RGBA 0x84EE #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 #define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 #define GL_COMPRESSED_RG_RGTC2 0x8DBD #define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 #define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC @@ -1578,6 +1582,8 @@ GLAD_API_CALL int GLAD_GL_VERSION_4_1; GLAD_API_CALL int GLAD_GL_VERSION_4_2; #define GL_VERSION_4_3 1 GLAD_API_CALL int GLAD_GL_VERSION_4_3; +#define GL_EXT_texture_compression_s3tc 1 +GLAD_API_CALL int GLAD_GL_EXT_texture_compression_s3tc; typedef void (GLAD_API_PTR *PFNGLACTIVESHADERPROGRAMPROC)(GLuint pipeline, GLuint program); diff --git a/include/opengl.h b/include/opengl.h index 46de730..23ca694 100644 --- a/include/opengl.h +++ b/include/opengl.h @@ -10,6 +10,8 @@ extern "C" { unsigned int load_uniform_buffer(char const * const path); + void load_dds_texture_2D(char const * const path); + #ifdef __cplusplus } #endif diff --git a/shader/collada/generic.frag b/shader/collada/generic.frag index 8040155..6aa9ba2 100644 --- a/shader/collada/generic.frag +++ b/shader/collada/generic.frag @@ -1,8 +1,65 @@ #version 430 core -out vec4 Color; +in vec3 PixelNormal; +in vec2 PixelTexture; +/* + +in vec4 PixelViewPosition; +in vec4 PixelWorldPosition; +in vec4 PixelLightPosition; +*/ + +layout (location = 10) uniform vec4 EmissionColor; +layout (location = 11) uniform vec4 AmbientColor; +layout (location = 12) uniform vec4 DiffuseColor; +layout (location = 13) uniform vec4 SpecularColor; + +layout (location = 14) uniform float shininess; + +layout (location = 15) uniform sampler2D EmissionSampler; +layout (location = 16) uniform sampler2D AmbientSampler; +layout (location = 17) uniform sampler2D DiffuseSampler; +layout (location = 18) uniform sampler2D SpecularSampler; + +layout (location = 19) uniform ivec4 TextureChannel; + +layout (location = 0) out vec4 g_color; +//layout (location = 1) out vec4 g_position; +//layout (location = 2) out vec4 g_normal; void main() { - Color = vec4(1, 0, 0, 1); + vec4 emission; + vec4 ambient; + vec4 diffuse; + vec4 specular; + if (TextureChannel.x >= 0) { // emission + emission = texture(EmissionSampler, PixelTexture.xy); + } else { + emission = EmissionColor; + } + if (TextureChannel.y >= 0) { // ambient + ambient = texture(AmbientSampler, PixelTexture.xy); + } else { + ambient = AmbientColor; + } + if (TextureChannel.z >= 0) { // diffuse + diffuse = texture(DiffuseSampler, PixelTexture.xy); + } else { + diffuse = DiffuseColor; + } + if (TextureChannel.w >= 0) { // specular + specular = texture(SpecularSampler, PixelTexture.xy); + } else { + specular = SpecularColor; + } + + vec3 color = emission.xyz * 0; + color += ambient.xyz * 0.05; + color += diffuse.xyz * 1; + color += specular.xyz * 0 * 0.3; + + g_color = vec4(color, 1.0); + //g_color = vec4(PixelNormal.xyz, 1.0); + //g_color = vec4(PixelTexture.xy, 0, 1); } diff --git a/shader/collada/static.vert b/shader/collada/static.vert index 952d348..971eba0 100644 --- a/shader/collada/static.vert +++ b/shader/collada/static.vert @@ -2,11 +2,17 @@ layout (location = 0) in vec3 Position; layout (location = 1) in vec3 Normal; -layout (location = 2) in vec3 Texture; +layout (location = 2) in vec2 Texture; layout (location = 0) uniform mat4 Transform; +out vec3 PixelNormal; +out vec2 PixelTexture; + void main() { + PixelNormal = Normal; + PixelTexture = vec2(Texture.x, 1.0 - Texture.y); + gl_Position = Transform * vec4(Position, 1); } diff --git a/src/collada/scene.cpp b/src/collada/scene.cpp index 1c824aa..afc2bc5 100644 --- a/src/collada/scene.cpp +++ b/src/collada/scene.cpp @@ -8,6 +8,7 @@ #include "new.h" #include "file.h" #include "view.h" +#include "opengl.h" #include "collada/types.h" #include "collada/instance_types.h" @@ -25,8 +26,29 @@ namespace collada::scene { unsigned int blend_weight; } attribute; struct { + // vertex unsigned int transform; + // fragment + unsigned int emission_color; + unsigned int ambient_color; + unsigned int diffuse_color; + unsigned int specular_color; + + unsigned int shininess; + + unsigned int emission_sampler; + unsigned int ambient_sampler; + unsigned int diffuse_sampler; + unsigned int specular_sampler; + + unsigned int texture_channels; } uniform; + struct { + unsigned int emission; + unsigned int ambient; + unsigned int diffuse; + unsigned int specular; + } texture_unit; }; const layout layout = { @@ -38,7 +60,28 @@ namespace collada::scene { .blend_weight = 4, }, .uniform = { + // vertex .transform = 0, + // fragment + .emission_color = 10, + .ambient_color = 11, + .diffuse_color = 12, + .specular_color = 13, + + .shininess = 14, + + .emission_sampler = 15, + .ambient_sampler = 16, + .diffuse_sampler = 17, + .specular_sampler = 18, + + .texture_channels = 19, + }, + .texture_unit { + .emission = GL_TEXTURE0, + .ambient = GL_TEXTURE1, + .diffuse = GL_TEXTURE2, + .specular = GL_TEXTURE3, }, }; @@ -197,6 +240,27 @@ namespace collada::scene { return index_buffer; } + void state::load_images() + { + textures = New(descriptor->images_count); + glGenTextures(descriptor->images_count, textures); + + for (int i = 0; i < descriptor->images_count; i++) { + types::image const * const image = descriptor->images[i]; + + glBindTexture(GL_TEXTURE_2D, textures[i]); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + load_dds_texture_2D(image->resource_name); + } + + glBindTexture(GL_TEXTURE_2D, 0); + } + void state::load_scene(types::descriptor const * const descriptor) { this->descriptor = descriptor; @@ -206,6 +270,64 @@ namespace collada::scene { vertex_buffer_pnt = load_vertex_buffer(descriptor->position_normal_texture_buffer); vertex_buffer_jw = load_vertex_buffer(descriptor->joint_weight_buffer); index_buffer = load_index_buffer(descriptor->index_buffer); + + load_images(); + } + + void state::set_color_or_texture(types::color_or_texture const& color_or_texture, + unsigned int color_uniform, + unsigned int texture_unit) + { + switch (color_or_texture.type) { + case types::color_or_texture_type::COLOR: + glUniform4fv(color_uniform, 1, (float *)&color_or_texture.color); + break; + case types::color_or_texture_type::TEXTURE: + glActiveTexture(texture_unit); + glBindTexture(GL_TEXTURE_2D, textures[color_or_texture.texture.image_index]); + break; + default: + assert(false); + } + } + + void state::set_instance_material(types::instance_material const& instance_material) + { + types::effect const& effect = *instance_material.material->effect; + switch (effect.type) { + case types::effect_type::BLINN: + set_color_or_texture(effect.blinn.emission, layout.uniform.emission_color, layout.texture_unit.emission); + set_color_or_texture(effect.blinn.ambient, layout.uniform.ambient_color, layout.texture_unit.ambient); + set_color_or_texture(effect.blinn.diffuse, layout.uniform.diffuse_color, layout.texture_unit.diffuse); + set_color_or_texture(effect.blinn.specular, layout.uniform.specular_color, layout.texture_unit.specular); + glUniform1f(layout.uniform.shininess, effect.blinn.shininess); + break; + case types::effect_type::LAMBERT: + set_color_or_texture(effect.lambert.emission, layout.uniform.emission_color, layout.texture_unit.emission); + set_color_or_texture(effect.lambert.ambient, layout.uniform.ambient_color, layout.texture_unit.ambient); + set_color_or_texture(effect.lambert.diffuse, layout.uniform.diffuse_color, layout.texture_unit.diffuse); + break; + case types::effect_type::PHONG: + set_color_or_texture(effect.phong.emission, layout.uniform.emission_color, layout.texture_unit.emission); + set_color_or_texture(effect.phong.ambient, layout.uniform.ambient_color, layout.texture_unit.ambient); + set_color_or_texture(effect.phong.diffuse, layout.uniform.diffuse_color, layout.texture_unit.diffuse); + set_color_or_texture(effect.phong.specular, layout.uniform.specular_color, layout.texture_unit.specular); + glUniform1f(layout.uniform.shininess, effect.phong.shininess); + break; + case types::effect_type::CONSTANT: + glUniform4fv(layout.uniform.emission_color, 1, (float *)&effect.constant.color); + break; + default: + break; + } + + int texture_channels[4] = { + instance_material.emission.input_set, + instance_material.ambient.input_set, + instance_material.diffuse.input_set, + instance_material.specular.input_set, + }; + glUniform4iv(layout.uniform.texture_channels, 1, texture_channels); } void state::draw_geometry(types::geometry const & geometry, @@ -217,10 +339,13 @@ namespace collada::scene { types::mesh const& mesh = geometry.mesh; for (int j = 0; j < instance_materials_count; j++) { + //if (j != 1) + //continue; + types::instance_material const& instance_material = instance_materials[j]; types::triangles const& triangles = mesh.triangles[instance_material.element_index]; - //set_instance_material(instance_material); + set_instance_material(instance_material); unsigned int vertex_buffer_offset = mesh.vertex_buffer_offset; unsigned int vertex_buffer_stride = vertex_buffer_strides[triangles.inputs_index]; @@ -235,7 +360,7 @@ namespace collada::scene { unsigned int instance_count = 1; unsigned int base_vertex = 0; - unsigned int base_instance = 1; + unsigned int base_instance = 0; glDrawElementsInstancedBaseVertexBaseInstance(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, @@ -266,6 +391,16 @@ namespace collada::scene { { glUseProgram(collada::effect::program_static); glUniformMatrix4fv(layout.uniform.transform, 1, false, (float *)&view::state.float_transform); + glUniform1i(layout.uniform.emission_sampler, 0); + glUniform1i(layout.uniform.ambient_sampler, 1); + glUniform1i(layout.uniform.diffuse_sampler, 2); + glUniform1i(layout.uniform.specular_sampler, 3); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_GREATER); + glDisable(GL_CULL_FACE); + //glCullFace(GL_FRONT); + //glFrontFace(GL_CCW); for (int i = 0; i < descriptor->nodes_count; i++) { types::node const & node = *descriptor->nodes[i]; diff --git a/src/dds_validate.cpp b/src/dds_validate.cpp new file mode 100644 index 0000000..a99ff35 --- /dev/null +++ b/src/dds_validate.cpp @@ -0,0 +1,69 @@ +#include +#include + +#include "dds_validate.h" + +static inline unsigned int max(unsigned int a, unsigned int b) +{ + return (a > b) ? a : b; +} + +struct dds_size_levels { + unsigned int const size; + unsigned int const levels; +}; + +static inline unsigned int dim(unsigned int d) +{ + return max(1, (d / 4)); +} + +static inline dds_size_levels dds_mip_total_size(uintptr_t data, + unsigned int height, + unsigned int width, + unsigned int max_mip_levels) +{ + unsigned int mip_total_size = 0; + unsigned int mip_levels = 0; + while (true) { + unsigned int mip_size = dim(height) * dim(width) * 8; + mip_total_size += mip_size; + //subresourceData[mip_levels].pSysMem = (const void *)data; + //subresourceData[mip_levels].SysMemPitch = dim(width) * 8; + mip_levels += 1; + assert(mip_levels <= max_mip_levels); + data += mip_size; + + if (max_mip_levels == 1 || (width == 1 && height == 1)) + break; + + height /= 2; + width /= 2; + } + + return (dds_size_levels){mip_total_size, mip_levels}; +} + +DDS_FILE const * dds_validate(void * data, unsigned int size, void ** out_data, int * out_size) +{ + DDS_FILE const * const dds = (DDS_FILE const *)data; + assert(dds->dwMagic == DDS_MAGIC); + assert(dds->header.dwSize == 124); + assert(dds->header.ddspf.dwSize == 32); + assert(dds->header.ddspf.dwFlags == DDS_FOURCC); + assert(dds->header.ddspf.dwFourCC == MAKEFOURCC('D','X','T','1')); + + assert(dds->header.dwDepth == 0); + + uintptr_t image_data = ((uintptr_t)dds) + (sizeof (DDS_FILE)); + dds_size_levels ret = dds_mip_total_size(image_data, + dds->header.dwHeight, + dds->header.dwWidth, + dds->header.dwMipMapCount); + assert(ret.size + (sizeof (DDS_FILE)) == size); + assert(ret.levels == dds->header.dwMipMapCount); + + *out_data = (void *)image_data; + *out_size = ret.size; + return dds; +} diff --git a/src/gl.c b/src/gl.c index b027d1e..64faa39 100644 --- a/src/gl.c +++ b/src/gl.c @@ -39,6 +39,7 @@ int GLAD_GL_VERSION_4_0 = 0; int GLAD_GL_VERSION_4_1 = 0; int GLAD_GL_VERSION_4_2 = 0; int GLAD_GL_VERSION_4_3 = 0; +int GLAD_GL_EXT_texture_compression_s3tc = 0; @@ -1259,7 +1260,7 @@ static int glad_gl_find_extensions_gl(void) { char **exts_i = NULL; if (!glad_gl_get_extensions(&exts, &exts_i)) return 0; - GLAD_UNUSED(&glad_gl_has_extension); + GLAD_GL_EXT_texture_compression_s3tc = glad_gl_has_extension(exts, exts_i, "GL_EXT_texture_compression_s3tc"); glad_gl_free_extensions(exts_i); diff --git a/src/opengl.c b/src/opengl.cpp similarity index 71% rename from src/opengl.c rename to src/opengl.cpp index d0629af..0806181 100644 --- a/src/opengl.c +++ b/src/opengl.cpp @@ -7,12 +7,13 @@ #include "glad/gl.h" #include "opengl.h" #include "file.h" +#include "dds_validate.h" -unsigned int compile(const char * vertex_source, +unsigned int compile(char const * vertex_source, int vertex_source_size, - const char * geometry_source, + char const * geometry_source, int geometry_source_size, - const char * fragment_source, + char const * fragment_source, int fragment_source_size) { int compile_status; @@ -75,16 +76,16 @@ unsigned int compile(const char * vertex_source, return shader_program; } -unsigned int compile_from_files(const char * vertex_path, - const char * geometry_path, - const char * fragment_path) +unsigned int compile_from_files(char const * vertex_path, + char const * geometry_path, + char const * fragment_path) { int vertex_source_size = 0; - char * vertex_source = NULL; + void * vertex_source = NULL; int geometry_source_size = 0; - char * geometry_source = NULL; + void * geometry_source = NULL; int fragment_source_size = 0; - char * fragment_source = NULL; + void * fragment_source = NULL; vertex_source = read_file(vertex_path, &vertex_source_size); assert(vertex_source != NULL); @@ -97,9 +98,9 @@ unsigned int compile_from_files(const char * vertex_path, fragment_source = read_file(fragment_path, &fragment_source_size); assert(fragment_source != NULL); - unsigned int program = compile(vertex_source, vertex_source_size, - geometry_source, geometry_source_size, - fragment_source, fragment_source_size); + unsigned int program = compile((char const *)vertex_source, vertex_source_size, + (char const *)geometry_source, geometry_source_size, + (char const *)fragment_source, fragment_source_size); free(vertex_source); free(geometry_source); @@ -125,3 +126,27 @@ unsigned int load_uniform_buffer(char const * const path) return buffer; } + +void load_dds_texture_2D(char const * const path) +{ + fprintf(stderr, "load DDS texture: %s\n", path); + + int size; + void * data = read_file(path, &size); + assert(data != NULL); + + void * image_data; + int image_size; + DDS_FILE const * dds = dds_validate(data, size, &image_data, &image_size); + + glCompressedTexImage2D(GL_TEXTURE_2D, + 0, + GL_COMPRESSED_RGB_S3TC_DXT1_EXT, + dds->header.dwWidth, + dds->header.dwHeight, + 0, + image_size, + image_data); + + free(data); +} diff --git a/src/test.cpp b/src/test.cpp index 575df30..b6c8259 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -380,6 +380,7 @@ void update_joystick(int joystick_index, delta_yaw, delta_pitch); view::apply_fov(0.01 * up + -0.01 * down); + /* XMVECTOR sphere_position = view::state.at; float sphere_radius = 0.48; @@ -411,7 +412,9 @@ void update_joystick(int joystick_index, // apply the last direction impulse view::state.at = sphere.center + direction; } + */ + view::state.at = view::state.at + direction; view::state.eye = view::state.at - view::state.direction * view::at_distance; /* @@ -558,10 +561,9 @@ void update_mouse(int x, int y) void draw() { + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearDepth(-1.0f); if (false) { - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClearDepth(-1.0f); - // possibly re-initialize geometry buffer if window width/height changes init_geometry_buffer(geometry_buffer_pnc, geometry_buffer_pnc_types);