| 1 | // vtest build: misc-tooling // needs .h files that are produced by `v shader` |
| 2 | /********************************************************************** |
| 3 | * |
| 4 | * .obj viewer |
| 5 | * |
| 6 | * Copyright (c) 2021 Dario Deledda. All rights reserved. |
| 7 | * Use of this source code is governed by an MIT license |
| 8 | * that can be found in the LICENSE file. |
| 9 | * |
| 10 | * Example .obj model of V from SurmanPP |
| 11 | * |
| 12 | * HOW TO COMPILE SHADERS: |
| 13 | * Run `v shader .` in this directory to compile the shaders. |
| 14 | * For more info and help with shader compilation see `docs.md` and `v help shader`. |
| 15 | * |
| 16 | * ALTERNATIVE .OBJ MODELS: |
| 17 | * you can load alternative models putting them in the "assets/model" folder with or without their .mtl file. |
| 18 | * use the program help for further instructions. |
| 19 | * |
| 20 | * TODO: |
| 21 | * - frame counter |
| 22 | **********************************************************************/ |
| 23 | import gg |
| 24 | import gg.m4 |
| 25 | import math |
| 26 | import sokol.sapp |
| 27 | import sokol.gfx |
| 28 | import sokol.sgl |
| 29 | import time |
| 30 | import os |
| 31 | import obj |
| 32 | |
| 33 | // GLSL Include and functions |
| 34 | |
| 35 | #include "@VMODROOT/gouraud.h" # It should be generated with `v shader .` (see the instructions at the top of this file) |
| 36 | |
| 37 | fn C.gouraud_shader_desc(gfx.Backend) &gfx.ShaderDesc |
| 38 | |
| 39 | const win_width = 600 |
| 40 | const win_height = 600 |
| 41 | const bg_color = gg.white |
| 42 | |
| 43 | struct App { |
| 44 | mut: |
| 45 | gg &gg.Context = unsafe { nil } |
| 46 | texture gfx.Image |
| 47 | sampler gfx.Sampler |
| 48 | init_flag bool |
| 49 | frame_count int |
| 50 | |
| 51 | mouse_x int = -1 |
| 52 | mouse_y int = -1 |
| 53 | scroll_y int // mouse wheel value |
| 54 | // time |
| 55 | ticks i64 |
| 56 | // model |
| 57 | obj_part &obj.ObjPart = unsafe { nil } |
| 58 | n_vertex u32 |
| 59 | // init parameters |
| 60 | file_name string |
| 61 | single_material_flag bool |
| 62 | } |
| 63 | |
| 64 | /****************************************************************************** |
| 65 | * Draw functions |
| 66 | ******************************************************************************/ |
| 67 | @[inline] |
| 68 | fn vec4(x f32, y f32, z f32, w f32) m4.Vec4 { |
| 69 | return m4.Vec4{ |
| 70 | e: [x, y, z, w]! |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | fn calc_matrices(w f32, h f32, rx f32, ry f32, in_scale f32, pos m4.Vec4) obj.Mats { |
| 75 | proj := m4.perspective(60, w / h, 0.01, 100.0) // set far plane to 100 fro the zoom function |
| 76 | view := m4.look_at(vec4(f32(0.0), 0, 6, 0), vec4(f32(0), 0, 0, 0), vec4(f32(0), 1, 0, 0)) |
| 77 | view_proj := view * proj |
| 78 | |
| 79 | rxm := m4.rotate(m4.rad(rx), vec4(f32(1), 0, 0, 0)) |
| 80 | rym := m4.rotate(m4.rad(ry), vec4(f32(0), 1, 0, 0)) |
| 81 | |
| 82 | model_pos := m4.unit_m4().translate(pos) |
| 83 | |
| 84 | model_m := (rym * rxm) * model_pos |
| 85 | scale_m := m4.scale(vec4(in_scale, in_scale, in_scale, 1)) |
| 86 | |
| 87 | mv := scale_m * model_m // model view |
| 88 | nm := mv.inverse().transpose() // normal matrix |
| 89 | mvp := mv * view_proj // model view projection |
| 90 | |
| 91 | return obj.Mats{ |
| 92 | mv: mv |
| 93 | mvp: mvp |
| 94 | nm: nm |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | fn draw_model(app App, model_pos m4.Vec4) u32 { |
| 99 | if app.init_flag == false { |
| 100 | return 0 |
| 101 | } |
| 102 | |
| 103 | ws := gg.window_size_real_pixels() |
| 104 | dw := ws.width / 2 |
| 105 | dh := ws.height / 2 |
| 106 | |
| 107 | mut scale := f32(1) |
| 108 | if app.obj_part.radius > 1 { |
| 109 | scale = 1 / (app.obj_part.radius) |
| 110 | } else { |
| 111 | scale = app.obj_part.radius |
| 112 | } |
| 113 | scale *= 3 |
| 114 | |
| 115 | // *** vertex shader uniforms *** |
| 116 | rot := [f32(app.mouse_y), f32(app.mouse_x)] |
| 117 | mut zoom_scale := scale + f32(app.scroll_y) / (app.obj_part.radius * 4) |
| 118 | mats := calc_matrices(dw, dh, rot[0], rot[1], zoom_scale, model_pos) |
| 119 | |
| 120 | mut tmp_vs_param := obj.Tmp_vs_param{ |
| 121 | mv: mats.mv |
| 122 | mvp: mats.mvp |
| 123 | nm: mats.nm |
| 124 | } |
| 125 | |
| 126 | // *** fragment shader uniforms *** |
| 127 | time_ticks := f32(time.ticks() - app.ticks) / 1000 |
| 128 | radius_light := f32(app.obj_part.radius) |
| 129 | x_light := f32(math.cos(time_ticks) * radius_light) |
| 130 | z_light := f32(math.sin(time_ticks) * radius_light) |
| 131 | |
| 132 | mut tmp_fs_params := obj.Tmp_fs_param{} |
| 133 | tmp_fs_params.light = m4.vec3(x_light, radius_light, z_light) |
| 134 | |
| 135 | sd := obj.Shader_data{ |
| 136 | vs_data: unsafe { &tmp_vs_param } |
| 137 | vs_len: int(sizeof(tmp_vs_param)) |
| 138 | fs_data: unsafe { &tmp_fs_params } |
| 139 | fs_len: int(sizeof(tmp_fs_params)) |
| 140 | } |
| 141 | |
| 142 | return app.obj_part.bind_and_draw_all(sd) |
| 143 | } |
| 144 | |
| 145 | fn frame(mut app App) { |
| 146 | // clear |
| 147 | mut color_action := gfx.ColorAttachmentAction{ |
| 148 | load_action: .clear |
| 149 | clear_value: gfx.Color{ |
| 150 | r: 0.0 |
| 151 | g: 0.0 |
| 152 | b: 0.0 |
| 153 | a: 1.0 |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | mut pass_action := gfx.PassAction{} |
| 158 | pass_action.colors[0] = color_action |
| 159 | pass := sapp.create_default_pass(pass_action) |
| 160 | gfx.begin_pass(&pass) |
| 161 | |
| 162 | // render the data |
| 163 | draw_start_glsl(app) |
| 164 | draw_model(app, m4.Vec4{}) |
| 165 | // uncomment if you want a raw benchmark mode |
| 166 | /* |
| 167 | mut n_vertex_drawn := u32(0) |
| 168 | n_x_obj := 20 |
| 169 | |
| 170 | for x in 0..n_x_obj { |
| 171 | for z in 0..30 { |
| 172 | for y in 0..4 { |
| 173 | n_vertex_drawn += draw_model(app, m4.Vec4{e:[f32((x-(n_x_obj>>1))*3),-3 + y*3,f32(-6*z),1]!}) |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | */ |
| 178 | draw_end_glsl(app) |
| 179 | |
| 180 | // println("v:${n_vertex_drawn}") |
| 181 | app.frame_count++ |
| 182 | } |
| 183 | |
| 184 | fn draw_start_glsl(app App) { |
| 185 | if app.init_flag == false { |
| 186 | return |
| 187 | } |
| 188 | ws := gg.window_size_real_pixels() |
| 189 | gfx.apply_viewport(0, 0, ws.width, ws.height, true) |
| 190 | } |
| 191 | |
| 192 | fn draw_end_glsl(app App) { |
| 193 | gfx.end_pass() |
| 194 | gfx.commit() |
| 195 | } |
| 196 | |
| 197 | /****************************************************************************** |
| 198 | * Init / Cleanup |
| 199 | ******************************************************************************/ |
| 200 | fn my_init(mut app App) { |
| 201 | mut object := &obj.ObjPart{} |
| 202 | obj_file_lines := obj.read_lines_from_file(app.file_name) |
| 203 | object.parse_obj_buffer(obj_file_lines, app.single_material_flag) |
| 204 | object.summary() |
| 205 | app.obj_part = object |
| 206 | |
| 207 | // set max vertices, |
| 208 | // for a large number of the same type of object it is better use the instances!! |
| 209 | desc := sapp.create_desc() |
| 210 | gfx.setup(&desc) |
| 211 | sgl_desc := sgl.Desc{ |
| 212 | max_vertices: 128 * 65536 |
| 213 | } |
| 214 | sgl.setup(&sgl_desc) |
| 215 | |
| 216 | // 1x1 pixel white, default texture |
| 217 | unsafe { |
| 218 | tmp_txt := malloc(4) |
| 219 | tmp_txt[0] = u8(0xFF) |
| 220 | tmp_txt[1] = u8(0xFF) |
| 221 | tmp_txt[2] = u8(0xFF) |
| 222 | tmp_txt[3] = u8(0xFF) |
| 223 | app.texture, app.sampler = obj.create_texture(1, 1, tmp_txt) |
| 224 | free(tmp_txt) |
| 225 | } |
| 226 | // glsl |
| 227 | app.obj_part.init_render_data(app.texture, app.sampler) |
| 228 | app.init_flag = true |
| 229 | } |
| 230 | |
| 231 | fn cleanup(mut app App) { |
| 232 | /* |
| 233 | for _, mat in app.obj_part.texture { |
| 234 | obj.destroy_texture(mat) |
| 235 | } |
| 236 | */ |
| 237 | } |
| 238 | |
| 239 | /****************************************************************************** |
| 240 | * events handling |
| 241 | ******************************************************************************/ |
| 242 | fn my_event_manager(mut ev gg.Event, mut app App) { |
| 243 | if ev.typ == .mouse_move { |
| 244 | app.mouse_x = int(ev.mouse_x) |
| 245 | app.mouse_y = int(ev.mouse_y) |
| 246 | } |
| 247 | |
| 248 | if ev.scroll_y != 0 { |
| 249 | app.scroll_y += int(ev.scroll_y) |
| 250 | } |
| 251 | |
| 252 | if ev.typ == .touches_began || ev.typ == .touches_moved { |
| 253 | if ev.num_touches > 0 { |
| 254 | touch_point := ev.touches[0] |
| 255 | app.mouse_x = int(touch_point.pos_x) |
| 256 | app.mouse_y = int(touch_point.pos_y) |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | fn main() { |
| 262 | /* |
| 263 | obj.tst() |
| 264 | exit(0) |
| 265 | */ |
| 266 | // App init |
| 267 | mut app := &App{} |
| 268 | |
| 269 | // app.file_name = 'v.obj' // default object is the v logo |
| 270 | app.file_name = 'utahTeapot.obj' // default object is the v logo |
| 271 | |
| 272 | app.single_material_flag = false |
| 273 | $if !android { |
| 274 | if os.args.len > 3 || (os.args.len >= 2 && os.args[1] in ['-h', '--help', '\\?', '-?']) { |
| 275 | eprintln('Usage:\nshow_obj [file_name:string] [single_material_flag:(true|false)]\n') |
| 276 | eprintln('file_name : name of the .obj file.') |
| 277 | eprintln(' If no file name is passed the default V logo will be showed.') |
| 278 | eprintln(' Try one of the .obj files in the "assets/models/" folder.') |
| 279 | eprintln("single_material_flag: if true the viewer use for all the model's parts the default material") |
| 280 | exit(0) |
| 281 | } |
| 282 | |
| 283 | if os.args.len >= 2 { |
| 284 | app.file_name = os.args[1] |
| 285 | } |
| 286 | if os.args.len >= 3 { |
| 287 | app.single_material_flag = os.args[2].bool() |
| 288 | } |
| 289 | println('Loading model: ${app.file_name}') |
| 290 | println('Using single material: ${app.single_material_flag}') |
| 291 | } |
| 292 | |
| 293 | app.gg = gg.new_context( |
| 294 | width: win_width |
| 295 | height: win_height |
| 296 | create_window: true |
| 297 | window_title: 'V Wavefront OBJ viewer - Use the mouse wheel to zoom' |
| 298 | user_data: app |
| 299 | bg_color: bg_color |
| 300 | frame_fn: frame |
| 301 | init_fn: my_init |
| 302 | cleanup_fn: cleanup |
| 303 | event_fn: my_event_manager |
| 304 | ) |
| 305 | |
| 306 | app.ticks = time.ticks() |
| 307 | app.gg.run() |
| 308 | } |
| 309 | |