fix syntax

This commit is contained in:
2026-02-17 16:03:07 -06:00
parent 6df0338c84
commit 8321a991ac
12 changed files with 665 additions and 434 deletions

View File

@@ -12,24 +12,37 @@ function prepare_animations(model) {
if (!buffer_blob) return []
var prepared = []
var ai = null
var anim = null
var channels = null
var duration = null
var ci = null
var chan = null
var sampler = null
var input_acc = null
var output_acc = null
var input_view = null
var times_blob = null
var output_view = null
var values_blob = null
for (var ai = 0; ai < length(g.animations); ai++) {
var anim = g.animations[ai]
var channels = []
var duration = 0
for (ai = 0; ai < length(g.animations); ai++) {
anim = g.animations[ai]
channels = []
duration = 0
for (var ci = 0; ci < length(anim.channels); ci++) {
var chan = anim.channels[ci]
var sampler = anim.samplers[chan.sampler]
for (ci = 0; ci < length(anim.channels); ci++) {
chan = anim.channels[ci]
sampler = anim.samplers[chan.sampler]
if (!sampler) continue
var input_acc = g.accessors[sampler.input]
var output_acc = g.accessors[sampler.output]
input_acc = g.accessors[sampler.input]
output_acc = g.accessors[sampler.output]
if (!input_acc || !output_acc) continue
// Extract times blob
var input_view = g.views[input_acc.view]
var times_blob = model_c.extract_accessor(
input_view = g.views[input_acc.view]
times_blob = model_c.extract_accessor(
buffer_blob,
input_view.byte_offset || 0,
input_view.byte_stride || 0,
@@ -40,8 +53,8 @@ function prepare_animations(model) {
)
// Extract values blob
var output_view = g.views[output_acc.view]
var values_blob = model_c.extract_accessor(
output_view = g.views[output_acc.view]
values_blob = model_c.extract_accessor(
buffer_blob,
output_view.byte_offset || 0,
output_view.byte_stride || 0,
@@ -109,7 +122,8 @@ function clip_name(instance, clip_idx) {
// Find clip by name
function find_clip(instance, name) {
if (!instance.animations) return -1
for (var i = 0; i < length(instance.animations); i++) {
var i = null
for (i = 0; i < length(instance.animations); i++) {
if (instance.animations[i].name == name) return i
}
return -1
@@ -163,16 +177,23 @@ function apply(instance) {
var anim = instance.animations[instance.clip_index]
var model = instance.model
var t = instance.time
var ci = null
var chan = null
var node_idx = null
var node = null
var v = null
var q = null
var s = null
for (var ci = 0; ci < length(anim.channels); ci++) {
var chan = anim.channels[ci]
var node_idx = chan.node
for (ci = 0; ci < length(anim.channels); ci++) {
chan = anim.channels[ci]
node_idx = chan.node
if (node_idx == null || node_idx >= length(model.nodes)) continue
var node = model.nodes[node_idx]
node = model.nodes[node_idx]
if (chan.path == "translation") {
var v = model_c.sample_vec3(chan.times, chan.values, chan.count, t, chan.interpolation)
v = model_c.sample_vec3(chan.times, chan.values, chan.count, t, chan.interpolation)
node.x = v[0]
node.y = v[1]
node.z = v[2]
@@ -180,7 +201,7 @@ function apply(instance) {
node.dirty_local = true
node.dirty_world = true
} else if (chan.path == "rotation") {
var q = model_c.sample_quat(chan.times, chan.values, chan.count, t, chan.interpolation)
q = model_c.sample_quat(chan.times, chan.values, chan.count, t, chan.interpolation)
node.qx = q[0]
node.qy = q[1]
node.qz = q[2]
@@ -189,7 +210,7 @@ function apply(instance) {
node.dirty_local = true
node.dirty_world = true
} else if (chan.path == "scale") {
var s = model_c.sample_vec3(chan.times, chan.values, chan.count, t, chan.interpolation)
s = model_c.sample_vec3(chan.times, chan.values, chan.count, t, chan.interpolation)
node.sx = s[0]
node.sy = s[1]
node.sz = s[2]
@@ -200,8 +221,10 @@ function apply(instance) {
}
// Mark all children dirty (propagate down hierarchy)
for (var ni = 0; ni < length(model.nodes); ni++) {
var n = model.nodes[ni]
var ni = null
var n = null
for (ni = 0; ni < length(model.nodes); ni++) {
n = model.nodes[ni]
if (n.dirty_world) {
_mark_children_dirty(n)
}
@@ -209,8 +232,10 @@ function apply(instance) {
}
function _mark_children_dirty(node) {
for (var i = 0; i < length(node.children); i++) {
var child = node.children[i]
var i = null
var child = null
for (i = 0; i < length(node.children); i++) {
child = node.children[i]
child.dirty_world = true
_mark_children_dirty(child)
}

View File

@@ -17,16 +17,14 @@ function set_aspect(aspect) {
_aspect = aspect
}
function look_at(ex, ey, ez, tx, ty, tz, upx, upy, upz) {
upx = upx != null ? upx : 0
upy = upy != null ? upy : 1
upz = upz != null ? upz : 0
_eye = {x: ex, y: ey, z: ez}
_target = {x: tx, y: ty, z: tz}
_up = {x: upx, y: upy, z: upz}
_view_matrix = model_c.compute_view_matrix(ex, ey, ez, tx, ty, tz, upx, upy, upz)
function look_at(eye, target, up) {
var up_val = up || {x: 0, y: 1, z: 0}
_eye = {x: eye.x, y: eye.y, z: eye.z}
_target = {x: target.x, y: target.y, z: target.z}
_up = {x: up_val.x, y: up_val.y, z: up_val.z}
_view_matrix = model_c.compute_view_matrix(eye.x, eye.y, eye.z, target.x, target.y, target.z, up_val.x, up_val.y, up_val.z)
}
function perspective(fov_deg, near, far) {
@@ -38,12 +36,12 @@ function perspective(fov_deg, near, far) {
_proj_matrix = model_c.compute_perspective(_fov, _aspect, _near, _far)
}
function ortho(left, right, bottom, top, near, far) {
near = near != null ? near : -1
far = far != null ? far : 1
function ortho(bounds, depth) {
var near = depth && depth.near != null ? depth.near : -1
var far = depth && depth.far != null ? depth.far : 1
_projection_type = "ortho"
_proj_matrix = model_c.compute_ortho(left, right, bottom, top, near, far)
_proj_matrix = model_c.compute_ortho(bounds.left, bounds.right, bounds.bottom, bounds.top, near, far)
}
function get_view_matrix() {

View File

@@ -11,35 +11,36 @@ function clear() {
}
function add_sphere(transform, radius, opts) {
opts = opts || {}
var _opts = opts || {}
var c = {
id: _collider_id++,
type: "sphere",
transform: transform,
radius: radius,
layer_mask: opts.layer_mask || 1,
user: opts.user
layer_mask: _opts.layer_mask || 1,
user: _opts.user
}
_colliders.push(c)
return c
}
function add_box(transform, sx, sy, sz, opts) {
opts = opts || {}
function add_box(transform, size, opts) {
var _opts = opts || {}
var c = {
id: _collider_id++,
type: "box",
transform: transform,
sx: sx, sy: sy, sz: sz,
layer_mask: opts.layer_mask || 1,
user: opts.user
sx: size.x, sy: size.y, sz: size.z,
layer_mask: _opts.layer_mask || 1,
user: _opts.user
}
_colliders.push(c)
return c
}
function remove(collider) {
for (var i = 0; i < length(_colliders); i++) {
var i = null
for (i = 0; i < length(_colliders); i++) {
if (_colliders[i].id == collider.id) {
_colliders.splice(i, 1)
return true
@@ -50,14 +51,18 @@ function remove(collider) {
function overlaps(layer_mask_a, layer_mask_b) {
var results = []
for (var i = 0; i < length(_colliders); i++) {
for (var j = i + 1; j < length(_colliders); j++) {
var a = _colliders[i]
var b = _colliders[j]
var i = null
var j = null
var a = null
var b = null
for (i = 0; i < length(_colliders); i++) {
for (j = i + 1; j < length(_colliders); j++) {
a = _colliders[i]
b = _colliders[j]
if (layer_mask_a != null && !(a.layer_mask & layer_mask_a)) continue
if (layer_mask_b != null && !(b.layer_mask & layer_mask_b)) continue
if (_check_collision(a, b)) {
results.push({a: a, b: b})
}
@@ -66,38 +71,54 @@ function overlaps(layer_mask_a, layer_mask_b) {
return results
}
function raycast(ox, oy, oz, dx, dy, dz, opts) {
opts = opts || {}
var max_dist = opts.max_dist || 1000000
var layer_mask = opts.layer_mask || 0xFFFFFFFF
function raycast(origin, direction, opts) {
var _opts = opts || {}
var max_dist = _opts.max_dist || 1000000
var layer_mask = _opts.layer_mask || 0xFFFFFFFF
var ox = origin.x
var oy = origin.y
var oz = origin.z
var dx = direction.x
var dy = direction.y
var dz = direction.z
var closest = null
var closest_dist = null
var i = null
var c = null
var hit = null
var len = null
var org = null
var dir = null
// Normalize direction
var len = math.sqrt(dx*dx + dy*dy + dz*dz)
len = math.sqrt(dx*dx + dy*dy + dz*dz)
if (len < 0.0001) return null
dx /= len
dy /= len
dz /= len
var closest = null
var closest_dist = max_dist
for (var i = 0; i < length(_colliders); i++) {
var c = _colliders[i]
org = {x: ox, y: oy, z: oz}
dir = {x: dx, y: dy, z: dz}
closest_dist = max_dist
for (i = 0; i < length(_colliders); i++) {
c = _colliders[i]
if (!(c.layer_mask & layer_mask)) continue
var hit = null
hit = null
if (c.type == "sphere") {
hit = _ray_sphere(ox, oy, oz, dx, dy, dz, c)
hit = _ray_sphere(org, dir, c)
} else if (c.type == "box") {
hit = _ray_box(ox, oy, oz, dx, dy, dz, c)
hit = _ray_box(org, dir, c)
}
if (hit && hit.distance < closest_dist) {
closest = hit
closest_dist = hit.distance
}
}
return closest
}
@@ -117,49 +138,65 @@ function _get_position(transform) {
function _check_collision(a, b) {
var pa = _get_position(a.transform)
var pb = _get_position(b.transform)
var dx = pb.x - pa.x
var dy = pb.y - pa.y
var dz = pb.z - pa.z
var dist = math.sqrt(dx*dx + dy*dy + dz*dz)
// Simple sphere-sphere approximation
var ra = a.radius || max(a.sx || 0, a.sy || 0, a.sz || 0)
var rb = b.radius || max(b.sx || 0, b.sy || 0, b.sz || 0)
return dist < ra + rb
}
function _ray_sphere(ox, oy, oz, dx, dy, dz, sphere) {
function _ray_sphere(origin, dir, sphere) {
var pos = _get_position(sphere.transform)
var r = sphere.radius
var ox = origin.x
var oy = origin.y
var oz = origin.z
var dx = dir.x
var dy = dir.y
var dz = dir.z
// Vector from ray origin to sphere center
var lx = pos.x - ox
var ly = pos.y - oy
var lz = pos.z - oz
// Project onto ray direction
var tca = lx*dx + ly*dy + lz*dz
var d2 = null
var r2 = null
var thc = null
var t = null
var hx = null
var hy = null
var hz = null
var nx = null
var ny = null
var nz = null
if (tca < 0) return null
var d2 = lx*lx + ly*ly + lz*lz - tca*tca
var r2 = r*r
d2 = lx*lx + ly*ly + lz*lz - tca*tca
r2 = r*r
if (d2 > r2) return null
var thc = math.sqrt(r2 - d2)
var t = tca - thc
thc = math.sqrt(r2 - d2)
t = tca - thc
if (t < 0) t = tca + thc
if (t < 0) return null
var hx = ox + dx*t
var hy = oy + dy*t
var hz = oz + dz*t
hx = ox + dx*t
hy = oy + dy*t
hz = oz + dz*t
// Normal at hit point
var nx = (hx - pos.x) / r
var ny = (hy - pos.y) / r
var nz = (hz - pos.z) / r
nx = (hx - pos.x) / r
ny = (hy - pos.y) / r
nz = (hz - pos.z) / r
return {
x: hx, y: hy, z: hz,
nx: nx, ny: ny, nz: nz,
@@ -168,57 +205,73 @@ function _ray_sphere(ox, oy, oz, dx, dy, dz, sphere) {
}
}
function _ray_box(ox, oy, oz, dx, dy, dz, box) {
function _ray_box(origin, dir, box) {
var pos = _get_position(box.transform)
var ox = origin.x
var oy = origin.y
var oz = origin.z
var dx = dir.x
var dy = dir.y
var dz = dir.z
var hx = (box.sx || 1) / 2
var hy = (box.sy || 1) / 2
var hz = (box.sz || 1) / 2
var minx = pos.x - hx, maxx = pos.x + hx
var miny = pos.y - hy, maxy = pos.y + hy
var minz = pos.z - hz, maxz = pos.z + hz
var tmin = -1000000, tmax = 1000000
var nx = 0, ny = 0, nz = 0
var minx = pos.x - hx
var maxx = pos.x + hx
var miny = pos.y - hy
var maxy = pos.y + hy
var minz = pos.z - hz
var maxz = pos.z + hz
var tmin = -1000000
var tmax = 1000000
var nx = 0
var ny = 0
var nz = 0
var t1 = null
var t2 = null
var tmp = null
var t = null
// X slab
if (abs(dx) > 0.0001) {
var t1 = (minx - ox) / dx
var t2 = (maxx - ox) / dx
if (t1 > t2) { var tmp = t1; t1 = t2; t2 = tmp }
t1 = (minx - ox) / dx
t2 = (maxx - ox) / dx
if (t1 > t2) { tmp = t1; t1 = t2; t2 = tmp }
if (t1 > tmin) { tmin = t1; nx = dx > 0 ? -1 : 1; ny = 0; nz = 0 }
if (t2 < tmax) tmax = t2
} else if (ox < minx || ox > maxx) {
return null
}
// Y slab
if (abs(dy) > 0.0001) {
var t1 = (miny - oy) / dy
var t2 = (maxy - oy) / dy
if (t1 > t2) { var tmp = t1; t1 = t2; t2 = tmp }
t1 = (miny - oy) / dy
t2 = (maxy - oy) / dy
if (t1 > t2) { tmp = t1; t1 = t2; t2 = tmp }
if (t1 > tmin) { tmin = t1; nx = 0; ny = dy > 0 ? -1 : 1; nz = 0 }
if (t2 < tmax) tmax = t2
} else if (oy < miny || oy > maxy) {
return null
}
// Z slab
if (abs(dz) > 0.0001) {
var t1 = (minz - oz) / dz
var t2 = (maxz - oz) / dz
if (t1 > t2) { var tmp = t1; t1 = t2; t2 = tmp }
t1 = (minz - oz) / dz
t2 = (maxz - oz) / dz
if (t1 > t2) { tmp = t1; t1 = t2; t2 = tmp }
if (t1 > tmin) { tmin = t1; nx = 0; ny = 0; nz = dz > 0 ? -1 : 1 }
if (t2 < tmax) tmax = t2
} else if (oz < minz || oz > maxz) {
return null
}
if (tmin > tmax || tmax < 0) return null
var t = tmin > 0 ? tmin : tmax
t = tmin > 0 ? tmin : tmax
if (t < 0) return null
return {
x: ox + dx*t, y: oy + dy*t, z: oz + dz*t,
nx: nx, ny: ny, nz: nz,

311
core.cm
View File

@@ -183,16 +183,19 @@ function stat(name) {
function log_msg() {
var args = []
for (var i = 0; i < length(arguments); i++) {
var i = null
for (i = 0; i < length(arguments); i++) {
args.push(text(arguments[i]))
}
log.console("[lance3d] " + text(args, " "))
}
function set_lighting(opts) {
var d = null
var len = null
if (opts.sun_dir) {
var d = opts.sun_dir
var len = math.sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2])
d = opts.sun_dir
len = math.sqrt(d[0]*d[0] + d[1]*d[1] + d[2]*d[2])
if (len > 0) {
_state.lighting.sun_dir = [d[0]/len, d[1]/len, d[2]/len]
}
@@ -213,8 +216,8 @@ function set_fog(opts) {
// ============================================================================
function load_model(path, opts) {
opts = opts || {}
var tex_tier = opts.type || "normal"
var _opts = opts || {}
var tex_tier = _opts.type || "normal"
var style = _styles[_state.style]
return resources_mod.load_model(path, style, tex_tier)
}
@@ -269,35 +272,45 @@ function make_cube(w, h, d) {
}
function make_sphere(r, segments) {
if (!segments) segments = 12
var _segments = segments || 12
var positions = []
var normals = []
var uvs = []
var indices = []
for (var y = 0; y <= segments; y++) {
var v = y / segments
var theta = v * pi
for (var x = 0; x <= segments; x++) {
var u = x / segments
var phi = u * 2 * pi
var nx = math.sine(theta) * math.cosine(phi)
var ny = math.cosine(theta)
var nz = math.sine(theta) * math.sine(phi)
var y = null
var x = null
var v = null
var theta = null
var u = null
var phi = null
var nx = null
var ny = null
var nz = null
var i = null
for (y = 0; y <= _segments; y++) {
v = y / _segments
theta = v * pi
for (x = 0; x <= _segments; x++) {
u = x / _segments
phi = u * 2 * pi
nx = math.sine(theta) * math.cosine(phi)
ny = math.cosine(theta)
nz = math.sine(theta) * math.sine(phi)
positions.push(nx * r, ny * r, nz * r)
normals.push(nx, ny, nz)
uvs.push(u, v)
}
}
for (var y = 0; y < segments; y++) {
for (var x = 0; x < segments; x++) {
var i = y * (segments + 1) + x
indices.push(i, i + 1, i + segments + 1)
indices.push(i + 1, i + segments + 2, i + segments + 1)
for (y = 0; y < _segments; y++) {
for (x = 0; x < _segments; x++) {
i = y * (_segments + 1) + x
indices.push(i, i + 1, i + _segments + 1)
indices.push(i + 1, i + _segments + 2, i + _segments + 1)
}
}
@@ -305,30 +318,36 @@ function make_sphere(r, segments) {
}
function make_cylinder(r, h, segments) {
if (!segments) segments = 12
var _segments = segments || 12
var positions = []
var normals = []
var uvs = []
var indices = []
var hh = h / 2
for (var i = 0; i <= segments; i++) {
var u = i / segments
var angle = u * 2 * pi
var nx = math.cosine(angle)
var nz = math.sine(angle)
var i = null
var u = null
var angle = null
var nx = null
var nz = null
var base = null
for (i = 0; i <= _segments; i++) {
u = i / _segments
angle = u * 2 * pi
nx = math.cosine(angle)
nz = math.sine(angle)
positions.push(nx * r, -hh, nz * r)
normals.push(nx, 0, nz)
uvs.push(u, 1)
positions.push(nx * r, hh, nz * r)
normals.push(nx, 0, nz)
uvs.push(u, 0)
}
for (var i = 0; i < segments; i++) {
var base = i * 2
for (i = 0; i < _segments; i++) {
base = i * 2
indices.push(base, base + 1, base + 2)
indices.push(base + 1, base + 3, base + 2)
}
@@ -346,8 +365,8 @@ function make_plane(w, h) {
}
function load_texture(path, opts) {
opts = opts || {}
var tier = opts.type || "normal"
var _opts = opts || {}
var tier = _opts.type || "normal"
var style = _styles[_state.style]
return resources_mod.load_texture(path, style, tier)
}
@@ -360,8 +379,10 @@ function anim_info(model) {
if (!model || !model._internal) return []
var internal = model._internal
var result = []
for (var i = 0; i < length(internal.animations); i++) {
var anim = internal.animations[i]
var i = null
var anim = null
for (i = 0; i < length(internal.animations); i++) {
anim = internal.animations[i]
result.push({
name: anim.name || ("clip_" + text(i)),
duration: anim.duration || 0,
@@ -374,69 +395,83 @@ function anim_info(model) {
function sample_pose(model, name, time_val) {
if (!model || !model._internal) return null
var internal = model._internal
// Find animation by name or index
var anim_idx = -1
var i = null
var anim = null
var duration = null
var instance = null
var pose = null
var ni = null
var si = null
var skin = null
var world_matrices = null
var j = null
var node_idx = null
var jnode = null
var palette = null
// Find animation by name or index
if (is_number(name)) {
anim_idx = name
} else {
for (var i = 0; i < length(internal.animations); i++) {
for (i = 0; i < length(internal.animations); i++) {
if (internal.animations[i].name == name) {
anim_idx = i
break
}
}
}
if (anim_idx < 0 || anim_idx >= length(internal.animations)) {
return null
}
var anim = internal.animations[anim_idx]
var duration = anim.duration || 0
anim = internal.animations[anim_idx]
duration = anim.duration || 0
// Clamp time
if (time_val < 0) time_val = 0
if (time_val > duration) time_val = duration
var t = time_val
if (t < 0) t = 0
if (t > duration) t = duration
// Create a temporary animation instance and sample it
var instance = anim_mod.create_instance(internal)
instance = anim_mod.create_instance(internal)
anim_mod.play(instance, anim_idx, false)
anim_mod.set_time(instance, time_val)
anim_mod.set_time(instance, t)
anim_mod.apply(instance)
// Build pose: array of node transforms
var pose = {
pose = {
_internal: internal,
_anim_idx: anim_idx,
_time: time_val,
_time: t,
node_matrices: []
}
for (var ni = 0; ni < length(internal.nodes); ni++) {
for (ni = 0; ni < length(internal.nodes); ni++) {
pose.node_matrices.push(resources_mod.get_transform_world_matrix(internal.nodes[ni]))
}
// Build skin palettes if model has skins
if (internal.skins && length(internal.skins) > 0) {
pose.skin_palettes = []
for (var si = 0; si < length(internal.skins); si++) {
var skin = internal.skins[si]
var world_matrices = []
for (var j = 0; j < length(skin.joints); j++) {
var node_idx = skin.joints[j]
var jnode = internal.nodes[node_idx]
for (si = 0; si < length(internal.skins); si++) {
skin = internal.skins[si]
world_matrices = []
for (j = 0; j < length(skin.joints); j++) {
node_idx = skin.joints[j]
jnode = internal.nodes[node_idx]
if (jnode) {
world_matrices.push(resources_mod.get_transform_world_matrix(jnode))
} else {
world_matrices.push(model_c.mat4_identity())
}
}
var palette = model_c.build_joint_palette(world_matrices, skin.inv_bind, skin.joint_count)
palette = model_c.build_joint_palette(world_matrices, skin.inv_bind, skin.joint_count)
pose.skin_palettes.push(palette)
}
}
return pose
}
@@ -447,24 +482,39 @@ function sample_pose(model, name, time_val) {
function draw_model(model, transform, pose) {
if (!model || !model._internal) return
var internal = model._internal
var view_matrix = camera_mod.get_view_matrix()
var proj_matrix = camera_mod.get_proj_matrix()
var extra_transform = transform || null
// Get skin palettes from pose or build default
var skin_palettes = []
var si = null
var skin = null
var world_matrices = null
var j = null
var node_idx = null
var jnode = null
var jworld = null
var palette = null
var i = null
var entry = null
var mesh = null
var mat = null
var node_world = null
var world_matrix = null
var tex = null
var uniforms = null
// Get skin palettes from pose or build default
if (pose && pose.skin_palettes) {
skin_palettes = pose.skin_palettes
} else if (internal.skins && length(internal.skins) > 0) {
for (var si = 0; si < length(internal.skins); si++) {
var skin = internal.skins[si]
var world_matrices = []
for (var j = 0; j < length(skin.joints); j++) {
var node_idx = skin.joints[j]
var jnode = internal.nodes[node_idx]
for (si = 0; si < length(internal.skins); si++) {
skin = internal.skins[si]
world_matrices = []
for (j = 0; j < length(skin.joints); j++) {
node_idx = skin.joints[j]
jnode = internal.nodes[node_idx]
if (jnode) {
var jworld = resources_mod.get_transform_world_matrix(jnode)
jworld = resources_mod.get_transform_world_matrix(jnode)
if (extra_transform) {
jworld = model_c.mat4_mul(extra_transform, jworld)
}
@@ -473,40 +523,43 @@ function draw_model(model, transform, pose) {
world_matrices.push(model_c.mat4_identity())
}
}
var palette = model_c.build_joint_palette(world_matrices, skin.inv_bind, skin.joint_count)
palette = model_c.build_joint_palette(world_matrices, skin.inv_bind, skin.joint_count)
skin_palettes.push(palette)
}
}
// Draw each mesh in the model array
for (var i = 0; i < length(model); i++) {
var entry = model[i]
var mesh = entry.mesh
var mat = entry.material
var node_idx = entry._node_index
for (i = 0; i < length(model); i++) {
entry = model[i]
mesh = entry.mesh
mat = entry.material
node_idx = entry._node_index
// Get node world matrix (from pose or computed)
var node_world
if (pose && pose.node_matrices && pose.node_matrices[node_idx]) {
node_world = pose.node_matrices[node_idx]
} else {
node_world = resources_mod.get_transform_world_matrix(internal.nodes[node_idx])
}
// Apply extra transform
var world_matrix = extra_transform
world_matrix = extra_transform
? model_c.mat4_mul(extra_transform, node_world)
: node_world
var tex = mesh.texture || mat.color_map || backend.get_white_texture()
var uniforms = _build_uniforms(world_matrix, view_matrix, proj_matrix, mat)
var palette = null
tex = mesh.texture || mat.color_map || backend.get_white_texture()
uniforms = _build_uniforms(world_matrix, view_matrix, proj_matrix, mat)
palette = null
if (mesh.skinned && length(skin_palettes) > 0) {
palette = skin_palettes[0]
}
_queue_draw(mesh, uniforms, tex, mat, palette)
_queue_draw(mesh, uniforms, tex, {
coverage: mat.coverage,
face: mat.face,
palette: palette
})
}
}
@@ -515,40 +568,49 @@ function draw_mesh(mesh, transform, material) {
var proj_matrix = camera_mod.get_proj_matrix()
var world_matrix = transform || model_c.mat4_identity()
var mat = material || _default_material
var tex = mat.color_map || backend.get_white_texture()
var uniforms = _build_uniforms(world_matrix, view_matrix, proj_matrix, mat)
_queue_draw(mesh, uniforms, tex, mat, null)
_queue_draw(mesh, uniforms, tex, {
coverage: mat.coverage,
face: mat.face,
palette: null
})
}
function draw_billboard(texture, x, y, z, size, mat) {
function draw_billboard(texture, pos, size, mat) {
if (!texture) return
size = size || 1.0
mat = mat || _default_material
var _size = size || 1.0
var _mat = mat || _default_material
var cam_eye = camera_mod.get_eye()
var dx = cam_eye.x - x
var dz = cam_eye.z - z
var dx = cam_eye.x - pos.x
var dz = cam_eye.z - pos.z
var yaw = math.atan2(dx, dz)
var q = math_mod.euler_to_quat(0, yaw, 0)
var transform = math_mod.trs_matrix(x, y, z, q.x, q.y, q.z, q.w, size, size, 1)
var transform = math_mod.trs_matrix(
[pos.x, pos.y, pos.z],
[q.x, q.y, q.z, q.w],
[_size, _size, 1]
)
var quad = make_plane(1, 1)
var billboard_mat = {
color_map: texture,
paint: mat.paint || [1, 1, 1, 1],
coverage: mat.coverage || "cutoff",
paint: _mat.paint || [1, 1, 1, 1],
coverage: _mat.coverage || "cutoff",
face: "double",
lamp: "unlit"
}
draw_mesh(quad, transform, billboard_mat)
}
function draw_sprite(texture, x, y, size, mat) {
function draw_sprite(texture, pos, size, mat) {
// 2D sprite - uses orthographic projection
// pos is {x, y}
// TODO: implement 2D sprite rendering
}
@@ -557,18 +619,18 @@ function draw_sprite(texture, x, y, size, mat) {
// ============================================================================
function debug_point(vertex, size) {
size = size || 1.0
var _size = size || 1.0
// TODO: implement point rendering
}
function debug_line(vertex_a, vertex_b, width) {
width = width || 1.0
var _width = width || 1.0
// TODO: implement line rendering
}
function debug_grid(size, step, norm, color) {
norm = norm || {x: 0, y: 1, z: 0}
color = color || [0.5, 0.5, 0.5, 1]
var _norm = norm || {x: 0, y: 1, z: 0}
var _color = color || [0.5, 0.5, 0.5, 1]
// TODO: implement grid rendering
}
@@ -577,8 +639,8 @@ function debug_grid(size, step, norm, color) {
// ============================================================================
function clear(r, g, b, a) {
if (a == null) a = 1.0
_state._clear_color = [r, g, b, a]
var _a = a != null ? a : 1.0
_state._clear_color = [r, g, b, _a]
_state._clear_depth = true
}
@@ -617,10 +679,11 @@ function _end_frame() {
// Check if triangle count exceeds platform budget, warn once per minute
function _check_tri_budget() {
var style = _styles[_state.style]
var now = null
if (!style || !style.tri_budget) return
if (_state.triangles > style.tri_budget) {
var now = time_mod.number()
now = time_mod.number()
// Only warn once per minute (60 seconds)
if (now - _tri_warning_state.last_warn_time >= 60) {
log.console("[lance3d] WARNING: Triangle count " + text(_state.triangles) +
@@ -634,14 +697,14 @@ function _check_tri_budget() {
// Internal helpers
// ============================================================================
function _queue_draw(mesh, uniforms, texture, mat, palette) {
function _queue_draw(mesh, uniforms, texture, opts) {
_state._pending_draws.push({
mesh: mesh,
uniforms: uniforms,
texture: texture,
coverage: mat.coverage || "opaque",
face: mat.face || "single",
palette: palette
coverage: opts.coverage || "opaque",
face: opts.face || "single",
palette: opts.palette
})
}

View File

@@ -71,9 +71,12 @@ function _init() {
player_mat = { paint: [0.85, 0.25, 0.20, 1.0], coverage: "opaque", face: "single", lamp: "lit" }
// Generate trees
for (var i = 0; i < num_trees; i++) {
var x = (lance3d.rand() - 0.5) * 90
var z = (lance3d.rand() - 0.5) * 90
var i = null
var x = null
var z = null
for (i = 0; i < num_trees; i++) {
x = (lance3d.rand() - 0.5) * 90
z = (lance3d.rand() - 0.5) * 90
trees.push({
x: x,
@@ -98,6 +101,9 @@ function _update(dt) {
// Movement input
var forward = 0
var right = 0
var fx = null
var fz = null
var len = null
if (lance3d.key('w')) forward += 1
if (lance3d.key('s')) forward -= 1
if (lance3d.key('d')) right -= 1
@@ -106,11 +112,11 @@ function _update(dt) {
if (forward != 0 || right != 0) {
// Update yaw based on movement direction
player.yaw = math.arc_tangent(forward, -right)
var fx = math.sine(player.yaw)
var fz = math.cosine(player.yaw)
var len = math.sqrt(fx * fx + fz * fz)
fx = math.sine(player.yaw)
fz = math.cosine(player.yaw)
len = math.sqrt(fx * fx + fz * fz)
if (len > 0) {
fx /= len
fz /= len
@@ -122,48 +128,53 @@ function _update(dt) {
}
function _draw() {
var i = null
var tree = null
var trunk_transform = null
var canopy_transform = null
var q = null
var player_transform = null
lance3d.clear(0.55, 0.70, 0.90, 1.0)
// Set up camera following player
lance3d.camera_perspective(55, 0.1, 300)
lance3d.camera_look_at(
player.x + cam_offset.x,
cam_offset.y,
player.z + cam_offset.z,
player.x, 0, player.z
{x: player.x + cam_offset.x, y: cam_offset.y, z: player.z + cam_offset.z},
{x: player.x, y: 0, z: player.z}
)
// Draw ground
lance3d.draw_mesh(ground_mesh, null, ground_mat)
// Draw tree trunks
for (var i = 0; i < length(trees); i++) {
var tree = trees[i]
var trunk_transform = lance3d.trs_matrix(
tree.x, tree.trunk_h / 2, tree.z,
0, 0, 0, 1,
tree.trunk_r, tree.trunk_h, tree.trunk_r
for (i = 0; i < length(trees); i++) {
tree = trees[i]
trunk_transform = lance3d.trs_matrix(
[tree.x, tree.trunk_h / 2, tree.z],
[0, 0, 0, 1],
[tree.trunk_r, tree.trunk_h, tree.trunk_r]
)
lance3d.draw_mesh(trunk_mesh, trunk_transform, trunk_mat)
}
// Draw tree canopies
for (var i = 0; i < length(trees); i++) {
var tree = trees[i]
var canopy_transform = lance3d.trs_matrix(
tree.x, tree.trunk_h + tree.canopy_s / 2, tree.z,
0, 0, 0, 1,
tree.canopy_s, tree.canopy_s, tree.canopy_s
for (i = 0; i < length(trees); i++) {
tree = trees[i]
canopy_transform = lance3d.trs_matrix(
[tree.x, tree.trunk_h + tree.canopy_s / 2, tree.z],
[0, 0, 0, 1],
[tree.canopy_s, tree.canopy_s, tree.canopy_s]
)
lance3d.draw_mesh(canopy_mesh, canopy_transform, tree.canopy_mat)
}
// Draw player
var q = lance3d.euler_to_quat(0, player.yaw, 0)
var player_transform = lance3d.trs_matrix(
player.x, player.y, player.z,
q.x, q.y, q.z, q.w,
0.7, 1.0, 0.7
q = lance3d.euler_to_quat(0, player.yaw, 0)
player_transform = lance3d.trs_matrix(
[player.x, player.y, player.z],
[q.x, q.y, q.z, q.w],
[0.7, 1.0, 0.7]
)
lance3d.draw_mesh(player_mesh, player_transform, player_mat)
}

View File

@@ -55,9 +55,10 @@ function _init() {
log.console("Model loaded with " + text(length(model)) + " mesh(es)")
// Get animation info
var i = null
animations = lance3d.anim_info(model)
log.console(" Animations: " + text(length(animations)))
for (var i = 0; i < length(animations); i++) {
for (i = 0; i < length(animations); i++) {
log.console(" " + text(i) + ": " + animations[i].name + " (" + text(animations[i].duration) + "s)")
}
@@ -87,6 +88,10 @@ function _init() {
}
function _update(dt) {
var i = null
var clip_idx = null
var duration = null
// Handle input for camera orbit
if (lance3d.key('a')) {
cam_yaw -= orbit_speed * dt
@@ -134,9 +139,9 @@ function _update(dt) {
// Switch animation clips with number keys
if (length(animations) > 0) {
for (var i = 1; i <= 9; i++) {
for (i = 1; i <= 9; i++) {
if (lance3d.keyp(text(i))) {
var clip_idx = i - 1
clip_idx = i - 1
if (clip_idx < length(animations)) {
current_anim = clip_idx
anim_time = 0
@@ -160,7 +165,7 @@ function _update(dt) {
// Update animation time
if (anim_playing && length(animations) > 0) {
anim_time += dt * anim_speed
var duration = animations[current_anim].duration
duration = animations[current_anim].duration
if (duration > 0) {
while (anim_time > duration) {
anim_time -= duration
@@ -177,6 +182,8 @@ function _switch_to_style(new_style) {
}
function _draw() {
var pose = null
// Clear with a nice color based on style
if (style == "ps1") {
lance3d.clear(0.1, 0.05, 0.15, 1.0)
@@ -194,11 +201,11 @@ function _draw() {
var cam_y = math.sine(cam_pitch) * cam_distance + cam_target_y
var cam_z = math.cosine(cam_yaw) * math.cosine(cam_pitch) * cam_distance
lance3d.camera_look_at(cam_x, cam_y, cam_z, 0, cam_target_y, 0)
lance3d.camera_look_at({x: cam_x, y: cam_y, z: cam_z}, {x: 0, y: cam_target_y, z: 0})
// Draw the model
if (model) {
var pose = null
pose = null
if (length(animations) > 0) {
pose = lance3d.sample_pose(model, current_anim, anim_time)
}

View File

@@ -29,20 +29,23 @@ function begin_frame() {
// Process SDL events, returns false if quit requested
function process_events() {
var ev
var ev = null
var key_name = null
var pressed = null
var btn_pressed = null
while ((ev = events.poll()) != null) {
if (ev.type == "quit" || ev.type == "window_close_requested") {
return false
}
if (ev.type == "key_down" || ev.type == "key_up") {
var key_name = lower(keyboard.get_key_name(ev.key))
var pressed = ev.type == "key_down"
key_name = lower(keyboard.get_key_name(ev.key))
pressed = ev.type == "key_down"
if (pressed && !_keys_held[key_name]) {
_keys_pressed[key_name] = true
}
_keys_held[key_name] = pressed
// Map WASD to axes
if (key_name == "w") _axes[1] = pressed ? -1 : 0
if (key_name == "s") _axes[1] = pressed ? 1 : 0
@@ -50,7 +53,7 @@ function process_events() {
if (key_name == "d") _axes[0] = pressed ? 1 : 0
}
if (ev.type == "mouse_button_down" || ev.type == "mouse_button_up") {
var btn_pressed = ev.type == "mouse_button_down"
btn_pressed = ev.type == "mouse_button_down"
if (btn_pressed && !_mouse_buttons[ev.button]) {
_mouse_buttons_pressed[ev.button] = true
}

View File

@@ -37,8 +37,8 @@ function scale_matrix(sx, sy, sz) {
return model_c.mat4_from_trs(0, 0, 0, 0, 0, 0, 1, sx, sy, sz)
}
function trs_matrix(x, y, z, qx, qy, qz, qw, sx, sy, sz) {
return model_c.mat4_from_trs(x, y, z, qx, qy, qz, qw, sx, sy, sz)
function trs_matrix(pos, rot, scale) {
return model_c.mat4_from_trs(pos[0], pos[1], pos[2], rot[0], rot[1], rot[2], rot[3], scale[0], scale[1], scale[2])
}
function euler_to_quat(pitch, yaw, roll) {

91
model.c
View File

@@ -284,7 +284,7 @@ JSValue js_model_mat4_from_trs(JSContext *js, JSValue this_val, int argc, JSValu
// 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]))
if (argc < 1 || !JS_IsArray(argv[0]))
return JS_ThrowTypeError(js, "mat4_from_array requires an array of 16 numbers");
int len = JS_ArrayLength(js, argv[0]);
@@ -292,7 +292,7 @@ JSValue js_model_mat4_from_array(JSContext *js, JSValue this_val, int argc, JSVa
float m[16];
for (int i = 0; i < 16; i++) {
JSValue v = JS_GetPropertyUint32(js, argv[0], i);
JSValue v = JS_GetPropertyNumber(js, argv[0], i);
double d = 0.0;
JS_ToFloat64(js, &d, v);
JS_FreeValue(js, v);
@@ -550,14 +550,15 @@ JSValue js_model_pack_vertices(JSContext *js, JSValue this_val, int argc, JSValu
JS_FreeValue(js, joints_v);
JS_FreeValue(js, weights_v);
JSValue result = JS_NewObject(js);
JS_SetPropertyStr(js, result, "data", js_new_blob_stoned_copy(js, packed, total_size));
JS_SetPropertyStr(js, result, "stride", JS_NewInt32(js, stride));
JS_SetPropertyStr(js, result, "vertex_count", JS_NewInt32(js, vertex_count));
JS_SetPropertyStr(js, result, "skinned", JS_NewBool(js, skinned));
JS_FRAME(js);
JS_ROOT(result, JS_NewObject(js));
JS_SetPropertyStr(js, result.val, "data", js_new_blob_stoned_copy(js, packed, total_size));
JS_SetPropertyStr(js, result.val, "stride", JS_NewInt32(js, stride));
JS_SetPropertyStr(js, result.val, "vertex_count", JS_NewInt32(js, vertex_count));
JS_SetPropertyStr(js, result.val, "skinned", JS_NewBool(js, skinned));
free(packed);
return result;
JS_RETURN(result.val);
}
// Build uniform buffer for retro3d rendering
@@ -617,7 +618,7 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
JSValue ambient_v = JS_GetPropertyStr(js, params, "ambient");
if (!JS_IsNull(ambient_v)) {
for (int i = 0; i < 3; i++) {
JSValue c = JS_GetPropertyUint32(js, ambient_v, i);
JSValue c = JS_GetPropertyNumber(js, ambient_v, i);
double val = 0;
JS_ToFloat64(js, &val, c);
uniforms[64 + i] = val;
@@ -633,7 +634,7 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
JSValue light_dir_v = JS_GetPropertyStr(js, params, "light_dir");
if (!JS_IsNull(light_dir_v)) {
for (int i = 0; i < 3; i++) {
JSValue c = JS_GetPropertyUint32(js, light_dir_v, i);
JSValue c = JS_GetPropertyNumber(js, light_dir_v, i);
double val = 0;
JS_ToFloat64(js, &val, c);
uniforms[68 + i] = val;
@@ -649,7 +650,7 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
JSValue light_color_v = JS_GetPropertyStr(js, params, "light_color");
if (!JS_IsNull(light_color_v)) {
for (int i = 0; i < 3; i++) {
JSValue c = JS_GetPropertyUint32(js, light_color_v, i);
JSValue c = JS_GetPropertyNumber(js, light_color_v, i);
double val = 0;
JS_ToFloat64(js, &val, c);
uniforms[72 + i] = val;
@@ -683,7 +684,7 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
// 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);
JSValue c = JS_GetPropertyNumber(js, fog_color_v, i);
double val = 0;
JS_ToFloat64(js, &val, c);
uniforms[80 + i] = val;
@@ -702,7 +703,7 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
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);
JSValue c = JS_GetPropertyNumber(js, tint_v, i);
double val = 1.0;
JS_ToFloat64(js, &val, c);
uniforms[84 + i] = val;
@@ -772,7 +773,7 @@ JSValue js_model_build_uniforms(JSContext *js, JSValue this_val, int argc, JSVal
// Pack JS array of numbers into a float32 blob
JSValue js_model_f32_blob(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
{
if (argc < 1 || !JS_IsArray(js, argv[0]))
if (argc < 1 || !JS_IsArray(argv[0]))
return JS_ThrowTypeError(js, "f32_blob requires an array");
JSValue arr = argv[0];
@@ -783,7 +784,7 @@ JSValue js_model_f32_blob(JSContext *js, JSValue this_val, int argc, JSValueCons
if (!data) return JS_ThrowOutOfMemory(js);
for (int i = 0; i < len; i++) {
JSValue v = JS_GetPropertyUint32(js, arr, (uint32_t)i);
JSValue v = JS_GetPropertyNumber(js, arr, (uint32_t)i);
double d = 0.0;
JS_ToFloat64(js, &d, v);
JS_FreeValue(js, v);
@@ -883,11 +884,12 @@ JSValue js_model_sample_vec3(JSContext *js, JSValue this_val, int argc, JSValueC
JS_FreeCString(js, interp);
if (count <= 0) {
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_NewFloat64(js, 0));
JS_SetPropertyUint32(js, arr, 1, JS_NewFloat64(js, 0));
JS_SetPropertyUint32(js, arr, 2, JS_NewFloat64(js, 0));
return arr;
JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js));
JS_SetPropertyNumber(js, arr.val, 0, JS_NewFloat64(js, 0));
JS_SetPropertyNumber(js, arr.val, 1, JS_NewFloat64(js, 0));
JS_SetPropertyNumber(js, arr.val, 2, JS_NewFloat64(js, 0));
JS_RETURN(arr.val);
}
int idx = find_keyframe(times, count, t);
@@ -913,11 +915,12 @@ JSValue js_model_sample_vec3(JSContext *js, JSValue this_val, int argc, JSValueC
z = v0[2] + factor * (v1[2] - v0[2]);
}
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_NewFloat64(js, x));
JS_SetPropertyUint32(js, arr, 1, JS_NewFloat64(js, y));
JS_SetPropertyUint32(js, arr, 2, JS_NewFloat64(js, z));
return arr;
JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js));
JS_SetPropertyNumber(js, arr.val, 0, JS_NewFloat64(js, x));
JS_SetPropertyNumber(js, arr.val, 1, JS_NewFloat64(js, y));
JS_SetPropertyNumber(js, arr.val, 2, JS_NewFloat64(js, z));
JS_RETURN(arr.val);
}
// Sample quaternion animation track (with slerp for LINEAR)
@@ -943,12 +946,13 @@ JSValue js_model_sample_quat(JSContext *js, JSValue this_val, int argc, JSValueC
JS_FreeCString(js, interp);
if (count <= 0) {
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_NewFloat64(js, 0));
JS_SetPropertyUint32(js, arr, 1, JS_NewFloat64(js, 0));
JS_SetPropertyUint32(js, arr, 2, JS_NewFloat64(js, 0));
JS_SetPropertyUint32(js, arr, 3, JS_NewFloat64(js, 1));
return arr;
JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js));
JS_SetPropertyNumber(js, arr.val, 0, JS_NewFloat64(js, 0));
JS_SetPropertyNumber(js, arr.val, 1, JS_NewFloat64(js, 0));
JS_SetPropertyNumber(js, arr.val, 2, JS_NewFloat64(js, 0));
JS_SetPropertyNumber(js, arr.val, 3, JS_NewFloat64(js, 1));
JS_RETURN(arr.val);
}
int idx = find_keyframe(times, count, t);
@@ -972,12 +976,13 @@ JSValue js_model_sample_quat(JSContext *js, JSValue this_val, int argc, JSValueC
result = quat_slerp(q0, q1, factor);
}
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_NewFloat64(js, result.x));
JS_SetPropertyUint32(js, arr, 1, JS_NewFloat64(js, result.y));
JS_SetPropertyUint32(js, arr, 2, JS_NewFloat64(js, result.z));
JS_SetPropertyUint32(js, arr, 3, JS_NewFloat64(js, result.w));
return arr;
JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js));
JS_SetPropertyNumber(js, arr.val, 0, JS_NewFloat64(js, result.x));
JS_SetPropertyNumber(js, arr.val, 1, JS_NewFloat64(js, result.y));
JS_SetPropertyNumber(js, arr.val, 2, JS_NewFloat64(js, result.z));
JS_SetPropertyNumber(js, arr.val, 3, JS_NewFloat64(js, result.w));
JS_RETURN(arr.val);
}
// Build joint palette for skinning
@@ -988,7 +993,7 @@ JSValue js_model_build_joint_palette(JSContext *js, JSValue this_val, int argc,
if (argc < 3) return JS_ThrowTypeError(js, "build_joint_palette requires 3 arguments");
JSValue worlds_arr = argv[0];
if (!JS_IsArray(js, worlds_arr)) return JS_ThrowTypeError(js, "first arg must be array of world matrices");
if (!JS_IsArray(worlds_arr)) return JS_ThrowTypeError(js, "first arg must be array of world matrices");
size_t inv_bind_size;
float *inv_bind = js_get_blob_data(js, &inv_bind_size, argv[1]);
@@ -1004,7 +1009,7 @@ JSValue js_model_build_joint_palette(JSContext *js, JSValue this_val, int argc,
if (!palette) return JS_ThrowOutOfMemory(js);
for (int j = 0; j < joint_count; j++) {
JSValue world_v = JS_GetPropertyUint32(js, worlds_arr, j);
JSValue world_v = JS_GetPropertyNumber(js, worlds_arr, j);
size_t world_size;
float *world_m = js_get_blob_data(js, &world_size, world_v);
JS_FreeValue(js, world_v);
@@ -1034,12 +1039,12 @@ JSValue js_model_get_node_world(JSContext *js, JSValue this_val, int argc, JSVal
{
if (argc < 2) return JS_ThrowTypeError(js, "get_node_world requires 2 arguments");
if (!JS_IsArray(js, argv[0])) return JS_ThrowTypeError(js, "first arg must be array");
if (!JS_IsArray(argv[0])) return JS_ThrowTypeError(js, "first arg must be array");
int node_idx;
JS_ToInt32(js, &node_idx, argv[1]);
JSValue mat_v = JS_GetPropertyUint32(js, argv[0], node_idx);
JSValue mat_v = JS_GetPropertyNumber(js, argv[0], node_idx);
if (JS_IsNull(mat_v)) {
JS_FreeValue(js, mat_v);
mat4 id = mat4_identity();
@@ -1127,7 +1132,7 @@ JSValue js_model_mat4_invert(JSContext *js, JSValue this_val, int argc, JSValueC
// Pack JS array of numbers into a uint16 blob (little-endian)
JSValue js_model_u16_blob(JSContext *js, JSValue this_val, int argc, JSValueConst *argv)
{
if (argc < 1 || !JS_IsArray(js, argv[0]))
if (argc < 1 || !JS_IsArray(argv[0]))
return JS_ThrowTypeError(js, "u16_blob requires an array");
JSValue arr = argv[0];
@@ -1138,7 +1143,7 @@ JSValue js_model_u16_blob(JSContext *js, JSValue this_val, int argc, JSValueCons
if (!data) return JS_ThrowOutOfMemory(js);
for (int i = 0; i < len; i++) {
JSValue v = JS_GetPropertyUint32(js, arr, (uint32_t)i);
JSValue v = JS_GetPropertyNumber(js, arr, (uint32_t)i);
uint32_t u = 0;
JS_ToUint32(js, &u, v);
JS_FreeValue(js, v);

View File

@@ -58,37 +58,40 @@ function resize_image_for_platform(img, style, tier) {
}
// Create a texture with platform-appropriate sizing, storing original for re-resize
function create_texture_for_platform(w, h, pixels, style, tier) {
var original = { width: w, height: h, pixels: pixels }
function create_texture_for_platform(src, style, tier) {
var original = { width: src.width, height: src.height, pixels: src.pixels }
var img = resize_image_for_platform(original, style, tier)
var tex = _backend.create_texture(img.width, img.height, img.pixels)
// Tag texture with current style and tier for cache invalidation
tex._style_name = style ? style.name : null
tex._tier = tier || "normal"
tex[TEX_ORIGINAL] = original
return tex
}
// Get or create resized texture for current platform
function get_platform_texture(tex, style, tier) {
if (!tex) return _backend.get_white_texture()
// Check if texture needs re-resizing (style changed)
var style_name = style ? style.name : null
var original = null
var img = null
var new_tex = null
if (tex._style_name != style_name || tex._tier != tier) {
var original = tex[TEX_ORIGINAL]
original = tex[TEX_ORIGINAL]
if (original) {
var img = resize_image_for_platform(original, style, tier)
var new_tex = _backend.create_texture(img.width, img.height, img.pixels)
img = resize_image_for_platform(original, style, tier)
new_tex = _backend.create_texture(img.width, img.height, img.pixels)
new_tex._style_name = style_name
new_tex._tier = tier
new_tex[TEX_ORIGINAL] = original
return new_tex
}
}
return tex
}
@@ -100,7 +103,7 @@ function load_texture(path, style, tier) {
if (!img) return null
if (style) {
return create_texture_for_platform(img.width, img.height, img.pixels, style, tier)
return create_texture_for_platform(img, style, tier)
} else {
return _backend.create_texture(img.width, img.height, img.pixels)
}
@@ -127,11 +130,32 @@ function load_model(path, style, tier) {
var textures = []
var materials = []
var original_images = []
var ti = null
var mi = null
var ni = null
var ci = null
var pi = null
var img = null
var tex = null
var gmat = null
var paint = null
var color_map = null
var tex_info = null
var tex_obj = null
var coverage = null
var node = null
var t = null
var child_idx = null
var mesh = null
var prim = null
var gpu_mesh = null
var mat_idx = null
var mat = null
// Load textures with platform-appropriate sizing
for (var ti = 0; ti < length(g.images); ti++) {
var img = g.images[ti]
var tex = null
for (ti = 0; ti < length(g.images); ti++) {
img = g.images[ti]
tex = null
if (img && img.pixels) {
original_images.push({
width: img.pixels.width,
@@ -139,7 +163,7 @@ function load_model(path, style, tier) {
pixels: img.pixels.pixels
})
if (style) {
tex = create_texture_for_platform(img.pixels.width, img.pixels.height, img.pixels.pixels, style, tier)
tex = create_texture_for_platform(img.pixels, style, tier)
} else {
tex = _backend.create_texture(img.pixels.width, img.pixels.height, img.pixels.pixels)
}
@@ -151,27 +175,27 @@ function load_model(path, style, tier) {
// Create materials
var gltf_mats = g.materials || []
for (var mi = 0; mi < length(gltf_mats); mi++) {
var gmat = gltf_mats[mi]
var paint = [1, 1, 1, 1]
for (mi = 0; mi < length(gltf_mats); mi++) {
gmat = gltf_mats[mi]
paint = [1, 1, 1, 1]
if (gmat.pbr && gmat.pbr.base_color_factor) {
paint = array(gmat.pbr.base_color_factor)
}
var color_map = null
color_map = null
if (gmat.pbr && gmat.pbr.base_color_texture) {
var tex_info = gmat.pbr.base_color_texture
var tex_obj = g.textures[tex_info.texture]
tex_info = gmat.pbr.base_color_texture
tex_obj = g.textures[tex_info.texture]
if (tex_obj && tex_obj.image != null && textures[tex_obj.image]) {
color_map = textures[tex_obj.image]
}
}
var coverage = "opaque"
coverage = "opaque"
if (gmat.alpha_mode == "MASK") coverage = "cutoff"
else if (gmat.alpha_mode == "BLEND") coverage = "blend"
materials.push({
color_map: color_map,
paint: paint,
@@ -196,9 +220,9 @@ function load_model(path, style, tier) {
}
// Build node transforms
for (var ni = 0; ni < length(g.nodes); ni++) {
var node = g.nodes[ni]
var t = _make_internal_transform(node)
for (ni = 0; ni < length(g.nodes); ni++) {
node = g.nodes[ni]
t = _make_internal_transform(node)
t.mesh_index = node.mesh
t.name = node.name
t.gltf_children = node.children || []
@@ -206,10 +230,10 @@ function load_model(path, style, tier) {
}
// Set up parent-child relationships
for (var ni = 0; ni < length(internal_model.nodes); ni++) {
var t = internal_model.nodes[ni]
for (var ci = 0; ci < length(t.gltf_children); ci++) {
var child_idx = t.gltf_children[ci]
for (ni = 0; ni < length(internal_model.nodes); ni++) {
t = internal_model.nodes[ni]
for (ci = 0; ci < length(t.gltf_children); ci++) {
child_idx = t.gltf_children[ci]
if (child_idx < length(internal_model.nodes)) {
_transform_set_parent(internal_model.nodes[child_idx], t)
}
@@ -217,18 +241,18 @@ function load_model(path, style, tier) {
}
// Find root nodes
for (var ni = 0; ni < length(internal_model.nodes); ni++) {
for (ni = 0; ni < length(internal_model.nodes); ni++) {
if (!internal_model.nodes[ni].parent) {
internal_model.root_nodes.push(internal_model.nodes[ni])
}
}
// Process meshes
for (var mi = 0; mi < length(g.meshes); mi++) {
var mesh = g.meshes[mi]
for (var pi = 0; pi < length(mesh.primitives); pi++) {
var prim = mesh.primitives[pi]
var gpu_mesh = _process_gltf_primitive(g, buffer_blob, prim, textures)
for (mi = 0; mi < length(g.meshes); mi++) {
mesh = g.meshes[mi]
for (pi = 0; pi < length(mesh.primitives); pi++) {
prim = mesh.primitives[pi]
gpu_mesh = _process_gltf_primitive(g, buffer_blob, prim, textures)
if (gpu_mesh) {
gpu_mesh.name = mesh.name
gpu_mesh.mesh_index = mi
@@ -243,16 +267,16 @@ function load_model(path, style, tier) {
internal_model.skins = skin_mod.prepare_skins(internal_model)
// Build result array: [{mesh, material, node_index}]
for (var ni = 0; ni < length(internal_model.nodes); ni++) {
var node = internal_model.nodes[ni]
for (ni = 0; ni < length(internal_model.nodes); ni++) {
node = internal_model.nodes[ni]
if (node.mesh_index == null) continue
for (var mi = 0; mi < length(internal_model.meshes); mi++) {
var mesh = internal_model.meshes[mi]
for (mi = 0; mi < length(internal_model.meshes); mi++) {
mesh = internal_model.meshes[mi]
if (mesh.mesh_index != node.mesh_index) continue
var mat_idx = mesh.material_index
var mat = (mat_idx != null && materials[mat_idx]) ? materials[mat_idx] : meme(_default_material)
mat_idx = mesh.material_index
mat = (mat_idx != null && materials[mat_idx]) ? materials[mat_idx] : meme(_default_material)
result.push({
mesh: mesh,
@@ -272,42 +296,53 @@ function load_model(path, style, tier) {
function recalc_model_textures(model, style, tier) {
if (!model || !model._internal) return
var internal = model._internal
for (var ti = 0; ti < length(internal._original_images); ti++) {
var original = internal._original_images[ti]
var ti = null
var mi = null
var i = null
var original = null
var new_tex = null
var gmat = null
var tex_info = null
var tex_obj = null
var mesh = null
var mat_idx = null
var entry = null
for (ti = 0; ti < length(internal._original_images); ti++) {
original = internal._original_images[ti]
if (!original) continue
var new_tex = create_texture_for_platform(original.width, original.height, original.pixels, style, tier)
new_tex = create_texture_for_platform(original, style, tier)
internal.textures[ti] = new_tex
}
// Update material references
var g = internal._gltf
var gltf_mats = g.materials || []
for (var mi = 0; mi < length(gltf_mats); mi++) {
var gmat = gltf_mats[mi]
for (mi = 0; mi < length(gltf_mats); mi++) {
gmat = gltf_mats[mi]
if (gmat.pbr && gmat.pbr.base_color_texture) {
var tex_info = gmat.pbr.base_color_texture
var tex_obj = g.textures[tex_info.texture]
tex_info = gmat.pbr.base_color_texture
tex_obj = g.textures[tex_info.texture]
if (tex_obj && tex_obj.image != null && internal.textures[tex_obj.image]) {
internal.materials[mi].color_map = internal.textures[tex_obj.image]
}
}
}
// Update mesh textures
for (var mi = 0; mi < length(internal.meshes); mi++) {
var mesh = internal.meshes[mi]
var mat_idx = mesh.material_index
for (mi = 0; mi < length(internal.meshes); mi++) {
mesh = internal.meshes[mi]
mat_idx = mesh.material_index
if (mat_idx != null && internal.materials[mat_idx]) {
mesh.texture = internal.materials[mat_idx].color_map
}
}
// Update result array materials
for (var i = 0; i < length(model); i++) {
var entry = model[i]
var mat_idx = entry.mesh.material_index
for (i = 0; i < length(model); i++) {
entry = model[i]
mat_idx = entry.mesh.material_index
if (mat_idx != null && internal.materials[mat_idx]) {
entry.material = internal.materials[mat_idx]
}
@@ -329,24 +364,28 @@ function _make_internal_transform(node) {
dirty_world: true
}
var trans = null
var rot = null
var scl = null
if (node.matrix) {
t.local_mat = model_c.mat4_from_array(node.matrix)
t.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]
trans = node.translation || [0, 0, 0]
rot = node.rotation || [0, 0, 0, 1]
scl = node.scale || [1, 1, 1]
t.x = trans[0]; t.y = trans[1]; t.z = trans[2]
t.qx = rot[0]; t.qy = rot[1]; t.qz = rot[2]; t.qw = rot[3]
t.sx = scale[0]; t.sy = scale[1]; t.sz = scale[2]
t.sx = scl[0]; t.sy = scl[1]; t.sz = scl[2]
}
return t
}
function _transform_set_parent(child, parent) {
var idx = null
if (child.parent) {
var idx = find(child.parent.children, child)
idx = find(child.parent.children, child)
if (idx != null) child.parent.children.splice(idx, 1)
}
child.parent = parent
@@ -375,8 +414,9 @@ function _transform_get_world_matrix(t) {
if (!t.dirty_world && t.world_mat) return t.world_mat
var local = _transform_get_local_matrix(t)
var parent_world = null
if (t.parent) {
var parent_world = _transform_get_world_matrix(t.parent)
parent_world = _transform_get_world_matrix(t.parent)
t.world_mat = model_c.mat4_mul(parent_world, local)
} else {
t.world_mat = local
@@ -411,8 +451,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
)
var normals = null
var norm_view = null
if (norm_acc) {
var norm_view = g.views[norm_acc.view]
norm_view = g.views[norm_acc.view]
normals = model_c.extract_accessor(
buffer_blob,
norm_view.byte_offset || 0,
@@ -425,8 +466,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
}
var uvs = null
var uv_view = null
if (uv_acc) {
var uv_view = g.views[uv_acc.view]
uv_view = g.views[uv_acc.view]
uvs = model_c.extract_accessor(
buffer_blob,
uv_view.byte_offset || 0,
@@ -439,8 +481,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
}
var colors = null
var color_view = null
if (color_acc) {
var color_view = g.views[color_acc.view]
color_view = g.views[color_acc.view]
colors = model_c.extract_accessor(
buffer_blob,
color_view.byte_offset || 0,
@@ -453,8 +496,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
}
var joints = null
var joints_view = null
if (joints_acc) {
var joints_view = g.views[joints_acc.view]
joints_view = g.views[joints_acc.view]
joints = model_c.extract_accessor(
buffer_blob,
joints_view.byte_offset || 0,
@@ -467,8 +511,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
}
var weights = null
var weights_view = null
if (weights_acc) {
var weights_view = g.views[weights_acc.view]
weights_view = g.views[weights_acc.view]
weights = model_c.extract_accessor(
buffer_blob,
weights_view.byte_offset || 0,
@@ -483,8 +528,9 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
var indices = null
var index_count = 0
var index_type = "uint16"
var idx_view = null
if (idx_acc) {
var idx_view = g.views[idx_acc.view]
idx_view = g.views[idx_acc.view]
indices = model_c.extract_indices(
buffer_blob,
idx_view.byte_offset || 0,
@@ -511,11 +557,14 @@ function _process_gltf_primitive(g, buffer_blob, prim, textures) {
var index_buffer = indices ? _backend.create_index_buffer(indices) : null
var texture = null
var mat = null
var tex_info = null
var tex_obj = null
if (prim.material != null && g.materials[prim.material]) {
var mat = g.materials[prim.material]
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]
tex_info = mat.pbr.base_color_texture
tex_obj = g.textures[tex_info.texture]
if (tex_obj && tex_obj.image != null && textures[tex_obj.image]) {
texture = textures[tex_obj.image]
}

30
sdl.cm
View File

@@ -21,12 +21,12 @@ var _resolution_h = 480
// Initialize the GPU backend
function init(opts) {
opts = opts || {}
_resolution_w = opts.width || 640
_resolution_h = opts.height || 480
var _opts = opts || {}
_resolution_w = _opts.width || 640
_resolution_h = _opts.height || 480
_window = video.window({
title: opts.title || "lance3d",
title: _opts.title || "lance3d",
width: _resolution_w,
height: _resolution_h
})
@@ -337,19 +337,25 @@ function submit_frame(draws, clear_color, clear_depth, style_id) {
var k = order[d.coverage]
return k == null ? 0 : k
})
draws = sort(draws, keys)
var sorted_draws = sort(draws, keys)
var draw_calls = 0
var triangles = 0
var sampler = get_sampler(style_id)
var i = null
var d = null
var skinned = null
var cull = null
var alpha_mode = null
var pipeline = null
for (var i = 0; i < length(draws); i++) {
var d = draws[i]
for (i = 0; i < length(sorted_draws); i++) {
d = sorted_draws[i]
var skinned = d.mesh.skinned && d.palette
var cull = d.face == "double" ? "none" : "back"
var alpha_mode = d.coverage == "blend" ? "blend" : (d.coverage == "cutoff" || d.coverage == "mask" ? "mask" : "opaque")
var pipeline = get_pipeline(skinned, alpha_mode, cull)
skinned = d.mesh.skinned && d.palette
cull = d.face == "double" ? "none" : "back"
alpha_mode = d.coverage == "blend" ? "blend" : (d.coverage == "cutoff" || d.coverage == "mask" ? "mask" : "opaque")
pipeline = get_pipeline(skinned, alpha_mode, cull)
if (!pipeline) continue

49
skin.cm
View File

@@ -12,16 +12,21 @@ function prepare_skins(model) {
if (!buffer_blob) return []
var prepared = []
var si = null
var skin = null
var inv_bind_blob = null
var acc = null
var view = null
for (var si = 0; si < length(g.skins); si++) {
var skin = g.skins[si]
for (si = 0; si < length(g.skins); si++) {
skin = g.skins[si]
// Extract inverse bind matrices
var inv_bind_blob = null
inv_bind_blob = null
if (skin.inverse_bind_matrices != null) {
var acc = g.accessors[skin.inverse_bind_matrices]
acc = g.accessors[skin.inverse_bind_matrices]
if (acc) {
var view = g.views[acc.view]
view = g.views[acc.view]
inv_bind_blob = model_c.extract_accessor(
buffer_blob,
view.byte_offset || 0,
@@ -54,11 +59,15 @@ function build_palette(skin, model, retro3d) {
// Collect world matrices for each joint
var world_matrices = []
for (var j = 0; j < length(skin.joints); j++) {
var node_idx = skin.joints[j]
var node = model.nodes[node_idx]
var j = null
var node_idx = null
var node = null
var world_mat = null
for (j = 0; j < length(skin.joints); j++) {
node_idx = skin.joints[j]
node = model.nodes[node_idx]
if (node) {
var world_mat = retro3d.transform_get_world_matrix(node)
world_mat = retro3d.transform_get_world_matrix(node)
world_matrices.push(world_mat)
} else {
world_matrices.push(model_c.mat4_identity())
@@ -85,10 +94,12 @@ function get_joint_world(skin, joint_index, model, retro3d) {
// Find joint index by name
function find_joint(skin, name, model) {
if (!skin || !skin.joints) return -1
for (var j = 0; j < length(skin.joints); j++) {
var node_idx = skin.joints[j]
var node = model.nodes[node_idx]
var j = null
var node_idx = null
var node = null
for (j = 0; j < length(skin.joints); j++) {
node_idx = skin.joints[j]
node = model.nodes[node_idx]
if (node && node.name == name) return j
}
return -1
@@ -97,8 +108,8 @@ function find_joint(skin, name, model) {
// Find joint index by node index
function joint_from_node(skin, node_idx) {
if (!skin || !skin.joints) return -1
for (var j = 0; j < length(skin.joints); j++) {
var j = null
for (j = 0; j < length(skin.joints); j++) {
if (skin.joints[j] == node_idx) return j
}
return -1
@@ -112,11 +123,11 @@ function node_from_joint(skin, joint_idx) {
// Compute attachment transform
// Returns world matrix for an object attached to a joint with an offset
function attachment_transform(skin, joint_index, model, retro3d, offset_mat) {
var joint_world = get_joint_world(skin, joint_index, model, retro3d)
function attachment_transform(skin, joint_index, ctx) {
var joint_world = get_joint_world(skin, joint_index, ctx.model, ctx.retro3d)
if (offset_mat) {
return model_c.mat4_mul(joint_world, offset_mat)
if (ctx.offset_mat) {
return model_c.mat4_mul(joint_world, ctx.offset_mat)
}
return joint_world
}