fixed render

This commit is contained in:
2025-12-13 00:46:00 -06:00
parent 42f7048a56
commit 863bc7fe9b
4 changed files with 742 additions and 192 deletions

517
core.cm
View File

@@ -1,7 +1,7 @@
// retro3d fantasy game console
var io = use('fd')
var time_mod = use('time')
var blob = use('blob')
var blob_mod = use('blob')
var video = use('sdl3/video')
var gpu_mod = use('sdl3/gpu')
var events = use('sdl3/events')
@@ -9,6 +9,7 @@ var keyboard = use('sdl3/keyboard')
var gltf = use('mload/gltf')
var obj_loader = use('mload/obj')
var model_c = use('model')
var png = use('cell-image/png')
// Internal state
var _state = {
@@ -166,47 +167,274 @@ function log_msg() {
function load_model(path) {
var data = io.slurp(path)
if (!data) return null
var ext = path.slice(path.lastIndexOf('.') + 1).toLowerCase()
var parsed = null
if (ext == "gltf" || ext == "glb") {
parsed = gltf.decode(data)
} else if (ext == "obj") {
parsed = obj_loader.decode(data)
} else {
if (ext == "obj") {
var parsed = obj_loader.decode(data)
if (!parsed) return null
return _load_obj_model(parsed)
}
if (ext != "gltf" && ext != "glb") {
log.console("retro3d: unsupported model format: " + ext)
return null
}
if (!parsed || parsed.mesh_count == 0) return null
// Process meshes into GPU-ready format
// Parse gltf
var g = gltf.decode(data)
if (!g) return null
// Get the main buffer blob
var buffer_blob = g.buffers[0] ? g.buffers[0].blob : null
if (!buffer_blob) {
log.console("retro3d: gltf has no buffer data")
return null
}
// Build model structure
var model = {
meshes: [],
animations: parsed.animations || [],
animation_count: parsed.animation_count || 0
nodes: [],
root_nodes: [],
textures: [],
materials: g.materials || [],
animations: g.animations || [],
animation_count: g.animations ? g.animations.length : 0,
_gltf: g
}
// Load textures from embedded images
for (var ti = 0; ti < g.images.length; ti++) {
var img = g.images[ti]
var tex = null
if (img.kind == "buffer_view" && img.view != null) {
var view = g.views[img.view]
var img_data = _extract_buffer_view(buffer_blob, view)
if (img.mime == "image/png") {
var decoded = png.decode(img_data)
if (decoded) {
tex = _create_texture(decoded.width, decoded.height, decoded.pixels)
}
}
}
model.textures.push(tex)
}
// Build node transforms (preserving hierarchy)
for (var ni = 0; ni < g.nodes.length; ni++) {
var node = g.nodes[ni]
var t = null
if (node.matrix) {
t = make_transform({
local_mat: model_c.mat4_from_array(node.matrix),
has_local_mat: true
})
} else {
var trans = node.translation || [0, 0, 0]
var rot = node.rotation || [0, 0, 0, 1]
var scale = node.scale || [1, 1, 1]
t = make_transform({
x: trans[0], y: trans[1], z: trans[2],
qx: rot[0], qy: rot[1], qz: rot[2], qw: rot[3],
sx: scale[0], sy: scale[1], sz: scale[2]
})
}
t.mesh_index = node.mesh
t.name = node.name
t.gltf_children = node.children || []
model.nodes.push(t)
}
// Set up parent-child relationships
for (var ni = 0; ni < model.nodes.length; ni++) {
var t = model.nodes[ni]
for (var ci = 0; ci < t.gltf_children.length; ci++) {
var child_idx = t.gltf_children[ci]
if (child_idx < model.nodes.length) {
transform_set_parent(model.nodes[child_idx], t)
}
}
}
// Find root nodes (those without parents)
for (var ni = 0; ni < model.nodes.length; ni++) {
if (!model.nodes[ni].parent) {
model.root_nodes.push(model.nodes[ni])
}
}
// Process meshes
for (var mi = 0; mi < g.meshes.length; mi++) {
var mesh = g.meshes[mi]
for (var pi = 0; pi < mesh.primitives.length; pi++) {
var prim = mesh.primitives[pi]
var gpu_mesh = _process_gltf_primitive(g, buffer_blob, prim, model.textures)
if (gpu_mesh) {
gpu_mesh.name = mesh.name
gpu_mesh.mesh_index = mi
gpu_mesh.primitive_index = pi
model.meshes.push(gpu_mesh)
}
}
}
return model
}
function _extract_buffer_view(buffer_blob, view) {
// Extract a portion of the buffer as a new blob
var offset = view.byte_offset || 0
var length = view.byte_length
var newblob = buffer_blob.read_blob(offset*8, (offset + length)*8)
return stone(newblob)
}
function _process_gltf_primitive(g, buffer_blob, prim, textures) {
var attrs = prim.attributes
if (!attrs.POSITION) return null
// Get accessors
var pos_acc = g.accessors[attrs.POSITION]
var norm_acc = attrs.NORMAL != null ? g.accessors[attrs.NORMAL] : null
var uv_acc = attrs.TEXCOORD_0 != null ? g.accessors[attrs.TEXCOORD_0] : null
var idx_acc = prim.indices != null ? g.accessors[prim.indices] : null
var vertex_count = pos_acc.count
// Extract position data
var pos_view = g.views[pos_acc.view]
var positions = model_c.extract_accessor(
buffer_blob,
pos_view.byte_offset || 0,
pos_view.byte_stride || 0,
pos_acc.byte_offset || 0,
pos_acc.count,
pos_acc.component_type,
pos_acc.type
)
// Extract normals
var normals = null
if (norm_acc) {
var norm_view = g.views[norm_acc.view]
normals = model_c.extract_accessor(
buffer_blob,
norm_view.byte_offset || 0,
norm_view.byte_stride || 0,
norm_acc.byte_offset || 0,
norm_acc.count,
norm_acc.component_type,
norm_acc.type
)
}
// Extract UVs
var uvs = null
if (uv_acc) {
var uv_view = g.views[uv_acc.view]
uvs = model_c.extract_accessor(
buffer_blob,
uv_view.byte_offset || 0,
uv_view.byte_stride || 0,
uv_acc.byte_offset || 0,
uv_acc.count,
uv_acc.component_type,
uv_acc.type
)
}
// Extract indices
var indices = null
var index_count = 0
var index_type = "uint16"
if (idx_acc) {
var idx_view = g.views[idx_acc.view]
indices = model_c.extract_indices(
buffer_blob,
idx_view.byte_offset || 0,
idx_acc.byte_offset || 0,
idx_acc.count,
idx_acc.component_type
)
index_count = idx_acc.count
index_type = idx_acc.component_type == "u32" ? "uint32" : "uint16"
}
// Pack vertices
var mesh_data = {
vertex_count: vertex_count,
positions: positions,
normals: normals,
uvs: uvs,
colors: null
}
var packed = model_c.pack_vertices(mesh_data)
// Create GPU buffers
var vertex_buffer = _create_vertex_buffer(packed.data)
var index_buffer = indices ? _create_index_buffer(indices) : null
// Get material texture
var texture = null
if (prim.material != null && g.materials[prim.material]) {
var mat = g.materials[prim.material]
if (mat.pbr && mat.pbr.base_color_texture) {
var tex_info = mat.pbr.base_color_texture
var tex_obj = g.textures[tex_info.texture]
if (tex_obj && tex_obj.image != null && textures[tex_obj.image]) {
texture = textures[tex_obj.image]
}
}
}
return {
vertex_buffer: vertex_buffer,
index_buffer: index_buffer,
index_count: index_count,
index_type: index_type,
vertex_count: vertex_count,
material_index: prim.material,
texture: texture
}
}
function _load_obj_model(parsed) {
if (!parsed || !parsed.meshes || parsed.meshes.length == 0) return null
var model = {
meshes: [],
nodes: [],
root_nodes: [],
textures: [],
materials: [],
animations: [],
animation_count: 0
}
for (var i = 0; i < parsed.meshes.length; i++) {
var mesh = parsed.meshes[i]
var packed = model_c.pack_vertices(mesh)
// Create GPU buffers
var vertex_buffer = _create_vertex_buffer(packed.data)
var index_buffer = _create_index_buffer(mesh.indices)
model.meshes.push({
vertex_buffer: vertex_buffer,
index_buffer: index_buffer,
index_count: mesh.index_count,
index_type: mesh.index_type,
index_type: mesh.index_type || "uint16",
vertex_count: mesh.vertex_count,
material: mesh.material,
name: mesh.name
name: mesh.name,
texture: null
})
}
// Create a single root transform
var root = make_transform()
model.nodes.push(root)
model.root_nodes.push(root)
return model
}
@@ -379,18 +607,123 @@ function set_music_volume(v) {}
function set_sfx_volume(v) {}
// ============================================================================
// 3) Transforms
// 3) Transforms - Hierarchical with dirty flags
// ============================================================================
function make_transform() {
function make_transform(opts) {
opts = opts || {}
return {
x: 0, y: 0, z: 0,
rot_x: 0, rot_y: 0, rot_z: 0,
scale_x: 1, scale_y: 1, scale_z: 1,
parent: null
parent: opts.parent || null,
children: [],
// TRS (authoring) - translation
x: opts.x || 0,
y: opts.y || 0,
z: opts.z || 0,
// Quaternion rotation
qx: opts.qx || 0,
qy: opts.qy || 0,
qz: opts.qz || 0,
qw: opts.qw != null ? opts.qw : 1,
// Scale
sx: opts.sx != null ? opts.sx : 1,
sy: opts.sy != null ? opts.sy : 1,
sz: opts.sz != null ? opts.sz : 1,
// Matrices (opaque blobs)
local_mat: opts.local_mat || null,
world_mat: null,
// Authority: if true, local_mat is used directly, TRS ignored
has_local_mat: opts.has_local_mat || false,
// Cache invalidation
dirty_local: true,
dirty_world: true
}
}
function transform_set_parent(child, parent) {
if (child.parent) {
var idx = child.parent.children.indexOf(child)
if (idx >= 0) child.parent.children.splice(idx, 1)
}
child.parent = parent
if (parent) parent.children.push(child)
transform_mark_dirty(child)
}
function transform_mark_dirty(t) {
t.dirty_local = true
t.dirty_world = true
for (var i = 0; i < t.children.length; i++) {
transform_mark_world_dirty(t.children[i])
}
}
function transform_mark_world_dirty(t) {
t.dirty_world = true
for (var i = 0; i < t.children.length; i++) {
transform_mark_world_dirty(t.children[i])
}
}
function transform_get_local_matrix(t) {
if (!t.dirty_local && t.local_mat) return t.local_mat
if (t.has_local_mat && t.local_mat) {
t.dirty_local = false
return t.local_mat
}
// Build from TRS
t.local_mat = model_c.mat4_from_trs(
t.x, t.y, t.z,
t.qx, t.qy, t.qz, t.qw,
t.sx, t.sy, t.sz
)
t.dirty_local = false
return t.local_mat
}
function transform_get_world_matrix(t) {
if (!t.dirty_world && t.world_mat) return t.world_mat
var local = transform_get_local_matrix(t)
if (t.parent) {
var parent_world = transform_get_world_matrix(t.parent)
t.world_mat = model_c.mat4_mul(parent_world, local)
} else {
t.world_mat = local
}
t.dirty_world = false
return t.world_mat
}
function transform_set_position(t, x, y, z) {
t.x = x; t.y = y; t.z = z
t.has_local_mat = false
transform_mark_dirty(t)
}
function transform_set_rotation_quat(t, qx, qy, qz, qw) {
t.qx = qx; t.qy = qy; t.qz = qz; t.qw = qw
t.has_local_mat = false
transform_mark_dirty(t)
}
function transform_set_scale(t, sx, sy, sz) {
t.sx = sx; t.sy = sy; t.sz = sz
t.has_local_mat = false
transform_mark_dirty(t)
}
function transform_set_matrix(t, mat) {
t.local_mat = mat
t.has_local_mat = true
transform_mark_dirty(t)
}
// ============================================================================
// 4) Camera & Projection
// ============================================================================
@@ -513,19 +846,69 @@ function clear_depth() {
function draw_model(model, transform, anim_instance) {
if (!model || !model.meshes) return
var world_matrix = model_c.compute_world_matrix(transform)
// Get view and projection matrices
var view_matrix = _compute_view_matrix()
var proj_matrix = _compute_projection_matrix()
var mat = _state.current_material
var tint = mat ? mat.color : [1, 1, 1, 1]
var tex = mat && mat.texture ? mat.texture : _state.white_texture
var uniforms = model_c.build_uniforms({
model: world_matrix,
view: view_matrix,
projection: proj_matrix,
// If transform is provided, use it as an additional parent for the model's root nodes
var extra_transform = transform ? transform_get_world_matrix(transform) : null
// Draw each node that has a mesh
for (var ni = 0; ni < model.nodes.length; ni++) {
var node = model.nodes[ni]
if (node.mesh_index == null) continue
// Find meshes for this node
for (var mi = 0; mi < model.meshes.length; mi++) {
var mesh = model.meshes[mi]
if (mesh.mesh_index != node.mesh_index) continue
// Get world matrix for this node
var node_world = transform_get_world_matrix(node)
// Apply extra transform if provided
var world_matrix = extra_transform
? model_c.mat4_mul(extra_transform, node_world)
: node_world
// Get material/texture
var mat = _state.current_material
var tint = mat ? mat.color : [1, 1, 1, 1]
var tex = mesh.texture || (mat && mat.texture ? mat.texture : _state.white_texture)
// Build uniforms in cell script
var uniforms = _build_uniforms(world_matrix, view_matrix, proj_matrix, tint)
_draw_mesh(mesh, uniforms, tex, mat ? mat.kind : "lit")
_state.draw_calls++
_state.triangles += mesh.index_count / 3
}
}
// If model has no nodes with meshes, draw meshes directly (fallback)
if (model.nodes.length == 0) {
var world_matrix = transform ? transform_get_world_matrix(transform) : model_c.mat4_identity()
var mat = _state.current_material
var tint = mat ? mat.color : [1, 1, 1, 1]
for (var i = 0; i < model.meshes.length; i++) {
var mesh = model.meshes[i]
var tex = mesh.texture || (mat && mat.texture ? mat.texture : _state.white_texture)
var uniforms = _build_uniforms(world_matrix, view_matrix, proj_matrix, tint)
_draw_mesh(mesh, uniforms, tex, mat ? mat.kind : "lit")
_state.draw_calls++
_state.triangles += mesh.index_count / 3
}
}
}
// Build uniform buffer using C helper (blob API doesn't have write_f32)
function _build_uniforms(model_mat, view_mat, proj_mat, tint) {
return model_c.build_uniforms({
model: model_mat,
view: view_mat,
projection: proj_mat,
ambient: _state.ambient,
light_dir: _state.light_dir,
light_color: _state.light_color,
@@ -538,13 +921,6 @@ function draw_model(model, transform, anim_instance) {
resolution_w: _state.resolution_w,
resolution_h: _state.resolution_h
})
for (var i = 0; i < model.meshes.length; i++) {
var mesh = model.meshes[i]
_draw_mesh(mesh, uniforms, tex, mat ? mat.kind : "lit")
_state.draw_calls++
_state.triangles += mesh.index_count / 3
}
}
// Immediate mode
@@ -764,7 +1140,7 @@ function _init_gpu() {
})
// Create GPU device
_state.gpu = new gpu_mod.gpu({ debug: false, shaders_msl: true })
_state.gpu = new gpu_mod.gpu({ debug: true, shaders_msl: true })
_state.gpu.claim_window(_state.window)
// Load shaders
@@ -781,7 +1157,7 @@ function _init_gpu() {
stage: "vertex",
format: "msl",
entrypoint: "vertex_main",
num_uniform_buffers: 1
num_uniform_buffers: 2
})
var frag_shader = new gpu_mod.shader(_state.gpu, {
@@ -789,7 +1165,7 @@ function _init_gpu() {
stage: "fragment",
format: "msl",
entrypoint: "fragment_main",
num_uniform_buffers: 1,
num_uniform_buffers: 2,
num_samplers: 1
})
@@ -853,7 +1229,7 @@ function _init_gpu() {
})
// Create white texture (1x1)
var white_pixels = new blob(32, true)
var white_pixels = new blob_mod(32, true)
_state.white_texture = _create_texture(1, 1, stone(white_pixels))
}
@@ -948,7 +1324,7 @@ function _create_texture(w, h, pixels) {
function _make_model_from_arrays(positions, normals, uvs, indices, colors) {
var vertex_count = positions.length / 3
// Build mesh object
var mesh = {
vertex_count: vertex_count,
@@ -962,18 +1338,24 @@ function _make_model_from_arrays(positions, normals, uvs, indices, colors) {
mesh.indices = model_c.u16_blob(indices)
mesh.index_type = "uint16"
// Pack and create GPU buffers
var packed = model_c.pack_vertices(mesh)
return {
meshes: [{
vertex_buffer: _create_vertex_buffer(packed.data),
index_buffer: _create_index_buffer(mesh.indices),
index_count: mesh.index_count,
index_type: "uint16",
vertex_count: vertex_count
vertex_count: vertex_count,
texture: null,
mesh_index: 0
}],
nodes: [],
root_nodes: [],
textures: [],
materials: [],
animations: [],
animation_count: 0
}
@@ -1079,7 +1461,7 @@ function _end_frame() {
var cmd = _state.gpu.acquire_cmd_buffer()
// Get swapchain pass instead
var swap_pass = cmd.swapchain_pass(_state.window, {
var pass_desc = {
color_targets: [{
texture: null, // Will use swapchain
load: "clear",
@@ -1090,8 +1472,11 @@ function _end_frame() {
b: _state._clear_color[2],
a: _state._clear_color[3]
}
}],
depth_stencil: {
}]
}
if (_state.depth_texture) {
pass_desc.depth_stencil = {
texture: _state.depth_texture,
load: _state._clear_depth ? "clear" : "load",
store: "dont_care",
@@ -1100,7 +1485,9 @@ function _end_frame() {
clear: 1.0,
clear_stencil: 0
}
})
}
var swap_pass = cmd.swapchain_pass(_state.window, pass_desc)
// Draw all pending meshes
var draws = _state._pending_draws || []
@@ -1111,8 +1498,9 @@ function _end_frame() {
swap_pass.bind_vertex_buffers(0, [{ buffer: d.mesh.vertex_buffer, offset: 0 }])
swap_pass.bind_index_buffer({ buffer: d.mesh.index_buffer, offset: 0 }, d.mesh.index_type == "uint32" ? 32 : 16)
cmd.push_vertex_uniform_data(0, d.uniforms)
cmd.push_fragment_uniform_data(0, d.uniforms)
// Shaders use [[buffer(1)]] for uniforms (buffer(0) is vertex data)
cmd.push_vertex_uniform_data(1, d.uniforms)
cmd.push_fragment_uniform_data(1, d.uniforms)
var sampler = _state.style_id == 1 ? _state.sampler_linear : _state.sampler_nearest
swap_pass.bind_fragment_samplers(0, [{ texture: d.texture, sampler: sampler }])
@@ -1154,6 +1542,13 @@ return {
set_sfx_volume: set_sfx_volume,
make_transform: make_transform,
transform_set_parent: transform_set_parent,
transform_set_position: transform_set_position,
transform_set_rotation_quat: transform_set_rotation_quat,
transform_set_scale: transform_set_scale,
transform_set_matrix: transform_set_matrix,
transform_get_local_matrix: transform_get_local_matrix,
transform_get_world_matrix: transform_get_world_matrix,
camera_look_at: camera_look_at,
camera_perspective: camera_perspective,