misty
This commit is contained in:
32
action.cm
32
action.cm
@@ -29,8 +29,8 @@ action.get_icon_for_action = function(action)
|
||||
var primary_binding = bindings[0]
|
||||
|
||||
if (this.current_device == 'keyboard') {
|
||||
if (primary_binding.startsWith('mouse_button_')) {
|
||||
var button = primary_binding.replace('mouse_button_', '')
|
||||
if (starts_with(primary_binding, 'mouse_button_')) {
|
||||
var button = replace(primary_binding, 'mouse_button_', '')
|
||||
return 'ui/mouse/mouse_' + button + '.png'
|
||||
} else {
|
||||
// Handle special keyboard keys
|
||||
@@ -132,10 +132,10 @@ default_action_map.accion = 'y'
|
||||
|
||||
// Utility to detect device from input id
|
||||
function detect_device(input_id) {
|
||||
if (input_id.startsWith('gamepad_')) return 'gamepad'
|
||||
if (input_id.startsWith('swipe_')) return 'touch'
|
||||
if (input_id.startsWith('mouse_button')) return 'keyboard' // Mouse buttons are part of keyboard/mouse controller
|
||||
if (input_id.startsWith('touch_')) return 'touch'
|
||||
if (starts_with(input_id, 'gamepad_')) return 'gamepad'
|
||||
if (starts_with(input_id, 'swipe_')) return 'touch'
|
||||
if (starts_with(input_id, 'mouse_button')) return 'keyboard' // Mouse buttons are part of keyboard/mouse controller
|
||||
if (starts_with(input_id, 'touch_')) return 'touch'
|
||||
return 'keyboard'
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ action.current_gamepad_type = null
|
||||
|
||||
// Copy defaults
|
||||
for (var key in default_action_map) {
|
||||
action.action_map[key] = default_action_map[key].slice()
|
||||
action.action_map[key] = array(default_action_map[key])
|
||||
}
|
||||
|
||||
// Swipe‐recognizer state & tuning
|
||||
@@ -181,14 +181,14 @@ 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)
|
||||
if (action_id != 'mouse_move' && !action_id.startsWith('mouse_pos') && evt.pressed) {
|
||||
if (action_id != 'mouse_move' && !starts_with(action_id, 'mouse_pos') && evt.pressed) {
|
||||
var 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 ||
|
||||
action_id.startsWith('mouse_button')))
|
||||
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
|
||||
@@ -225,7 +225,7 @@ action.on_input = function(action_id, evt)
|
||||
// 3) Otherwise, find all mapped actions for this input
|
||||
var matched_actions = []
|
||||
for (var mapped_action in this.action_map) {
|
||||
if (this.action_map[mapped_action].includes(action_id)) {
|
||||
if (find(this.action_map[mapped_action], action_id) != null) {
|
||||
matched_actions.push(mapped_action)
|
||||
|
||||
if (evt.pressed)
|
||||
@@ -254,8 +254,8 @@ action.rebind_action = function(action_name, new_key) {
|
||||
|
||||
// Remove this key from all other actions
|
||||
for (var act in this.action_map) {
|
||||
var idx = this.action_map[act].indexOf(new_key)
|
||||
if (idx >= 0)
|
||||
var idx = find(this.action_map[act], new_key)
|
||||
if (idx != null)
|
||||
this.action_map[act].splice(idx, 1)
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ action.rebind_action = function(action_name, new_key) {
|
||||
action.get_bindings_for_device = function(action_name) {
|
||||
var all = this.action_map[action_name] || []
|
||||
var self = this
|
||||
return all.filter(function(id) { return detect_device(id) == self.current_device })
|
||||
return filter(all, function(id) { return detect_device(id) == self.current_device })
|
||||
}
|
||||
|
||||
// Returns the primary binding for display - prefer keyboard/mouse, then others
|
||||
@@ -284,7 +284,7 @@ action.get_primary_binding = function(action_name) {
|
||||
if (!all.length) return '(unbound)'
|
||||
|
||||
// Prefer keyboard/mouse bindings for display stability (mouse buttons now detect as keyboard)
|
||||
var keyboard = all.filter(function(id) { return detect_device(id) == 'keyboard' })
|
||||
var keyboard = filter(all, function(id) { return detect_device(id) == 'keyboard' })
|
||||
if (keyboard.length) return keyboard[0]
|
||||
|
||||
// Fall back to any binding
|
||||
@@ -328,7 +328,7 @@ action.load_bindings = function() {
|
||||
|
||||
action.reset_to_defaults = function() {
|
||||
for (var key in default_action_map) {
|
||||
this.action_map[key] = default_action_map[key].slice()
|
||||
this.action_map[key] = array(default_action_map[key])
|
||||
}
|
||||
this.save_bindings()
|
||||
}
|
||||
@@ -344,7 +344,7 @@ return function()
|
||||
obj.current_gamepad_type = null
|
||||
// Copy defaults
|
||||
for (var key in default_action_map) {
|
||||
obj.action_map[key] = default_action_map[key].slice()
|
||||
obj.action_map[key] = array(default_action_map[key])
|
||||
}
|
||||
obj.load_bindings()
|
||||
return obj
|
||||
|
||||
12
clay.cm
12
clay.cm
@@ -116,12 +116,12 @@ function build_drawables(node, root_height, parent_abs_x, parent_abs_y, parent_s
|
||||
|
||||
// Intersect with parent
|
||||
if (parent_scissor) {
|
||||
sx = number.max(sx, parent_scissor.x)
|
||||
sy = number.max(sy, parent_scissor.y)
|
||||
var right = number.min(vis_x + sw, parent_scissor.x + parent_scissor.width)
|
||||
var bottom = number.min(vis_y + sh, parent_scissor.y + parent_scissor.height)
|
||||
sw = number.max(0, right - sx)
|
||||
sh = number.max(0, bottom - sy)
|
||||
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)
|
||||
}
|
||||
|
||||
current_scissor = {x: sx, y: sy, width: sw, height: sh}
|
||||
|
||||
24
color.cm
24
color.cm
@@ -1,7 +1,7 @@
|
||||
function tohex(n) {
|
||||
var s = number.floor(n).toString(16);
|
||||
var s = floor(n).toString(16);
|
||||
if (s.length == 1) s = "0" + s;
|
||||
return s.toUpperCase();
|
||||
return upper(s);
|
||||
};
|
||||
|
||||
var Color = {
|
||||
@@ -22,19 +22,15 @@ Color.editor = {};
|
||||
Color.editor.ur = Color.green;
|
||||
|
||||
Color.tohtml = function (v) {
|
||||
var html = v.map(function (n) {
|
||||
return tohex(n * 255);
|
||||
});
|
||||
return "#" + html.join("");
|
||||
var html = array(v, n => tohex(n * 255));
|
||||
return "#" + text(html);
|
||||
};
|
||||
|
||||
var esc = {};
|
||||
esc.reset = "\x1b[0";
|
||||
esc.color = function (v) {
|
||||
var c = v.map(function (n) {
|
||||
return number.floor(n * 255);
|
||||
});
|
||||
var truecolor = "\x1b[38;2;" + c.join(";") + ";";
|
||||
var c = array(v, n => floor(n * 255));
|
||||
var truecolor = "\x1b[38;2;" + text(c, ";") + ";";
|
||||
return truecolor;
|
||||
};
|
||||
|
||||
@@ -92,7 +88,7 @@ Color.Editor = {
|
||||
/* Detects the format of all colors and munges them into a floating point format */
|
||||
Color.normalize = function (c) {
|
||||
var add_a = function (a) {
|
||||
var n = this.slice();
|
||||
var n = array(this);
|
||||
n[3] = a;
|
||||
return n;
|
||||
};
|
||||
@@ -120,9 +116,7 @@ Color.normalize = function (c) {
|
||||
|
||||
// Convert from 0-255 to 0-1 if needed
|
||||
if (needs_conversion) {
|
||||
c[p] = c[p].map(function (x) {
|
||||
return x / 255;
|
||||
});
|
||||
c[p] = array(c[p], x => x / 255);
|
||||
}
|
||||
|
||||
c[p].alpha = add_a;
|
||||
@@ -192,7 +186,7 @@ ColorMap.sample = function (t, map = this) {
|
||||
if (t > 1) return map[1];
|
||||
|
||||
var lastkey = 0;
|
||||
for (var key of array(map).sort()) {
|
||||
for (var key of sorted(array(map))) {
|
||||
if (t < key) {
|
||||
var b = map[key];
|
||||
var a = map[lastkey];
|
||||
|
||||
@@ -353,16 +353,16 @@ function _calc_presentation(src, dst, mode) {
|
||||
return {x: 0, y: 0, width: dst.width, height: dst.height}
|
||||
|
||||
if (mode == 'integer_scale') {
|
||||
var sx = number.floor(dst.width / src.width)
|
||||
var sy = number.floor(dst.height / src.height)
|
||||
var s = number.max(1, number.min(sx, sy))
|
||||
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
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
}
|
||||
|
||||
// letterbox
|
||||
var scale = number.min(dst.width / src.width, dst.height / src.height)
|
||||
var scale = min(dst.width / src.width, dst.height / src.height)
|
||||
var w = src.width * scale
|
||||
var h = src.height * scale
|
||||
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
|
||||
|
||||
@@ -6,7 +6,7 @@ var downkeys = {};
|
||||
function keyname(key)
|
||||
{
|
||||
var str = input.keyname(key);
|
||||
return str.toLowerCase();
|
||||
return lower(str);
|
||||
}
|
||||
|
||||
function modstr(mod = input.keymod()) {
|
||||
@@ -66,7 +66,7 @@ prosperon.on('mouse_button_up', function(e) {
|
||||
|
||||
input.mouse = {};
|
||||
input.mouse.screenpos = function mouse_screenpos() {
|
||||
return mousepos.slice();
|
||||
return array(mousepos);
|
||||
};
|
||||
input.mouse.worldpos = function mouse_worldpos() {
|
||||
return prosperon.camera.screen2world(mousepos);
|
||||
@@ -75,7 +75,7 @@ input.mouse.viewpos = function mouse_viewpos()
|
||||
{
|
||||
var world = input.mouse.worldpos();
|
||||
|
||||
return mousepos.slice();
|
||||
return array(world)
|
||||
}
|
||||
input.mouse.disabled = function mouse_disabled() {
|
||||
input.mouse_mode(1);
|
||||
@@ -109,7 +109,7 @@ input.mouse.normal.doc = "Set the mouse to show again after hiding.";
|
||||
input.keyboard = {};
|
||||
input.keyboard.down = function (code) {
|
||||
if (is_number(code)) return downkeys[code];
|
||||
if (is_text(code)) return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
|
||||
if (is_text(code)) return downkeys[codepoint(upper(code))] || downkeys[codepoint(lower(code))];
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -175,7 +175,7 @@ input.action = {
|
||||
|
||||
input.tabcomplete = function tabcomplete(val, list) {
|
||||
if (!val) return val;
|
||||
list = filter(x => x.startsWith(val))
|
||||
list = filter(list, x => starts_with(x, val))
|
||||
|
||||
if (list.length == 1) {
|
||||
return list[0];
|
||||
@@ -186,14 +186,12 @@ input.tabcomplete = function tabcomplete(val, list) {
|
||||
while (!ret && list.length != 0) {
|
||||
var char = list[0][i];
|
||||
if (
|
||||
!list.every(function (x) {
|
||||
return x[i] == char;
|
||||
})
|
||||
!every(list, x => x[i] == char)
|
||||
)
|
||||
ret = list[0].slice(0, i);
|
||||
ret = text(list[0], 0, i);
|
||||
else {
|
||||
i++;
|
||||
list = list.filter(x => x.length-1 > i)
|
||||
list = filter(list, x => x.length-1 > i)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,13 +207,13 @@ input.tabcomplete = function tabcomplete(val, list) {
|
||||
var Player = {
|
||||
players: [],
|
||||
input(fn, ...args) {
|
||||
this.pawns.forEach(x => x[fn]?.(...args));
|
||||
arrfor(this.pawns, x => x[fn]?.(...args));
|
||||
},
|
||||
|
||||
mouse_input(type, ...args) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
for (var pawn of reverse([...this.pawns])) {
|
||||
if (is_function(pawn.inputs?.mouse?.[type])) {
|
||||
pawn.inputs.mouse[type].call(pawn, ...args);
|
||||
call(pawn.inputs.mouse[type], pawn, ...args);
|
||||
pawn.inputs.post?.call(pawn);
|
||||
if (!pawn.inputs.fallthru) return;
|
||||
}
|
||||
@@ -223,9 +221,9 @@ var Player = {
|
||||
},
|
||||
|
||||
char_input(c) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
for (var pawn of reverse([...this.pawns])) {
|
||||
if (is_function(pawn.inputs?.char)) {
|
||||
pawn.inputs.char.call(pawn, c);
|
||||
call(pawn.inputs.char, pawn, c);
|
||||
pawn.inputs.post?.call(pawn);
|
||||
if (!pawn.inputs.fallthru) return;
|
||||
}
|
||||
@@ -233,7 +231,7 @@ var Player = {
|
||||
},
|
||||
|
||||
joy_input(name, joystick) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
for (var pawn of reverse([...this.pawns])) {
|
||||
if (!pawn.inputs) return;
|
||||
if (!pawn.inputs.joystick) return;
|
||||
if (!pawn.inputs.joystick[name]) return;
|
||||
@@ -250,7 +248,7 @@ var Player = {
|
||||
},
|
||||
|
||||
raw_input(cmd, state, ...args) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
for (var pawn of reverse([...this.pawns])) {
|
||||
var inputs = pawn.inputs;
|
||||
|
||||
if (!inputs[cmd]) {
|
||||
@@ -277,10 +275,10 @@ var Player = {
|
||||
|
||||
var consumed = false;
|
||||
if (is_function(fn)) {
|
||||
fn.call(pawn, ...args);
|
||||
call(fn, pawn, ...args);
|
||||
consumed = true;
|
||||
}
|
||||
if (state == "released") inputs.release_post?.call(pawn);
|
||||
if (state == "released") call(inputs.release_post, pawn);
|
||||
if (inputs.block) return;
|
||||
if (consumed) return;
|
||||
}
|
||||
@@ -295,12 +293,12 @@ var Player = {
|
||||
},
|
||||
|
||||
print_pawns() {
|
||||
[...this.pawns].reverse().forEach(x => log.console(x))
|
||||
arrfor(reverse([...this.pawns]), x => log.console(x))
|
||||
},
|
||||
|
||||
create() {
|
||||
var n = meme(this);
|
||||
n.pawns = new Set()
|
||||
n.pawns = {}
|
||||
n.gamepads = [];
|
||||
this.players.push(n);
|
||||
this[this.players.length - 1] = n;
|
||||
@@ -314,18 +312,18 @@ var Player = {
|
||||
if (!pawn.inputs)
|
||||
throw Error("attempted to control a pawn without any input object.");
|
||||
|
||||
this.pawns.add(pawn);
|
||||
this.pawns[pawn] = true
|
||||
},
|
||||
|
||||
uncontrol(pawn) {
|
||||
this.pawns.delete(pawn)
|
||||
delete this.pawns[pawn]
|
||||
},
|
||||
};
|
||||
|
||||
input.do_uncontrol = function input_do_uncontrol(pawn) {
|
||||
if (!pawn.inputs) return;
|
||||
Player.players.forEach(function (p) {
|
||||
p.pawns.delete(pawn)
|
||||
arrfor(Player.players, function (p) {
|
||||
delete p.pawns[pawn]
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
7
core.cm
7
core.cm
@@ -97,6 +97,9 @@ function _main_loop() {
|
||||
|
||||
var win_size = _backend.get_window_size()
|
||||
|
||||
// Get input module for auto-ingestion
|
||||
var input_mod = use('input')
|
||||
|
||||
for (var ev of evts) {
|
||||
if (_config.imgui || _config.editor) {
|
||||
imgui.process_event(ev)
|
||||
@@ -117,6 +120,10 @@ function _main_loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-ingest through input system
|
||||
input_mod.ingest(ev_obj)
|
||||
|
||||
// Optional raw hook for debugging
|
||||
if (_config.input) {
|
||||
_config.input(ev_obj)
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ function _render_node_summary(imgui, node) {
|
||||
var info = []
|
||||
|
||||
if (node.pos) {
|
||||
info.push("pos:(" + text(number.round(node.pos.x)) + "," + text(number.round(node.pos.y)) + ")")
|
||||
info.push("pos:(" + text(round(node.pos.x)) + "," + text(round(node.pos.y)) + ")")
|
||||
}
|
||||
|
||||
if (node.width && node.height) {
|
||||
@@ -156,7 +156,7 @@ function _render_node_summary(imgui, node) {
|
||||
|
||||
if (node.text) {
|
||||
var t = node.text
|
||||
if (t.length > 20) t = t.substring(0, 17) + "..."
|
||||
if (t.length > 20) t = text(t, 0, 17) + "..."
|
||||
info.push("\"" + t + "\"")
|
||||
}
|
||||
|
||||
@@ -165,11 +165,11 @@ function _render_node_summary(imgui, node) {
|
||||
for (var i = 0; i < node.effects.length; i++) {
|
||||
fx.push(node.effects[i].type)
|
||||
}
|
||||
info.push("fx:[" + fx.join(",") + "]")
|
||||
info.push("fx:[" + text(fx, ",") + "]")
|
||||
}
|
||||
|
||||
if (info.length > 0) {
|
||||
imgui.text(" " + info.join(" "))
|
||||
imgui.text(" " + text(info, " "))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,10 +221,10 @@ function _render_node_inspector(imgui, node) {
|
||||
// World transform (read-only)
|
||||
if (node.world_pos) {
|
||||
imgui.text("---")
|
||||
imgui.text("World Position: (" + text(number.round(node.world_pos.x * 100) / 100) + ", " + text(number.round(node.world_pos.y * 100) / 100) + ")")
|
||||
imgui.text("World Position: (" + text(round(node.world_pos.x * 100) / 100) + ", " + text(round(node.world_pos.y * 100) / 100) + ")")
|
||||
}
|
||||
if (node.world_opacity != null) {
|
||||
imgui.text("World Opacity: " + text(number.round(node.world_opacity * 100) / 100))
|
||||
imgui.text("World Opacity: " + text(round(node.world_opacity * 100) / 100))
|
||||
}
|
||||
|
||||
// Image
|
||||
@@ -360,8 +360,8 @@ function _render_pass_inspector(imgui, pass) {
|
||||
imgui.text("Uniforms:")
|
||||
for (var k in pass.uniforms) {
|
||||
var v = pass.uniforms[k]
|
||||
if (Array.isArray(v)) {
|
||||
imgui.text(" " + k + ": [" + v.join(", ") + "]")
|
||||
if (is_array(v)) {
|
||||
imgui.text(" " + k + ": [" + text(v, ", ") + "]")
|
||||
} else {
|
||||
imgui.text(" " + k + ": " + text(v))
|
||||
}
|
||||
@@ -372,10 +372,10 @@ function _render_pass_inspector(imgui, pass) {
|
||||
if (pass.color) {
|
||||
imgui.text("---")
|
||||
imgui.text("Clear: rgba(" +
|
||||
text(number.round(pass.color.r * 255)) + "," +
|
||||
text(number.round(pass.color.g * 255)) + "," +
|
||||
text(number.round(pass.color.b * 255)) + "," +
|
||||
text(number.round(pass.color.a * 100) / 100) + ")")
|
||||
text(round(pass.color.r * 255)) + "," +
|
||||
text(round(pass.color.g * 255)) + "," +
|
||||
text(round(pass.color.b * 255)) + "," +
|
||||
text(round(pass.color.a * 100) / 100) + ")")
|
||||
}
|
||||
|
||||
// Source size
|
||||
@@ -449,8 +449,8 @@ function _render_stats(imgui, stats) {
|
||||
|
||||
if (stats.fps) {
|
||||
imgui.text("---")
|
||||
imgui.text("FPS: " + text(number.round(stats.fps)))
|
||||
imgui.text("Frame time: " + text(number.round(stats.frame_time_ms * 100) / 100) + " ms")
|
||||
imgui.text("FPS: " + text(round(stats.fps)))
|
||||
imgui.text("Frame time: " + text(round(stats.frame_time_ms * 100) / 100) + " ms")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
2
ease.cm
2
ease.cm
@@ -130,7 +130,7 @@ Ease.zoom = {
|
||||
return function(t) {
|
||||
if (t == 0) return 0
|
||||
if (t == 1) return 1
|
||||
if (number.abs(ratio - 1) < 0.001) return t
|
||||
if (abs(ratio - 1) < 0.001) return t
|
||||
// Position interpolation formula: (r^t - 1) / (r - 1)
|
||||
return (math.power(ratio, t) - 1) / (ratio - 1)
|
||||
}
|
||||
|
||||
4
emacs.cm
4
emacs.cm
@@ -15,7 +15,7 @@ action.on_input = function(action_id, action_data)
|
||||
{
|
||||
if (!action_data.pressed) return
|
||||
|
||||
if (!valid_keys.includes(action_id))
|
||||
if (find(valid_keys, action_id) == null)
|
||||
return
|
||||
|
||||
// Only process key events with modifiers or if we're waiting for a prefix continuation
|
||||
@@ -35,7 +35,7 @@ action.on_input = function(action_id, action_data)
|
||||
var key = action_id
|
||||
if (key.length == 1) {
|
||||
// Single character keys
|
||||
emacs_notation += key.toLowerCase()
|
||||
emacs_notation += lower(key)
|
||||
} else {
|
||||
// Handle special keys
|
||||
switch (key) {
|
||||
|
||||
@@ -72,7 +72,7 @@ function handleMouseButtonDown(e) {
|
||||
var mx = e.mouse.x;
|
||||
var my = e.mouse.y;
|
||||
|
||||
var c = [number.floor(mx / 60), number.floor(my / 60)];
|
||||
var c = [floor(mx / 60), floor(my / 60)];
|
||||
if (!grid.inBounds(c)) return;
|
||||
|
||||
var cell = grid.at(c);
|
||||
@@ -103,7 +103,7 @@ function handleMouseButtonUp(e) {
|
||||
var mx = e.mouse.x;
|
||||
var my = e.mouse.y;
|
||||
|
||||
var c = [number.floor(mx / 60), number.floor(my / 60)];
|
||||
var c = [floor(mx / 60), floor(my / 60)];
|
||||
if (!grid.inBounds(c)) {
|
||||
holdingPiece = false;
|
||||
return;
|
||||
@@ -138,7 +138,7 @@ function handleMouseMotion(e) {
|
||||
var mx = e.pos.x;
|
||||
var my = e.pos.y;
|
||||
|
||||
var c = [number.floor(mx / 60), number.floor(my / 60)];
|
||||
var c = [floor(mx / 60), floor(my / 60)];
|
||||
if (!grid.inBounds(c)) {
|
||||
hoverPos = null;
|
||||
return;
|
||||
|
||||
@@ -25,7 +25,7 @@ grid.prototype = {
|
||||
// add an entity into a cell
|
||||
add(entity, pos) {
|
||||
this.cell(pos.x, pos.y).push(entity);
|
||||
entity.coord = pos.slice();
|
||||
entity.coord = array(pos);
|
||||
},
|
||||
|
||||
// remove an entity from a cell
|
||||
|
||||
@@ -7,7 +7,7 @@ function Piece(kind, colour) {
|
||||
this.coord = [0,0];
|
||||
}
|
||||
Piece.prototype.toString = function () {
|
||||
return this.colour.charAt(0) + this.kind.charAt(0).toUpperCase();
|
||||
return character(this.colour) + upper(character(this.kind));
|
||||
};
|
||||
|
||||
function startingPosition(grid) {
|
||||
|
||||
@@ -11,20 +11,20 @@ var deltas = {
|
||||
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
|
||||
grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir }).length == 0 &&
|
||||
grid.at(to).length == 0);
|
||||
var cap = (dy == dir && number.abs(dx) == 1 && grid.at(to).length);
|
||||
var cap = (dy == dir && Math.abs(dx) == 1 && grid.at(to).length);
|
||||
return one || two || cap;
|
||||
},
|
||||
rook : function (pc, dx, dy) { return (dx == 0 || dy == 0); },
|
||||
bishop: function (pc, dx, dy) { return number.abs(dx) == number.abs(dy); },
|
||||
queen : function (pc, dx, dy) { return (dx == 0 || dy == 0 || number.abs(dx) == number.abs(dy)); },
|
||||
knight: function (pc, dx, dy) { return (number.abs(dx) == 1 && number.abs(dy) == 2) ||
|
||||
(number.abs(dx) == 2 && number.abs(dy) == 1); },
|
||||
king : function (pc, dx, dy) { return number.max(number.abs(dx), number.abs(dy)) == 1; }
|
||||
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; }
|
||||
};
|
||||
|
||||
function clearLine(from, to, grid) {
|
||||
var dx = number.sign(cx(to) - cx(from));
|
||||
var dy = number.sign(cy(to) - cy(from));
|
||||
var dx = Math.sign(cx(to) - cx(from));
|
||||
var dy = Math.sign(cy(to) - cy(from));
|
||||
var x = cx(from) + dx, y = cy(from) + dy;
|
||||
while (x != cx(to) || y != cy(to)) {
|
||||
if (grid.at({ x: x, y: y }).length) return false;
|
||||
|
||||
@@ -60,10 +60,10 @@ this.update = function(dt) {
|
||||
|
||||
// Collide with paddle 1?
|
||||
if (r>left1 && l<right1 && t>bottom1 && b<top1)
|
||||
ball.vx = number.abs(ball.vx)
|
||||
ball.vx = abs(ball.vx)
|
||||
// Collide with paddle 2?
|
||||
if (r>left2 && l<right2 && t>bottom2 && b<top2)
|
||||
ball.vx = -number.abs(ball.vx)
|
||||
ball.vx = -abs(ball.vx)
|
||||
|
||||
// Check left/right out-of-bounds
|
||||
if (r<0) { score2++; resetBall() }
|
||||
|
||||
@@ -10,8 +10,8 @@ var random = use('random')
|
||||
prosperon.camera.transform.pos = [0,0]
|
||||
|
||||
var cellSize = 20
|
||||
var gridW = number.floor(config.width / cellSize)
|
||||
var gridH = number.floor(config.height / cellSize)
|
||||
var gridW = floor(config.width / cellSize)
|
||||
var gridH = floor(config.height / cellSize)
|
||||
|
||||
var snake, direction, nextDirection, apple
|
||||
var moveInterval = 0.1
|
||||
@@ -19,8 +19,8 @@ var moveTimer = 0
|
||||
var gameState = "playing"
|
||||
|
||||
function resetGame() {
|
||||
var cx = number.floor(gridW / 2)
|
||||
var cy = number.floor(gridH / 2)
|
||||
var cx = floor(gridW / 2)
|
||||
var cy = floor(gridH / 2)
|
||||
snake = [
|
||||
{x: cx, y: cy},
|
||||
{x: cx-1, y: cy},
|
||||
@@ -34,7 +34,7 @@ function resetGame() {
|
||||
}
|
||||
|
||||
function spawnApple() {
|
||||
apple = {x:number.floor(random.random()*gridW), y:number.floor(random.random()*gridH)}
|
||||
apple = {x:floor(random.random()*gridW), y:floor(random.random()*gridH)}
|
||||
// Re-spawn if apple lands on snake
|
||||
for (var i=0; i<snake.length; i++)
|
||||
if (snake[i].x == apple.x && snake[i].y == apple.y) { spawnApple(); return }
|
||||
|
||||
@@ -65,12 +65,12 @@ function initBoard() {
|
||||
initBoard()
|
||||
|
||||
function randomShape() {
|
||||
var key = shapeKeys[number.floor(random.random()*shapeKeys.length)]
|
||||
var key = shapeKeys[floor(random.random()*shapeKeys.length)]
|
||||
// Make a copy of the shape’s blocks
|
||||
return {
|
||||
type: key,
|
||||
color: SHAPES[key].color,
|
||||
blocks: SHAPES[key].blocks.map(b => [b[0], b[1]])
|
||||
blocks: array(SHAPES[key].blocks, b => [b[0], b[1]])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ function rotate(blocks) {
|
||||
function clearLines() {
|
||||
var lines = 0
|
||||
for (var r=ROWS-1; r>=0;) {
|
||||
if (board[r].every(cell => cell)) {
|
||||
if (every(board[r], cell => cell)) {
|
||||
lines++
|
||||
// remove row
|
||||
board.splice(r,1)
|
||||
@@ -134,7 +134,7 @@ function clearLines() {
|
||||
else if (lines==3) score += 500
|
||||
else if (lines==4) score += 800
|
||||
linesCleared += lines
|
||||
level = number.floor(linesCleared/10)
|
||||
level = floor(linesCleared/10)
|
||||
}
|
||||
|
||||
function placePiece() {
|
||||
@@ -198,7 +198,7 @@ this.update = function(dt) {
|
||||
if (input.keyboard.down('w')) {
|
||||
if (!rotateHeld) {
|
||||
rotateHeld = true
|
||||
var test = piece.blocks.map(b => [b[0], b[1]])
|
||||
var test = array(piece.blocks, b => [b[0], b[1]])
|
||||
rotate(test)
|
||||
if (!collides(pieceX, pieceY, test)) piece.blocks = test
|
||||
}
|
||||
@@ -211,7 +211,7 @@ this.update = function(dt) {
|
||||
|
||||
// Gravity
|
||||
gravityTimer += dt * fallSpeed
|
||||
var dropInterval = number.max(0.1, baseGravity - level*0.05)
|
||||
var dropInterval = max(0.1, baseGravity - level*0.05)
|
||||
if (gravityTimer >= dropInterval) {
|
||||
gravityTimer = 0
|
||||
if (!collides(pieceX, pieceY+1, piece.blocks)) {
|
||||
|
||||
223
film2d.cm
223
film2d.cm
@@ -1,10 +1,115 @@
|
||||
var film2d = {}
|
||||
var backend = null
|
||||
|
||||
film2d.set_backend = function(b) {
|
||||
backend = b
|
||||
}
|
||||
|
||||
var next_id = 1
|
||||
var registry = {} // id -> drawable
|
||||
var group_index = {} // group_name -> [id, id, ...]
|
||||
var plane_index = {} // plane_name -> [id, id, ...]
|
||||
|
||||
// Resolve sprite dimensions and UV based on fit mode
|
||||
// fit: 'fill' | 'contain' | 'cover' | 'none'
|
||||
// - fill: stretch to exactly match box (distort)
|
||||
// - contain: fit inside box, preserve aspect (letterbox)
|
||||
// - cover: fill box, preserve aspect (crop)
|
||||
// - none: use native pixel size (or pixels_per_tile driven)
|
||||
function _resolve_sprite_fit(sprite) {
|
||||
if (!backend || !backend.get_texture_info) return sprite
|
||||
|
||||
var img = sprite.texture || sprite.image
|
||||
if (!img) return sprite
|
||||
|
||||
var tex_info = backend.get_texture_info(img)
|
||||
if (!tex_info || !tex_info.width || !tex_info.height) return sprite
|
||||
|
||||
var tex_w = tex_info.width
|
||||
var tex_h = tex_info.height
|
||||
var tex_aspect = tex_w / tex_h
|
||||
|
||||
var target_w = sprite.width
|
||||
var target_h = sprite.height
|
||||
var fit = sprite.fit || 'none'
|
||||
|
||||
// If one dimension is null, derive from aspect ratio
|
||||
if (target_w == null && target_h != null) {
|
||||
target_w = target_h * tex_aspect
|
||||
sprite.width = target_w
|
||||
sprite.height = target_h
|
||||
return sprite
|
||||
}
|
||||
if (target_h == null && target_w != null) {
|
||||
target_h = target_w / tex_aspect
|
||||
sprite.width = target_w
|
||||
sprite.height = target_h
|
||||
return sprite
|
||||
}
|
||||
|
||||
// Both null - use native size (1 pixel = 1 unit, or could use pixels_per_tile)
|
||||
if (target_w == null && target_h == null) {
|
||||
sprite.width = tex_w
|
||||
sprite.height = tex_h
|
||||
return sprite
|
||||
}
|
||||
|
||||
// Both dimensions specified - apply fit mode
|
||||
var box_aspect = target_w / target_h
|
||||
|
||||
if (fit == 'fill') {
|
||||
// Stretch to fill - no changes needed, just use target dimensions
|
||||
sprite.width = target_w
|
||||
sprite.height = target_h
|
||||
return sprite
|
||||
}
|
||||
|
||||
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
|
||||
} else {
|
||||
// Image taller than box - constrain by height
|
||||
scale = target_h / tex_h
|
||||
}
|
||||
sprite.width = tex_w * scale
|
||||
sprite.height = tex_h * scale
|
||||
return sprite
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Compute visible portion of texture in UV space
|
||||
var visible_w = target_w / scale
|
||||
var visible_h = target_h / 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
|
||||
|
||||
sprite.width = target_w
|
||||
sprite.height = target_h
|
||||
sprite.uv_rect = {x: uv_x, y: uv_y, width: uv_w, height: uv_h}
|
||||
return sprite
|
||||
}
|
||||
|
||||
// fit == 'none' - use native size
|
||||
sprite.width = tex_w
|
||||
sprite.height = tex_h
|
||||
return sprite
|
||||
}
|
||||
|
||||
film2d.register = function(drawable) {
|
||||
var id = text(next_id++)
|
||||
drawable._id = id
|
||||
@@ -34,8 +139,8 @@ film2d.unregister = function(id) {
|
||||
// Remove from plane index
|
||||
var plane = drawable.plane || 'default'
|
||||
if (plane_index[plane]) {
|
||||
var idx = plane_index[plane].indexOf(id_str)
|
||||
if (idx >= 0) plane_index[plane].splice(idx, 1)
|
||||
var idx = find(plane_index[plane], id_str)
|
||||
if (idx != null) plane_index[plane].splice(idx, 1)
|
||||
}
|
||||
|
||||
// Remove from group indices
|
||||
@@ -43,8 +148,8 @@ film2d.unregister = function(id) {
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var g = groups[i]
|
||||
if (group_index[g]) {
|
||||
var idx = group_index[g].indexOf(id_str)
|
||||
if (idx >= 0) group_index[g].splice(idx, 1)
|
||||
var idx = find(group_index[g], id_str)
|
||||
if (idx != null) group_index[g].splice(idx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,73 +249,90 @@ film2d.all_groups = function() {
|
||||
}
|
||||
|
||||
// Render function - takes drawables directly, no tree traversal
|
||||
film2d.render = function(params, backend) {
|
||||
film2d.render = function(params, render_backend) {
|
||||
backend = render_backend
|
||||
|
||||
var drawables = params.drawables || []
|
||||
var camera = params.camera
|
||||
var target = params.target
|
||||
var target_size = params.target_size
|
||||
var clear_color = params.clear
|
||||
var layer_sort = params.layer_sort || {} // layer -> 'y' or 'explicit'
|
||||
|
||||
if (drawables.length == 0) return {commands: []}
|
||||
var layer_sort = params.layer_sort || {} // layer(text) -> "y" or "explicit"
|
||||
|
||||
function _y_sort_key(d) {
|
||||
if (drawables.length == 0) return { commands: [] }
|
||||
|
||||
function _y_sort_key(d) {
|
||||
if (!d || !d.pos) return 0
|
||||
var y = d.pos.y || 0
|
||||
var h = d.height || 0
|
||||
var ay = d.anchor_y
|
||||
if (ay == null) ay = 0.5
|
||||
// Convert "pos.y at anchor" -> "feet y"
|
||||
return y + h * (1 - ay)
|
||||
return y + h * (1 - ay) // "pos at anchor" -> "feet y"
|
||||
}
|
||||
|
||||
// Sort by layer, then optionally by Y based on layer_sort policy
|
||||
drawables.sort(function(a, b) {
|
||||
var al = a.layer || 0
|
||||
var bl = b.layer || 0
|
||||
var dl = al - bl
|
||||
if (dl != 0) return dl
|
||||
|
||||
var sort_mode = layer_sort[text(al)] || 'explicit'
|
||||
if (sort_mode == 'y') {
|
||||
var ay = _y_sort_key(a)
|
||||
var by = _y_sort_key(b)
|
||||
// Deterministic "explicit" order anchor (keep if you want _id tie-break behavior)
|
||||
drawables = sort(drawables, "_id")
|
||||
|
||||
// Make this explicit instead of guessing
|
||||
var y_down = camera && camera.y_down == true
|
||||
// Bucket drawables by layer
|
||||
var buckets = Object.create(null) // layer_text -> array
|
||||
for (var i = 0; i < drawables.length; i++) {
|
||||
var d = drawables[i]
|
||||
var layer_key = String(d.layer || 0)
|
||||
var b = buckets[layer_key]
|
||||
if (!b) {
|
||||
b = []
|
||||
buckets[layer_key] = b
|
||||
}
|
||||
b.push(d)
|
||||
}
|
||||
|
||||
// If y_down: bigger y is lower on screen => should draw later (on top)
|
||||
// If y_up: smaller y is lower on screen => should draw later (on top)
|
||||
if (ay != by) return y_down ? (ay - by) : (by - ay)
|
||||
// Sort layers numerically (keys are text)
|
||||
var layers = array(buckets) // text keys
|
||||
var layer_nums = array(layers, k => number(k))
|
||||
layers = sort(layers, layer_nums)
|
||||
|
||||
// Merge buckets, y-sorting buckets that request it
|
||||
var y_down = camera && camera.y_down == true
|
||||
var sorted_drawables = []
|
||||
|
||||
for (var li = 0; li < layers.length; li++) {
|
||||
var layer_key = layers[li]
|
||||
var b = buckets[layer_key]
|
||||
|
||||
var mode = layer_sort[layer_key] || "explicit"
|
||||
if (mode == "y") {
|
||||
var 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
|
||||
}
|
||||
|
||||
var aid = a._id || 0
|
||||
var bid = b._id || 0
|
||||
return aid < bid ? -1 : 1
|
||||
})
|
||||
|
||||
for (var j = 0; j < b.length; j++) sorted_drawables.push(b[j])
|
||||
}
|
||||
|
||||
drawables = sorted_drawables
|
||||
|
||||
var commands = []
|
||||
commands.push({cmd: 'begin_render', target: target, clear: clear_color})
|
||||
commands.push({cmd: 'set_camera', camera: camera})
|
||||
|
||||
commands.push({ cmd: "begin_render", target: target, clear: clear_color, target_size: target_size })
|
||||
commands.push({ cmd: "set_camera", camera: camera })
|
||||
|
||||
var batches = _batch_drawables(drawables)
|
||||
|
||||
|
||||
for (var i = 0; i < batches.length; i++) {
|
||||
var batch = batches[i]
|
||||
if (batch.type == 'sprite_batch')
|
||||
commands.push({cmd: 'draw_batch', batch_type: 'sprites', geometry: {sprites: batch.sprites}, texture: batch.texture, material: batch.material})
|
||||
else if (batch.type == 'mesh2d_batch')
|
||||
commands.push({cmd: 'draw_mesh2d', meshes: batch.meshes, texture: batch.texture, material: batch.material})
|
||||
else if (batch.type == 'text')
|
||||
commands.push({cmd: 'draw_text', drawable: batch.drawable})
|
||||
else if (batch.type == 'texture_ref')
|
||||
commands.push({cmd: 'draw_texture_ref', drawable: batch.drawable})
|
||||
else if (batch.type == 'shape')
|
||||
commands.push({cmd: 'draw_shape', drawable: batch.drawable})
|
||||
if (batch.type == "sprite_batch")
|
||||
commands.push({ cmd: "draw_batch", batch_type: "sprites", geometry: { sprites: batch.sprites }, texture: batch.texture, material: batch.material })
|
||||
else if (batch.type == "mesh2d_batch")
|
||||
commands.push({ cmd: "draw_mesh2d", meshes: batch.meshes, texture: batch.texture, material: batch.material })
|
||||
else if (batch.type == "text")
|
||||
commands.push({ cmd: "draw_text", drawable: batch.drawable })
|
||||
else if (batch.type == "texture_ref")
|
||||
commands.push({ cmd: "draw_texture_ref", drawable: batch.drawable })
|
||||
else if (batch.type == "shape")
|
||||
commands.push({ cmd: "draw_shape", drawable: batch.drawable })
|
||||
}
|
||||
|
||||
commands.push({cmd: 'end_render'})
|
||||
return {commands: commands}
|
||||
|
||||
commands.push({ cmd: "end_render" })
|
||||
return { commands: commands }
|
||||
}
|
||||
|
||||
function _batch_drawables(drawables) {
|
||||
@@ -222,6 +344,9 @@ function _batch_drawables(drawables) {
|
||||
var 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'}
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ NODE_EXECUTORS.clip_rect = function(params, backend) {
|
||||
if (!input) return {target: null, commands: []}
|
||||
|
||||
// Clip doesn't need a new target, just adds scissor to commands
|
||||
var commands = input.commands ? input.commands.slice() : []
|
||||
var commands = input.commands ? array(input.commands) : []
|
||||
|
||||
// Insert scissor after begin_render
|
||||
var insert_idx = 0
|
||||
|
||||
@@ -16,7 +16,7 @@ gesture.reset = function() {
|
||||
}
|
||||
|
||||
gesture.on_input = function(action_id, action) {
|
||||
if (!action_id.includes('gamepad_touchpad_')) return
|
||||
if (search(action_id, 'gamepad_touchpad_') == null) return
|
||||
|
||||
var finger = action.finger || 0
|
||||
var touchpad = action.touchpad || 0
|
||||
@@ -59,7 +59,7 @@ gesture.on_input = function(action_id, action) {
|
||||
var currentDist = this.dist(fingers[0], fingers[1])
|
||||
var d = currentDist - this.startDist
|
||||
|
||||
if (number.abs(d) >= this.PINCH_TH) {
|
||||
if (abs(d) >= this.PINCH_TH) {
|
||||
var gesture_type = d > 0 ? 'pinch_out' : 'pinch_in'
|
||||
// scene.recurse(game.root, 'on_input', gesture_type, { delta: d })
|
||||
this.startDist = currentDist
|
||||
@@ -79,7 +79,7 @@ gesture.on_input = function(action_id, action) {
|
||||
var dy = action.y - touch.startY
|
||||
|
||||
if (dt < this.MAX_TIME / 1000) { // Convert to seconds
|
||||
var absX = number.abs(dx), absY = number.abs(dy)
|
||||
var 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
|
||||
? (dx > 0 ? 'swipe_right' : 'swipe_left')
|
||||
|
||||
26
graphics.cm
26
graphics.cm
@@ -55,10 +55,10 @@ function decorate_rect_px(img) {
|
||||
|
||||
// store pixel-space version: [x, y, w, h] in texels
|
||||
img.rect_px = {
|
||||
x:number.round(img.rect.x * width),
|
||||
y:number.round(img.rect.y * height),
|
||||
width:number.round(img.rect.width * width),
|
||||
height:number.round(img.rect.height * height)
|
||||
x:round(img.rect.x * width),
|
||||
y:round(img.rect.y * height),
|
||||
width:round(img.rect.width * width),
|
||||
height:round(img.rect.height * height)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ function wrapSurface(surf, maybeRect){
|
||||
return h;
|
||||
}
|
||||
function wrapFrames(arr){ /* [{surface,time,rect}, …] → [{image,time}] */
|
||||
return arr.map(f => {
|
||||
return array(arr, f => {
|
||||
// Handle both surface objects and objects with surface property
|
||||
var surf = f.surface || f;
|
||||
return {
|
||||
@@ -119,7 +119,7 @@ function decode_gif(decoded) {
|
||||
}
|
||||
|
||||
// Multiple frames - return array with time property
|
||||
return decoded.frames.map(function(frame) {
|
||||
return array(decoded.frames, frame => {
|
||||
return {
|
||||
surface: frame,
|
||||
time: (frame.duration || 100) / 1000.0 // convert ms to seconds
|
||||
@@ -138,7 +138,7 @@ function decode_aseprite(decoded) {
|
||||
|
||||
// Multiple frames without tags - return as single animation
|
||||
return {
|
||||
frames: decoded.frames.map(function(frame) {
|
||||
frames: array(decoded.frames, frame => {
|
||||
return {
|
||||
surface: frame,
|
||||
time: (frame.duration || 100) / 1000.0 // convert ms to seconds
|
||||
@@ -152,7 +152,7 @@ function create_image(path){
|
||||
try{
|
||||
def bytes = io.slurp(path);
|
||||
|
||||
var ext = path.split('.').pop()
|
||||
var ext = array(path, '.').pop()
|
||||
var raw = decode_image(bytes, ext);
|
||||
|
||||
/* ── Case A: single surface (from make_texture) ────────────── */
|
||||
@@ -258,7 +258,7 @@ graphics.texture = function texture(path) {
|
||||
if (!is_text(path))
|
||||
throw Error('need a string for graphics.texture')
|
||||
|
||||
var parts = path.split(':')
|
||||
var parts = array(path, ':')
|
||||
var id = parts[0]
|
||||
var animName = parts[1]
|
||||
var frameIndex = parts[2]
|
||||
@@ -353,7 +353,7 @@ graphics.texture = function texture(path) {
|
||||
}
|
||||
|
||||
graphics.tex_hotreload = function tex_hotreload(file) {
|
||||
var basename = file.split('/').pop().split('.')[0]
|
||||
var basename = array(array(file, '/').pop(), '.')[0]
|
||||
|
||||
// Check if this basename exists in our cache
|
||||
if (!(basename in cache)) return
|
||||
@@ -390,7 +390,7 @@ graphics.tex_hotreload = function tex_hotreload(file) {
|
||||
Merges specific properties from nv into ov, using an array of property names.
|
||||
*/
|
||||
function merge_objects(ov, nv, arr) {
|
||||
arr.forEach(x => ov[x] = nv[x])
|
||||
arrfor(arr, x => ov[x] = nv[x])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -411,7 +411,7 @@ graphics.get_font = function get_font(path) {
|
||||
if (!is_text(path))
|
||||
throw Error(`Can't find font with path: ${path}`)
|
||||
|
||||
var parts = path.split('.')
|
||||
var parts = array(path, '.')
|
||||
var size = 16 // default size
|
||||
parts[1] = number(parts[1])
|
||||
if (parts[1]) {
|
||||
@@ -434,7 +434,7 @@ graphics.get_font = function get_font(path) {
|
||||
}
|
||||
|
||||
graphics.queue_sprite_mesh = function(queue) {
|
||||
var sprites = queue.filter(x => x.type == 'sprite')
|
||||
var sprites = filter(queue, x => x.type == 'sprite')
|
||||
if (sprites.length == 0) return []
|
||||
var mesh = graphics.make_sprite_mesh(sprites)
|
||||
for (var i = 0; i < sprites.length; i++) {
|
||||
|
||||
278
input.cm
278
input.cm
@@ -1,12 +1,274 @@
|
||||
var sdl_input = use('sdl3/input')
|
||||
var emacs = use('emacs')
|
||||
var gestures = use('gestures')
|
||||
// Prosperon Input System
|
||||
// Engine-driven input with user pairing and possession dispatch
|
||||
|
||||
return {
|
||||
gamepad_id_to_tyle: sdl_input.gamepad_id_to_type,
|
||||
make: function() {
|
||||
return {
|
||||
|
||||
var backend = use('input/backends/sdl3')
|
||||
var devices = use('input/devices')
|
||||
var bindings_mod = use('input/bindings')
|
||||
var router_mod = use('input/router')
|
||||
|
||||
// Default UI-focused action map
|
||||
var default_action_map = {
|
||||
'ui_up': ['w', 'up', 'gamepad_dpup'],
|
||||
'ui_down': ['s', 'down', 'gamepad_dpdown'],
|
||||
'ui_left': ['a', 'left', 'gamepad_dpleft'],
|
||||
'ui_right': ['d', 'right', 'gamepad_dpright'],
|
||||
'confirm': ['return', 'space', 'mouse_button_left', 'gamepad_a'],
|
||||
'cancel': ['escape', 'gamepad_b'],
|
||||
'menu': ['escape', 'gamepad_start']
|
||||
}
|
||||
|
||||
var default_display_names = {
|
||||
'ui_up': 'UI Up',
|
||||
'ui_down': 'UI Down',
|
||||
'ui_left': 'UI Left',
|
||||
'ui_right': 'UI Right',
|
||||
'confirm': 'Confirm',
|
||||
'cancel': 'Cancel',
|
||||
'menu': 'Menu'
|
||||
}
|
||||
|
||||
// Module state
|
||||
var _users = []
|
||||
var _config = {
|
||||
max_users: 1,
|
||||
pairing: 'last_used',
|
||||
emacs: true,
|
||||
gestures: true,
|
||||
action_map: default_action_map,
|
||||
display_names: default_display_names
|
||||
}
|
||||
|
||||
var _initialized = false
|
||||
var _window_callback = null
|
||||
|
||||
// Create an input user
|
||||
function create_user(index, config) {
|
||||
var action_map = {}
|
||||
var display_names = {}
|
||||
|
||||
// Merge defaults with config
|
||||
for (var k in default_action_map) {
|
||||
action_map[k] = array(default_action_map[k])
|
||||
display_names[k] = default_display_names[k]
|
||||
}
|
||||
if (config.action_map) {
|
||||
for (var k in config.action_map) {
|
||||
var val = config.action_map[k]
|
||||
action_map[k] = is_array(val) ? array(val) : [val]
|
||||
}
|
||||
}
|
||||
if (config.display_names) {
|
||||
for (var k in config.display_names) {
|
||||
display_names[k] = config.display_names[k]
|
||||
}
|
||||
}
|
||||
|
||||
var user = {
|
||||
index: index,
|
||||
paired_devices: [],
|
||||
active_device: null,
|
||||
bindings: bindings_mod.make(action_map, display_names),
|
||||
router: null,
|
||||
control_stack: [],
|
||||
|
||||
// Get current device kind
|
||||
get device_kind() {
|
||||
if (!this.active_device) return 'keyboard'
|
||||
return devices.kind(this.active_device)
|
||||
},
|
||||
|
||||
// Get current gamepad type
|
||||
get gamepad_type() {
|
||||
if (!this.active_device) return null
|
||||
return devices.gamepad_type(this.active_device)
|
||||
},
|
||||
|
||||
// Get action down state
|
||||
get down() {
|
||||
return this.router ? this.router.down : {}
|
||||
},
|
||||
|
||||
// Possess an entity (clears stack, sets as sole target)
|
||||
possess: function(entity) {
|
||||
this.control_stack = [entity]
|
||||
},
|
||||
|
||||
// Push entity onto control stack
|
||||
push: function(entity) {
|
||||
this.control_stack.push(entity)
|
||||
},
|
||||
|
||||
// Pop from control stack
|
||||
pop: function() {
|
||||
if (this.control_stack.length > 1) {
|
||||
return this.control_stack.pop()
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
// Get current control target
|
||||
get target() {
|
||||
return this.control_stack.length > 0
|
||||
? this.control_stack[this.control_stack.length - 1]
|
||||
: null
|
||||
},
|
||||
|
||||
// Dispatch action to current target
|
||||
dispatch: function(action, data) {
|
||||
var target = this.target
|
||||
if (target && target.on_input) {
|
||||
target.on_input(action, data)
|
||||
}
|
||||
},
|
||||
|
||||
// Get icon for action using current device
|
||||
get_icon_for_action: function(action) {
|
||||
return this.bindings.get_icon_for_action(action, this.device_kind, this.gamepad_type)
|
||||
},
|
||||
|
||||
// Get primary binding for action using current device
|
||||
get_primary_binding: function(action) {
|
||||
return this.bindings.get_primary_binding(action, this.device_kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Create router
|
||||
user.router = router_mod.make(user, {
|
||||
emacs: config.emacs,
|
||||
gestures: config.gestures,
|
||||
swipe_min_dist: config.swipe_min_dist,
|
||||
swipe_max_time: config.swipe_max_time,
|
||||
pinch_threshold: config.pinch_threshold
|
||||
})
|
||||
|
||||
// Load saved bindings
|
||||
user.bindings.load()
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// Pick user based on pairing policy
|
||||
function pick_user(canon) {
|
||||
if (_users.length == 0) return null
|
||||
|
||||
// For last_used: always user 0, just update active device
|
||||
if (_config.pairing == 'last_used') {
|
||||
var user = _users[0]
|
||||
|
||||
// Only switch on button press, not axis/motion
|
||||
if (canon.kind == 'button' && canon.pressed) {
|
||||
if (user.active_device != canon.device_id) {
|
||||
// Release all held actions when switching device
|
||||
var old_down = user.router.down
|
||||
for (var action in old_down) {
|
||||
if (old_down[action]) {
|
||||
user.dispatch(action, { pressed: false, released: true, time: canon.time })
|
||||
}
|
||||
}
|
||||
|
||||
user.active_device = canon.device_id
|
||||
if (find(user.paired_devices, canon.device_id) == null) {
|
||||
user.paired_devices.push(canon.device_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
// For explicit pairing: find user paired to this device
|
||||
for (var i = 0; i < _users.length; 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 || {}
|
||||
|
||||
_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++) {
|
||||
_users.push(create_user(i, _config))
|
||||
}
|
||||
|
||||
_initialized = true
|
||||
}
|
||||
|
||||
// Ingest a raw SDL event
|
||||
function ingest(raw_evt) {
|
||||
if (!_initialized) {
|
||||
configure({})
|
||||
}
|
||||
|
||||
// Translate to canonical format
|
||||
var canon = backend.translate(raw_evt)
|
||||
if (!canon) return
|
||||
|
||||
// Handle window events specially
|
||||
if (canon.kind == 'window') {
|
||||
if (_window_callback) {
|
||||
_window_callback(canon)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle device events
|
||||
if (canon.kind == 'device') {
|
||||
if (canon.control == 'connected') {
|
||||
devices.register(canon)
|
||||
} else if (canon.control == 'disconnected') {
|
||||
devices.unregister(canon.device_id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Register device
|
||||
devices.register(canon)
|
||||
|
||||
// Pick user and route
|
||||
var user = pick_user(canon)
|
||||
if (user) {
|
||||
user.router.handle(canon)
|
||||
}
|
||||
}
|
||||
|
||||
// Get user by index
|
||||
function user(index) {
|
||||
return _users[index]
|
||||
}
|
||||
|
||||
return {
|
||||
configure: configure,
|
||||
ingest: ingest,
|
||||
user: user,
|
||||
|
||||
get player1() { return _users[0] },
|
||||
get player2() { return _users[1] },
|
||||
get player3() { return _users[2] },
|
||||
get player4() { return _users[3] },
|
||||
|
||||
// Re-export for convenience
|
||||
devices: devices,
|
||||
backend: backend
|
||||
}
|
||||
24
line2d.cm
24
line2d.cm
@@ -43,30 +43,6 @@ var line_proto = {
|
||||
return this
|
||||
},
|
||||
|
||||
set_groups: function(groups) {
|
||||
var old_groups = this.groups
|
||||
this.groups = groups
|
||||
film2d.reindex(this._id, old_groups, groups)
|
||||
return this
|
||||
},
|
||||
|
||||
add_group: function(group) {
|
||||
if (this.groups.indexOf(group) < 0) {
|
||||
this.groups.push(group)
|
||||
film2d.index_group(this._id, group)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
remove_group: function(group) {
|
||||
var idx = this.groups.indexOf(group)
|
||||
if (idx >= 0) {
|
||||
this.groups.splice(idx, 1)
|
||||
film2d.unindex_group(this._id, group)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
_rebuild: function() {
|
||||
var result = build_polyline_mesh(this)
|
||||
this.verts = result.verts
|
||||
|
||||
@@ -372,7 +372,7 @@ PlaydateBackend.prototype.gray_to_dither_pattern = function(gray) {
|
||||
[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
|
||||
]
|
||||
|
||||
var index = number.floor(gray * (patterns.length - 1))
|
||||
var index = floor(gray * (patterns.length - 1))
|
||||
return patterns[index]
|
||||
}
|
||||
|
||||
|
||||
30
rasterize.cm
30
rasterize.cm
@@ -26,10 +26,10 @@ rasterize.ellipse = function ellipse(pos, radii, opt) {
|
||||
var cx = pos[0], cy = pos[1]
|
||||
var raw_start = opt.start || 0
|
||||
var raw_end = opt.end || 1
|
||||
var full_circle = number.abs(raw_end - raw_start) >= 1 - 1e-9
|
||||
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 = number.max(1, opt.thickness || 1)
|
||||
var thickness = max(1, opt.thickness || 1)
|
||||
|
||||
var rx_i = rx - thickness,
|
||||
ry_i = ry - thickness
|
||||
@@ -46,8 +46,8 @@ rasterize.ellipse = function ellipse(pos, radii, opt) {
|
||||
var pts = [
|
||||
[cx + x, cy + y], [cx - x, cy + y],
|
||||
[cx + x, cy - y], [cx - x, cy - y]
|
||||
].filter(pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle))
|
||||
points = points.concat(pts)
|
||||
]
|
||||
points = array(points, filter(pts, pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle)))
|
||||
}
|
||||
|
||||
while (px < py) {
|
||||
@@ -72,14 +72,14 @@ rasterize.ellipse = function ellipse(pos, radii, opt) {
|
||||
|
||||
for (var dy = -ry; dy <= ry; ++dy) {
|
||||
var yy = dy * dy
|
||||
var x_out = number.floor(rx * math.sqrt(1 - yy / ry_sq))
|
||||
var x_out = floor(rx * math.sqrt(1 - yy / ry_sq))
|
||||
var y_screen = cy + dy
|
||||
|
||||
var x_in = hole ? number.floor(rx_i * math.sqrt(1 - yy / ry_i_sq)) : -1
|
||||
var 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) {
|
||||
if (hole && number.abs(dx) <= x_in) { run_start = null; continue }
|
||||
if (hole && abs(dx) <= x_in) { run_start = null; continue }
|
||||
if (!within_wedge(dx, dy, start, end, full_circle)) { run_start = null; continue }
|
||||
|
||||
if (run_start == null) run_start = cx + dx
|
||||
@@ -87,7 +87,7 @@ rasterize.ellipse = function ellipse(pos, radii, opt) {
|
||||
var last = (dx == x_out)
|
||||
var next_in_ring =
|
||||
!last &&
|
||||
!(hole && number.abs(dx+1) <= x_in) &&
|
||||
!(hole && abs(dx+1) <= x_in) &&
|
||||
within_wedge(dx+1, dy, start, end, full_circle)
|
||||
|
||||
if (last || !next_in_ring) {
|
||||
@@ -140,7 +140,7 @@ rasterize.round_rect = function round_rect(rect, radius, thickness) {
|
||||
return rasterize.fill_round_rect(rect, radius)
|
||||
}
|
||||
|
||||
radius = number.min(radius, rect.width >> 1, rect.height >> 1)
|
||||
radius = min(radius, rect.width >> 1, rect.height >> 1)
|
||||
|
||||
if ((thickness << 1) >= rect.width ||
|
||||
(thickness << 1) >= rect.height ||
|
||||
@@ -169,9 +169,9 @@ rasterize.round_rect = function round_rect(rect, radius, thickness) {
|
||||
|
||||
for (var dy = 0; dy < radius; ++dy) {
|
||||
var dy_sq = dy * dy
|
||||
var dx_out = number.floor(math.sqrt(r_out * r_out - dy_sq))
|
||||
var dx_out = floor(math.sqrt(r_out * r_out - dy_sq))
|
||||
var dx_in = (r_in > 0 && dy < r_in)
|
||||
? number.floor(math.sqrt(r_in * r_in - dy_sq))
|
||||
? floor(math.sqrt(r_in * r_in - dy_sq))
|
||||
: -1
|
||||
var w = dx_out - dx_in
|
||||
if (w <= 0) continue
|
||||
@@ -184,11 +184,11 @@ rasterize.round_rect = function round_rect(rect, radius, thickness) {
|
||||
)
|
||||
}
|
||||
|
||||
return {type: 'rects', data: rects.concat(strips)}
|
||||
return {type: 'rects', data: array(rects, strips)}
|
||||
}
|
||||
|
||||
rasterize.fill_round_rect = function fill_round_rect(rect, radius) {
|
||||
radius = number.min(radius, rect.width >> 1, rect.height >> 1)
|
||||
radius = min(radius, rect.width >> 1, rect.height >> 1)
|
||||
|
||||
var x0 = rect.x,
|
||||
y0 = rect.y,
|
||||
@@ -206,7 +206,7 @@ rasterize.fill_round_rect = function fill_round_rect(rect, radius) {
|
||||
var caps = []
|
||||
|
||||
for (var dy = 0; dy < radius; ++dy) {
|
||||
var dx = number.floor(math.sqrt(radius * radius - dy * dy))
|
||||
var dx = floor(math.sqrt(radius * radius - dy * dy))
|
||||
var w = (dx << 1) + 1
|
||||
|
||||
caps.push(
|
||||
@@ -217,7 +217,7 @@ rasterize.fill_round_rect = function fill_round_rect(rect, radius) {
|
||||
)
|
||||
}
|
||||
|
||||
return {type: 'rects', data: rects.concat(caps)}
|
||||
return {type: 'rects', data: array(rects, caps)}
|
||||
}
|
||||
|
||||
return rasterize
|
||||
28
resources.cm
28
resources.cm
@@ -23,24 +23,24 @@ Resources.lib = [".so", ".dll", ".dylib"]
|
||||
function getExtension(path) {
|
||||
var idx = path.lastIndexOf('.')
|
||||
if (idx < 0) return ''
|
||||
return path.substring(idx + 1).toLowerCase()
|
||||
return lower(text(path, idx + 1))
|
||||
}
|
||||
|
||||
// Return true if ext is in at least one of the recognized lists
|
||||
function isRecognizedExtension(ext) {
|
||||
if (!ext) return false
|
||||
if (Resources.scripts.includes(ext)) return true
|
||||
if (Resources.images.includes(ext)) return true
|
||||
if (Resources.sounds.includes(ext)) return true
|
||||
if (Resources.fonts.includes(ext)) return true
|
||||
if (Resources.lib.includes('.' + ext)) return true // for .so or .dll
|
||||
if (search(Resources.scripts, ext) != null) return true
|
||||
if (search(Resources.images, ext) != null) return true
|
||||
if (search(Resources.sounds, ext) != null) return true
|
||||
if (search(Resources.fonts, ext) != null) return true
|
||||
if (search(Resources.lib, '.' + ext) != null) return true // for .so or .dll
|
||||
return false
|
||||
}
|
||||
|
||||
function find_in_path(filename, exts = []) {
|
||||
if (!is_text(filename)) return null
|
||||
|
||||
if (filename.includes('.')) {
|
||||
if (search(filename, '.') != null) {
|
||||
var candidate = filename // possibly need "/" ?
|
||||
if (io.exists(candidate) && !io.is_directory(candidate)) return candidate
|
||||
return null
|
||||
@@ -87,10 +87,10 @@ function read_ignore(dir) {
|
||||
var path = dir + '/.prosperonignore'
|
||||
var patterns = []
|
||||
if (io.exists(path)) {
|
||||
var lines = io.slurp(path).split('\n')
|
||||
var lines = array(io.slurp(path), '\n')
|
||||
for (var line of lines) {
|
||||
line = line.trim()
|
||||
if (!line || line.startsWith('#')) continue
|
||||
line = trim(line)
|
||||
if (!line || starts_with(line, '#')) continue
|
||||
patterns.push(line)
|
||||
}
|
||||
}
|
||||
@@ -124,19 +124,19 @@ Resources.gatherStats = function(filePaths) {
|
||||
}
|
||||
for (var path of filePaths) {
|
||||
var ext = getExtension(path)
|
||||
if (Resources.scripts.includes(ext)) {
|
||||
if (find(Resources.scripts, ext) != null) {
|
||||
stats.scripts++
|
||||
continue
|
||||
}
|
||||
if (Resources.images.includes(ext)) {
|
||||
if (find(Resources.images, ext) != null) {
|
||||
stats.images++
|
||||
continue
|
||||
}
|
||||
if (Resources.sounds.includes(ext)) {
|
||||
if (find(Resources.sounds, ext) != null) {
|
||||
stats.sounds++
|
||||
continue
|
||||
}
|
||||
if (Resources.fonts.includes(ext)) {
|
||||
if (find(Resources.fonts, ext) != null) {
|
||||
stats.fonts++
|
||||
continue
|
||||
}
|
||||
|
||||
14
sdl_gpu.cm
14
sdl_gpu.cm
@@ -700,7 +700,7 @@ function _load_image_file(path) {
|
||||
var decoded
|
||||
if (!bytes) return null
|
||||
|
||||
var ext = path.split('.').pop().toLowerCase()
|
||||
var ext = lower(array(path, '.').pop())
|
||||
var surface = null
|
||||
|
||||
switch (ext) {
|
||||
@@ -758,6 +758,13 @@ sdl_gpu.get_texture = function(path) {
|
||||
return tex
|
||||
}
|
||||
|
||||
// Get texture info (dimensions) for a path
|
||||
sdl_gpu.get_texture_info = function(path) {
|
||||
var tex = sdl_gpu.get_texture(path)
|
||||
if (!tex) return null
|
||||
return {width: tex.width, height: tex.height}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// RENDER TARGET MANAGEMENT
|
||||
// ========================================================================
|
||||
@@ -844,7 +851,8 @@ function _build_sprite_vertices(sprites, camera) {
|
||||
|
||||
var white = {r: 1, g: 1, b: 1, a: 1}
|
||||
|
||||
array.for(sprites, s => {
|
||||
for(var i = 0; i < sprites.length; i++) {
|
||||
var s = sprites[i]
|
||||
var px = s.pos.x
|
||||
var py = s.pos.y
|
||||
var w = s.width || 1
|
||||
@@ -948,7 +956,7 @@ function _build_sprite_vertices(sprites, camera) {
|
||||
index_data.w16(vertex_count + 3)
|
||||
|
||||
vertex_count += 4
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
vertices: stone(vertex_data),
|
||||
|
||||
26
shape2d.cm
26
shape2d.cm
@@ -8,31 +8,7 @@ var shape_proto = {
|
||||
this.pos.y = y
|
||||
return this
|
||||
},
|
||||
|
||||
set_groups: function(groups) {
|
||||
var old_groups = this.groups
|
||||
this.groups = groups
|
||||
film2d.reindex(this._id, old_groups, groups)
|
||||
return this
|
||||
},
|
||||
|
||||
add_group: function(group) {
|
||||
if (this.groups.indexOf(group) < 0) {
|
||||
this.groups.push(group)
|
||||
film2d.index_group(this._id, group)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
remove_group: function(group) {
|
||||
var idx = this.groups.indexOf(group)
|
||||
if (idx >= 0) {
|
||||
this.groups.splice(idx, 1)
|
||||
film2d.unindex_group(this._id, group)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
|
||||
destroy: function() {
|
||||
film2d.unregister(this._id)
|
||||
}
|
||||
|
||||
35
sprite.cm
35
sprite.cm
@@ -3,36 +3,6 @@ var film2d = use('film2d')
|
||||
var sprite_proto = {
|
||||
type: 'sprite',
|
||||
|
||||
set_pos: function(x, y) {
|
||||
this.pos.x = x
|
||||
this.pos.y = y
|
||||
return this
|
||||
},
|
||||
|
||||
set_groups: function(groups) {
|
||||
var old_groups = this.groups
|
||||
this.groups = groups
|
||||
film2d.reindex(this._id, old_groups, groups)
|
||||
return this
|
||||
},
|
||||
|
||||
add_group: function(group) {
|
||||
if (this.groups.indexOf(group) < 0) {
|
||||
this.groups.push(group)
|
||||
film2d.index_group(this._id, group)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
remove_group: function(group) {
|
||||
var idx = this.groups.indexOf(group)
|
||||
if (idx >= 0) {
|
||||
this.groups.splice(idx, 1)
|
||||
film2d.unindex_group(this._id, group)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
film2d.unregister(this._id)
|
||||
}
|
||||
@@ -44,7 +14,10 @@ return function(props) {
|
||||
pos: {x: 0, y: 0},
|
||||
image: null,
|
||||
width: 1,
|
||||
height: 1,
|
||||
height: null,
|
||||
fit: 'contain',
|
||||
fit_anchor_x: 0.5,
|
||||
fit_anchor_y: 0.5,
|
||||
anchor_x: 0,
|
||||
anchor_y: 0,
|
||||
flip: {x: false, y: false},
|
||||
|
||||
@@ -15,7 +15,7 @@ var Anim = (() => {
|
||||
a.timer += dt;
|
||||
def frames = a.src.frames;
|
||||
while(true){
|
||||
def time = number.max(frames[a.idx].time || 0, Anim.minDelay);
|
||||
def time = max(frames[a.idx].time || 0, Anim.minDelay);
|
||||
if(a.timer < time) break; /* still on current frame */
|
||||
|
||||
a.timer -= time;
|
||||
@@ -98,7 +98,7 @@ function loop(){
|
||||
}
|
||||
|
||||
/* schedule next tick: aim for 60 Hz but won’t matter to anim speed */
|
||||
$delay(loop, number.max(0, (1/60) - (os.now()-now)));
|
||||
$delay(loop, max(0, (1/60) - (os.now()-now)));
|
||||
}
|
||||
loop();
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ var center = [0.5,0.5]
|
||||
var vel = 50
|
||||
|
||||
function hsl_to_rgb(h, s, l) {
|
||||
var c = (1 - number.abs(2 * l - 1)) * s
|
||||
var x = c * (1 - number.abs((h / 60) % 2 - 1))
|
||||
var c = (1 - abs(2 * l - 1)) * s
|
||||
var x = c * (1 - abs((h / 60) % 2 - 1))
|
||||
var m = l - c / 2
|
||||
var r = 0, g = 0, b = 0
|
||||
|
||||
@@ -75,15 +75,15 @@ function loop()
|
||||
render.clear([22/255,120/255,194/255,255/255])
|
||||
render.camera(camera)
|
||||
|
||||
sprite.forEach(x => x.move(x.dir.scale(dt)))
|
||||
arrfor(sprite, x => x.move(x.dir.scale(dt)))
|
||||
var queue = sprite.queue()
|
||||
|
||||
//log.console(queue)
|
||||
|
||||
for (var q of queue) {
|
||||
if (!q.image) continue
|
||||
arrfor(queue, q => {
|
||||
if (!q.image) return
|
||||
render.geometry(q.image.texture, q.mesh)
|
||||
}
|
||||
})
|
||||
|
||||
render.present()
|
||||
dt = os.now() - now
|
||||
@@ -93,7 +93,7 @@ function loop()
|
||||
|
||||
if (now - last_fps_update >= fps_update_period) {
|
||||
var sum = 0
|
||||
for (var i = 0; i < fps_samples.length; i++) sum += fps_samples[i]
|
||||
arrfor(fps_samples, x => sum += x)
|
||||
prosperon.window.title = `Bunnymark [fps: ${(fps_samples.length/sum).toFixed(1)}]`;
|
||||
last_fps_update = now
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ for (var cs in colorspaces) {
|
||||
}
|
||||
|
||||
// Just test first 3 colorspaces
|
||||
if (array(colorspaces).indexOf(cs) >= 2) break;
|
||||
if (find(array(colorspaces), cs) >= 2) break;
|
||||
}
|
||||
|
||||
log.console("\nColorspace test complete!");
|
||||
|
||||
14
tween.cm
14
tween.cm
@@ -11,13 +11,13 @@ function make_engine(default_clock) {
|
||||
this.tweens.push(tween)
|
||||
},
|
||||
remove(tween) {
|
||||
this.tweens = this.tweens.filter(t => t != tween)
|
||||
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()
|
||||
}
|
||||
for (var tween of this.tweens.slice()) {
|
||||
for (var tween of array(this.tweens)) {
|
||||
tween._update(current_time)
|
||||
}
|
||||
},
|
||||
@@ -92,7 +92,7 @@ var TweenProto = {
|
||||
|
||||
seek: function(global_time) {
|
||||
var elapsed = global_time - this.startTime
|
||||
var t = number.min(number.max(elapsed / this.duration, 0), 1)
|
||||
var t = min(max(elapsed / this.duration, 0), 1)
|
||||
var eased = this.easing(t)
|
||||
|
||||
for (var key in this.endVals) {
|
||||
@@ -100,8 +100,8 @@ var TweenProto = {
|
||||
var end = this.endVals[key]
|
||||
var value = start + (end - start) * eased
|
||||
|
||||
if (key.includes('.')) {
|
||||
var parts = key.split('.')
|
||||
if (search(key, '.') != null) {
|
||||
var parts = array(key, '.')
|
||||
var objKey = parts[0]
|
||||
var subKey = parts[1]
|
||||
|
||||
@@ -211,8 +211,8 @@ var TimelineProto = {
|
||||
toJSON: function() {
|
||||
return {
|
||||
current_time: this.current_time,
|
||||
events: this.events.map(e => ({ time: e.time, fired: e.fired })),
|
||||
tweens: this.engine.tweens.map(t => t.toJSON())
|
||||
events: array(this.events, e => ({ time: e.time, fired: e.fired })),
|
||||
tweens: array(this.engine.tweens, t => t.toJSON())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
world.cm
13
world.cm
@@ -1,10 +1,19 @@
|
||||
var world = {}
|
||||
|
||||
world.add_entity = function(entity_proto) {
|
||||
var entity = meme(entity_proto)
|
||||
var entities = {}
|
||||
|
||||
world.add_entity = function(entity_proto, data) {
|
||||
var entity = meme(entity_proto, data)
|
||||
if (is_function(entity.init))
|
||||
entity.init()
|
||||
entities[entity] = true
|
||||
return entity
|
||||
}
|
||||
|
||||
world.destroy_entity = function(entity) {
|
||||
if (is_function(entity.on_destroy))
|
||||
entity.on_destroy()
|
||||
delete entities[entity]
|
||||
}
|
||||
|
||||
return world
|
||||
|
||||
Reference in New Issue
Block a user