783 lines
21 KiB
Plaintext
783 lines
21 KiB
Plaintext
// lance3d - retro 3D fantasy console
|
|
var time_mod = use('time')
|
|
var model_c = use('model')
|
|
var anim_mod = use('animation')
|
|
var skin_mod = use('skin')
|
|
|
|
// Import sub-modules
|
|
var backend = use('sdl')
|
|
var input_mod = use('input')
|
|
var collision_mod = use('collision')
|
|
var resources_mod = use('resources')
|
|
var camera_mod = use('camera')
|
|
var math_mod = use('math')
|
|
|
|
// Style configurations (PS1, N64, Saturn)
|
|
var _styles = {
|
|
ps1: {
|
|
name: "ps1",
|
|
id: 0,
|
|
resolution: [320, 240],
|
|
vertex_snap: true,
|
|
affine_texturing: false,
|
|
filtering: "nearest",
|
|
color_depth: 15,
|
|
dither: false,
|
|
tex_sizes: { low: 64, normal: 128, hero: 256 },
|
|
tri_budget: 2000
|
|
},
|
|
n64: {
|
|
name: "n64",
|
|
id: 1,
|
|
resolution: [320, 240],
|
|
vertex_snap: false,
|
|
affine_texturing: false,
|
|
filtering: "linear",
|
|
color_depth: 16,
|
|
dither: false,
|
|
tex_sizes: { normal: 32, hero: 64 },
|
|
tri_budget: 3000
|
|
},
|
|
saturn: {
|
|
name: "saturn",
|
|
id: 2,
|
|
resolution: [320, 224],
|
|
vertex_snap: false,
|
|
affine_texturing: true,
|
|
filtering: "nearest",
|
|
color_depth: 15,
|
|
dither: true,
|
|
tex_sizes: { low: 32, normal: 64, hero: 128 },
|
|
tri_budget: 1500
|
|
}
|
|
}
|
|
|
|
// Triangle budget warning state
|
|
var _tri_warning_state = {
|
|
last_warn_time: 0
|
|
}
|
|
|
|
// Internal state
|
|
var _state = {
|
|
style: null,
|
|
style_id: 0,
|
|
resolution_w: 320,
|
|
resolution_h: 240,
|
|
boot_time: 0,
|
|
dt: 1/60,
|
|
frame_count: 0,
|
|
draw_calls: 0,
|
|
triangles: 0,
|
|
|
|
// Environment (lighting/fog)
|
|
lighting: {
|
|
sun_dir: [0.3, -1, 0.2],
|
|
sun_color: [1, 1, 1],
|
|
ambient: [0.25, 0.25, 0.25]
|
|
},
|
|
fog: {
|
|
enabled: false,
|
|
color: [0.5, 0.6, 0.7],
|
|
near: 10,
|
|
far: 80
|
|
},
|
|
|
|
// Pending draws
|
|
_pending_draws: [],
|
|
_clear_color: [0, 0, 0, 1],
|
|
_clear_depth: true
|
|
}
|
|
|
|
// Default material prototype
|
|
var _default_material = {
|
|
color_map: null,
|
|
paint: [1, 1, 1, 1],
|
|
coverage: "opaque",
|
|
face: "single",
|
|
lamp: "lit"
|
|
}
|
|
|
|
// ============================================================================
|
|
// System / Style / Time / Logging
|
|
// ============================================================================
|
|
|
|
function set_style(style_name) {
|
|
if (_state.style != null) return // Already set
|
|
|
|
var style = _styles[style_name]
|
|
if (!style) {
|
|
log.console("lance3d: unknown style: " + style_name)
|
|
return
|
|
}
|
|
|
|
_state.style = style_name
|
|
_state.style_id = style.id
|
|
_state.resolution_w = style.resolution[0]
|
|
_state.resolution_h = style.resolution[1]
|
|
_state.boot_time = time_mod.number()
|
|
|
|
// Initialize backend
|
|
backend.init({
|
|
title: "lance3d - " + style_name,
|
|
width: _state.resolution_w,
|
|
height: _state.resolution_h
|
|
})
|
|
|
|
// Set up resources module with backend
|
|
resources_mod.set_backend(backend)
|
|
|
|
// Set camera aspect ratio
|
|
camera_mod.set_aspect(_state.resolution_w / _state.resolution_h)
|
|
}
|
|
|
|
function get_style() {
|
|
return _state.style
|
|
}
|
|
|
|
function get_style_config() {
|
|
return _styles[_state.style]
|
|
}
|
|
|
|
function set_resolution(w, h) {
|
|
_state.resolution_w = w
|
|
_state.resolution_h = h
|
|
camera_mod.set_aspect(w / h)
|
|
}
|
|
|
|
// Switch platform style at runtime (re-resizes all cached textures)
|
|
function switch_style(style_name) {
|
|
var style = _styles[style_name]
|
|
if (!style) {
|
|
log.console("lance3d: unknown style: " + style_name)
|
|
return false
|
|
}
|
|
|
|
_state.style = style_name
|
|
_state.style_id = style.id
|
|
_state.resolution_w = style.resolution[0]
|
|
_state.resolution_h = style.resolution[1]
|
|
|
|
camera_mod.set_aspect(_state.resolution_w / _state.resolution_h)
|
|
|
|
log.console("lance3d: switched to " + style_name + " style")
|
|
return true
|
|
}
|
|
|
|
function time() {
|
|
return time_mod.number() - _state.boot_time
|
|
}
|
|
|
|
function dt() {
|
|
return _state.dt
|
|
}
|
|
|
|
function stat(name) {
|
|
if (name == "draw_calls") return _state.draw_calls
|
|
if (name == "triangles") return _state.triangles
|
|
if (name == "fps") return 1 / _state.dt
|
|
if (name == "memory_bytes") return 0
|
|
return 0
|
|
}
|
|
|
|
function log_msg() {
|
|
var args = []
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
args.push(text(arguments[i]))
|
|
}
|
|
log.console("[lance3d] " + args.join(" "))
|
|
}
|
|
|
|
function set_lighting(opts) {
|
|
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])
|
|
if (len > 0) {
|
|
_state.lighting.sun_dir = [d[0]/len, d[1]/len, d[2]/len]
|
|
}
|
|
}
|
|
if (opts.sun_color) _state.lighting.sun_color = opts.sun_color.slice()
|
|
if (opts.ambient) _state.lighting.ambient = opts.ambient.slice()
|
|
}
|
|
|
|
function set_fog(opts) {
|
|
if (opts.enabled != null) _state.fog.enabled = opts.enabled
|
|
if (opts.color) _state.fog.color = opts.color.slice()
|
|
if (opts.near != null) _state.fog.near = opts.near
|
|
if (opts.far != null) _state.fog.far = opts.far
|
|
}
|
|
|
|
// ============================================================================
|
|
// Draw API - Models & Meshes
|
|
// ============================================================================
|
|
|
|
function load_model(path, opts) {
|
|
opts = opts || {}
|
|
var tex_tier = opts.type || "normal"
|
|
var style = _styles[_state.style]
|
|
return resources_mod.load_model(path, style, tex_tier)
|
|
}
|
|
|
|
// Recalculate model textures for current style (call after switch_style)
|
|
function recalc_model_textures(model) {
|
|
var style = _styles[_state.style]
|
|
var tier = model._internal ? model._internal._tex_tier : "normal"
|
|
resources_mod.recalc_model_textures(model, style, tier)
|
|
}
|
|
|
|
function make_cube(w, h, d) {
|
|
var hw = w / 2, hh = h / 2, hd = d / 2
|
|
|
|
var positions = [
|
|
-hw, -hh, hd, hw, -hh, hd, hw, hh, hd, -hw, hh, hd,
|
|
-hw, -hh, -hd, -hw, hh, -hd, hw, hh, -hd, hw, -hh, -hd,
|
|
-hw, hh, -hd, -hw, hh, hd, hw, hh, hd, hw, hh, -hd,
|
|
-hw, -hh, -hd, hw, -hh, -hd, hw, -hh, hd, -hw, -hh, hd,
|
|
hw, -hh, -hd, hw, hh, -hd, hw, hh, hd, hw, -hh, hd,
|
|
-hw, -hh, -hd, -hw, -hh, hd, -hw, hh, hd, -hw, hh, -hd
|
|
]
|
|
|
|
var normals = [
|
|
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
|
|
0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1,
|
|
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
|
|
0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0,
|
|
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
|
|
-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0
|
|
]
|
|
|
|
var uvs = [
|
|
0,1, 1,1, 1,0, 0,0,
|
|
1,1, 1,0, 0,0, 0,1,
|
|
0,0, 0,1, 1,1, 1,0,
|
|
0,1, 1,1, 1,0, 0,0,
|
|
1,1, 1,0, 0,0, 0,1,
|
|
0,1, 1,1, 1,0, 0,0
|
|
]
|
|
|
|
var indices = [
|
|
0, 1, 2, 0, 2, 3,
|
|
4, 5, 6, 4, 6, 7,
|
|
8, 9,10, 8,10,11,
|
|
12,13,14, 12,14,15,
|
|
16,17,18, 16,18,19,
|
|
20,21,22, 20,22,23
|
|
]
|
|
|
|
return _make_mesh_from_arrays(positions, normals, uvs, indices)
|
|
}
|
|
|
|
function make_sphere(r, segments) {
|
|
if (!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 * 3.14159265
|
|
|
|
for (var x = 0; x <= segments; x++) {
|
|
var u = x / segments
|
|
var phi = u * 2 * 3.14159265
|
|
|
|
var nx = Math.sin(theta) * Math.cos(phi)
|
|
var ny = Math.cos(theta)
|
|
var nz = Math.sin(theta) * Math.sin(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)
|
|
}
|
|
}
|
|
|
|
return _make_mesh_from_arrays(positions, normals, uvs, indices)
|
|
}
|
|
|
|
function make_cylinder(r, h, segments) {
|
|
if (!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 * 3.14159265
|
|
var nx = Math.cos(angle)
|
|
var nz = Math.sin(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
|
|
indices.push(base, base + 1, base + 2)
|
|
indices.push(base + 1, base + 3, base + 2)
|
|
}
|
|
|
|
return _make_mesh_from_arrays(positions, normals, uvs, indices)
|
|
}
|
|
|
|
function make_plane(w, h) {
|
|
var hw = w / 2, hh = h / 2
|
|
var positions = [-hw, 0, -hh, hw, 0, -hh, hw, 0, hh, -hw, 0, hh]
|
|
var normals = [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]
|
|
var uvs = [0, 0, 1, 0, 1, 1, 0, 1]
|
|
var indices = [0, 1, 2, 0, 2, 3]
|
|
return _make_mesh_from_arrays(positions, normals, uvs, indices)
|
|
}
|
|
|
|
function load_texture(path, opts) {
|
|
opts = opts || {}
|
|
var tier = opts.type || "normal"
|
|
var style = _styles[_state.style]
|
|
return resources_mod.load_texture(path, style, tier)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Animation API
|
|
// ============================================================================
|
|
|
|
function anim_info(model) {
|
|
if (!model || !model._internal) return []
|
|
var internal = model._internal
|
|
var result = []
|
|
for (var i = 0; i < internal.animations.length; i++) {
|
|
var anim = internal.animations[i]
|
|
result.push({
|
|
name: anim.name || ("clip_" + text(i)),
|
|
duration: anim.duration || 0,
|
|
index: i
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
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
|
|
if (typeof name == "number") {
|
|
anim_idx = name
|
|
} else {
|
|
for (var i = 0; i < internal.animations.length; i++) {
|
|
if (internal.animations[i].name == name) {
|
|
anim_idx = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (anim_idx < 0 || anim_idx >= internal.animations.length) {
|
|
return null
|
|
}
|
|
|
|
var anim = internal.animations[anim_idx]
|
|
var duration = anim.duration || 0
|
|
|
|
// Clamp time
|
|
if (time_val < 0) time_val = 0
|
|
if (time_val > duration) time_val = duration
|
|
|
|
// Create a temporary animation instance and sample it
|
|
var instance = anim_mod.create_instance(internal)
|
|
anim_mod.play(instance, anim_idx, false)
|
|
anim_mod.set_time(instance, time_val)
|
|
anim_mod.apply(instance)
|
|
|
|
// Build pose: array of node transforms
|
|
var pose = {
|
|
_internal: internal,
|
|
_anim_idx: anim_idx,
|
|
_time: time_val,
|
|
node_matrices: []
|
|
}
|
|
|
|
for (var ni = 0; ni < internal.nodes.length; ni++) {
|
|
pose.node_matrices.push(resources_mod.get_transform_world_matrix(internal.nodes[ni]))
|
|
}
|
|
|
|
// Build skin palettes if model has skins
|
|
if (internal.skins && internal.skins.length > 0) {
|
|
pose.skin_palettes = []
|
|
for (var si = 0; si < internal.skins.length; si++) {
|
|
var skin = internal.skins[si]
|
|
var world_matrices = []
|
|
for (var j = 0; j < skin.joints.length; j++) {
|
|
var node_idx = skin.joints[j]
|
|
var 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)
|
|
pose.skin_palettes.push(palette)
|
|
}
|
|
}
|
|
|
|
return pose
|
|
}
|
|
|
|
// ============================================================================
|
|
// Drawing
|
|
// ============================================================================
|
|
|
|
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 = []
|
|
if (pose && pose.skin_palettes) {
|
|
skin_palettes = pose.skin_palettes
|
|
} else if (internal.skins && internal.skins.length > 0) {
|
|
for (var si = 0; si < internal.skins.length; si++) {
|
|
var skin = internal.skins[si]
|
|
var world_matrices = []
|
|
for (var j = 0; j < skin.joints.length; j++) {
|
|
var node_idx = skin.joints[j]
|
|
var jnode = internal.nodes[node_idx]
|
|
if (jnode) {
|
|
var jworld = resources_mod.get_transform_world_matrix(jnode)
|
|
if (extra_transform) {
|
|
jworld = model_c.mat4_mul(extra_transform, jworld)
|
|
}
|
|
world_matrices.push(jworld)
|
|
} else {
|
|
world_matrices.push(model_c.mat4_identity())
|
|
}
|
|
}
|
|
var 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 < model.length; i++) {
|
|
var entry = model[i]
|
|
var mesh = entry.mesh
|
|
var mat = entry.material
|
|
var 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
|
|
? 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
|
|
if (mesh.skinned && skin_palettes.length > 0) {
|
|
palette = skin_palettes[0]
|
|
}
|
|
|
|
_queue_draw(mesh, uniforms, tex, mat, palette)
|
|
}
|
|
}
|
|
|
|
function draw_mesh(mesh, transform, material) {
|
|
var view_matrix = camera_mod.get_view_matrix()
|
|
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)
|
|
}
|
|
|
|
function draw_billboard(texture, x, y, z, size, mat) {
|
|
if (!texture) return
|
|
size = size || 1.0
|
|
mat = mat || _default_material
|
|
|
|
var cam_eye = camera_mod.get_eye()
|
|
var dx = cam_eye.x - x
|
|
var dz = cam_eye.z - 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 quad = make_plane(1, 1)
|
|
var billboard_mat = {
|
|
color_map: texture,
|
|
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) {
|
|
// 2D sprite - uses orthographic projection
|
|
// TODO: implement 2D sprite rendering
|
|
}
|
|
|
|
// ============================================================================
|
|
// Debug API
|
|
// ============================================================================
|
|
|
|
function debug_point(vertex, size) {
|
|
size = size || 1.0
|
|
// TODO: implement point rendering
|
|
}
|
|
|
|
function debug_line(vertex_a, vertex_b, width) {
|
|
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]
|
|
// TODO: implement grid rendering
|
|
}
|
|
|
|
// ============================================================================
|
|
// Frame management
|
|
// ============================================================================
|
|
|
|
function clear(r, g, b, a) {
|
|
if (a == null) a = 1.0
|
|
_state._clear_color = [r, g, b, a]
|
|
_state._clear_depth = true
|
|
}
|
|
|
|
function _begin_frame() {
|
|
_state.draw_calls = 0
|
|
_state.triangles = 0
|
|
_state._pending_draws = []
|
|
_state._clear_color = [0, 0, 0, 1]
|
|
_state._clear_depth = true
|
|
|
|
// Begin input frame
|
|
input_mod.begin_frame()
|
|
}
|
|
|
|
function _process_events() {
|
|
return input_mod.process_events()
|
|
}
|
|
|
|
function _end_frame() {
|
|
// Submit all pending draws to backend
|
|
var result = backend.submit_frame(
|
|
_state._pending_draws,
|
|
_state._clear_color,
|
|
_state._clear_depth,
|
|
_state.style_id
|
|
)
|
|
|
|
_state.draw_calls = result.draw_calls
|
|
_state.triangles = result.triangles
|
|
_state.frame_count++
|
|
|
|
// Check triangle budget and warn (once per minute max)
|
|
_check_tri_budget()
|
|
}
|
|
|
|
// Check if triangle count exceeds platform budget, warn once per minute
|
|
function _check_tri_budget() {
|
|
var style = _styles[_state.style]
|
|
if (!style || !style.tri_budget) return
|
|
|
|
if (_state.triangles > style.tri_budget) {
|
|
var 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) +
|
|
" exceeds " + _state.style + " budget of " + text(style.tri_budget))
|
|
_tri_warning_state.last_warn_time = now
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Internal helpers
|
|
// ============================================================================
|
|
|
|
function _queue_draw(mesh, uniforms, texture, mat, palette) {
|
|
_state._pending_draws.push({
|
|
mesh: mesh,
|
|
uniforms: uniforms,
|
|
texture: texture,
|
|
coverage: mat.coverage || "opaque",
|
|
face: mat.face || "single",
|
|
palette: palette
|
|
})
|
|
}
|
|
|
|
function _build_uniforms(model_mat, view_mat, proj_mat, mat) {
|
|
var paint = mat.paint || [1, 1, 1, 1]
|
|
var alpha_mode = 0
|
|
if (mat.coverage == "cutoff") alpha_mode = 1
|
|
else if (mat.coverage == "blend") alpha_mode = 2
|
|
var unlit = mat.lamp == "unlit" ? 1 : 0
|
|
|
|
return model_c.build_uniforms({
|
|
model: model_mat,
|
|
view: view_mat,
|
|
projection: proj_mat,
|
|
ambient: _state.lighting.ambient,
|
|
light_dir: _state.lighting.sun_dir,
|
|
light_color: _state.lighting.sun_color,
|
|
light_intensity: 1.0,
|
|
fog_near: _state.fog.enabled ? _state.fog.near : 10000,
|
|
fog_far: _state.fog.enabled ? _state.fog.far : 10001,
|
|
fog_color: _state.fog.enabled ? _state.fog.color : null,
|
|
tint: paint,
|
|
style_id: _state.style_id,
|
|
resolution_w: _state.resolution_w,
|
|
resolution_h: _state.resolution_h,
|
|
alpha_mode: alpha_mode,
|
|
alpha_cutoff: 0.5,
|
|
unlit: unlit
|
|
})
|
|
}
|
|
|
|
function _make_mesh_from_arrays(positions, normals, uvs, indices) {
|
|
var vertex_count = positions.length / 3
|
|
|
|
var mesh = {
|
|
vertex_count: vertex_count,
|
|
index_count: indices.length
|
|
}
|
|
|
|
mesh.positions = model_c.f32_blob(positions)
|
|
mesh.normals = normals && normals.length > 0 ? model_c.f32_blob(normals) : null
|
|
mesh.uvs = uvs && uvs.length > 0 ? model_c.f32_blob(uvs) : null
|
|
|
|
mesh.indices = model_c.u16_blob(indices)
|
|
mesh.index_type = "uint16"
|
|
|
|
var packed = model_c.pack_vertices(mesh)
|
|
|
|
return {
|
|
vertex_buffer: backend.create_vertex_buffer(packed.data),
|
|
index_buffer: backend.create_index_buffer(mesh.indices),
|
|
index_count: mesh.index_count,
|
|
index_type: "uint16",
|
|
vertex_count: vertex_count,
|
|
texture: null,
|
|
skinned: false
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Export
|
|
// ============================================================================
|
|
|
|
return {
|
|
// System / Style
|
|
set_style: set_style,
|
|
get_style: get_style,
|
|
get_style_config: get_style_config,
|
|
switch_style: switch_style,
|
|
set_resolution: set_resolution,
|
|
time: time,
|
|
dt: dt,
|
|
stat: stat,
|
|
log: log_msg,
|
|
set_lighting: set_lighting,
|
|
set_fog: set_fog,
|
|
|
|
// Transform/Matrix
|
|
identity_matrix: math_mod.identity_matrix,
|
|
translation_matrix: math_mod.translation_matrix,
|
|
rotation_matrix: math_mod.rotation_matrix,
|
|
scale_matrix: math_mod.scale_matrix,
|
|
trs_matrix: math_mod.trs_matrix,
|
|
euler_to_quat: math_mod.euler_to_quat,
|
|
euler_matrix: math_mod.euler_matrix,
|
|
multiply_matrices: math_mod.multiply_matrices,
|
|
|
|
// Draw API
|
|
load_model: load_model,
|
|
recalc_model_textures: recalc_model_textures,
|
|
make_cube: make_cube,
|
|
make_sphere: make_sphere,
|
|
make_cylinder: make_cylinder,
|
|
make_plane: make_plane,
|
|
load_texture: load_texture,
|
|
anim_info: anim_info,
|
|
sample_pose: sample_pose,
|
|
draw_model: draw_model,
|
|
draw_mesh: draw_mesh,
|
|
draw_billboard: draw_billboard,
|
|
draw_sprite: draw_sprite,
|
|
|
|
// Camera
|
|
camera_look_at: camera_mod.look_at,
|
|
camera_perspective: camera_mod.perspective,
|
|
camera_ortho: camera_mod.ortho,
|
|
|
|
// Collision
|
|
add_collider_sphere: collision_mod.add_collider_sphere,
|
|
add_collider_box: collision_mod.add_collider_box,
|
|
remove_collider: collision_mod.remove_collider,
|
|
overlaps: collision_mod.overlaps,
|
|
raycast: collision_mod.raycast,
|
|
|
|
// Input
|
|
btn: input_mod.btn,
|
|
btnp: input_mod.btnp,
|
|
key: input_mod.key,
|
|
keyp: input_mod.keyp,
|
|
axis: input_mod.axis,
|
|
|
|
// Math
|
|
seed: math_mod.seed,
|
|
rand: math_mod.rand,
|
|
irand: math_mod.irand,
|
|
|
|
// Debug
|
|
point: debug_point,
|
|
line: debug_line,
|
|
grid: debug_grid,
|
|
|
|
// Frame management
|
|
clear: clear,
|
|
|
|
// Internal (for runner)
|
|
_state: _state,
|
|
_begin_frame: _begin_frame,
|
|
_process_events: _process_events,
|
|
_end_frame: _end_frame
|
|
}
|