fixed render
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
[dependencies]
|
||||
mload = "/Users/john/work/cell-model"
|
||||
sdl3 = "gitea.pockle.world/john/cell-sdl3"
|
||||
sdl3 = "/Users/john/work/cell-sdl3"
|
||||
cell-image = "/Users/john/work/cell-image"
|
||||
|
||||
517
core.cm
517
core.cm
@@ -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,
|
||||
|
||||
@@ -7,16 +7,9 @@ var time_mod = use('time')
|
||||
var retro3d = use('core')
|
||||
|
||||
// Parse command line arguments
|
||||
var model_path = args[0]
|
||||
var model_path = args[0] || "Duck.glb"
|
||||
var style = args[1] || "ps1"
|
||||
|
||||
if (!model_path) {
|
||||
log.console("Usage: cell run examples/modelview.ce <model_path> [style]")
|
||||
log.console(" style: ps1, n64, or saturn (default: ps1)")
|
||||
log.console("Example: cell run examples/modelview.ce mymodel.glb ps1")
|
||||
$_.stop()
|
||||
}
|
||||
|
||||
// Camera orbit state
|
||||
var cam_distance = 5
|
||||
var cam_yaw = 0
|
||||
@@ -36,10 +29,10 @@ function _init() {
|
||||
log.console("retro3d Model Viewer")
|
||||
log.console("Style: " + style)
|
||||
log.console("Loading: " + model_path)
|
||||
|
||||
|
||||
// Initialize retro3d with selected style
|
||||
retro3d.set_style(style)
|
||||
|
||||
|
||||
// Load the model
|
||||
model = retro3d.load_model(model_path)
|
||||
if (!model) {
|
||||
@@ -47,31 +40,33 @@ function _init() {
|
||||
$_.stop()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
log.console("Model loaded with " + text(model.meshes.length) + " mesh(es)")
|
||||
|
||||
// Create transform for the model
|
||||
log.console(" Nodes: " + text(model.nodes.length))
|
||||
log.console(" Textures: " + text(model.textures.length))
|
||||
|
||||
// Create transform for the model (this will be an extra parent transform)
|
||||
transform = retro3d.make_transform()
|
||||
|
||||
|
||||
// Set up lighting
|
||||
retro3d.set_ambient(0.3, 0.3, 0.35)
|
||||
retro3d.set_light_dir(0.5, 1.0, 0.3, 1.0, 0.95, 0.9, 1.0)
|
||||
|
||||
|
||||
// Set up a default material
|
||||
var mat = retro3d.make_material("lit", {
|
||||
color: [1, 1, 1, 1]
|
||||
})
|
||||
retro3d.set_material(mat)
|
||||
|
||||
|
||||
last_time = time_mod.number()
|
||||
|
||||
|
||||
log.console("")
|
||||
log.console("Controls:")
|
||||
log.console(" WASD - Orbit camera")
|
||||
log.console(" Q/E - Zoom in/out")
|
||||
log.console(" R/F - Move target up/down")
|
||||
log.console(" ESC - Exit")
|
||||
|
||||
|
||||
// Start the main loop
|
||||
frame()
|
||||
}
|
||||
@@ -92,7 +87,7 @@ function _update(dt) {
|
||||
cam_pitch -= orbit_speed * dt
|
||||
if (cam_pitch < -1.5) cam_pitch = -1.5
|
||||
}
|
||||
|
||||
|
||||
// Zoom
|
||||
if (retro3d._state.keys_held['q']) {
|
||||
cam_distance -= zoom_speed * dt * cam_distance
|
||||
@@ -102,7 +97,7 @@ function _update(dt) {
|
||||
cam_distance += zoom_speed * dt * cam_distance
|
||||
if (cam_distance > 100) cam_distance = 100
|
||||
}
|
||||
|
||||
|
||||
// Move target up/down
|
||||
if (retro3d._state.keys_held['r']) {
|
||||
cam_target_y += zoom_speed * dt
|
||||
@@ -110,7 +105,7 @@ function _update(dt) {
|
||||
if (retro3d._state.keys_held['f']) {
|
||||
cam_target_y -= zoom_speed * dt
|
||||
}
|
||||
|
||||
|
||||
// Exit on escape
|
||||
if (retro3d._state.keys_held['escape']) {
|
||||
$_.stop()
|
||||
@@ -150,7 +145,7 @@ function _draw() {
|
||||
retro3d.set_material(grid_mat)
|
||||
|
||||
retro3d.begin_lines()
|
||||
retro3d.color(0.3, 0.3, 0.3, 1)
|
||||
retro3d.color(0.3, 1, 0.3, 1)
|
||||
|
||||
var grid_size = 10
|
||||
var grid_step = 1
|
||||
|
||||
375
model.c
375
model.c
@@ -12,6 +12,7 @@ typedef struct {
|
||||
// Vector types
|
||||
typedef struct { float x, y, z; } vec3;
|
||||
typedef struct { float x, y, z, w; } vec4;
|
||||
typedef struct { float x, y, z, w; } quat;
|
||||
|
||||
// Identity matrix
|
||||
static mat4 mat4_identity(void) {
|
||||
@@ -20,13 +21,15 @@ static mat4 mat4_identity(void) {
|
||||
return m;
|
||||
}
|
||||
|
||||
// Matrix multiplication
|
||||
// Matrix multiplication (column-major: result = a * b)
|
||||
// For column-major matrices: r[col][row] = sum(a[k][row] * b[col][k])
|
||||
// Index: col * 4 + row
|
||||
static mat4 mat4_mul(mat4 a, mat4 b) {
|
||||
mat4 r = {0};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
for (int col = 0; col < 4; col++) {
|
||||
for (int row = 0; row < 4; row++) {
|
||||
for (int k = 0; k < 4; k++) {
|
||||
r.m[i * 4 + j] += a.m[i * 4 + k] * b.m[k * 4 + j];
|
||||
r.m[col * 4 + row] += a.m[k * 4 + row] * b.m[col * 4 + k];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +79,51 @@ static mat4 mat4_rotate_z(float rad) {
|
||||
return m;
|
||||
}
|
||||
|
||||
// Matrix from quaternion (column-major)
|
||||
static mat4 mat4_from_quat(quat q) {
|
||||
mat4 m = mat4_identity();
|
||||
float x = q.x, y = q.y, z = q.z, w = q.w;
|
||||
float x2 = x + x, y2 = y + y, z2 = z + z;
|
||||
float xx = x * x2, xy = x * y2, xz = x * z2;
|
||||
float yy = y * y2, yz = y * z2, zz = z * z2;
|
||||
float wx = w * x2, wy = w * y2, wz = w * z2;
|
||||
|
||||
m.m[0] = 1.0f - (yy + zz);
|
||||
m.m[1] = xy + wz;
|
||||
m.m[2] = xz - wy;
|
||||
m.m[3] = 0.0f;
|
||||
|
||||
m.m[4] = xy - wz;
|
||||
m.m[5] = 1.0f - (xx + zz);
|
||||
m.m[6] = yz + wx;
|
||||
m.m[7] = 0.0f;
|
||||
|
||||
m.m[8] = xz + wy;
|
||||
m.m[9] = yz - wx;
|
||||
m.m[10] = 1.0f - (xx + yy);
|
||||
m.m[11] = 0.0f;
|
||||
|
||||
m.m[12] = 0.0f;
|
||||
m.m[13] = 0.0f;
|
||||
m.m[14] = 0.0f;
|
||||
m.m[15] = 1.0f;
|
||||
return m;
|
||||
}
|
||||
|
||||
// Build TRS matrix from translation, quaternion rotation, scale (column-major)
|
||||
static mat4 mat4_trs(vec3 t, quat r, vec3 s) {
|
||||
mat4 rot = mat4_from_quat(r);
|
||||
// Scale the rotation matrix columns
|
||||
rot.m[0] *= s.x; rot.m[1] *= s.x; rot.m[2] *= s.x;
|
||||
rot.m[4] *= s.y; rot.m[5] *= s.y; rot.m[6] *= s.y;
|
||||
rot.m[8] *= s.z; rot.m[9] *= s.z; rot.m[10] *= s.z;
|
||||
// Set translation
|
||||
rot.m[12] = t.x;
|
||||
rot.m[13] = t.y;
|
||||
rot.m[14] = t.z;
|
||||
return rot;
|
||||
}
|
||||
|
||||
// Perspective projection
|
||||
static mat4 mat4_perspective(float fov_deg, float aspect, float near, float far) {
|
||||
mat4 m = {0};
|
||||
@@ -123,78 +171,6 @@ static mat4 mat4_look_at(vec3 eye, vec3 target, vec3 up) {
|
||||
return m;
|
||||
}
|
||||
|
||||
// Compute world matrix from transform object
|
||||
// transform: { x, y, z, rot_x, rot_y, rot_z, scale_x, scale_y, scale_z, parent }
|
||||
JSValue js_model_compute_world_matrix(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1) return JS_ThrowTypeError(js, "compute_world_matrix requires a transform");
|
||||
|
||||
// Walk up parent chain, collecting transforms
|
||||
mat4 matrices[32];
|
||||
int depth = 0;
|
||||
|
||||
JSValue current = JS_DupValue(js, argv[0]);
|
||||
while (!JS_IsNull(current) && depth < 32) {
|
||||
JSValue x_v = JS_GetPropertyStr(js, current, "x");
|
||||
JSValue y_v = JS_GetPropertyStr(js, current, "y");
|
||||
JSValue z_v = JS_GetPropertyStr(js, current, "z");
|
||||
JSValue rx_v = JS_GetPropertyStr(js, current, "rot_x");
|
||||
JSValue ry_v = JS_GetPropertyStr(js, current, "rot_y");
|
||||
JSValue rz_v = JS_GetPropertyStr(js, current, "rot_z");
|
||||
JSValue sx_v = JS_GetPropertyStr(js, current, "scale_x");
|
||||
JSValue sy_v = JS_GetPropertyStr(js, current, "scale_y");
|
||||
JSValue sz_v = JS_GetPropertyStr(js, current, "scale_z");
|
||||
|
||||
double x = 0, y = 0, z = 0;
|
||||
double rx = 0, ry = 0, rz = 0;
|
||||
double sx = 1, sy = 1, sz = 1;
|
||||
|
||||
JS_ToFloat64(js, &x, x_v);
|
||||
JS_ToFloat64(js, &y, y_v);
|
||||
JS_ToFloat64(js, &z, z_v);
|
||||
JS_ToFloat64(js, &rx, rx_v);
|
||||
JS_ToFloat64(js, &ry, ry_v);
|
||||
JS_ToFloat64(js, &rz, rz_v);
|
||||
JS_ToFloat64(js, &sx, sx_v);
|
||||
JS_ToFloat64(js, &sy, sy_v);
|
||||
JS_ToFloat64(js, &sz, sz_v);
|
||||
|
||||
JS_FreeValue(js, x_v);
|
||||
JS_FreeValue(js, y_v);
|
||||
JS_FreeValue(js, z_v);
|
||||
JS_FreeValue(js, rx_v);
|
||||
JS_FreeValue(js, ry_v);
|
||||
JS_FreeValue(js, rz_v);
|
||||
JS_FreeValue(js, sx_v);
|
||||
JS_FreeValue(js, sy_v);
|
||||
JS_FreeValue(js, sz_v);
|
||||
|
||||
// Build local matrix: T * Rz * Ry * Rx * S
|
||||
mat4 T = mat4_translate(x, y, z);
|
||||
mat4 Rx = mat4_rotate_x(rx);
|
||||
mat4 Ry = mat4_rotate_y(ry);
|
||||
mat4 Rz = mat4_rotate_z(rz);
|
||||
mat4 S = mat4_scale(sx, sy, sz);
|
||||
|
||||
mat4 local = mat4_mul(T, mat4_mul(Rz, mat4_mul(Ry, mat4_mul(Rx, S))));
|
||||
matrices[depth++] = local;
|
||||
|
||||
JSValue parent = JS_GetPropertyStr(js, current, "parent");
|
||||
JS_FreeValue(js, current);
|
||||
current = parent;
|
||||
}
|
||||
JS_FreeValue(js, current);
|
||||
|
||||
// Multiply from root to leaf
|
||||
mat4 world = mat4_identity();
|
||||
for (int i = depth - 1; i >= 0; i--) {
|
||||
world = mat4_mul(world, matrices[i]);
|
||||
}
|
||||
|
||||
// Return as blob (64 bytes = 16 floats)
|
||||
return js_new_blob_stoned_copy(js, world.m, sizeof(world.m));
|
||||
}
|
||||
|
||||
// Compute view matrix from look-at parameters
|
||||
JSValue js_model_compute_view_matrix(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
@@ -279,6 +255,173 @@ JSValue js_model_mat4_identity(JSContext *js, JSValue this_val, int argc, JSValu
|
||||
return js_new_blob_stoned_copy(js, m.m, sizeof(m.m));
|
||||
}
|
||||
|
||||
// Create matrix from TRS (translation, quaternion rotation, scale)
|
||||
// Args: tx, ty, tz, qx, qy, qz, qw, sx, sy, sz
|
||||
JSValue js_model_mat4_from_trs(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 10) return JS_ThrowTypeError(js, "mat4_from_trs requires 10 arguments");
|
||||
|
||||
double tx, ty, tz, qx, qy, qz, qw, sx, sy, sz;
|
||||
JS_ToFloat64(js, &tx, argv[0]);
|
||||
JS_ToFloat64(js, &ty, argv[1]);
|
||||
JS_ToFloat64(js, &tz, argv[2]);
|
||||
JS_ToFloat64(js, &qx, argv[3]);
|
||||
JS_ToFloat64(js, &qy, argv[4]);
|
||||
JS_ToFloat64(js, &qz, argv[5]);
|
||||
JS_ToFloat64(js, &qw, argv[6]);
|
||||
JS_ToFloat64(js, &sx, argv[7]);
|
||||
JS_ToFloat64(js, &sy, argv[8]);
|
||||
JS_ToFloat64(js, &sz, argv[9]);
|
||||
|
||||
vec3 t = {tx, ty, tz};
|
||||
quat r = {qx, qy, qz, qw};
|
||||
vec3 s = {sx, sy, sz};
|
||||
|
||||
mat4 m = mat4_trs(t, r, s);
|
||||
return js_new_blob_stoned_copy(js, m.m, sizeof(m.m));
|
||||
}
|
||||
|
||||
// Create matrix from 16-element array (column-major)
|
||||
JSValue js_model_mat4_from_array(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1 || !JS_IsArray(js, argv[0]))
|
||||
return JS_ThrowTypeError(js, "mat4_from_array requires an array of 16 numbers");
|
||||
|
||||
int len = JS_ArrayLength(js, argv[0]);
|
||||
if (len < 16) return JS_ThrowTypeError(js, "mat4_from_array requires 16 elements");
|
||||
|
||||
float m[16];
|
||||
for (int i = 0; i < 16; i++) {
|
||||
JSValue v = JS_GetPropertyUint32(js, argv[0], i);
|
||||
double d = 0.0;
|
||||
JS_ToFloat64(js, &d, v);
|
||||
JS_FreeValue(js, v);
|
||||
m[i] = (float)d;
|
||||
}
|
||||
|
||||
return js_new_blob_stoned_copy(js, m, sizeof(m));
|
||||
}
|
||||
|
||||
// Extract accessor data from a gltf buffer
|
||||
// Args: buffer_blob, view_byte_offset, view_byte_stride (or 0), accessor_byte_offset, count, component_type, type
|
||||
// Returns: blob of floats (always converts to f32)
|
||||
JSValue js_model_extract_accessor(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 7) return JS_ThrowTypeError(js, "extract_accessor requires 7 arguments");
|
||||
|
||||
size_t buf_size;
|
||||
uint8_t *buf = js_get_blob_data(js, &buf_size, argv[0]);
|
||||
if (!buf) return JS_ThrowTypeError(js, "invalid buffer blob");
|
||||
|
||||
int view_offset, view_stride, acc_offset, count;
|
||||
JS_ToInt32(js, &view_offset, argv[1]);
|
||||
JS_ToInt32(js, &view_stride, argv[2]);
|
||||
JS_ToInt32(js, &acc_offset, argv[3]);
|
||||
JS_ToInt32(js, &count, argv[4]);
|
||||
|
||||
const char *comp_type = JS_ToCString(js, argv[5]);
|
||||
const char *type_str = JS_ToCString(js, argv[6]);
|
||||
if (!comp_type || !type_str) {
|
||||
if (comp_type) JS_FreeCString(js, comp_type);
|
||||
if (type_str) JS_FreeCString(js, type_str);
|
||||
return JS_ThrowTypeError(js, "invalid component_type or type");
|
||||
}
|
||||
|
||||
// Determine component count
|
||||
int comp_count = 1;
|
||||
if (strcmp(type_str, "vec2") == 0) comp_count = 2;
|
||||
else if (strcmp(type_str, "vec3") == 0) comp_count = 3;
|
||||
else if (strcmp(type_str, "vec4") == 0) comp_count = 4;
|
||||
else if (strcmp(type_str, "mat2") == 0) comp_count = 4;
|
||||
else if (strcmp(type_str, "mat3") == 0) comp_count = 9;
|
||||
else if (strcmp(type_str, "mat4") == 0) comp_count = 16;
|
||||
|
||||
// Determine component size and type
|
||||
int comp_size = 4;
|
||||
int is_float = 1;
|
||||
int is_signed = 0;
|
||||
if (strcmp(comp_type, "f32") == 0) { comp_size = 4; is_float = 1; }
|
||||
else if (strcmp(comp_type, "u8") == 0) { comp_size = 1; is_float = 0; is_signed = 0; }
|
||||
else if (strcmp(comp_type, "i8") == 0) { comp_size = 1; is_float = 0; is_signed = 1; }
|
||||
else if (strcmp(comp_type, "u16") == 0) { comp_size = 2; is_float = 0; is_signed = 0; }
|
||||
else if (strcmp(comp_type, "i16") == 0) { comp_size = 2; is_float = 0; is_signed = 1; }
|
||||
else if (strcmp(comp_type, "u32") == 0) { comp_size = 4; is_float = 0; is_signed = 0; }
|
||||
|
||||
int element_size = comp_size * comp_count;
|
||||
int stride = view_stride > 0 ? view_stride : element_size;
|
||||
|
||||
JS_FreeCString(js, comp_type);
|
||||
JS_FreeCString(js, type_str);
|
||||
|
||||
// Allocate output (always f32)
|
||||
size_t out_size = count * comp_count * sizeof(float);
|
||||
float *out = malloc(out_size);
|
||||
if (!out) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
uint8_t *src = buf + view_offset + acc_offset;
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint8_t *elem = src + i * stride;
|
||||
for (int c = 0; c < comp_count; c++) {
|
||||
float val = 0.0f;
|
||||
if (is_float) {
|
||||
val = *(float*)(elem + c * comp_size);
|
||||
} else if (comp_size == 1) {
|
||||
val = is_signed ? (float)*(int8_t*)(elem + c) : (float)*(uint8_t*)(elem + c);
|
||||
} else if (comp_size == 2) {
|
||||
val = is_signed ? (float)*(int16_t*)(elem + c * 2) : (float)*(uint16_t*)(elem + c * 2);
|
||||
} else if (comp_size == 4) {
|
||||
val = (float)*(uint32_t*)(elem + c * 4);
|
||||
}
|
||||
out[i * comp_count + c] = val;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue ret = js_new_blob_stoned_copy(js, out, out_size);
|
||||
free(out);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Extract index data from a gltf buffer
|
||||
// Args: buffer_blob, view_byte_offset, accessor_byte_offset, count, component_type
|
||||
// Returns: blob of u16 or u32 indices
|
||||
JSValue js_model_extract_indices(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 5) return JS_ThrowTypeError(js, "extract_indices requires 5 arguments");
|
||||
|
||||
size_t buf_size;
|
||||
uint8_t *buf = js_get_blob_data(js, &buf_size, argv[0]);
|
||||
if (!buf) return JS_ThrowTypeError(js, "invalid buffer blob");
|
||||
|
||||
int view_offset, acc_offset, count;
|
||||
JS_ToInt32(js, &view_offset, argv[1]);
|
||||
JS_ToInt32(js, &acc_offset, argv[2]);
|
||||
JS_ToInt32(js, &count, argv[3]);
|
||||
|
||||
const char *comp_type = JS_ToCString(js, argv[4]);
|
||||
if (!comp_type) return JS_ThrowTypeError(js, "invalid component_type");
|
||||
|
||||
uint8_t *src = buf + view_offset + acc_offset;
|
||||
JSValue ret;
|
||||
|
||||
if (strcmp(comp_type, "u32") == 0) {
|
||||
ret = js_new_blob_stoned_copy(js, src, count * sizeof(uint32_t));
|
||||
} else if (strcmp(comp_type, "u16") == 0) {
|
||||
ret = js_new_blob_stoned_copy(js, src, count * sizeof(uint16_t));
|
||||
} else if (strcmp(comp_type, "u8") == 0) {
|
||||
// Convert u8 to u16
|
||||
uint16_t *out = malloc(count * sizeof(uint16_t));
|
||||
for (int i = 0; i < count; i++) out[i] = src[i];
|
||||
ret = js_new_blob_stoned_copy(js, out, count * sizeof(uint16_t));
|
||||
free(out);
|
||||
} else {
|
||||
JS_FreeCString(js, comp_type);
|
||||
return JS_ThrowTypeError(js, "unsupported index type");
|
||||
}
|
||||
|
||||
JS_FreeCString(js, comp_type);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Pack interleaved vertex data for GPU
|
||||
// Takes separate position, normal, uv, color blobs and packs into interleaved format
|
||||
// Returns: { data: blob, stride: number }
|
||||
@@ -372,16 +515,26 @@ JSValue js_model_pack_vertices(JSContext *js, JSValue this_val, int argc, JSValu
|
||||
}
|
||||
|
||||
// Build uniform buffer for retro3d rendering
|
||||
// Contains: MVP matrix (64), model matrix (64), view matrix (64), projection matrix (64)
|
||||
// ambient (16), light_dir (16), light_color (16), fog params (16), tint (16)
|
||||
// style params (16) = 352 bytes total, padded to 368 for alignment
|
||||
// Layout matches shader struct Uniforms (384 bytes = 96 floats):
|
||||
// float4x4 mvp [0-15] (64 bytes)
|
||||
// float4x4 model [16-31] (64 bytes)
|
||||
// float4x4 view [32-47] (64 bytes)
|
||||
// float4x4 projection [48-63] (64 bytes)
|
||||
// float4 ambient [64-67] (16 bytes) - rgb, unused
|
||||
// float4 light_dir [68-71] (16 bytes) - xyz, unused
|
||||
// float4 light_color [72-75] (16 bytes) - rgb, intensity
|
||||
// float4 fog_params [76-79] (16 bytes) - near, far, unused, enabled
|
||||
// float4 fog_color [80-83] (16 bytes) - rgb, unused
|
||||
// float4 tint [84-87] (16 bytes) - rgba
|
||||
// float4 style_params [88-91] (16 bytes) - style_id, vertex_snap, affine, dither
|
||||
// float4 resolution [92-95] (16 bytes) - w, h, unused, unused
|
||||
JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
|
||||
{
|
||||
if (argc < 1) return JS_ThrowTypeError(js, "build_uniforms requires params object");
|
||||
|
||||
JSValue params = argv[0];
|
||||
|
||||
// Allocate uniform buffer (384 bytes for good alignment)
|
||||
// Allocate uniform buffer (384 bytes = 96 floats)
|
||||
float uniforms[96] = {0};
|
||||
|
||||
// Get matrices
|
||||
@@ -400,20 +553,20 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
|
||||
mat4 proj = proj_m ? *(mat4*)proj_m : mat4_identity();
|
||||
mat4 mvp = mat4_mul(proj, mat4_mul(view, model));
|
||||
|
||||
// MVP at offset 0
|
||||
// MVP at offset 0-15
|
||||
memcpy(&uniforms[0], mvp.m, 64);
|
||||
// Model at offset 16
|
||||
// Model at offset 16-31
|
||||
memcpy(&uniforms[16], model.m, 64);
|
||||
// View at offset 32
|
||||
// View at offset 32-47
|
||||
memcpy(&uniforms[32], view.m, 64);
|
||||
// Projection at offset 48
|
||||
// Projection at offset 48-63
|
||||
memcpy(&uniforms[48], proj.m, 64);
|
||||
|
||||
JS_FreeValue(js, model_v);
|
||||
JS_FreeValue(js, view_v);
|
||||
JS_FreeValue(js, proj_v);
|
||||
|
||||
// Ambient color at offset 64
|
||||
// Ambient color at offset 64-67 (rgb, unused)
|
||||
JSValue ambient_v = JS_GetPropertyStr(js, params, "ambient");
|
||||
if (!JS_IsNull(ambient_v)) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -426,10 +579,10 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
|
||||
} else {
|
||||
uniforms[64] = 0.2f; uniforms[65] = 0.2f; uniforms[66] = 0.2f;
|
||||
}
|
||||
uniforms[67] = 1.0f;
|
||||
uniforms[67] = 0.0f; // unused
|
||||
JS_FreeValue(js, ambient_v);
|
||||
|
||||
// Light direction at offset 68
|
||||
// Light direction at offset 68-71 (xyz, unused)
|
||||
JSValue light_dir_v = JS_GetPropertyStr(js, params, "light_dir");
|
||||
if (!JS_IsNull(light_dir_v)) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -442,10 +595,10 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
|
||||
} else {
|
||||
uniforms[68] = 0.5f; uniforms[69] = 1.0f; uniforms[70] = 0.3f;
|
||||
}
|
||||
uniforms[71] = 0.0f;
|
||||
uniforms[71] = 0.0f; // unused
|
||||
JS_FreeValue(js, light_dir_v);
|
||||
|
||||
// Light color at offset 72
|
||||
// Light color at offset 72-75 (rgb, intensity)
|
||||
JSValue light_color_v = JS_GetPropertyStr(js, params, "light_color");
|
||||
if (!JS_IsNull(light_color_v)) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -460,14 +613,14 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
|
||||
}
|
||||
JS_FreeValue(js, light_color_v);
|
||||
|
||||
// Light intensity
|
||||
// Light intensity at offset 75
|
||||
JSValue light_int_v = JS_GetPropertyStr(js, params, "light_intensity");
|
||||
double light_int = 1.0;
|
||||
JS_ToFloat64(js, &light_int, light_int_v);
|
||||
uniforms[75] = light_int;
|
||||
JS_FreeValue(js, light_int_v);
|
||||
|
||||
// Fog params at offset 76: near, far, r, g, b, enabled
|
||||
// Fog params at offset 76-79 (near, far, unused, enabled)
|
||||
JSValue fog_near_v = JS_GetPropertyStr(js, params, "fog_near");
|
||||
JSValue fog_far_v = JS_GetPropertyStr(js, params, "fog_far");
|
||||
JSValue fog_color_v = JS_GetPropertyStr(js, params, "fog_color");
|
||||
@@ -477,60 +630,63 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
|
||||
JS_ToFloat64(js, &fog_far, fog_far_v);
|
||||
uniforms[76] = fog_near;
|
||||
uniforms[77] = fog_far;
|
||||
uniforms[78] = 0.0f; // unused
|
||||
uniforms[79] = JS_IsNull(fog_color_v) ? 0.0f : 1.0f; // enabled flag
|
||||
|
||||
// Fog color at offset 80-83 (rgb, unused)
|
||||
if (!JS_IsNull(fog_color_v)) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
JSValue c = JS_GetPropertyUint32(js, fog_color_v, i);
|
||||
double val = 0;
|
||||
JS_ToFloat64(js, &val, c);
|
||||
uniforms[78 + i] = val;
|
||||
uniforms[80 + i] = val;
|
||||
JS_FreeValue(js, c);
|
||||
}
|
||||
uniforms[81] = 1.0f; // fog enabled
|
||||
} else {
|
||||
uniforms[78] = 0; uniforms[79] = 0; uniforms[80] = 0;
|
||||
uniforms[81] = 0.0f; // fog disabled
|
||||
uniforms[80] = 0; uniforms[81] = 0; uniforms[82] = 0;
|
||||
}
|
||||
uniforms[83] = 0.0f; // unused
|
||||
|
||||
JS_FreeValue(js, fog_near_v);
|
||||
JS_FreeValue(js, fog_far_v);
|
||||
JS_FreeValue(js, fog_color_v);
|
||||
|
||||
// Tint color at offset 82
|
||||
// Tint color at offset 84-87 (rgba)
|
||||
JSValue tint_v = JS_GetPropertyStr(js, params, "tint");
|
||||
if (!JS_IsNull(tint_v)) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
JSValue c = JS_GetPropertyUint32(js, tint_v, i);
|
||||
double val = 1.0;
|
||||
JS_ToFloat64(js, &val, c);
|
||||
uniforms[82 + i] = val;
|
||||
uniforms[84 + i] = val;
|
||||
JS_FreeValue(js, c);
|
||||
}
|
||||
} else {
|
||||
uniforms[82] = 1; uniforms[83] = 1; uniforms[84] = 1; uniforms[85] = 1;
|
||||
uniforms[84] = 1; uniforms[85] = 1; uniforms[86] = 1; uniforms[87] = 1;
|
||||
}
|
||||
JS_FreeValue(js, tint_v);
|
||||
|
||||
// Style params at offset 86: style_id, vertex_snap, affine_amount, dither
|
||||
// Style params at offset 88-91 (style_id, vertex_snap, affine, dither)
|
||||
JSValue style_v = JS_GetPropertyStr(js, params, "style_id");
|
||||
double style_id = 0;
|
||||
JS_ToFloat64(js, &style_id, style_v);
|
||||
uniforms[86] = style_id;
|
||||
uniforms[88] = style_id;
|
||||
JS_FreeValue(js, style_v);
|
||||
|
||||
// Style-specific params
|
||||
uniforms[87] = (style_id == 0) ? 1.0f : 0.0f; // vertex_snap for PS1
|
||||
uniforms[88] = (style_id == 0) ? 1.0f : 0.0f; // affine texturing for PS1
|
||||
uniforms[89] = (style_id == 2) ? 1.0f : 0.0f; // dither for Saturn
|
||||
uniforms[89] = (style_id == 0) ? 1.0f : 0.0f; // vertex_snap for PS1
|
||||
uniforms[90] = (style_id == 0) ? 1.0f : 0.0f; // affine texturing for PS1
|
||||
uniforms[91] = (style_id == 2) ? 1.0f : 0.0f; // dither for Saturn
|
||||
|
||||
// Resolution for vertex snapping
|
||||
// Resolution at offset 92-95 (w, h, unused, unused)
|
||||
JSValue res_w_v = JS_GetPropertyStr(js, params, "resolution_w");
|
||||
JSValue res_h_v = JS_GetPropertyStr(js, params, "resolution_h");
|
||||
double res_w = 320, res_h = 240;
|
||||
JS_ToFloat64(js, &res_w, res_w_v);
|
||||
JS_ToFloat64(js, &res_h, res_h_v);
|
||||
uniforms[90] = res_w;
|
||||
uniforms[91] = res_h;
|
||||
uniforms[92] = res_w;
|
||||
uniforms[93] = res_h;
|
||||
uniforms[94] = 0.0f; // unused
|
||||
uniforms[95] = 0.0f; // unused
|
||||
JS_FreeValue(js, res_w_v);
|
||||
JS_FreeValue(js, res_h_v);
|
||||
|
||||
@@ -591,12 +747,15 @@ JSValue js_model_u16_blob(JSContext *js, JSValue this_val, int argc, JSValueCons
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_model_funcs[] = {
|
||||
MIST_FUNC_DEF(model, compute_world_matrix, 1),
|
||||
MIST_FUNC_DEF(model, compute_view_matrix, 9),
|
||||
MIST_FUNC_DEF(model, compute_perspective, 4),
|
||||
MIST_FUNC_DEF(model, compute_ortho, 6),
|
||||
MIST_FUNC_DEF(model, mat4_mul, 2),
|
||||
MIST_FUNC_DEF(model, mat4_identity, 0),
|
||||
MIST_FUNC_DEF(model, mat4_from_trs, 10),
|
||||
MIST_FUNC_DEF(model, mat4_from_array, 1),
|
||||
MIST_FUNC_DEF(model, extract_accessor, 7),
|
||||
MIST_FUNC_DEF(model, extract_indices, 5),
|
||||
MIST_FUNC_DEF(model, pack_vertices, 1),
|
||||
MIST_FUNC_DEF(model, build_uniforms, 1),
|
||||
MIST_FUNC_DEF(model, f32_blob, 1),
|
||||
|
||||
Reference in New Issue
Block a user