rework
This commit is contained in:
10
clay.cm
10
clay.cm
@@ -13,15 +13,15 @@ var CHILDREN = 'children'
|
||||
var PARENT = 'parent'
|
||||
|
||||
function normalizeSpacing(spacing) {
|
||||
if (typeof spacing == 'number') {
|
||||
if (is_number(spacing)) {
|
||||
return {l: spacing, r: spacing, t: spacing, b: spacing}
|
||||
} else if (isa(spacing, array)) {
|
||||
} else if (is_array(spacing)) {
|
||||
if (spacing.length == 2) {
|
||||
return {l: spacing[0], r: spacing[0], t: spacing[1], b: spacing[1]}
|
||||
} else if (spacing.length == 4) {
|
||||
return {l: spacing[0], r: spacing[1], t: spacing[2], b: spacing[3]}
|
||||
}
|
||||
} else if (typeof spacing == 'object') {
|
||||
} else if (is_object(spacing)) {
|
||||
return {l: spacing.l || 0, r: spacing.r || 0, t: spacing.t || 0, b: spacing.b || 0}
|
||||
} else {
|
||||
return {l:0, r:0, t:0, b:0}
|
||||
@@ -66,7 +66,7 @@ clay.draw = function draw(fn, size = [prosperon.camera.width, prosperon.camera.h
|
||||
lay_ctx.reset();
|
||||
var root = lay_ctx.item();
|
||||
// Accept both array and object formats
|
||||
if (isa(size, array)) {
|
||||
if (is_array(size)) {
|
||||
size = {width: size[0], height: size[1]};
|
||||
}
|
||||
lay_ctx.set_size(root,size);
|
||||
@@ -225,7 +225,7 @@ function add_item(config)
|
||||
lay_ctx.set_margins(item, use_config.margin);
|
||||
use_config.size ??= {width:0, height:0}
|
||||
// Convert array to object if needed
|
||||
if (isa(use_config.size, array)) {
|
||||
if (is_array(use_config.size)) {
|
||||
use_config.size = {width: use_config.size[0], height: use_config.size[1]};
|
||||
}
|
||||
// Apply max_size constraint - only clamp computed size, don't set explicit size pre-layout
|
||||
|
||||
26
clay2.cm
26
clay2.cm
@@ -38,12 +38,12 @@ var base_config = {
|
||||
}
|
||||
|
||||
function normalize_spacing(s) {
|
||||
if (typeof s == 'number') return {l:s, r:s, t:s, b:s}
|
||||
if (isa(s, array)) {
|
||||
if (is_number(s)) return {l:s, r:s, t:s, b:s}
|
||||
if (is_array(s)) {
|
||||
if (s.length == 2) return {l:s[0], r:s[0], t:s[1], b:s[1]}
|
||||
if (s.length == 4) return {l:s[0], r:s[1], t:s[2], b:s[3]}
|
||||
}
|
||||
if (typeof s == 'object') return {l:s.l||0, r:s.r||0, t:s.t||0, b:s.b||0}
|
||||
if (is_object(s)) return {l:s.l||0, r:s.r||0, t:s.t||0, b:s.b||0}
|
||||
return {l:0, r:0, t:0, b:0}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ var tree_stack = []
|
||||
clay.layout = function(fn, size) {
|
||||
lay_ctx.reset()
|
||||
var root_id = lay_ctx.item()
|
||||
if (isa(size, array)) size = {width: size[0], height: size[1]}
|
||||
if (is_array(size)) size = {width: size[0], height: size[1]}
|
||||
|
||||
lay_ctx.set_size(root_id, size)
|
||||
lay_ctx.set_contain(root_id, layout.contain.row)
|
||||
@@ -235,7 +235,7 @@ function push_node(configs, contain_mode) {
|
||||
|
||||
if (config.size) {
|
||||
var s = config.size
|
||||
if (isa(s, array)) s = {width: s[0], height: s[1]}
|
||||
if (is_array(s)) s = {width: s[0], height: s[1]}
|
||||
lay_ctx.set_size(item, s)
|
||||
}
|
||||
|
||||
@@ -260,8 +260,8 @@ function pop_node() {
|
||||
|
||||
// Generic container
|
||||
clay.container = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
push_node(configs, null)
|
||||
if (fn) fn()
|
||||
@@ -270,8 +270,8 @@ clay.container = function(configs, fn) {
|
||||
|
||||
// Stacks
|
||||
clay.vstack = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
var c = layout.contain.column
|
||||
|
||||
@@ -281,8 +281,8 @@ clay.vstack = function(configs, fn) {
|
||||
}
|
||||
|
||||
clay.hstack = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
var c = layout.contain.row
|
||||
push_node(configs, c)
|
||||
@@ -291,8 +291,8 @@ clay.hstack = function(configs, fn) {
|
||||
}
|
||||
|
||||
clay.zstack = function(configs, fn) {
|
||||
if (typeof configs == 'function') { fn = configs; configs = {} }
|
||||
if (!isa(configs, array)) configs = [configs]
|
||||
if (is_function(configs)) { fn = configs; configs = {} }
|
||||
if (!is_array(configs)) configs = [configs]
|
||||
|
||||
var c = layout.contain.layout
|
||||
|
||||
|
||||
4
color.cm
4
color.cm
@@ -98,8 +98,8 @@ Color.normalize = function (c) {
|
||||
};
|
||||
|
||||
for (var p of array(c)) {
|
||||
if (typeof c[p] != "object") continue;
|
||||
if (!isa(c[p], array)) {
|
||||
if (!is_object(c[p])) continue;
|
||||
if (!is_array(c[p])) {
|
||||
Color.normalize(c[p]);
|
||||
continue;
|
||||
}
|
||||
|
||||
942
compositor.cm
942
compositor.cm
File diff suppressed because it is too large
Load Diff
@@ -108,8 +108,8 @@ input.mouse.normal.doc = "Set the mouse to show again after hiding.";
|
||||
|
||||
input.keyboard = {};
|
||||
input.keyboard.down = function (code) {
|
||||
if (typeof code == "number") return downkeys[code];
|
||||
if (typeof code == "string") return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
|
||||
if (is_number(code)) return downkeys[code];
|
||||
if (is_text(code)) return downkeys[code.toUpperCase().charCodeAt()] || downkeys[code.toLowerCase().charCodeAt()];
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ input.print_md_kbm = function print_md_kbm(pawn) {
|
||||
};
|
||||
|
||||
input.has_bind = function (pawn, bind) {
|
||||
return typeof pawn.inputs?.[bind] == "function";
|
||||
return is_function(pawn.inputs?.[bind]);
|
||||
};
|
||||
|
||||
input.action = {
|
||||
@@ -214,7 +214,7 @@ var Player = {
|
||||
|
||||
mouse_input(type, ...args) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
if (typeof pawn.inputs?.mouse?.[type] == "function") {
|
||||
if (is_function(pawn.inputs?.mouse?.[type])) {
|
||||
pawn.inputs.mouse[type].call(pawn, ...args);
|
||||
pawn.inputs.post?.call(pawn);
|
||||
if (!pawn.inputs.fallthru) return;
|
||||
@@ -224,7 +224,7 @@ var Player = {
|
||||
|
||||
char_input(c) {
|
||||
for (var pawn of [...this.pawns].reverse()) {
|
||||
if (typeof pawn.inputs?.char == "function") {
|
||||
if (is_function(pawn.inputs?.char)) {
|
||||
pawn.inputs.char.call(pawn, c);
|
||||
pawn.inputs.post?.call(pawn);
|
||||
if (!pawn.inputs.fallthru) return;
|
||||
@@ -271,12 +271,12 @@ var Player = {
|
||||
fn = inputs[cmd].released;
|
||||
break;
|
||||
case "down":
|
||||
if (typeof inputs[cmd].down == "function") fn = inputs[cmd].down;
|
||||
if (is_function(inputs[cmd].down)) fn = inputs[cmd].down;
|
||||
else if (inputs[cmd].down) fn = inputs[cmd];
|
||||
}
|
||||
|
||||
var consumed = false;
|
||||
if (typeof fn == "function") {
|
||||
if (is_function(fn)) {
|
||||
fn.call(pawn, ...args);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ function _render_node_tree(imgui, node, depth) {
|
||||
var label = type + " [" + id + "]"
|
||||
|
||||
// Add dirty indicator
|
||||
if (isa(node.dirty, number) && node.dirty > 0) {
|
||||
if (is_number(node.dirty) && node.dirty > 0) {
|
||||
label += " *"
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ function _render_node_inspector(imgui, node) {
|
||||
for (var k in fx) {
|
||||
if (k != 'type' && k != 'source') {
|
||||
var v = fx[k]
|
||||
if (typeof v == 'number') {
|
||||
if (is_number(v)) {
|
||||
fx[k] = imgui.slider(k, v, 0, 10)
|
||||
} else {
|
||||
imgui.text(k + ": " + text(v))
|
||||
|
||||
@@ -180,7 +180,7 @@ draw.grid = function grid(rect, spacing, thickness = 1, offset = {x: 0, y: 0}, m
|
||||
rect.width == null || rect.height == null) {
|
||||
throw Error('Grid requires rect with x, y, width, height')
|
||||
}
|
||||
if (!spacing || typeof spacing.x == 'undefined' || typeof spacing.y == 'undefined') {
|
||||
if (!is_object(spacing)|| is_null(spacing.x) || is_null(spacing.y)) {
|
||||
throw Error('Grid requires spacing with x and y')
|
||||
}
|
||||
|
||||
|
||||
16
effects.cm
16
effects.cm
@@ -92,7 +92,8 @@ effects.register('mask', {
|
||||
type: 'conditional',
|
||||
requires_target: true,
|
||||
params: {
|
||||
source: {required: true},
|
||||
source: {required: false}, // Legacy: direct handle reference
|
||||
source_id: {required: false}, // New: ID string for film2d.get()
|
||||
channel: {default: 'alpha'},
|
||||
invert: {default: false},
|
||||
soft: {default: false},
|
||||
@@ -112,11 +113,22 @@ effects.register('mask', {
|
||||
return [{type: 'blit', source: input, dest: output}]
|
||||
}
|
||||
|
||||
// Resolve mask source
|
||||
var mask_source = params.source
|
||||
if (params.source_id && ctx.film2d) {
|
||||
mask_source = ctx.film2d.get(params.source_id)
|
||||
}
|
||||
|
||||
if (!mask_source) {
|
||||
// No mask source - pass through
|
||||
return [{type: 'blit', source: input, dest: output}]
|
||||
}
|
||||
|
||||
// Render mask source to target
|
||||
var mask_target = ctx.alloc_target(size.width, size.height, 'mask_src')
|
||||
passes.push({
|
||||
type: 'render_subtree',
|
||||
root: params.source,
|
||||
root: mask_source,
|
||||
output: mask_target,
|
||||
clear: {r: 0, g: 0, b: 0, a: 0},
|
||||
space: params.space || 'local'
|
||||
|
||||
551
film2d.cm
551
film2d.cm
@@ -1,462 +1,185 @@
|
||||
// film2d.cm - 2D Scene Renderer (Rewritten)
|
||||
//
|
||||
// Handles scene tree traversal, sorting, batching, and draw command emission.
|
||||
// This is the "how to draw a 2D plate" module.
|
||||
//
|
||||
// The compositor calls this renderer with (root, camera, target, target_size)
|
||||
// and it produces draw commands.
|
||||
//
|
||||
// This module does NOT handle effects - that's compositor territory.
|
||||
// It only knows about sprites, tilemaps, text, particles, and rects.
|
||||
|
||||
var film2d = {}
|
||||
|
||||
// Renderer capabilities
|
||||
film2d.capabilities = {
|
||||
supports_mask_stencil: true,
|
||||
supports_mask_alpha: true,
|
||||
supports_bloom: true,
|
||||
supports_blur: true
|
||||
}
|
||||
var next_id = 1
|
||||
var registry = {} // id -> drawable
|
||||
var group_index = {} // group_name -> [id, id, ...]
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Handle Registry
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
var _next_handle_id = 1
|
||||
var _handles = {}
|
||||
|
||||
function _create_handle(type, props) {
|
||||
var id = _next_handle_id++
|
||||
var handle = {
|
||||
_id: id,
|
||||
_gen: 1,
|
||||
type: type,
|
||||
active: true,
|
||||
|
||||
// Core Transform
|
||||
x: props.pos ? props.pos.x : 0,
|
||||
y: props.pos ? props.pos.y : 0,
|
||||
rotation: props.rotation || 0,
|
||||
scale_x: props.scale ? (props.scale.x || props.scale) : 1,
|
||||
scale_y: props.scale ? (props.scale.y || props.scale) : 1,
|
||||
anchor_x: props.anchor_x || 0,
|
||||
anchor_y: props.anchor_y || 0,
|
||||
|
||||
// Properties
|
||||
layer: props.layer || 0,
|
||||
color: props.color || {r:1, g:1, b:1, a:1},
|
||||
visible: true,
|
||||
tags: props.tags || [],
|
||||
|
||||
// Methods
|
||||
set_pos: function(x, y) { this.x = x; this.y = y; return this },
|
||||
set_scale: function(x, y) { this.scale_x = x; this.scale_y = (y == null ? x : y); return this },
|
||||
set_rotation: function(r) { this.rotation = r; return this },
|
||||
set_layer: function(l) { this.layer = l; return this },
|
||||
set_color: function(r, g, b, a) {
|
||||
if (typeof r == 'object') { this.color = r }
|
||||
else { this.color = {r:r, g:g, b:b, a:a} }
|
||||
return this
|
||||
},
|
||||
set_visible: function(v) { this.visible = v; return this },
|
||||
add_tag: function(t) { if (this.tags.indexOf(t) < 0) this.tags.push(t); return this },
|
||||
remove_tag: function(t) {
|
||||
var idx = this.tags.indexOf(t)
|
||||
if (idx >= 0) this.tags.splice(idx, 1)
|
||||
return this
|
||||
},
|
||||
has_tag: function(t) { return this.tags.indexOf(t) >= 0 },
|
||||
destroy: function() { this.active = false; delete _handles[this._id] }
|
||||
film2d.register = function(drawable) {
|
||||
var id = text(next_id++)
|
||||
drawable._id = id
|
||||
registry[id] = drawable
|
||||
|
||||
var groups = drawable.groups || ['default']
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
var g = groups[i]
|
||||
if (!group_index[g]) group_index[g] = []
|
||||
group_index[g].push(id)
|
||||
}
|
||||
|
||||
// Type specific properties
|
||||
if (type == 'sprite') {
|
||||
handle.image = props.image
|
||||
handle.width = props.width || 0
|
||||
handle.height = props.height || 0
|
||||
handle.material = props.material
|
||||
handle.slice = props.slice
|
||||
handle.tile = props.tile
|
||||
} else if (type == 'text') {
|
||||
handle.text = props.text
|
||||
handle.font = props.font
|
||||
handle.size = props.size
|
||||
handle.mode = props.mode
|
||||
handle.outline_width = props.outline_width
|
||||
handle.outline_color = props.outline_color
|
||||
} else if (type == 'particles') {
|
||||
handle.particles = props.particles || []
|
||||
handle.image = props.image
|
||||
handle.width = props.width
|
||||
handle.height = props.height
|
||||
handle.material = props.material
|
||||
} else if (type == 'tilemap') {
|
||||
handle.tiles = props.tiles || []
|
||||
handle.offset_x = props.offset_x || 0
|
||||
handle.offset_y = props.offset_y || 0
|
||||
handle.scale_tile_x = props.scale_x || 1 // avoid naming conflict with transform scale
|
||||
handle.scale_tile_y = props.scale_y || 1
|
||||
handle.material = props.material
|
||||
return id
|
||||
}
|
||||
|
||||
film2d.unregister = function(id) {
|
||||
var id_str = text(id)
|
||||
var drawable = registry[id_str]
|
||||
if (!drawable) return
|
||||
|
||||
var groups = drawable.groups || []
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
_handles[id] = handle
|
||||
return handle
|
||||
delete registry[id_str]
|
||||
}
|
||||
|
||||
film2d.create_sprite = function(props) { return _create_handle('sprite', props) }
|
||||
film2d.create_text = function(props) { return _create_handle('text', props) }
|
||||
film2d.create_particles = function(props) { return _create_handle('particles', props) }
|
||||
film2d.create_tilemap = function(props) { return _create_handle('tilemap', props) }
|
||||
|
||||
// Support for creating raw texture refs (usually internal, but exposed)
|
||||
film2d.create_texture_ref = function(props) {
|
||||
// Texture refs are usually transient drawables, but we can make a handle
|
||||
return _create_handle('texture_ref', props)
|
||||
film2d.index_group = function(id, group) {
|
||||
if (!group_index[group]) group_index[group] = []
|
||||
if (group_index[group].indexOf(text(id)) < 0)
|
||||
group_index[group].push(text(id))
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Render Pipeline
|
||||
// ------------------------------------------------------------------------
|
||||
film2d.unindex_group = function(id, group) {
|
||||
if (!group_index[group]) return
|
||||
var idx = group_index[group].indexOf(text(id))
|
||||
if (idx >= 0) group_index[group].splice(idx, 1)
|
||||
}
|
||||
|
||||
// Main entrypoint: render({ drawables, camera, target, ... })
|
||||
// No root tree.
|
||||
film2d.reindex = function(id, old_groups, new_groups) {
|
||||
for (var i = 0; i < old_groups.length; i++)
|
||||
film2d.unindex_group(id, old_groups[i])
|
||||
for (var i = 0; i < new_groups.length; i++)
|
||||
film2d.index_group(id, new_groups[i])
|
||||
}
|
||||
|
||||
film2d.get = function(id) {
|
||||
return registry[text(id)]
|
||||
}
|
||||
|
||||
// Query by group - returns array of drawables
|
||||
film2d.query = function(selector) {
|
||||
var result = []
|
||||
|
||||
if (selector.group) {
|
||||
var ids = group_index[selector.group] || []
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var d = registry[ids[i]]
|
||||
if (d && d.visible != false) result.push(d)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
if (selector.groups) {
|
||||
var seen = {}
|
||||
for (var g = 0; g < selector.groups.length; g++) {
|
||||
var ids = group_index[selector.groups[g]] || []
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
if (!seen[ids[i]]) {
|
||||
seen[ids[i]] = true
|
||||
var d = registry[ids[i]]
|
||||
if (d && d.visible != false) result.push(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// All drawables
|
||||
for (var id in registry) {
|
||||
var d = registry[id]
|
||||
if (d && d.visible != false) result.push(d)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Get all groups a drawable belongs to
|
||||
film2d.get_groups = function(id) {
|
||||
var d = registry[text(id)]
|
||||
return d ? (d.groups || []) : []
|
||||
}
|
||||
|
||||
// List all known groups
|
||||
film2d.all_groups = function() {
|
||||
var groups = []
|
||||
for (var g in group_index)
|
||||
if (group_index[g].length > 0) groups.push(g)
|
||||
return groups
|
||||
}
|
||||
|
||||
// Render function - takes drawables directly, no tree traversal
|
||||
film2d.render = function(params, backend) {
|
||||
var drawables_in = params.drawables || []
|
||||
var drawables = params.drawables || []
|
||||
var camera = params.camera
|
||||
var target = params.target
|
||||
var target_size = params.target_size
|
||||
var clear_color = params.clear
|
||||
|
||||
if (drawables_in.length == 0) return {commands: []}
|
||||
if (drawables.length == 0) return {commands: []}
|
||||
|
||||
// flatten and resolve handles
|
||||
var resolved_drawables = _resolve_and_flatten(drawables_in)
|
||||
|
||||
// Sort by layer, then by Y
|
||||
resolved_drawables.sort(function(a, b) {
|
||||
var difflayer = a.layer - b.layer
|
||||
if (difflayer != 0) return difflayer
|
||||
return b.world_y - a.world_y
|
||||
// Sort by layer, then Y
|
||||
drawables.sort(function(a, b) {
|
||||
var dl = (a.layer || 0) - (b.layer || 0)
|
||||
if (dl != 0) return dl
|
||||
return (b.pos.y || 0) - (a.pos.y || 0)
|
||||
})
|
||||
|
||||
// Build render commands
|
||||
var commands = []
|
||||
commands.push({cmd: 'begin_render', target: target, clear: clear_color})
|
||||
|
||||
// Apply camera?
|
||||
// User didn't specify if film2d owns camera application or if it's baked.
|
||||
// Existing film2d used 'set_camera' command. preserving that.
|
||||
commands.push({cmd: 'set_camera', camera: camera})
|
||||
|
||||
// Batch and emit
|
||||
var batches = _batch_drawables(resolved_drawables)
|
||||
var current_scissor = null
|
||||
var batches = _batch_drawables(drawables)
|
||||
|
||||
for (var i = 0; i < batches.length; i++) {
|
||||
var batch = batches[i]
|
||||
|
||||
// Emit scissor if changed
|
||||
if (!_rect_equal(current_scissor, batch.scissor)) {
|
||||
commands.push({cmd: 'scissor', rect: batch.scissor})
|
||||
current_scissor = batch.scissor
|
||||
}
|
||||
|
||||
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 == 'text') {
|
||||
commands.push({
|
||||
cmd: 'draw_text',
|
||||
drawable: batch.drawable
|
||||
})
|
||||
} else if (batch.type == 'rect') {
|
||||
commands.push({
|
||||
cmd: 'draw_rect',
|
||||
drawable: batch.drawable
|
||||
})
|
||||
} else if (batch.type == 'particles') {
|
||||
commands.push({
|
||||
cmd: 'draw_batch',
|
||||
batch_type: 'particles',
|
||||
geometry: {sprites: batch.sprites},
|
||||
texture: batch.texture,
|
||||
material: batch.material
|
||||
})
|
||||
} else if (batch.type == 'texture_ref') {
|
||||
commands.push({
|
||||
cmd: 'draw_texture_ref',
|
||||
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 == 'text')
|
||||
commands.push({cmd: 'draw_text', drawable: batch.drawable})
|
||||
else if (batch.type == 'texture_ref')
|
||||
commands.push({cmd: 'draw_texture_ref', drawable: batch.drawable})
|
||||
}
|
||||
|
||||
|
||||
commands.push({cmd: 'end_render'})
|
||||
|
||||
return {target: target, commands: commands}
|
||||
return {commands: commands}
|
||||
}
|
||||
|
||||
// Convert input list (handles or structs) into flat list of primitive drawables
|
||||
function _resolve_and_flatten(inputs) {
|
||||
var out = []
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
var item = inputs[i]
|
||||
if (!item) continue
|
||||
|
||||
// If it's a handle (has _id), use it. If it's a raw struct, use it.
|
||||
// Handles are already mostly "drawable-like", but tilemaps need expansion.
|
||||
|
||||
if (item.type == 'tilemap') {
|
||||
// Tilemaps expand to many sprites
|
||||
_expand_tilemap(item, out)
|
||||
} else if (item.type == 'sprite') {
|
||||
_expand_sprite(item, out)
|
||||
} else if (item.type == 'text' || item.type == 'texture_ref' || item.type == 'particles' || item.type == 'rect') {
|
||||
// Pass through (maybe copy needed if we mutate for batching?)
|
||||
// We need 'world_y' for sorting.
|
||||
// Ensure pos is world pos.
|
||||
// If item is a handle, it has x/y which are world x/y in flat model.
|
||||
var d = _clone_for_render(item)
|
||||
d.world_y = d.pos.y
|
||||
out.push(d)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function _clone_for_render(item) {
|
||||
// Start with a shallow copy or extraction of render properties
|
||||
var d = {
|
||||
type: item.type,
|
||||
layer: item.layer,
|
||||
pos: {x: item.x != null ? item.x : item.pos.x, y: item.y != null ? item.y : item.pos.y},
|
||||
color: item.color,
|
||||
scissor: item.scissor,
|
||||
// specific props
|
||||
text: item.text,
|
||||
font: item.font,
|
||||
size: item.size,
|
||||
mode: item.mode,
|
||||
sdf: item.sdf,
|
||||
outline_width: item.outline_width,
|
||||
outline_color: item.outline_color,
|
||||
anchor_x: item.anchor_x,
|
||||
anchor_y: item.anchor_y,
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
texture_target: item.texture_target,
|
||||
blend: item.blend,
|
||||
particles: item.particles,
|
||||
image: item.image,
|
||||
texture: item.texture,
|
||||
material: item.material
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
function _expand_sprite(node, out) {
|
||||
var x = node.x != null ? node.x : (node.pos ? node.pos.x : 0)
|
||||
var y = node.y != null ? node.y : (node.pos ? node.pos.y : 0)
|
||||
var w = node.width || 1
|
||||
var h = node.height || 1
|
||||
var ax = node.anchor_x || 0
|
||||
var ay = node.anchor_y || 0
|
||||
var color = node.color || {r:1,g:1,b:1,a:1}
|
||||
var layer = node.layer || 0
|
||||
var scissor = node.scissor
|
||||
|
||||
var x0 = x - w * ax
|
||||
var y0 = y - h * ay
|
||||
|
||||
if (node.slice) {
|
||||
// 9-slice expansion
|
||||
var s = node.slice
|
||||
var L = s.left != null ? s.left : (typeof s == 'number' ? s : 0)
|
||||
var R = s.right != null ? s.right : (typeof s == 'number' ? s : 0)
|
||||
var T = s.top != null ? s.top : (typeof s == 'number' ? s : 0)
|
||||
var B = s.bottom != null ? s.bottom : (typeof s == 'number' ? s : 0)
|
||||
|
||||
var stretch = s.stretch != null ? s.stretch : node.stretch
|
||||
var Sx = stretch != null ? (stretch.x || stretch) : w
|
||||
var Sy = stretch != null ? (stretch.y || stretch) : h
|
||||
|
||||
var WL = L * Sx
|
||||
var WR = R * Sx
|
||||
var HT = T * Sy
|
||||
var HB = B * Sy
|
||||
var WM = w - WL - WR
|
||||
var HM = h - HT - HB
|
||||
var UM = 1 - L - R
|
||||
var VM = 1 - T - B
|
||||
var TW = stretch != null ? UM * Sx : WM
|
||||
var TH = stretch != null ? VM * Sy : HM
|
||||
|
||||
function add(rect, uv) {
|
||||
out.push({
|
||||
type: 'sprite', layer: layer, world_y: y, pos: {x: rect.x, y: rect.y},
|
||||
image: node.image, texture: node.texture, material: node.material,
|
||||
width: rect.width, height: rect.height, anchor_x: 0, anchor_y: 0,
|
||||
uv_rect: uv, color: color, scissor: scissor
|
||||
})
|
||||
}
|
||||
|
||||
function tiled(rect, uv, tile_size) {
|
||||
var tx = tile_size ? (tile_size.x || tile_size) : rect.width
|
||||
var ty = tile_size ? (tile_size.y || tile_size) : rect.height
|
||||
var nx = number.ceiling(rect.width / tx - 0.00001)
|
||||
var ny = number.ceiling(rect.height / ty - 0.00001)
|
||||
for (var ix = 0; ix < nx; ix++) {
|
||||
for (var iy = 0; iy < ny; iy++) {
|
||||
var qw = number.min(tx, rect.width - ix * tx)
|
||||
var qh = number.min(ty, rect.height - iy * ty)
|
||||
var quv = {x: uv.x, y: uv.y, width: uv.width * (qw/tx), height: uv.height * (qh/ty)}
|
||||
add({x: rect.x + ix*tx, y: rect.y + iy*ty, width: qw, height: qh}, quv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TL, TM, TR
|
||||
add({x: x0, y: y0, width: WL, height: HT}, {x:0, y:0, width: L, height: T})
|
||||
tiled({x: x0 + WL, y: y0, width: WM, height: HT}, {x:L, y:0, width: UM, height: T}, {x: TW, y: HT})
|
||||
add({x: x0 + WL + WM, y: y0, width: WR, height: HT}, {x: 1-R, y:0, width: R, height: T})
|
||||
|
||||
// ML, MM, MR
|
||||
tiled({x: x0, y: y0 + HT, width: WL, height: HM}, {x:0, y:T, width: L, height: VM}, {x: WL, y: TH})
|
||||
tiled({x: x0 + WL, y: y0 + HT, width: WM, height: HM}, {x:L, y:T, width: UM, height: VM}, {x: TW, y: TH})
|
||||
tiled({x: x0 + WL + WM, y: y0 + HT, width: WR, height: HM}, {x: 1-R, y:T, width: R, height: VM}, {x: WR, y: TH})
|
||||
|
||||
// BL, BM, BR
|
||||
add({x: x0, y: y0 + HT + HM, width: WL, height: HB}, {x:0, y: 1-B, width: L, height: B})
|
||||
tiled({x: x0 + WL, y: y0 + HT + HM, width: WM, height: HB}, {x:L, y: 1-B, width: UM, height: B}, {x: TW, y: HB})
|
||||
add({x: x0 + WL + WM, y: y0 + HT + HM, width: WR, height: HB}, {x: 1-R, y: 1-B, width: R, height: B})
|
||||
|
||||
} else if (node.tile) {
|
||||
// Tiled mode logic ... simplified: assume pre-expanded in batched logic?
|
||||
// Actually batch logic handles simple sprites. We need to expand here.
|
||||
var tx = node.tile.x || node.tile
|
||||
var ty = node.tile.y || node.tile // if just number
|
||||
if (typeof node.tile == 'number') { tx = node.tile; ty = node.tile }
|
||||
|
||||
var nx = number.ceiling(w / tx - 0.00001)
|
||||
var ny = number.ceiling(h / ty - 0.00001)
|
||||
for (var ix = 0; ix < nx; ix++) {
|
||||
for (var iy = 0; iy < ny; iy++) {
|
||||
var qw = number.min(tx, w - ix * tx)
|
||||
var qh = number.min(ty, h - iy * ty)
|
||||
out.push({
|
||||
type: 'sprite', layer: layer, world_y: y, pos: {x: x0 + ix*tx, y: y0 + iy*ty},
|
||||
image: node.image, texture: node.texture, material: node.material,
|
||||
width: qw, height: qh, anchor_x: 0, anchor_y: 0,
|
||||
// uv logic for detailed tiling omitted for brevity but should be here
|
||||
// assuming simple repeat
|
||||
uv_rect: {x:0, y:0, width: qw/tx, height: qh/ty},
|
||||
color: color, scissor: scissor
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal sprite
|
||||
out.push(_clone_for_render(node))
|
||||
}
|
||||
}
|
||||
|
||||
function _expand_tilemap(node, out) {
|
||||
var tiles = node.tiles || []
|
||||
var x = node.x || 0
|
||||
var y = node.y || 0
|
||||
var offset_x = node.offset_x || 0
|
||||
var offset_y = node.offset_y || 0
|
||||
var scale_x = node.scale_tile_x || node.scale_x || 1
|
||||
var scale_y = node.scale_tile_y || node.scale_y || 1
|
||||
var color = node.color || {r:1,g:1,b:1,a:1}
|
||||
|
||||
for (var ix = 0; ix < tiles.length; ix++) {
|
||||
if (!tiles[ix]) continue
|
||||
for (var iy = 0; iy < tiles[ix].length; iy++) {
|
||||
var tile = tiles[ix][iy]
|
||||
if (!tile) continue
|
||||
|
||||
var world_x = x + (ix + offset_x) * scale_x
|
||||
var world_y = y + (iy + offset_y) * scale_y
|
||||
|
||||
out.push({
|
||||
type: 'sprite',
|
||||
layer: node.layer || 0,
|
||||
world_y: world_y,
|
||||
pos: {x: world_x, y: world_y},
|
||||
image: tile,
|
||||
texture: tile,
|
||||
width: scale_x,
|
||||
height: scale_y,
|
||||
anchor_x: 0,
|
||||
anchor_y: 0,
|
||||
color: color,
|
||||
material: node.material,
|
||||
scissor: node.scissor
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Batch drawables for efficient rendering
|
||||
function _batch_drawables(drawables) {
|
||||
var batches = []
|
||||
var current_batch = null
|
||||
|
||||
var mat = {blend: 'alpha', sampler: 'nearest'}
|
||||
var current = null
|
||||
var default_mat = {blend: 'alpha', sampler: 'nearest'}
|
||||
|
||||
array.for(drawables, drawable => {
|
||||
if (drawable.type == 'sprite') {
|
||||
var texture = drawable.texture || drawable.image
|
||||
var material = drawable.material || mat
|
||||
var scissor = drawable.scissor
|
||||
for (var i = 0; i < drawables.length; i++) {
|
||||
var d = drawables[i]
|
||||
|
||||
if (d.type == 'sprite') {
|
||||
var tex = d.texture || d.image
|
||||
var mat = d.material || default_mat
|
||||
|
||||
// Check if can merge with current batch
|
||||
if (current_batch &&
|
||||
current_batch.type == 'sprite_batch' &&
|
||||
current_batch.texture == texture &&
|
||||
_rect_equal(current_batch.scissor, scissor) &&
|
||||
_materials_equal(current_batch.material, material)) {
|
||||
current_batch.sprites.push(drawable)
|
||||
if (current && current.type == 'sprite_batch' && current.texture == tex && _mat_eq(current.material, mat)) {
|
||||
current.sprites.push(d)
|
||||
} else {
|
||||
if (current_batch) batches.push(current_batch)
|
||||
current_batch = {
|
||||
type: 'sprite_batch',
|
||||
texture: texture,
|
||||
material: material,
|
||||
scissor: scissor,
|
||||
sprites: [drawable]
|
||||
}
|
||||
if (current) batches.push(current)
|
||||
current = {type: 'sprite_batch', texture: tex, material: mat, sprites: [d]}
|
||||
}
|
||||
} else {
|
||||
// Non-sprite: flush batch, add individually
|
||||
if (current_batch) {
|
||||
batches.push(current_batch)
|
||||
current_batch = null
|
||||
if (current) {
|
||||
batches.push(current)
|
||||
current = null
|
||||
}
|
||||
batches.push({type: drawable.type, drawable: drawable, scissor: drawable.scissor})
|
||||
batches.push({type: d.type, drawable: d})
|
||||
}
|
||||
})
|
||||
|
||||
if (current_batch) batches.push(current_batch)
|
||||
}
|
||||
|
||||
if (current) batches.push(current)
|
||||
return batches
|
||||
}
|
||||
|
||||
function _rect_equal(a, b) {
|
||||
if (!a && !b) return true
|
||||
if (!a || !b) return false
|
||||
return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height
|
||||
}
|
||||
|
||||
function _materials_equal(a, b) {
|
||||
function _mat_eq(a, b) {
|
||||
if (!a || !b) return a == b
|
||||
return a.blend == b.blend && a.sampler == b.sampler && a.shader == b.shader
|
||||
return a.blend == b.blend && a.sampler == b.sampler
|
||||
}
|
||||
|
||||
return film2d
|
||||
return film2d
|
||||
28
graphics.cm
28
graphics.cm
@@ -161,12 +161,12 @@ function create_image(path){
|
||||
var raw = decode_image(bytes, ext);
|
||||
|
||||
/* ── Case A: single surface (from make_texture) ────────────── */
|
||||
if(raw && raw.width && raw.pixels && !isa(raw, array)) {
|
||||
if(raw && raw.width && raw.pixels && !is_array(raw)) {
|
||||
return new graphics.Image(raw)
|
||||
}
|
||||
|
||||
/* ── Case B: array of surfaces (from make_gif) ────────────── */
|
||||
if(isa(raw, array)) {
|
||||
if(is_array(raw)) {
|
||||
// Single frame GIF returns array with one surface
|
||||
if(raw.length == 1 && !raw[0].time) {
|
||||
return new graphics.Image(raw[0])
|
||||
@@ -175,16 +175,16 @@ function create_image(path){
|
||||
return makeAnim(wrapFrames(raw), true);
|
||||
}
|
||||
|
||||
if(typeof raw == 'object' && !raw.width) {
|
||||
if(is_object(raw) && !raw.width) {
|
||||
if(raw.surface)
|
||||
return new graphics.Image(raw.surface)
|
||||
|
||||
if(raw.frames && isa(raw.frames, array) && raw.loop != null)
|
||||
if(raw.frames && is_array(raw.frames) && raw.loop != null)
|
||||
return makeAnim(wrapFrames(raw.frames), !!raw.loop);
|
||||
|
||||
def anims = {};
|
||||
for(def [name, anim] of Object.entries(raw)){
|
||||
if(anim && isa(anim.frames, array))
|
||||
if(anim && is_array(anim.frames))
|
||||
anims[name] = makeAnim(wrapFrames(anim.frames), !!anim.loop);
|
||||
else if(anim && anim.surface)
|
||||
anims[name] = new graphics.Image(anim.surface);
|
||||
@@ -250,7 +250,7 @@ graphics.from_surface = function(surf)
|
||||
|
||||
graphics.from = function(id, data)
|
||||
{
|
||||
if (typeof id != 'string')
|
||||
if (!is_text(id))
|
||||
throw new Error('Expected a string ID')
|
||||
|
||||
if (data instanceof ArrayBuffer)
|
||||
@@ -260,7 +260,7 @@ graphics.from = function(id, data)
|
||||
graphics.texture = function texture(path) {
|
||||
if (path instanceof graphics.Image) return path
|
||||
|
||||
if (typeof path != 'string')
|
||||
if (!is_text(path))
|
||||
throw new Error('need a string for graphics.texture')
|
||||
|
||||
var parts = path.split(':')
|
||||
@@ -269,7 +269,7 @@ graphics.texture = function texture(path) {
|
||||
var frameIndex = parts[2]
|
||||
|
||||
// Handle the case where animName is actually a frame index (e.g., "gears:0")
|
||||
if (animName != null && frameIndex == null && !isa(number(animName), null)) {
|
||||
if (animName != null && frameIndex == null && !is_null(number(animName))) {
|
||||
frameIndex = number(animName)
|
||||
animName = null
|
||||
}
|
||||
@@ -294,7 +294,7 @@ graphics.texture = function texture(path) {
|
||||
// Handle frame index without animation name (e.g., "gears:0")
|
||||
if (!animName && frameIndex != null) {
|
||||
// If cached is a single animation (has .frames property)
|
||||
if (cached.frames && isa(cached.frames, array)) {
|
||||
if (cached.frames && is_array(cached.frames)) {
|
||||
var idx = number(frameIndex)
|
||||
if (idx == null) return cached
|
||||
// Wrap the index
|
||||
@@ -318,7 +318,7 @@ graphics.texture = function texture(path) {
|
||||
}
|
||||
|
||||
// If cached is a single animation (has .frames property)
|
||||
if (cached.frames && isa(cached.frames, array)) {
|
||||
if (cached.frames && is_array(cached.frames)) {
|
||||
if (frameIndex != null) {
|
||||
var idx = number(frameIndex)
|
||||
if (isNaN(idx)) return cached
|
||||
@@ -331,7 +331,7 @@ graphics.texture = function texture(path) {
|
||||
}
|
||||
|
||||
// If cached is an object of multiple animations
|
||||
if (typeof cached == 'object' && !cached.frames) {
|
||||
if (is_object(cached) && !cached.frames) {
|
||||
var anim = cached[animName]
|
||||
if (!anim)
|
||||
throw new Error(`animation ${animName} not found in ${id}`)
|
||||
@@ -343,7 +343,7 @@ graphics.texture = function texture(path) {
|
||||
if (anim instanceof graphics.Image) {
|
||||
// Single image animation - any frame index returns the image
|
||||
return anim
|
||||
} else if (anim.frames && isa(anim.frames, array)) {
|
||||
} else if (anim.frames && is_array(anim.frames)) {
|
||||
// Multi-frame animation - wrap the index
|
||||
idx = idx % anim.frames.length
|
||||
return anim.frames[idx].image
|
||||
@@ -412,8 +412,8 @@ var fontcache = {}
|
||||
var datas = []
|
||||
|
||||
graphics.get_font = function get_font(path) {
|
||||
if (isa(path, object)) return path
|
||||
if (!isa(path, text))
|
||||
if (is_object(path)) return path
|
||||
if (!is_text(path))
|
||||
throw new Error(`Can't find font with path: ${path}`)
|
||||
|
||||
var parts = path.split('.')
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
// particles
|
||||
// particles
|
||||
|
||||
return function() {
|
||||
|
||||
}
|
||||
139
particles2d.cm
139
particles2d.cm
@@ -1,5 +1,140 @@
|
||||
// particles2d.cm - Particle system factory
|
||||
//
|
||||
// Creates particle data objects that register with film2d
|
||||
// Also provides emitter update logic
|
||||
|
||||
var film2d = use('film2d')
|
||||
var random = use('random').random
|
||||
|
||||
return function() {
|
||||
var particles2d_proto = {
|
||||
type: 'particles',
|
||||
|
||||
destroy: function() {
|
||||
film2d.unregister(this._id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Emitter management
|
||||
var emitters = {
|
||||
// Spawn a particle for an emitter
|
||||
spawn: function(emitter) {
|
||||
emitter.particles.push({
|
||||
pos: {
|
||||
x: emitter.pos.x + (random() - 0.5) * emitter.spawn_area.width,
|
||||
y: emitter.pos.y + (random() - 0.5) * emitter.spawn_area.height
|
||||
},
|
||||
velocity: {
|
||||
x: emitter.velocity.x + (random() - 0.5) * emitter.velocity_var.x,
|
||||
y: emitter.velocity.y + (random() - 0.5) * emitter.velocity_var.y
|
||||
},
|
||||
life: emitter.life,
|
||||
time: 0,
|
||||
max_scale: emitter.scale + (random() - 0.5) * emitter.scale_var,
|
||||
scale: 0,
|
||||
color: {
|
||||
r: emitter.color.r,
|
||||
g: emitter.color.g + (random() - 0.5) * 0.3,
|
||||
b: emitter.color.b,
|
||||
a: 1
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// Update an emitter and its particles
|
||||
update: function(emitter, dt) {
|
||||
// Spawn new particles
|
||||
if (emitter.rate > 0) {
|
||||
emitter.spawn_timer = (emitter.spawn_timer || 0) + dt
|
||||
var pp = 1 / emitter.rate
|
||||
while (emitter.spawn_timer > pp) {
|
||||
emitter.spawn_timer -= pp
|
||||
emitters.spawn(emitter)
|
||||
}
|
||||
}
|
||||
|
||||
// Update existing particles
|
||||
for (var i = emitter.particles.length - 1; i >= 0; i--) {
|
||||
var 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
|
||||
if (p.time < grow_for) {
|
||||
p.scale = lerp(0, p.max_scale, p.time / grow_for)
|
||||
} else if (p.time > p.life - shrink_for) {
|
||||
p.scale = lerp(0, p.max_scale, (p.life - p.time) / shrink_for)
|
||||
} else {
|
||||
p.scale = p.max_scale
|
||||
}
|
||||
|
||||
// Alpha fade
|
||||
var alpha = 1
|
||||
if (p.time > p.life * 0.7) {
|
||||
alpha = 1 - (p.time - p.life * 0.7) / (p.life * 0.3)
|
||||
}
|
||||
p.color.a = alpha
|
||||
|
||||
// Remove dead particles
|
||||
if (p.time >= p.life) {
|
||||
emitter.particles.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync to film2d if handle provided
|
||||
if (emitter.handle) {
|
||||
emitter.handle.particles = emitter.particles
|
||||
}
|
||||
},
|
||||
|
||||
// Create an emitter config
|
||||
create: function(config) {
|
||||
return {
|
||||
pos: config.pos || {x: 0, y: 0},
|
||||
spawn_area: config.spawn_area || {width: 10, height: 10},
|
||||
velocity: config.velocity || {x: 0, y: 100},
|
||||
velocity_var: config.velocity_var || {x: 20, y: 20},
|
||||
life: config.life || 2,
|
||||
rate: config.rate || 10,
|
||||
scale: config.scale || 1,
|
||||
scale_var: config.scale_var || 0.3,
|
||||
grow_for: config.grow_for || 0.3,
|
||||
shrink_for: config.shrink_for || 0.5,
|
||||
color: config.color || {r: 1, g: 1, b: 1, a: 1},
|
||||
spawn_timer: 0,
|
||||
particles: [],
|
||||
handle: config.handle || null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lerp(a, b, t) { return a + (b - a) * t }
|
||||
|
||||
// Factory function - auto-registers with film2d
|
||||
var factory = function(props) {
|
||||
var defaults = {
|
||||
type: 'particles',
|
||||
image: null,
|
||||
width: 16,
|
||||
height: 16,
|
||||
plane: 'default',
|
||||
layer: 0,
|
||||
tags: [],
|
||||
particles: []
|
||||
}
|
||||
|
||||
var data = {}
|
||||
for(var k in defaults) data[k] = defaults[k]
|
||||
for(var k in props) data[k] = props[k]
|
||||
|
||||
var newparticles = meme(particles2d_proto, data)
|
||||
film2d.register(newparticles)
|
||||
return newparticles
|
||||
}
|
||||
|
||||
// Attach emitter helpers to factory
|
||||
factory.emitters = emitters
|
||||
|
||||
return factory
|
||||
24
prosperon.cm
24
prosperon.cm
@@ -329,9 +329,9 @@ function pack_ubo(obj, ubo_type, reflection) {
|
||||
|
||||
// Convert value to appropriate format based on type
|
||||
if (member.type == "vec4") {
|
||||
if (isa(value, array)) {
|
||||
if (is_array(value)) {
|
||||
result_blob.write_blob(geometry.array_blob(value));
|
||||
} else if (typeof value == "object" && value.r != null) {
|
||||
} else if (is_object(value) && value.r != null) {
|
||||
// Color object
|
||||
result_blob.write_blob(geometry.array_blob([value.r, value.g, value.b, value.a || 1]));
|
||||
} else {
|
||||
@@ -339,14 +339,14 @@ function pack_ubo(obj, ubo_type, reflection) {
|
||||
result_blob.write_blob(geometry.array_blob([value, value, value, value]));
|
||||
}
|
||||
} else if (member.type == "vec3") {
|
||||
if (isa(value, array)) {
|
||||
if (is_array(value)) {
|
||||
result_blob.write_blob(geometry.array_blob(value));
|
||||
} else if (typeof value == 'object' && value.r != null)
|
||||
} else if (is_object(value) && value.r != null)
|
||||
result_blob.write_blob(geometry.array_blob([value.r, value.g, value.b]));
|
||||
else
|
||||
result_blob.write_blob(geometry.array_blob([value, value, value]));
|
||||
} else if (member.type == "vec2") {
|
||||
if (isa(value, array)) {
|
||||
if (is_array(value)) {
|
||||
result_blob.write_blob(geometry.array_blob(value));
|
||||
} else {
|
||||
result_blob.write_blob(geometry.array_blob([value, value]));
|
||||
@@ -459,15 +459,15 @@ function pack_model_buffer(material) {
|
||||
|
||||
// Use material properties if available
|
||||
if (material && material.model) {
|
||||
if (isa(material.model, array) && material.model.length >= 16) {
|
||||
if (is_array(material.model) && material.model.length >= 16) {
|
||||
model_matrix = material.model.slice(0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
if (material && material.color) {
|
||||
if (isa(material.color, array) && material.color.length >= 4) {
|
||||
if (is_array(material.color) && material.color.length >= 4) {
|
||||
color = material.color.slice(0, 4);
|
||||
} else if (typeof material.color == "object" && material.color.r != null) {
|
||||
} else if (is_object(material.color) && material.color.r != null) {
|
||||
color = [material.color.r, material.color.g, material.color.b, material.color.a || 1];
|
||||
}
|
||||
}
|
||||
@@ -586,7 +586,7 @@ function poll_input() {
|
||||
var evs = input.get_events()
|
||||
|
||||
// Filter and transform events
|
||||
if (isa(evs, array)) {
|
||||
if (is_array(evs)) {
|
||||
var filteredEvents = []
|
||||
// var wantMouse = imgui.wantmouse()
|
||||
// var wantKeys = imgui.wantkeys()
|
||||
@@ -779,7 +779,7 @@ function render_geom(geom, img, pipeline = get_pipeline_for_material(null), mate
|
||||
cmd_fns.draw_image = function(cmd)
|
||||
{
|
||||
var img
|
||||
if (typeof cmd.image == 'string')
|
||||
if (is_text(cmd.image))
|
||||
img = graphics.texture(cmd.image)
|
||||
else
|
||||
img = cmd.image
|
||||
@@ -854,7 +854,7 @@ cmd_fns.tilemap = function(cmd)
|
||||
cmd_fns.geometry = function(cmd)
|
||||
{
|
||||
var img
|
||||
if (typeof cmd.image == 'object') {
|
||||
if (is_object(cmd.image)) {
|
||||
img = cmd.image
|
||||
} else {
|
||||
if (!cmd.image) return
|
||||
@@ -886,7 +886,7 @@ cmd_fns.draw_slice9 = function(cmd)
|
||||
|
||||
// Convert single slice value to LRTB object if needed
|
||||
var slice_lrtb = cmd.slice
|
||||
if (typeof cmd.slice == 'number') {
|
||||
if (is_number(cmd.slice)) {
|
||||
var slice_val = cmd.slice
|
||||
if (slice_val > 0 && slice_val < 1) {
|
||||
slice_lrtb = {
|
||||
|
||||
@@ -38,7 +38,7 @@ function isRecognizedExtension(ext) {
|
||||
}
|
||||
|
||||
function find_in_path(filename, exts = []) {
|
||||
if (typeof filename != 'string') return null
|
||||
if (!is_text(filename)) return null
|
||||
|
||||
if (filename.includes('.')) {
|
||||
var candidate = filename // possibly need "/" ?
|
||||
|
||||
@@ -1027,7 +1027,7 @@ function _preload_textures(commands) {
|
||||
|
||||
for (var cmd of commands) {
|
||||
if (cmd.cmd == 'draw_batch' && cmd.texture) {
|
||||
if (typeof cmd.texture == 'string') {
|
||||
if (is_text(cmd.texture)) {
|
||||
paths[cmd.texture] = true
|
||||
}
|
||||
}
|
||||
|
||||
96
sprite.cm
96
sprite.cm
@@ -1,85 +1,71 @@
|
||||
// sprite.cm - Sprite node factory
|
||||
//
|
||||
// Returns a function that creates sprite instances via meme()
|
||||
var film2d = use('film2d')
|
||||
|
||||
var dirty = {}
|
||||
|
||||
var sprite = {
|
||||
// Setters that mark dirty
|
||||
var sprite_proto = {
|
||||
type: 'sprite',
|
||||
|
||||
set_pos: function(x, y) {
|
||||
if (!this.pos) this.pos = {x: 0, y: 0}
|
||||
if (this.pos.x == x && this.pos.y == y) return this
|
||||
this.pos.x = x
|
||||
this.pos.y = y
|
||||
this.dirty |= 1 // TRANSFORM
|
||||
return this
|
||||
},
|
||||
|
||||
set_image: function(img) {
|
||||
if (this.image == img) return this
|
||||
this.image = img
|
||||
this.dirty |= 2 // CONTENT
|
||||
set_groups: function(groups) {
|
||||
var old_groups = this.groups
|
||||
this.groups = groups
|
||||
film2d.reindex(this._id, old_groups, groups)
|
||||
return this
|
||||
},
|
||||
|
||||
set_size: function(w, h) {
|
||||
if (this.width == w && this.height == h) return this
|
||||
this.width = w
|
||||
this.height = h
|
||||
this.dirty |= 2 // CONTENT
|
||||
add_group: function(group) {
|
||||
if (this.groups.indexOf(group) < 0) {
|
||||
this.groups.push(group)
|
||||
film2d.index_group(this._id, group)
|
||||
}
|
||||
return this
|
||||
},
|
||||
|
||||
set_anchor: function(x, y) {
|
||||
if (this.anchor_x == x && this.anchor_y == y) return this
|
||||
this.anchor_x = x
|
||||
this.anchor_y = y
|
||||
this.dirty |= 1 // TRANSFORM
|
||||
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
|
||||
},
|
||||
|
||||
set_color: function(color) {
|
||||
if (!this.color) this.color = {r: 1, g: 1, b: 1, a: 1}
|
||||
if (this.color.r == color.r && this.color.g == color.g && this.color.b == color.b && this.color.a == color.a) return this
|
||||
this.color.r = color.r
|
||||
this.color.g = color.g
|
||||
this.color.b = color.b
|
||||
this.color.a = a
|
||||
this.dirty |= 2 // CONTENT
|
||||
return this
|
||||
},
|
||||
|
||||
set_opacity: function(o) {
|
||||
if (this.opacity == o) return this
|
||||
this.opacity = o
|
||||
this.dirty |= 2 // CONTENT
|
||||
return this
|
||||
destroy: function() {
|
||||
film2d.unregister(this._id)
|
||||
}
|
||||
}
|
||||
|
||||
stone(sprite)
|
||||
|
||||
// Factory function
|
||||
return function(props) {
|
||||
var defaults = {
|
||||
pos: {x:0,y:0},
|
||||
layer: 0,
|
||||
type: 'sprite',
|
||||
pos: {x: 0, y: 0},
|
||||
image: null,
|
||||
width: 1,
|
||||
height: 1,
|
||||
anchor_x: 0,
|
||||
anchor_y: 0,
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
color: null,
|
||||
uv_rect: null,
|
||||
slice: null,
|
||||
tile: null,
|
||||
material: null,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
rotation: 0,
|
||||
color: {r: 1, g: 1, b: 1, a: 1},
|
||||
opacity: 1,
|
||||
layer: 0,
|
||||
groups: ['default'],
|
||||
visible: true
|
||||
}
|
||||
|
||||
var newsprite = meme(sprite, [defaults,props])
|
||||
newsprite[dirty] = 7
|
||||
return newsprite
|
||||
var data = {}
|
||||
for (var k in defaults) data[k] = defaults[k]
|
||||
for (var k in props) data[k] = props[k]
|
||||
|
||||
// Ensure groups is array
|
||||
if (!data.groups) data.groups = ['default']
|
||||
if (is_text(data.groups)) data.groups = [data.groups]
|
||||
|
||||
var s = meme(sprite_proto, data)
|
||||
film2d.register(s)
|
||||
return s
|
||||
}
|
||||
57
text2d.cm
57
text2d.cm
@@ -1,14 +1,51 @@
|
||||
var text2d = {
|
||||
text: "",
|
||||
pos: {x:0,y:0},
|
||||
layer: 0,
|
||||
font: "fonts/dos",
|
||||
size: 16,
|
||||
color: {r:1,g:1,b:1,a:1}
|
||||
// text2d.cm - Text factory
|
||||
//
|
||||
// Creates text data objects that register with film2d
|
||||
|
||||
var film2d = use('film2d')
|
||||
|
||||
var text2d_proto = {
|
||||
type: 'text',
|
||||
|
||||
set_text: function(t) {
|
||||
this.text = t
|
||||
return this
|
||||
},
|
||||
|
||||
set_pos: function(x, y) {
|
||||
this.pos.x = x
|
||||
this.pos.y = y
|
||||
return this
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
film2d.unregister(this._id)
|
||||
}
|
||||
}
|
||||
|
||||
stone(text2d)
|
||||
|
||||
// Factory function - auto-registers with film2d
|
||||
return function(props) {
|
||||
return meme(text2d, props)
|
||||
var defaults = {
|
||||
type: 'text',
|
||||
text: "",
|
||||
pos: {x: 0, y: 0},
|
||||
plane: 'default',
|
||||
layer: 0,
|
||||
font: "fonts/dos",
|
||||
size: 16,
|
||||
color: {r: 1, g: 1, b: 1, a: 1},
|
||||
mode: null,
|
||||
sdf: null,
|
||||
outline_width: null,
|
||||
outline_color: null,
|
||||
tags: []
|
||||
}
|
||||
|
||||
var data = {}
|
||||
for(var k in defaults) data[k] = defaults[k]
|
||||
for(var k in props) data[k] = props[k]
|
||||
|
||||
var newtext = meme(text2d_proto, data)
|
||||
film2d.register(newtext)
|
||||
return newtext
|
||||
}
|
||||
@@ -67,7 +67,7 @@ tilemap.prototype =
|
||||
while (this.tiles.length <= x) this.tiles.push([]);
|
||||
|
||||
// Convert string to image object if needed, or handle null to remove tile
|
||||
if (image && typeof image == 'string') {
|
||||
if (image && is_text(image)) {
|
||||
var graphics = use('graphics');
|
||||
image = graphics.texture(image);
|
||||
}
|
||||
|
||||
33
tilemap2d.cm
33
tilemap2d.cm
@@ -1,16 +1,23 @@
|
||||
// tilemap.js - MUCH SIMPLER
|
||||
// tilemap2d.cm - Tilemap factory
|
||||
//
|
||||
// Creates tilemap data objects that register with film2d
|
||||
|
||||
var film2d = use('film2d')
|
||||
|
||||
var tilemap = {
|
||||
at(pos) {
|
||||
type: 'tilemap',
|
||||
|
||||
at: function(pos) {
|
||||
var x = pos.x - this.offset_x
|
||||
var y = pos.y - this.offset_y
|
||||
if (!this.tiles[x]) return null
|
||||
return this.tiles[x][y]
|
||||
},
|
||||
set(pos, image) {
|
||||
|
||||
set: function(pos, image) {
|
||||
var x = pos.x - this.offset_x
|
||||
var y = pos.y - this.offset_y
|
||||
|
||||
// Expand tiles array if needed
|
||||
while (this.tiles.length <= x)
|
||||
this.tiles.push([])
|
||||
|
||||
@@ -19,20 +26,34 @@ var tilemap = {
|
||||
|
||||
this.tiles[x][y] = image
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
film2d.unregister(this._id)
|
||||
}
|
||||
}
|
||||
|
||||
stone(tilemap)
|
||||
|
||||
// Factory function - auto-registers with film2d
|
||||
return function(props) {
|
||||
var defaults = {
|
||||
type: 'tilemap',
|
||||
tiles: [],
|
||||
offset_x: 0,
|
||||
offset_y: 0,
|
||||
plane: 'default',
|
||||
layer: 0,
|
||||
tile_width: 1,
|
||||
tile_height: 1,
|
||||
tags: []
|
||||
}
|
||||
var newtilemap = meme(tilemap, [defaults,props])
|
||||
newtilemap.tiles = []
|
||||
|
||||
var data = {}
|
||||
for(var k in defaults) data[k] = defaults[k]
|
||||
for(var k in props) data[k] = props[k]
|
||||
|
||||
var newtilemap = meme(tilemap, data)
|
||||
newtilemap.tiles = [] // Initialize tiles
|
||||
film2d.register(newtilemap)
|
||||
return newtilemap
|
||||
}
|
||||
|
||||
2
tween.cm
2
tween.cm
@@ -41,7 +41,7 @@ function Tween(obj) {
|
||||
Tween.prototype.to = function(props, duration, start_time) {
|
||||
for (var key in props) {
|
||||
var value = props[key]
|
||||
if (isa(value, object)) {
|
||||
if (is_object(value)) {
|
||||
// Handle nested objects by flattening them
|
||||
for (var subkey in value) {
|
||||
var flatKey = key + '.' + subkey
|
||||
|
||||
Reference in New Issue
Block a user