| 1 | /********************************************************************** |
| 2 | * |
| 3 | * .obj loader |
| 4 | * |
| 5 | * Copyright (c) 2021 Dario Deledda. All rights reserved. |
| 6 | * Use of this source code is governed by an MIT license |
| 7 | * that can be found in the LICENSE file. |
| 8 | * |
| 9 | * TODO: |
| 10 | **********************************************************************/ |
| 11 | module obj |
| 12 | |
| 13 | import sokol.gfx |
| 14 | import gg.m4 |
| 15 | import math |
| 16 | import stbi |
| 17 | |
| 18 | /****************************************************************************** |
| 19 | * Texture functions |
| 20 | ******************************************************************************/ |
| 21 | pub fn create_texture(w int, h int, buf &u8) (gfx.Image, gfx.Sampler) { |
| 22 | sz := w * h * 4 |
| 23 | mut img_desc := gfx.ImageDesc{ |
| 24 | width: w |
| 25 | height: h |
| 26 | num_mipmaps: 0 |
| 27 | // min_filter: .linear |
| 28 | // mag_filter: .linear |
| 29 | // usage: .dynamic |
| 30 | // wrap_u: .clamp_to_edge |
| 31 | // wrap_v: .clamp_to_edge |
| 32 | label: &char(unsafe { nil }) |
| 33 | d3d11_texture: 0 |
| 34 | } |
| 35 | // comment if .dynamic is enabled |
| 36 | img_desc.data.subimage[0][0] = gfx.Range{ |
| 37 | ptr: buf |
| 38 | size: usize(sz) |
| 39 | } |
| 40 | |
| 41 | sg_img := gfx.make_image(&img_desc) |
| 42 | |
| 43 | mut smp_desc := gfx.SamplerDesc{ |
| 44 | min_filter: .linear |
| 45 | mag_filter: .linear |
| 46 | wrap_u: .clamp_to_edge |
| 47 | wrap_v: .clamp_to_edge |
| 48 | } |
| 49 | |
| 50 | sg_smp := gfx.make_sampler(&smp_desc) |
| 51 | return sg_img, sg_smp |
| 52 | } |
| 53 | |
| 54 | pub fn destroy_texture(sg_img gfx.Image) { |
| 55 | gfx.destroy_image(sg_img) |
| 56 | } |
| 57 | |
| 58 | pub fn load_texture(file_name string) (gfx.Image, gfx.Sampler) { |
| 59 | buffer := read_bytes_from_file(file_name) |
| 60 | stbi.set_flip_vertically_on_load(true) |
| 61 | img := stbi.load_from_memory(buffer.data, buffer.len) or { |
| 62 | eprintln('Texture file: [${file_name}] ERROR!') |
| 63 | exit(0) |
| 64 | } |
| 65 | sg_img, sg_smp := create_texture(int(img.width), int(img.height), img.data) |
| 66 | img.free() |
| 67 | return sg_img, sg_smp |
| 68 | } |
| 69 | |
| 70 | /****************************************************************************** |
| 71 | * Pipeline |
| 72 | ******************************************************************************/ |
| 73 | pub fn (mut obj_part ObjPart) create_pipeline(in_part []int, shader gfx.Shader, texture gfx.Image, sampler gfx.Sampler) Render_data { |
| 74 | mut res := Render_data{} |
| 75 | obj_buf := obj_part.get_buffer(in_part) |
| 76 | res.n_vert = obj_buf.n_vertex |
| 77 | res.material = obj_part.part[in_part[0]].material |
| 78 | |
| 79 | // vertex buffer |
| 80 | mut vert_buffer_desc := gfx.BufferDesc{} |
| 81 | unsafe { vmemset(&vert_buffer_desc, 0, int(sizeof(vert_buffer_desc))) } |
| 82 | |
| 83 | vert_buffer_desc.size = usize(obj_buf.vbuf.len * int(sizeof(Vertex_pnct))) |
| 84 | vert_buffer_desc.data = gfx.Range{ |
| 85 | ptr: obj_buf.vbuf.data |
| 86 | size: usize(obj_buf.vbuf.len * int(sizeof(Vertex_pnct))) |
| 87 | } |
| 88 | |
| 89 | vert_buffer_desc.type = .vertexbuffer |
| 90 | vert_buffer_desc.label = &char('vertbuf_part_${in_part:03}'.str) |
| 91 | vbuf := gfx.make_buffer(&vert_buffer_desc) |
| 92 | |
| 93 | // index buffer |
| 94 | mut index_buffer_desc := gfx.BufferDesc{} |
| 95 | unsafe { vmemset(&index_buffer_desc, 0, int(sizeof(index_buffer_desc))) } |
| 96 | |
| 97 | index_buffer_desc.size = usize(obj_buf.ibuf.len * int(sizeof(u32))) |
| 98 | index_buffer_desc.data = gfx.Range{ |
| 99 | ptr: obj_buf.ibuf.data |
| 100 | size: usize(obj_buf.ibuf.len * int(sizeof(u32))) |
| 101 | } |
| 102 | |
| 103 | index_buffer_desc.type = .indexbuffer |
| 104 | index_buffer_desc.label = &char('indbuf_part_${in_part:03}'.str) |
| 105 | ibuf := gfx.make_buffer(&index_buffer_desc) |
| 106 | |
| 107 | mut pipdesc := gfx.PipelineDesc{} |
| 108 | unsafe { vmemset(&pipdesc, 0, int(sizeof(pipdesc))) } |
| 109 | pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_pnct)) |
| 110 | |
| 111 | // the constants [C.ATTR_vs_a_Position, C.ATTR_vs_a_Color, C.ATTR_vs_a_Texcoord0] are generated by sokol-shdc |
| 112 | pipdesc.layout.attrs[C.ATTR_vs_a_Position].format = .float3 // x,y,z as f32 |
| 113 | pipdesc.layout.attrs[C.ATTR_vs_a_Normal].format = .float3 // x,y,z as f32 |
| 114 | // pipdesc.layout.attrs[C.ATTR_vs_a_Color].format = .ubyte4n // color as u32 |
| 115 | pipdesc.layout.attrs[C.ATTR_vs_a_Texcoord0].format = .float2 // u,v as f32 |
| 116 | // pipdesc.layout.attrs[C.ATTR_vs_a_Texcoord0].format = .short2n // u,v as u16 |
| 117 | pipdesc.index_type = .uint32 |
| 118 | |
| 119 | color_state := gfx.ColorTargetState{ |
| 120 | blend: gfx.BlendState{ |
| 121 | enabled: true |
| 122 | src_factor_rgb: .src_alpha |
| 123 | dst_factor_rgb: .one_minus_src_alpha |
| 124 | } |
| 125 | } |
| 126 | pipdesc.colors[0] = color_state |
| 127 | |
| 128 | pipdesc.depth = gfx.DepthState{ |
| 129 | write_enabled: true |
| 130 | compare: .less_equal |
| 131 | } |
| 132 | pipdesc.cull_mode = .front |
| 133 | |
| 134 | pipdesc.label = &char('pip_part_${in_part:03}'.str) |
| 135 | |
| 136 | // shader |
| 137 | pipdesc.shader = shader |
| 138 | |
| 139 | res.bind.vertex_buffers[0] = vbuf |
| 140 | res.bind.index_buffer = ibuf |
| 141 | res.bind.fs.images[C.SLOT_tex] = texture |
| 142 | res.bind.fs.samplers[C.SLOT_smp] = sampler |
| 143 | res.pipeline = gfx.make_pipeline(&pipdesc) |
| 144 | // println('Buffers part [${in_part}] init done!') |
| 145 | |
| 146 | return res |
| 147 | } |
| 148 | |
| 149 | /****************************************************************************** |
| 150 | * Render functions |
| 151 | ******************************************************************************/ |
| 152 | // aggregate all the part by materials |
| 153 | pub fn (mut obj_part ObjPart) init_render_data(texture gfx.Image, sampler gfx.Sampler) { |
| 154 | // create shader |
| 155 | // One shader for all the model |
| 156 | shader := gfx.make_shader(voidptr(C.gouraud_shader_desc(gfx.query_backend()))) |
| 157 | |
| 158 | mut part_dict := map[string][]int{} |
| 159 | for i, p in obj_part.part { |
| 160 | if p.faces.len > 0 { |
| 161 | part_dict[p.material] << i |
| 162 | } |
| 163 | } |
| 164 | obj_part.rend_data.clear() |
| 165 | // println("Material dict: ${obj_part.mat_map.keys()}") |
| 166 | |
| 167 | for k, v in part_dict { |
| 168 | // println("${k} => Parts ${v}") |
| 169 | |
| 170 | mut txt := texture |
| 171 | mut smp := sampler |
| 172 | |
| 173 | if k in obj_part.mat_map { |
| 174 | mat_map := obj_part.mat[obj_part.mat_map[k]] |
| 175 | if 'map_Kd' in mat_map.maps { |
| 176 | file_name := mat_map.maps['map_Kd'] |
| 177 | if file_name in obj_part.texture { |
| 178 | txt = obj_part.texture[file_name] |
| 179 | // println("Texture [${file_name}] => from CACHE") |
| 180 | } else { |
| 181 | txt, smp = load_texture(file_name) |
| 182 | obj_part.texture[file_name] = txt |
| 183 | obj_part.sampler[file_name] = smp |
| 184 | // println("Texture [${file_name}] => LOADED") |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | // key := obj_part.texture.keys()[0] |
| 189 | // obj_part.rend_data << obj_part.create_pipeline(v, shader, obj_part.texture[key]) |
| 190 | obj_part.rend_data << obj_part.create_pipeline(v, shader, txt, smp) |
| 191 | } |
| 192 | // println("Texture array len: ${obj_part.texture.len}") |
| 193 | // println("Calc bounding box.") |
| 194 | obj_part.calc_bbox() |
| 195 | println('init_render_data DONE!') |
| 196 | } |
| 197 | |
| 198 | pub fn (obj_part ObjPart) bind_and_draw(rend_data_index int, in_data Shader_data) u32 { |
| 199 | // apply the pipeline and bindings |
| 200 | mut part_render_data := obj_part.rend_data[rend_data_index] |
| 201 | |
| 202 | // pass light position |
| 203 | mut tmp_fs_params := Tmp_fs_param{} |
| 204 | tmp_fs_params.light = in_data.fs_data.light |
| 205 | |
| 206 | if part_render_data.material in obj_part.mat_map { |
| 207 | mat_index := obj_part.mat_map[part_render_data.material] |
| 208 | mat := obj_part.mat[mat_index] |
| 209 | |
| 210 | // ambient |
| 211 | tmp_fs_params.ka = in_data.fs_data.ka |
| 212 | if 'Ka' in mat.ks { |
| 213 | tmp_fs_params.ka = mat.ks['Ka'] |
| 214 | } |
| 215 | |
| 216 | // specular |
| 217 | tmp_fs_params.ks = in_data.fs_data.ks |
| 218 | if 'Ks' in mat.ks { |
| 219 | tmp_fs_params.ks = mat.ks['Ks'] |
| 220 | } |
| 221 | |
| 222 | // specular exponent Ns |
| 223 | if 'Ns' in mat.ns { |
| 224 | tmp_fs_params.ks.e[3] = mat.ns['Ns'] / 1000.0 |
| 225 | } else { |
| 226 | // default value is 10 |
| 227 | tmp_fs_params.ks.e[3] = f32(10) / 1000.0 |
| 228 | } |
| 229 | |
| 230 | // diffuse |
| 231 | tmp_fs_params.kd = in_data.fs_data.kd |
| 232 | if 'Kd' in mat.ks { |
| 233 | tmp_fs_params.kd = mat.ks['Kd'] |
| 234 | } |
| 235 | |
| 236 | // alpha/transparency |
| 237 | if 'Tr' in mat.ns { |
| 238 | tmp_fs_params.kd.e[3] = mat.ns['Tr'] |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | gfx.apply_pipeline(part_render_data.pipeline) |
| 243 | gfx.apply_bindings(part_render_data.bind) |
| 244 | |
| 245 | vs_uniforms_range := gfx.Range{ |
| 246 | ptr: in_data.vs_data |
| 247 | size: usize(in_data.vs_len) |
| 248 | } |
| 249 | fs_uniforms_range := gfx.Range{ |
| 250 | ptr: unsafe { &tmp_fs_params } |
| 251 | size: usize(in_data.fs_len) |
| 252 | } |
| 253 | |
| 254 | gfx.apply_uniforms(.vs, C.SLOT_vs_params, &vs_uniforms_range) |
| 255 | gfx.apply_uniforms(.fs, C.SLOT_fs_params, &fs_uniforms_range) |
| 256 | gfx.draw(0, int(part_render_data.n_vert), 1) |
| 257 | return part_render_data.n_vert |
| 258 | } |
| 259 | |
| 260 | pub fn (obj_part ObjPart) bind_and_draw_all(in_data Shader_data) u32 { |
| 261 | mut n_vert := u32(0) |
| 262 | // println("Parts: ${obj_part.rend_data.len}") |
| 263 | for i, _ in obj_part.rend_data { |
| 264 | n_vert += obj_part.bind_and_draw(i, in_data) |
| 265 | } |
| 266 | return n_vert |
| 267 | } |
| 268 | |
| 269 | pub fn (mut obj_part ObjPart) calc_bbox() { |
| 270 | obj_part.max = m4.Vec4{ |
| 271 | e: [f32(-math.max_f32), -math.max_f32, -math.max_f32, 0]! |
| 272 | } |
| 273 | obj_part.min = m4.Vec4{ |
| 274 | e: [f32(math.max_f32), math.max_f32, math.max_f32, 0]! |
| 275 | } |
| 276 | for v in obj_part.v { |
| 277 | if v.e[0] > obj_part.max.e[0] { |
| 278 | obj_part.max.e[0] = v.e[0] |
| 279 | } |
| 280 | if v.e[1] > obj_part.max.e[1] { |
| 281 | obj_part.max.e[1] = v.e[1] |
| 282 | } |
| 283 | if v.e[2] > obj_part.max.e[2] { |
| 284 | obj_part.max.e[2] = v.e[2] |
| 285 | } |
| 286 | |
| 287 | if v.e[0] < obj_part.min.e[0] { |
| 288 | obj_part.min.e[0] = v.e[0] |
| 289 | } |
| 290 | if v.e[1] < obj_part.min.e[1] { |
| 291 | obj_part.min.e[1] = v.e[1] |
| 292 | } |
| 293 | if v.e[2] < obj_part.min.e[2] { |
| 294 | obj_part.min.e[2] = v.e[2] |
| 295 | } |
| 296 | } |
| 297 | val1 := obj_part.max.mod3() |
| 298 | val2 := obj_part.min.mod3() |
| 299 | if val1 > val2 { |
| 300 | obj_part.radius = f32(val1) |
| 301 | } else { |
| 302 | obj_part.radius = f32(val2) |
| 303 | } |
| 304 | // println("BBox: ${obj_part.min} <=> ${obj_part.max}\nRadius: ${obj_part.radius}") |
| 305 | } |
| 306 | |