fix syntax
This commit is contained in:
100
action.cm
100
action.cm
@@ -25,55 +25,60 @@ action.get_icon_for_action = function(action)
|
||||
{
|
||||
var bindings = this.get_bindings_for_device(action)
|
||||
if (!length(bindings)) return null
|
||||
|
||||
|
||||
var primary_binding = bindings[0]
|
||||
|
||||
var button = null
|
||||
var key_mapping = {
|
||||
'escape': 'escape',
|
||||
'return': 'return',
|
||||
'space': 'space',
|
||||
'up': 'arrow_up',
|
||||
'down': 'arrow_down',
|
||||
'left': 'arrow_left',
|
||||
'right': 'arrow_right'
|
||||
}
|
||||
var key = null
|
||||
var controller_prefix = null
|
||||
var gamepad_mapping = null
|
||||
var icon_name = null
|
||||
|
||||
if (this.current_device == 'keyboard') {
|
||||
if (starts_with(primary_binding, 'mouse_button_')) {
|
||||
var button = replace(primary_binding, 'mouse_button_', '')
|
||||
button = replace(primary_binding, 'mouse_button_', '')
|
||||
return 'ui/mouse/mouse_' + button + '.png'
|
||||
} else {
|
||||
// Handle special keyboard keys
|
||||
var key_mapping = {
|
||||
'escape': 'escape',
|
||||
'return': 'return',
|
||||
'space': 'space',
|
||||
'up': 'arrow_up',
|
||||
'down': 'arrow_down',
|
||||
'left': 'arrow_left',
|
||||
'right': 'arrow_right'
|
||||
}
|
||||
var key = key_mapping[primary_binding] || primary_binding
|
||||
key = key_mapping[primary_binding] || primary_binding
|
||||
return 'ui/keyboard/keyboard_' + key + '.png'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (this.current_device == 'gamepad' && this.current_gamepad_type) {
|
||||
var controller_prefix = controller_map[this.current_gamepad_type] || 'playstation'
|
||||
|
||||
controller_prefix = controller_map[this.current_gamepad_type] || 'playstation'
|
||||
|
||||
// Map gamepad inputs to icon names
|
||||
var gamepad_mapping = {
|
||||
gamepad_mapping = {
|
||||
'gamepad_a': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_cross' : 'xbox_button_a',
|
||||
'gamepad_b': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_circle' : 'xbox_button_b',
|
||||
'gamepad_b': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_circle' : 'xbox_button_b',
|
||||
'gamepad_x': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_square' : 'xbox_button_x',
|
||||
'gamepad_y': this.current_gamepad_type == 'ps5' || this.current_gamepad_type == 'ps4' || this.current_gamepad_type == 'ps3' ? 'playstation_button_triangle' : 'xbox_button_y',
|
||||
'gamepad_dpup': controller_prefix + '_dpad_up',
|
||||
'gamepad_dpdown': controller_prefix + '_dpad_down',
|
||||
'gamepad_dpdown': controller_prefix + '_dpad_down',
|
||||
'gamepad_dpleft': controller_prefix + '_dpad_left',
|
||||
'gamepad_dpright': controller_prefix + '_dpad_right',
|
||||
'gamepad_l1': controller_prefix + '_trigger_l1',
|
||||
'gamepad_r1': controller_prefix + '_trigger_r1',
|
||||
'gamepad_r1': controller_prefix + '_trigger_r1',
|
||||
'gamepad_l2': controller_prefix + '_trigger_l2',
|
||||
'gamepad_r2': controller_prefix + '_trigger_r2',
|
||||
'gamepad_start': this.get_start_button_icon()
|
||||
}
|
||||
|
||||
var icon_name = gamepad_mapping[primary_binding]
|
||||
|
||||
icon_name = gamepad_mapping[primary_binding]
|
||||
if (icon_name) {
|
||||
return 'ui/' + controller_prefix + '/' + icon_name + '.png'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -181,23 +186,26 @@ var SWIPE_MAX_TIME = 500 // ms
|
||||
action.on_input = function(action_id, evt)
|
||||
{
|
||||
// 1) Detect & store which device user is on (only from raw input, ignore passive inputs)
|
||||
var new_device = null
|
||||
var is_real_kb_mouse = false
|
||||
var gamepad_type = null
|
||||
if (action_id != 'mouse_move' && !starts_with(action_id, 'mouse_pos') && evt.pressed) {
|
||||
var new_device = detect_device(action_id)
|
||||
|
||||
new_device = detect_device(action_id)
|
||||
|
||||
// For keyboard/mouse detection, also check if the event has modifier keys or is a mouse button
|
||||
// This helps distinguish real keyboard/mouse events from mapped actions
|
||||
var is_real_kb_mouse = (new_device == 'keyboard' &&
|
||||
(evt.ctrl != null || evt.shift != null || evt.alt != null ||
|
||||
starts_with(action_id, 'mouse_button')))
|
||||
|
||||
is_real_kb_mouse = (new_device == 'keyboard' &&
|
||||
(evt.ctrl != null || evt.shift != null || evt.alt != null ||
|
||||
starts_with(action_id, 'mouse_button')))
|
||||
|
||||
if (new_device == 'keyboard' && !is_real_kb_mouse) {
|
||||
// This might be a mapped action, not a real keyboard/mouse event
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (new_device != this.current_device) {
|
||||
if (new_device == 'gamepad') {
|
||||
var gamepad_type = evt.which != null ? input.gamepad_id_to_type(evt.which) : 'unknown'
|
||||
gamepad_type = evt.which != null ? input.gamepad_id_to_type(evt.which) : 'unknown'
|
||||
log.console("Input switched to 'gamepad' (event: " + action_id + ", type: " + gamepad_type + ")")
|
||||
this.current_gamepad_type = gamepad_type
|
||||
} else if (this.current_device == 'gamepad') {
|
||||
@@ -236,8 +244,9 @@ action.on_input = function(action_id, evt)
|
||||
})
|
||||
|
||||
// Send all matched actions (only if we found mappings - this means it's a raw input)
|
||||
var i = 0
|
||||
if (length(matched_actions) > 0) {
|
||||
for (var i = 0; i < length(matched_actions); i++) {
|
||||
for (i = 0; i < length(matched_actions); i++) {
|
||||
// scene.recurse(game.root, 'on_input', [matched_actions[i], evt])
|
||||
}
|
||||
}
|
||||
@@ -261,7 +270,8 @@ action.rebind_action = function(action_name, new_key) {
|
||||
|
||||
// Clear existing bindings for the current device from the target action
|
||||
var target_bindings = this.action_map[action_name]
|
||||
for (var i = length(target_bindings) - 1; i >= 0; i--) {
|
||||
var i = length(target_bindings) - 1
|
||||
for (; i >= 0; i--) {
|
||||
if (detect_device(target_bindings[i]) == this.current_device)
|
||||
this.action_map[action_name] = array(array(this.action_map[action_name], 0, i), array(this.action_map[action_name], i+1))
|
||||
}
|
||||
@@ -299,22 +309,28 @@ action.get_current_device_binding = function(action_name) {
|
||||
}
|
||||
|
||||
action.save_bindings = function() {
|
||||
try {
|
||||
io.slurpwrite('keybindings.json', json.encode(this.action_map))
|
||||
} catch(e) {
|
||||
log.console("Failed to save key bindings:", e)
|
||||
var self = this
|
||||
var _save = function() {
|
||||
io.slurpwrite('keybindings.json', json.encode(self.action_map))
|
||||
} disruption {
|
||||
log.console("Failed to save key bindings")
|
||||
}
|
||||
_save()
|
||||
}
|
||||
|
||||
action.load_bindings = function() {
|
||||
try {
|
||||
var self = this
|
||||
var _load = function() {
|
||||
var data = null
|
||||
var bindings = null
|
||||
if (io.exists('keybindings.json')) {
|
||||
var data = io.slurp('keybindings.json')
|
||||
var bindings = object(json.decode(data), this.action_map)
|
||||
data = io.slurp('keybindings.json')
|
||||
bindings = object(json.decode(data), self.action_map)
|
||||
}
|
||||
} catch(e) {
|
||||
log.console("Failed to load key bindings:", e)
|
||||
} disruption {
|
||||
log.console("Failed to load key bindings")
|
||||
}
|
||||
_load()
|
||||
}
|
||||
|
||||
action.reset_to_defaults = function() {
|
||||
|
||||
137
clay.cm
137
clay.cm
@@ -38,13 +38,13 @@ var base_config = {
|
||||
}
|
||||
|
||||
function normalize_color(c, fallback) {
|
||||
fallback = fallback || {r:1, g:1, b:1, a:1}
|
||||
if (!c) return {r:fallback.r, g:fallback.g, b:fallback.b, a:fallback.a}
|
||||
var fb = fallback || {r:1, g:1, b:1, a:1}
|
||||
if (!c) return {r:fb.r, g:fb.g, b:fb.b, a:fb.a}
|
||||
return {
|
||||
r: c.r != null ? c.r : fallback.r,
|
||||
g: c.g != null ? c.g : fallback.g,
|
||||
b: c.b != null ? c.b : fallback.b,
|
||||
a: c.a != null ? c.a : fallback.a
|
||||
r: c.r != null ? c.r : fb.r,
|
||||
g: c.g != null ? c.g : fb.g,
|
||||
b: c.b != null ? c.b : fb.b,
|
||||
a: c.a != null ? c.a : fb.a
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ function normalize_spacing(s) {
|
||||
}
|
||||
|
||||
// Tree building state
|
||||
var root_item
|
||||
var tree_root
|
||||
var root_item = null
|
||||
var tree_root = null
|
||||
var config_stack = []
|
||||
|
||||
// Rewriting state management for cleaner recursion
|
||||
@@ -89,11 +89,11 @@ clay.layout = function(fn, size) {
|
||||
return build_drawables(root_node, size.height)
|
||||
}
|
||||
|
||||
function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_scissor, parent_layer) {
|
||||
parent_abs_x = parent_abs_x || 0
|
||||
parent_abs_y = parent_abs_y || 0
|
||||
parent_layer = parent_layer || 0 // UI usually on top, but let's start at 0
|
||||
|
||||
function build_drawables(node, root_height, parent, parent_scissor) {
|
||||
var p_abs_x = (parent && parent.x) || 0
|
||||
var p_abs_y = (parent && parent.y) || 0
|
||||
var p_layer = (parent && parent.layer) || 0
|
||||
|
||||
var rect = lay_ctx.get_rect(node.id)
|
||||
|
||||
// Calculate absolute world Y for this node (bottom-up layout to top-down render)
|
||||
@@ -108,22 +108,23 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s
|
||||
|
||||
// Scissor
|
||||
var current_scissor = parent_scissor
|
||||
var sx = vis_x
|
||||
var sy = vis_y
|
||||
var sw = rect.width
|
||||
var sh = rect.height
|
||||
var clip_right = null
|
||||
var clip_bottom = null
|
||||
if (node.config.clipped) {
|
||||
var sx = vis_x
|
||||
var sy = vis_y
|
||||
var sw = rect.width
|
||||
var sh = rect.height
|
||||
|
||||
// Intersect with parent
|
||||
if (parent_scissor) {
|
||||
sx = max(sx, parent_scissor.x)
|
||||
sy = max(sy, parent_scissor.y)
|
||||
var right = min(vis_x + sw, parent_scissor.x + parent_scissor.width)
|
||||
var bottom = min(vis_y + sh, parent_scissor.y + parent_scissor.height)
|
||||
sw = max(0, right - sx)
|
||||
sh = max(0, bottom - sy)
|
||||
clip_right = min(vis_x + sw, parent_scissor.x + parent_scissor.width)
|
||||
clip_bottom = min(vis_y + sh, parent_scissor.y + parent_scissor.height)
|
||||
sw = max(0, clip_right - sx)
|
||||
sh = max(0, clip_bottom - sy)
|
||||
}
|
||||
|
||||
|
||||
current_scissor = {x: sx, y: sy, width: sw, height: sh}
|
||||
}
|
||||
|
||||
@@ -138,7 +139,7 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s
|
||||
height: rect.height,
|
||||
slice: node.config.slice,
|
||||
color: node.config.background_color || {r:1, g:1, b:1, a:1},
|
||||
layer: parent_layer - 0.1, // slightly behind content
|
||||
layer: p_layer - 0.1, // slightly behind content
|
||||
scissor: current_scissor
|
||||
})
|
||||
} else {
|
||||
@@ -149,7 +150,7 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.background_color || {r:1, g:1, b:1, a:1},
|
||||
layer: parent_layer - 0.1,
|
||||
layer: p_layer - 0.1,
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
@@ -160,7 +161,7 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.background_color,
|
||||
layer: parent_layer - 0.1,
|
||||
layer: p_layer - 0.1,
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
@@ -174,7 +175,7 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
color: node.config.color,
|
||||
layer: parent_layer,
|
||||
layer: p_layer,
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
@@ -196,14 +197,14 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s
|
||||
// `abs_y = root_height - (rect.y + rect.height)` -> Top edge of element.
|
||||
// Text usually wants baseline.
|
||||
// If we put it at `vis_y + rect.height`, that's bottom of element.
|
||||
layer: parent_layer,
|
||||
layer: p_layer,
|
||||
scissor: current_scissor
|
||||
})
|
||||
}
|
||||
|
||||
// Children
|
||||
arrfor(node.children, function(child) {
|
||||
drawables = array(drawables, build_drawables(child, root_height, vis_x, vis_y, current_scissor, parent_layer + 0.01))
|
||||
drawables = array(drawables, build_drawables(child, root_height, {x: vis_x, y: vis_y, layer: p_layer + 0.01}, current_scissor))
|
||||
})
|
||||
|
||||
return drawables
|
||||
@@ -235,8 +236,8 @@ function push_node(configs, contain_mode) {
|
||||
lay_ctx.set_contain(item, config.contain)
|
||||
lay_ctx.set_behave(item, config.behave)
|
||||
|
||||
if (config.size) {
|
||||
var s = config.size
|
||||
var s = config.size
|
||||
if (s) {
|
||||
if (is_array(s)) s = {width: s[0], height: s[1]}
|
||||
lay_ctx.set_size(item, s)
|
||||
}
|
||||
@@ -262,44 +263,44 @@ function pop_node() {
|
||||
|
||||
// Generic container
|
||||
clay.container = function(configs, fn) {
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
push_node(configs, null)
|
||||
if (fn) fn()
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
push_node(_configs, null)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
// Stacks
|
||||
clay.vstack = function(configs, fn) {
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
var c = layout.contain.column
|
||||
|
||||
push_node(configs, c)
|
||||
if (fn) fn()
|
||||
|
||||
push_node(_configs, c)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.hstack = function(configs, fn) {
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
var c = layout.contain.row
|
||||
push_node(configs, c)
|
||||
if (fn) fn()
|
||||
push_node(_configs, c)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.zstack = function(configs, fn) {
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
var c = layout.contain.layout
|
||||
|
||||
push_node(configs, c)
|
||||
if (fn) fn()
|
||||
var _fn = is_function(configs) ? configs : fn
|
||||
var _configs = is_function(configs) ? [{}] : (is_array(configs) ? configs : [configs])
|
||||
|
||||
var c = layout.contain.layout
|
||||
|
||||
push_node(_configs, c)
|
||||
if (_fn) _fn()
|
||||
pop_node()
|
||||
}
|
||||
|
||||
@@ -308,12 +309,12 @@ clay.image = function(path, configs) {
|
||||
var img = graphics.texture(path)
|
||||
var c = [{image: path}]
|
||||
var final_config = process_configs(configs)
|
||||
if (!final_config.size && !final_config.behave)
|
||||
if (!final_config.size && !final_config.behave)
|
||||
c.size = {width: img.width, height: img.height}
|
||||
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
push_node(array(c, configs), null)
|
||||
var _configs = is_array(configs) ? configs : [configs]
|
||||
|
||||
push_node(array(c, _configs), null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
@@ -321,18 +322,18 @@ clay.text = function(str, configs) {
|
||||
var c = [{text: str}]
|
||||
var final_config = process_configs(configs)
|
||||
if (!final_config.size && !final_config.behave) {
|
||||
c.size = {width: 100, height: 20}
|
||||
c.size = {width: 100, height: 20}
|
||||
}
|
||||
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
push_node(array(c, configs), null)
|
||||
var _configs = is_array(configs) ? configs : [configs]
|
||||
|
||||
push_node(array(c, _configs), null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
clay.rectangle = function(configs) {
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
push_node(configs, null)
|
||||
var _configs = is_array(configs) ? configs : [configs]
|
||||
push_node(_configs, null)
|
||||
pop_node()
|
||||
}
|
||||
|
||||
@@ -341,10 +342,10 @@ clay.button = function(str, action, configs) {
|
||||
padding: 10,
|
||||
background_color: {r:0.3, g:0.3, b:0.4, a:1}
|
||||
}]
|
||||
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
clay.zstack(array(btn_config, configs), function() {
|
||||
|
||||
var _configs = is_array(configs) ? configs : [configs]
|
||||
|
||||
clay.zstack(array(btn_config, _configs), function() {
|
||||
clay.text(str, {color: {r:1,g:1,b:1,a:1}})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,12 +35,16 @@ function find_path(node, path, pos) {
|
||||
if (!rect_contains(node, pos)) return null
|
||||
|
||||
var next_path = array(path, node)
|
||||
var i = 0
|
||||
var child = null
|
||||
var child_path = null
|
||||
|
||||
if (node[clay.CHILDREN] && !should_skip_children(node)) {
|
||||
// Children drawn later should be tested first; reverse if your render order differs
|
||||
for (var i = length(node[clay.CHILDREN]) - 1; i >= 0; i--) {
|
||||
var child = node[clay.CHILDREN][i]
|
||||
var child_path = find_path(child, next_path, pos)
|
||||
i = length(node[clay.CHILDREN]) - 1
|
||||
for (; i >= 0; i--) {
|
||||
child = node[clay.CHILDREN][i]
|
||||
child_path = find_path(child, next_path, pos)
|
||||
if (child_path) return child_path
|
||||
}
|
||||
}
|
||||
@@ -65,7 +69,8 @@ clay_input.bubble = function bubble(deepest, prop) {
|
||||
return null
|
||||
}
|
||||
|
||||
clay_input.click = function click(tree_root, mousepos, button = 'left') {
|
||||
clay_input.click = function click(tree_root, mousepos, button) {
|
||||
var _button = button || 'left'
|
||||
var deepest = clay_input.deepest(tree_root, mousepos)
|
||||
var action_target = clay_input.bubble(deepest, 'action')
|
||||
if (action_target && action_target.config.action) action_target.config.action()
|
||||
|
||||
26
color.cm
26
color.cm
@@ -181,24 +181,30 @@ ColorMap.Viridis = ColorMap.makemap({
|
||||
|
||||
Color.normalize(ColorMap);
|
||||
|
||||
ColorMap.sample = function(t, map = this) {
|
||||
if (t < 0) return map[0]
|
||||
if (t > 1) return map[1]
|
||||
ColorMap.sample = function(t, map) {
|
||||
var local_map = map || this
|
||||
if (t < 0) return local_map[0]
|
||||
if (t > 1) return local_map[1]
|
||||
|
||||
var keys = sorted(array(map))
|
||||
var keys = sorted(array(local_map))
|
||||
var lastkey = 0
|
||||
var i = 0
|
||||
var key = null
|
||||
var b = null
|
||||
var a = null
|
||||
var tt = 0
|
||||
|
||||
for (var i = 0; i < length(keys); i++) {
|
||||
var key = keys[i]
|
||||
for (i = 0; i < length(keys); i++) {
|
||||
key = keys[i]
|
||||
if (t < key) {
|
||||
var b = map[key]
|
||||
var a = map[lastkey]
|
||||
var tt = (t - lastkey) / (key - lastkey)
|
||||
b = local_map[key]
|
||||
a = local_map[lastkey]
|
||||
tt = (t - lastkey) / (key - lastkey)
|
||||
return a.lerp(b, tt)
|
||||
}
|
||||
lastkey = key
|
||||
}
|
||||
return map[1]
|
||||
return local_map[1]
|
||||
}
|
||||
|
||||
ColorMap.doc = {
|
||||
|
||||
156
compositor.cm
156
compositor.cm
@@ -27,9 +27,12 @@ compositor.compile = function(config) {
|
||||
|
||||
// Process each plane (supports both 'planes' and legacy 'layers' key)
|
||||
var planes = config.planes || config.layers || []
|
||||
for (var i = 0; i < length(planes); i++) {
|
||||
var plane = planes[i]
|
||||
var type = plane.type || 'film2d'
|
||||
var i = 0
|
||||
var plane = null
|
||||
var type = null
|
||||
for (i = 0; i < length(planes); i++) {
|
||||
plane = planes[i]
|
||||
type = plane.type || 'film2d'
|
||||
if (type == 'imgui') {
|
||||
compile_imgui_layer(plane, ctx)
|
||||
} else {
|
||||
@@ -58,7 +61,8 @@ function compile_plane(plane_config, ctx, group_effects) {
|
||||
var mask_groups = {}
|
||||
arrfor(array(group_effects), gname => {
|
||||
var effects = group_effects[gname].effects || []
|
||||
for (var e = 0; e < length(effects); e++) {
|
||||
var e = 0
|
||||
for (e = 0; e < length(effects); e++) {
|
||||
if (effects[e].type == 'mask' && effects[e].mask_group)
|
||||
mask_groups[effects[e].mask_group] = true
|
||||
}
|
||||
@@ -68,33 +72,41 @@ function compile_plane(plane_config, ctx, group_effects) {
|
||||
var all_sprites = film2d.query({plane: plane_name})
|
||||
|
||||
// Add manual drawables
|
||||
var di = 0
|
||||
if (plane_config.drawables) {
|
||||
for (var i = 0; i < length(plane_config.drawables); i++)
|
||||
push(all_sprites, plane_config.drawables[i])
|
||||
for (di = 0; di < length(plane_config.drawables); di++)
|
||||
push(all_sprites, plane_config.drawables[di])
|
||||
}
|
||||
|
||||
// Find which sprites belong to groups with effects
|
||||
var effect_groups = {} // group_name -> {sprites: [], effects: []}
|
||||
var base_sprites = []
|
||||
|
||||
for (var i = 0; i < length(all_sprites); i++) {
|
||||
var s = all_sprites[i]
|
||||
var sprite_groups = s.groups || []
|
||||
var assigned = false
|
||||
var is_mask_only = length(sprite_groups) > 0
|
||||
|
||||
|
||||
var si = 0
|
||||
var s = null
|
||||
var sprite_groups = null
|
||||
var assigned = false
|
||||
var is_mask_only = false
|
||||
var g = 0
|
||||
var gname = null
|
||||
for (si = 0; si < length(all_sprites); si++) {
|
||||
s = all_sprites[si]
|
||||
sprite_groups = s.groups || []
|
||||
assigned = false
|
||||
is_mask_only = length(sprite_groups) > 0
|
||||
|
||||
// First pass: check if sprite has any non-mask group
|
||||
for (var g = 0; g < length(sprite_groups); g++) {
|
||||
var gname = sprite_groups[g]
|
||||
for (g = 0; g < length(sprite_groups); g++) {
|
||||
gname = sprite_groups[g]
|
||||
if (!mask_groups[gname]) {
|
||||
is_mask_only = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Second pass: assign to effect groups
|
||||
for (var g = 0; g < length(sprite_groups); g++) {
|
||||
var gname = sprite_groups[g]
|
||||
for (g = 0; g < length(sprite_groups); g++) {
|
||||
gname = sprite_groups[g]
|
||||
if (group_effects[gname]) {
|
||||
if (!effect_groups[gname])
|
||||
effect_groups[gname] = {sprites: [], effects: group_effects[gname].effects}
|
||||
@@ -103,7 +115,7 @@ function compile_plane(plane_config, ctx, group_effects) {
|
||||
break // Only assign to first matching effect group
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add to base sprites if not assigned to effect group and not mask-only
|
||||
if (!assigned && !is_mask_only) push(base_sprites, s)
|
||||
}
|
||||
@@ -136,9 +148,11 @@ function compile_plane(plane_config, ctx, group_effects) {
|
||||
|
||||
// Apply effects
|
||||
var current = group_target
|
||||
for (var e = 0; e < length(eg.effects); e++) {
|
||||
var effect = eg.effects[e]
|
||||
current = apply_effect(ctx, effect, current, res, camera, gname, plane_name, group_effects)
|
||||
var e = 0
|
||||
var effect = null
|
||||
for (e = 0; e < length(eg.effects); e++) {
|
||||
effect = eg.effects[e]
|
||||
current = apply_effect(ctx, effect, current, {size: res, camera: camera, hint: gname, current_plane: plane_name, group_effects: group_effects})
|
||||
}
|
||||
|
||||
// Composite result to plane
|
||||
@@ -176,14 +190,28 @@ function compile_plane(plane_config, ctx, group_effects) {
|
||||
})
|
||||
}
|
||||
|
||||
function apply_effect(ctx, effect, input, size, camera, hint, current_plane, group_effects) {
|
||||
function apply_effect(ctx, effect, input, params) {
|
||||
var size = params.size
|
||||
var camera = params.camera
|
||||
var hint = params.hint
|
||||
var current_plane = params.current_plane
|
||||
var group_effects = params.group_effects
|
||||
var output = ctx.alloc(size.width, size.height, hint + '_' + effect.type)
|
||||
|
||||
|
||||
var bright = null
|
||||
var blur1 = null
|
||||
var blur2 = null
|
||||
var blur_passes = null
|
||||
var blur_in = null
|
||||
var p = 0
|
||||
var mask_group = null
|
||||
var mask_sprites = null
|
||||
var mask_target = null
|
||||
if (effect.type == 'bloom') {
|
||||
var bright = ctx.alloc(size.width, size.height, hint + '_bright')
|
||||
var blur1 = ctx.alloc(size.width, size.height, hint + '_blur1')
|
||||
var blur2 = ctx.alloc(size.width, size.height, hint + '_blur2')
|
||||
|
||||
bright = ctx.alloc(size.width, size.height, hint + '_bright')
|
||||
blur1 = ctx.alloc(size.width, size.height, hint + '_blur1')
|
||||
blur2 = ctx.alloc(size.width, size.height, hint + '_blur2')
|
||||
|
||||
// Threshold
|
||||
push(ctx.passes, {
|
||||
type: 'shader_pass',
|
||||
@@ -192,11 +220,11 @@ function apply_effect(ctx, effect, input, size, camera, hint, current_plane, gro
|
||||
output: bright,
|
||||
uniforms: {threshold: effect.threshold || 0.8, intensity: effect.intensity || 1}
|
||||
})
|
||||
|
||||
|
||||
// Blur passes
|
||||
var blur_passes = effect.blur_passes || 2
|
||||
var blur_in = bright
|
||||
for (var p = 0; p < blur_passes; p++) {
|
||||
blur_passes = effect.blur_passes || 2
|
||||
blur_in = bright
|
||||
for (p = 0; p < blur_passes; p++) {
|
||||
push(ctx.passes, {type: 'shader_pass', shader: 'blur', input: blur_in, output: blur1, uniforms: {direction: {x: 1, y: 0}, texel_size: {x: 1/size.width, y: 1/size.height}}})
|
||||
push(ctx.passes, {type: 'shader_pass', shader: 'blur', input: blur1, output: blur2, uniforms: {direction: {x: 0, y: 1}, texel_size: {x: 1/size.width, y: 1/size.height}}})
|
||||
blur_in = blur2
|
||||
@@ -206,12 +234,12 @@ function apply_effect(ctx, effect, input, size, camera, hint, current_plane, gro
|
||||
push(ctx.passes, {type: 'composite_textures', base: input, overlay: blur2, output: output, mode: 'add'})
|
||||
|
||||
} else if (effect.type == 'mask') {
|
||||
var mask_group = effect.mask_group
|
||||
mask_group = effect.mask_group
|
||||
// Query masks within the same plane to avoid cross-plane mask issues
|
||||
var mask_sprites = film2d.query({group: mask_group, plane: current_plane})
|
||||
|
||||
mask_sprites = film2d.query({group: mask_group, plane: current_plane})
|
||||
|
||||
if (length(mask_sprites) > 0) {
|
||||
var mask_target = ctx.alloc(size.width, size.height, hint + '_mask')
|
||||
mask_target = ctx.alloc(size.width, size.height, hint + '_mask')
|
||||
|
||||
// Render mask
|
||||
push(ctx.passes, {
|
||||
@@ -261,17 +289,25 @@ compositor.execute = function(plan) {
|
||||
}
|
||||
|
||||
var commands = []
|
||||
|
||||
for (var i = 0; i < length(plan.passes); i++) {
|
||||
var pass = plan.passes[i]
|
||||
|
||||
|
||||
var i = 0
|
||||
var pass = null
|
||||
var target = null
|
||||
var result = null
|
||||
var c = 0
|
||||
var rect = null
|
||||
var src = null
|
||||
var dst = null
|
||||
for (i = 0; i < length(plan.passes); i++) {
|
||||
pass = plan.passes[i]
|
||||
|
||||
if (pass.type == 'clear') {
|
||||
var target = resolve(pass.target)
|
||||
target = resolve(pass.target)
|
||||
push(commands, {cmd: 'begin_render', target: target, clear: pass.color})
|
||||
push(commands, {cmd: 'end_render'})
|
||||
|
||||
|
||||
} else if (pass.type == 'render') {
|
||||
var result = film2d.render({
|
||||
result = film2d.render({
|
||||
drawables: pass.drawables,
|
||||
camera: pass.camera,
|
||||
target: resolve(pass.target),
|
||||
@@ -279,7 +315,7 @@ compositor.execute = function(plan) {
|
||||
layer_sort: pass.layer_sort || {},
|
||||
clear: pass.clear
|
||||
}, backend)
|
||||
for (var c = 0; c < length(result.commands); c++)
|
||||
for (c = 0; c < length(result.commands); c++)
|
||||
push(commands, result.commands[c])
|
||||
|
||||
} else if (pass.type == 'shader_pass') {
|
||||
@@ -319,7 +355,7 @@ compositor.execute = function(plan) {
|
||||
})
|
||||
|
||||
} else if (pass.type == 'blit_to_screen') {
|
||||
var rect = _calc_presentation(pass.source_size, pass.dest_size, pass.presentation)
|
||||
rect = _calc_presentation(pass.source_size, pass.dest_size, pass.presentation)
|
||||
push(commands, {
|
||||
cmd: 'blit',
|
||||
texture: resolve(pass.source),
|
||||
@@ -328,8 +364,8 @@ compositor.execute = function(plan) {
|
||||
filter: pass.presentation == 'integer_scale' ? 'nearest' : 'linear'
|
||||
})
|
||||
} else if (pass.type == 'blit') {
|
||||
var src = resolve(pass.source)
|
||||
var dst = resolve(pass.dest)
|
||||
src = resolve(pass.source)
|
||||
dst = resolve(pass.dest)
|
||||
push(commands, {
|
||||
cmd: 'blit',
|
||||
texture: src,
|
||||
@@ -351,20 +387,26 @@ compositor.execute = function(plan) {
|
||||
function _calc_presentation(src, dst, mode) {
|
||||
if (mode == 'stretch')
|
||||
return {x: 0, y: 0, width: dst.width, height: dst.height}
|
||||
|
||||
|
||||
var sx = 0
|
||||
var sy = 0
|
||||
var s = 0
|
||||
var w = 0
|
||||
var h = 0
|
||||
var scale = 0
|
||||
if (mode == 'integer_scale') {
|
||||
var sx = floor(dst.width / src.width)
|
||||
var sy = floor(dst.height / src.height)
|
||||
var s = max(1, min(sx, sy))
|
||||
var w = src.width * s
|
||||
var h = src.height * s
|
||||
sx = floor(dst.width / src.width)
|
||||
sy = floor(dst.height / src.height)
|
||||
s = max(1, min(sx, sy))
|
||||
w = src.width * s
|
||||
h = src.height * s
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
|
||||
// letterbox
|
||||
var scale = min(dst.width / src.width, dst.height / src.height)
|
||||
var w = src.width * scale
|
||||
var h = src.height * scale
|
||||
scale = min(dst.width / src.width, dst.height / src.height)
|
||||
w = src.width * scale
|
||||
h = src.height * scale
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
|
||||
16
core.cm
16
core.cm
@@ -84,11 +84,12 @@ var _fps_sample_pos = 0
|
||||
|
||||
function fps_add_sample(sample) {
|
||||
var n = length(_fps_samples)
|
||||
var old = null
|
||||
if (n < _fps_sample_count) {
|
||||
push(_fps_samples, sample)
|
||||
_fps_sample_sum += sample
|
||||
} else {
|
||||
var old = _fps_samples[_fps_sample_pos]
|
||||
old = _fps_samples[_fps_sample_pos]
|
||||
_fps_samples[_fps_sample_pos] = sample
|
||||
_fps_sample_sum += sample - old
|
||||
_fps_sample_pos++
|
||||
@@ -165,22 +166,25 @@ function _main_loop() {
|
||||
}
|
||||
|
||||
// Render
|
||||
var render_result = null
|
||||
var dbg = false
|
||||
var stats = null
|
||||
if (_config.render) {
|
||||
var render_result = _config.render()
|
||||
render_result = _config.render()
|
||||
if (render_result) {
|
||||
if (_config.debug == 'graph') {
|
||||
log.console(render_result)
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
var dbg = _config.debug == 'cmd'
|
||||
|
||||
dbg = _config.debug == 'cmd'
|
||||
|
||||
// Build stats for debug_imgui
|
||||
var stats = {
|
||||
stats = {
|
||||
fps: _current_fps,
|
||||
frame_time_ms: _frame_time_ms
|
||||
}
|
||||
|
||||
|
||||
// Handle both compositor result ({commands: [...]}) and fx_graph (graph object)
|
||||
if (render_result.commands) {
|
||||
if (_config.imgui || _config.editor) {
|
||||
|
||||
@@ -117,14 +117,15 @@ function _render_node_tree(imgui, node, depth) {
|
||||
}
|
||||
|
||||
var has_children = node.children && length(node.children) > 0
|
||||
|
||||
|
||||
if (has_children) {
|
||||
imgui.tree(label, function() {
|
||||
// Show node summary
|
||||
_render_node_summary(imgui, node)
|
||||
|
||||
|
||||
// Recurse children
|
||||
for (var i = 0; i < length(node.children); i++) {
|
||||
var i = 0
|
||||
for (i = 0; i < length(node.children); i++) {
|
||||
_render_node_tree(imgui, node.children[i], depth + 1)
|
||||
}
|
||||
})
|
||||
@@ -154,16 +155,18 @@ function _render_node_summary(imgui, node) {
|
||||
push(info, "img:" + node.image)
|
||||
}
|
||||
|
||||
var t = null
|
||||
if (node.text) {
|
||||
var t = node.text
|
||||
t = node.text
|
||||
if (length(t) > 20) t = text(t, 0, 17) + "..."
|
||||
push(info, "\"" + t + "\"")
|
||||
}
|
||||
|
||||
var fx = []
|
||||
var j = 0
|
||||
if (node.effects && length(node.effects) > 0) {
|
||||
var fx = []
|
||||
for (var i = 0; i < length(node.effects); i++) {
|
||||
push(fx, node.effects[i].type)
|
||||
for (j = 0; j < length(node.effects); j++) {
|
||||
push(fx, node.effects[j].type)
|
||||
}
|
||||
push(info, "fx:[" + text(fx, ",") + "]")
|
||||
}
|
||||
@@ -189,9 +192,10 @@ function _render_node_inspector(imgui, node) {
|
||||
imgui.text("---")
|
||||
|
||||
// Position
|
||||
var pos = null
|
||||
if (node.pos) {
|
||||
imgui.text("Position")
|
||||
var pos = imgui.slider("X", node.pos.x, -1000, 1000)
|
||||
pos = imgui.slider("X", node.pos.x, -1000, 1000)
|
||||
if (pos != node.pos.x) node.pos.x = pos
|
||||
pos = imgui.slider("Y", node.pos.y, -1000, 1000)
|
||||
if (pos != node.pos.y) node.pos.y = pos
|
||||
@@ -242,15 +246,17 @@ function _render_node_inspector(imgui, node) {
|
||||
}
|
||||
|
||||
// Effects
|
||||
var ei = 0
|
||||
var fx = null
|
||||
if (node.effects && length(node.effects) > 0) {
|
||||
imgui.text("---")
|
||||
imgui.text("Effects:")
|
||||
for (var i = 0; i < length(node.effects); i++) {
|
||||
var fx = node.effects[i]
|
||||
for (ei = 0; ei < length(node.effects); ei++) {
|
||||
fx = node.effects[ei]
|
||||
imgui.tree(fx.type, function() {
|
||||
arrfor(array(fx), k => {
|
||||
var v = fx[k]
|
||||
if (k != 'type' && k != 'source') {
|
||||
var v = fx[k]
|
||||
if (is_number(v)) {
|
||||
fx[k] = imgui.slider(k, v, 0, 10)
|
||||
} else {
|
||||
@@ -288,21 +294,25 @@ function _render_graph_view(imgui, plan) {
|
||||
imgui.text("Persistent: " + text(length(array(plan.persistent_targets || {}))))
|
||||
|
||||
imgui.text("---")
|
||||
|
||||
for (var i = 0; i < length(plan.passes); i++) {
|
||||
var pass = plan.passes[i]
|
||||
var label = text(i) + ": " + pass.type
|
||||
|
||||
|
||||
var i = 0
|
||||
var pass = null
|
||||
var label = null
|
||||
var target_info = null
|
||||
for (i = 0; i < length(plan.passes); i++) {
|
||||
pass = plan.passes[i]
|
||||
label = text(i) + ": " + pass.type
|
||||
|
||||
if (pass.shader) label += " [" + pass.shader + "]"
|
||||
if (pass.renderer) label += " [" + pass.renderer + "]"
|
||||
|
||||
|
||||
if (imgui.button(label)) {
|
||||
_selected_pass = pass
|
||||
}
|
||||
|
||||
|
||||
// Show target info
|
||||
imgui.sameline(0)
|
||||
var target_info = ""
|
||||
target_info = ""
|
||||
if (pass.target) {
|
||||
if (pass.target == 'screen') {
|
||||
target_info = "-> screen"
|
||||
@@ -397,14 +407,17 @@ function _render_effects_panel(imgui) {
|
||||
imgui.text("Registered effects: " + text(length(effect_list)))
|
||||
imgui.text("---")
|
||||
|
||||
for (var i = 0; i < length(effect_list); i++) {
|
||||
var name = effect_list[i]
|
||||
var deff = effects_mod.get(name)
|
||||
|
||||
var i = 0
|
||||
var name = null
|
||||
var deff = null
|
||||
for (i = 0; i < length(effect_list); i++) {
|
||||
name = effect_list[i]
|
||||
deff = effects_mod.get(name)
|
||||
|
||||
imgui.tree(name, function() {
|
||||
imgui.text("Type: " + (deff.type || 'unknown'))
|
||||
imgui.text("Requires target: " + (deff.requires_target ? "yes" : "no"))
|
||||
|
||||
|
||||
if (deff.params) {
|
||||
imgui.text("Parameters:")
|
||||
arrfor(array(deff.params), k => {
|
||||
|
||||
20
ease.cm
20
ease.cm
@@ -66,13 +66,19 @@ Ease.bounce = {
|
||||
out(t) {
|
||||
var n1 = 7.5625
|
||||
var d1 = 2.75
|
||||
if (t < 1 / d1) {
|
||||
return n1 * t * t
|
||||
} else if (t < 2 / d1) {
|
||||
return n1 * (t -= 1.5 / d1) * t + 0.75
|
||||
} else if (t < 2.5 / d1) {
|
||||
return n1 * (t -= 2.25 / d1) * t + 0.9375
|
||||
} else return n1 * (t -= 2.625 / d1) * t + 0.984375
|
||||
var u = t
|
||||
if (u < 1 / d1) {
|
||||
return n1 * u * u
|
||||
} else if (u < 2 / d1) {
|
||||
u = u - 1.5 / d1
|
||||
return n1 * u * u + 0.75
|
||||
} else if (u < 2.5 / d1) {
|
||||
u = u - 2.25 / d1
|
||||
return n1 * u * u + 0.9375
|
||||
} else {
|
||||
u = u - 2.625 / d1
|
||||
return n1 * u * u + 0.984375
|
||||
}
|
||||
},
|
||||
inout(t) {
|
||||
return t < 0.5 ? (1 - this.out(1 - 2 * t)) / 2 : (1 + this.out(2 * t - 1)) / 2
|
||||
|
||||
@@ -53,8 +53,9 @@ effects.register('bloom', {
|
||||
var blur_src = thresh_target
|
||||
var texel = {x: 1 / size.width, y: 1 / size.height}
|
||||
var blur_count = params.blur_passes != null ? params.blur_passes : 3
|
||||
var i = 0
|
||||
|
||||
for (var i = 0; i < blur_count; i++) {
|
||||
for (i = 0; i < blur_count; i++) {
|
||||
push(passes, {
|
||||
type: 'shader',
|
||||
shader: 'blur',
|
||||
@@ -188,8 +189,9 @@ effects.register('blur', {
|
||||
var blur_b = ctx.alloc_target(size.width, size.height, 'blur_b')
|
||||
var src = input
|
||||
var blur_count = params.passes != null ? params.passes : 2
|
||||
var i = 0
|
||||
|
||||
for (var i = 0; i < blur_count; i++) {
|
||||
for (i = 0; i < blur_count; i++) {
|
||||
push(passes, {
|
||||
type: 'shader',
|
||||
shader: 'blur',
|
||||
|
||||
62
emacs.cm
62
emacs.cm
@@ -38,41 +38,28 @@ action.on_input = function(action_id, action_data)
|
||||
emacs_notation += lower(key)
|
||||
} else {
|
||||
// Handle special keys
|
||||
switch (key) {
|
||||
case 'return':
|
||||
case 'enter':
|
||||
emacs_notation += "RET"
|
||||
break
|
||||
case 'space':
|
||||
emacs_notation += "SPC"
|
||||
break
|
||||
case 'escape':
|
||||
emacs_notation += "ESC"
|
||||
break
|
||||
case 'tab':
|
||||
emacs_notation += "TAB"
|
||||
break
|
||||
case 'backspace':
|
||||
emacs_notation += "DEL"
|
||||
break
|
||||
case 'delete':
|
||||
emacs_notation += "delete"
|
||||
break
|
||||
case 'up':
|
||||
emacs_notation += "up"
|
||||
break
|
||||
case 'down':
|
||||
emacs_notation += "down"
|
||||
break
|
||||
case 'left':
|
||||
emacs_notation += "left"
|
||||
break
|
||||
case 'right':
|
||||
emacs_notation += "right"
|
||||
break
|
||||
default:
|
||||
emacs_notation += key
|
||||
break
|
||||
if (key == 'return' || key == 'enter') {
|
||||
emacs_notation += "RET"
|
||||
} else if (key == 'space') {
|
||||
emacs_notation += "SPC"
|
||||
} else if (key == 'escape') {
|
||||
emacs_notation += "ESC"
|
||||
} else if (key == 'tab') {
|
||||
emacs_notation += "TAB"
|
||||
} else if (key == 'backspace') {
|
||||
emacs_notation += "DEL"
|
||||
} else if (key == 'delete') {
|
||||
emacs_notation += "delete"
|
||||
} else if (key == 'up') {
|
||||
emacs_notation += "up"
|
||||
} else if (key == 'down') {
|
||||
emacs_notation += "down"
|
||||
} else if (key == 'left') {
|
||||
emacs_notation += "left"
|
||||
} else if (key == 'right') {
|
||||
emacs_notation += "right"
|
||||
} else {
|
||||
emacs_notation += key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +68,11 @@ action.on_input = function(action_id, action_data)
|
||||
this.prefix_key = emacs_notation
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// If we have a prefix key, build the full command
|
||||
var full_command = null
|
||||
if (this.prefix_key) {
|
||||
var full_command = this.prefix_key + " " + emacs_notation
|
||||
full_command = this.prefix_key + " " + emacs_notation
|
||||
this.prefix_key = null // Reset prefix key
|
||||
// scene.recurse(game.root, 'on_input', [full_command, action_data])
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,8 @@ var bunnyTex = graphics.texture("bunny")
|
||||
var bunnies = []
|
||||
|
||||
// Start with some initial bunnies:
|
||||
for (var i = 0; i < 100; i++) {
|
||||
var i = 0;
|
||||
for (i = 0; i < 100; i++) {
|
||||
push(bunnies, {
|
||||
x: random.random() * config.width,
|
||||
y: random.random() * config.height,
|
||||
@@ -25,8 +26,10 @@ for (var i = 0; i < 100; i++) {
|
||||
this.update = function(dt) {
|
||||
// If left mouse is down, spawn some more bunnies:
|
||||
var mouse = input.mousestate()
|
||||
var i = 0;
|
||||
var b = null;
|
||||
if (mouse.left)
|
||||
for (var i = 0; i < 50; i++) {
|
||||
for (i = 0; i < 50; i++) {
|
||||
push(bunnies, {
|
||||
x: mouse.x,
|
||||
y: mouse.y,
|
||||
@@ -36,8 +39,8 @@ this.update = function(dt) {
|
||||
}
|
||||
|
||||
// Update bunny positions and bounce them inside the screen:
|
||||
for (var i = 0; i < length(bunnies); i++) {
|
||||
var b = bunnies[i]
|
||||
for (i = 0; i < length(bunnies); i++) {
|
||||
b = bunnies[i]
|
||||
b.x += b.vx * dt
|
||||
b.y += b.vy * dt
|
||||
|
||||
|
||||
@@ -28,26 +28,21 @@ var isMyTurn = false;
|
||||
|
||||
function updateTitle() {
|
||||
var title = "Misty Chess - ";
|
||||
|
||||
switch(gameState) {
|
||||
case 'waiting':
|
||||
title += "Press S to start server or J to join";
|
||||
break;
|
||||
case 'searching':
|
||||
title += "Searching for server...";
|
||||
break;
|
||||
case 'server_waiting':
|
||||
title += "Waiting for player to join...";
|
||||
break;
|
||||
case 'connected':
|
||||
if (myColor) {
|
||||
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
|
||||
} else {
|
||||
title += mover.turn + " turn";
|
||||
}
|
||||
break;
|
||||
|
||||
if (gameState == 'waiting') {
|
||||
title += "Press S to start server or J to join";
|
||||
} else if (gameState == 'searching') {
|
||||
title += "Searching for server...";
|
||||
} else if (gameState == 'server_waiting') {
|
||||
title += "Waiting for player to join...";
|
||||
} else if (gameState == 'connected') {
|
||||
if (myColor) {
|
||||
title += (mover.turn == myColor ? "Your turn (" + myColor + ")" : "Opponent's turn (" + mover.turn + ")");
|
||||
} else {
|
||||
title += mover.turn + " turn";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log.console(title)
|
||||
}
|
||||
|
||||
@@ -179,13 +174,19 @@ var opponentMouseColor = [1.0, 0.0, 0.0, 1.0]; // Red for opponent mouse
|
||||
|
||||
/* ── draw one 8×8 chess board ──────────────────────────────────── */
|
||||
function drawBoard() {
|
||||
for (var y = 0; y < 8; ++y)
|
||||
for (var x = 0; x < 8; ++x) {
|
||||
var isMyHover = hoverPos && hoverPos[0] == x && hoverPos[1] == y;
|
||||
var isOpponentHover = opponentMousePos && opponentMousePos[0] == x && opponentMousePos[1] == y;
|
||||
var isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
|
||||
|
||||
var color = ((x+y)&1) ? dark : light;
|
||||
var y = 0;
|
||||
var x = 0;
|
||||
var isMyHover = null;
|
||||
var isOpponentHover = null;
|
||||
var isValidMove = null;
|
||||
var color = null;
|
||||
for (y = 0; y < 8; ++y)
|
||||
for (x = 0; x < 8; ++x) {
|
||||
isMyHover = hoverPos && hoverPos[0] == x && hoverPos[1] == y;
|
||||
isOpponentHover = opponentMousePos && opponentMousePos[0] == x && opponentMousePos[1] == y;
|
||||
isValidMove = selectPos && holdingPiece && isValidMoveForTurn(selectPos, [x, y]);
|
||||
|
||||
color = ((x+y)&1) ? dark : light;
|
||||
|
||||
if (isValidMove) {
|
||||
color = allowedColor; // Gold for allowed moves
|
||||
@@ -220,47 +221,51 @@ function isValidMoveForTurn(from, to) {
|
||||
|
||||
/* ── draw every live piece ─────────────────────────────────────── */
|
||||
function drawPieces() {
|
||||
grid.each(function (piece) {
|
||||
if (piece.captured) return;
|
||||
|
||||
var piece = null;
|
||||
var r = null;
|
||||
var opponentPiece = null;
|
||||
|
||||
grid.each(function (p) {
|
||||
if (p.captured) return;
|
||||
|
||||
// Skip drawing the piece being held (by me or opponent)
|
||||
if (holdingPiece && selectPos &&
|
||||
piece.coord[0] == selectPos[0] &&
|
||||
piece.coord[1] == selectPos[1]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip drawing the piece being held by opponent
|
||||
if (opponentHoldingPiece && opponentSelectPos &&
|
||||
piece.coord[0] == opponentSelectPos[0] &&
|
||||
piece.coord[1] == opponentSelectPos[1]) {
|
||||
if (holdingPiece && selectPos &&
|
||||
p.coord[0] == selectPos[0] &&
|
||||
p.coord[1] == selectPos[1]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var r = { x: piece.coord[0]*S, y: piece.coord[1]*S,
|
||||
// Skip drawing the piece being held by opponent
|
||||
if (opponentHoldingPiece && opponentSelectPos &&
|
||||
p.coord[0] == opponentSelectPos[0] &&
|
||||
p.coord[1] == opponentSelectPos[1]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pr = { x: p.coord[0]*S, y: p.coord[1]*S,
|
||||
width:S, height:S };
|
||||
|
||||
draw2d.image(piece.sprite, r);
|
||||
draw2d.image(p.sprite, pr);
|
||||
});
|
||||
|
||||
|
||||
// Draw the held piece at the mouse position if we're holding one
|
||||
if (holdingPiece && selectPos && hoverPos) {
|
||||
var piece = grid.at(selectPos)[0];
|
||||
piece = grid.at(selectPos)[0];
|
||||
if (piece) {
|
||||
var r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
|
||||
r = { x: hoverPos[0]*S, y: hoverPos[1]*S,
|
||||
width:S, height:S };
|
||||
|
||||
|
||||
draw2d.image(piece.sprite, r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw opponent's held piece if they're dragging one
|
||||
if (opponentHoldingPiece && opponentSelectPos && opponentMousePos) {
|
||||
var opponentPiece = grid.at(opponentSelectPos)[0];
|
||||
opponentPiece = grid.at(opponentSelectPos)[0];
|
||||
if (opponentPiece) {
|
||||
var r = { x: opponentMousePos[0]*S, y: opponentMousePos[1]*S,
|
||||
r = { x: opponentMousePos[0]*S, y: opponentMousePos[1]*S,
|
||||
width:S, height:S };
|
||||
|
||||
|
||||
// Draw with slight transparency to show it's the opponent's piece
|
||||
draw2d.image(opponentPiece.sprite, r);
|
||||
}
|
||||
@@ -325,13 +330,16 @@ function joinServer() {
|
||||
}
|
||||
|
||||
$receiver(e => {
|
||||
var fromCell = null;
|
||||
var piece = null;
|
||||
|
||||
if (e.kind == 'update')
|
||||
send(e, update(e.dt))
|
||||
else if (e.kind == 'draw')
|
||||
send(e, draw())
|
||||
else if (e.type == 'game_start' || e.type == 'move' || e.type == 'greet')
|
||||
log.console("Receiver got message:", e.type, e);
|
||||
|
||||
|
||||
if (e.type == 'greet') {
|
||||
log.console("Server received greet from client");
|
||||
// Store the client's actor object for ongoing communication
|
||||
@@ -339,7 +347,7 @@ $receiver(e => {
|
||||
log.console("Stored client actor:", opponent);
|
||||
gameState = 'connected';
|
||||
updateTitle();
|
||||
|
||||
|
||||
// Send game_start to the client
|
||||
log.console("Sending game_start to client");
|
||||
send(opponent, {
|
||||
@@ -357,9 +365,9 @@ $receiver(e => {
|
||||
} else if (e.type == 'move') {
|
||||
log.console("Received move from opponent:", e.from, "to", e.to);
|
||||
// Apply opponent's move
|
||||
var fromCell = grid.at(e.from);
|
||||
fromCell = grid.at(e.from);
|
||||
if (length(fromCell)) {
|
||||
var piece = fromCell[0];
|
||||
piece = fromCell[0];
|
||||
if (mover.tryMove(piece, e.to)) {
|
||||
isMyTurn = true; // It's now our turn
|
||||
updateTitle();
|
||||
|
||||
@@ -2,11 +2,13 @@ function grid(w, h) {
|
||||
var newgrid = meme(grid_prototype)
|
||||
newgrid.width = w;
|
||||
newgrid.height = h;
|
||||
// create a height×width array of empty lists
|
||||
// create a height*width array of empty lists
|
||||
newgrid.cells = array(h);
|
||||
for (var y = 0; y < h; y++) {
|
||||
var y = 0;
|
||||
var x = 0;
|
||||
for (y = 0; y < h; y++) {
|
||||
newgrid.cells[y] = array(w);
|
||||
for (var x = 0; x < w; x++) {
|
||||
for (x = 0; x < w; x++) {
|
||||
newgrid.cells[y][x] = []; // each cell holds its own list
|
||||
}
|
||||
}
|
||||
@@ -45,9 +47,12 @@ var grid_prototype = {
|
||||
|
||||
// call fn(entity, coord) for every entity in every cell
|
||||
each(fn) {
|
||||
for (var y = 0; y < this.height; y++) {
|
||||
for (var x = 0; x < this.width; x++) {
|
||||
def list = this.cells[y][x]
|
||||
var list = null;
|
||||
var y = 0;
|
||||
var x = 0;
|
||||
for (y = 0; y < this.height; y++) {
|
||||
for (x = 0; x < this.width; x++) {
|
||||
list = this.cells[y][x]
|
||||
arrfor(list, function(entity) {
|
||||
fn(entity, entity.coord);
|
||||
})
|
||||
@@ -57,9 +62,11 @@ var grid_prototype = {
|
||||
|
||||
// printable representation
|
||||
toString() {
|
||||
var out = `grid [${this.width}×${this.height}]\n`;
|
||||
for (var y = 0; y < this.height; y++) {
|
||||
for (var x = 0; x < this.width; x++) {
|
||||
var out = `grid [${this.width}x${this.height}]\n`;
|
||||
var y = 0;
|
||||
var x = 0;
|
||||
for (y = 0; y < this.height; y++) {
|
||||
for (x = 0; x < this.width; x++) {
|
||||
out += length(this.cells[y][x]);
|
||||
}
|
||||
if (y != this.height - 1) out += "\n";
|
||||
|
||||
@@ -10,9 +10,9 @@ var MovementSystem_prototype = {
|
||||
tryMove: function (piece, to) {
|
||||
if (piece.colour != this.turn) return false;
|
||||
|
||||
// normalise ‘to’ into our hybrid coord
|
||||
var dest = [to.x ?? t[0],
|
||||
to.y ?? to[1]];
|
||||
// normalise 'to' into our hybrid coord
|
||||
var dest = [!is_null(to.x) ? to.x : to[0],
|
||||
!is_null(to.y) ? to.y : to[1]];
|
||||
|
||||
if (!this.grid.inBounds(dest)) return false;
|
||||
if (!this.rules.canMove(piece, piece.coord, dest, this.grid)) return false;
|
||||
|
||||
@@ -10,7 +10,7 @@ function Piece(kind, colour) {
|
||||
}
|
||||
|
||||
function startingPosition(grid) {
|
||||
var W = 'white', B = 'black', x;
|
||||
var W = 'white', B = 'black', x = 0;
|
||||
|
||||
// pawns
|
||||
for (x = 0; x < 8; x++) {
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
/* helper – robust coord access */
|
||||
function cx(c) { return c.x ?? c[0] }
|
||||
function cy(c) { return c.y ?? c[1] }
|
||||
/* helper -- robust coord access */
|
||||
function cx(c) { return !is_null(c.x) ? c.x : c[0] }
|
||||
function cy(c) { return !is_null(c.y) ? c.y : c[1] }
|
||||
|
||||
/* simple move-shape checks */
|
||||
var deltas = {
|
||||
pawn: function (pc, dx, dy, grid, to) {
|
||||
pawn: function (pc, dx, dy, ctx) {
|
||||
var dir = (pc.colour == 'white') ? -1 : 1;
|
||||
var base = (pc.colour == 'white') ? 6 : 1;
|
||||
var one = (dy == dir && dx == 0 && length(grid.at(to)) == 0);
|
||||
var one = (dy == dir && dx == 0 && length(ctx.grid.at(ctx.to)) == 0);
|
||||
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
|
||||
length(grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir })) == 0 &&
|
||||
length(grid.at(to)) == 0);
|
||||
var cap = (dy == dir && Math.abs(dx) == 1 && length(grid.at(to)));
|
||||
length(ctx.grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir })) == 0 &&
|
||||
length(ctx.grid.at(ctx.to)) == 0);
|
||||
var cap = (dy == dir && abs(dx) == 1 && length(ctx.grid.at(ctx.to)));
|
||||
return one || two || cap;
|
||||
},
|
||||
rook : function (pc, dx, dy) { return (dx == 0 || dy == 0); },
|
||||
bishop: function (pc, dx, dy) { return Math.abs(dx) == Math.abs(dy); },
|
||||
queen : function (pc, dx, dy) { return (dx == 0 || dy == 0 || Math.abs(dx) == Math.abs(dy)); },
|
||||
knight: function (pc, dx, dy) { return (Math.abs(dx) == 1 && Math.abs(dy) == 2) ||
|
||||
(Math.abs(dx) == 2 && Math.abs(dy) == 1); },
|
||||
king : function (pc, dx, dy) { return Math.max(Math.abs(dx), Math.abs(dy)) == 1; }
|
||||
bishop: function (pc, dx, dy) { return abs(dx) == abs(dy); },
|
||||
queen : function (pc, dx, dy) { return (dx == 0 || dy == 0 || abs(dx) == abs(dy)); },
|
||||
knight: function (pc, dx, dy) { return (abs(dx) == 1 && abs(dy) == 2) ||
|
||||
(abs(dx) == 2 && abs(dy) == 1); },
|
||||
king : function (pc, dx, dy) { return max(abs(dx), abs(dy)) == 1; }
|
||||
};
|
||||
|
||||
function clearLine(from, to, grid) {
|
||||
var dx = Math.sign(cx(to) - cx(from));
|
||||
var dy = Math.sign(cy(to) - cy(from));
|
||||
var dx = sign(cx(to) - cx(from));
|
||||
var dy = sign(cy(to) - cy(from));
|
||||
var x = cx(from) + dx, y = cy(from) + dy;
|
||||
while (x != cx(to) || y != cy(to)) {
|
||||
if (length(grid.at({ x: x, y: y }))) return false;
|
||||
@@ -37,7 +37,7 @@ function canMove(piece, from, to, grid) {
|
||||
var dx = cx(to) - cx(from);
|
||||
var dy = cy(to) - cy(from);
|
||||
var f = deltas[piece.kind];
|
||||
if (!f || !f(piece, dx, dy, grid, to)) return false;
|
||||
if (!f || !f(piece, dx, dy, {grid: grid, to: to})) return false;
|
||||
if (piece.kind == 'knight') return true;
|
||||
return clearLine(from, to, grid);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var cellSize = 20
|
||||
var gridW = floor(config.width / cellSize)
|
||||
var gridH = floor(config.height / cellSize)
|
||||
|
||||
var snake, direction, nextDirection, apple
|
||||
var snake = null, direction = null, nextDirection = null, apple = null
|
||||
var moveInterval = 0.1
|
||||
var moveTimer = 0
|
||||
var gameState = "playing"
|
||||
@@ -36,7 +36,8 @@ function resetGame() {
|
||||
function spawnApple() {
|
||||
apple = {x:floor(random.random()*gridW), y:floor(random.random()*gridH)}
|
||||
// Re-spawn if apple lands on snake
|
||||
for (var i=0; i<length(snake); i++)
|
||||
var i = 0;
|
||||
for (i=0; i<length(snake); i++)
|
||||
if (snake[i].x == apple.x && snake[i].y == apple.y) { spawnApple(); return }
|
||||
}
|
||||
|
||||
@@ -63,7 +64,8 @@ this.update = function(dt) {
|
||||
wrap(head)
|
||||
|
||||
// Check collision with body
|
||||
for (var i=0; i<length(snake); i++) {
|
||||
var i = 0;
|
||||
for (i=0; i<length(snake); i++) {
|
||||
if (snake[i].x == head.x && snake[i].y == head.y) {
|
||||
gameState = "gameover"
|
||||
return
|
||||
@@ -81,10 +83,13 @@ this.update = function(dt) {
|
||||
this.hud = function() {
|
||||
// Optional clear screen
|
||||
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
|
||||
|
||||
|
||||
// Draw snake
|
||||
for (var i=0; i<length(snake); i++) {
|
||||
var s = snake[i]
|
||||
var i = 0;
|
||||
var s = null;
|
||||
var msg = null;
|
||||
for (i=0; i<length(snake); i++) {
|
||||
s = snake[i]
|
||||
draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, color.green)
|
||||
}
|
||||
|
||||
@@ -92,7 +97,7 @@ this.hud = function() {
|
||||
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, color.red)
|
||||
|
||||
if (gameState == "gameover") {
|
||||
var msg = "GAME OVER! Press SPACE to restart."
|
||||
msg = "GAME OVER! Press SPACE to restart."
|
||||
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, null, 0, color.white)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ function unlock_achievement(achievement_name) {
|
||||
function update_stat(stat_name, value, is_float) {
|
||||
if (!steam_available || !stats_loaded) return false;
|
||||
|
||||
var success;
|
||||
var success = null;
|
||||
if (is_float) {
|
||||
success = steam.stats.stats_set_float(stat_name, value);
|
||||
} else {
|
||||
@@ -145,7 +145,7 @@ function end_game(score) {
|
||||
function save_to_cloud(save_data) {
|
||||
if (!steam_available) return false;
|
||||
|
||||
var json_data = JSON.stringify(save_data);
|
||||
var json_data = json.encode(save_data);
|
||||
return steam.cloud.cloud_write("savegame.json", json_data);
|
||||
}
|
||||
|
||||
@@ -153,9 +153,10 @@ function load_from_cloud() {
|
||||
if (!steam_available) return null;
|
||||
|
||||
var data = steam.cloud.cloud_read("savegame.json");
|
||||
var json_str = null;
|
||||
if (data) {
|
||||
var json_str = text(data)
|
||||
return JSON.parse(json_str);
|
||||
json_str = text(data)
|
||||
return json.decode(json_str);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -169,17 +170,5 @@ function cleanup_steam() {
|
||||
}
|
||||
}
|
||||
|
||||
// Export the API
|
||||
module.exports = {
|
||||
init: init_steam,
|
||||
update: update_steam,
|
||||
cleanup: cleanup_steam,
|
||||
unlock_achievement: unlock_achievement,
|
||||
update_stat: update_stat,
|
||||
get_stat: get_stat,
|
||||
start_game: start_game,
|
||||
end_game: end_game,
|
||||
save_to_cloud: save_to_cloud,
|
||||
load_from_cloud: load_from_cloud,
|
||||
is_available: function() { return steam_available; }
|
||||
};
|
||||
// Initialize on start
|
||||
init_steam();
|
||||
@@ -56,9 +56,12 @@ var shapeKeys = array(SHAPES)
|
||||
// Initialize board with empty (0)
|
||||
function initBoard() {
|
||||
board = []
|
||||
for (var r=0; r<ROWS; r++) {
|
||||
var row = []
|
||||
for (var c=0; c<COLS; c++) push(row, 0)
|
||||
var r = 0;
|
||||
var row = null;
|
||||
var c = 0;
|
||||
for (r=0; r<ROWS; r++) {
|
||||
row = []
|
||||
for (c=0; c<COLS; c++) push(row, 0)
|
||||
push(board, row)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +69,7 @@ initBoard()
|
||||
|
||||
function randomShape() {
|
||||
var key = shapeKeys[floor(random.random()*length(shapeKeys))]
|
||||
// Make a copy of the shape’s blocks
|
||||
// Make a copy of the shape's blocks
|
||||
return {
|
||||
type: key,
|
||||
color: SHAPES[key].color,
|
||||
@@ -84,9 +87,12 @@ function spawnPiece() {
|
||||
}
|
||||
|
||||
function collides(px, py, blocks) {
|
||||
for (var i=0; i<length(blocks); i++) {
|
||||
var x = px + blocks[i][0]
|
||||
var y = py + blocks[i][1]
|
||||
var i = 0;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
for (i=0; i<length(blocks); i++) {
|
||||
x = px + blocks[i][0]
|
||||
y = py + blocks[i][1]
|
||||
if (x<0 || x>=COLS || y<0 || y>=ROWS) return true
|
||||
if (y>=0 && board[y][x]) return true
|
||||
}
|
||||
@@ -95,9 +101,12 @@ function collides(px, py, blocks) {
|
||||
|
||||
// Lock piece into board
|
||||
function lockPiece() {
|
||||
for (var i=0; i<length(piece.blocks); i++) {
|
||||
var x = pieceX + piece.blocks[i][0]
|
||||
var y = pieceY + piece.blocks[i][1]
|
||||
var i = 0;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
for (i=0; i<length(piece.blocks); i++) {
|
||||
x = pieceX + piece.blocks[i][0]
|
||||
y = pieceY + piece.blocks[i][1]
|
||||
if (y>=0) board[y][x] = piece.color
|
||||
}
|
||||
}
|
||||
@@ -105,9 +114,12 @@ function lockPiece() {
|
||||
// Rotate 90° clockwise
|
||||
function rotate(blocks) {
|
||||
// (x,y) => (y,-x)
|
||||
for (var i=0; i<length(blocks); i++) {
|
||||
var x = blocks[i][0]
|
||||
var y = blocks[i][1]
|
||||
var i = 0;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
for (i=0; i<length(blocks); i++) {
|
||||
x = blocks[i][0]
|
||||
y = blocks[i][1]
|
||||
blocks[i][0] = y
|
||||
blocks[i][1] = -x
|
||||
}
|
||||
@@ -115,14 +127,17 @@ function rotate(blocks) {
|
||||
|
||||
function clearLines() {
|
||||
var lines = 0
|
||||
for (var r=ROWS-1; r>=0;) {
|
||||
var r = ROWS-1;
|
||||
var newRow = null;
|
||||
var c = 0;
|
||||
for (r=ROWS-1; r>=0;) {
|
||||
if (every(board[r], cell => cell)) {
|
||||
lines++
|
||||
// remove row
|
||||
board = array(array(board, 0, r), array(board, r+1))
|
||||
// add empty row on top
|
||||
var newRow = []
|
||||
for (var c=0; c<COLS; c++) push(newRow, 0)
|
||||
newRow = []
|
||||
for (c=0; c<COLS; c++) push(newRow, 0)
|
||||
board.unshift(newRow)
|
||||
} else {
|
||||
r--
|
||||
@@ -195,10 +210,11 @@ this.update = function(dt) {
|
||||
// ======= End Horizontal Movement Gate =======
|
||||
|
||||
// Rotate with W (once per press, no spinning)
|
||||
var test = null;
|
||||
if (input.keyboard.down('w')) {
|
||||
if (!rotateHeld) {
|
||||
rotateHeld = true
|
||||
var test = array(piece.blocks, b => [b[0], b[1]])
|
||||
test = array(piece.blocks, b => [b[0], b[1]])
|
||||
rotate(test)
|
||||
if (!collides(pieceX, pieceY, test)) piece.blocks = test
|
||||
}
|
||||
@@ -232,9 +248,19 @@ this.hud = function() {
|
||||
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
|
||||
|
||||
// Draw board
|
||||
for (var r=0; r<ROWS; r++) {
|
||||
for (var c=0; c<COLS; c++) {
|
||||
var cell = board[r][c]
|
||||
var r = 0;
|
||||
var c = 0;
|
||||
var cell = null;
|
||||
var i = 0;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
var nx = 0;
|
||||
var ny = 0;
|
||||
var dx = 0;
|
||||
var dy = 0;
|
||||
for (r=0; r<ROWS; r++) {
|
||||
for (c=0; c<COLS; c++) {
|
||||
cell = board[r][c]
|
||||
if (!cell) continue
|
||||
draw.rectangle({x:c*TILE, y:(ROWS-1-r)*TILE, width:TILE, height:TILE}, cell)
|
||||
}
|
||||
@@ -242,9 +268,9 @@ this.hud = function() {
|
||||
|
||||
// Draw falling piece
|
||||
if (!gameOver && piece) {
|
||||
for (var i=0; i<length(piece.blocks); i++) {
|
||||
var x = pieceX + piece.blocks[i][0]
|
||||
var y = pieceY + piece.blocks[i][1]
|
||||
for (i=0; i<length(piece.blocks); i++) {
|
||||
x = pieceX + piece.blocks[i][0]
|
||||
y = pieceY + piece.blocks[i][1]
|
||||
draw.rectangle({x:x*TILE, y:(ROWS-1-y)*TILE, width:TILE, height:TILE}, piece.color)
|
||||
}
|
||||
}
|
||||
@@ -252,11 +278,11 @@ this.hud = function() {
|
||||
// Next piece window
|
||||
draw.text("Next", {x:70, y:5, width:50, height:10}, null, 0, color.white)
|
||||
if (nextPiece) {
|
||||
for (var i=0; i<length(nextPiece.blocks); i++) {
|
||||
var nx = nextPiece.blocks[i][0]
|
||||
var ny = nextPiece.blocks[i][1]
|
||||
var dx = 12 + nx
|
||||
var dy = 16 - ny
|
||||
for (i=0; i<length(nextPiece.blocks); i++) {
|
||||
nx = nextPiece.blocks[i][0]
|
||||
ny = nextPiece.blocks[i][1]
|
||||
dx = 12 + nx
|
||||
dy = 16 - ny
|
||||
draw.rectangle({x:dx*TILE, y:(ROWS-1-dy)*TILE, width:TILE, height:TILE}, nextPiece.color)
|
||||
}
|
||||
}
|
||||
@@ -269,4 +295,3 @@ this.hud = function() {
|
||||
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, null, 0, color.red)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
241
film2d.cm
241
film2d.cm
@@ -64,9 +64,9 @@ function _resolve_sprite_fit(sprite) {
|
||||
return sprite
|
||||
}
|
||||
|
||||
var scale = null
|
||||
if (fit == 'contain') {
|
||||
// Fit inside box, preserve aspect (letterbox)
|
||||
var scale
|
||||
if (tex_aspect > box_aspect) {
|
||||
// Image wider than box - constrain by width
|
||||
scale = target_w / tex_w
|
||||
@@ -79,24 +79,35 @@ function _resolve_sprite_fit(sprite) {
|
||||
return sprite
|
||||
}
|
||||
|
||||
var cover_scale = null
|
||||
var fit_ax = null
|
||||
var fit_ay = null
|
||||
var scale_w = null
|
||||
var scale_h = null
|
||||
var visible_w = null
|
||||
var visible_h = null
|
||||
var uv_w = null
|
||||
var uv_h = null
|
||||
var uv_x = null
|
||||
var uv_y = null
|
||||
if (fit == 'cover') {
|
||||
// Fill box, preserve aspect (crop via UV)
|
||||
var fit_ax = sprite.fit_anchor_x != null ? sprite.fit_anchor_x : 0.5
|
||||
var fit_ay = sprite.fit_anchor_y != null ? sprite.fit_anchor_y : 0.5
|
||||
|
||||
var scale_w = target_w / tex_w
|
||||
var scale_h = target_h / tex_h
|
||||
var scale = max(scale_w, scale_h)
|
||||
fit_ax = sprite.fit_anchor_x != null ? sprite.fit_anchor_x : 0.5
|
||||
fit_ay = sprite.fit_anchor_y != null ? sprite.fit_anchor_y : 0.5
|
||||
|
||||
scale_w = target_w / tex_w
|
||||
scale_h = target_h / tex_h
|
||||
cover_scale = max(scale_w, scale_h)
|
||||
|
||||
// Compute visible portion of texture in UV space
|
||||
var visible_w = target_w / scale
|
||||
var visible_h = target_h / scale
|
||||
|
||||
visible_w = target_w / cover_scale
|
||||
visible_h = target_h / cover_scale
|
||||
|
||||
// UV rect (0-1 space)
|
||||
var uv_w = visible_w / tex_w
|
||||
var uv_h = visible_h / tex_h
|
||||
var uv_x = (1 - uv_w) * fit_ax
|
||||
var uv_y = (1 - uv_h) * fit_ay
|
||||
uv_w = visible_w / tex_w
|
||||
uv_h = visible_h / tex_h
|
||||
uv_x = (1 - uv_w) * fit_ax
|
||||
uv_y = (1 - uv_h) * fit_ay
|
||||
|
||||
sprite.width = target_w
|
||||
sprite.height = target_h
|
||||
@@ -122,12 +133,14 @@ film2d.register = function(drawable) {
|
||||
|
||||
// Index by groups (effect routing only)
|
||||
var groups = drawable.groups || []
|
||||
for (var i = 0; i < length(groups); i++) {
|
||||
var g = groups[i]
|
||||
var i = 0
|
||||
var g = null
|
||||
for (i = 0; i < length(groups); i++) {
|
||||
g = groups[i]
|
||||
if (!group_index[g]) group_index[g] = []
|
||||
push(group_index[g], id)
|
||||
}
|
||||
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -135,21 +148,24 @@ film2d.unregister = function(id) {
|
||||
var id_str = text(id)
|
||||
var drawable = registry[id_str]
|
||||
if (!drawable) return
|
||||
|
||||
|
||||
// Remove from plane index
|
||||
var plane = drawable.plane || 'default'
|
||||
var idx = null
|
||||
if (plane_index[plane]) {
|
||||
var idx = find(plane_index[plane], id_str)
|
||||
idx = find(plane_index[plane], id_str)
|
||||
if (idx != null)
|
||||
plane_index[plane] = array(array(plane_index[plane], 0, idx), array(plane_index[plane], idx+1))
|
||||
}
|
||||
|
||||
|
||||
// Remove from group indices
|
||||
var groups = drawable.groups || []
|
||||
for (var i = 0; i < length(groups); i++) {
|
||||
var g = groups[i]
|
||||
var i = 0
|
||||
var g = null
|
||||
for (i = 0; i < length(groups); i++) {
|
||||
g = groups[i]
|
||||
if (group_index[g]) {
|
||||
var idx = find(group_index[g], id_str)
|
||||
idx = find(group_index[g], id_str)
|
||||
if (idx != null)
|
||||
group_index[g] = array(array(group_index[g], 0, idx), array(group_index[g], idx+1))
|
||||
}
|
||||
@@ -172,9 +188,10 @@ film2d.unindex_group = function(id, group) {
|
||||
}
|
||||
|
||||
film2d.reindex = function(id, old_groups, new_groups) {
|
||||
for (var i = 0; i < length(old_groups); i++)
|
||||
var i = 0
|
||||
for (i = 0; i < length(old_groups); i++)
|
||||
film2d.unindex_group(id, old_groups[i])
|
||||
for (var i = 0; i < length(new_groups); i++)
|
||||
for (i = 0; i < length(new_groups); i++)
|
||||
film2d.index_group(id, new_groups[i])
|
||||
}
|
||||
|
||||
@@ -185,16 +202,22 @@ film2d.get = function(id) {
|
||||
// Query by plane and/or group - returns array of drawables
|
||||
film2d.query = function(selector) {
|
||||
var result = []
|
||||
|
||||
var ids = null
|
||||
var i = 0
|
||||
var d = null
|
||||
var groups = null
|
||||
var seen = null
|
||||
var g = 0
|
||||
|
||||
// Query by plane (primary selection)
|
||||
if (selector.plane) {
|
||||
var ids = plane_index[selector.plane] || []
|
||||
for (var i = 0; i < length(ids); i++) {
|
||||
var d = registry[ids[i]]
|
||||
ids = plane_index[selector.plane] || []
|
||||
for (i = 0; i < length(ids); i++) {
|
||||
d = registry[ids[i]]
|
||||
if (d && d.visible != false) {
|
||||
// If also filtering by group, check membership
|
||||
if (selector.group) {
|
||||
var groups = d.groups || []
|
||||
groups = d.groups || []
|
||||
if (search(groups, selector.group) != null) push(result, d)
|
||||
} else {
|
||||
push(result, d)
|
||||
@@ -203,32 +226,32 @@ film2d.query = function(selector) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// Query by group only (for effect routing)
|
||||
if (selector.group) {
|
||||
var ids = group_index[selector.group] || []
|
||||
for (var i = 0; i < length(ids); i++) {
|
||||
var d = registry[ids[i]]
|
||||
ids = group_index[selector.group] || []
|
||||
for (i = 0; i < length(ids); i++) {
|
||||
d = registry[ids[i]]
|
||||
if (d && d.visible != false) push(result, d)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
if (selector.groups) {
|
||||
var seen = {}
|
||||
for (var g = 0; g < length(selector.groups); g++) {
|
||||
var ids = group_index[selector.groups[g]] || []
|
||||
for (var i = 0; i < length(ids); i++) {
|
||||
seen = {}
|
||||
for (g = 0; g < length(selector.groups); g++) {
|
||||
ids = group_index[selector.groups[g]] || []
|
||||
for (i = 0; i < length(ids); i++) {
|
||||
if (!seen[ids[i]]) {
|
||||
seen[ids[i]] = true
|
||||
var d = registry[ids[i]]
|
||||
d = registry[ids[i]]
|
||||
if (d && d.visible != false) push(result, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// All drawables
|
||||
var draws = array(registry, id => registry[id])
|
||||
result = array(result, filter(draws, d => d.visible != false))
|
||||
@@ -277,10 +300,14 @@ film2d.render = function(params, render_backend) {
|
||||
|
||||
// Bucket drawables by layer
|
||||
var buckets = {}
|
||||
for (var i = 0; i < length(drawables); i++) {
|
||||
var d = drawables[i]
|
||||
var layer_key = text(d.layer)
|
||||
var b = buckets[layer_key]
|
||||
var i = 0
|
||||
var d = null
|
||||
var layer_key = null
|
||||
var b = null
|
||||
for (i = 0; i < length(drawables); i++) {
|
||||
d = drawables[i]
|
||||
layer_key = text(d.layer)
|
||||
b = buckets[layer_key]
|
||||
if (!b) {
|
||||
b = []
|
||||
buckets[layer_key] = b
|
||||
@@ -296,19 +323,23 @@ film2d.render = function(params, render_backend) {
|
||||
// Merge buckets, y-sorting buckets that request it
|
||||
var y_down = camera && camera.y_down == true
|
||||
var sorted_drawables = []
|
||||
var li = 0
|
||||
var mode = null
|
||||
var keys = null
|
||||
var j = 0
|
||||
|
||||
for (var li = 0; li < length(layers); li++) {
|
||||
var layer_key = layers[li]
|
||||
var b = buckets[layer_key]
|
||||
for (li = 0; li < length(layers); li++) {
|
||||
layer_key = layers[li]
|
||||
b = buckets[layer_key]
|
||||
|
||||
var mode = layer_sort[layer_key] || "explicit"
|
||||
mode = layer_sort[layer_key] || "explicit"
|
||||
if (mode == "y") {
|
||||
var keys = array(b, d => _y_sort_key(d))
|
||||
keys = array(b, d => _y_sort_key(d))
|
||||
b = sort(b, keys) // ascending feet-y
|
||||
if (!y_down) b = reverse(b) // y_up => smaller y draws later => reverse
|
||||
}
|
||||
|
||||
for (var j = 0; j < length(b); j++) push(sorted_drawables, b[j])
|
||||
for (j = 0; j < length(b); j++) push(sorted_drawables, b[j])
|
||||
}
|
||||
|
||||
drawables = sorted_drawables
|
||||
@@ -318,9 +349,10 @@ film2d.render = function(params, render_backend) {
|
||||
push(commands, { cmd: "set_camera", camera: camera })
|
||||
|
||||
var batches = _batch_drawables(drawables)
|
||||
var batch = null
|
||||
|
||||
for (var i = 0; i < length(batches); i++) {
|
||||
var batch = batches[i]
|
||||
for (i = 0; i < length(batches); i++) {
|
||||
batch = batches[i]
|
||||
if (batch.type == "sprite_batch")
|
||||
push(commands, { cmd: "draw_batch", batch_type: "sprites", geometry: { sprites: batch.sprites }, texture: batch.texture, material: batch.material })
|
||||
else if (batch.type == "mesh2d_batch")
|
||||
@@ -341,17 +373,40 @@ function _batch_drawables(drawables) {
|
||||
var batches = []
|
||||
var current = null
|
||||
var default_mat = {blend: 'alpha', sampler: 'nearest'}
|
||||
|
||||
for (var i = 0; i < length(drawables); i++) {
|
||||
var d = drawables[i]
|
||||
|
||||
var i = 0
|
||||
var d = null
|
||||
var tex = null
|
||||
var mat = null
|
||||
var particles = null
|
||||
var emitter_opacity = 0
|
||||
var emitter_tint = null
|
||||
var p = 0
|
||||
var part = null
|
||||
var pc = null
|
||||
var sprite = null
|
||||
var tiles = null
|
||||
var tile_w = 0
|
||||
var tile_h = 0
|
||||
var off_x = 0
|
||||
var off_y = 0
|
||||
var tilemap_opacity = 0
|
||||
var tilemap_tint = null
|
||||
var x = 0
|
||||
var y = 0
|
||||
var img = null
|
||||
var wx = 0
|
||||
var wy = 0
|
||||
|
||||
for (i = 0; i < length(drawables); i++) {
|
||||
d = drawables[i]
|
||||
|
||||
if (d.type == 'sprite') {
|
||||
// Resolve fit mode (computes final width/height/uv_rect)
|
||||
_resolve_sprite_fit(d)
|
||||
|
||||
var tex = d.texture || d.image
|
||||
var mat = d.material || {blend: 'alpha', sampler: d.filter || 'nearest'}
|
||||
|
||||
|
||||
tex = d.texture || d.image
|
||||
mat = d.material || {blend: 'alpha', sampler: d.filter || 'nearest'}
|
||||
|
||||
if (current && current.type == 'sprite_batch' && current.texture == tex && _mat_eq(current.material, mat)) {
|
||||
push(current.sprites, d)
|
||||
} else {
|
||||
@@ -360,16 +415,16 @@ function _batch_drawables(drawables) {
|
||||
}
|
||||
} else if (d.type == 'particles') {
|
||||
// Convert particles to sprites
|
||||
var tex = d.texture || d.image
|
||||
var mat = d.material || default_mat
|
||||
var particles = d.particles || []
|
||||
var emitter_opacity = d.opacity != null ? d.opacity : 1
|
||||
var emitter_tint = d.tint || {r: 1, g: 1, b: 1, a: 1}
|
||||
|
||||
for (var p = 0; p < length(particles); p++) {
|
||||
var part = particles[p]
|
||||
var pc = part.color || {r: 1, g: 1, b: 1, a: 1}
|
||||
var sprite = {
|
||||
tex = d.texture || d.image
|
||||
mat = d.material || default_mat
|
||||
particles = d.particles || []
|
||||
emitter_opacity = d.opacity != null ? d.opacity : 1
|
||||
emitter_tint = d.tint || {r: 1, g: 1, b: 1, a: 1}
|
||||
|
||||
for (p = 0; p < length(particles); p++) {
|
||||
part = particles[p]
|
||||
pc = part.color || {r: 1, g: 1, b: 1, a: 1}
|
||||
sprite = {
|
||||
type: 'sprite',
|
||||
pos: part.pos,
|
||||
width: (d.width || 16) * (part.scale || 1),
|
||||
@@ -380,7 +435,7 @@ function _batch_drawables(drawables) {
|
||||
opacity: emitter_opacity,
|
||||
tint: emitter_tint
|
||||
}
|
||||
|
||||
|
||||
if (current && current.type == 'sprite_batch' && current.texture == tex && _mat_eq(current.material, mat)) {
|
||||
push(current.sprites, sprite)
|
||||
} else {
|
||||
@@ -390,25 +445,25 @@ function _batch_drawables(drawables) {
|
||||
}
|
||||
} else if (d.type == 'tilemap') {
|
||||
// Expand tilemap to sprites
|
||||
var tiles = d.tiles || []
|
||||
var tile_w = d.tile_width || 1
|
||||
var tile_h = d.tile_height || 1
|
||||
var off_x = d.offset_x || 0
|
||||
var off_y = d.offset_y || 0
|
||||
var tilemap_opacity = d.opacity != null ? d.opacity : 1
|
||||
var tilemap_tint = d.tint || {r: 1, g: 1, b: 1, a: 1}
|
||||
|
||||
for (var x = 0; x < length(tiles); x++) {
|
||||
tiles = d.tiles || []
|
||||
tile_w = d.tile_width || 1
|
||||
tile_h = d.tile_height || 1
|
||||
off_x = d.offset_x || 0
|
||||
off_y = d.offset_y || 0
|
||||
tilemap_opacity = d.opacity != null ? d.opacity : 1
|
||||
tilemap_tint = d.tint || {r: 1, g: 1, b: 1, a: 1}
|
||||
|
||||
for (x = 0; x < length(tiles); x++) {
|
||||
if (!tiles[x]) continue
|
||||
for (var y = 0; y < length(tiles[x]); y++) {
|
||||
var img = tiles[x][y]
|
||||
for (y = 0; y < length(tiles[x]); y++) {
|
||||
img = tiles[x][y]
|
||||
if (!img) continue
|
||||
|
||||
var wx = (x + off_x) * tile_w
|
||||
var wy = (y + off_y) * tile_h
|
||||
|
||||
|
||||
wx = (x + off_x) * tile_w
|
||||
wy = (y + off_y) * tile_h
|
||||
|
||||
// Center anchor for sprite
|
||||
var sprite = {
|
||||
sprite = {
|
||||
type: 'sprite',
|
||||
image: img,
|
||||
pos: {x: wx + tile_w/2, y: wy + tile_h/2},
|
||||
@@ -420,10 +475,10 @@ function _batch_drawables(drawables) {
|
||||
opacity: tilemap_opacity,
|
||||
tint: tilemap_tint
|
||||
}
|
||||
|
||||
|
||||
// Batching
|
||||
var tex = img
|
||||
var mat = default_mat
|
||||
tex = img
|
||||
mat = default_mat
|
||||
if (current && current.type == 'sprite_batch' && current.texture == tex && _mat_eq(current.material, mat)) {
|
||||
push(current.sprites, sprite)
|
||||
} else {
|
||||
@@ -434,8 +489,8 @@ function _batch_drawables(drawables) {
|
||||
}
|
||||
} else if (d.type == 'mesh2d') {
|
||||
// Mesh2d drawables - arbitrary triangle meshes (for lines, ropes, etc)
|
||||
var tex = d.texture || d.image
|
||||
var mat = d.material || {blend: d.blend || 'alpha', sampler: d.filter || 'linear'}
|
||||
tex = d.texture || d.image
|
||||
mat = d.material || {blend: d.blend || 'alpha', sampler: d.filter || 'linear'}
|
||||
|
||||
if (current && current.type == 'mesh2d_batch' && current.texture == tex && _mat_eq(current.material, mat)) {
|
||||
push(current.meshes, d)
|
||||
|
||||
30
fx_graph.cm
30
fx_graph.cm
@@ -60,11 +60,11 @@
|
||||
var fx_graph = {}
|
||||
|
||||
fx_graph.add_node = function(type, params) {
|
||||
params = params || {}
|
||||
var local_params = params || {}
|
||||
var node = {
|
||||
id: this.next_id++,
|
||||
type: type,
|
||||
params: params,
|
||||
params: local_params,
|
||||
output: {node_id: this.next_id - 1, slot: 'output'}
|
||||
}
|
||||
push(this.nodes, node)
|
||||
@@ -137,7 +137,7 @@ NODE_EXECUTORS.render_view = function(params, backend) {
|
||||
var renderer = params.renderer
|
||||
|
||||
// Determine target
|
||||
var target
|
||||
var target = null
|
||||
if (target_spec == 'screen') {
|
||||
target = 'screen'
|
||||
} else if (target_spec && target_spec.texture) {
|
||||
@@ -257,17 +257,18 @@ NODE_EXECUTORS.clip_rect = function(params, backend) {
|
||||
|
||||
// Insert scissor after begin_render
|
||||
var insert_idx = 0
|
||||
for (var i = 0; i < length(commands); i++) {
|
||||
var i = 0
|
||||
for (i = 0; i < length(commands); i++) {
|
||||
if (commands[i].cmd == 'begin_render') {
|
||||
insert_idx = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
commands = array(array(array(commands, 0, insert_idx), [{cmd: 'scissor', rect: rect}]), array(commands, insert_idx))
|
||||
|
||||
|
||||
// Add scissor reset before end_render
|
||||
for (var i = length(commands) - 1; i >= 0; i--) {
|
||||
for (i = length(commands) - 1; i >= 0; i--) {
|
||||
if (commands[i].cmd == 'end_render') {
|
||||
commands = array(array(array(commands, 0, i), [{cmd: 'scissor', rect:null}]) ,array(commands, i+1))
|
||||
break
|
||||
@@ -287,7 +288,8 @@ NODE_EXECUTORS.blit = function(params, backend) {
|
||||
var src_target = input && input.target ? input.target : input
|
||||
if (!src_target) return {target: null, commands: []}
|
||||
|
||||
var target
|
||||
var target = null
|
||||
var key = null
|
||||
if (target_spec == 'screen') {
|
||||
target = 'screen'
|
||||
} else if (target_spec && target_spec.target) {
|
||||
@@ -298,7 +300,7 @@ NODE_EXECUTORS.blit = function(params, backend) {
|
||||
target = target_spec
|
||||
} else if (target_spec && target_spec.width) {
|
||||
// Target spec - use a consistent key based on the spec itself
|
||||
var key = `blit_${target_spec.width}x${target_spec.height}`
|
||||
key = `blit_${target_spec.width}x${target_spec.height}`
|
||||
target = backend.get_or_create_target(target_spec.width, target_spec.height, key)
|
||||
} else {
|
||||
return {target: null, commands: []}
|
||||
@@ -336,16 +338,18 @@ NODE_EXECUTORS.shader_pass = function(params, backend) {
|
||||
if (!input || !input.target) return {target: null, commands: []}
|
||||
|
||||
var src = input.target
|
||||
var target
|
||||
|
||||
var target = null
|
||||
var w = 0
|
||||
var h = 0
|
||||
|
||||
if (output_spec == 'screen') {
|
||||
target = 'screen'
|
||||
} else if (output_spec && output_spec.texture) {
|
||||
target = output_spec
|
||||
} else {
|
||||
// Default to input size if not specified
|
||||
var w = output_spec && output_spec.width ? output_spec.width : src.width
|
||||
var h = output_spec && output_spec.height ? output_spec.height : src.height
|
||||
w = output_spec && output_spec.width ? output_spec.width : src.width
|
||||
h = output_spec && output_spec.height ? output_spec.height : src.height
|
||||
target = backend.get_or_create_target(w, h, 'shader_' + shader + '_' + params._node_id)
|
||||
}
|
||||
|
||||
|
||||
32
geometry.c
32
geometry.c
@@ -615,7 +615,7 @@ JSC_CCALL(geometry_tilemap_to_data,
|
||||
JS_FreeValue(js, pos_y_val);
|
||||
|
||||
JSValue tiles_array = JS_GetPropertyStr(js, tilemap_obj, "tiles");
|
||||
if (!JS_IsArray(js, tiles_array)) {
|
||||
if (!JS_IsArray(tiles_array)) {
|
||||
JS_FreeValue(js, tiles_array);
|
||||
return JS_ThrowTypeError(js, "tilemap.tiles must be an array");
|
||||
}
|
||||
@@ -624,11 +624,11 @@ JSC_CCALL(geometry_tilemap_to_data,
|
||||
int tile_count = 0;
|
||||
int tiles_len = JS_ArrayLength(js, tiles_array);
|
||||
for (int x = 0; x < tiles_len; x++) {
|
||||
JSValue col = JS_GetPropertyUint32(js, tiles_array, x);
|
||||
if (JS_IsArray(js, col)) {
|
||||
JSValue col = JS_GetPropertyNumber(js, tiles_array, x);
|
||||
if (JS_IsArray(col)) {
|
||||
int col_len = JS_ArrayLength(js, col);
|
||||
for (int y = 0; y < col_len; y++) {
|
||||
JSValue tile = JS_GetPropertyUint32(js, col, y);
|
||||
JSValue tile = JS_GetPropertyNumber(js, col, y);
|
||||
if (!JS_IsNull(tile) && !JS_IsNull(tile)) {
|
||||
tile_count++;
|
||||
}
|
||||
@@ -657,11 +657,11 @@ JSC_CCALL(geometry_tilemap_to_data,
|
||||
int index_idx = 0;
|
||||
|
||||
for (int x = 0; x < tiles_len; x++) {
|
||||
JSValue col = JS_GetPropertyUint32(js, tiles_array, x);
|
||||
if (JS_IsArray(js, col)) {
|
||||
JSValue col = JS_GetPropertyNumber(js, tiles_array, x);
|
||||
if (JS_IsArray(col)) {
|
||||
int col_len = JS_ArrayLength(js, col);
|
||||
for (int y = 0; y < col_len; y++) {
|
||||
JSValue tile = JS_GetPropertyUint32(js, col, y);
|
||||
JSValue tile = JS_GetPropertyNumber(js, col, y);
|
||||
if (!JS_IsNull(tile) && !JS_IsNull(tile)) {
|
||||
// Calculate world position
|
||||
// x and y are array indices, need to convert to logical coordinates
|
||||
@@ -781,7 +781,7 @@ static void print_buffers(float *xy_data, float *uv_data, SDL_FColor *color_data
|
||||
|
||||
JSC_CCALL(geometry_sprites_to_data,
|
||||
JSValue sprites_array = argv[0];
|
||||
if (!JS_IsArray(js, sprites_array)) {
|
||||
if (!JS_IsArray(sprites_array)) {
|
||||
return JS_ThrowTypeError(js, "sprites must be an array");
|
||||
}
|
||||
|
||||
@@ -804,7 +804,7 @@ JSC_CCALL(geometry_sprites_to_data,
|
||||
int index_idx = 0;
|
||||
|
||||
for (int i = 0; i < sprite_count; i++) {
|
||||
JSValue sprite = JS_GetPropertyUint32(js, sprites_array, i);
|
||||
JSValue sprite = JS_GetPropertyNumber(js, sprites_array, i);
|
||||
|
||||
// Get sprite properties
|
||||
JSValue pos_val = JS_GetPropertyStr(js, sprite, "pos");
|
||||
@@ -989,16 +989,16 @@ JSC_CCALL(geometry_transform_xy_blob,
|
||||
}
|
||||
|
||||
JSValue camera_params = argv[1];
|
||||
if (!JS_IsArray(js, camera_params) || JS_ArrayLength(js, camera_params) != 4) {
|
||||
if (!JS_IsArray(camera_params) || JS_ArrayLength(js, camera_params) != 4) {
|
||||
return JS_ThrowTypeError(js, "Second argument must be an array of 4 camera transform parameters [a, c, e, f]");
|
||||
}
|
||||
|
||||
// Get camera transform parameters
|
||||
double a, c, e, f;
|
||||
JSValue a_val = JS_GetPropertyUint32(js, camera_params, 0);
|
||||
JSValue c_val = JS_GetPropertyUint32(js, camera_params, 1);
|
||||
JSValue e_val = JS_GetPropertyUint32(js, camera_params, 2);
|
||||
JSValue f_val = JS_GetPropertyUint32(js, camera_params, 3);
|
||||
JSValue a_val = JS_GetPropertyNumber(js, camera_params, 0);
|
||||
JSValue c_val = JS_GetPropertyNumber(js, camera_params, 1);
|
||||
JSValue e_val = JS_GetPropertyNumber(js, camera_params, 2);
|
||||
JSValue f_val = JS_GetPropertyNumber(js, camera_params, 3);
|
||||
|
||||
JS_ToFloat64(js, &a, a_val);
|
||||
JS_ToFloat64(js, &c, c_val);
|
||||
@@ -1058,7 +1058,7 @@ JSC_CCALL(geometry_array_blob,
|
||||
size_t len = JS_ArrayLength(js,arr);
|
||||
float data[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
JSValue val = JS_GetPropertyUint32(js,arr,i);
|
||||
JSValue val = JS_GetPropertyNumber(js,arr,i);
|
||||
data[i] = js2number(js, val);
|
||||
JS_FreeValue(js,val);
|
||||
}
|
||||
@@ -1168,7 +1168,7 @@ JSC_CCALL(geometry_weave,
|
||||
int num_elements = -1;
|
||||
|
||||
for (uint32_t i = 0; i < stream_count; i++) {
|
||||
JSValue stream_obj = JS_GetPropertyUint32(js, argv[0], i);
|
||||
JSValue stream_obj = JS_GetPropertyNumber(js, argv[0], i);
|
||||
JSValue data_blob = JS_GetPropertyStr(js, stream_obj, "data");
|
||||
JSValue stride_val = JS_GetPropertyStr(js, stream_obj, "stride");
|
||||
|
||||
|
||||
63
gestures.cm
63
gestures.cm
@@ -17,11 +17,23 @@ gesture.reset = function() {
|
||||
|
||||
gesture.on_input = function(action_id, action) {
|
||||
if (search(action_id, 'gamepad_touchpad_') == null) return
|
||||
|
||||
|
||||
var finger = action.finger || 0
|
||||
var touchpad = action.touchpad || 0
|
||||
var fingerId = `${touchpad}_${finger}`
|
||||
|
||||
var touchCount = 0
|
||||
var fingers = null
|
||||
var currentDist = 0
|
||||
var d = 0
|
||||
var gesture_type = null
|
||||
var touch = null
|
||||
var dt = 0
|
||||
var dx = 0
|
||||
var dy = 0
|
||||
var absX = 0
|
||||
var absY = 0
|
||||
var dir = null
|
||||
|
||||
if (action_id == 'gamepad_touchpad_down') {
|
||||
// Add new touch
|
||||
this.touches[fingerId] = {
|
||||
@@ -31,9 +43,9 @@ gesture.on_input = function(action_id, action) {
|
||||
startY: action.y,
|
||||
startTime: time.number()
|
||||
}
|
||||
|
||||
var touchCount = length(array(this.touches))
|
||||
|
||||
|
||||
touchCount = length(array(this.touches))
|
||||
|
||||
if (touchCount == 1) {
|
||||
// Single touch started
|
||||
this.gestureState = 'single'
|
||||
@@ -41,7 +53,7 @@ gesture.on_input = function(action_id, action) {
|
||||
} else if (touchCount == 2) {
|
||||
// Two touches - potential pinch
|
||||
this.gestureState = 'multi'
|
||||
var fingers = array(array(this.touches), k => this.touches[k])
|
||||
fingers = array(array(this.touches), k => this.touches[k])
|
||||
this.startDist = this.dist(fingers[0], fingers[1])
|
||||
}
|
||||
}
|
||||
@@ -50,17 +62,17 @@ gesture.on_input = function(action_id, action) {
|
||||
// Update touch position
|
||||
this.touches[fingerId].x = action.x
|
||||
this.touches[fingerId].y = action.y
|
||||
|
||||
var touchCount = length(array(this.touches))
|
||||
|
||||
|
||||
touchCount = length(array(this.touches))
|
||||
|
||||
if (touchCount == 2 && this.gestureState == 'multi') {
|
||||
// Check for pinch gesture
|
||||
var fingers = array(array(this.touches), k => this.touches[k])
|
||||
var currentDist = this.dist(fingers[0], fingers[1])
|
||||
var d = currentDist - this.startDist
|
||||
|
||||
fingers = array(array(this.touches), k => this.touches[k])
|
||||
currentDist = this.dist(fingers[0], fingers[1])
|
||||
d = currentDist - this.startDist
|
||||
|
||||
if (abs(d) >= this.PINCH_TH) {
|
||||
var gesture_type = d > 0 ? 'pinch_out' : 'pinch_in'
|
||||
gesture_type = d > 0 ? 'pinch_out' : 'pinch_in'
|
||||
// scene.recurse(game.root, 'on_input', [gesture_type, { delta: d }])
|
||||
this.startDist = currentDist
|
||||
}
|
||||
@@ -69,19 +81,20 @@ gesture.on_input = function(action_id, action) {
|
||||
}
|
||||
else if (action_id == 'gamepad_touchpad_up') {
|
||||
if (this.touches[fingerId]) {
|
||||
var touch = this.touches[fingerId]
|
||||
var touchCount = length(array(this.touches))
|
||||
|
||||
touch = this.touches[fingerId]
|
||||
touchCount = length(array(this.touches))
|
||||
|
||||
// Check for swipe if this was the only/last touch
|
||||
if (touchCount == 1 && this.gestureState == 'single') {
|
||||
var dt = time.number() - touch.startTime
|
||||
var dx = action.x - touch.startX
|
||||
var dy = action.y - touch.startY
|
||||
|
||||
dt = time.number() - touch.startTime
|
||||
dx = action.x - touch.startX
|
||||
dy = action.y - touch.startY
|
||||
|
||||
if (dt < this.MAX_TIME / 1000) { // Convert to seconds
|
||||
var absX = abs(dx), absY = abs(dy)
|
||||
absX = abs(dx)
|
||||
absY = abs(dy)
|
||||
if (absX > this.MIN_SWIPE / 100 || absY > this.MIN_SWIPE / 100) { // Normalize for 0-1 range
|
||||
var dir = absX > absY
|
||||
dir = absX > absY
|
||||
? (dx > 0 ? 'swipe_right' : 'swipe_left')
|
||||
: (dy > 0 ? 'swipe_down' : 'swipe_up')
|
||||
audio.play('swipe')
|
||||
@@ -89,10 +102,10 @@ gesture.on_input = function(action_id, action) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Remove touch
|
||||
delete this.touches[fingerId]
|
||||
|
||||
|
||||
// Reset if no touches left
|
||||
if (length(array(this.touches)) == 0) {
|
||||
this.gestureState = null
|
||||
|
||||
186
graphics.cm
186
graphics.cm
@@ -42,7 +42,7 @@ function calc_image_size(img) {
|
||||
|
||||
function decorate_rect_px(img) {
|
||||
// default UV rect is the whole image if none supplied
|
||||
img.rect ??= {x:0, y:0, width:1, height:1} // [u0,v0,uw,vh] in 0-1
|
||||
if (!img.rect) img.rect = {x:0, y:0, width:1, height:1} // [u0,v0,uw,vh] in 0-1
|
||||
|
||||
var width = 0, height = 0;
|
||||
if (img.texture) {
|
||||
@@ -87,27 +87,32 @@ function wrapFrames(arr){ /* [{surface,time,rect}, …] → [{image,t
|
||||
}
|
||||
});
|
||||
}
|
||||
function makeAnim(frames, loop=true){
|
||||
return { frames, loop }
|
||||
function makeAnim(frames, loop){
|
||||
var local_loop = loop != null ? loop : true
|
||||
return { frames: frames, loop: local_loop }
|
||||
}
|
||||
|
||||
function decode_image(bytes, ext)
|
||||
{
|
||||
switch(ext) {
|
||||
case 'gif': return decode_gif(gif.decode(bytes))
|
||||
case 'ase':
|
||||
case 'aseprite': return decode_aseprite(aseprite.decode(bytes))
|
||||
case 'qoi': return qoi.decode(bytes) // returns single surface
|
||||
case 'png': return png.decode(bytes) // returns single surface
|
||||
case 'jpg':
|
||||
case 'jpeg': return png.decode(bytes) // png.decode handles jpg too via stb_image
|
||||
case 'bmp': return png.decode(bytes) // png.decode handles bmp too via stb_image
|
||||
default:
|
||||
// Try QOI first since it's fast to check
|
||||
var qoi_result = qoi.decode(bytes)
|
||||
if (qoi_result) return qoi_result
|
||||
// Fall back to png decoder for other formats (uses stb_image)
|
||||
return png.decode(bytes)
|
||||
var qoi_result = null
|
||||
if (ext == 'gif') {
|
||||
return decode_gif(gif.decode(bytes))
|
||||
} else if (ext == 'ase' || ext == 'aseprite') {
|
||||
return decode_aseprite(aseprite.decode(bytes))
|
||||
} else if (ext == 'qoi') {
|
||||
return qoi.decode(bytes) // returns single surface
|
||||
} else if (ext == 'png') {
|
||||
return png.decode(bytes) // returns single surface
|
||||
} else if (ext == 'jpg' || ext == 'jpeg') {
|
||||
return png.decode(bytes) // png.decode handles jpg too via stb_image
|
||||
} else if (ext == 'bmp') {
|
||||
return png.decode(bytes) // png.decode handles bmp too via stb_image
|
||||
} else {
|
||||
// Try QOI first since it's fast to check
|
||||
qoi_result = qoi.decode(bytes)
|
||||
if (qoi_result) return qoi_result
|
||||
// Fall back to png decoder for other formats (uses stb_image)
|
||||
return png.decode(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,52 +156,49 @@ function decode_aseprite(decoded) {
|
||||
}
|
||||
|
||||
function create_image(path){
|
||||
try{
|
||||
def bytes = io.slurp(path);
|
||||
def bytes = io.slurp(path);
|
||||
|
||||
var ext = pop(array(path, '.'))
|
||||
var raw = decode_image(bytes, ext);
|
||||
|
||||
/* ── Case A: single surface (from make_texture) ────────────── */
|
||||
if(raw && raw.width && raw.pixels && !is_array(raw)) {
|
||||
return graphics.Image(raw)
|
||||
}
|
||||
var ext = pop(array(path, '.'))
|
||||
var raw = decode_image(bytes, ext);
|
||||
var anims = null
|
||||
var keys = null
|
||||
|
||||
/* ── Case B: array of surfaces (from make_gif) ────────────── */
|
||||
if(is_array(raw)) {
|
||||
// Single frame GIF returns array with one surface
|
||||
if(length(raw) == 1 && !raw[0].time) {
|
||||
return graphics.Image(raw[0])
|
||||
}
|
||||
// Multiple frames - create animation
|
||||
return makeAnim(wrapFrames(raw), true);
|
||||
}
|
||||
|
||||
if(is_object(raw) && !raw.width) {
|
||||
if(raw.surface)
|
||||
return graphics.Image(raw.surface)
|
||||
|
||||
if(raw.frames && is_array(raw.frames) && raw.loop != null)
|
||||
return makeAnim(wrapFrames(raw.frames), !!raw.loop);
|
||||
|
||||
def anims = {};
|
||||
var keys = array(raw)
|
||||
arrfor(keys, function(name) {
|
||||
var anim = raw[name]
|
||||
if(anim && is_array(anim.frames))
|
||||
anims[name] = makeAnim(wrapFrames(anim.frames), !!anim.loop);
|
||||
else if(anim && anim.surface)
|
||||
anims[name] = graphics.Image(anim.surface);
|
||||
})
|
||||
if(length(array(anims))) return anims;
|
||||
}
|
||||
|
||||
throw Error('Unsupported image structure from decoder');
|
||||
|
||||
}catch(e){
|
||||
log.error(`Error loading image ${path}: ${e.message}`);
|
||||
throw e;
|
||||
/* ── Case A: single surface (from make_texture) ────────────── */
|
||||
if(raw && raw.width && raw.pixels && !is_array(raw)) {
|
||||
return graphics.Image(raw)
|
||||
}
|
||||
|
||||
/* ── Case B: array of surfaces (from make_gif) ────────────── */
|
||||
if(is_array(raw)) {
|
||||
// Single frame GIF returns array with one surface
|
||||
if(length(raw) == 1 && !raw[0].time) {
|
||||
return graphics.Image(raw[0])
|
||||
}
|
||||
// Multiple frames - create animation
|
||||
return makeAnim(wrapFrames(raw), true);
|
||||
}
|
||||
|
||||
if(is_object(raw) && !raw.width) {
|
||||
if(raw.surface)
|
||||
return graphics.Image(raw.surface)
|
||||
|
||||
if(raw.frames && is_array(raw.frames) && raw.loop != null)
|
||||
return makeAnim(wrapFrames(raw.frames), !!raw.loop);
|
||||
|
||||
anims = {};
|
||||
keys = array(raw)
|
||||
arrfor(keys, function(name) {
|
||||
var anim = raw[name]
|
||||
if(anim && is_array(anim.frames))
|
||||
anims[name] = makeAnim(wrapFrames(anim.frames), !!anim.loop);
|
||||
else if(anim && anim.surface)
|
||||
anims[name] = graphics.Image(anim.surface);
|
||||
})
|
||||
if(length(array(anims))) return anims;
|
||||
}
|
||||
|
||||
log.error(`Error loading image ${path}: Unsupported image structure from decoder`);
|
||||
return null
|
||||
}
|
||||
|
||||
var image = {}
|
||||
@@ -212,7 +214,7 @@ image.dimensions = function() {
|
||||
return [width, height].scale([this.rect[2], this.rect[3]])
|
||||
}
|
||||
|
||||
var spritesheet
|
||||
var spritesheet = null
|
||||
var sheet_frames = []
|
||||
var sheetsize = 1024
|
||||
|
||||
@@ -249,8 +251,10 @@ graphics.from_surface = function(surf)
|
||||
|
||||
graphics.from = function(id, data)
|
||||
{
|
||||
if (!is_text(id))
|
||||
throw Error('Expected a string ID')
|
||||
if (!is_text(id)) {
|
||||
log.error('Expected a string ID')
|
||||
return null
|
||||
}
|
||||
|
||||
if (is_blob(data))
|
||||
return graphics.texture_from_data(data)
|
||||
@@ -258,14 +262,20 @@ graphics.from = function(id, data)
|
||||
|
||||
graphics.texture = function texture(path) {
|
||||
if (is_proto(path, graphics.Image)) return path
|
||||
|
||||
if (!is_text(path))
|
||||
throw Error('need a string for graphics.texture')
|
||||
|
||||
if (!is_text(path)) {
|
||||
log.error('need a string for graphics.texture')
|
||||
return null
|
||||
}
|
||||
|
||||
var parts = array(path, ':')
|
||||
var id = parts[0]
|
||||
var animName = parts[1]
|
||||
var frameIndex = parts[2]
|
||||
var ipath = null
|
||||
var result = null
|
||||
var idx = null
|
||||
var anim = null
|
||||
|
||||
// Handle the case where animName is actually a frame index (e.g., "gears:0")
|
||||
if (animName != null && frameIndex == null && !is_null(number(animName))) {
|
||||
@@ -274,14 +284,14 @@ graphics.texture = function texture(path) {
|
||||
}
|
||||
|
||||
if (!cache[id]) {
|
||||
var ipath = res.find_image(id)
|
||||
|
||||
ipath = res.find_image(id)
|
||||
|
||||
if (!ipath) {
|
||||
// If still not found, return notex
|
||||
return graphics.texture('notex')
|
||||
}
|
||||
|
||||
var result = create_image(ipath)
|
||||
|
||||
result = create_image(ipath)
|
||||
cache[id] = result
|
||||
}
|
||||
|
||||
@@ -294,7 +304,7 @@ graphics.texture = function texture(path) {
|
||||
if (!animName && frameIndex != null) {
|
||||
// If cached is a single animation (has .frames property)
|
||||
if (cached.frames && is_array(cached.frames)) {
|
||||
var idx = number(frameIndex)
|
||||
idx = number(frameIndex)
|
||||
if (idx == null) return cached
|
||||
// Wrap the index
|
||||
idx = idx % length(cached.frames)
|
||||
@@ -319,8 +329,8 @@ graphics.texture = function texture(path) {
|
||||
// If cached is a single animation (has .frames property)
|
||||
if (cached.frames && is_array(cached.frames)) {
|
||||
if (frameIndex != null) {
|
||||
var idx = number(frameIndex)
|
||||
if (isNaN(idx)) return cached
|
||||
idx = number(frameIndex)
|
||||
if (idx == null) return cached
|
||||
// Wrap the index
|
||||
idx = idx % length(cached.frames)
|
||||
return cached.frames[idx].image
|
||||
@@ -331,13 +341,14 @@ graphics.texture = function texture(path) {
|
||||
|
||||
// If cached is an object of multiple animations
|
||||
if (is_object(cached) && !cached.frames) {
|
||||
var anim = cached[animName]
|
||||
anim = cached[animName]
|
||||
if (!anim)
|
||||
throw Error(`animation ${animName} not found in ${id}`)
|
||||
log.error(`animation ${animName} not found in ${id}`)
|
||||
return null
|
||||
|
||||
if (frameIndex != null) {
|
||||
var idx = number(frameIndex)
|
||||
if (isNaN(idx)) return anim
|
||||
idx = number(frameIndex)
|
||||
if (idx == null) return anim
|
||||
|
||||
if (is_proto(anim, graphics.Image)) {
|
||||
// Single image animation - any frame index returns the image
|
||||
@@ -412,19 +423,25 @@ var datas = []
|
||||
|
||||
graphics.get_font = function get_font(path) {
|
||||
if (is_object(path)) return path
|
||||
if (!is_text(path))
|
||||
throw Error(`Can't find font with path: ${path}`)
|
||||
if (!is_text(path)) {
|
||||
log.error(`Can't find font with path: ${path}`)
|
||||
return null
|
||||
}
|
||||
|
||||
var parts = array(path, '.')
|
||||
var size = 16 // default size
|
||||
var font_path = path
|
||||
parts[1] = number(parts[1])
|
||||
if (parts[1]) {
|
||||
path = parts[0]
|
||||
font_path = parts[0]
|
||||
size = parts[1]
|
||||
}
|
||||
|
||||
var fullpath = res.find_font(path)
|
||||
if (!fullpath) throw Error(`Cannot load font ${path}`)
|
||||
var fullpath = res.find_font(font_path)
|
||||
if (!fullpath) {
|
||||
log.error(`Cannot load font ${path}`)
|
||||
return null
|
||||
}
|
||||
|
||||
var fontstr = `${fullpath}.${size}`
|
||||
if (fontcache[fontstr]) return fontcache[fontstr]
|
||||
@@ -441,7 +458,8 @@ graphics.queue_sprite_mesh = function(queue) {
|
||||
var sprites = filter(queue, x => x.type == 'sprite')
|
||||
if (length(sprites) == 0) return []
|
||||
var mesh = graphics.make_sprite_mesh(sprites)
|
||||
for (var i = 0; i < length(sprites); i++) {
|
||||
var i = 0
|
||||
for (i = 0; i < length(sprites); i++) {
|
||||
sprites[i].mesh = mesh
|
||||
sprites[i].first_index = i*6
|
||||
sprites[i].num_indices = 6
|
||||
|
||||
36
imgui.cpp
36
imgui.cpp
@@ -38,8 +38,8 @@ static inline ImVec2 js2imvec2(JSContext *js, JSValue v)
|
||||
{
|
||||
ImVec2 c;
|
||||
double dx, dy;
|
||||
JSValue x = JS_GetPropertyUint32(js, v, 0);
|
||||
JSValue y = JS_GetPropertyUint32(js, v, 1);
|
||||
JSValue x = JS_GetPropertyNumber(js, v, 0);
|
||||
JSValue y = JS_GetPropertyNumber(js, v, 1);
|
||||
JS_ToFloat64(js, &dx, x);
|
||||
JS_ToFloat64(js, &dy, y);
|
||||
JS_FreeValue(js, x);
|
||||
@@ -53,10 +53,10 @@ static inline ImVec4 js2imvec4(JSContext *js, JSValue v)
|
||||
{
|
||||
ImVec4 c;
|
||||
double dx, dy, dz, dw;
|
||||
JSValue x = JS_GetPropertyUint32(js, v, 0);
|
||||
JSValue y = JS_GetPropertyUint32(js, v, 1);
|
||||
JSValue z = JS_GetPropertyUint32(js, v, 2);
|
||||
JSValue w = JS_GetPropertyUint32(js, v, 3);
|
||||
JSValue x = JS_GetPropertyNumber(js, v, 0);
|
||||
JSValue y = JS_GetPropertyNumber(js, v, 1);
|
||||
JSValue z = JS_GetPropertyNumber(js, v, 2);
|
||||
JSValue w = JS_GetPropertyNumber(js, v, 3);
|
||||
JS_ToFloat64(js, &dx, x);
|
||||
JS_ToFloat64(js, &dy, y);
|
||||
JS_ToFloat64(js, &dz, z);
|
||||
@@ -75,8 +75,8 @@ static inline ImVec4 js2imvec4(JSContext *js, JSValue v)
|
||||
static inline JSValue imvec22js(JSContext *js, ImVec2 vec)
|
||||
{
|
||||
JSValue v = JS_NewObject(js);
|
||||
JS_SetPropertyUint32(js, v, 0, JS_NewFloat64(js, vec.x));
|
||||
JS_SetPropertyUint32(js, v, 1, JS_NewFloat64(js, vec.y));
|
||||
JS_SetPropertyNumber(js, v, 0, JS_NewFloat64(js, vec.x));
|
||||
JS_SetPropertyNumber(js, v, 1, JS_NewFloat64(js, vec.y));
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -169,17 +169,17 @@ JSC_SCALL(imgui_combo,
|
||||
|
||||
if (JS_IsNumber(argv[1])) {
|
||||
current_item = js2number(js, argv[1]);
|
||||
} else if (JS_IsString(argv[1])) {
|
||||
} else if (JS_IsText(argv[1])) {
|
||||
preview_str = JS_ToCString(js, argv[1]);
|
||||
}
|
||||
|
||||
if (JS_IsArray(js, argv[2])) {
|
||||
if (JS_IsArray(argv[2])) {
|
||||
// Handle array of strings
|
||||
int item_count = JS_ArrayLength(js, argv[2]);
|
||||
const char **items = (const char**)malloc(sizeof(char*) * item_count);
|
||||
|
||||
for (int i = 0; i < item_count; i++) {
|
||||
JSValue item = JS_GetPropertyUint32(js, argv[2], i);
|
||||
JSValue item = JS_GetPropertyNumber(js, argv[2], i);
|
||||
items[i] = JS_ToCString(js, item);
|
||||
JS_FreeValue(js, item);
|
||||
}
|
||||
@@ -210,7 +210,7 @@ JSC_SCALL(imgui_combo,
|
||||
}
|
||||
free(items);
|
||||
|
||||
} else if (JS_IsString(argv[2])) {
|
||||
} else if (JS_IsText(argv[2])) {
|
||||
// Handle single string with \0 separators
|
||||
const char *items_str = JS_ToCString(js, argv[2]);
|
||||
|
||||
@@ -248,13 +248,13 @@ JSC_SCALL(imgui_slider,
|
||||
float low = JS_IsNull(argv[2]) ? 0.0 : js2number(js, argv[2]);
|
||||
float high = JS_IsNull(argv[3]) ? 1.0 : js2number(js, argv[3]);
|
||||
|
||||
if (JS_IsArray(js, argv[1])) {
|
||||
if (JS_IsArray(argv[1])) {
|
||||
int n = JS_ArrayLength(js, argv[1]);
|
||||
float a[4]; // Max 4 elements for SliderFloat4
|
||||
|
||||
// Read values from JS array
|
||||
for (int i = 0; i < n && i < 4; i++) {
|
||||
JSValue val = JS_GetPropertyUint32(js, argv[1], i);
|
||||
JSValue val = JS_GetPropertyNumber(js, argv[1], i);
|
||||
double d;
|
||||
JS_ToFloat64(js, &d, val);
|
||||
a[i] = (float)d;
|
||||
@@ -276,7 +276,7 @@ JSC_SCALL(imgui_slider,
|
||||
// Write values back to JS array
|
||||
ret = JS_NewArray(js);
|
||||
for (int i = 0; i < n && i < 4; i++) {
|
||||
JS_SetPropertyUint32(js, ret, i, JS_NewFloat64(js, a[i]));
|
||||
JS_SetPropertyNumber(js, ret, i, JS_NewFloat64(js, a[i]));
|
||||
}
|
||||
} else {
|
||||
float val = js2number(js, argv[1]);
|
||||
@@ -289,13 +289,13 @@ JSC_SCALL(imgui_intslider,
|
||||
int low = JS_IsNull(argv[2]) ? 0 : js2number(js, argv[2]);
|
||||
int high = JS_IsNull(argv[3]) ? 100 : js2number(js, argv[3]);
|
||||
|
||||
if (JS_IsArray(js, argv[1])) {
|
||||
if (JS_IsArray(argv[1])) {
|
||||
int n = JS_ArrayLength(js, argv[1]);
|
||||
int a[4]; // Max 4 elements for SliderInt4
|
||||
|
||||
// Read values from JS array
|
||||
for (int i = 0; i < n && i < 4; i++) {
|
||||
JSValue val = JS_GetPropertyUint32(js, argv[1], i);
|
||||
JSValue val = JS_GetPropertyNumber(js, argv[1], i);
|
||||
double d;
|
||||
JS_ToFloat64(js, &d, val);
|
||||
a[i] = (int)d;
|
||||
@@ -317,7 +317,7 @@ JSC_SCALL(imgui_intslider,
|
||||
// Write values back to JS array
|
||||
ret = JS_NewArray(js);
|
||||
for (int i = 0; i < n && i < 4; i++) {
|
||||
JS_SetPropertyUint32(js, ret, i, JS_NewInt32(js, a[i]));
|
||||
JS_SetPropertyNumber(js, ret, i, JS_NewInt32(js, a[i]));
|
||||
}
|
||||
} else {
|
||||
int val = js2number(js, argv[1]);
|
||||
|
||||
50
input.cm
50
input.cm
@@ -147,69 +147,73 @@ function create_user(index, config) {
|
||||
|
||||
// Pick user based on pairing policy
|
||||
function pick_user(canon) {
|
||||
var picked = null
|
||||
var old_down = null
|
||||
var i = 0
|
||||
if (length(_users) == 0) return null
|
||||
|
||||
|
||||
// For last_used: always user 0, just update active device
|
||||
if (_config.pairing == 'last_used') {
|
||||
var user = _users[0]
|
||||
|
||||
picked = _users[0]
|
||||
|
||||
// Only switch on button press, not axis/motion
|
||||
if (canon.kind == 'button' && canon.pressed) {
|
||||
if (user.active_device != canon.device_id) {
|
||||
if (picked.active_device != canon.device_id) {
|
||||
// Release all held actions when switching device
|
||||
var old_down = user.router.down
|
||||
old_down = picked.router.down
|
||||
arrfor(array(old_down), action => {
|
||||
if (old_down[action]) {
|
||||
user.dispatch(action, { pressed: false, released: true, time: canon.time })
|
||||
picked.dispatch(action, { pressed: false, released: true, time: canon.time })
|
||||
}
|
||||
})
|
||||
|
||||
user.active_device = canon.device_id
|
||||
if (find(user.paired_devices, canon.device_id) == null) {
|
||||
push(user.paired_devices, canon.device_id)
|
||||
|
||||
picked.active_device = canon.device_id
|
||||
if (find(picked.paired_devices, canon.device_id) == null) {
|
||||
push(picked.paired_devices, canon.device_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return user
|
||||
|
||||
return picked
|
||||
}
|
||||
|
||||
|
||||
// For explicit pairing: find user paired to this device
|
||||
for (var i = 0; i < length(_users); i++) {
|
||||
for (i = 0; i < length(_users); i++) {
|
||||
if (find(_users[i].paired_devices, canon.device_id) != null) {
|
||||
_users[i].active_device = canon.device_id
|
||||
return _users[i]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Unpaired device - could implement join logic here
|
||||
return null
|
||||
}
|
||||
|
||||
// Configure the input system
|
||||
function configure(opts) {
|
||||
opts = opts || {}
|
||||
|
||||
function configure(o) {
|
||||
var opts = o || {}
|
||||
var i = 0
|
||||
|
||||
_config.max_users = opts.max_users || 1
|
||||
_config.pairing = opts.pairing || 'last_used'
|
||||
_config.emacs = opts.emacs != false
|
||||
_config.gestures = opts.gestures != false
|
||||
|
||||
|
||||
if (opts.action_map) _config.action_map = opts.action_map
|
||||
if (opts.display_names) _config.display_names = opts.display_names
|
||||
if (opts.on_window) _window_callback = opts.on_window
|
||||
|
||||
|
||||
// Copy gesture config
|
||||
_config.swipe_min_dist = opts.swipe_min_dist
|
||||
_config.swipe_max_time = opts.swipe_max_time
|
||||
_config.pinch_threshold = opts.pinch_threshold
|
||||
|
||||
|
||||
// Create users
|
||||
_users = []
|
||||
for (var i = 0; i < _config.max_users; i++) {
|
||||
for (i = 0; i < _config.max_users; i++) {
|
||||
push(_users, create_user(i, _config))
|
||||
}
|
||||
|
||||
|
||||
_initialized = true
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,15 @@ function parse_mods(mod) {
|
||||
// Translate SDL event to canonical format
|
||||
// Returns canonical event or null if not translatable
|
||||
function translate(evt) {
|
||||
var control = null
|
||||
|
||||
// Keyboard
|
||||
if (evt.type == 'key_down' || evt.type == 'key_up') {
|
||||
if (evt.repeat) return null // Ignore key repeats
|
||||
|
||||
var control = keycode_to_control(evt.key)
|
||||
|
||||
control = keycode_to_control(evt.key)
|
||||
if (!control) return null
|
||||
|
||||
|
||||
return {
|
||||
kind: 'button',
|
||||
device_id: 'kbm',
|
||||
|
||||
@@ -17,30 +17,30 @@ var controller_map = {
|
||||
joyconpair: 'joyconpair'
|
||||
}
|
||||
|
||||
function make(defaults, display_names) {
|
||||
defaults = defaults || {}
|
||||
display_names = display_names || {}
|
||||
|
||||
function make(defs, names) {
|
||||
var d = defs || {}
|
||||
var n = names || {}
|
||||
|
||||
var bindings = {
|
||||
action_map: {},
|
||||
_defaults: {},
|
||||
display_names: display_names,
|
||||
display_names: n,
|
||||
is_rebinding: false,
|
||||
rebind_target: null
|
||||
}
|
||||
|
||||
|
||||
// Copy defaults
|
||||
arrfor(array(defaults), function(key){
|
||||
var val = defaults[key]
|
||||
arrfor(array(d), function(key){
|
||||
var val = d[key]
|
||||
bindings.action_map[key] = is_array(val) ? array(val) : [val]
|
||||
bindings._defaults[key] = array(bindings.action_map[key])
|
||||
})
|
||||
|
||||
|
||||
// Get actions that match a control
|
||||
bindings.get_actions = function(control) {
|
||||
return filter(array(this.action_map), action => find(this.action_map[action], control) != null)
|
||||
}
|
||||
|
||||
|
||||
// Get bindings for a specific device kind
|
||||
bindings.get_bindings_for_device = function(action, device_kind) {
|
||||
var all = this.action_map[action] || []
|
||||
@@ -57,69 +57,76 @@ function make(defaults, display_names) {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Get primary binding for display
|
||||
bindings.get_primary_binding = function(action, device_kind) {
|
||||
var device_bindings = this.get_bindings_for_device(action, device_kind)
|
||||
if (length(device_bindings)) return device_bindings[0]
|
||||
|
||||
|
||||
var all = this.action_map[action] || []
|
||||
return length(all) > 0 ? all[0] : null
|
||||
}
|
||||
|
||||
|
||||
// Get icon for action
|
||||
bindings.get_icon_for_action = function(action, device_kind, gamepad_type) {
|
||||
var binding = this.get_primary_binding(action, device_kind)
|
||||
var button = null
|
||||
var key_map = null
|
||||
var key = null
|
||||
var gp_prefix = null
|
||||
var is_ps = null
|
||||
var gamepad_map = null
|
||||
var icon = null
|
||||
if (!binding) return null
|
||||
|
||||
|
||||
if (device_kind == 'keyboard') {
|
||||
if (starts_with(binding, 'mouse_button_')) {
|
||||
var button = replace(binding, 'mouse_button_', '')
|
||||
button = replace(binding, 'mouse_button_', '')
|
||||
return 'ui/mouse/mouse_' + button + '.png'
|
||||
} else {
|
||||
var key_map = {
|
||||
key_map = {
|
||||
'escape': 'escape', 'return': 'return', 'space': 'space',
|
||||
'up': 'arrow_up', 'down': 'arrow_down', 'left': 'arrow_left', 'right': 'arrow_right'
|
||||
}
|
||||
var key = key_map[binding] || binding
|
||||
key = key_map[binding] || binding
|
||||
return 'ui/keyboard/keyboard_' + key + '.png'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (device_kind == 'gamepad' && gamepad_type) {
|
||||
var prefix = controller_map[gamepad_type] || 'playstation'
|
||||
var is_ps = find(['ps3', 'ps4', 'ps5'], gamepad_type) != null
|
||||
|
||||
var gamepad_map = {
|
||||
gp_prefix = controller_map[gamepad_type] || 'playstation'
|
||||
is_ps = find(['ps3', 'ps4', 'ps5'], gamepad_type) != null
|
||||
|
||||
gamepad_map = {
|
||||
'gamepad_a': is_ps ? 'playstation_button_cross' : 'xbox_button_a',
|
||||
'gamepad_b': is_ps ? 'playstation_button_circle' : 'xbox_button_b',
|
||||
'gamepad_x': is_ps ? 'playstation_button_square' : 'xbox_button_x',
|
||||
'gamepad_y': is_ps ? 'playstation_button_triangle' : 'xbox_button_y',
|
||||
'gamepad_dpup': prefix + '_dpad_up',
|
||||
'gamepad_dpdown': prefix + '_dpad_down',
|
||||
'gamepad_dpleft': prefix + '_dpad_left',
|
||||
'gamepad_dpright': prefix + '_dpad_right',
|
||||
'gamepad_l1': prefix + '_trigger_l1',
|
||||
'gamepad_r1': prefix + '_trigger_r1',
|
||||
'gamepad_l2': prefix + '_trigger_l2',
|
||||
'gamepad_r2': prefix + '_trigger_r2',
|
||||
'gamepad_dpup': gp_prefix + '_dpad_up',
|
||||
'gamepad_dpdown': gp_prefix + '_dpad_down',
|
||||
'gamepad_dpleft': gp_prefix + '_dpad_left',
|
||||
'gamepad_dpright': gp_prefix + '_dpad_right',
|
||||
'gamepad_l1': gp_prefix + '_trigger_l1',
|
||||
'gamepad_r1': gp_prefix + '_trigger_r1',
|
||||
'gamepad_l2': gp_prefix + '_trigger_l2',
|
||||
'gamepad_r2': gp_prefix + '_trigger_r2',
|
||||
'gamepad_start': this._get_start_icon(gamepad_type)
|
||||
}
|
||||
|
||||
var icon = gamepad_map[binding]
|
||||
if (icon) return 'ui/' + prefix + '/' + icon + '.png'
|
||||
|
||||
icon = gamepad_map[binding]
|
||||
if (icon) return 'ui/' + gp_prefix + '/' + icon + '.png'
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
bindings._get_start_icon = function(gamepad_type) {
|
||||
if (gamepad_type == 'ps3') return 'playstation3_button_start'
|
||||
if (gamepad_type == 'ps4') return 'playstation4_button_options'
|
||||
if (gamepad_type == 'ps5') return 'playstation5_button_options'
|
||||
return 'xbox_button_start'
|
||||
}
|
||||
|
||||
|
||||
// Start rebinding
|
||||
bindings.start_rebind = function(action) {
|
||||
if (!this.action_map[action]) return false
|
||||
@@ -127,73 +134,75 @@ function make(defaults, display_names) {
|
||||
this.rebind_target = action
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// Complete rebinding with new control
|
||||
bindings.rebind = function(action, new_control, device_kind) {
|
||||
var target = null
|
||||
var i = 0
|
||||
var existing_kind = null
|
||||
if (!this.action_map[action]) return false
|
||||
|
||||
|
||||
// Remove from other actions
|
||||
arrfor(array(this.action_map), act => {
|
||||
var idx = search(this.action_map[act], new_control)
|
||||
if (idx >= 0)
|
||||
this.action_map[act] = array(array(this.action_map[act], 0, idx), array(this.action_map[act], idx+1))
|
||||
})
|
||||
|
||||
|
||||
// Clear existing bindings for this device kind
|
||||
var target = this.action_map[action]
|
||||
for (var i = length(target) - 1; i >= 0; i--) {
|
||||
var existing_kind = starts_with(target[i], 'gamepad_') ? 'gamepad' :
|
||||
starts_with(target[i], 'swipe_') ? 'touch' : 'keyboard'
|
||||
target = this.action_map[action]
|
||||
for (i = length(target) - 1; i >= 0; i--) {
|
||||
existing_kind = starts_with(target[i], 'gamepad_') ? 'gamepad' :
|
||||
starts_with(target[i], 'swipe_') ? 'touch' : 'keyboard'
|
||||
if (existing_kind == device_kind)
|
||||
this.action_map[action] = array(array(this.action_map[action], 0, i), array(this.action_map[action], i+1))
|
||||
}
|
||||
|
||||
|
||||
// Add new binding
|
||||
this.action_map[action].unshift(new_control)
|
||||
this.is_rebinding = false
|
||||
this.rebind_target = null
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// Cancel rebinding
|
||||
bindings.cancel_rebind = function() {
|
||||
this.is_rebinding = false
|
||||
this.rebind_target = null
|
||||
}
|
||||
|
||||
|
||||
// Save bindings
|
||||
bindings.save = function(path) {
|
||||
path = path || 'keybindings.json'
|
||||
try {
|
||||
io.slurpwrite(path, json.encode(this.action_map))
|
||||
return true
|
||||
} catch(e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Load bindings
|
||||
bindings.load = function(path) {
|
||||
path = path || 'keybindings.json'
|
||||
try {
|
||||
if (io.exists(path)) {
|
||||
var data = json.decode(io.slurp(path))
|
||||
arrfor(array(data), key => {
|
||||
if (this.action_map[key]) this.action_map[key] = data[key]
|
||||
})
|
||||
return true
|
||||
}
|
||||
} catch(e) {}
|
||||
bindings.save = function(p) {
|
||||
var save_path = p || 'keybindings.json'
|
||||
io.slurpwrite(save_path, json.encode(this.action_map))
|
||||
return true
|
||||
} disruption {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Load bindings
|
||||
bindings.load = function(p) {
|
||||
var load_path = p || 'keybindings.json'
|
||||
var data = null
|
||||
if (io.exists(load_path)) {
|
||||
data = json.decode(io.slurp(load_path))
|
||||
arrfor(array(data), key => {
|
||||
if (this.action_map[key]) this.action_map[key] = data[key]
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} disruption {
|
||||
return false
|
||||
}
|
||||
|
||||
// Reset to defaults
|
||||
bindings.reset = function() {
|
||||
arrfor(array(this._defaults), key => {
|
||||
this.action_map[key] = array(this._defaults[key])
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ var _devices = {}
|
||||
// Register a device from a canonical event
|
||||
function register(canon) {
|
||||
if (!canon || !canon.device_id) return
|
||||
|
||||
var kind = null
|
||||
|
||||
if (!_devices[canon.device_id]) {
|
||||
var kind = 'keyboard'
|
||||
kind = 'keyboard'
|
||||
if (starts_with(canon.device_id, 'gp:')) kind = 'gamepad'
|
||||
else if (starts_with(canon.device_id, 'touch:')) kind = 'touch'
|
||||
|
||||
|
||||
_devices[canon.device_id] = {
|
||||
id: canon.device_id,
|
||||
kind: kind,
|
||||
|
||||
196
input/router.cm
196
input/router.cm
@@ -15,62 +15,76 @@ var emacs_special = {
|
||||
}
|
||||
|
||||
// Gesture stage - detects swipes and pinches from touchpad
|
||||
function gesture_stage(config) {
|
||||
config = config || {}
|
||||
var min_swipe = config.swipe_min_dist || 30
|
||||
var max_time = config.swipe_max_time || 500
|
||||
var pinch_th = config.pinch_threshold || 10
|
||||
|
||||
function gesture_stage(cfg) {
|
||||
var o = cfg || {}
|
||||
var min_swipe = o.swipe_min_dist || 30
|
||||
var max_time = o.swipe_max_time || 500
|
||||
var pinch_th = o.pinch_threshold || 10
|
||||
|
||||
var touches = {}
|
||||
var gesture_state = null
|
||||
var start_dist = 0
|
||||
|
||||
|
||||
function dist(p1, p2) {
|
||||
var dx = p2[0] - p1[0]
|
||||
var dy = p2[1] - p1[1]
|
||||
return Math.sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
process: function(events) {
|
||||
var output = []
|
||||
|
||||
for (var i = 0; i < length(events); i++) {
|
||||
var ev = events[i]
|
||||
|
||||
var i = 0
|
||||
var ev = null
|
||||
var fingerId = null
|
||||
var count = 0
|
||||
var fingers = null
|
||||
var currentDist = 0
|
||||
var d = 0
|
||||
var touch = null
|
||||
var dt = 0
|
||||
var dx = 0
|
||||
var dy = 0
|
||||
var absX = 0
|
||||
var absY = 0
|
||||
var dir = null
|
||||
|
||||
for (i = 0; i < length(events); i++) {
|
||||
ev = events[i]
|
||||
|
||||
// Only process gamepad touchpad events
|
||||
if (ev.control != 'gamepad_touchpad') {
|
||||
push(output, ev)
|
||||
continue
|
||||
}
|
||||
|
||||
var fingerId = (ev.touchpad || 0) + '_' + (ev.finger || 0)
|
||||
|
||||
|
||||
fingerId = (ev.touchpad || 0) + '_' + (ev.finger || 0)
|
||||
|
||||
if (ev.pressed) {
|
||||
touches[fingerId] = {
|
||||
pos: ev.pos,
|
||||
startPos: ev.pos,
|
||||
startTime: time.number()
|
||||
}
|
||||
|
||||
var count = length(array(touches))
|
||||
|
||||
count = length(array(touches))
|
||||
if (count == 1) gesture_state = 'single'
|
||||
else if (count == 2) {
|
||||
gesture_state = 'multi'
|
||||
var fingers = Object.values(touches)
|
||||
fingers = Object.values(touches)
|
||||
start_dist = dist(fingers[0].pos, fingers[1].pos)
|
||||
}
|
||||
}
|
||||
else if (ev.kind == 'axis') {
|
||||
if (touches[fingerId]) {
|
||||
touches[fingerId].pos = ev.pos
|
||||
|
||||
var count = length(array(touches))
|
||||
|
||||
count = length(array(touches))
|
||||
if (count == 2 && gesture_state == 'multi') {
|
||||
var fingers = Object.values(touches)
|
||||
var currentDist = dist(fingers[0].pos, fingers[1].pos)
|
||||
var d = currentDist - start_dist
|
||||
|
||||
fingers = Object.values(touches)
|
||||
currentDist = dist(fingers[0].pos, fingers[1].pos)
|
||||
d = currentDist - start_dist
|
||||
|
||||
if (Math.abs(d) >= pinch_th / 100) {
|
||||
push(output, {
|
||||
kind: 'gesture',
|
||||
@@ -88,21 +102,22 @@ function gesture_stage(config) {
|
||||
}
|
||||
else if (ev.released) {
|
||||
if (touches[fingerId]) {
|
||||
var touch = touches[fingerId]
|
||||
var count = length(array(touches))
|
||||
|
||||
touch = touches[fingerId]
|
||||
count = length(array(touches))
|
||||
|
||||
if (count == 1 && gesture_state == 'single') {
|
||||
var dt = (time.number() - touch.startTime) * 1000
|
||||
var dx = ev.pos[0] - touch.startPos[0]
|
||||
var dy = ev.pos[1] - touch.startPos[1]
|
||||
|
||||
dt = (time.number() - touch.startTime) * 1000
|
||||
dx = ev.pos[0] - touch.startPos[0]
|
||||
dy = ev.pos[1] - touch.startPos[1]
|
||||
|
||||
if (dt < max_time) {
|
||||
var absX = Math.abs(dx), absY = Math.abs(dy)
|
||||
absX = Math.abs(dx)
|
||||
absY = Math.abs(dy)
|
||||
if (absX > min_swipe / 100 || absY > min_swipe / 100) {
|
||||
var dir = absX > absY
|
||||
dir = absX > absY
|
||||
? (dx > 0 ? 'swipe_right' : 'swipe_left')
|
||||
: (dy > 0 ? 'swipe_down' : 'swipe_up')
|
||||
|
||||
|
||||
push(output, {
|
||||
kind: 'gesture',
|
||||
device_id: ev.device_id,
|
||||
@@ -114,13 +129,13 @@ function gesture_stage(config) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
delete touches[fingerId]
|
||||
if (length(array(touches)) == 0) gesture_state = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
@@ -129,50 +144,54 @@ function gesture_stage(config) {
|
||||
// Emacs stage - converts keyboard input to emacs chords
|
||||
function emacs_stage() {
|
||||
var prefix = null
|
||||
|
||||
|
||||
return {
|
||||
process: function(events) {
|
||||
var output = []
|
||||
|
||||
for (var i = 0; i < length(events); i++) {
|
||||
var ev = events[i]
|
||||
|
||||
var i = 0
|
||||
var ev = null
|
||||
var notation = null
|
||||
var chord = null
|
||||
|
||||
for (i = 0; i < length(events); i++) {
|
||||
ev = events[i]
|
||||
|
||||
// Only process keyboard button events
|
||||
if (ev.device_id != 'kbm' || ev.kind != 'button' || !ev.pressed) {
|
||||
push(output, ev)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
/* if (find(valid_emacs_keys, ev.control) == null) {
|
||||
push(output, ev)
|
||||
continue
|
||||
}
|
||||
*/
|
||||
*/
|
||||
// Only process if we have modifiers OR waiting for chord
|
||||
if (!ev.mods?.ctrl && !ev.mods?.alt && !prefix) {
|
||||
push(output, ev)
|
||||
continue
|
||||
}
|
||||
|
||||
var notation = ""
|
||||
|
||||
notation = ""
|
||||
if (ev.mods?.ctrl) notation += "C-"
|
||||
if (ev.mods?.alt) notation += "M-"
|
||||
|
||||
|
||||
if (length(ev.control) == 1) {
|
||||
notation += lower(ev.control)
|
||||
} else {
|
||||
notation += emacs_special[ev.control] || ev.control
|
||||
}
|
||||
|
||||
|
||||
// Handle prefix keys
|
||||
if (notation == "C-x" || notation == "C-c") {
|
||||
prefix = notation
|
||||
continue // Consume, don't output
|
||||
}
|
||||
|
||||
|
||||
// Complete chord if we have prefix
|
||||
if (prefix) {
|
||||
var chord = prefix + " " + notation
|
||||
chord = prefix + " " + notation
|
||||
prefix = null
|
||||
push(output, {
|
||||
kind: 'chord',
|
||||
@@ -193,7 +212,7 @@ function emacs_stage() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
@@ -202,34 +221,39 @@ function emacs_stage() {
|
||||
// Action mapping stage - converts controls to named actions
|
||||
function action_stage(bindings) {
|
||||
var down = {}
|
||||
|
||||
|
||||
return {
|
||||
down: down,
|
||||
process: function(events) {
|
||||
var output = []
|
||||
|
||||
for (var i = 0; i < length(events); i++) {
|
||||
var ev = events[i]
|
||||
|
||||
var i = 0
|
||||
var ev = null
|
||||
var actions = null
|
||||
var j = 0
|
||||
var action = null
|
||||
|
||||
for (i = 0; i < length(events); i++) {
|
||||
ev = events[i]
|
||||
|
||||
// Pass through non-button events
|
||||
if (ev.kind != 'button' && ev.kind != 'chord' && ev.kind != 'gesture') {
|
||||
push(output, ev)
|
||||
continue
|
||||
}
|
||||
|
||||
var actions = bindings.get_actions(ev.control)
|
||||
|
||||
|
||||
actions = bindings.get_actions(ev.control)
|
||||
|
||||
if (length(actions) == 0) {
|
||||
push(output, ev)
|
||||
continue
|
||||
}
|
||||
|
||||
for (var j = 0; j < length(actions); j++) {
|
||||
var action = actions[j]
|
||||
|
||||
|
||||
for (j = 0; j < length(actions); j++) {
|
||||
action = actions[j]
|
||||
|
||||
if (ev.pressed) down[action] = true
|
||||
else if (ev.released) down[action] = false
|
||||
|
||||
|
||||
push(output, {
|
||||
kind: 'action',
|
||||
device_id: ev.device_id,
|
||||
@@ -241,7 +265,7 @@ function action_stage(bindings) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
@@ -251,55 +275,59 @@ function action_stage(bindings) {
|
||||
function delivery_stage(user) {
|
||||
return {
|
||||
process: function(events) {
|
||||
for (var i = 0; i < length(events); i++) {
|
||||
var ev = events[i]
|
||||
|
||||
var i = 0
|
||||
var ev = null
|
||||
|
||||
for (i = 0; i < length(events); i++) {
|
||||
ev = events[i]
|
||||
|
||||
// Only deliver actions
|
||||
if (ev.kind != 'action') continue
|
||||
|
||||
|
||||
user.dispatch(ev.control, {
|
||||
pressed: ev.pressed,
|
||||
released: ev.released,
|
||||
time: ev.time
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return events
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create router with pipeline
|
||||
function make(user, config) {
|
||||
config = config || {}
|
||||
|
||||
function make(user, cfg) {
|
||||
var o = cfg || {}
|
||||
|
||||
var stages = []
|
||||
var action = null
|
||||
|
||||
if (config.gestures != false) {
|
||||
push(stages, gesture_stage(config))
|
||||
|
||||
if (o.gestures != false) {
|
||||
push(stages, gesture_stage(o))
|
||||
}
|
||||
|
||||
if (config.emacs != false) {
|
||||
|
||||
if (o.emacs != false) {
|
||||
push(stages, emacs_stage())
|
||||
}
|
||||
|
||||
|
||||
action = action_stage(user.bindings)
|
||||
push(stages, action)
|
||||
push(stages, delivery_stage(user))
|
||||
|
||||
|
||||
return {
|
||||
stages: stages,
|
||||
|
||||
|
||||
down() { return action.down },
|
||||
|
||||
|
||||
handle: function(canon) {
|
||||
var events = [canon]
|
||||
|
||||
for (var i = 0; i < length(this.stages); i++) {
|
||||
var i = 0
|
||||
|
||||
for (i = 0; i < length(this.stages); i++) {
|
||||
events = this.stages[i].process(events)
|
||||
}
|
||||
|
||||
|
||||
return events
|
||||
}
|
||||
}
|
||||
|
||||
10
layout.c
10
layout.c
@@ -39,9 +39,9 @@ static JSValue js_layout_set_size(JSContext *js, JSValueConst self, int argc, JS
|
||||
double width = 0, height = 0;
|
||||
|
||||
// Check if it's an array (for backwards compatibility)
|
||||
if (JS_IsArray(js, argv[1])) {
|
||||
JSValue width_val = JS_GetPropertyUint32(js, argv[1], 0);
|
||||
JSValue height_val = JS_GetPropertyUint32(js, argv[1], 1);
|
||||
if (JS_IsArray(argv[1])) {
|
||||
JSValue width_val = JS_GetPropertyNumber(js, argv[1], 0);
|
||||
JSValue height_val = JS_GetPropertyNumber(js, argv[1], 1);
|
||||
JS_ToFloat64(js, &width, width_val);
|
||||
JS_ToFloat64(js, &height, height_val);
|
||||
JS_FreeValue(js, width_val);
|
||||
@@ -162,7 +162,7 @@ static JSValue js_layout_reset(JSContext *js, JSValueConst self, int argc, JSVal
|
||||
static void js_layout_finalizer(JSRuntime *rt, JSValue val) {
|
||||
lay_context *ctx = JS_GetOpaque(val, js_layout_class_id);
|
||||
lay_destroy_context(ctx);
|
||||
js_free_rt(rt, ctx);
|
||||
js_free_rt(ctx);
|
||||
}
|
||||
|
||||
static JSClassDef js_layout_class = {
|
||||
@@ -190,7 +190,7 @@ static const JSCFunctionListEntry js_layout_funcs[] = {
|
||||
|
||||
CELL_USE_INIT(
|
||||
JS_NewClassID(&js_layout_class_id);
|
||||
JS_NewClass(JS_GetRuntime(js), js_layout_class_id, &js_layout_class);
|
||||
JS_NewClass(js, js_layout_class_id, &js_layout_class);
|
||||
|
||||
JSValue proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, proto, js_layout_proto_funcs, sizeof(js_layout_proto_funcs) / sizeof(JSCFunctionListEntry));
|
||||
|
||||
225
line2d.cm
225
line2d.cm
@@ -78,117 +78,147 @@ function build_polyline_mesh(line) {
|
||||
|
||||
// Transform points if in local space
|
||||
var pts = []
|
||||
for (var i = 0; i < length(points); i++) {
|
||||
var p = points[i]
|
||||
var i = 0
|
||||
var p = null
|
||||
var dx = 0
|
||||
var dy = 0
|
||||
|
||||
for (i = 0; i < length(points); i++) {
|
||||
p = points[i]
|
||||
if (points_space == 'local') {
|
||||
push(pts, {x: p.x + pos.x, y: p.y + pos.y})
|
||||
} else {
|
||||
push(pts, {x: p.x, y: p.y})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculate cumulative distances
|
||||
var cumulative = [0]
|
||||
for (var i = 1; i < length(pts); i++) {
|
||||
var dx = pts[i].x - pts[i-1].x
|
||||
var dy = pts[i].y - pts[i-1].y
|
||||
for (i = 1; i < length(pts); i++) {
|
||||
dx = pts[i].x - pts[i-1].x
|
||||
dy = pts[i].y - pts[i-1].y
|
||||
push(cumulative, cumulative[i-1] + math.sqrt(dx*dx + dy*dy))
|
||||
}
|
||||
var total_length = cumulative[length(cumulative) - 1]
|
||||
|
||||
|
||||
// Build triangle strip mesh
|
||||
var verts = []
|
||||
var indices = []
|
||||
|
||||
|
||||
// Get width at point i
|
||||
function get_width(i) {
|
||||
if (widths && length(widths) > i) return widths[i]
|
||||
function get_width(idx) {
|
||||
if (widths && length(widths) > idx) return widths[idx]
|
||||
return width
|
||||
}
|
||||
|
||||
|
||||
// Get U coordinate at point i
|
||||
function get_u(i) {
|
||||
var seg_idx = 0
|
||||
var seg_len = 0
|
||||
function get_u(idx) {
|
||||
if (uv_mode == 'stretch') {
|
||||
return total_length > 0 ? cumulative[i] / total_length : 0
|
||||
return total_length > 0 ? cumulative[idx] / total_length : 0
|
||||
} else if (uv_mode == 'per_segment') {
|
||||
if (i == 0) return 0
|
||||
var seg_idx = i - 1
|
||||
var seg_len = cumulative[i] - cumulative[i-1]
|
||||
if (idx == 0) return 0
|
||||
seg_idx = idx - 1
|
||||
seg_len = cumulative[idx] - cumulative[idx-1]
|
||||
return 1 // Each segment ends at u=1
|
||||
} else {
|
||||
// repeat (default)
|
||||
return cumulative[i] * u_per_unit + u_offset
|
||||
return cumulative[idx] * u_per_unit + u_offset
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculate normals at each point
|
||||
var normals = []
|
||||
for (var i = 0; i < length(pts); i++) {
|
||||
var prev = i > 0 ? pts[i-1] : (closed ? pts[length(pts)-1] : null)
|
||||
var curr = pts[i]
|
||||
var next = i < length(pts)-1 ? pts[i+1] : (closed ? pts[0] : null)
|
||||
|
||||
var n = {x: 0, y: 0}
|
||||
|
||||
var prev = null
|
||||
var curr = null
|
||||
var next = null
|
||||
var n = null
|
||||
var d1x = 0
|
||||
var d1y = 0
|
||||
var d2x = 0
|
||||
var d2y = 0
|
||||
var len1 = 0
|
||||
var len2 = 0
|
||||
var n1x = 0
|
||||
var n1y = 0
|
||||
var n2x = 0
|
||||
var n2y = 0
|
||||
var nlen = 0
|
||||
var dot = 0
|
||||
var miter_scale = 0
|
||||
var len = 0
|
||||
|
||||
for (i = 0; i < length(pts); i++) {
|
||||
prev = i > 0 ? pts[i-1] : (closed ? pts[length(pts)-1] : null)
|
||||
curr = pts[i]
|
||||
next = i < length(pts)-1 ? pts[i+1] : (closed ? pts[0] : null)
|
||||
|
||||
n = {x: 0, y: 0}
|
||||
|
||||
if (prev && next) {
|
||||
// Middle point - average normals
|
||||
var d1x = curr.x - prev.x
|
||||
var d1y = curr.y - prev.y
|
||||
var d2x = next.x - curr.x
|
||||
var d2y = next.y - curr.y
|
||||
|
||||
var len1 = math.sqrt(d1x*d1x + d1y*d1y)
|
||||
var len2 = math.sqrt(d2x*d2x + d2y*d2y)
|
||||
|
||||
d1x = curr.x - prev.x
|
||||
d1y = curr.y - prev.y
|
||||
d2x = next.x - curr.x
|
||||
d2y = next.y - curr.y
|
||||
|
||||
len1 = math.sqrt(d1x*d1x + d1y*d1y)
|
||||
len2 = math.sqrt(d2x*d2x + d2y*d2y)
|
||||
|
||||
if (len1 > 0.0001) { d1x /= len1; d1y /= len1 }
|
||||
if (len2 > 0.0001) { d2x /= len2; d2y /= len2 }
|
||||
|
||||
|
||||
// Normals (perpendicular)
|
||||
var n1x = -d1y, n1y = d1x
|
||||
var n2x = -d2y, n2y = d2x
|
||||
|
||||
n1x = -d1y
|
||||
n1y = d1x
|
||||
n2x = -d2y
|
||||
n2y = d2x
|
||||
|
||||
// Average
|
||||
n.x = n1x + n2x
|
||||
n.y = n1y + n2y
|
||||
var nlen = math.sqrt(n.x*n.x + n.y*n.y)
|
||||
nlen = math.sqrt(n.x*n.x + n.y*n.y)
|
||||
if (nlen > 0.0001) { n.x /= nlen; n.y /= nlen }
|
||||
|
||||
|
||||
// Miter correction
|
||||
var dot = n1x * n.x + n1y * n.y
|
||||
dot = n1x * n.x + n1y * n.y
|
||||
if (dot > 0.0001) {
|
||||
var miter_scale = 1 / dot
|
||||
miter_scale = 1 / dot
|
||||
if (miter_scale > miter_limit) miter_scale = miter_limit
|
||||
n.x *= miter_scale
|
||||
n.y *= miter_scale
|
||||
}
|
||||
} else if (next) {
|
||||
// Start point
|
||||
var dx = next.x - curr.x
|
||||
var dy = next.y - curr.y
|
||||
var len = math.sqrt(dx*dx + dy*dy)
|
||||
dx = next.x - curr.x
|
||||
dy = next.y - curr.y
|
||||
len = math.sqrt(dx*dx + dy*dy)
|
||||
if (len > 0.0001) { dx /= len; dy /= len }
|
||||
n.x = -dy
|
||||
n.y = dx
|
||||
} else if (prev) {
|
||||
// End point
|
||||
var dx = curr.x - prev.x
|
||||
var dy = curr.y - prev.y
|
||||
var len = math.sqrt(dx*dx + dy*dy)
|
||||
dx = curr.x - prev.x
|
||||
dy = curr.y - prev.y
|
||||
len = math.sqrt(dx*dx + dy*dy)
|
||||
if (len > 0.0001) { dx /= len; dy /= len }
|
||||
n.x = -dy
|
||||
n.y = dx
|
||||
}
|
||||
|
||||
|
||||
push(normals, n)
|
||||
}
|
||||
|
||||
|
||||
// Generate vertices (2 per point - left and right of line)
|
||||
for (var i = 0; i < length(pts); i++) {
|
||||
var p = pts[i]
|
||||
var n = normals[i]
|
||||
var w = get_width(i) * 0.5
|
||||
var u = get_u(i)
|
||||
|
||||
var w = 0
|
||||
var u = 0
|
||||
for (i = 0; i < length(pts); i++) {
|
||||
p = pts[i]
|
||||
n = normals[i]
|
||||
w = get_width(i) * 0.5
|
||||
u = get_u(i)
|
||||
|
||||
// Left vertex (v=0)
|
||||
push(verts, {
|
||||
x: p.x + n.x * w,
|
||||
@@ -197,7 +227,7 @@ function build_polyline_mesh(line) {
|
||||
v: v_offset,
|
||||
r: 1, g: 1, b: 1, a: 1
|
||||
})
|
||||
|
||||
|
||||
// Right vertex (v=1)
|
||||
push(verts, {
|
||||
x: p.x - n.x * w,
|
||||
@@ -207,10 +237,11 @@ function build_polyline_mesh(line) {
|
||||
r: 1, g: 1, b: 1, a: 1
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Generate indices (triangle strip as triangles)
|
||||
for (var i = 0; i < length(pts) - 1; i++) {
|
||||
var base = i * 2
|
||||
var base = 0
|
||||
for (i = 0; i < length(pts) - 1; i++) {
|
||||
base = i * 2
|
||||
// First triangle
|
||||
push(indices, base + 0)
|
||||
push(indices, base + 1)
|
||||
@@ -220,10 +251,11 @@ function build_polyline_mesh(line) {
|
||||
push(indices, base + 3)
|
||||
push(indices, base + 2)
|
||||
}
|
||||
|
||||
|
||||
// Handle closed path
|
||||
var last = 0
|
||||
if (closed && length(pts) > 2) {
|
||||
var last = (length(pts) - 1) * 2
|
||||
last = (length(pts) - 1) * 2
|
||||
push(indices, last + 0)
|
||||
push(indices, last + 1)
|
||||
push(indices, 0)
|
||||
@@ -234,11 +266,11 @@ function build_polyline_mesh(line) {
|
||||
|
||||
// Add round caps if requested
|
||||
if (!closed && cap == 'round') {
|
||||
add_round_cap(verts, indices, pts[0], normals[0], get_width(0), get_u(0), v_offset, v_scale, true)
|
||||
add_round_cap(verts, indices, pts[length(pts)-1], normals[length(pts)-1], get_width(length(pts)-1), get_u(length(pts)-1), v_offset, v_scale, false)
|
||||
add_round_cap({verts: verts, indices: indices, p: pts[0], n: normals[0], width: get_width(0), u: get_u(0), v_offset: v_offset, v_scale: v_scale, is_start: true})
|
||||
add_round_cap({verts: verts, indices: indices, p: pts[length(pts)-1], n: normals[length(pts)-1], width: get_width(length(pts)-1), u: get_u(length(pts)-1), v_offset: v_offset, v_scale: v_scale, is_start: false})
|
||||
} else if (!closed && cap == 'square') {
|
||||
add_square_cap(verts, indices, pts[0], normals[0], get_width(0), get_u(0), v_offset, v_scale, true, pts[1])
|
||||
add_square_cap(verts, indices, pts[length(pts)-1], normals[length(pts)-1], get_width(length(pts)-1), get_u(length(pts)-1), v_offset, v_scale, false, pts[length(pts)-2])
|
||||
add_square_cap({verts: verts, indices: indices, p: pts[0], n: normals[0], width: get_width(0), u: get_u(0), v_offset: v_offset, v_scale: v_scale, is_start: true, adjacent: pts[1]})
|
||||
add_square_cap({verts: verts, indices: indices, p: pts[length(pts)-1], n: normals[length(pts)-1], width: get_width(length(pts)-1), u: get_u(length(pts)-1), v_offset: v_offset, v_scale: v_scale, is_start: false, adjacent: pts[length(pts)-2]})
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -249,15 +281,25 @@ function build_polyline_mesh(line) {
|
||||
}
|
||||
}
|
||||
|
||||
function add_round_cap(verts, indices, p, n, width, u, v_offset, v_scale, is_start) {
|
||||
var w = width * 0.5
|
||||
function add_round_cap(opts) {
|
||||
var verts = opts.verts
|
||||
var indices = opts.indices
|
||||
var p = opts.p
|
||||
var n = opts.n
|
||||
var rc_width = opts.width
|
||||
var u = opts.u
|
||||
var v_offset = opts.v_offset
|
||||
var v_scale = opts.v_scale
|
||||
var is_start = opts.is_start
|
||||
|
||||
var w = rc_width * 0.5
|
||||
var segments = 8
|
||||
var base_idx = length(verts)
|
||||
|
||||
|
||||
// Direction along the line
|
||||
var dx = is_start ? -n.y : n.y
|
||||
var dy = is_start ? n.x : -n.x
|
||||
|
||||
|
||||
// Center vertex
|
||||
push(verts, {
|
||||
x: p.x,
|
||||
@@ -266,14 +308,18 @@ function add_round_cap(verts, indices, p, n, width, u, v_offset, v_scale, is_sta
|
||||
v: 0.5 * v_scale + v_offset,
|
||||
r: 1, g: 1, b: 1, a: 1
|
||||
})
|
||||
|
||||
|
||||
// Arc vertices
|
||||
var start_angle = is_start ? math.arc_tangent(n.y, n.x) : math.arc_tangent(-n.y, -n.x)
|
||||
for (var i = 0; i <= segments; i++) {
|
||||
var angle = start_angle + (i / segments) * 3.14159
|
||||
var cx = math.cosine(angle)
|
||||
var cy = math.sine(angle)
|
||||
|
||||
var i = 0
|
||||
var angle = 0
|
||||
var cx = 0
|
||||
var cy = 0
|
||||
for (i = 0; i <= segments; i++) {
|
||||
angle = start_angle + (i / segments) * 3.14159
|
||||
cx = math.cosine(angle)
|
||||
cy = math.sine(angle)
|
||||
|
||||
push(verts, {
|
||||
x: p.x + cx * w,
|
||||
y: p.y + cy * w,
|
||||
@@ -282,30 +328,41 @@ function add_round_cap(verts, indices, p, n, width, u, v_offset, v_scale, is_sta
|
||||
r: 1, g: 1, b: 1, a: 1
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Fan triangles
|
||||
for (var i = 0; i < segments; i++) {
|
||||
for (i = 0; i < segments; i++) {
|
||||
push(indices, base_idx)
|
||||
push(indices, base_idx + 1 + i)
|
||||
push(indices, base_idx + 2 + i)
|
||||
}
|
||||
}
|
||||
|
||||
function add_square_cap(verts, indices, p, n, width, u, v_offset, v_scale, is_start, adjacent) {
|
||||
var w = width * 0.5
|
||||
function add_square_cap(opts) {
|
||||
var verts = opts.verts
|
||||
var indices = opts.indices
|
||||
var p = opts.p
|
||||
var n = opts.n
|
||||
var sc_width = opts.width
|
||||
var u = opts.u
|
||||
var v_offset = opts.v_offset
|
||||
var v_scale = opts.v_scale
|
||||
var is_start = opts.is_start
|
||||
var adjacent = opts.adjacent
|
||||
|
||||
var w = sc_width * 0.5
|
||||
var base_idx = length(verts)
|
||||
|
||||
|
||||
// Direction along the line (away from adjacent point)
|
||||
var dx = p.x - adjacent.x
|
||||
var dy = p.y - adjacent.y
|
||||
var len = math.sqrt(dx*dx + dy*dy)
|
||||
if (len > 0.0001) { dx /= len; dy /= len }
|
||||
|
||||
|
||||
// Extend by half width
|
||||
var ext = w
|
||||
var ex = p.x + dx * ext
|
||||
var ey = p.y + dy * ext
|
||||
|
||||
|
||||
// Four corners of the cap
|
||||
push(verts, {x: p.x + n.x * w, y: p.y + n.y * w, u: u, v: v_offset, r: 1, g: 1, b: 1, a: 1})
|
||||
push(verts, {x: p.x - n.x * w, y: p.y - n.y * w, u: u, v: v_scale + v_offset, r: 1, g: 1, b: 1, a: 1})
|
||||
@@ -388,9 +445,9 @@ var line2d = {
|
||||
return make_line(props)
|
||||
},
|
||||
|
||||
line: function(x1, y1, x2, y2, props) {
|
||||
line: function(from, to, props) {
|
||||
var p = props || {}
|
||||
p.points = [{x: x1, y: y1}, {x: x2, y: y2}]
|
||||
p.points = [{x: from.x, y: from.y}, {x: to.x, y: to.y}]
|
||||
return make_line(p)
|
||||
}
|
||||
}
|
||||
|
||||
8
math.c
8
math.c
@@ -10,7 +10,7 @@
|
||||
// Utility function to get number from array index
|
||||
static double js_getnum_uint32(JSContext *js, JSValue v, unsigned int i)
|
||||
{
|
||||
JSValue val = JS_GetPropertyUint32(js,v,i);
|
||||
JSValue val = JS_GetPropertyNumber(js,v,i);
|
||||
double ret = js2number(js, val);
|
||||
JS_FreeValue(js,val);
|
||||
return ret;
|
||||
@@ -30,7 +30,7 @@ static float *js2floats(JSContext *js, JSValue v, size_t *len)
|
||||
static JSValue floats2array(JSContext *js, float *vals, size_t len) {
|
||||
JSValue arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
JS_SetPropertyUint32(js, arr, i, number2js(js, vals[i]));
|
||||
JS_SetPropertyNumber(js, arr, i, number2js(js, vals[i]));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ JSC_CCALL(math_norm,
|
||||
JSValue newarr = JS_NewArray(js);
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
JS_SetPropertyUint32(js, newarr, i, number2js(js,js_getnum_uint32(js, argv[0],i)/length));
|
||||
JS_SetPropertyNumber(js, newarr, i, number2js(js,js_getnum_uint32(js, argv[0],i)/length));
|
||||
|
||||
ret = newarr;
|
||||
)
|
||||
@@ -238,7 +238,7 @@ JSC_CCALL(math_from_to,
|
||||
int i = 0;
|
||||
for (double val = start; val <= end; val += step) {
|
||||
if (val == end && !inclusive) break;
|
||||
JS_SetPropertyUint32(js, jsarr, i++, number2js(js, val));
|
||||
JS_SetPropertyNumber(js, jsarr, i++, number2js(js, val));
|
||||
}
|
||||
|
||||
return jsarr;
|
||||
|
||||
@@ -69,7 +69,7 @@ static JSClassID js_mersenne_class_id;
|
||||
|
||||
static void js_mersenne_finalizer(JSRuntime *rt, JSValue val) {
|
||||
MTRand *mrand = JS_GetOpaque(val, js_mersenne_class_id);
|
||||
js_free_rt(rt, mrand);
|
||||
js_free_rt(mrand);
|
||||
}
|
||||
|
||||
static JSClassDef js_mersenne_class = {
|
||||
@@ -131,7 +131,7 @@ static JSValue js_mersenne_use_call(JSContext *js, JSValueConst new_target, int
|
||||
|
||||
CELL_USE_INIT(
|
||||
JS_NewClassID(&js_mersenne_class_id);
|
||||
JS_NewClass(JS_GetRuntime(js), js_mersenne_class_id, &js_mersenne_class);
|
||||
JS_NewClass(js, js_mersenne_class_id, &js_mersenne_class);
|
||||
|
||||
JSValue proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, proto, js_mersenne_funcs, sizeof(js_mersenne_funcs)/sizeof(JSCFunctionListEntry));
|
||||
|
||||
@@ -43,25 +43,31 @@ var emitters = {
|
||||
// Update an emitter and its particles
|
||||
update: function(emitter, dt) {
|
||||
// Spawn new particles
|
||||
var pp = 0
|
||||
if (emitter.rate > 0) {
|
||||
emitter.spawn_timer = (emitter.spawn_timer || 0) + dt
|
||||
var pp = 1 / emitter.rate
|
||||
pp = 1 / emitter.rate
|
||||
while (emitter.spawn_timer > pp) {
|
||||
emitter.spawn_timer -= pp
|
||||
emitters.spawn(emitter)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update existing particles
|
||||
for (var i = length(emitter.particles) - 1; i >= 0; i--) {
|
||||
var p = emitter.particles[i]
|
||||
var i = 0
|
||||
var p = null
|
||||
var grow_for = 0
|
||||
var shrink_for = 0
|
||||
var alpha = 0
|
||||
for (i = length(emitter.particles) - 1; i >= 0; i--) {
|
||||
p = emitter.particles[i]
|
||||
p.time += dt
|
||||
p.pos.x += p.velocity.x * dt
|
||||
p.pos.y += p.velocity.y * dt
|
||||
|
||||
|
||||
// Scale animation
|
||||
var grow_for = emitter.grow_for || 0.3
|
||||
var shrink_for = emitter.shrink_for || 0.5
|
||||
grow_for = emitter.grow_for || 0.3
|
||||
shrink_for = emitter.shrink_for || 0.5
|
||||
if (p.time < grow_for) {
|
||||
p.scale = lerp(0, p.max_scale, p.time / grow_for)
|
||||
} else if (p.time > p.life - shrink_for) {
|
||||
@@ -69,9 +75,9 @@ var emitters = {
|
||||
} else {
|
||||
p.scale = p.max_scale
|
||||
}
|
||||
|
||||
|
||||
// Alpha fade
|
||||
var alpha = 1
|
||||
alpha = 1
|
||||
if (p.time > p.life * 0.7) {
|
||||
alpha = 1 - (p.time - p.life * 0.7) / (p.life * 0.3)
|
||||
}
|
||||
|
||||
178
playdate.cm
178
playdate.cm
@@ -82,46 +82,36 @@ PlaydateBackend.prototype.execute = function(commands) {
|
||||
}
|
||||
|
||||
PlaydateBackend.prototype.execute_command = function(cmd) {
|
||||
switch (cmd.cmd) {
|
||||
case 'begin_render':
|
||||
this.cmd_begin_render(cmd)
|
||||
break
|
||||
case 'end_render':
|
||||
this.cmd_end_render()
|
||||
break
|
||||
case 'set_camera':
|
||||
this.cmd_set_camera(cmd)
|
||||
break
|
||||
case 'draw_batch':
|
||||
this.cmd_draw_batch(cmd)
|
||||
break
|
||||
case 'shader_pass':
|
||||
this.cmd_shader_pass(cmd) // DEGRADES
|
||||
break
|
||||
case 'apply_mask':
|
||||
this.cmd_apply_mask(cmd) // NATIVE!
|
||||
break
|
||||
case 'composite':
|
||||
this.cmd_composite(cmd)
|
||||
break
|
||||
case 'blit':
|
||||
this.cmd_blit(cmd)
|
||||
break
|
||||
case 'clear':
|
||||
this.cmd_clear(cmd)
|
||||
break
|
||||
case 'present':
|
||||
// Nothing to do, display updates automatically
|
||||
break
|
||||
default:
|
||||
console.error(`Unknown command: ${cmd.cmd}`)
|
||||
if (cmd.cmd == 'begin_render') {
|
||||
this.cmd_begin_render(cmd)
|
||||
} else if (cmd.cmd == 'end_render') {
|
||||
this.cmd_end_render()
|
||||
} else if (cmd.cmd == 'set_camera') {
|
||||
this.cmd_set_camera(cmd)
|
||||
} else if (cmd.cmd == 'draw_batch') {
|
||||
this.cmd_draw_batch(cmd)
|
||||
} else if (cmd.cmd == 'shader_pass') {
|
||||
this.cmd_shader_pass(cmd) // DEGRADES
|
||||
} else if (cmd.cmd == 'apply_mask') {
|
||||
this.cmd_apply_mask(cmd) // NATIVE!
|
||||
} else if (cmd.cmd == 'composite') {
|
||||
this.cmd_composite(cmd)
|
||||
} else if (cmd.cmd == 'blit') {
|
||||
this.cmd_blit(cmd)
|
||||
} else if (cmd.cmd == 'clear') {
|
||||
this.cmd_clear(cmd)
|
||||
} else if (cmd.cmd == 'present') {
|
||||
// Nothing to do, display updates automatically
|
||||
} else {
|
||||
log.console(`Unknown command: ${cmd.cmd}`)
|
||||
}
|
||||
}
|
||||
|
||||
PlaydateBackend.prototype.cmd_begin_render = function(cmd) {
|
||||
var target = cmd.target
|
||||
var clear = cmd.clear
|
||||
|
||||
var pattern = null
|
||||
|
||||
if (target == 'screen') {
|
||||
// Render to screen framebuffer
|
||||
this.current_target = 'screen'
|
||||
@@ -130,10 +120,10 @@ PlaydateBackend.prototype.cmd_begin_render = function(cmd) {
|
||||
// Render to bitmap
|
||||
this.current_target = target
|
||||
this.pd.graphics.pushContext(target.bitmap)
|
||||
|
||||
|
||||
if (clear) {
|
||||
// Clear with color (Playdate is 1-bit, so color becomes pattern/dither)
|
||||
var pattern = this.color_to_dither_pattern(clear)
|
||||
pattern = this.color_to_dither_pattern(clear)
|
||||
this.pd.graphics.setDitherPattern(pattern)
|
||||
this.pd.graphics.fillRect(0, 0, target.width, target.height)
|
||||
}
|
||||
@@ -156,44 +146,54 @@ PlaydateBackend.prototype.cmd_set_camera = function(cmd) {
|
||||
PlaydateBackend.prototype.cmd_draw_batch = function(cmd) {
|
||||
var geometry = cmd.geometry
|
||||
var material = cmd.material || {}
|
||||
|
||||
|
||||
// Get image (Playdate uses LCDBitmap for textures)
|
||||
var image = this.get_image(material.texture || 'white')
|
||||
if (!image) return
|
||||
|
||||
|
||||
// Set draw mode based on material
|
||||
var draw_mode = this.material_to_draw_mode(material)
|
||||
this.pd.graphics.setImageDrawMode(draw_mode)
|
||||
|
||||
|
||||
// Draw each sprite in the batch
|
||||
for (var i = 0; i < length(geometry.indices); i += 6) {
|
||||
var i = 0
|
||||
var vert_idx = 0
|
||||
var v = null
|
||||
var screen_pos = null
|
||||
var v2 = null
|
||||
var width = 0
|
||||
var height = 0
|
||||
var scale = 0
|
||||
var alpha_pattern = null
|
||||
var scaled = null
|
||||
for (i = 0; i < length(geometry.indices); i += 6) {
|
||||
// Each sprite is 2 triangles = 6 indices = 4 vertices
|
||||
var vert_idx = geometry.indices[i]
|
||||
var v = geometry.vertices[vert_idx]
|
||||
|
||||
vert_idx = geometry.indices[i]
|
||||
v = geometry.vertices[vert_idx]
|
||||
|
||||
// Transform by camera
|
||||
var screen_pos = this.world_to_screen(v.pos, this.current_camera)
|
||||
|
||||
screen_pos = this.world_to_screen(v.pos, this.current_camera)
|
||||
|
||||
// Get sprite size from vertices
|
||||
var v2 = geometry.vertices[vert_idx + 2]
|
||||
var width = v2.pos[0] - v.pos[0]
|
||||
var height = v2.pos[1] - v.pos[1]
|
||||
|
||||
v2 = geometry.vertices[vert_idx + 2]
|
||||
width = v2.pos[0] - v.pos[0]
|
||||
height = v2.pos[1] - v.pos[1]
|
||||
|
||||
// Transform size by camera zoom
|
||||
var scale = this.get_camera_scale(this.current_camera)
|
||||
scale = this.get_camera_scale(this.current_camera)
|
||||
width *= scale
|
||||
height *= scale
|
||||
|
||||
|
||||
// Apply vertex color as dither pattern (best we can do on 1-bit)
|
||||
if (v.color.a < 1.0) {
|
||||
var alpha_pattern = this.alpha_to_dither_pattern(v.color.a)
|
||||
alpha_pattern = this.alpha_to_dither_pattern(v.color.a)
|
||||
this.pd.graphics.setDitherPattern(alpha_pattern)
|
||||
}
|
||||
|
||||
|
||||
// Draw image
|
||||
if (width != image.width || height != image.height) {
|
||||
// Need scaling
|
||||
var scaled = image.scaledImage(width / image.width, height / image.height)
|
||||
scaled = image.scaledImage(width / image.width, height / image.height)
|
||||
this.pd.graphics.drawBitmap(scaled, screen_pos[0], screen_pos[1])
|
||||
} else {
|
||||
this.pd.graphics.drawBitmap(image, screen_pos[0], screen_pos[1])
|
||||
@@ -204,35 +204,35 @@ PlaydateBackend.prototype.cmd_draw_batch = function(cmd) {
|
||||
PlaydateBackend.prototype.cmd_shader_pass = function(cmd) {
|
||||
// NO SHADERS ON PLAYDATE
|
||||
// Degrade gracefully based on shader type
|
||||
|
||||
|
||||
var shader = cmd.shader
|
||||
var input = cmd.input
|
||||
var params = cmd.params
|
||||
|
||||
|
||||
if (shader == 'threshold') {
|
||||
// Threshold: Just copy input (or could dither based on threshold)
|
||||
console.warn('Threshold shader not supported on Playdate, copying')
|
||||
log.console('Threshold shader not supported on Playdate, copying')
|
||||
this.copy_bitmap(input.bitmap, this.current_target.bitmap)
|
||||
}
|
||||
else if (shader == 'gaussian_blur') {
|
||||
// Blur: Box blur in CPU (slow but possible)
|
||||
console.warn('Blur shader using CPU fallback')
|
||||
log.console('Blur shader using CPU fallback')
|
||||
this.cpu_box_blur(input.bitmap, this.current_target.bitmap, params.radius || 5)
|
||||
}
|
||||
else if (shader == 'add_textures') {
|
||||
// Additive blend: Use XOR draw mode (not perfect but interesting)
|
||||
console.warn('Additive blend approximated with XOR')
|
||||
log.console('Additive blend approximated with XOR')
|
||||
this.pd.graphics.setImageDrawMode(this.pd.graphics.kDrawModeXOR)
|
||||
this.pd.graphics.drawBitmap(input.bitmap, 0, 0)
|
||||
}
|
||||
else if (shader == 'crt_filter') {
|
||||
// CRT: Not possible, just copy
|
||||
console.warn('CRT filter not supported on Playdate, copying')
|
||||
log.console('CRT filter not supported on Playdate, copying')
|
||||
this.copy_bitmap(input.bitmap, this.current_target.bitmap)
|
||||
}
|
||||
else {
|
||||
// Unknown shader: copy
|
||||
console.warn(`Shader ${shader} not supported on Playdate, copying`)
|
||||
log.console(`Shader ${shader} not supported on Playdate, copying`)
|
||||
this.copy_bitmap(input.bitmap, this.current_target.bitmap)
|
||||
}
|
||||
}
|
||||
@@ -242,23 +242,24 @@ PlaydateBackend.prototype.cmd_apply_mask = function(cmd) {
|
||||
var content = cmd.content_texture.bitmap
|
||||
var mask = cmd.mask_texture.bitmap
|
||||
var invert = cmd.invert
|
||||
|
||||
var inverted = null
|
||||
|
||||
if (invert) {
|
||||
// Invert mask first
|
||||
var inverted = this.pd.graphics.newBitmap(mask.width, mask.height)
|
||||
inverted = this.pd.graphics.newBitmap(mask.width, mask.height)
|
||||
this.pd.graphics.pushContext(inverted)
|
||||
this.pd.graphics.setImageDrawMode(this.pd.graphics.kDrawModeInverted)
|
||||
this.pd.graphics.drawBitmap(mask, 0, 0)
|
||||
this.pd.graphics.popContext()
|
||||
mask = inverted
|
||||
}
|
||||
|
||||
|
||||
// Set mask on content bitmap
|
||||
content.setMask(mask)
|
||||
|
||||
|
||||
// Draw masked content to current target
|
||||
this.pd.graphics.drawBitmap(content, 0, 0)
|
||||
|
||||
|
||||
// Clear mask (don't leave it set)
|
||||
content.setMask(null)
|
||||
}
|
||||
@@ -284,14 +285,15 @@ PlaydateBackend.prototype.cmd_blit = function(cmd) {
|
||||
var bitmap = cmd.texture.bitmap
|
||||
var dst_rect = cmd.dst_rect
|
||||
var filter = cmd.filter
|
||||
|
||||
|
||||
// Scale bitmap to fit dst_rect
|
||||
var scale_x = dst_rect.width / bitmap.width
|
||||
var scale_y = dst_rect.height / bitmap.height
|
||||
|
||||
var scaled = null
|
||||
|
||||
if (scale_x != 1.0 || scale_y != 1.0) {
|
||||
// Playdate only supports nearest-neighbor scaling
|
||||
var scaled = bitmap.scaledImage(scale_x, scale_y)
|
||||
scaled = bitmap.scaledImage(scale_x, scale_y)
|
||||
this.pd.graphics.drawBitmap(scaled, dst_rect.x, dst_rect.y)
|
||||
} else {
|
||||
this.pd.graphics.drawBitmap(bitmap, dst_rect.x, dst_rect.y)
|
||||
@@ -400,36 +402,44 @@ PlaydateBackend.prototype.get_image = function(name) {
|
||||
PlaydateBackend.prototype.cpu_box_blur = function(src, dst, radius) {
|
||||
// Simple box blur implementation in CPU
|
||||
// This is SLOW but correct
|
||||
|
||||
|
||||
var width = src.width
|
||||
var height = src.height
|
||||
|
||||
|
||||
// Get pixel data (Playdate API for accessing bitmap pixels)
|
||||
var src_data = src.getData()
|
||||
var dst_data = dst.getData()
|
||||
|
||||
for (var y = 0; y < height; y++) {
|
||||
for (var x = 0; x < width; x++) {
|
||||
var sum = 0
|
||||
var count = 0
|
||||
|
||||
var y = 0
|
||||
var x = 0
|
||||
var sum = 0
|
||||
var count = 0
|
||||
var dy = 0
|
||||
var dx = 0
|
||||
var sx = 0
|
||||
var sy = 0
|
||||
|
||||
for (y = 0; y < height; y++) {
|
||||
for (x = 0; x < width; x++) {
|
||||
sum = 0
|
||||
count = 0
|
||||
|
||||
// Sample neighborhood
|
||||
for (var dy = -radius; dy <= radius; dy++) {
|
||||
for (var dx = -radius; dx <= radius; dx++) {
|
||||
var sx = x + dx
|
||||
var sy = y + dy
|
||||
|
||||
for (dy = -radius; dy <= radius; dy++) {
|
||||
for (dx = -radius; dx <= radius; dx++) {
|
||||
sx = x + dx
|
||||
sy = y + dy
|
||||
|
||||
if (sx >= 0 && sx < width && sy >= 0 && sy < height) {
|
||||
sum += src_data[sy * width + sx]
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dst_data[y * width + x] = sum / count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dst.setData(dst_data)
|
||||
}
|
||||
|
||||
|
||||
60
prosperon.c
60
prosperon.c
@@ -7,10 +7,10 @@ colorf js2color(JSContext *js,JSValue v) {
|
||||
|
||||
colorf color = {1,1,1,1}; // Default to white
|
||||
|
||||
if (JS_IsArray(js, v)) {
|
||||
if (JS_IsArray(v)) {
|
||||
// Handle array format: [r, g, b, a]
|
||||
JSValue c[4];
|
||||
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
|
||||
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyNumber(js,v,i);
|
||||
|
||||
color.r = js2number(js,c[0]);
|
||||
color.g = js2number(js,c[1]);
|
||||
@@ -31,10 +31,10 @@ colorf js2color(JSContext *js,JSValue v) {
|
||||
JSValue color2js(JSContext *js, colorf color)
|
||||
{
|
||||
JSValue arr = JS_NewArray(js);
|
||||
JS_SetPropertyUint32(js, arr,0,number2js(js,(double)color.r));
|
||||
JS_SetPropertyUint32(js, arr,1,number2js(js,(double)color.g));
|
||||
JS_SetPropertyUint32(js, arr,2,number2js(js,(double)color.b));
|
||||
JS_SetPropertyUint32(js, arr,3,number2js(js,(double)color.a));
|
||||
JS_SetPropertyNumber(js, arr,0,number2js(js,(double)color.r));
|
||||
JS_SetPropertyNumber(js, arr,1,number2js(js,(double)color.g));
|
||||
JS_SetPropertyNumber(js, arr,2,number2js(js,(double)color.b));
|
||||
JS_SetPropertyNumber(js, arr,3,number2js(js,(double)color.a));
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ HMM_Vec2 js2vec2(JSContext *js,JSValue v)
|
||||
HMM_Vec2 v2;
|
||||
|
||||
// Check if it's an array
|
||||
if (JS_IsArray(js, v)) {
|
||||
{ JSValue val = JS_GetPropertyUint32(js,v,0); v2.X = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyUint32(js,v,1); v2.Y = js2number(js, val); JS_FreeValue(js,val); }
|
||||
if (JS_IsArray(v)) {
|
||||
{ JSValue val = JS_GetPropertyNumber(js,v,0); v2.X = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js,v,1); v2.Y = js2number(js, val); JS_FreeValue(js,val); }
|
||||
} else {
|
||||
// Try to get x,y properties from object
|
||||
JSValue x_val = JS_GetPropertyStr(js, v, "x");
|
||||
@@ -64,9 +64,9 @@ HMM_Vec2 js2vec2(JSContext *js,JSValue v)
|
||||
HMM_Vec3 js2vec3(JSContext *js,JSValue v)
|
||||
{
|
||||
HMM_Vec3 v3;
|
||||
{ JSValue val = JS_GetPropertyUint32(js, v,0); v3.x = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyUint32(js, v,1); v3.y = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyUint32(js, v,2); v3.z = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js, v,0); v3.x = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js, v,1); v3.y = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js, v,2); v3.z = js2number(js, val); JS_FreeValue(js,val); }
|
||||
return v3;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ float *js2floats(JSContext *js, JSValue v, size_t *len)
|
||||
*len = JS_ArrayLength(js,v);
|
||||
float *arr = malloc(sizeof(float)* *len);
|
||||
for (int i = 0; i < *len; i++)
|
||||
{ JSValue val = JS_GetPropertyUint32(js,v,i); arr[i] = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js,v,i); arr[i] = js2number(js, val); JS_FreeValue(js,val); }
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -84,14 +84,14 @@ double *js2doubles(JSContext *js, JSValue v, size_t *len)
|
||||
*len = JS_ArrayLength(js,v);
|
||||
double *arr = malloc(sizeof(double)* *len);
|
||||
for (int i = 0; i < *len; i++)
|
||||
{ JSValue val = JS_GetPropertyUint32(js,v,i); arr[i] = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js,v,i); arr[i] = js2number(js, val); JS_FreeValue(js,val); }
|
||||
return arr;
|
||||
}
|
||||
|
||||
HMM_Vec3 js2vec3f(JSContext *js, JSValue v)
|
||||
{
|
||||
HMM_Vec3 vec;
|
||||
if (JS_IsArray(js, v))
|
||||
if (JS_IsArray(v))
|
||||
return js2vec3(js,v);
|
||||
else
|
||||
vec.x = vec.y = vec.z = js2number(js,v);
|
||||
@@ -101,9 +101,9 @@ HMM_Vec3 js2vec3f(JSContext *js, JSValue v)
|
||||
JSValue vec32js(JSContext *js, HMM_Vec3 v)
|
||||
{
|
||||
JSValue array = JS_NewArray(js);
|
||||
JS_SetPropertyUint32(js, array,0,number2js(js,v.x));
|
||||
JS_SetPropertyUint32(js, array,1,number2js(js,v.y));
|
||||
JS_SetPropertyUint32(js, array,2,number2js(js,v.z));
|
||||
JS_SetPropertyNumber(js, array,0,number2js(js,v.x));
|
||||
JS_SetPropertyNumber(js, array,1,number2js(js,v.y));
|
||||
JS_SetPropertyNumber(js, array,2,number2js(js,v.z));
|
||||
return array;
|
||||
}
|
||||
|
||||
@@ -115,10 +115,10 @@ JSValue vec3f2js(JSContext *js, HMM_Vec3 v)
|
||||
JSValue quat2js(JSContext *js, HMM_Quat q)
|
||||
{
|
||||
JSValue arr = JS_NewArray(js);
|
||||
JS_SetPropertyUint32(js, arr, 0, number2js(js,q.x));
|
||||
JS_SetPropertyUint32(js, arr,1,number2js(js,q.y));
|
||||
JS_SetPropertyUint32(js, arr,2,number2js(js,q.z));
|
||||
JS_SetPropertyUint32(js, arr,3,number2js(js,q.w));
|
||||
JS_SetPropertyNumber(js, arr, 0, number2js(js,q.x));
|
||||
JS_SetPropertyNumber(js, arr,1,number2js(js,q.y));
|
||||
JS_SetPropertyNumber(js, arr,2,number2js(js,q.z));
|
||||
JS_SetPropertyNumber(js, arr,3,number2js(js,q.w));
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ HMM_Vec4 js2vec4(JSContext *js, JSValue v)
|
||||
{
|
||||
HMM_Vec4 v4;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{ JSValue val = JS_GetPropertyUint32(js, v,i); v4.e[i] = js2number(js, val); JS_FreeValue(js,val); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js, v,i); v4.e[i] = js2number(js, val); JS_FreeValue(js,val); }
|
||||
return v4;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ double arr_vec_length(JSContext *js,JSValue v)
|
||||
|
||||
double sum = 0;
|
||||
for (int i = 0; i < len; i++)
|
||||
{ JSValue val = JS_GetPropertyUint32(js, v, i); double num = js2number(js, val); JS_FreeValue(js,val); sum += pow(num, 2); }
|
||||
{ JSValue val = JS_GetPropertyNumber(js, v, i); double num = js2number(js, val); JS_FreeValue(js,val); sum += pow(num, 2); }
|
||||
|
||||
return sqrt(sum);
|
||||
}
|
||||
@@ -155,7 +155,7 @@ JSValue vec42js(JSContext *js, HMM_Vec4 v)
|
||||
{
|
||||
JSValue array = JS_NewArray(js);
|
||||
for (int i = 0; i < 4; i++)
|
||||
JS_SetPropertyUint32(js, array,i,number2js(js,v.e[i]));
|
||||
JS_SetPropertyNumber(js, array,i,number2js(js,v.e[i]));
|
||||
return array;
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ HMM_Vec2 *js2cpvec2arr(JSContext *js,JSValue v) {
|
||||
arrsetlen(arr,n);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
JSValue ii = JS_GetPropertyUint32(js,v,i);
|
||||
JSValue ii = JS_GetPropertyNumber(js,v,i);
|
||||
arr[i] = js2vec2(js,ii);
|
||||
JS_FreeValue(js,ii);
|
||||
}
|
||||
@@ -193,7 +193,7 @@ rect js2rect(JSContext *js,JSValue v) {
|
||||
static JSValue floats2array(JSContext *js, float *vals, size_t len) {
|
||||
JSValue arr = JS_NewArray(js);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
JS_SetPropertyUint32(js, arr, i, number2js(js, vals[i]));
|
||||
JS_SetPropertyNumber(js, arr, i, number2js(js, vals[i]));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
@@ -211,15 +211,15 @@ lrtb js2lrtb(JSContext *js, JSValue v)
|
||||
JSValue vec22js(JSContext *js,HMM_Vec2 v)
|
||||
{
|
||||
JSValue array = JS_NewArray(js);
|
||||
JS_SetPropertyUint32(js, array,0,number2js(js,v.x));
|
||||
JS_SetPropertyUint32(js, array,1,number2js(js,v.y));
|
||||
JS_SetPropertyNumber(js, array,0,number2js(js,v.x));
|
||||
JS_SetPropertyNumber(js, array,1,number2js(js,v.y));
|
||||
return array;
|
||||
}
|
||||
|
||||
JSValue vecarr2js(JSContext *js,HMM_Vec2 *points, int n) {
|
||||
JSValue array = JS_NewArray(js);
|
||||
for (int i = 0; i < n; i++)
|
||||
JS_SetPropertyUint32(js, array,i,vec22js(js,points[i]));
|
||||
JS_SetPropertyNumber(js, array,i,vec22js(js,points[i]));
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
166
rasterize.cm
166
rasterize.cm
@@ -7,88 +7,106 @@ var math = use('math')
|
||||
|
||||
var rasterize = {}
|
||||
|
||||
function within_wedge(dx, dy, start, end, full_circle) {
|
||||
if (full_circle) return true
|
||||
function within_wedge(dx, dy, wedge) {
|
||||
if (wedge.full) return true
|
||||
|
||||
var ang = math.arc_tangent(dy, dx)
|
||||
if (ang < 0) ang += pi * 2
|
||||
var t = ang / (pi * 2)
|
||||
|
||||
if (start <= end) return t >= start && t <= end
|
||||
return t >= start || t <= end
|
||||
if (wedge.start <= wedge.end) return t >= wedge.start && t <= wedge.end
|
||||
return t >= wedge.start || t <= wedge.end
|
||||
}
|
||||
|
||||
rasterize.ellipse = function ellipse(pos, radii, opt) {
|
||||
opt = opt || {}
|
||||
var _opt = opt || {}
|
||||
var rx = radii[0], ry = radii[1]
|
||||
if (rx <= 0 || ry <= 0) return []
|
||||
|
||||
var cx = pos[0], cy = pos[1]
|
||||
var raw_start = opt.start || 0
|
||||
var raw_end = opt.end || 1
|
||||
var raw_start = _opt.start || 0
|
||||
var raw_end = _opt.end || 1
|
||||
var full_circle = abs(raw_end - raw_start) >= 1 - 1e-9
|
||||
var start = (raw_start % 1 + 1) % 1
|
||||
var end = (raw_end % 1 + 1) % 1
|
||||
var thickness = max(1, opt.thickness || 1)
|
||||
var thickness = max(1, _opt.thickness || 1)
|
||||
var wedge_info = {start: start, end: end, full: full_circle}
|
||||
|
||||
var rx_i = rx - thickness,
|
||||
ry_i = ry - thickness
|
||||
var hole = (rx_i > 0 && ry_i > 0)
|
||||
|
||||
if (!hole && thickness == 1) {
|
||||
var points = []
|
||||
var rx_sq = rx * rx, ry_sq = ry * ry
|
||||
var two_rx_sq = rx_sq << 1, two_ry_sq = ry_sq << 1
|
||||
var x = 0, y = ry, px = 0, py = two_rx_sq * y
|
||||
var p = ry_sq - rx_sq * ry + 0.25 * rx_sq
|
||||
var points = []
|
||||
var rx_sq = rx * rx, ry_sq = ry * ry
|
||||
var two_rx_sq = null
|
||||
var two_ry_sq = null
|
||||
var x = 0, y = 0, px = 0, py = 0
|
||||
var p = 0
|
||||
|
||||
function add_pts(x, y) {
|
||||
var pts = [
|
||||
[cx + x, cy + y], [cx - x, cy + y],
|
||||
[cx + x, cy - y], [cx - x, cy - y]
|
||||
]
|
||||
points = array(points, filter(pts, pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle)))
|
||||
}
|
||||
var add_pts = function(ax, ay) {
|
||||
var pts = [
|
||||
[cx + ax, cy + ay], [cx - ax, cy + ay],
|
||||
[cx + ax, cy - ay], [cx - ax, cy - ay]
|
||||
]
|
||||
points = array(points, filter(pts, pt => within_wedge(pt[0]-cx, pt[1]-cy, wedge_info)))
|
||||
}
|
||||
|
||||
if (!hole && thickness == 1) {
|
||||
two_rx_sq = rx_sq << 1
|
||||
two_ry_sq = ry_sq << 1
|
||||
x = 0
|
||||
y = ry
|
||||
px = 0
|
||||
py = two_rx_sq * y
|
||||
p = ry_sq - rx_sq * ry + 0.25 * rx_sq
|
||||
|
||||
while (px < py) {
|
||||
add_pts(x, y)
|
||||
++x; px += two_ry_sq
|
||||
x += 1; px += two_ry_sq
|
||||
if (p < 0) p += ry_sq + px
|
||||
else { --y; py -= two_rx_sq; p += ry_sq + px - py }
|
||||
else { y -= 1; py -= two_rx_sq; p += ry_sq + px - py }
|
||||
}
|
||||
p = ry_sq*(x+.5)*(x+.5) + rx_sq*(y-1)*(y-1) - rx_sq*ry_sq
|
||||
while (y >= 0) {
|
||||
add_pts(x, y)
|
||||
--y; py -= two_rx_sq
|
||||
y -= 1; py -= two_rx_sq
|
||||
if (p > 0) p += rx_sq - py
|
||||
else { ++x; px += two_ry_sq; p += rx_sq - py + px }
|
||||
else { x += 1; px += two_ry_sq; p += rx_sq - py + px }
|
||||
}
|
||||
return {type: 'points', data: points}
|
||||
}
|
||||
|
||||
var strips = []
|
||||
var rx_sq = rx * rx, ry_sq = ry * ry
|
||||
var rx_i_sq = rx_i * rx_i, ry_i_sq = ry_i * ry_i
|
||||
var dy = -ry
|
||||
var yy = 0
|
||||
var x_out = 0
|
||||
var y_screen = 0
|
||||
var x_in = 0
|
||||
var run_start = null
|
||||
var dx = 0
|
||||
var last = false
|
||||
var next_in_ring = false
|
||||
|
||||
for (var dy = -ry; dy <= ry; ++dy) {
|
||||
var yy = dy * dy
|
||||
var x_out = floor(rx * math.sqrt(1 - yy / ry_sq))
|
||||
var y_screen = cy + dy
|
||||
for (dy = -ry; dy <= ry; ++dy) {
|
||||
yy = dy * dy
|
||||
x_out = floor(rx * math.sqrt(1 - yy / ry_sq))
|
||||
y_screen = cy + dy
|
||||
|
||||
var x_in = hole ? floor(rx_i * math.sqrt(1 - yy / ry_i_sq)) : -1
|
||||
x_in = hole ? floor(rx_i * math.sqrt(1 - yy / ry_i_sq)) : -1
|
||||
|
||||
var run_start = null
|
||||
for (var dx = -x_out; dx <= x_out; ++dx) {
|
||||
run_start = null
|
||||
for (dx = -x_out; dx <= x_out; ++dx) {
|
||||
if (hole && abs(dx) <= x_in) { run_start = null; continue }
|
||||
if (!within_wedge(dx, dy, start, end, full_circle)) { run_start = null; continue }
|
||||
if (!within_wedge(dx, dy, wedge_info)) { run_start = null; continue }
|
||||
|
||||
if (run_start == null) run_start = cx + dx
|
||||
|
||||
var last = (dx == x_out)
|
||||
var next_in_ring =
|
||||
last = (dx == x_out)
|
||||
next_in_ring =
|
||||
!last &&
|
||||
!(hole && abs(dx+1) <= x_in) &&
|
||||
within_wedge(dx+1, dy, start, end, full_circle)
|
||||
within_wedge(dx+1, dy, wedge_info)
|
||||
|
||||
if (last || !next_in_ring) {
|
||||
push(strips, {
|
||||
@@ -134,18 +152,18 @@ rasterize.outline_rect = function outline_rect(rect, thickness) {
|
||||
}
|
||||
|
||||
rasterize.round_rect = function round_rect(rect, radius, thickness) {
|
||||
thickness = thickness || 1
|
||||
|
||||
if (thickness <= 0) {
|
||||
var _thickness = thickness || 1
|
||||
|
||||
if (_thickness <= 0) {
|
||||
return rasterize.fill_round_rect(rect, radius)
|
||||
}
|
||||
|
||||
radius = min(radius, rect.width >> 1, rect.height >> 1)
|
||||
var _radius = min(radius, rect.width >> 1, rect.height >> 1)
|
||||
|
||||
if ((thickness << 1) >= rect.width ||
|
||||
(thickness << 1) >= rect.height ||
|
||||
thickness >= radius) {
|
||||
return rasterize.fill_round_rect(rect, radius)
|
||||
if ((_thickness << 1) >= rect.width ||
|
||||
(_thickness << 1) >= rect.height ||
|
||||
_thickness >= _radius) {
|
||||
return rasterize.fill_round_rect(rect, _radius)
|
||||
}
|
||||
|
||||
var x0 = rect.x,
|
||||
@@ -153,27 +171,32 @@ rasterize.round_rect = function round_rect(rect, radius, thickness) {
|
||||
x1 = rect.x + rect.width - 1,
|
||||
y1 = rect.y + rect.height - 1
|
||||
|
||||
var cx_l = x0 + radius, cx_r = x1 - radius
|
||||
var cy_t = y0 + radius, cy_b = y1 - radius
|
||||
var r_out = radius
|
||||
var r_in = radius - thickness
|
||||
var cx_l = x0 + _radius, cx_r = x1 - _radius
|
||||
var cy_t = y0 + _radius, cy_b = y1 - _radius
|
||||
var r_out = _radius
|
||||
var r_in = _radius - _thickness
|
||||
|
||||
var rects = [
|
||||
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1), height:thickness },
|
||||
{ x:x0 + radius, y:y1 - thickness + 1, width:rect.width - (radius << 1), height:thickness },
|
||||
{ x:x0, y:y0 + radius, width:thickness, height:rect.height - (radius << 1) },
|
||||
{ x:x1 - thickness + 1, y:y0 + radius, width:thickness, height:rect.height - (radius << 1) }
|
||||
{ x:x0 + _radius, y:y0, width:rect.width - (_radius << 1), height:_thickness },
|
||||
{ x:x0 + _radius, y:y1 - _thickness + 1, width:rect.width - (_radius << 1), height:_thickness },
|
||||
{ x:x0, y:y0 + _radius, width:_thickness, height:rect.height - (_radius << 1) },
|
||||
{ x:x1 - _thickness + 1, y:y0 + _radius, width:_thickness, height:rect.height - (_radius << 1) }
|
||||
]
|
||||
|
||||
var strips = []
|
||||
var dy = 0
|
||||
var dy_sq = 0
|
||||
var dx_out = 0
|
||||
var dx_in = 0
|
||||
var w = 0
|
||||
|
||||
for (var dy = 0; dy < radius; ++dy) {
|
||||
var dy_sq = dy * dy
|
||||
var dx_out = floor(math.sqrt(r_out * r_out - dy_sq))
|
||||
var dx_in = (r_in > 0 && dy < r_in)
|
||||
? floor(math.sqrt(r_in * r_in - dy_sq))
|
||||
: -1
|
||||
var w = dx_out - dx_in
|
||||
for (dy = 0; dy < _radius; ++dy) {
|
||||
dy_sq = dy * dy
|
||||
dx_out = floor(math.sqrt(r_out * r_out - dy_sq))
|
||||
dx_in = (r_in > 0 && dy < r_in)
|
||||
? floor(math.sqrt(r_in * r_in - dy_sq))
|
||||
: -1
|
||||
w = dx_out - dx_in
|
||||
if (w <= 0) continue
|
||||
|
||||
push(strips,
|
||||
@@ -188,7 +211,7 @@ rasterize.round_rect = function round_rect(rect, radius, thickness) {
|
||||
}
|
||||
|
||||
rasterize.fill_round_rect = function fill_round_rect(rect, radius) {
|
||||
radius = min(radius, rect.width >> 1, rect.height >> 1)
|
||||
var _radius = min(radius, rect.width >> 1, rect.height >> 1)
|
||||
|
||||
var x0 = rect.x,
|
||||
y0 = rect.y,
|
||||
@@ -196,20 +219,23 @@ rasterize.fill_round_rect = function fill_round_rect(rect, radius) {
|
||||
y1 = rect.y + rect.height - 1
|
||||
|
||||
var rects = [
|
||||
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1), height:rect.height },
|
||||
{ x:x0, y:y0 + radius, width:radius, height:rect.height - (radius << 1) },
|
||||
{ x:x1 - radius + 1, y:y0 + radius, width:radius, height:rect.height - (radius << 1) }
|
||||
{ x:x0 + _radius, y:y0, width:rect.width - (_radius << 1), height:rect.height },
|
||||
{ x:x0, y:y0 + _radius, width:_radius, height:rect.height - (_radius << 1) },
|
||||
{ x:x1 - _radius + 1, y:y0 + _radius, width:_radius, height:rect.height - (_radius << 1) }
|
||||
]
|
||||
|
||||
var cx_l = x0 + radius, cx_r = x1 - radius
|
||||
var cy_t = y0 + radius, cy_b = y1 - radius
|
||||
var cx_l = x0 + _radius, cx_r = x1 - _radius
|
||||
var cy_t = y0 + _radius, cy_b = y1 - _radius
|
||||
var caps = []
|
||||
var dy = 0
|
||||
var dx = 0
|
||||
var w = 0
|
||||
|
||||
for (var dy = 0; dy < radius; ++dy) {
|
||||
var dx = floor(math.sqrt(radius * radius - dy * dy))
|
||||
var w = (dx << 1) + 1
|
||||
for (dy = 0; dy < _radius; ++dy) {
|
||||
dx = floor(math.sqrt(_radius * _radius - dy * dy))
|
||||
w = (dx << 1) + 1
|
||||
|
||||
push(caps,
|
||||
push(caps,
|
||||
{ x:cx_l - dx, y:cy_t - dy, width:w, height:1 },
|
||||
{ x:cx_r - dx, y:cy_t - dy, width:w, height:1 },
|
||||
{ x:cx_l - dx, y:cy_b + dy, width:w, height:1 },
|
||||
|
||||
41
resources.cm
41
resources.cm
@@ -41,20 +41,22 @@ function isRecognizedExtension(ext) {
|
||||
return false
|
||||
}
|
||||
|
||||
function find_in_path(filename, exts = []) {
|
||||
function find_in_path(filename, exts) {
|
||||
var _exts = exts || []
|
||||
if (!is_text(filename)) return null
|
||||
|
||||
var candidate = null
|
||||
if (search(filename, '.') != null) {
|
||||
var candidate = filename // possibly need "/" ?
|
||||
candidate = filename // possibly need "/" ?
|
||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||
return null
|
||||
}
|
||||
|
||||
// Only check extensions if exts is provided and not empty
|
||||
if (length(exts) > 0) {
|
||||
var cand = null
|
||||
arrfor(exts, function(ext) {
|
||||
var candidate = filename + '.' + ext
|
||||
var cand = null
|
||||
if (length(_exts) > 0) {
|
||||
arrfor(_exts, function(ext) {
|
||||
candidate = filename + '.' + ext
|
||||
if (io.exists(candidate) && !io.is_directory(candidate)){
|
||||
cand = candidate
|
||||
return true
|
||||
@@ -63,7 +65,7 @@ function find_in_path(filename, exts = []) {
|
||||
if (cand != null) return cand
|
||||
} else {
|
||||
// Fallback to extensionless file only if no extensions are specified
|
||||
var candidate = filename
|
||||
candidate = filename
|
||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||
}
|
||||
return null
|
||||
@@ -95,12 +97,13 @@ Resources.find_font = hashify(function(file) {
|
||||
function read_ignore(dir) {
|
||||
var path = dir + '/.prosperonignore'
|
||||
var patterns = []
|
||||
var lines = null
|
||||
if (io.exists(path)) {
|
||||
var lines = array(io.slurp(path), '\n')
|
||||
lines = array(io.slurp(path), '\n')
|
||||
arrfor(lines, function(line) {
|
||||
line = trim(line)
|
||||
if (!line || starts_with(line, '#')) return
|
||||
push(patterns, line)
|
||||
var trimmed = trim(line)
|
||||
if (!trimmed || starts_with(trimmed, '#')) return
|
||||
push(patterns, trimmed)
|
||||
})
|
||||
}
|
||||
return patterns
|
||||
@@ -108,20 +111,24 @@ function read_ignore(dir) {
|
||||
|
||||
// Return a list of recognized files in the directory (and subdirectories),
|
||||
// skipping those matched by .prosperonignore. Directory paths are skipped.
|
||||
Resources.getAllFiles = function(dir = "") {
|
||||
var patterns = read_ignore(dir)
|
||||
var all = io.globfs(patterns, dir)
|
||||
Resources.getAllFiles = function(dir) {
|
||||
var _dir = dir || ""
|
||||
var patterns = read_ignore(_dir)
|
||||
var all = io.globfs(patterns, _dir)
|
||||
var results = []
|
||||
arrfor(all, function(f) {
|
||||
var fullPath = dir + '/' + f
|
||||
try {
|
||||
var fullPath = _dir + '/' + f
|
||||
var _stat = function() {
|
||||
var st = io.stat(fullPath)
|
||||
// skip directories (filesize=0) or unrecognized extension
|
||||
if (!st.filesize) return
|
||||
var ext = getExtension(f)
|
||||
if (!isRecognizedExtension(ext)) return
|
||||
push(results, fullPath)
|
||||
} catch(e) {}
|
||||
} disruption {
|
||||
// skip files that can't be stat'd
|
||||
}
|
||||
_stat()
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
1022
sdl_gpu.cm
1022
sdl_gpu.cm
File diff suppressed because it is too large
Load Diff
21
sound.cm
21
sound.cm
@@ -35,16 +35,16 @@ var player = soundwave.create({
|
||||
|
||||
// Load and cache PCM data from a file path
|
||||
audio.pcm = function pcm(file) {
|
||||
file = res.find_sound(file)
|
||||
if (!file) return null
|
||||
|
||||
var path = res.find_sound(file)
|
||||
if (!path) return null
|
||||
|
||||
// Check player's cache first
|
||||
if (player.pcm_cache[file]) return player.pcm_cache[file]
|
||||
|
||||
var buf = io.slurp(file)
|
||||
if (player.pcm_cache[path]) return player.pcm_cache[path]
|
||||
|
||||
var buf = io.slurp(path)
|
||||
if (!buf) return null
|
||||
|
||||
return player.decode(buf, file)
|
||||
|
||||
return player.decode(buf, path)
|
||||
}
|
||||
|
||||
// Play a sound file, returns voice object
|
||||
@@ -73,11 +73,12 @@ feeder.resume_device()
|
||||
|
||||
// Audio pump - called periodically to fill the audio buffer
|
||||
function pump() {
|
||||
var mixed = null
|
||||
while (feeder.queued() < CHUNK_BYTES * 3) {
|
||||
var mixed = player.pull(FRAMES_PER_CHUNK)
|
||||
mixed = player.pull(FRAMES_PER_CHUNK)
|
||||
feeder.put(mixed)
|
||||
}
|
||||
|
||||
|
||||
$delay(pump, 1/240)
|
||||
}
|
||||
|
||||
|
||||
8
sprite.c
8
sprite.c
@@ -46,13 +46,7 @@ void sprite_apply(sprite *sp)
|
||||
|
||||
// Sprite class definitions
|
||||
static JSClassID js_sprite_id;
|
||||
static void js_sprite_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {
|
||||
sprite *sp = JS_GetOpaque(val, js_sprite_id);
|
||||
if (!sp) return;
|
||||
JS_MarkValue(rt, sp->image, mark_func);
|
||||
}
|
||||
|
||||
// Class definition for sprite with mark function for GC
|
||||
QJSCLASSMARK(sprite,)
|
||||
|
||||
// SPRITE ACTION FUNCTIONS
|
||||
@@ -135,7 +129,7 @@ static JSValue js_sprite_constructor(JSContext *js, JSValueConst new_target, int
|
||||
JS_GETATOM(js, sp->layer, argv[0], layer, number)
|
||||
JS_GETATOM(js, sp->color, argv[0], color, color)
|
||||
|
||||
JSValue image = JS_GetProperty(js, argv[0], JS_NewAtom(js, "image"));
|
||||
JSValue image = JS_GetPropertyStr(js, argv[0], "image");
|
||||
if (!JS_IsNull(image)) {
|
||||
sp->image = image; // Transfer ownership, no need to dup
|
||||
}
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
var Anim = (() => {
|
||||
def DEFAULT_MIN = 1 / 60; /* 16 ms – one frame */
|
||||
|
||||
function play(source, loop=true){
|
||||
function play(source, loop){
|
||||
var local_loop = !is_null(loop) ? loop : (!is_null(source.loop) ? source.loop : true);
|
||||
return {
|
||||
src : source,
|
||||
idx : 0,
|
||||
timer : 0,
|
||||
loop : loop ?? source.loop ?? true
|
||||
loop : local_loop
|
||||
};
|
||||
}
|
||||
|
||||
function update(a, dt){
|
||||
a.timer += dt;
|
||||
def frames = a.src.frames;
|
||||
var frames = a.src.frames;
|
||||
var time = null;
|
||||
while(true){
|
||||
def time = max(frames[a.idx].time || 0, Anim.minDelay);
|
||||
time = max(frames[a.idx].time || 0, Anim.minDelay);
|
||||
if(a.timer < time) break; /* still on current frame */
|
||||
|
||||
a.timer -= time;
|
||||
|
||||
@@ -52,10 +52,14 @@ function hsl_to_rgb(h, s, l) {
|
||||
|
||||
var bunny_count = 20
|
||||
|
||||
for (var i = 0; i < bunny_count; i++) {
|
||||
var pct = i/bunny_count
|
||||
var hue = 270 * i / bunny_count
|
||||
var sp = sprite.create(bunny, {
|
||||
var i = 0;
|
||||
var pct = 0;
|
||||
var hue = 0;
|
||||
var sp = null;
|
||||
for (i = 0; i < bunny_count; i++) {
|
||||
pct = i/bunny_count
|
||||
hue = 270 * i / bunny_count
|
||||
sp = sprite.create(bunny, {
|
||||
pos: [random.random()*dim.x*pct, random.random()*dim.y*pct],
|
||||
center,
|
||||
color: hsl_to_rgb(hue, 0.5, 0.5)
|
||||
|
||||
@@ -18,8 +18,10 @@ log.console("\nLooking for different colorspaces in supported formats...");
|
||||
|
||||
// Group formats by colorspace
|
||||
var colorspaces = {};
|
||||
for (var i = 0; i < length(formats); i++) {
|
||||
var fmt = formats[i];
|
||||
var i = 0;
|
||||
var fmt = null;
|
||||
for (i = 0; i < length(formats); i++) {
|
||||
fmt = formats[i];
|
||||
if (!colorspaces[fmt.colorspace]) {
|
||||
colorspaces[fmt.colorspace] = [];
|
||||
}
|
||||
@@ -53,8 +55,9 @@ arrfor(array(colorspaces), function(cs) {
|
||||
};
|
||||
|
||||
var cam = camera.open(cam_id, custom_format);
|
||||
var actual = null;
|
||||
if (cam) {
|
||||
var actual = cam.get_format();
|
||||
actual = cam.get_format();
|
||||
log.console(" Opened successfully!");
|
||||
log.console(" Actual colorspace: " + actual.colorspace);
|
||||
|
||||
|
||||
@@ -40,67 +40,76 @@ $receiver(e => {
|
||||
});
|
||||
|
||||
// Wait for approval then capture
|
||||
var srgb_surf = null;
|
||||
var linear_surf = null;
|
||||
var jpeg_surf = null;
|
||||
var hd_surf = null;
|
||||
|
||||
function capture_test() {
|
||||
if (!approved) {
|
||||
$delay(capture_test, 0.1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
log.console("\nCapturing frame...");
|
||||
var surf = cam.capture();
|
||||
|
||||
|
||||
if (!surf) {
|
||||
log.console("No frame captured yet, retrying...");
|
||||
$delay(capture_test, 0.1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
log.console("\nCaptured surface:");
|
||||
log.console(" Size:", surf.width + "x" + surf.height);
|
||||
log.console(" Format:", surf.format);
|
||||
|
||||
|
||||
// Test various colorspace conversions
|
||||
log.console("\nTesting colorspace conversions:");
|
||||
|
||||
|
||||
// Convert to sRGB if not already
|
||||
var convert_srgb = function() {
|
||||
srgb_surf = surf.convert(surf.format, "srgb");
|
||||
log.console(" Converted to sRGB colorspace");
|
||||
} disruption {
|
||||
log.console(" sRGB conversion failed");
|
||||
}
|
||||
if (format.colorspace != "srgb") {
|
||||
try {
|
||||
var srgb_surf = surf.convert(surf.format, "srgb");
|
||||
log.console(" Converted to sRGB colorspace");
|
||||
} catch(e) {
|
||||
log.console(" sRGB conversion failed:", e.message);
|
||||
}
|
||||
convert_srgb();
|
||||
}
|
||||
|
||||
|
||||
// Convert to linear sRGB for processing
|
||||
try {
|
||||
var linear_surf = surf.convert("rgba8888", "srgb_linear");
|
||||
var convert_linear = function() {
|
||||
linear_surf = surf.convert("rgba8888", "srgb_linear");
|
||||
log.console(" Converted to linear sRGB (RGBA8888) for processing");
|
||||
} catch(e) {
|
||||
log.console(" Linear sRGB conversion failed:", e.message);
|
||||
} disruption {
|
||||
log.console(" Linear sRGB conversion failed");
|
||||
}
|
||||
|
||||
convert_linear();
|
||||
|
||||
// Convert to JPEG colorspace (common for compression)
|
||||
try {
|
||||
var jpeg_surf = surf.convert("rgb888", "jpeg");
|
||||
var convert_jpeg = function() {
|
||||
jpeg_surf = surf.convert("rgb888", "jpeg");
|
||||
log.console(" Converted to JPEG colorspace (RGB888) for compression");
|
||||
} catch(e) {
|
||||
log.console(" JPEG colorspace conversion failed:", e.message);
|
||||
} disruption {
|
||||
log.console(" JPEG colorspace conversion failed");
|
||||
}
|
||||
|
||||
convert_jpeg();
|
||||
|
||||
// If YUV format, try BT.709 (HD video standard)
|
||||
if (search(surf.format, "yuv") != null || search(surf.format, "yuy") != null) {
|
||||
try {
|
||||
var hd_surf = surf.convert(surf.format, "bt709_limited");
|
||||
log.console(" Converted to BT.709 limited (HD video standard)");
|
||||
} catch(e) {
|
||||
log.console(" BT.709 conversion failed:", e.message);
|
||||
}
|
||||
var convert_hd = function() {
|
||||
hd_surf = surf.convert(surf.format, "bt709_limited");
|
||||
log.console(" Converted to BT.709 limited (HD video standard)");
|
||||
} disruption {
|
||||
log.console(" BT.709 conversion failed");
|
||||
}
|
||||
|
||||
if (search(surf.format, "yuv") != null || search(surf.format, "yuy") != null) {
|
||||
convert_hd();
|
||||
}
|
||||
|
||||
log.console("\nTest complete!");
|
||||
$stop();
|
||||
}
|
||||
|
||||
// Start capture test after a short delay
|
||||
$delay(capture_test, 0.5);
|
||||
$delay(capture_test, 0.5);
|
||||
|
||||
@@ -10,23 +10,32 @@ var cameras = camera.list();
|
||||
log.console("Found", length(cameras), "cameras");
|
||||
|
||||
// Get info about each camera
|
||||
for (var i = 0; i < length(cameras); i++) {
|
||||
var cam_id = cameras[i];
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
var cam_id = null;
|
||||
var formats = null;
|
||||
var fmt = null;
|
||||
var preferred_format = null;
|
||||
var cam = null;
|
||||
var actual_format = null;
|
||||
|
||||
for (i = 0; i < length(cameras); i++) {
|
||||
cam_id = cameras[i];
|
||||
log.console("\nCamera", i + 1, "ID:", cam_id);
|
||||
log.console(" Name:", camera.name(cam_id));
|
||||
log.console(" Position:", camera.position(cam_id));
|
||||
|
||||
|
||||
// Get supported formats
|
||||
var formats = camera.supported_formats(cam_id);
|
||||
formats = camera.supported_formats(cam_id);
|
||||
log.console(" Supported formats:", length(formats));
|
||||
|
||||
|
||||
// Show first few formats
|
||||
for (var j = 0; j < length(formats); j++) {
|
||||
var fmt = formats[j];
|
||||
for (j = 0; j < length(formats); j++) {
|
||||
fmt = formats[j];
|
||||
log.console(" Format", j + 1 + ":");
|
||||
log.console(" Pixel format:", fmt.format);
|
||||
log.console(" Resolution:", fmt.width + "x" + fmt.height);
|
||||
log.console(" FPS:", fmt.framerate_numerator + "/" + fmt.framerate_denominator,
|
||||
log.console(" FPS:", fmt.framerate_numerator + "/" + fmt.framerate_denominator,
|
||||
"(" + (fmt.framerate_numerator / fmt.framerate_denominator) + ")");
|
||||
log.console(" Colorspace:", fmt.colorspace);
|
||||
}
|
||||
@@ -35,19 +44,19 @@ for (var i = 0; i < length(cameras); i++) {
|
||||
// Open the first camera with a specific format if available
|
||||
if (length(cameras) > 0) {
|
||||
log.console("\nOpening first camera...");
|
||||
var cam_id = cameras[0];
|
||||
var formats = camera.supported_formats(cam_id);
|
||||
|
||||
cam_id = cameras[0];
|
||||
formats = camera.supported_formats(cam_id);
|
||||
|
||||
// Try to find a 640x480 format
|
||||
var preferred_format = null;
|
||||
for (var i = 0; i < length(formats); i++) {
|
||||
preferred_format = null;
|
||||
for (i = 0; i < length(formats); i++) {
|
||||
if (formats[i].width == 640 && formats[i].height == 480) {
|
||||
preferred_format = formats[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var cam;
|
||||
|
||||
cam = null;
|
||||
if (preferred_format) {
|
||||
log.console("Opening with 640x480 format...");
|
||||
cam = camera.open(cam_id, preferred_format);
|
||||
@@ -55,21 +64,21 @@ if (length(cameras) > 0) {
|
||||
log.console("Opening with default format...");
|
||||
cam = camera.open(cam_id);
|
||||
}
|
||||
|
||||
|
||||
if (cam) {
|
||||
log.console("Camera opened successfully!");
|
||||
log.console("Driver being used:", cam.get_driver());
|
||||
|
||||
|
||||
// Get the actual format being used
|
||||
var actual_format = cam.get_format();
|
||||
actual_format = cam.get_format();
|
||||
log.console("Actual format being used:");
|
||||
log.console(" Pixel format:", actual_format.format);
|
||||
log.console(" Resolution:", actual_format.width + "x" + actual_format.height);
|
||||
log.console(" FPS:", actual_format.framerate_numerator + "/" + actual_format.framerate_denominator,
|
||||
"(" + (actual_format.framerate_numerator / actual_format.framerate_denominator) + ")");
|
||||
log.console(" Colorspace:", actual_format.colorspace);
|
||||
|
||||
|
||||
// Clean up - camera will be closed when object is freed
|
||||
cam = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Test draw2d module without moth framework
|
||||
var draw2d
|
||||
var graphics
|
||||
var draw2d = null
|
||||
var graphics = null
|
||||
var os = use('os');
|
||||
var input = use('input')
|
||||
var math = use('math/radians')
|
||||
@@ -170,11 +170,16 @@ function start_drawing() {
|
||||
|
||||
// Draw some points in a pattern
|
||||
var point_count = 20;
|
||||
for (var i = 0; i < point_count; i++) {
|
||||
var angle = (i / point_count) * pi * 2;
|
||||
var r = 30 + math.sine(t * 4 + i * 0.5) * 10;
|
||||
var px = 650 + math.cosine(angle) * r;
|
||||
var py = 300 + math.sine(angle) * r;
|
||||
var i = 0;
|
||||
var angle = 0;
|
||||
var r = 0;
|
||||
var px = 0;
|
||||
var py = 0;
|
||||
for (i = 0; i < point_count; i++) {
|
||||
angle = (i / point_count) * pi * 2;
|
||||
r = 30 + math.sine(t * 4 + i * 0.5) * 10;
|
||||
px = 650 + math.cosine(angle) * r;
|
||||
py = 300 + math.sine(angle) * r;
|
||||
|
||||
draw2d.point(
|
||||
[px, py],
|
||||
|
||||
@@ -31,13 +31,17 @@ var colorspaces = ["srgb", "srgb_linear", "jpeg", "bt601_limited", "bt709_limite
|
||||
var test_format = "rgba8888";
|
||||
|
||||
log.console("\nTest 3: Converting to", test_format, "with different colorspaces:");
|
||||
for (var i = 0; i < length(colorspaces); i++) {
|
||||
try {
|
||||
var conv = surf.convert(test_format, colorspaces[i]);
|
||||
var i = 0;
|
||||
var conv = null;
|
||||
var try_convert = null;
|
||||
for (i = 0; i < length(colorspaces); i++) {
|
||||
try_convert = function() {
|
||||
conv = surf.convert(test_format, colorspaces[i]);
|
||||
log.console(" " + colorspaces[i] + ": Success");
|
||||
} catch(e) {
|
||||
log.console(" " + colorspaces[i] + ": Failed -", e.message);
|
||||
} disruption {
|
||||
log.console(" " + colorspaces[i] + ": Failed");
|
||||
}
|
||||
try_convert();
|
||||
}
|
||||
|
||||
// Test 4: YUV formats with appropriate colorspaces
|
||||
@@ -49,15 +53,18 @@ var yuv_tests = [
|
||||
{format: "yvyu", colorspace: "bt601_full"}
|
||||
];
|
||||
|
||||
for (var i = 0; i < length(yuv_tests); i++) {
|
||||
var test = yuv_tests[i];
|
||||
try {
|
||||
var conv = surf.convert(test.format, test.colorspace);
|
||||
var test = null;
|
||||
var try_yuv = null;
|
||||
for (i = 0; i < length(yuv_tests); i++) {
|
||||
test = yuv_tests[i];
|
||||
try_yuv = function() {
|
||||
conv = surf.convert(test.format, test.colorspace);
|
||||
log.console(" " + test.format + " with " + test.colorspace + ": Success");
|
||||
} catch(e) {
|
||||
log.console(" " + test.format + " with " + test.colorspace + ": Failed -", e.message);
|
||||
} disruption {
|
||||
log.console(" " + test.format + " with " + test.colorspace + ": Failed");
|
||||
}
|
||||
try_yuv();
|
||||
}
|
||||
|
||||
log.console("\nColorspace conversion test complete!");
|
||||
$stop();
|
||||
$stop();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Test webcam display
|
||||
var draw2d
|
||||
var graphics
|
||||
var draw2d = null
|
||||
var graphics = null
|
||||
var os = use('os');
|
||||
var input = use('input')
|
||||
var json = use('json')
|
||||
@@ -88,7 +88,8 @@ send(video_actor, {
|
||||
|
||||
// Look for a 640x480 format with preferred colorspace
|
||||
var preferred_format = null;
|
||||
for (var i = 0; i < length(formats); i++) {
|
||||
var i = 0;
|
||||
for (i = 0; i < length(formats); i++) {
|
||||
if (formats[i].width == 640 && formats[i].height == 480) {
|
||||
preferred_format = formats[i];
|
||||
// Prefer JPEG or sRGB colorspace if available
|
||||
|
||||
16
transform.c
16
transform.c
@@ -182,16 +182,6 @@ transform mat2transform(HMM_Mat4 m)
|
||||
|
||||
// Transform class definitions
|
||||
JSClassID js_transform_id;
|
||||
static void js_transform_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {
|
||||
transform *t = JS_GetOpaque(val, js_transform_id);
|
||||
if (!t) return;
|
||||
// Mark the JSValue references stored in your struct
|
||||
JS_MarkValue(rt, t->change_hook, mark_func);
|
||||
JS_MarkValue(rt, t->jsparent, mark_func);
|
||||
// Mark the array elements
|
||||
for (int i = 0; i < arrlen(t->jschildren); i++)
|
||||
JS_MarkValue(rt, t->jschildren[i], mark_func);
|
||||
}
|
||||
|
||||
QJSCLASSMARK_EXTERN(transform,)
|
||||
|
||||
@@ -241,7 +231,7 @@ static JSValue js_transform_set_parent(JSContext *js, JSValueConst self, JSValue
|
||||
}
|
||||
|
||||
for (int i = 0; i < arrlen(cur_parent->jschildren); i++) {
|
||||
if (JS_SameValue(js,cur_parent->jschildren[i],self)) {
|
||||
if (JS_StrictEq(js,cur_parent->jschildren[i],self)) {
|
||||
JS_FreeValue(js,cur_parent->jschildren[i]);
|
||||
arrdelswap(cur_parent->jschildren,i);
|
||||
break;
|
||||
@@ -341,7 +331,7 @@ JSC_CCALL(transform_array,
|
||||
HMM_Mat4 m= transform2mat(t);
|
||||
ret = JS_NewArray(js);
|
||||
for (int i = 0; i < 16; i++)
|
||||
JS_SetPropertyUint32(js,ret,i, number2js(js,m.em[i]));
|
||||
JS_SetPropertyNumber(js,ret,i, number2js(js,m.em[i]));
|
||||
)
|
||||
|
||||
JSC_CCALL(transform_torect,
|
||||
@@ -353,7 +343,7 @@ JSC_CCALL(transform_children,
|
||||
transform *t = js2transform(js,self);
|
||||
ret = JS_NewArray(js);
|
||||
for (int i = 0; i < arrlen(t->jschildren); i++)
|
||||
JS_SetPropertyUint32(js,ret,i,JS_DupValue(js,t->jschildren[i]));
|
||||
JS_SetPropertyNumber(js,ret,i,JS_DupValue(js,t->jschildren[i]));
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_transform_funcs[] = {
|
||||
|
||||
20
tween.cm
20
tween.cm
@@ -14,11 +14,12 @@ function make_engine(default_clock) {
|
||||
this.tweens = filter(this.tweens, t => t != tween)
|
||||
},
|
||||
update(current_time) {
|
||||
if (current_time == null) {
|
||||
current_time = this.default_clock ? this.default_clock() : time.number()
|
||||
var ct = current_time
|
||||
if (ct == null) {
|
||||
ct = this.default_clock ? this.default_clock() : time.number()
|
||||
}
|
||||
arrfor(this.tweens, function(tween) {
|
||||
tween._update(current_time)
|
||||
tween._update(ct)
|
||||
})
|
||||
},
|
||||
clear() {
|
||||
@@ -47,7 +48,7 @@ var TweenProto = {
|
||||
if (is_object(value)) {
|
||||
arrfor(array(value), subkey => {
|
||||
var flatKey = key + '.' + subkey
|
||||
this.startVals[flatKey] = this.obj[key] ? this.obj[key][subkey] : undefined
|
||||
this.startVals[flatKey] = this.obj[key] ? this.obj[key][subkey] : null
|
||||
this.endVals[flatKey] = value[subkey]
|
||||
})
|
||||
} else {
|
||||
@@ -87,7 +88,7 @@ var TweenProto = {
|
||||
|
||||
_update: function(now) {
|
||||
this.seek(now)
|
||||
this.onUpdateCallback?.()
|
||||
if (this.onUpdateCallback) this.onUpdateCallback()
|
||||
},
|
||||
|
||||
seek: function(global_time) {
|
||||
@@ -99,11 +100,14 @@ var TweenProto = {
|
||||
var start = this.startVals[key]
|
||||
var end = this.endVals[key]
|
||||
var value = start + (end - start) * eased
|
||||
var parts = null
|
||||
var objKey = null
|
||||
var subKey = null
|
||||
|
||||
if (search(key, '.') != null) {
|
||||
var parts = array(key, '.')
|
||||
var objKey = parts[0]
|
||||
var subKey = parts[1]
|
||||
parts = array(key, '.')
|
||||
objKey = parts[0]
|
||||
subKey = parts[1]
|
||||
|
||||
if (!this.obj[objKey]) {
|
||||
this.obj[objKey] = {}
|
||||
|
||||
Reference in New Issue
Block a user