Created
August 6, 2025 21:54
-
-
Save amirrajan/a64c06b5096bc446e79c96308b30fd6e to your computer and use it in GitHub Desktop.
DragonRuby Game Toolkit - Rinoa Final Fantasy 8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| def tick args | |
| args.outputs.watch "#{GTK.current_framerate}" | |
| if Kernel.tick_count == 0 | |
| r = read_obj_raw 'models/rinoa.obj' | |
| end | |
| args.grid.origin_center! | |
| args.state.obj ||= read_obj_raw 'models/rinoa.obj' | |
| args.state.triangles ||= read_obj 'models/rinoa.obj' | |
| args.state.target_triangles_start ||= 0 | |
| args.state.target_triangles_end ||= 952 | |
| args.state.target_triangles ||= (read_obj 'models/rinoa.obj')[args.state.target_triangles_start..args.state.target_triangles_end] | |
| movement_multiplier = 1000 | |
| args.state.cam_y ||= 100.0 | |
| if args.inputs.keyboard.i | |
| args.state.cam_y += 0.01 | |
| elsif args.inputs.keyboard.k | |
| args.state.cam_y -= 0.01 | |
| end | |
| args.state.cam_angle_y ||= 0 | |
| if args.inputs.keyboard.q | |
| args.state.cam_angle_y += 0.25 | |
| elsif args.inputs.keyboard.e | |
| args.state.cam_angle_y -= 0.25 | |
| end | |
| args.state.cam_angle_x ||= 0 | |
| if args.inputs.keyboard.u | |
| args.state.cam_angle_x += 0.1 | |
| elsif args.inputs.keyboard.o | |
| args.state.cam_angle_x -= 0.1 | |
| end | |
| args.state.cam_angle_z ||= 0 | |
| if args.inputs.keyboard.c | |
| args.state.cam_angle_z += 0.1 | |
| elsif args.inputs.keyboard.z | |
| args.state.cam_angle_z -= 0.1 | |
| end | |
| if args.inputs.mouse.has_focus | |
| y_change_rate = (args.inputs.mouse.x / 640) ** 2 | |
| if args.inputs.mouse.x < 0 | |
| args.state.cam_angle_y -= 1.8 * y_change_rate | |
| else | |
| args.state.cam_angle_y += 1.8 * y_change_rate | |
| end | |
| x_change_rate = (args.inputs.mouse.y / 360) ** 2 | |
| if args.inputs.mouse.y < 0 | |
| args.state.cam_angle_x += 1.8 * x_change_rate | |
| else | |
| args.state.cam_angle_x -= 1.8 * x_change_rate | |
| end | |
| end | |
| args.state.cam_z ||= 1200 | |
| if args.inputs.keyboard.up | |
| point_1 = { x: 0, y: 0.02 } | |
| point_r = args.geometry.rotate_point point_1, args.state.cam_angle_y | |
| args.state.cam_x -= point_r.x * movement_multiplier | |
| args.state.cam_z -= point_r.y * movement_multiplier | |
| elsif args.inputs.keyboard.down | |
| point_1 = { x: 0, y: -0.02 } | |
| point_r = args.geometry.rotate_point point_1, args.state.cam_angle_y | |
| args.state.cam_x -= point_r.x * movement_multiplier | |
| args.state.cam_z -= point_r.y * movement_multiplier | |
| end | |
| args.state.cam_x ||= -4 | |
| if args.inputs.keyboard.right | |
| point_1 = { x: -0.02, y: 0 } | |
| point_r = args.geometry.rotate_point point_1, args.state.cam_angle_y | |
| args.state.cam_x -= point_r.x * movement_multiplier | |
| args.state.cam_z -= point_r.y * movement_multiplier | |
| elsif args.inputs.keyboard.left | |
| point_1 = { x: 0.02, y: 0 } | |
| point_r = args.geometry.rotate_point point_1, args.state.cam_angle_y | |
| args.state.cam_x -= point_r.x * movement_multiplier | |
| args.state.cam_z -= point_r.y * movement_multiplier | |
| end | |
| if args.inputs.keyboard.key_down.r || args.inputs.keyboard.key_down.zero | |
| args.state.cam_x = 0.00 | |
| args.state.cam_y = 0.00 | |
| args.state.cam_z = 1.00 | |
| args.state.cam_angle_y = 0 | |
| args.state.cam_angle_x = 0 | |
| end | |
| camera_matrix = Matrix.mul (translate -args.state.cam_x, -args.state.cam_y, -args.state.cam_z), | |
| (rotate_y args.state.cam_angle_y), | |
| (rotate_x args.state.cam_angle_x), | |
| (rotate_z args.state.cam_angle_z) | |
| args.state.perspective_target_triangles = args.state.target_triangles.map do |t| | |
| per = { p1: perspective(Matrix.mul(t.v[0], camera_matrix)), | |
| p2: perspective(Matrix.mul(t.v[1], camera_matrix)), | |
| p3: perspective(Matrix.mul(t.v[2], camera_matrix)), | |
| source: t } | |
| per.z_avg = (per.p1.z + per.p2.z + per.p3.z) / 3 | |
| per | |
| end | |
| args.state.perspective_target_triangles.sort_by { |t| t.z_avg }.each do |triangle| | |
| if triangle.p1 && triangle.p2 && triangle.p3 | |
| args.state.sprite_sizes ||= {} | |
| p1 = triangle.p1 | |
| p2 = triangle.p2 | |
| p3 = triangle.p3 | |
| path = if triangle.source.vt[0].usemtl | |
| args.state.obj.mtl[triangle.source.vt[0].usemtl].map_Kd | |
| else | |
| :solid | |
| end | |
| if path != :solid | |
| args.state.sprite_sizes[path] ||= args.gtk.calcspritebox path | |
| sprite_w, sprite_h = args.state.sprite_sizes[path] | |
| source_x = triangle.source.vt[0].u * sprite_w | |
| source_y = triangle.source.vt[0].v * sprite_h | |
| source_x2 = triangle.source.vt[1].u * sprite_w | |
| source_y2 = triangle.source.vt[1].v * sprite_h | |
| source_x3 = triangle.source.vt[2].u * sprite_w | |
| source_y3 = triangle.source.vt[2].v * sprite_h | |
| r = 255 | |
| g = 255 | |
| b = 255 | |
| a = 255 | |
| else | |
| source_x = 0 | |
| source_y = 0 | |
| source_x2 = 100 | |
| source_y2 = 0 | |
| source_x3 = 100 | |
| source_y3 = 100 | |
| r = 255 | |
| g = 0 | |
| b = 0 | |
| a = 80 | |
| end | |
| args.outputs.sprites << { | |
| x: p1.x, | |
| y: p1.y, | |
| x2: p2.x, | |
| y2: p2.y, | |
| x3: p3.x, | |
| y3: p3.y, | |
| source_x: source_x, | |
| source_y: source_y, | |
| source_x2: source_x2, | |
| source_y2: source_y2, | |
| source_x3: source_x3, | |
| source_y3: source_y3, | |
| path: path, | |
| r: r, g: g, b: b, a: a | |
| } | |
| end | |
| end | |
| # args.outputs.watch "#{args.state.cam_x}" | |
| # args.outputs.watch "#{args.state.cam_y}" | |
| # args.outputs.watch "#{args.state.cam_z}" | |
| end | |
| def perspective vec | |
| left = 100.0 | |
| right = -100.0 | |
| bottom = 100.0 | |
| top = -100.0 | |
| near = 3000.0 | |
| far = 8000.0 | |
| sx = 2 * near / (right - left) | |
| sy = 2 * near / (top - bottom) | |
| c2 = - (far + near) / (far - near) | |
| c1 = 2 * near * far / (near - far) | |
| tx = -near * (left + right) / (right - left) | |
| ty = -near * (bottom + top) / (top - bottom) | |
| p = Matrix.mat4 sx, 0, 0, tx, | |
| 0, sy, 0, ty, | |
| 0, 0, c2, c1, | |
| 0, 0, -1, 0 | |
| r = Matrix.mul vec, p | |
| return nil if r.w < 0 | |
| r.x *= r.z / r.w / 100 | |
| r.y *= r.z / r.w / 100 | |
| Matrix.vec4(r.x, r.y, r.z, r.w) | |
| end | |
| def translate dx, dy, dz | |
| Matrix.mat4 1, 0, 0, dx, | |
| 0, 1, 0, dy, | |
| 0, 0, 1, dz, | |
| 0, 0, 0, 1 | |
| end | |
| def rotate_y angle_d | |
| cos_t = Math.cos angle_d.to_radians | |
| sin_t = Math.sin angle_d.to_radians | |
| (Matrix.mat4 cos_t, 0, sin_t, 0, | |
| 0, 1, 0, 0, | |
| -sin_t, 0, cos_t, 0, | |
| 0, 0, 0, 1) | |
| end | |
| def rotate_x angle_d | |
| cos_t = Math.cos angle_d.to_radians | |
| sin_t = Math.sin angle_d.to_radians | |
| (Matrix.mat4 1, 0, 0, 0, | |
| 0, cos_t, -sin_t, 0, | |
| 0, sin_t, cos_t, 0, | |
| 0, 0, 0, 1) | |
| end | |
| def scale sx, sy, sz | |
| (Matrix.mat4 sx, 0, 0, 0, | |
| 0, sy, 0, 0, | |
| 0, 0, sz, 0, | |
| 0, 0, 0, 1) | |
| end | |
| def rotate_z angle_d | |
| cos_t = Math.cos angle_d.to_radians | |
| sin_t = Math.sin angle_d.to_radians | |
| (Matrix.mat4 cos_t, -sin_t, 0, 0, | |
| sin_t, cos_t, 0, 0, | |
| 0, 0, 1, 0, | |
| 0, 0, 0, 1) | |
| end | |
| def read_obj_raw path | |
| contents = ($gtk.read_file path) | |
| root = { | |
| v: [], | |
| f: [], | |
| vt: [], | |
| vn: [], | |
| groups: { default: {} }, | |
| mtllib: [], | |
| mtl: {} | |
| } | |
| current_group = root[:groups][:default] | |
| contents.each_line do |l| | |
| if l.strip.start_with? "v " | |
| x, y, z = l.strip.split(' ')[1..-1].map { |t| t.to_f } | |
| root.v << { x: x, y: y, z: z } | |
| elsif l.strip.start_with? "usemtl " | |
| name = l.strip.split(' ')[1] | |
| current_group[:usemtl] = name | |
| elsif l.strip.start_with? "f " | |
| a, b, c = l.strip.split(' ')[1..-1] | |
| as = a.split('/') | |
| a_v = as[0] ? as[0].to_i - 1 : nil | |
| a_vt = as[1] ? as[1].to_i - 1 : nil | |
| a_vn = as[2] ? as[2].to_i - 1 : nil | |
| bs = b.split('/') | |
| b_v = bs[0] ? bs[0].to_i - 1 : nil | |
| b_vt = bs[1] ? bs[1].to_i - 1 : nil | |
| b_vn = bs[2] ? bs[2].to_i - 1 : nil | |
| cs = c.split('/') | |
| c_v = cs[0] ? cs[0].to_i - 1 : nil | |
| c_vt = cs[1] ? cs[1].to_i - 1 : nil | |
| c_vn = cs[2] ? cs[2].to_i - 1 : nil | |
| root.f << { a: { v: a_v, vt: a_vt, vn: a_vn }, | |
| b: { v: b_v, vt: b_vt, vn: b_vn }, | |
| c: { v: c_v, vt: c_vt, vn: c_vn }, | |
| group: current_group } | |
| elsif l.strip.start_with? "g " | |
| name = l.strip.split(' ')[1] || "" | |
| root[:groups][name] ||= {} | |
| current_group = root[:groups][name] | |
| elsif l.strip.start_with? "mtllib " | |
| name = l.strip.split(' ')[1] | |
| root[:mtllib] << name | |
| elsif l.strip.start_with? "vt " | |
| u, v, w = l.strip.split(' ')[1..-1].map { |t| t.to_f } | |
| u ||= 0 | |
| v ||= 0 | |
| w ||= 0 | |
| u = if u < -1 | |
| u + 2 | |
| elsif u < 0 | |
| u + 1 | |
| elsif u > 1 | |
| u - 1 | |
| else | |
| u | |
| end | |
| v = if v < -1 | |
| v + 2 | |
| elsif v < 0 | |
| v + 1 | |
| elsif v > 1 | |
| v - 1 | |
| else | |
| v | |
| end | |
| w = if w < -1 | |
| w + 2 | |
| elsif w < 0 | |
| w + 1 | |
| elsif w > 1 | |
| w - 1 | |
| else | |
| w | |
| end | |
| root[:vt] ||= [] | |
| root[:vt] << { u: u, v: v, w: w } | |
| elsif l.strip.start_with? "vn " | |
| x, y, z = l.strip.split(' ')[1..-1].map { |t| t.to_f } | |
| root[:vn] ||= [] | |
| root[:vn] << { x: x, y: y, z: z } | |
| end | |
| end | |
| root.mtllib.each do |v| | |
| contents = ($gtk.read_file File.dirname(path) + "/" + v) | |
| current_mtl = nil | |
| contents.each_line do |l| | |
| if l.strip.start_with? "#" | |
| next | |
| elsif l.strip.start_with? "newmtl " | |
| name = l.strip.split(' ')[1] | |
| root.mtl[name] = {} | |
| current_mtl = root.mtl[name] | |
| elsif l.strip.start_with? "Kd " | |
| r, g, b = l.strip.split(' ')[1..-1].map { |t| t.to_f } | |
| current_mtl[:Kd] = { r: r, g: g, b: b } | |
| elsif l.strip.start_with? "Ks " | |
| r, g, b = l.strip.split(' ')[1..-1].map { |t| t.to_f } | |
| current_mtl[:Ks] = { r: r, g: g, b: b } | |
| elsif l.strip.start_with? "Ns " | |
| ns = l.strip.split(' ')[1].to_f | |
| current_mtl[:Ns] = ns | |
| elsif l.strip.start_with? "map_Kd " | |
| map_kd = l.strip.split(' ')[1] | |
| current_mtl[:map_Kd] = File.dirname(path) + "/" + map_kd | |
| end | |
| end | |
| end | |
| root | |
| end | |
| def read_obj path | |
| obj = read_obj_raw path | |
| triangles = obj.f.map do |f| | |
| { | |
| v: [ | |
| Matrix.mul(Matrix.mul(Matrix.vec4(obj.v[f.a.v].x, obj.v[f.a.v].y, obj.v[f.a.v].z, 1), (rotate_z -180)), (scale 0.01, 0.01, 0.01)), | |
| Matrix.mul(Matrix.mul(Matrix.vec4(obj.v[f.b.v].x, obj.v[f.b.v].y, obj.v[f.b.v].z, 1), (rotate_z -180)), (scale 0.01, 0.01, 0.01)), | |
| Matrix.mul(Matrix.mul(Matrix.vec4(obj.v[f.c.v].x, obj.v[f.c.v].y, obj.v[f.c.v].z, 1), (rotate_z -180)), (scale 0.01, 0.01, 0.01)) | |
| ], | |
| vt: [ | |
| f.a.vt ? obj.vt[f.a.vt].merge(f.group) : nil, | |
| f.b.vt ? obj.vt[f.b.vt].merge(f.group) : nil, | |
| f.c.vt ? obj.vt[f.c.vt].merge(f.group) : nil, | |
| ], | |
| vn: [ | |
| f.a.vn ? obj.vn[f.a.vn] : nil, | |
| f.b.vn ? obj.vn[f.b.vn] : nil, | |
| f.c.vn ? obj.vn[f.c.vn] : nil | |
| ], | |
| } | |
| end | |
| triangles | |
| end | |
| $gtk.reset |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment