Compare commits

...

4 Commits

Author SHA1 Message Date
John Alanbrook
b88d22aefc fix find/search error for mask 2026-02-26 16:40:38 -06:00
John Alanbrook
9147db6fdc more examples 2026-02-26 15:54:11 -06:00
John Alanbrook
ce6b0ddb3a rm push/pop 2026-02-26 08:13:27 -06:00
John Alanbrook
ef7e3e6449 fix clay 2026-02-26 00:56:25 -06:00
420 changed files with 2479 additions and 336 deletions

View File

@@ -234,7 +234,7 @@ action.on_input = function(action_id, evt)
var matched_actions = []
arrfor(array(this.action_map), mapped_action => {
if (find(this.action_map[mapped_action], action_id) != null) {
push(matched_actions, mapped_action)
matched_actions[] = mapped_action
if (evt.pressed)
this.down[mapped_action] = true

193
clay.cm
View File

@@ -1,17 +1,21 @@
// clay2.cm - Revised UI layout engine emitting flat drawables
// clay.cm - UI layout engine emitting flat drawables with annotated tree
//
// Changes from clay.cm:
// - No __proto__, uses meme/merge
// - Uses meme/merge for config chains
// - Emits flat list of drawables for film2d
// - Supports scissor clipping
//
// Now returns [drawable, drawable, ...] instead of {type:'group', ...}
// - Returns annotated tree root with .drawables for clay_input compatibility
var layout = use('layout')
var graphics = use('graphics')
var clay = {}
// Unique key objects for tree traversal (used by clay_input)
var CHILDREN = {}
var PARENT = {}
clay.CHILDREN = CHILDREN
clay.PARENT = PARENT
// Layout context
var lay_ctx = layout.make_context()
@@ -65,28 +69,50 @@ var config_stack = []
// Rewriting state management for cleaner recursion
var tree_stack = []
var _next_id = 0
function annotate_tree(node, root_height, parent_node) {
var rect = lay_ctx.get_rect(node.id)
node.boundingbox = {
x: rect.x,
y: root_height - (rect.y + rect.height),
width: rect.width,
height: rect.height
}
node[CHILDREN] = node.children
node[PARENT] = parent_node
arrfor(node.children, function(child) {
annotate_tree(child, root_height, node)
})
}
clay.layout = function(fn, size) {
var sz = is_array(size) ? {width: size[0], height: size[1]} : size
lay_ctx.reset()
_next_id = 0
var root_id = lay_ctx.item()
lay_ctx.set_size(root_id, size)
lay_ctx.set_size(root_id, sz)
lay_ctx.set_contain(root_id, layout.contain.row)
var root_node = {
id: root_id,
config: meme(base_config, {size: size}),
config: meme(base_config, {size: sz}),
children: []
}
tree_stack = [root_node]
fn() // User builds tree
lay_ctx.run()
// Post-layout: build flat drawable list
return build_drawables(root_node, size.height)
// Annotate tree for clay_input (boundingbox, CHILDREN, PARENT)
annotate_tree(root_node, sz.height, null)
// Build flat drawable list and attach to tree root
root_node.drawables = build_drawables(root_node, sz.height)
return root_node
}
function build_drawables(node, root_height, parent, parent_scissor) {
@@ -130,8 +156,10 @@ function build_drawables(node, root_height, parent, parent_scissor) {
// Background
if (node.config.background_image) {
_next_id = _next_id + 1
if (node.config.slice) {
push(drawables, {
drawables[] = {
_id: _next_id,
type: 'sprite',
image: node.config.background_image,
pos: {x: vis_x, y: vis_y},
@@ -139,11 +167,12 @@ function build_drawables(node, root_height, parent, parent_scissor) {
height: rect.height,
slice: node.config.slice,
color: node.config.background_color || {r:1, g:1, b:1, a:1},
layer: p_layer - 0.1, // slightly behind content
layer: p_layer - 0.1,
scissor: current_scissor
})
}
} else {
push(drawables, {
drawables[] = {
_id: _next_id,
type: 'sprite',
image: node.config.background_image,
pos: {x: vis_x, y: vis_y},
@@ -152,10 +181,12 @@ function build_drawables(node, root_height, parent, parent_scissor) {
color: node.config.background_color || {r:1, g:1, b:1, a:1},
layer: p_layer - 0.1,
scissor: current_scissor
})
}
}
} else if (node.config.background_color) {
push(drawables, {
_next_id = _next_id + 1
drawables[] = {
_id: _next_id,
type: 'rect',
pos: {x: vis_x, y: vis_y},
width: rect.width,
@@ -163,43 +194,39 @@ function build_drawables(node, root_height, parent, parent_scissor) {
color: node.config.background_color,
layer: p_layer - 0.1,
scissor: current_scissor
})
}
}
// Content (Image/Text)
if (node.config.image) {
push(drawables, {
_next_id = _next_id + 1
drawables[] = {
_id: _next_id,
type: 'sprite',
image: node.config.image,
pos: {x: vis_x, y: vis_y},
width: rect.width,
height: rect.height,
height: rect.height,
color: node.config.color,
layer: p_layer,
scissor: current_scissor
})
}
}
if (node.config.text) {
push(drawables, {
_next_id = _next_id + 1
drawables[] = {
_id: _next_id,
type: 'text',
text: node.config.text,
font: node.config.font_path,
size: node.config.font_size,
size: node.config.font_size,
color: node.config.color,
pos: {x: vis_x, y: vis_y + rect.height}, // Baseline adjustment
anchor_y: 1.0, // Text usually draws from baseline up or top down?
// film2d text uses top-left by default unless anchor set.
// Original clay put it at `y + rect.height`.
// Let's assume origin top-left, so we might need anchor adjustment or just position.
// If frame is top-down (0 at top), `abs_y` is top.
// `rect.y` in layout is bottom-up? "rect.y is from bottom" says original comment.
// `abs_y = root_height - (rect.y + rect.height)` -> Top edge of element.
// Text usually wants baseline.
// If we put it at `vis_y + rect.height`, that's bottom of element.
pos: {x: vis_x, y: vis_y + rect.height},
anchor_y: 1.0,
layer: p_layer,
scissor: current_scissor
})
}
}
// Children
@@ -216,6 +243,20 @@ function build_drawables(node, root_height, parent, parent_scissor) {
function process_configs(configs) {
var cfg = meme(base_config, configs)
// Parse shorthand font string (e.g. 'blackcastle.64') into font_path and font_size
var font_parts = null
var parsed_size = null
if (cfg.font && is_text(cfg.font)) {
font_parts = array(cfg.font, '.')
if (length(font_parts) >= 2) {
parsed_size = number(font_parts[length(font_parts) - 1])
if (parsed_size) {
cfg.font_size = parsed_size
cfg.font_path = font_parts[0]
}
}
}
cfg.color = normalize_color(cfg.color, base_config.color)
if (cfg.background_color) cfg.background_color = normalize_color(cfg.background_color, {r:1,g:1,b:1,a:1})
@@ -250,15 +291,15 @@ function push_node(configs, contain_mode) {
// Add to parent
var parent = tree_stack[length(tree_stack)-1]
push(parent.children, node)
parent.children[] = node
lay_ctx.insert(parent.id, item)
push(tree_stack, node)
tree_stack[] = node
return node
}
function pop_node() {
pop(tree_stack)
tree_stack[]
}
// Generic container
@@ -305,34 +346,34 @@ clay.zstack = function(configs, fn) {
}
// Leaf nodes
clay.image = function(path, configs) {
clay.image = function(path, configs, extra) {
var img = graphics.texture(path)
var c = [{image: path}]
var final_config = process_configs(configs)
var _configs = configs ? (is_array(configs) ? configs : [configs]) : []
if (extra) _configs = array(_configs, is_array(extra) ? extra : [extra])
var final_config = process_configs(_configs)
if (!final_config.size && !final_config.behave)
c.size = {width: img.width, height: img.height}
var _configs = is_array(configs) ? configs : [configs]
c[] = {size: {width: img.width, height: img.height}}
push_node(array(c, _configs), null)
pop_node()
}
clay.text = function(str, configs) {
clay.text = function(str, configs, extra) {
var c = [{text: str}]
var final_config = process_configs(configs)
var _configs = configs ? (is_array(configs) ? configs : [configs]) : []
if (extra) _configs = array(_configs, is_array(extra) ? extra : [extra])
var final_config = process_configs(_configs)
if (!final_config.size && !final_config.behave) {
c.size = {width: 100, height: 20}
c[] = {size: {width: 100, height: 20}}
}
var _configs = is_array(configs) ? configs : [configs]
push_node(array(c, _configs), null)
pop_node()
}
clay.rectangle = function(configs) {
var _configs = is_array(configs) ? configs : [configs]
var _configs = configs ? (is_array(configs) ? configs : [configs]) : [{}]
push_node(_configs, null)
pop_node()
}
@@ -344,12 +385,54 @@ clay.button = function(str, action, configs) {
}]
var _configs = is_array(configs) ? configs : [configs]
var merged = process_configs(_configs)
clay.zstack(array(btn_config, _configs), function() {
clay.text(str, {color: {r:1,g:1,b:1,a:1}})
clay.text(str, {color: {r:1,g:1,b:1,a:1}, font_path: merged.font_path})
})
}
// Spacer — fills available space
clay.spacer = function(config) {
var cfg = config || {}
if (!cfg.behave) cfg.behave = layout.behave.hfill | layout.behave.vfill
push_node([cfg], null)
pop_node()
}
// Convenience draw wrapper — auto-detects call patterns:
// clay.draw(fn) — default 640x360
// clay.draw(fn, size_obj) — fn first, size object second
// clay.draw(size_array, fn) — size array first, fn second
clay.draw = function(arg1, arg2) {
var fn = null
var size = {width: 640, height: 360}
if (is_function(arg1)) {
fn = arg1
if (arg2) {
if (is_array(arg2)) size = {width: arg2[0], height: arg2[1]}
else if (is_object(arg2)) size = arg2
}
} else if (is_array(arg1)) {
size = {width: arg1[0], height: arg1[1]}
fn = arg2
}
return clay.layout(fn, size)
}
// Offset all drawables in an array by a position
clay.offset_drawables = function(drawables, offset) {
var result = []
var i = 0
var d = null
for (i = 0; i < length(drawables); i = i + 1) {
d = meme(drawables[i])
d.pos = {x: d.pos.x + offset.x, y: d.pos.y + offset.y}
result[] = d
}
return result
}
// Constants
clay.behave = layout.behave
clay.contain = layout.contain

View File

@@ -34,7 +34,8 @@ function find_path(node, path, pos) {
if (!pointer_enabled(node)) return null
if (!rect_contains(node, pos)) return null
var next_path = array(path, node)
var next_path = array(path)
next_path[] = node
var i = 0
var child = null
var child_path = null
@@ -79,7 +80,7 @@ clay_input.click = function click(tree_root, mousepos, button) {
clay_input.get_actionable = function get_actionable(tree_root) {
var actionable = []
function walk(node) {
if (node.config.action) push(actionable, node)
if (node.config.action) actionable[] = node
if (node[clay.CHILDREN])
arrfor(node[clay.CHILDREN], walk)
}
@@ -90,7 +91,7 @@ clay_input.get_actionable = function get_actionable(tree_root) {
clay_input.filter = function filter(tree_root, predicate) {
var results = []
function rec(node) {
if (predicate(node)) push(results, node)
if (predicate(node)) results[] = node
if (node[clay.CHILDREN])
arrfor(node[clay.CHILDREN], rec)
}

View File

@@ -73,7 +73,7 @@ collision2d.overlap = function(body, pos, others) {
for (i = 0; i < length(others); i++) {
other = others[i]
if (collision2d.test(body, pos, other.body, other.pos))
push(results, other)
results[] = other
}
return results
}
@@ -94,12 +94,12 @@ collision2d.overlap_point = function(point, bodies) {
hw = b.body.width * 0.5
hh = b.body.height * 0.5
if (abs(point.x - c.x) < hw && abs(point.y - c.y) < hh)
push(results, b)
results[] = b
} else if (b.body.type == 'circle') {
dx = point.x - c.x
dy = point.y - c.y
if (dx * dx + dy * dy < b.body.radius * b.body.radius)
push(results, b)
results[] = b
}
}
return results

View File

@@ -23,7 +23,7 @@ compositor.compile = function(config) {
// Clear screen
if (config.clear)
push(ctx.passes, {type: 'clear', target: 'screen', color: config.clear})
ctx.passes[] = {type: 'clear', target: 'screen', color: config.clear}
// Process each plane (supports both 'planes' and legacy 'layers' key)
var planes = config.planes || config.layers || []
@@ -44,11 +44,11 @@ compositor.compile = function(config) {
}
function compile_imgui_layer(layer, ctx) {
push(ctx.passes, {
ctx.passes[] = {
type: 'imgui',
target: 'screen',
draw: layer.draw
})
}
}
function compile_plane(plane_config, ctx, group_effects) {
@@ -75,7 +75,7 @@ function compile_plane(plane_config, ctx, group_effects) {
var di = 0
if (plane_config.drawables) {
for (di = 0; di < length(plane_config.drawables); di++)
push(all_sprites, plane_config.drawables[di])
all_sprites[] = plane_config.drawables[di]
}
// Find which sprites belong to groups with effects
@@ -110,21 +110,21 @@ function compile_plane(plane_config, ctx, group_effects) {
if (group_effects[gname]) {
if (!effect_groups[gname])
effect_groups[gname] = {sprites: [], effects: group_effects[gname].effects}
push(effect_groups[gname].sprites, s)
effect_groups[gname].sprites[] = s
assigned = true
break // Only assign to first matching effect group
}
}
// Add to base sprites if not assigned to effect group and not mask-only
if (!assigned && !is_mask_only) push(base_sprites, s)
if (!assigned && !is_mask_only) base_sprites[] = s
}
// Allocate plane target
var plane_target = ctx.alloc(res.width, res.height, plane_config.name)
// Always clear plane target to prevent stale data between frames
push(ctx.passes, {type: 'clear', target: plane_target, color: plane_config.clear || {r: 0, g: 0, b: 0, a: 0}})
ctx.passes[] = {type: 'clear', target: plane_target, color: plane_config.clear || {r: 0, g: 0, b: 0, a: 0}}
// Render each effect group to temp target, apply effects, composite back
arrfor(array(effect_groups), gname => {
@@ -134,7 +134,7 @@ function compile_plane(plane_config, ctx, group_effects) {
var group_target = ctx.alloc(res.width, res.height, gname + '_content')
// Render group content
push(ctx.passes, {
ctx.passes[] = {
type: 'render',
renderer: 'film2d',
drawables: eg.sprites,
@@ -143,7 +143,7 @@ function compile_plane(plane_config, ctx, group_effects) {
target_size: res,
layer_sort: layer_sort,
clear: {r: 0, g: 0, b: 0, a: 0}
})
}
// Apply effects
var current = group_target
@@ -155,19 +155,19 @@ function compile_plane(plane_config, ctx, group_effects) {
}
// Composite result to plane
push(ctx.passes, {
ctx.passes[] = {
type: 'composite',
source: current,
dest: plane_target,
source_size: res,
dest_size: res,
blend: 'over'
})
}
})
// Render base sprites (no effects)
if (length(base_sprites) > 0) {
push(ctx.passes, {
ctx.passes[] = {
type: 'render',
renderer: 'film2d',
drawables: base_sprites,
@@ -176,17 +176,17 @@ function compile_plane(plane_config, ctx, group_effects) {
target_size: res,
layer_sort: layer_sort,
clear: null // Don't clear, blend on top
})
}
}
// Composite plane to screen
push(ctx.passes, {
ctx.passes[] = {
type: 'blit_to_screen',
source: plane_target,
source_size: res,
dest_size: ctx.screen_size,
presentation: plane_config.presentation || 'stretch'
})
}
}
function apply_effect(ctx, effect, input, params) {
@@ -212,36 +212,37 @@ function apply_effect(ctx, effect, input, params) {
blur2 = ctx.alloc(size.width, size.height, hint + '_blur2')
// Threshold
push(ctx.passes, {
ctx.passes[] = {
type: 'shader_pass',
shader: 'threshold',
input: input,
output: bright,
uniforms: {threshold: effect.threshold || 0.8, intensity: effect.intensity || 1}
})
}
// Blur passes
blur_passes = effect.blur_passes || 2
blur_passes = effect.blur_passes != null ? effect.blur_passes : 2
blur_in = bright
for (p = 0; p < blur_passes; p++) {
push(ctx.passes, {type: 'shader_pass', shader: 'blur', input: blur_in, output: blur1, uniforms: {direction: {x: 1, y: 0}, texel_size: {x: 1/size.width, y: 1/size.height}}})
push(ctx.passes, {type: 'shader_pass', shader: 'blur', input: blur1, output: blur2, uniforms: {direction: {x: 0, y: 1}, texel_size: {x: 1/size.width, y: 1/size.height}}})
ctx.passes[] = {type: 'shader_pass', shader: 'blur', input: blur_in, output: blur1, uniforms: {direction: {x: 1, y: 0}, texel_size: {x: 1/size.width, y: 1/size.height}}}
ctx.passes[] = {type: 'shader_pass', shader: 'blur', input: blur1, output: blur2, uniforms: {direction: {x: 0, y: 1}, texel_size: {x: 1/size.width, y: 1/size.height}}}
blur_in = blur2
}
// Composite bloom
push(ctx.passes, {type: 'composite_textures', base: input, overlay: blur2, output: output, mode: 'add'})
ctx.passes[] = {type: 'composite_textures', base: input, overlay: blur_in, output: output, mode: 'add'}
} else if (effect.type == 'mask') {
mask_group = effect.mask_group
// Query masks within the same plane to avoid cross-plane mask issues
mask_sprites = film2d.query({group: mask_group, plane: current_plane})
log.compositor("mask effect: group=" + mask_group + " plane=" + current_plane + " sprites=" + text(length(mask_sprites)))
if (length(mask_sprites) > 0) {
mask_target = ctx.alloc(size.width, size.height, hint + '_mask')
// Render mask
push(ctx.passes, {
ctx.passes[] = {
type: 'render',
renderer: 'film2d',
drawables: mask_sprites,
@@ -249,24 +250,24 @@ function apply_effect(ctx, effect, input, params) {
target: mask_target,
target_size: size,
clear: {r: 0, g: 0, b: 0, a: 0}
})
}
// Apply mask
push(ctx.passes, {
ctx.passes[] = {
type: 'apply_mask',
content: input,
mask: mask_target,
output: output,
mode: effect.channel || 'alpha',
invert: effect.invert || false
})
}
} else {
// No mask sprites, pass through
push(ctx.passes, {type: 'blit', source: input, dest: output})
ctx.passes[] = {type: 'blit', source: input, dest: output}
}
} else {
// Unknown effect, pass through
push(ctx.passes, {type: 'blit', source: input, dest: output})
ctx.passes[] = {type: 'blit', source: input, dest: output}
}
return output
@@ -302,8 +303,8 @@ compositor.execute = function(plan) {
if (pass.type == 'clear') {
target = resolve(pass.target)
push(commands, {cmd: 'begin_render', target: target, clear: pass.color})
push(commands, {cmd: 'end_render'})
commands[] = {cmd: 'begin_render', target: target, clear: pass.color}
commands[] = {cmd: 'end_render'}
} else if (pass.type == 'render') {
result = film2d.render({
@@ -315,68 +316,68 @@ compositor.execute = function(plan) {
clear: pass.clear
}, backend)
for (c = 0; c < length(result.commands); c++)
push(commands, result.commands[c])
commands[] = result.commands[c]
} else if (pass.type == 'shader_pass') {
push(commands, {
commands[] = {
cmd: 'shader_pass',
shader: pass.shader,
input: resolve(pass.input),
output: resolve(pass.output),
uniforms: pass.uniforms
})
}
} else if (pass.type == 'composite_textures') {
push(commands, {
commands[] = {
cmd: 'composite_textures',
base: resolve(pass.base),
overlay: resolve(pass.overlay),
output: resolve(pass.output),
mode: pass.mode
})
}
} else if (pass.type == 'apply_mask') {
push(commands, {
commands[] = {
cmd: 'apply_mask',
content_texture: resolve(pass.content),
mask_texture: resolve(pass.mask),
output: resolve(pass.output),
mode: pass.mode,
invert: pass.invert
})
}
} else if (pass.type == 'composite') {
push(commands, {
commands[] = {
cmd: 'blit',
texture: resolve(pass.source),
target: resolve(pass.dest),
dst_rect: {x: 0, y: 0, width: pass.dest_size.width, height: pass.dest_size.height}
})
}
} else if (pass.type == 'blit_to_screen') {
rect = _calc_presentation(pass.source_size, pass.dest_size, pass.presentation)
push(commands, {
commands[] = {
cmd: 'blit',
texture: resolve(pass.source),
target: 'screen',
dst_rect: rect,
filter: pass.presentation == 'integer_scale' ? 'nearest' : 'linear'
})
}
} else if (pass.type == 'blit') {
src = resolve(pass.source)
dst = resolve(pass.dest)
push(commands, {
commands[] = {
cmd: 'blit',
texture: src,
target: dst,
dst_rect: {x: 0, y: 0, width: dst.width, height: dst.height}
})
}
} else if (pass.type == 'imgui') {
push(commands, {
commands[] = {
cmd: 'imgui',
target: resolve(pass.target),
draw: pass.draw
})
}
}
}
@@ -425,7 +426,7 @@ compositor.snapshot = function() {
for (i = 0; i < length(_last_plan.passes); i++) {
pass = _last_plan.passes[i]
if (pass.type == 'render') {
push(planes, {
planes[] = {
drawable_count: length(pass.drawables),
camera: pass.camera ? {
pos: pass.camera.pos,
@@ -433,7 +434,7 @@ compositor.snapshot = function() {
height: pass.camera.height
} : null,
target_size: pass.target_size
})
}
}
}
return {

View File

@@ -78,6 +78,9 @@ core.start = function(config) {
// Start main loop
_main_loop()
// Call start callback after the first frame and main loop are established
if (config.start) config.start()
return true
}
@@ -105,7 +108,7 @@ function fps_add_sample(sample) {
var n = length(_fps_samples)
var old = null
if (n < _fps_sample_count) {
push(_fps_samples, sample)
_fps_samples[] = sample
_fps_sample_sum += sample
} else {
old = _fps_samples[_fps_sample_pos]
@@ -225,7 +228,7 @@ function _main_loop() {
// Handle both compositor result ({commands: [...]}) and fx_graph (graph object)
if (render_result.commands) {
if (_config.imgui || _config.editor) {
push(render_result.commands, {
render_result.commands[] = {
cmd: 'imgui',
draw: function(ui) {
if (_config.imgui) _config.imgui(ui)
@@ -235,7 +238,7 @@ function _main_loop() {
}
},
target: 'screen'
})
}
}
// Compositor result - execute commands directly
_backend.execute_commands(render_result.commands, win_size, dbg)

View File

@@ -144,31 +144,31 @@ function _render_node_summary(imgui, node) {
var info = []
if (node.pos) {
push(info, "pos:(" + text(round(node.pos.x)) + "," + text(round(node.pos.y)) + ")")
info[] = "pos:(" + text(round(node.pos.x)) + "," + text(round(node.pos.y)) + ")"
}
if (node.width && node.height) {
push(info, "size:" + text(node.width) + "x" + text(node.height))
info[] = "size:" + text(node.width) + "x" + text(node.height)
}
if (node.image) {
push(info, "img:" + node.image)
info[] = "img:" + node.image
}
var t = null
if (node.text) {
t = node.text
if (length(t) > 20) t = text(t, 0, 17) + "..."
push(info, "\"" + t + "\"")
info[] = "\"" + t + "\""
}
var fx = []
var j = 0
if (node.effects && length(node.effects) > 0) {
for (j = 0; j < length(node.effects); j++) {
push(fx, node.effects[j].type)
fx[] = node.effects[j].type
}
push(info, "fx:[" + text(fx, ",") + "]")
info[] = "fx:[" + text(fx, ",") + "]"
}
if (length(info) > 0) {

View File

@@ -36,7 +36,7 @@ effects.register('bloom', {
// Threshold extraction
var thresh_target = ctx.alloc_target(size.width, size.height, 'bloom_thresh')
push(passes, {
passes[] = {
type: 'shader',
shader: 'threshold',
input: input,
@@ -45,7 +45,7 @@ effects.register('bloom', {
threshold: params.threshold != null ? params.threshold : 0.8,
intensity: params.intensity != null ? params.intensity : 1.0
}
})
}
// Blur ping-pong
var blur_a = ctx.alloc_target(size.width, size.height, 'bloom_blur_a')
@@ -56,31 +56,31 @@ effects.register('bloom', {
var i = 0
for (i = 0; i < blur_count; i++) {
push(passes, {
passes[] = {
type: 'shader',
shader: 'blur',
input: blur_src,
output: blur_a,
uniforms: {direction: {x: 2, y: 0}, texel_size: texel}
})
push(passes, {
}
passes[] = {
type: 'shader',
shader: 'blur',
input: blur_a,
output: blur_b,
uniforms: {direction: {x: 0, y: 2}, texel_size: texel}
})
}
blur_src = blur_b
}
// Additive composite
push(passes, {
passes[] = {
type: 'composite',
base: input,
overlay: blur_src,
output: output,
blend: 'add'
})
}
return passes
}
@@ -125,16 +125,16 @@ effects.register('mask', {
// Render mask source to target
var mask_target = ctx.alloc_target(size.width, size.height, 'mask_src')
push(passes, {
passes[] = {
type: 'render_subtree',
root: mask_source,
output: mask_target,
clear: {r: 0, g: 0, b: 0, a: 0},
space: params.space || 'local'
})
}
// Apply mask shader
push(passes, {
passes[] = {
type: 'shader',
shader: 'mask',
inputs: [input, mask_target],
@@ -143,7 +143,7 @@ effects.register('mask', {
channel: params.channel == 'alpha' ? 0 : 1,
invert: params.invert ? 1 : 0
}
})
}
return passes
}
@@ -192,25 +192,25 @@ effects.register('blur', {
var i = 0
for (i = 0; i < blur_count; i++) {
push(passes, {
passes[] = {
type: 'shader',
shader: 'blur',
input: src,
output: blur_a,
uniforms: {direction: {x: 2, y: 0}, texel_size: texel}
})
push(passes, {
}
passes[] = {
type: 'shader',
shader: 'blur',
input: blur_a,
output: blur_b,
uniforms: {direction: {x: 0, y: 2}, texel_size: texel}
})
}
src = blur_b
}
// Final blit to output
push(passes, {type: 'blit', source: src, dest: output})
passes[] = {type: 'blit', source: src, dest: output}
return passes
}
})

45
examples/bisect.ce Normal file
View File

@@ -0,0 +1,45 @@
log.console("bisect: start")
var sprite = use('sprite')
var clay = use('clay')
var core = use('core')
var fx_graph = use('fx_graph')
var sdl_gpu = use('sdl_gpu')
log.console("bisect: modules loaded")
core.start({
width: 640,
height: 480,
title: "Paladin Simple",
framerate: 60,
update: function(dt) {},
render: function() {
var win_size = core.window_size()
var graph = fx_graph.create()
var ui_scene = clay.layout(function() {
clay.container({padding: 20, contain: clay.contain.content}, function() {
clay.vstack({
padding: 20,
spacing: 10,
width: 200
}, function() {
clay.text("PALADIN UI", {font_size: 24, color: {r:1, g:0.8, b:0.2, a:1}})
clay.text("Item 1")
clay.text("Item 2")
})
})
}, [win_size.width, win_size.height])
graph.add_node('render_view', {
root: ui_scene,
camera: {pos: [0, 0], width: win_size.width, height: win_size.height, anchor: [0, 0], ortho: true},
target: 'screen',
clear_color: {r:0,g:0,b:0,a:0}
})
return graph
},
input: function(ev) {
if (ev.type == 'quit') $stop()
}
})

BIN
examples/bunny.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -47,8 +47,8 @@ function add_bunny(x, y) {
anchor_x: 0.5, anchor_y: 0.5,
plane: 'game'
})
push(bunnies, bunny)
push(bunny_sprites, s)
bunnies[] = bunny
bunny_sprites[] = s
}
// Initial bunnies

View File

@@ -29,14 +29,14 @@ function init(grid) {
row = []
for (bx = 0; bx < 8; bx++) {
col = ((bx + by) & 1) ? dark_color : light_color
push(row, shape.rect({
row[] = shape.rect({
pos: {x: bx * S + S / 2, y: by * S + S / 2},
width: S, height: S,
fill: {r: col.r, g: col.g, b: col.b, a: col.a},
plane: 'game', layer: 0
}))
})
}
push(board_shapes, row)
board_shapes[] = row
}
next_id = 0

View File

@@ -49,7 +49,7 @@ function compute_valid_moves(from) {
dest = grid.at(to)
if (length(dest) && dest[0].colour == piece.colour) continue
if (rules.canMove(piece, from, to, grid))
push(validMoves, to)
validMoves[] = to
}
}
}

View File

@@ -77,7 +77,7 @@ function compute_valid_moves(from) {
dest = grid.at(to)
if (length(dest) && dest[0].colour == piece.colour) continue
if (rules.canMove(piece, from, to, grid))
push(validMoves, to)
validMoves[] = to
}
}
}

View File

@@ -32,7 +32,7 @@ var grid_prototype = {
// add an entity into a cell
add(entity, pos) {
var cx = px(pos), cy = py(pos);
push(this.cell(cx, cy), entity);
this.cell(cx, cy)[] = entity;
entity.coord = [cx, cy];
},

View File

@@ -91,7 +91,7 @@ function compute_valid_moves(from) {
dest = grid.at(to)
if (length(dest) && dest[0].colour == piece.colour) continue
if (rules.canMove(piece, from, to, grid))
push(validMoves, to)
validMoves[] = to
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
examples/enemies/bee_a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
examples/enemies/bee_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
examples/enemies/fly_a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
examples/enemies/fly_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
examples/enemies/saw_a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
examples/enemies/saw_b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

BIN
examples/fonts/c64.ttf Normal file

Binary file not shown.

BIN
examples/fonts/dos.ttf Normal file

Binary file not shown.

Binary file not shown.

303
examples/paladin.ce Normal file
View File

@@ -0,0 +1,303 @@
log.console("paladin game starting")
var time = use('time')
var random = use('random').random
var core = use('core')
var sprite = use('sprite')
var text2d = use('text2d')
var particles2d = use('particles2d')
var compositor = use('compositor')
var film2d = use('film2d')
var tilemap2d = use('tilemap2d')
var clay = use('clay')
var sprites = [] // Keep references for cleanup
var emitters = [] // Particle emitters
var bunnies = [] // Bouncing bunnies for Y-sort test
// Cameras
var world_camera = {
pos: {x: 2.5, y: 2.5},
width: 5,
height: 5,
anchor: {x: 0.5, y: 0.5}
}
var hud_camera = {
pos: {x: 0, y: 0},
width: 640,
height: 360,
anchor: {x: 0, y: 0}
}
// Group effects definition - THIS IS THE KEY PART
var group_effects = {
'bloom': {
effects: [{type: 'bloom', threshold: 0.1, intensity: 5, blur_passes: 1}]
},
'masked_fire': {
effects: [
{type: 'bloom', threshold: 0.1, intensity: 5, blur_passes: 0},
{type: 'mask', channel: 'alpha', mask_group: 'mask_text'}
]
},
'corner_fire': {
effects: [{type: 'mask', channel: 'alpha', mask_group: 'corner_mask'}]
}
}
// Compositor config - pure data
var compositor_config = {
clear: {r: 0, g: 0, b: 0, a: 1},
planes: [
{
name: 'world',
plane: 'world',
camera: world_camera,
resolution: {width: 500, height: 500},
presentation: 'letterbox',
clear: {r: 0.1, g: 0.1, b: 0.9, a: 1}
},
{
name: 'hud',
plane: 'hud',
camera: hud_camera,
resolution: {width: 640, height: 360},
presentation: 'integer_scale',
clear: {r: 0, g: 0, b: 0, a: 0},
layer_sort: {
'100': 'y' // Bunny layer uses Y-sort
}
},
{
name: 'ui',
plane: 'ui',
camera: hud_camera,
resolution: {width: 640, height: 360},
presentation: 'integer_scale',
clear: null
}
],
group_effects
}
function init_game() {
// Cleanup old sprites
var i = 0
for (i = 0; i < length(sprites); i++)
sprites[i].destroy()
sprites = []
emitters = []
bunnies = []
// World sprites
// Background Tilemap
var tiles = []
var x = 0
var y = 0
for (x = 0; x < 5; x++) {
tiles[x] = []
for (y = 0; y < 5; y++) {
tiles[x][y] = 'examples/tiles/terrain_dirt_cloud'
}
}
sprites[] = tilemap2d({
plane: 'world',
layer: 0,
tiles: tiles,
tile_width: 1,
tile_height: 1,
offset_x: 0,
offset_y: 0
})
sprites[] = sprite({
plane: 'world',
layer: 10,
image: 'examples/enemies/saw_a',
pos: {x: 1, y: 1},
anchor_x: 0.5,
})
// HUD sprites
sprites[] = text2d({
plane: 'hud',
groups: ['mask_text'], // Effect routing only
layer: 50,
text: 'PALADIN',
pos: {x: 640/2 - 270, y: 360/2},
font: 'examples/fonts/dos',
size: 150
})
var masked_fire = particles2d.create({
plane: 'hud',
groups: ['masked_fire'], // Effect routing only
layer: 40,
image: 'examples/tiles/fireball',
width: 40,
height: 40
})
sprites[] = masked_fire
emitters[] = particles2d.emitters.create({
pos: {x: 320, y: 180},
spawn_area: {width: 500, height: 200},
velocity: {x: 0, y: 50},
velocity_var: {x: 30, y: 30},
life: 2,
rate: 60,
scale: 3,
scale_var: 0.3,
color: {r: 1, g: 0.6, b: 0.2},
handle: masked_fire
})
sprites[] = sprite({
plane: 'hud',
groups: ['corner_mask'], // Effect routing only
layer: 60,
image: 'examples/tiles/fireball',
pos: {x: 600, y: 40},
width: 50,
height: 50,
anchor_x: 0.5,
anchor_y: 0.5
})
var corner_fire = particles2d.create({
plane: 'hud',
groups: ['corner_fire'], // Effect routing only
layer: 70,
image: 'examples/tiles/fireball',
width: 25,
height: 25
})
sprites[] = corner_fire
emitters[] = particles2d.emitters.create({
pos: {x: 600, y: 40},
spawn_area: {width: 30, height: 30},
velocity: {x: 0, y: 30},
velocity_var: {x: 20, y: 20},
life: 1.5,
rate: 10,
scale: 1,
scale_var: 0.2,
color: {r: 1, g: 0.5, b: 0.1},
handle: corner_fire
})
var bloom_fire = particles2d.create({
plane: 'hud',
groups: ['bloom'], // Effect routing only
layer: 80,
image: 'examples/tiles/fireball',
width: 48,
height: 48
})
sprites[] = bloom_fire
emitters[] = particles2d.emitters.create({
pos: {x: 100, y: 100},
spawn_area: {width: 50, height: 50},
velocity: {x: 100, y: 0},
velocity_var: {x: 40, y: 40},
life: 2.5,
rate: 12,
scale: 1.2,
scale_var: 0.4,
color: {r: 1, g: 0.8, b: 0.3},
handle: bloom_fire
})
// Bouncing bunnies - exactly two, big, bottom of screen, Y-sort layer 100
var bunny = null
for (i = 0; i < 2; i++) {
bunny = sprite({
plane: 'hud',
layer: 100, // This layer has Y-sort enabled
image: 'examples/bunny',
pos: {x: 320 + (i == 0 ? -20 : 20), y: 340},
width: 96,
height: 96,
anchor_x: 0.5,
})
bunny._base_y = 340
bunny._t = i * 0.7 // phase offset so they dont match
bunny._amp = 240 // bounce height in pixels
bunny._speed = 1 * random() // bounce speed
sprites[] = bunny
bunnies[] = bunny
}
log.console("World initialized - " + text(length(film2d.query({}))) + " drawables")
}
var json = use('json')
var last_time = 0
function update(dt) {
// Update all particle emitters
var i = 0
for (i = 0; i < length(emitters); i++)
particles2d.emitters.update(emitters[i], dt)
// Update bouncing bunnies
// Update bouncing bunnies (vertical bob only)
var b = null
var cycle = null
var u = null
for (i = 0; i < length(bunnies); i++) {
b = bunnies[i]
b._t += dt
// Bob between base_y and base_y - amp using triangle wave
cycle = (b._t * b._speed) % 2
u = cycle < 1 ? cycle : 2 - cycle
b.pos.y = b._base_y - u * b._amp
// Optional: if your Y-sort uses an explicit key, keep it in sync
// b.y_sort_key = b.pos.y
}
}
function render() {
// 1. Build Clay UI
var clay_tree = clay.layout(function() {
clay.vstack({gap: 10, padding: 20, align: 'center'}, function() {
clay.text("Clay UI Sample", {font_size: 24, color: {r:1,g:1,b:0}})
clay.image('examples/tiles/key_blue', {size:{width:32,height:32}, color: {r:1,g:0,b:0}})
clay.container({size: [200, 100], background_color: {r:0.2, g:0.2, b:0.2, a:0.8}, padding: 10}, function() {
clay.vstack(function() {
clay.text("List Item 1")
clay.text("List Item 2")
clay.text("List Item 3")
})
})
})
}, {width: 640, height: 360})
// Assign clay drawables to UI plane
compositor_config.planes[2].drawables = clay_tree.drawables
compositor_config.planes[2].clear = {r: 0, g: 0, b: 0, a: 0}
var plan = compositor.compile(compositor_config)
return compositor.execute(plan)
}
init_game()
core.start({
width: 1280,
height: 720,
title: "Paladin - Data Oriented",
framerate: 60,
update: update,
render: render,
editor: function(ui) {
}
})

View File

@@ -0,0 +1,99 @@
// paladin_simple.ce - Simple test for prosperon rendering
log.console("paladin simple test starting")
var sprite = use('sprite')
var clay = use('clay')
var core = use('core')
var fx_graph = use('fx_graph')
var sdl_gpu = use('sdl_gpu')
var fcfg = {font_path: 'examples/fonts/dos'}
core.start({
width: 640,
height: 480,
title: "Paladin Simple",
framerate: 60,
update: function(dt) {},
render: build_render_graph,
input: function(ev) {
if (ev.type == 'quit') $stop()
}
})
function build_render_graph() {
var win_size = core.window_size()
var graph = fx_graph.create()
// Simple sprite scene
var scene = {
type: 'group',
children: [{
type: 'sprite',
image: 'examples/tiles/fireball',
pos: {x: win_size.width / 2, y: win_size.height / 2},
width: win_size.height / 2, // Half screen height
height: win_size.height / 2,
anchor_x: 0.5,
anchor_y: 0.5,
color: {r: 1, g: 1, b: 1, a: 1}
}]
}
var camera = {
pos: [0, 0],
width: win_size.width,
height: win_size.height,
anchor: [0, 0],
ortho: true,
background: {r: 0.2, g: 0.2, b: 0.3, a: 1}
}
// Render to screen
var ui_scene = clay.layout(function() {
clay.container({padding: 20, contain: clay.contain.content}, function() {
// Top-left HUD panel
clay.vstack({
background_image: 'examples/tiles/brick_brown',
slice: 0.33,
padding: 20,
spacing: 10,
width: 200
}, function() {
clay.text("PALADIN UI", [{font_size: 24, color: {r:1, g:0.8, b:0.2, a:1}}, fcfg])
clay.hstack({spacing: 10}, function() {
clay.image('examples/tiles/hud_heart', {width: 24, height: 24})
clay.text("x 3", [{font_size: 20}, fcfg])
})
clay.button("PAUSE", function() { log.console("Pause clicked") }, fcfg)
// Scissored scroll area test
clay.container({
width: 160, height: 60,
background_color: {r:0, g:0, b:0, a:0.5},
clipped: true
}, function() {
clay.vstack({offset: {x:0, y: -10}}, function() {
clay.text("Item 1", fcfg)
clay.text("Item 2", fcfg)
clay.text("Item 3", fcfg)
clay.text("Item 4", fcfg)
})
})
})
})
}, {width: win_size.width, height: win_size.height})
graph.add_node('render_view', {
root: ui_scene,
camera: {pos: [0, 0], width: win_size.width, height: win_size.height, anchor: [0, 0], ortho: true},
target: 'screen',
clear_color: {r:0,g:0,b:0,a:0} // Cleared transparent
})
return graph
}

BIN
examples/rope.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

View File

@@ -91,12 +91,12 @@ function reset_game() {
var cy = floor(gridH / 2)
var wp = null
for (i = 0; i < 3; i++) {
push(snake_pos, {x: cx - i, y: cy})
snake_pos[] = {x: cx - i, y: cy}
wp = grid_to_world(cx - i, cy)
push(snake_shapes, shape.rect({
snake_shapes[] = shape.rect({
pos: {x: wp.x, y: wp.y}, width: cellSize - 2, height: cellSize - 2,
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
}))
})
}
dir = {x: 1, y: 0}
@@ -166,7 +166,7 @@ core.start({
var old_wp = grid_to_world(snake_pos[0].x, snake_pos[0].y)
var new_wp = grid_to_world(hx, hy)
var new_pos = [{x: hx, y: hy}]
for (i = 0; i < length(snake_pos); i++) push(new_pos, snake_pos[i])
for (i = 0; i < length(snake_pos); i++) new_pos[] = snake_pos[i]
snake_pos = new_pos
var head = shape.rect({
pos: {x: old_wp.x, y: old_wp.y}, width: cellSize - 2, height: cellSize - 2,
@@ -175,7 +175,7 @@ core.start({
// Smooth tween from old position to new position
tw.tween(head.pos).to({x: new_wp.x, y: new_wp.y}, move_interval).ease(ease.linear)
var new_shapes = [head]
for (i = 0; i < length(snake_shapes); i++) push(new_shapes, snake_shapes[i])
for (i = 0; i < length(snake_shapes); i++) new_shapes[] = snake_shapes[i]
snake_shapes = new_shapes
// Eat apple?
@@ -185,9 +185,9 @@ core.start({
spawn_apple()
} else {
// Remove tail
tail = pop(snake_shapes)
tail = snake_shapes[]
tail.destroy()
pop(snake_pos)
snake_pos[]
}
},

315
examples/test_line2d.ce Normal file
View File

@@ -0,0 +1,315 @@
log.console("test_line2d starting")
var time = use('time')
var core = use('core')
var line2d = use('line2d')
var compositor = use('compositor')
var film2d = use('film2d')
var math = use('math/radians')
var lines = []
var t = 0
var camera = {
pos: {x: 400, y: 300},
width: 800,
height: 600,
anchor: {x: 0.5, y: 0.5}
}
var compositor_config = {
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1},
planes: [
{
name: 'main',
plane: 'main',
camera: camera,
resolution: {width: 800, height: 600},
presentation: 'stretch',
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1}
}
]
}
function init() {
// Row 1: Basic lines with different widths
// Thin line
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 50, y: 550}, {x: 150, y: 520}, {x: 200, y: 560}],
width: 4,
tint: {r: 1, g: 0.3, b: 0.3, a: 1}
})
// Medium line
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 250, y: 550}, {x: 350, y: 520}, {x: 400, y: 560}],
width: 12,
tint: {r: 0.3, g: 1, b: 0.3, a: 1}
})
// Thick line
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 450, y: 550}, {x: 550, y: 520}, {x: 600, y: 560}],
width: 24,
tint: {r: 0.3, g: 0.3, b: 1, a: 1}
})
// Variable width line
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 650, y: 560}, {x: 700, y: 520}, {x: 750, y: 560}],
widths: [8, 24, 8],
tint: {r: 1, g: 1, b: 0.3, a: 1}
})
// Row 2: Textured ropes with different UV modes
// Rope with repeat UV (default for ropes)
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 50, y: 450}, {x: 120, y: 420}, {x: 200, y: 440}, {x: 280, y: 400}],
width: 20,
image: 'examples/rope',
uv: {
mode: 'repeat',
u_per_unit: 1 / 32
}
})
// Rope with stretch UV
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 320, y: 450}, {x: 400, y: 420}, {x: 480, y: 440}, {x: 560, y: 400}],
width: 20,
image: 'examples/rope',
uv: {
mode: 'stretch'
}
})
// Rope with per-segment UV
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 600, y: 450}, {x: 660, y: 420}, {x: 720, y: 440}, {x: 780, y: 400}],
width: 20,
image: 'examples/rope',
uv: {
mode: 'per_segment'
}
})
// Row 3: Cap styles
// Butt cap (default)
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 80, y: 320}, {x: 180, y: 320}],
width: 20,
cap: 'butt',
tint: {r: 0.8, g: 0.5, b: 0.2, a: 1}
})
// Square cap
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 250, y: 320}, {x: 350, y: 320}],
width: 20,
cap: 'square',
tint: {r: 0.5, g: 0.8, b: 0.2, a: 1}
})
// Round cap
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 420, y: 320}, {x: 520, y: 320}],
width: 20,
cap: 'round',
tint: {r: 0.2, g: 0.5, b: 0.8, a: 1}
})
// Round cap with texture
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 590, y: 320}, {x: 750, y: 320}],
width: 24,
cap: 'round',
image: 'examples/rope'
})
// Row 4: Complex paths
// Wavy line (will be animated)
var wave_points = []
var i = 0
for (i = 0; i < 12; i++) {
wave_points[] = {
x: 50 + i * 30,
y: 220 + math.sine(i * 0.8) * 30
}
}
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: wave_points,
width: 14,
image: 'examples/rope',
uv: {
mode: 'repeat',
u_per_unit: 1 / 24
}
})
// Spiral/coil
var spiral_points = []
var angle = null
var radius = null
for (i = 0; i < 20; i++) {
angle = i * 0.5
radius = 20 + i * 3
spiral_points[] = {
x: 550 + math.cosine(angle) * radius,
y: 200 + math.sine(angle) * radius
}
}
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: spiral_points,
width: 10,
image: 'examples/rope',
uv: {
mode: 'repeat',
u_per_unit: 1 / 20
}
})
// Row 5: Closed shapes and special cases
// Closed triangle
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 80, y: 80}, {x: 140, y: 140}, {x: 20, y: 140}],
width: 8,
closed: true,
tint: {r: 1, g: 0.5, b: 0.8, a: 1}
})
// Closed pentagon
var pent_points = []
for (i = 0; i < 5; i++) {
angle = i * (2 * 3.14159 / 5) - 3.14159 / 2
pent_points[] = {
x: 220 + math.cosine(angle) * 50,
y: 110 + math.sine(angle) * 50
}
}
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: pent_points,
width: 10,
closed: true,
image: 'examples/rope',
uv: {
mode: 'repeat',
u_per_unit: 1 / 30
}
})
// Animated rope (will swing)
lines[] = line2d.polyline({
plane: 'main',
layer: 1,
points: [{x: 400, y: 50}, {x: 400, y: 100}, {x: 400, y: 150}],
width: 16,
image: 'examples/rope',
uv: {
mode: 'repeat',
u_per_unit: 1 / 20
}
})
// Fuse/wire with opacity gradient (simulated via tint)
lines[] = line2d.polyline({
plane: 'main',
layer: 0,
points: [{x: 500, y: 80}, {x: 580, y: 60}, {x: 660, y: 100}, {x: 740, y: 70}],
width: 8,
image: 'examples/rope',
opacity: 0.7,
tint: {r: 1, g: 0.8, b: 0.4, a: 1}
})
log.console("test_line2d initialized with " + text(length(lines)) + " lines")
}
function update(dt) {
t += dt
// Animate the wavy line (index 11)
var pts = null
var i = 0
if (lines[11]) {
pts = lines[11].points
for (i = 0; i < length(pts); i++) {
pts[i].y = 220 + math.sine(i * 0.8 + t * 2) * 30
}
lines[11].set_points(pts)
}
// Animate the hanging rope (index 15) - swing like a pendulum
var swing = null
if (lines[15]) {
swing = math.sine(t * 1.5) * 40
pts = [
{x: 400, y: 50},
{x: 400 + swing * 0.5, y: 100},
{x: 400 + swing, y: 150}
]
lines[15].set_points(pts)
}
// Animate UV offset on the spiral (index 12) for scrolling texture effect
if (lines[12]) {
lines[12].uv.u_offset = t * 0.5
lines[12]._rebuild()
}
// Pulse opacity on the fuse (index 16)
if (lines[16]) {
lines[16].opacity = 0.5 + 0.5 * math.sine(t * 3)
}
}
function render() {
var plan = compositor.compile(compositor_config)
return compositor.execute(plan)
}
init()
core.start({
width: 800,
height: 600,
title: "Test Line2D - Ropes and Lines",
framerate: 60,
update: update,
render: render
})

301
examples/test_particles.ce Normal file
View File

@@ -0,0 +1,301 @@
log.console("test_particles starting")
var time = use('time')
var random = use('random').random
var core = use('core')
var particles2d = use('particles2d')
var text2d = use('text2d')
var compositor = use('compositor')
var film2d = use('film2d')
var math = use('math/radians')
var particle_handles = []
var emitters = []
var labels = []
var t = 0
var camera = {
pos: {x: 250, y: 250},
width: 500,
height: 500,
anchor: {x: 0.5, y: 0.5}
}
var compositor_config = {
clear: {r: 0.05, g: 0.05, b: 0.1, a: 1},
planes: [
{
name: 'main',
plane: 'main',
camera: camera,
resolution: {width: 500, height: 500},
presentation: 'stretch',
clear: {r: 0.05, g: 0.05, b: 0.1, a: 1}
}
]
}
function init() {
// Emitter 1: Normal particles (top-left)
var handle1 = particles2d.create({
plane: 'main',
layer: 0,
image: 'examples/tiles/fireball',
width: 20,
height: 20
})
particle_handles[] = handle1
emitters[] = particles2d.emitters.create({
pos: {x: 100, y: 400},
spawn_area: {width: 30, height: 10},
velocity: {x: 0, y: 50},
velocity_var: {x: 20, y: 10},
life: 2,
rate: 15,
scale: 1,
scale_var: 0.2,
color: {r: 1, g: 0.8, b: 0.3},
handle: handle1
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Normal",
pos: {x: 70, y: 480},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Emitter 2: Half opacity (top-center)
var handle2 = particles2d.create({
plane: 'main',
layer: 0,
image: 'examples/tiles/fireball',
width: 20,
height: 20,
opacity: 0.5
})
particle_handles[] = handle2
emitters[] = particles2d.emitters.create({
pos: {x: 250, y: 400},
spawn_area: {width: 30, height: 10},
velocity: {x: 0, y: 50},
velocity_var: {x: 20, y: 10},
life: 2,
rate: 15,
scale: 1,
scale_var: 0.2,
color: {r: 1, g: 0.8, b: 0.3},
handle: handle2
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Opacity 0.5",
pos: {x: 210, y: 480},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Emitter 3: Red tint (top-right)
var handle3 = particles2d.create({
plane: 'main',
layer: 0,
image: 'examples/tiles/fireball',
width: 20,
height: 20,
tint: {r: 1, g: 0.2, b: 0.2, a: 1}
})
particle_handles[] = handle3
emitters[] = particles2d.emitters.create({
pos: {x: 400, y: 400},
spawn_area: {width: 30, height: 10},
velocity: {x: 0, y: 50},
velocity_var: {x: 20, y: 10},
life: 2,
rate: 15,
scale: 1,
scale_var: 0.2,
color: {r: 1, g: 0.8, b: 0.3},
handle: handle3
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Red Tint",
pos: {x: 370, y: 480},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Emitter 4: Blue tint (bottom-left)
var handle4 = particles2d.create({
plane: 'main',
layer: 0,
image: 'examples/tiles/fireball',
width: 24,
height: 24,
tint: {r: 0.3, g: 0.5, b: 1, a: 1}
})
particle_handles[] = handle4
emitters[] = particles2d.emitters.create({
pos: {x: 100, y: 200},
spawn_area: {width: 40, height: 10},
velocity: {x: 0, y: 60},
velocity_var: {x: 30, y: 15},
life: 2.5,
rate: 20,
scale: 1.2,
scale_var: 0.3,
color: {r: 1, g: 1, b: 1},
handle: handle4
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Blue Tint",
pos: {x: 70, y: 280},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Emitter 5: Green tint + opacity (bottom-center)
var handle5 = particles2d.create({
plane: 'main',
layer: 0,
image: 'examples/tiles/fireball',
width: 24,
height: 24,
tint: {r: 0.3, g: 1, b: 0.3, a: 1},
opacity: 0.7
})
particle_handles[] = handle5
emitters[] = particles2d.emitters.create({
pos: {x: 250, y: 200},
spawn_area: {width: 40, height: 10},
velocity: {x: 0, y: 60},
velocity_var: {x: 30, y: 15},
life: 2.5,
rate: 20,
scale: 1.2,
scale_var: 0.3,
color: {r: 1, g: 1, b: 1},
handle: handle5
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Green + 70%",
pos: {x: 210, y: 280},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Emitter 6: Animated tint (bottom-right)
var handle6 = particles2d.create({
plane: 'main',
layer: 0,
image: 'examples/tiles/fireball',
width: 24,
height: 24,
tint: {r: 1, g: 1, b: 1, a: 1}
})
particle_handles[] = handle6
emitters[] = particles2d.emitters.create({
pos: {x: 400, y: 200},
spawn_area: {width: 40, height: 10},
velocity: {x: 0, y: 60},
velocity_var: {x: 30, y: 15},
life: 2.5,
rate: 20,
scale: 1.2,
scale_var: 0.3,
color: {r: 1, g: 1, b: 1},
handle: handle6
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Rainbow",
pos: {x: 370, y: 280},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Emitter 7: Animated opacity (center)
var handle7 = particles2d.create({
plane: 'main',
layer: 0,
image: 'examples/tiles/fireball',
width: 32,
height: 32,
opacity: 1
})
particle_handles[] = handle7
emitters[] = particles2d.emitters.create({
pos: {x: 250, y: 80},
spawn_area: {width: 60, height: 20},
velocity: {x: 0, y: 40},
velocity_var: {x: 40, y: 20},
life: 3,
rate: 25,
scale: 1.5,
scale_var: 0.4,
color: {r: 1, g: 0.6, b: 0.2},
handle: handle7
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Pulsing Opacity",
pos: {x: 180, y: 20},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 0, a: 1}
})
log.console("test_particles initialized with " + text(length(emitters)) + " emitters")
}
function update(dt) {
t += dt
// Update all emitters
var i = 0
for (i = 0; i < length(emitters); i++) {
particles2d.emitters.update(emitters[i], dt)
}
// Animate rainbow tint on emitter 6
var rainbow_handle = particle_handles[5]
rainbow_handle.tint.r = 0.5 + 0.5 * math.sine(t * 2)
rainbow_handle.tint.g = 0.5 + 0.5 * math.sine(t * 2 + 2.094)
rainbow_handle.tint.b = 0.5 + 0.5 * math.sine(t * 2 + 4.188)
// Animate pulsing opacity on emitter 7
var pulsing_handle = particle_handles[6]
pulsing_handle.opacity = 0.3 + 0.7 * abs(math.sine(t * 1.5))
}
function render() {
var plan = compositor.compile(compositor_config)
return compositor.execute(plan)
}
init()
core.start({
width: 500,
height: 500,
title: "Test Particles Features",
framerate: 60,
update: update,
render: render
})

352
examples/test_shape2d.ce Normal file
View File

@@ -0,0 +1,352 @@
log.console("test_shape2d starting")
var time = use('time')
var core = use('core')
var shape2d = use('shape2d')
var compositor = use('compositor')
var film2d = use('film2d')
var math = use('math/radians')
var shapes = []
var t = 0
var camera = {
pos: {x: 400, y: 300},
width: 800,
height: 600,
anchor: {x: 0.5, y: 0.5}
}
var compositor_config = {
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1},
planes: [
{
name: 'main',
plane: 'main',
camera: camera,
resolution: {width: 800, height: 600},
presentation: 'stretch',
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1}
}
]
}
function init() {
// Row 1: Basic shapes - rect, circle, ellipse, pill
// Rectangle with rounded corners
shapes[] = shape2d.rect({
plane: 'main',
layer: 0,
pos: {x: 100, y: 500},
width: 120,
height: 80,
radius: 10,
fill: {r: 0.2, g: 0.6, b: 0.9, a: 1}
})
// Circle
shapes[] = shape2d.circle({
plane: 'main',
layer: 0,
pos: {x: 250, y: 500},
radius: 50,
fill: {r: 0.9, g: 0.3, b: 0.3, a: 1}
})
// Ellipse
shapes[] = shape2d.ellipse({
plane: 'main',
layer: 0,
pos: {x: 400, y: 500},
width: 140,
height: 80,
fill: {r: 0.3, g: 0.9, b: 0.4, a: 1}
})
// Pill (stadium)
shapes[] = shape2d.pill({
plane: 'main',
layer: 0,
pos: {x: 550, y: 500},
width: 140,
height: 60,
fill: {r: 0.9, g: 0.7, b: 0.2, a: 1}
})
// Row 2: Stroke variations
// Rect with stroke only
shapes[] = shape2d.rect({
plane: 'main',
layer: 0,
pos: {x: 100, y: 380},
width: 100,
height: 80,
radius: 5,
fill: {r: 0, g: 0, b: 0, a: 0},
stroke: {r: 1, g: 1, b: 1, a: 1},
stroke_thickness: 3,
stroke_align: 'center',
dash_len: 14,
gap_len: 6
})
// Circle with thick stroke
shapes[] = shape2d.circle({
plane: 'main',
layer: 0,
pos: {x: 250, y: 380},
radius: 45,
fill: {r: 0.2, g: 0.2, b: 0.3, a: 1},
stroke: {r: 0.5, g: 0.8, b: 1, a: 1},
stroke_thickness: 6,
stroke_align: 'outside',
dash_len: 14,
gap_len: 6
})
// Ellipse with inside stroke
shapes[] = shape2d.ellipse({
plane: 'main',
layer: 0,
pos: {x: 400, y: 380},
width: 120,
height: 70,
fill: {r: 0.4, g: 0.2, b: 0.5, a: 1},
stroke: {r: 1, g: 0.5, b: 0.8, a: 1},
stroke_thickness: 4,
stroke_align: 'inside',
dash_len: 14,
gap_len: 6
})
// Pill with dashed stroke
shapes[] = shape2d.pill({
plane: 'main',
layer: 0,
pos: {x: 550, y: 380},
width: 130,
height: 50,
fill: {r: 0.1, g: 0.3, b: 0.2, a: 1},
stroke: {r: 0.3, g: 1, b: 0.5, a: 1},
stroke_thickness: 3,
dash_len: 15,
gap_len: 8
})
// Row 3: Feather / soft edges
// Soft rect
shapes[] = shape2d.rect({
plane: 'main',
layer: 0,
pos: {x: 100, y: 260},
width: 100,
height: 80,
radius: 20,
fill: {r: 0.8, g: 0.4, b: 0.1, a: 1},
feather: 10
})
// Soft circle (glow effect)
shapes[] = shape2d.circle({
plane: 'main',
layer: 0,
pos: {x: 250, y: 260},
radius: 40,
fill: {r: 1, g: 0.9, b: 0.3, a: 1},
feather: 20
})
// Soft ellipse
shapes[] = shape2d.ellipse({
plane: 'main',
layer: 0,
pos: {x: 400, y: 260},
width: 120,
height: 60,
fill: {r: 0.3, g: 0.5, b: 1, a: 0.8},
feather: 15
})
// Sharp vs soft comparison
shapes[] = shape2d.pill({
plane: 'main',
layer: 0,
pos: {x: 550, y: 260},
width: 120,
height: 50,
fill: {r: 1, g: 0.2, b: 0.6, a: 1},
feather: 5
})
// Row 4: Opacity and blending
// Semi-transparent overlapping shapes
shapes[] = shape2d.rect({
plane: 'main',
layer: 0,
pos: {x: 80, y: 130},
width: 100,
height: 80,
fill: {r: 1, g: 0, b: 0, a: 0.6},
opacity: 1
})
shapes[] = shape2d.rect({
plane: 'main',
layer: 1,
pos: {x: 120, y: 130},
width: 100,
height: 80,
fill: {r: 0, g: 1, b: 0, a: 0.6},
opacity: 1
})
shapes[] = shape2d.rect({
plane: 'main',
layer: 2,
pos: {x: 100, y: 150},
width: 100,
height: 80,
fill: {r: 0, g: 0, b: 1, a: 0.6},
opacity: 1
})
// Animated opacity circle
shapes[] = shape2d.circle({
plane: 'main',
layer: 0,
pos: {x: 280, y: 130},
radius: 50,
fill: {r: 1, g: 1, b: 1, a: 1},
opacity: 1
})
// Different corner radii
shapes[] = shape2d.rect({
plane: 'main',
layer: 0,
pos: {x: 420, y: 130},
width: 100,
height: 80,
radius: 0,
fill: {r: 0.5, g: 0.5, b: 0.5, a: 1}
})
shapes[] = shape2d.rect({
plane: 'main',
layer: 0,
pos: {x: 540, y: 130},
width: 100,
height: 80,
radius: 20,
fill: {r: 0.6, g: 0.6, b: 0.6, a: 1}
})
shapes[] = shape2d.rect({
plane: 'main',
layer: 0,
pos: {x: 660, y: 130},
width: 100,
height: 80,
radius: 40,
fill: {r: 0.7, g: 0.7, b: 0.7, a: 1}
})
// Row 5: Large decorative shapes
// Background panel
shapes[] = shape2d.rect({
plane: 'main',
layer: -1,
pos: {x: 700, y: 400},
width: 160,
height: 350,
radius: 15,
fill: {r: 0.15, g: 0.15, b: 0.2, a: 1},
stroke: {r: 0.3, g: 0.3, b: 0.4, a: 1},
stroke_thickness: 2
})
// Decorative circles inside panel
shapes[] = shape2d.circle({
plane: 'main',
layer: 0,
pos: {x: 700, y: 500},
radius: 30,
fill: {r: 0.8, g: 0.2, b: 0.3, a: 1}
})
shapes[] = shape2d.circle({
plane: 'main',
layer: 0,
pos: {x: 700, y: 420},
radius: 25,
fill: {r: 0.2, g: 0.8, b: 0.3, a: 1}
})
shapes[] = shape2d.circle({
plane: 'main',
layer: 0,
pos: {x: 700, y: 350},
radius: 20,
fill: {r: 0.2, g: 0.3, b: 0.8, a: 1}
})
log.console("test_shape2d initialized with " + text(length(shapes)) + " shapes")
}
function update(dt) {
t += dt
// Animate opacity on the white circle (index 16)
if (shapes[16]) {
shapes[16].opacity = 0.3 + 0.7 * (0.5 + 0.5 * math.sine(t * 2))
}
// Animate the glow circle size (index 10)
var scale = null
if (shapes[10]) {
scale = 1 + 0.2 * math.sine(t * 3)
shapes[10].width = 80 * scale
shapes[10].height = 80 * scale
}
// Rotate the dashed pill stroke (index 7)
if (shapes[7]) {
shapes[4].dash_offset = t * 30
shapes[5].dash_offset = t * 30
shapes[6].dash_offset = t * 30
shapes[7].dash_offset = t * 30
}
// Pulse the decorative circles
if (shapes[21]) {
shapes[21].fill.r = 0.5 + 0.5 * math.sine(t * 1.5)
}
if (shapes[22]) {
shapes[22].fill.g = 0.5 + 0.5 * math.sine(t * 1.5 + 2.094)
}
if (shapes[23]) {
shapes[23].fill.b = 0.5 + 0.5 * math.sine(t * 1.5 + 4.188)
}
}
function render() {
var plan = compositor.compile(compositor_config)
return compositor.execute(plan)
}
init()
core.start({
width: 800,
height: 600,
title: "Test Shape2D - SDF Shapes",
framerate: 60,
update: update,
render: render
})

206
examples/test_sprite.ce Normal file
View File

@@ -0,0 +1,206 @@
log.console("test_sprite starting")
var time = use('time')
var core = use('core')
var sprite = use('sprite')
var compositor = use('compositor')
var film2d = use('film2d')
var math = use('math/radians')
var sprites = []
var t = 0
var camera = {
pos: {x: 250, y: 250},
width: 500,
height: 500,
anchor: {x: 0.5, y: 0.5}
}
var compositor_config = {
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1},
planes: [
{
name: 'main',
plane: 'main',
camera: camera,
resolution: {width: 500, height: 500},
presentation: 'stretch',
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1}
}
]
}
function init() {
// Row 1: Opacity test - 0, 0.25, 0.5, 0.75, 1.0
var i = 0
for (i = 0; i < 5; i++) {
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 50 + i * 100, y: 450},
width: 64,
height: 64,
anchor_x: 0.5,
anchor_y: 0.5,
opacity: i * 0.25
})
}
// Row 2: Tint test - red, green, blue, yellow, magenta
var tints = [
{r: 1, g: 0, b: 0, a: 1},
{r: 0, g: 1, b: 0, a: 1},
{r: 0, g: 0, b: 1, a: 1},
{r: 1, g: 1, b: 0, a: 1},
{r: 1, g: 0, b: 1, a: 1}
]
for (i = 0; i < 5; i++) {
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 50 + i * 100, y: 350},
width: 64,
height: 64,
anchor_x: 0.5,
anchor_y: 0.5,
tint: tints[i]
})
}
// Row 3: Filter test - nearest (small scaled up) vs linear (small scaled up)
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 125, y: 250},
width: 150,
height: 150,
anchor_x: 0.5,
anchor_y: 0.5,
filter: 'nearest'
})
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 375, y: 250},
width: 150,
height: 150,
anchor_x: 0.5,
anchor_y: 0.5,
filter: 'linear'
})
// Row 4: Flip test - normal, flip_x, flip_y, flip_both
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 75, y: 125},
width: 64,
height: 64,
anchor_x: 0.5,
anchor_y: 0.5,
flip: {x: false, y: false}
})
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 175, y: 125},
width: 64,
height: 64,
anchor_x: 0.5,
anchor_y: 0.5,
flip: {x: true, y: false}
})
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 275, y: 125},
width: 64,
height: 64,
anchor_x: 0.5,
anchor_y: 0.5,
flip: {x: false, y: true}
})
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 375, y: 125},
width: 64,
height: 64,
anchor_x: 0.5,
anchor_y: 0.5,
flip: {x: true, y: true}
})
// Row 5: UV scroll test - animated texture offset
sprites[] = sprite({
plane: 'main',
layer: 0,
image: 'examples/bunny',
pos: {x: 450, y: 125},
width: 64,
height: 64,
anchor_x: 0.5,
anchor_y: 0.5,
uv: {offset: {x: 0, y: 0}, scale: {x: 1, y: 1}, rotate: 0}
})
// Animated opacity sprite
sprites[] = sprite({
plane: 'main',
layer: 1,
image: 'examples/bunny',
pos: {x: 250, y: 50},
width: 80,
height: 80,
anchor_x: 0.5,
anchor_y: 0.5,
opacity: 1
})
log.console("test_sprite initialized with " + text(length(sprites)) + " sprites")
}
function update(dt) {
t += dt
// Animate opacity on the last sprite (pulsing)
var animated_opacity_sprite = sprites[length(sprites) - 1]
animated_opacity_sprite.opacity = 0.5 + 0.5 * math.sine(t * 2)
// Animate UV scroll on the UV test sprite
var uv_sprite = sprites[length(sprites) - 2]
uv_sprite.uv.offset.x = (t * 0.5) % 1
uv_sprite.uv.offset.y = (t * 0.3) % 1
// Animate tint on first tint sprite (cycling hue)
var tint_sprite = sprites[5]
tint_sprite.tint.r = 0.5 + 0.5 * math.sine(t * 1.5)
tint_sprite.tint.g = 0.5 + 0.5 * math.sine(t * 1.5 + 2.094)
tint_sprite.tint.b = 0.5 + 0.5 * math.sine(t * 1.5 + 4.188)
}
function render() {
var plan = compositor.compile(compositor_config)
return compositor.execute(plan)
}
init()
core.start({
width: 500,
height: 500,
title: "Test Sprite Features",
framerate: 60,
update: update,
render: render
})

256
examples/test_text.ce Normal file
View File

@@ -0,0 +1,256 @@
log.console("test_text starting")
var time = use('time')
var core = use('core')
var text2d = use('text2d')
var compositor = use('compositor')
var film2d = use('film2d')
var math = use('math/radians')
var texts = []
var t = 0
var camera = {
pos: {x: 250, y: 250},
width: 500,
height: 500,
anchor: {x: 0.5, y: 0.5}
}
var compositor_config = {
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1},
planes: [
{
name: 'main',
plane: 'main',
camera: camera,
resolution: {width: 500, height: 500},
presentation: 'stretch',
clear: {r: 0.1, g: 0.1, b: 0.15, a: 1}
}
]
}
function init() {
// Row 1: Normal text
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Normal Text",
pos: {x: 20, y: 470},
font: 'examples/fonts/dos',
size: 24,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Row 2: Opacity variations
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Opacity 0.25",
pos: {x: 20, y: 420},
font: 'examples/fonts/dos',
size: 20,
color: {r: 1, g: 1, b: 1, a: 1},
opacity: 0.25
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Opacity 0.5",
pos: {x: 200, y: 420},
font: 'examples/fonts/dos',
size: 20,
color: {r: 1, g: 1, b: 1, a: 1},
opacity: 0.5
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Opacity 0.75",
pos: {x: 360, y: 420},
font: 'examples/fonts/dos',
size: 20,
color: {r: 1, g: 1, b: 1, a: 1},
opacity: 0.75
})
// Row 3: Tint colors
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Red Tint",
pos: {x: 20, y: 360},
font: 'examples/fonts/dos',
size: 24,
color: {r: 1, g: 1, b: 1, a: 1},
tint: {r: 1, g: 0.2, b: 0.2, a: 1}
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Green Tint",
pos: {x: 180, y: 360},
font: 'examples/fonts/dos',
size: 24,
color: {r: 1, g: 1, b: 1, a: 1},
tint: {r: 0.2, g: 1, b: 0.2, a: 1}
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Blue Tint",
pos: {x: 360, y: 360},
font: 'examples/fonts/dos',
size: 24,
color: {r: 1, g: 1, b: 1, a: 1},
tint: {r: 0.2, g: 0.2, b: 1, a: 1}
})
// Row 4: Combined tint + opacity
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Yellow + 50% Opacity",
pos: {x: 20, y: 300},
font: 'examples/fonts/dos',
size: 20,
color: {r: 1, g: 1, b: 1, a: 1},
tint: {r: 1, g: 1, b: 0, a: 1},
opacity: 0.5
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Cyan + 75% Opacity",
pos: {x: 280, y: 300},
font: 'examples/fonts/dos',
size: 20,
color: {r: 1, g: 1, b: 1, a: 1},
tint: {r: 0, g: 1, b: 1, a: 1},
opacity: 0.75
})
// Row 5: Different sizes
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Small",
pos: {x: 20, y: 240},
font: 'examples/fonts/dos',
size: 12,
color: {r: 1, g: 1, b: 1, a: 1}
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Medium",
pos: {x: 100, y: 240},
font: 'examples/fonts/dos',
size: 20,
color: {r: 1, g: 1, b: 1, a: 1}
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Large",
pos: {x: 220, y: 240},
font: 'examples/fonts/dos',
size: 32,
color: {r: 1, g: 1, b: 1, a: 1}
})
texts[] = text2d({
plane: 'main',
layer: 0,
text: "XL",
pos: {x: 360, y: 240},
font: 'examples/fonts/dos',
size: 48,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Row 6: Animated text (pulsing opacity)
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Pulsing Opacity",
pos: {x: 150, y: 150},
font: 'examples/fonts/dos',
size: 28,
color: {r: 1, g: 1, b: 1, a: 1},
opacity: 1
})
// Row 7: Animated tint (rainbow cycle)
texts[] = text2d({
plane: 'main',
layer: 0,
text: "Rainbow Tint",
pos: {x: 150, y: 100},
font: 'examples/fonts/dos',
size: 28,
color: {r: 1, g: 1, b: 1, a: 1},
tint: {r: 1, g: 0, b: 0, a: 1}
})
// Row 8: Typewriter effect simulation
texts[] = text2d({
plane: 'main',
layer: 0,
text: "",
pos: {x: 20, y: 50},
font: 'examples/fonts/dos',
size: 20,
color: {r: 0.8, g: 1, b: 0.8, a: 1}
})
log.console("test_text initialized with " + text(length(texts)) + " text elements")
}
var typewriter_text = "Hello, this is a typewriter effect demo..."
var typewriter_index = 0
var typewriter_timer = 0
function update(dt) {
t += dt
// Animate pulsing opacity
var pulsing_text = texts[length(texts) - 3]
pulsing_text.opacity = 0.3 + 0.7 * abs(math.sine(t * 2))
// Animate rainbow tint
var rainbow_text = texts[length(texts) - 2]
rainbow_text.tint.r = 0.5 + 0.5 * math.sine(t * 2)
rainbow_text.tint.g = 0.5 + 0.5 * math.sine(t * 2 + 2.094)
rainbow_text.tint.b = 0.5 + 0.5 * math.sine(t * 2 + 4.188)
// Typewriter effect
typewriter_timer += dt
var typewriter = null
if (typewriter_timer > 0.08) {
typewriter_timer = 0
typewriter_index++
if (typewriter_index > length(typewriter_text)) {
typewriter_index = 0
}
typewriter = texts[length(texts) - 1]
typewriter.text = text(typewriter_text, 0, typewriter_index)
}
}
function render() {
var plan = compositor.compile(compositor_config)
return compositor.execute(plan)
}
init()
core.start({
width: 500,
height: 500,
title: "Test Text Features",
framerate: 60,
update: update,
render: render
})

187
examples/test_tilemap.ce Normal file
View File

@@ -0,0 +1,187 @@
log.console("test_tilemap starting")
var time = use('time')
var core = use('core')
var tilemap2d = use('tilemap2d')
var text2d = use('text2d')
var compositor = use('compositor')
var film2d = use('film2d')
var math = use('math/radians')
var tilemaps = []
var labels = []
var t = 0
var camera = {
pos: {x: 250, y: 250},
width: 500,
height: 500,
anchor: {x: 0.5, y: 0.5}
}
var compositor_config = {
clear: {r: 0.05, g: 0.05, b: 0.1, a: 1},
planes: [
{
name: 'main',
plane: 'main',
camera: camera,
resolution: {width: 500, height: 500},
presentation: 'stretch',
clear: {r: 0.05, g: 0.05, b: 0.1, a: 1}
}
]
}
function make_grid(w, h, img) {
var tiles = []
var x = 0
var y = 0
for (x = 0; x < w; x++) {
tiles[x] = []
for (y = 0; y < h; y++) {
tiles[x][y] = img
}
}
return tiles
}
function init() {
// Tilemap 1: Normal (top-left)
tilemaps[] = tilemap2d({
plane: 'main',
layer: 0,
tiles: make_grid(4, 4, 'examples/tiles/terrain_dirt_cloud'),
tile_width: 25,
tile_height: 25,
offset_x: 0,
offset_y: 14
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Normal",
pos: {x: 50, y: 480},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Tilemap 2: Opacity 0.5 (top-right)
tilemaps[] = tilemap2d({
plane: 'main',
layer: 0,
tiles: make_grid(4, 4, 'examples/tiles/terrain_dirt_cloud'),
tile_width: 25,
tile_height: 25,
offset_x: 10,
offset_y: 14,
opacity: 0.5
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Opacity 0.5",
pos: {x: 280, y: 480},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Tilemap 3: Red tint (bottom-left)
tilemaps[] = tilemap2d({
plane: 'main',
layer: 0,
tiles: make_grid(4, 4, 'examples/tiles/terrain_dirt_cloud'),
tile_width: 25,
tile_height: 25,
offset_x: 0,
offset_y: 4,
tint: {r: 1, g: 0.3, b: 0.3, a: 1}
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Red Tint",
pos: {x: 50, y: 230},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Tilemap 4: Green tint (bottom-right)
tilemaps[] = tilemap2d({
plane: 'main',
layer: 0,
tiles: make_grid(4, 4, 'examples/tiles/terrain_dirt_cloud'),
tile_width: 25,
tile_height: 25,
offset_x: 10,
offset_y: 4,
tint: {r: 0.3, g: 1, b: 0.3, a: 1}
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Green Tint",
pos: {x: 280, y: 230},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 1, a: 1}
})
// Tilemap 5: Animated opacity (center bottom)
tilemaps[] = tilemap2d({
plane: 'main',
layer: 0,
tiles: make_grid(3, 3, 'examples/tiles/terrain_dirt_cloud'),
tile_width: 30,
tile_height: 30,
offset_x: 6,
offset_y: 0,
opacity: 1
})
labels[] = text2d({
plane: 'main',
layer: 10,
text: "Animated Opacity",
pos: {x: 180, y: 20},
font: 'examples/fonts/dos',
size: 14,
color: {r: 1, g: 1, b: 0, a: 1}
})
log.console("test_tilemap initialized with " + text(length(tilemaps)) + " tilemaps")
}
function update(dt) {
t += dt
// Animate opacity on the center tilemap
var animated_tilemap = tilemaps[4]
animated_tilemap.opacity = 0.3 + 0.7 * abs(math.sine(t * 1.5))
// Animate tint on the red tilemap (pulse)
var red_tilemap = tilemaps[2]
var pulse = 0.5 + 0.5 * math.sine(t * 2)
red_tilemap.tint.r = 0.5 + 0.5 * pulse
red_tilemap.tint.g = 0.2 * pulse
red_tilemap.tint.b = 0.2 * pulse
}
function render() {
var plan = compositor.compile(compositor_config)
return compositor.execute(plan)
}
init()
core.start({
width: 500,
height: 500,
title: "Test Tilemap Features",
framerate: 60,
update: update,
render: render
})

View File

@@ -50,16 +50,16 @@ function init_board() {
row = []
srow = []
for (c = 0; c < COLS; c++) {
push(row, null)
push(srow, shape.rect({
row[] = null
srow[] = shape.rect({
pos: {x: c * TILE + TILE / 2, y: r * TILE + TILE / 2},
width: TILE - 1, height: TILE - 1,
fill: {r: 0.5, g: 0.5, b: 0.5, a: 1},
plane: 'game', layer: 0, visible: false
}))
})
}
push(board, row)
push(board_shapes, srow)
board[] = row
board_shapes[] = srow
}
}
@@ -104,10 +104,10 @@ var next_label = text2d({
var next_shapes = []
var ns = 0
for (ns = 0; ns < 4; ns++) {
push(next_shapes, shape.rect({
next_shapes[] = shape.rect({
pos: {x: 0, y: 0}, width: TILE - 1, height: TILE - 1,
fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game', layer: 2, visible: false
}))
})
}
function random_shape() {
@@ -244,10 +244,10 @@ function place_piece() {
// Create 4 shapes for active piece (layer 1 = on top of board)
var pi = 0
for (pi = 0; pi < 4; pi++) {
push(piece_shapes, shape.rect({
piece_shapes[] = shape.rect({
pos: {x: 0, y: 0}, width: TILE - 1, height: TILE - 1,
fill: {r: 1, g: 1, b: 1, a: 1}, plane: 'game', layer: 1, visible: false
}))
})
}
init_board()

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show More