fixes for gameplay
This commit is contained in:
12
CLAUDE.md
12
CLAUDE.md
@@ -16,7 +16,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
|
After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
|
||||||
|
|
||||||
## Scripting language
|
## Scripting language
|
||||||
This is called "cell", but it is is a variant of javascript and extremely similar.
|
This is called "cell", a variant of JavaScript with important differences. See docs/cell.md for detailed language documentation.
|
||||||
|
|
||||||
### Common development commands
|
### Common development commands
|
||||||
- `meson setup build_<variant>` - Configure build directory
|
- `meson setup build_<variant>` - Configure build directory
|
||||||
@@ -34,12 +34,16 @@ Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty sy
|
|||||||
- Hierarchical actor system with spawning/killing
|
- Hierarchical actor system with spawning/killing
|
||||||
- Actor lifecycle: awake, update, draw, garbage collection
|
- Actor lifecycle: awake, update, draw, garbage collection
|
||||||
|
|
||||||
### JavaScript Style Guide
|
### Cell Language Style Guide
|
||||||
- Use `use()` function for imports (Misty-style, not ES6 import/export)
|
- Use `use()` function for imports (Misty-style, not ES6 import/export)
|
||||||
- Prefer closures and javascript objects and prototypes over ES6 style classes
|
- Prefer closures and javascript objects and prototypes over ES6 style classes
|
||||||
- Follow existing JavaScript patterns in the codebase
|
- Follow existing JavaScript patterns in the codebase
|
||||||
- Functions as first-class citizens
|
- Functions as first-class citizens
|
||||||
- Do not use const or let; only var
|
- Use `def` for constants (not const)
|
||||||
|
- Use `var` for variables (block-scoped like let)
|
||||||
|
- Check for null with `== null` (no undefined in Cell)
|
||||||
|
- Use `==` for equality (always strict, no `===`)
|
||||||
|
- See docs/cell.md for complete language reference
|
||||||
|
|
||||||
### Core Systems
|
### Core Systems
|
||||||
1. **Actor System** (scripts/core/engine.js)
|
1. **Actor System** (scripts/core/engine.js)
|
||||||
@@ -99,7 +103,7 @@ cd examples/chess
|
|||||||
- Documentation is found in docs
|
- Documentation is found in docs
|
||||||
- Documentation for the JS modules loaded with 'use' is docs/api/modules
|
- Documentation for the JS modules loaded with 'use' is docs/api/modules
|
||||||
- .md files directly in docs gives a high level overview
|
- .md files directly in docs gives a high level overview
|
||||||
- docs/dull is what this specific Javascript system is (including alterations from quickjs/es6)
|
- docs/cell.md documents the Cell language (JavaScript variant used in Prosperon)
|
||||||
|
|
||||||
### Shader Development
|
### Shader Development
|
||||||
- Shaders are in `shaders/` directory as HLSL
|
- Shaders are in `shaders/` directory as HLSL
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
var input = use('input')
|
var input = use('input')
|
||||||
|
return {}
|
||||||
|
|
||||||
var downkeys = {};
|
var downkeys = {};
|
||||||
|
|
||||||
|
|||||||
@@ -129,15 +129,15 @@ draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info
|
|||||||
if (!image) throw Error('Need an image to render.')
|
if (!image) throw Error('Need an image to render.')
|
||||||
|
|
||||||
add_command("draw_slice9", {
|
add_command("draw_slice9", {
|
||||||
image: image,
|
image,
|
||||||
rect: rect,
|
rect,
|
||||||
slice: slice,
|
slice,
|
||||||
info: info,
|
info,
|
||||||
material: material
|
material
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.image = function image(image, rect, rotation, anchor, shear, info, material) {
|
draw.image = function image(image, rect, rotation, anchor, shear, info = {mode:"nearest"}, material) {
|
||||||
if (!rect) throw Error('Need rectangle to render image.')
|
if (!rect) throw Error('Need rectangle to render image.')
|
||||||
if (!image) throw Error('Need an image to render.')
|
if (!image) throw Error('Need an image to render.')
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ draw.image = function image(image, rect, rotation, anchor, shear, info, material
|
|||||||
anchor,
|
anchor,
|
||||||
shear,
|
shear,
|
||||||
info,
|
info,
|
||||||
material,
|
material
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ draw.circle = function render_circle(pos, radius, defl, material) {
|
|||||||
draw.ellipse(pos, [radius,radius], defl, material)
|
draw.ellipse(pos, [radius,radius], defl, material)
|
||||||
}
|
}
|
||||||
|
|
||||||
draw.text = function text(text, pos, font = 'fonts/c64.ttf', size = 8, color = color.white, wrap = 0) {
|
draw.text = function text(text, pos, font = 'fonts/c64.ttf', size = 8, color = {r:1,g:1,b:1,a:1}, wrap = 0) {
|
||||||
add_command("draw_text", {
|
add_command("draw_text", {
|
||||||
text,
|
text,
|
||||||
pos,
|
pos,
|
||||||
@@ -169,4 +169,43 @@ draw.text = function text(text, pos, font = 'fonts/c64.ttf', size = 8, color = c
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draw.grid = function grid(rect, spacing, thickness = 1, offset = {x: 0, y: 0}, material) {
|
||||||
|
if (!rect || rect.x == null || rect.y == null ||
|
||||||
|
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') {
|
||||||
|
throw Error('Grid requires spacing with x and y')
|
||||||
|
}
|
||||||
|
|
||||||
|
var left = rect.x
|
||||||
|
var right = rect.x + rect.width
|
||||||
|
var top = rect.y
|
||||||
|
var bottom = rect.y + rect.height
|
||||||
|
|
||||||
|
// Apply offset and align to grid
|
||||||
|
var start_x = Math.floor((left - offset.x) / spacing.x) * spacing.x + offset.x
|
||||||
|
var end_x = Math.ceil((right - offset.x) / spacing.x) * spacing.x + offset.x
|
||||||
|
var start_y = Math.floor((top - offset.y) / spacing.y) * spacing.y + offset.y
|
||||||
|
var end_y = Math.ceil((bottom - offset.y) / spacing.y) * spacing.y + offset.y
|
||||||
|
|
||||||
|
// Draw vertical lines
|
||||||
|
for (var x = start_x; x <= end_x; x += spacing.x) {
|
||||||
|
if (x >= left && x <= right) {
|
||||||
|
var line_top = Math.max(top, start_y)
|
||||||
|
var line_bottom = Math.min(bottom, end_y)
|
||||||
|
draw.line([[x, line_top], [x, line_bottom]], {thickness: thickness}, material)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw horizontal lines
|
||||||
|
for (var y = start_y; y <= end_y; y += spacing.y) {
|
||||||
|
if (y >= top && y <= bottom) {
|
||||||
|
var line_left = Math.max(left, start_x)
|
||||||
|
var line_right = Math.min(right, end_x)
|
||||||
|
draw.line([[line_left, y], [line_right, y]], {thickness: thickness}, material)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return draw
|
return draw
|
||||||
124
prosperon/ease.cm
Normal file
124
prosperon/ease.cm
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
var Ease = {
|
||||||
|
linear(t) {
|
||||||
|
return t
|
||||||
|
},
|
||||||
|
in(t) {
|
||||||
|
return t * t
|
||||||
|
},
|
||||||
|
out(t) {
|
||||||
|
var d = 1 - t
|
||||||
|
return 1 - d * d
|
||||||
|
},
|
||||||
|
inout(t) {
|
||||||
|
var d = -2 * t + 2
|
||||||
|
return t < 0.5 ? 2 * t * t : 1 - (d * d) / 2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_easing_fns(num) {
|
||||||
|
var obj = {}
|
||||||
|
|
||||||
|
obj.in = function (t) {
|
||||||
|
return Math.pow(t, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.out = function (t) {
|
||||||
|
return 1 - Math.pow(1 - t, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mult = Math.pow(2, num - 1)
|
||||||
|
obj.inout = function (t) {
|
||||||
|
return t < 0.5 ? mult * Math.pow(t, num) : 1 - Math.pow(-2 * t + 2, num) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
Ease.quad = make_easing_fns(2)
|
||||||
|
Ease.cubic = make_easing_fns(3)
|
||||||
|
Ease.quart = make_easing_fns(4)
|
||||||
|
Ease.quint = make_easing_fns(5)
|
||||||
|
|
||||||
|
Ease.expo = {
|
||||||
|
in(t) {
|
||||||
|
return t == 0 ? 0 : Math.pow(2, 10 * t - 10)
|
||||||
|
},
|
||||||
|
out(t) {
|
||||||
|
return t == 1 ? 1 : 1 - Math.pow(2, -10 * t)
|
||||||
|
},
|
||||||
|
inout(t) {
|
||||||
|
return t == 0
|
||||||
|
? 0
|
||||||
|
: t == 1
|
||||||
|
? 1
|
||||||
|
: t < 0.5
|
||||||
|
? Math.pow(2, 20 * t - 10) / 2
|
||||||
|
: (2 - Math.pow(2, -20 * t + 10)) / 2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ease.bounce = {
|
||||||
|
in(t) {
|
||||||
|
return 1 - this.out(1 - t)
|
||||||
|
},
|
||||||
|
out(t) {
|
||||||
|
var n1 = 7.5625
|
||||||
|
var d1 = 2.75
|
||||||
|
if (t < 1 / d1) {
|
||||||
|
return n1 * t * t
|
||||||
|
} else if (t < 2 / d1) {
|
||||||
|
return n1 * (t -= 1.5 / d1) * t + 0.75
|
||||||
|
} else if (t < 2.5 / d1) {
|
||||||
|
return n1 * (t -= 2.25 / d1) * t + 0.9375
|
||||||
|
} else return n1 * (t -= 2.625 / d1) * t + 0.984375
|
||||||
|
},
|
||||||
|
inout(t) {
|
||||||
|
return t < 0.5 ? (1 - this.out(1 - 2 * t)) / 2 : (1 + this.out(2 * t - 1)) / 2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ease.sine = {
|
||||||
|
in(t) {
|
||||||
|
return 1 - Math.cos((t * Math.PI) / 2)
|
||||||
|
},
|
||||||
|
out(t) {
|
||||||
|
return Math.sin((t * Math.PI) / 2)
|
||||||
|
},
|
||||||
|
inout(t) {
|
||||||
|
return -(Math.cos(Math.PI * t) - 1) / 2
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ease.elastic = {
|
||||||
|
in(t) {
|
||||||
|
return t == 0
|
||||||
|
? 0
|
||||||
|
: t == 1
|
||||||
|
? 1
|
||||||
|
: -Math.pow(2, 10 * t - 10) *
|
||||||
|
Math.sin((t * 10 - 10.75) * this.c4)
|
||||||
|
},
|
||||||
|
out(t) {
|
||||||
|
return t == 0
|
||||||
|
? 0
|
||||||
|
: t == 1
|
||||||
|
? 1
|
||||||
|
: Math.pow(2, -10 * t) *
|
||||||
|
Math.sin((t * 10 - 0.75) * this.c4) +
|
||||||
|
1
|
||||||
|
},
|
||||||
|
inout(t) {
|
||||||
|
return t == 0
|
||||||
|
? 0
|
||||||
|
: t == 1
|
||||||
|
? 1
|
||||||
|
: t < 0.5
|
||||||
|
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
|
||||||
|
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2 + 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Ease.elastic.c4 = (2 * Math.PI) / 3
|
||||||
|
Ease.elastic.c5 = (2 * Math.PI) / 4.5
|
||||||
|
|
||||||
|
return Ease
|
||||||
@@ -6,11 +6,7 @@ var time = use('time')
|
|||||||
var tilemap = use('tilemap')
|
var tilemap = use('tilemap')
|
||||||
|
|
||||||
// Frame timing variables
|
// Frame timing variables
|
||||||
var frame_times = []
|
var framerate = 60
|
||||||
var frame_time_index = 0
|
|
||||||
var max_frame_samples = 60
|
|
||||||
var frame_start_time = 0
|
|
||||||
var average_frame_time = 0
|
|
||||||
|
|
||||||
var game = args[0]
|
var game = args[0]
|
||||||
|
|
||||||
@@ -34,40 +30,45 @@ $_.start(e => {
|
|||||||
|
|
||||||
var geometry = use('geometry')
|
var geometry = use('geometry')
|
||||||
|
|
||||||
function updateCameraMatrix(camera, winW, winH) {
|
var camera = {}
|
||||||
// world→NDC
|
|
||||||
def sx = 1 / camera.size[0];
|
|
||||||
def sy = 1 / camera.size[1];
|
|
||||||
def ox = camera.pos[0] - camera.size[0] * camera.anchor[0];
|
|
||||||
def oy = camera.pos[1] - camera.size[1] * camera.anchor[1];
|
|
||||||
|
|
||||||
// NDC→pixels
|
function updateCameraMatrix(cam) {
|
||||||
def vx = camera.viewport.x * winW;
|
def win_w = logical.width
|
||||||
def vy = camera.viewport.y * winH;
|
def win_h = logical.height
|
||||||
def vw = camera.viewport.width * winW;
|
def view_w = (cam.size?.[0] ?? win_w) / cam.zoom
|
||||||
def vh = camera.viewport.height * winH;
|
def view_h = (cam.size?.[1] ?? win_h) / cam.zoom
|
||||||
|
|
||||||
// final “mat” coefficients
|
def ox = cam.pos[0] - view_w * (cam.anchor?.[0] ?? 0)
|
||||||
// [ a 0 c ]
|
def oy = cam.pos[1] - view_h * (cam.anchor?.[1] ?? 0)
|
||||||
// [ 0 e f ]
|
|
||||||
// [ 0 0 1 ]
|
|
||||||
camera.a = sx * vw;
|
|
||||||
camera.c = vx - camera.a * ox;
|
|
||||||
camera.e = -sy * vh;
|
|
||||||
camera.f = vy + vh + sy * vh * oy;
|
|
||||||
|
|
||||||
// and store the inverses so we can go back cheaply
|
def vx = (cam.viewport?.x ?? 0) * win_w
|
||||||
camera.ia = 1 / camera.a;
|
def vy = (cam.viewport?.y ?? 0) * win_h
|
||||||
camera.ic = -camera.c * camera.ia;
|
def vw = (cam.viewport?.width ?? 1) * win_w
|
||||||
camera.ie = 1 / camera.e;
|
def vh = (cam.viewport?.height ?? 1) * win_h
|
||||||
camera.if = -camera.f * camera.ie;
|
|
||||||
|
def sx = vw / view_w
|
||||||
|
def sy = vh / view_h // flip-Y later
|
||||||
|
|
||||||
|
/* affine matrix that SDL wants (Y going down) */
|
||||||
|
cam.a = sx
|
||||||
|
cam.c = vx - sx * ox
|
||||||
|
cam.e = -sy // <-- minus = flip Y
|
||||||
|
cam.f = vy + vh + sy * oy
|
||||||
|
|
||||||
|
/* convenience inverses */
|
||||||
|
cam.ia = 1 / cam.a
|
||||||
|
cam.ic = -cam.c / cam.a
|
||||||
|
cam.ie = 1 / cam.e
|
||||||
|
cam.if = -cam.f / cam.e
|
||||||
|
|
||||||
|
camera = cam
|
||||||
}
|
}
|
||||||
|
|
||||||
//---- forward transform ----
|
//---- forward transform ----
|
||||||
function worldToScreenPoint(pos, camera) {
|
function worldToScreenPoint([x,y], camera) {
|
||||||
return {
|
return {
|
||||||
x: camera.a * pos[0] + camera.c,
|
x: camera.a * x + camera.c,
|
||||||
y: camera.e * pos[1] + camera.f
|
y: camera.e * y + camera.f
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,40 +81,21 @@ function screenToWorldPoint(pos, camera) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//---- rectangle (two corner) ----
|
//---- rectangle (two corner) ----
|
||||||
function worldToScreenRect(rect, camera) {
|
function worldToScreenRect({x,y,width,height}, camera) {
|
||||||
// map bottom-left and top-right
|
// map bottom-left and top-right
|
||||||
def x1 = camera.a * rect.x + camera.c;
|
def x1 = camera.a * x + camera.c;
|
||||||
def y1 = camera.e * rect.y + camera.f;
|
def y1 = camera.e * y + camera.f;
|
||||||
def x2 = camera.a * (rect.x + rect.width) + camera.c;
|
def x2 = camera.a * (x + width) + camera.c;
|
||||||
def y2 = camera.e * (rect.y + rect.height) + camera.f;
|
def y2 = camera.e * (y + height) + camera.f;
|
||||||
|
|
||||||
// pick mins and abs deltas
|
|
||||||
def x0 = x1 < x2 ? x1 : x2;
|
|
||||||
def y0 = y1 < y2 ? y1 : y2;
|
|
||||||
return {
|
return {
|
||||||
x: x0,
|
x:Math.min(x1,x2),
|
||||||
y: y0,
|
y:Math.min(y1,y2),
|
||||||
width: x2 > x1 ? x2 - x1 : x1 - x2,
|
width:Math.abs(x2-x1),
|
||||||
height: y2 > y1 ? y2 - y1 : y1 - y2
|
height:Math.abs(y2-y1)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var camera = {
|
|
||||||
size: [640,480],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
|
|
||||||
pos: [250,250],//{x:0,y:0}, // where it is
|
|
||||||
fov:50,
|
|
||||||
near_z:0,
|
|
||||||
far_z:1000,
|
|
||||||
viewport: {x:0,y:0,width:1,height:1}, // viewport it appears on screen
|
|
||||||
ortho:true,
|
|
||||||
anchor:[0.5,0.5],//{x:0.5,y:0.5},
|
|
||||||
rotation:[0,0,0,1],
|
|
||||||
surface: null
|
|
||||||
}
|
|
||||||
|
|
||||||
var util = use('util')
|
|
||||||
var cammy = util.camera_globals(camera)
|
|
||||||
|
|
||||||
var graphics
|
var graphics
|
||||||
|
|
||||||
var gameactor
|
var gameactor
|
||||||
@@ -122,12 +104,13 @@ var images = {}
|
|||||||
|
|
||||||
var renderer_commands = []
|
var renderer_commands = []
|
||||||
|
|
||||||
|
var win_size = {width:500,height:500}
|
||||||
|
var logical = {width:500,height:500}
|
||||||
|
|
||||||
// Convert high-level draw commands to low-level renderer commands
|
// Convert high-level draw commands to low-level renderer commands
|
||||||
function translate_draw_commands(commands) {
|
function translate_draw_commands(commands) {
|
||||||
if (!graphics) return
|
if (!graphics) return
|
||||||
|
|
||||||
updateCameraMatrix(camera,500,500)
|
|
||||||
|
|
||||||
renderer_commands.length = 0
|
renderer_commands.length = 0
|
||||||
|
|
||||||
commands.forEach(function(cmd) {
|
commands.forEach(function(cmd) {
|
||||||
@@ -140,6 +123,10 @@ function translate_draw_commands(commands) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch(cmd.cmd) {
|
switch(cmd.cmd) {
|
||||||
|
case "camera":
|
||||||
|
updateCameraMatrix(cmd.camera, win_size.width, win_size.height)
|
||||||
|
break
|
||||||
|
|
||||||
case "draw_rect":
|
case "draw_rect":
|
||||||
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||||
// Handle rectangles with optional rounding and thickness
|
// Handle rectangles with optional rounding and thickness
|
||||||
@@ -208,7 +195,10 @@ function translate_draw_commands(commands) {
|
|||||||
case "draw_line":
|
case "draw_line":
|
||||||
renderer_commands.push({
|
renderer_commands.push({
|
||||||
op: "line",
|
op: "line",
|
||||||
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera))}
|
data: {points: cmd.points.map(p => {
|
||||||
|
var pt = worldToScreenPoint(p, camera)
|
||||||
|
return [pt.x, pt.y]
|
||||||
|
})}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -238,13 +228,12 @@ function translate_draw_commands(commands) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
log.console(json.encode(renderer_commands[renderer_commands.length-1]))
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case "draw_text":
|
case "draw_text":
|
||||||
if (!cmd.text) break
|
if (!cmd.text) break
|
||||||
if (!cmd.pos) break
|
if (!cmd.pos) break
|
||||||
var rect = worldToScreenRect({x:cmd.pos.x, y:cmd.pos.y, width:8, height:8}, camera, 500,500)
|
var rect = worldToScreenRect({x:cmd.pos.x, y:cmd.pos.y, width:8, height:8}, camera)
|
||||||
var pos = {x: rect.x, y: rect.y}
|
var pos = {x: rect.x, y: rect.y}
|
||||||
renderer_commands.push({
|
renderer_commands.push({
|
||||||
op: "debugText",
|
op: "debugText",
|
||||||
@@ -293,38 +282,28 @@ function rpc_req(actor, msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game_rec = parseq.sequence([
|
|
||||||
rpc_req(gameactor, {kind:'update', dt:1/60}),
|
|
||||||
rpc_req(gameactor, {kind:'draw'})
|
|
||||||
])
|
|
||||||
|
|
||||||
var pending_draw = null
|
var pending_draw = null
|
||||||
var pending_next = null
|
var pending_next = null
|
||||||
var last_time = time.number()
|
var last_time = time.number()
|
||||||
var frames = []
|
|
||||||
var frame_avg = 0
|
|
||||||
|
|
||||||
var input = use('input')
|
var input = use('input')
|
||||||
|
|
||||||
var input_state = {
|
var input_state = {
|
||||||
poll: 1/60
|
poll: 1/framerate
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1) input runs completely independently
|
// 1) input runs completely independently
|
||||||
function poll_input() {
|
function poll_input() {
|
||||||
send(video, {kind:'input', op:'get'}, evs => {
|
send(video, {kind:'input', op:'get'}, evs => {
|
||||||
for (var ev of evs) {
|
for (var ev of evs) {
|
||||||
|
if (ev.type == 'window_pixel_size_changed') {
|
||||||
|
win_size.width = ev.width
|
||||||
|
win_size.height = ev.height
|
||||||
|
}
|
||||||
|
|
||||||
if (ev.type == 'quit')
|
if (ev.type == 'quit')
|
||||||
$_.stop()
|
$_.stop()
|
||||||
|
|
||||||
if (ev.type.includes('mouse')) {
|
|
||||||
if (ev.pos)
|
|
||||||
ev.pos = screenToWorldPoint(ev.pos, camera, 500,500)
|
|
||||||
|
|
||||||
if (ev.d_pos)
|
|
||||||
ev.d_pos.y *= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.type.includes('key')) {
|
if (ev.type.includes('key')) {
|
||||||
if (ev.key)
|
if (ev.key)
|
||||||
ev.key = input.keyname(ev.key)
|
ev.key = input.keyname(ev.key)
|
||||||
@@ -339,37 +318,25 @@ function poll_input() {
|
|||||||
// 2) helper to build & send a batch, then call done()
|
// 2) helper to build & send a batch, then call done()
|
||||||
function create_batch(draw_cmds, done) {
|
function create_batch(draw_cmds, done) {
|
||||||
def batch = [
|
def batch = [
|
||||||
{op:'set', prop:'drawColor', value:[0.1,0.1,0.15,1]},
|
{op:'set', prop:'drawColor', value:{r:0.1,g:0.1,b:0.15,a:1}},
|
||||||
{op:'clear'}
|
{op:'clear'}
|
||||||
]
|
]
|
||||||
if (draw_cmds && draw_cmds.length)
|
if (draw_cmds && draw_cmds.length)
|
||||||
batch.push(...translate_draw_commands(draw_cmds))
|
batch.push(...translate_draw_commands(draw_cmds))
|
||||||
|
|
||||||
batch.push(
|
batch.push(
|
||||||
{op:'set', prop:'drawColor', value:[1,1,1,1]},
|
{op:'set', prop:'drawColor', value:{r:1,g:1,b:1,a:1}},
|
||||||
{op:'debugText', data:{pos:{x:10,y:10}, text:`Fps: ${(1/frame_avg).toFixed(2)}`}},
|
// {op:'debugText', data:{pos:{x:10,y:10}, text:`Fps: ${(1/frame_avg).toFixed(2)}`}},
|
||||||
{op:'present'}
|
{op:'present'}
|
||||||
)
|
)
|
||||||
|
|
||||||
send(video, {kind:'renderer', op:'batch', data:batch}, () => {
|
send(video, {kind:'renderer', op:'batch', data:batch}, done)
|
||||||
def now = time.number()
|
|
||||||
def dt = now - last_time
|
|
||||||
last_time = now
|
|
||||||
|
|
||||||
frames.push(dt)
|
|
||||||
if (frames.length > 60) frames.shift()
|
|
||||||
let sum = 0
|
|
||||||
for (let f of frames) sum += f
|
|
||||||
frame_avg = sum / frames.length
|
|
||||||
|
|
||||||
done(dt)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) kick off the very first update→draw
|
// 3) kick off the very first update→draw
|
||||||
function start_pipeline() {
|
function start_pipeline() {
|
||||||
poll_input()
|
poll_input()
|
||||||
send(gameactor, {kind:'update', dt:1/60}, () => {
|
send(gameactor, {kind:'update', dt:0}, () => {
|
||||||
send(gameactor, {kind:'draw'}, cmds => {
|
send(gameactor, {kind:'draw'}, cmds => {
|
||||||
pending_draw = cmds
|
pending_draw = cmds
|
||||||
render_step()
|
render_step()
|
||||||
@@ -378,24 +345,26 @@ function start_pipeline() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render_step() {
|
function render_step() {
|
||||||
// a) fire off the next update→draw immediately
|
// a) Calculate actual dt since last frame
|
||||||
def dt = time.number() - last_time
|
def now = time.number()
|
||||||
send(gameactor, {kind:'update', dt:1/60}, () =>
|
def dt = now - last_time
|
||||||
send(gameactor, {kind:'draw'}, cmds => pending_next = cmds)
|
last_time = now
|
||||||
)
|
|
||||||
|
// b) Send update with actual dt, then wait for draw response
|
||||||
// c) render the current frame
|
send(gameactor, {kind:'update', dt}, () => {
|
||||||
create_batch(pending_draw, ttr => { // time to render
|
send(gameactor, {kind:'draw'}, cmds => {
|
||||||
// only swap in when there's a new set of commands
|
// Only render after receiving draw commands
|
||||||
if (pending_next) {
|
pending_draw = cmds
|
||||||
pending_draw = pending_next
|
|
||||||
pending_next = null
|
// c) render the current frame
|
||||||
}
|
create_batch(pending_draw, _ => { // time to render
|
||||||
|
def frame_end = time.number()
|
||||||
// d) schedule the next render step
|
def wait_time = Math.max(0, (frame_end - now) - 1/framerate)
|
||||||
def render_dur = time.number() - last_time
|
|
||||||
def wait = Math.max(0, 1/60 - ttr)
|
// e) Schedule next frame
|
||||||
$_.delay(render_step, 0)
|
$_.delay(render_step, wait_time)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +378,15 @@ $_.receiver(e => {
|
|||||||
prop:'logicalPresentation',
|
prop:'logicalPresentation',
|
||||||
value: {...e}
|
value: {...e}
|
||||||
})
|
})
|
||||||
|
logical.width = e.width
|
||||||
|
logical.height = e.height
|
||||||
|
break
|
||||||
|
case 'framerate':
|
||||||
|
// Allow setting target framerate dynamically
|
||||||
|
if (e.fps && e.fps > 0) {
|
||||||
|
framerate = e.fps
|
||||||
|
input_state.poll = 1/framerate
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ $_.receiver(function(msg) {
|
|||||||
|
|
||||||
var response = {};
|
var response = {};
|
||||||
|
|
||||||
|
// log.console(json.encode(msg))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (msg.kind) {
|
switch (msg.kind) {
|
||||||
case 'window':
|
case 'window':
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ var pcms = {};
|
|||||||
audio.pcm = function pcm(file)
|
audio.pcm = function pcm(file)
|
||||||
{
|
{
|
||||||
file = res.find_sound(file);
|
file = res.find_sound(file);
|
||||||
if (!file) throw new Error(`Could not findfile ${file}`);
|
if (!file) return//throw new Error(`Could not findfile ${file}`);
|
||||||
if (pcms[file]) return pcms[file];
|
if (pcms[file]) return pcms[file];
|
||||||
var bytes = io.slurpbytes(file)
|
var bytes = io.slurpbytes(file)
|
||||||
var newpcm = soloud.load_wav_mem(io.slurpbytes(file));
|
var newpcm = soloud.load_wav_mem(io.slurpbytes(file));
|
||||||
@@ -88,9 +88,6 @@ var BYTES_PER_F = 4
|
|||||||
var SAMPLES = FRAMES * CHANNELS
|
var SAMPLES = FRAMES * CHANNELS
|
||||||
var CHUNK_BYTES = FRAMES * CHANNELS * BYTES_PER_F
|
var CHUNK_BYTES = FRAMES * CHANNELS * BYTES_PER_F
|
||||||
|
|
||||||
var mixview = new Float32Array(FRAMES*CHANNELS)
|
|
||||||
var mixbuf = mixview.buffer
|
|
||||||
|
|
||||||
function pump()
|
function pump()
|
||||||
{
|
{
|
||||||
if (feeder.queued() < CHUNK_BYTES*3) {
|
if (feeder.queued() < CHUNK_BYTES*3) {
|
||||||
|
|||||||
@@ -1,281 +1,79 @@
|
|||||||
var util = use('util')
|
var Ease = use('ease')
|
||||||
|
var time = use('time')
|
||||||
|
|
||||||
var Ease = {
|
var rate = 1/240
|
||||||
linear(t) {
|
|
||||||
return t
|
var TweenEngine = {
|
||||||
|
tweens: [],
|
||||||
|
add(tween) {
|
||||||
|
this.tweens.push(tween)
|
||||||
},
|
},
|
||||||
in(t) {
|
remove(tween) {
|
||||||
return t * t
|
this.tweens = this.tweens.filter(t => t != tween)
|
||||||
},
|
|
||||||
out(t) {
|
|
||||||
var d = 1 - t
|
|
||||||
return 1 - d * d
|
|
||||||
},
|
|
||||||
inout(t) {
|
|
||||||
var d = -2 * t + 2
|
|
||||||
return t < 0.5 ? 2 * t * t : 1 - (d * d) / 2
|
|
||||||
},
|
},
|
||||||
|
update(dt) {
|
||||||
|
var now = time.number()
|
||||||
|
for (var tween of this.tweens.slice()) {
|
||||||
|
tween._update(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
$_.delay(_ => TweenEngine.update(), rate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function make_easing_fns(num) {
|
function Tween(obj) {
|
||||||
var obj = {}
|
this.obj = obj
|
||||||
|
this.startVals = {}
|
||||||
|
this.endVals = {}
|
||||||
|
this.duration = 0
|
||||||
|
this.easing = Ease.linear
|
||||||
|
this.startTime = 0
|
||||||
|
this.onCompleteCallback = function() {}
|
||||||
|
}
|
||||||
|
|
||||||
obj.in = function (t) {
|
Tween.prototype.to = function(props, duration) {
|
||||||
return Math.pow(t, num)
|
for (var key in props) {
|
||||||
|
this.startVals[key] = this.obj[key]
|
||||||
|
this.endVals[key] = props[key]
|
||||||
|
}
|
||||||
|
this.duration = duration
|
||||||
|
this.startTime = time.number()
|
||||||
|
|
||||||
|
TweenEngine.add(this)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
Tween.prototype.ease = function(easingFn) {
|
||||||
|
this.easing = easingFn
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
Tween.prototype.onComplete = function(callback) {
|
||||||
|
this.onCompleteCallback = callback
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
Tween.prototype._update = function(now) {
|
||||||
|
var elapsed = now - this.startTime
|
||||||
|
var t = Math.min(elapsed / this.duration, 1)
|
||||||
|
var eased = this.easing(t)
|
||||||
|
|
||||||
|
for (var key in this.endVals) {
|
||||||
|
var start = this.startVals[key]
|
||||||
|
var end = this.endVals[key]
|
||||||
|
this.obj[key] = start + (end - start) * eased
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.out = function (t) {
|
if (t == 1) {
|
||||||
return 1 - Math.pow(1 - t, num)
|
this.onCompleteCallback()
|
||||||
|
TweenEngine.remove(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
var mult = Math.pow(2, num - 1)
|
|
||||||
obj.inout = function (t) {
|
|
||||||
return t < 0.5 ? mult * Math.pow(t, num) : 1 - Math.pow(-2 * t + 2, num) / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ease.quad = make_easing_fns(2)
|
function tween(obj) {
|
||||||
Ease.cubic = make_easing_fns(3)
|
return new Tween(obj)
|
||||||
Ease.quart = make_easing_fns(4)
|
|
||||||
Ease.quint = make_easing_fns(5)
|
|
||||||
|
|
||||||
Ease.expo = {
|
|
||||||
in(t) {
|
|
||||||
return t == 0 ? 0 : Math.pow(2, 10 * t - 10)
|
|
||||||
},
|
|
||||||
out(t) {
|
|
||||||
return t == 1 ? 1 : 1 - Math.pow(2, -10 * t)
|
|
||||||
},
|
|
||||||
inout(t) {
|
|
||||||
return t == 0
|
|
||||||
? 0
|
|
||||||
: t == 1
|
|
||||||
? 1
|
|
||||||
: t < 0.5
|
|
||||||
? Math.pow(2, 20 * t - 10) / 2
|
|
||||||
: (2 - Math.pow(2, -20 * t + 10)) / 2
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ease.bounce = {
|
$_.delay(_ => TweenEngine.update(), rate)
|
||||||
in(t) {
|
|
||||||
return 1 - this.out(t - 1)
|
|
||||||
},
|
|
||||||
out(t) {
|
|
||||||
var n1 = 7.5625
|
|
||||||
var d1 = 2.75
|
|
||||||
if (t < 1 / d1) {
|
|
||||||
return n1 * t * t
|
|
||||||
} else if (t < 2 / d1) {
|
|
||||||
return n1 * (t -= 1.5 / d1) * t + 0.75
|
|
||||||
} else if (t < 2.5 / d1) {
|
|
||||||
return n1 * (t -= 2.25 / d1) * t + 0.9375
|
|
||||||
} else return n1 * (t -= 2.625 / d1) * t + 0.984375
|
|
||||||
},
|
|
||||||
inout(t) {
|
|
||||||
return t < 0.5 ? (1 - this.out(1 - 2 * t)) / 2 : (1 + this.out(2 * t - 1)) / 2
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ease.sine = {
|
return tween
|
||||||
in(t) {
|
|
||||||
return 1 - Math.cos((t * Math.PI) / 2)
|
|
||||||
},
|
|
||||||
out(t) {
|
|
||||||
return Math.sin((t * Math.PI) / 2)
|
|
||||||
},
|
|
||||||
inout(t) {
|
|
||||||
return -(Math.cos(Math.PI * t) - 1) / 2
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ease.elastic = {
|
|
||||||
in(t) {
|
|
||||||
return t == 0
|
|
||||||
? 0
|
|
||||||
: t == 1
|
|
||||||
? 1
|
|
||||||
: -Math.pow(2, 10 * t - 10) *
|
|
||||||
Math.sin((t * 10 - 10.75) * this.c4)
|
|
||||||
},
|
|
||||||
out(t) {
|
|
||||||
return t == 0
|
|
||||||
? 0
|
|
||||||
: t == 1
|
|
||||||
? 1
|
|
||||||
: Math.pow(2, -10 * t) *
|
|
||||||
Math.sin((t * 10 - 0.75) * this.c4) +
|
|
||||||
1
|
|
||||||
},
|
|
||||||
inout(t) {
|
|
||||||
t == 0
|
|
||||||
? 0
|
|
||||||
: t == 1
|
|
||||||
? 1
|
|
||||||
: t < 0.5
|
|
||||||
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2
|
|
||||||
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * this.c5)) / 2 + 1
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ease.elastic.c4 = (2 * Math.PI) / 3
|
|
||||||
Ease.elastic.c5 = (2 * Math.PI) / 4.5
|
|
||||||
|
|
||||||
var tween = function (from, to, time, fn, cb) {
|
|
||||||
var start = os.now()
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
stop()
|
|
||||||
fn = null
|
|
||||||
stop = null
|
|
||||||
cb = null
|
|
||||||
update = null
|
|
||||||
}
|
|
||||||
|
|
||||||
var update = function tween_update(dt) {
|
|
||||||
var elapsed = os.now() - start
|
|
||||||
fn(util.obj_lerp(from, to, elapsed / time))
|
|
||||||
if (elapsed >= time) {
|
|
||||||
fn(to)
|
|
||||||
cb?.()
|
|
||||||
cleanup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var stop = Register.update.register(update)
|
|
||||||
return cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
var Tween = {
|
|
||||||
default: {
|
|
||||||
loop: "hold",
|
|
||||||
time: 1,
|
|
||||||
ease: Ease.linear,
|
|
||||||
whole: true,
|
|
||||||
cb: function () {},
|
|
||||||
},
|
|
||||||
start(obj, target, tvals, options) {
|
|
||||||
var defn = Object.create(this.default)
|
|
||||||
Object.assign(defn, options)
|
|
||||||
|
|
||||||
if (defn.loop == "circle") tvals.push(tvals[0])
|
|
||||||
else if (defn.loop == "yoyo") {
|
|
||||||
for (var i = tvals.length - 2; i >= 0; i--) tvals.push(tvals[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
defn.accum = 0
|
|
||||||
var slices = tvals.length - 1
|
|
||||||
var slicelen = 1 / slices
|
|
||||||
|
|
||||||
defn.fn = function (dt) {
|
|
||||||
defn.accum += dt
|
|
||||||
if (defn.accum >= defn.time && defn.loop == "hold") {
|
|
||||||
if (typeof target == "string") obj[target] = tvals[tvals.length - 1]
|
|
||||||
else target(tvals[tvals.length - 1])
|
|
||||||
defn.pause()
|
|
||||||
defn.cb.call(obj)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defn.pct = (defn.accum % defn.time) / defn.time
|
|
||||||
if (defn.loop == "none" && defn.accum >= defn.time) defn.stop()
|
|
||||||
|
|
||||||
var t = defn.whole ? defn.ease(defn.pct) : defn.pct
|
|
||||||
var nval = t / slicelen
|
|
||||||
var i = Math.trunc(nval)
|
|
||||||
nval -= i
|
|
||||||
if (!defn.whole) nval = defn.ease(nval)
|
|
||||||
|
|
||||||
if (typeof target == "string") obj[target] = tvals[i].lerp(tvals[i + 1], nval)
|
|
||||||
else target(tvals[i].lerp(tvals[i + 1], nval))
|
|
||||||
}
|
|
||||||
|
|
||||||
var playing = false
|
|
||||||
|
|
||||||
defn.play = function () {
|
|
||||||
if (playing) return
|
|
||||||
defn._end = Register.update.register(defn.fn.bind(defn))
|
|
||||||
playing = true
|
|
||||||
}
|
|
||||||
defn.restart = function () {
|
|
||||||
defn.accum = 0
|
|
||||||
if (typeof target == "string") obj[target] = tvals[0]
|
|
||||||
else target(tvals[0])
|
|
||||||
}
|
|
||||||
defn.stop = function () {
|
|
||||||
if (!playing) return
|
|
||||||
defn.pause()
|
|
||||||
defn.restart()
|
|
||||||
}
|
|
||||||
defn.pause = function () {
|
|
||||||
defn._end()
|
|
||||||
if (!playing) return
|
|
||||||
playing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return defn
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Tween.make = Tween.start
|
|
||||||
|
|
||||||
Ease[cell.DOC] = `
|
|
||||||
This object provides multiple easing functions that remap a 0..1 input to produce
|
|
||||||
a smoothed or non-linear output. They can be used standalone or inside tweens.
|
|
||||||
|
|
||||||
Available functions:
|
|
||||||
- linear(t)
|
|
||||||
- in(t), out(t), inout(t)
|
|
||||||
- quad.in, quad.out, quad.inout
|
|
||||||
- cubic.in, cubic.out, cubic.inout
|
|
||||||
- quart.in, quart.out, quart.inout
|
|
||||||
- quint.in, quint.out, quint.inout
|
|
||||||
- expo.in, expo.out, expo.inout
|
|
||||||
- bounce.in, bounce.out, bounce.inout
|
|
||||||
- sine.in, sine.out, sine.inout
|
|
||||||
- elastic.in, elastic.out, elastic.inout
|
|
||||||
|
|
||||||
All easing functions expect t in [0..1] and return a remapped value in [0..1].
|
|
||||||
`
|
|
||||||
|
|
||||||
tween[cell.DOC] = `
|
|
||||||
:param from: The starting object or value to interpolate from.
|
|
||||||
:param to: The ending object or value to interpolate to.
|
|
||||||
:param time: The total duration of the tween in milliseconds or some time unit.
|
|
||||||
:param fn: A callback function that receives the interpolated value at each update.
|
|
||||||
:param cb: (Optional) A callback invoked once the tween completes.
|
|
||||||
:return: A function that, when called, cleans up and stops the tween.
|
|
||||||
|
|
||||||
Creates a simple tween that linearly interpolates from "from" to "to" over "time"
|
|
||||||
and calls "fn" with each interpolated value. Once finished, "fn" is called with "to",
|
|
||||||
then "cb" is invoked if provided, and the tween is cleaned up.
|
|
||||||
`
|
|
||||||
|
|
||||||
Tween[cell.DOC] = `
|
|
||||||
An object providing methods to create and control tweens with additional features
|
|
||||||
like looping, custom easing, multiple stages, etc.
|
|
||||||
|
|
||||||
Properties:
|
|
||||||
- default: A template object with loop/time/ease/whole/cb properties.
|
|
||||||
Methods:
|
|
||||||
- start(obj, target, tvals, options): Create a tween over multiple target values.
|
|
||||||
- make: Alias of start.
|
|
||||||
`
|
|
||||||
|
|
||||||
Tween.start[cell.DOC] = `
|
|
||||||
:param obj: The object whose property is being tweened, or context for the callback.
|
|
||||||
:param target: A string property name in obj or a callback function receiving interpolated values.
|
|
||||||
:param tvals: An array of values to tween through (each must support .lerp()).
|
|
||||||
:param options: An optional object overriding defaults (loop type, time, ease, etc.).
|
|
||||||
:return: A tween definition object with .play(), .pause(), .stop(), .restart(), etc.
|
|
||||||
|
|
||||||
Set up a multi-stage tween. You can specify looping modes (none, hold, restart, yoyo, circle),
|
|
||||||
time is the total duration, and "ease" can be any function from Ease. Once started, it updates
|
|
||||||
every frame until completion or stop/pause is called.
|
|
||||||
`
|
|
||||||
|
|
||||||
Tween.make[cell.DOC] = `
|
|
||||||
Alias of Tween.start. See Tween.start for usage details.
|
|
||||||
`
|
|
||||||
|
|
||||||
return { Tween, Ease, tween }
|
|
||||||
@@ -43,7 +43,7 @@ var console_mod = cell.hidden.console
|
|||||||
globalThis.log = {}
|
globalThis.log = {}
|
||||||
log.console = function(msg)
|
log.console = function(msg)
|
||||||
{
|
{
|
||||||
var caller = caller_data(2)
|
var caller = caller_data(1)
|
||||||
console_mod.print(console_rec(caller.line, caller.file, msg))
|
console_mod.print(console_rec(caller.line, caller.file, msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ globalThis.use = function use(file, ...args) {
|
|||||||
} else {
|
} else {
|
||||||
// Compile from source
|
// Compile from source
|
||||||
var script = io.slurp(path)
|
var script = io.slurp(path)
|
||||||
var mod_script = `(function setup_${mod_name}_module(arg){${script};})`
|
var mod_script = `(function setup_${mod_name}_module(arg, $_){${script};})`
|
||||||
fn = js.compile(path, mod_script)
|
fn = js.compile(path, mod_script)
|
||||||
|
|
||||||
// Save compiled version to .cell directory
|
// Save compiled version to .cell directory
|
||||||
@@ -221,7 +221,7 @@ globalThis.use = function use(file, ...args) {
|
|||||||
context.__proto__ = embed_mod
|
context.__proto__ = embed_mod
|
||||||
|
|
||||||
// Call the script - pass embedded module as 'this' if it exists
|
// Call the script - pass embedded module as 'this' if it exists
|
||||||
var ret = fn.call(context, args)
|
var ret = fn.call(context, args, $_)
|
||||||
|
|
||||||
// If script doesn't return anything, check if we have embedded module
|
// If script doesn't return anything, check if we have embedded module
|
||||||
if (!ret && embed_mod) {
|
if (!ret && embed_mod) {
|
||||||
|
|||||||
@@ -381,17 +381,37 @@ typedef HMM_Vec4 colorf;
|
|||||||
|
|
||||||
colorf js2color(JSContext *js,JSValue v) {
|
colorf js2color(JSContext *js,JSValue v) {
|
||||||
if (JS_IsNull(v)) return (colorf){1,1,1,1};
|
if (JS_IsNull(v)) return (colorf){1,1,1,1};
|
||||||
JSValue c[4];
|
|
||||||
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
|
colorf color = {1,1,1,1}; // Default to white
|
||||||
float a = JS_IsNull(c[3]) ? 1.0 : js2number(js,c[3]);
|
|
||||||
colorf color = {
|
if (JS_IsArray(js, v)) {
|
||||||
.r = js2number(js,c[0]),
|
// Handle array format: [r, g, b, a]
|
||||||
.g = js2number(js,c[1]),
|
JSValue c[4];
|
||||||
.b = js2number(js,c[2]),
|
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
|
||||||
.a = a,
|
|
||||||
};
|
color.r = js2number(js,c[0]);
|
||||||
|
color.g = js2number(js,c[1]);
|
||||||
for (int i = 0; i < 4; i++) JS_FreeValue(js,c[i]);
|
color.b = js2number(js,c[2]);
|
||||||
|
color.a = JS_IsNull(c[3]) ? 1.0 : js2number(js,c[3]);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) JS_FreeValue(js,c[i]);
|
||||||
|
} else if (JS_IsObject(v)) {
|
||||||
|
// Handle object format: {r, g, b, a}
|
||||||
|
JSValue r_val = JS_GetPropertyStr(js, v, "r");
|
||||||
|
JSValue g_val = JS_GetPropertyStr(js, v, "g");
|
||||||
|
JSValue b_val = JS_GetPropertyStr(js, v, "b");
|
||||||
|
JSValue a_val = JS_GetPropertyStr(js, v, "a");
|
||||||
|
|
||||||
|
color.r = JS_IsNull(r_val) ? 1.0 : js2number(js, r_val);
|
||||||
|
color.g = JS_IsNull(g_val) ? 1.0 : js2number(js, g_val);
|
||||||
|
color.b = JS_IsNull(b_val) ? 1.0 : js2number(js, b_val);
|
||||||
|
color.a = JS_IsNull(a_val) ? 1.0 : js2number(js, a_val);
|
||||||
|
|
||||||
|
JS_FreeValue(js, r_val);
|
||||||
|
JS_FreeValue(js, g_val);
|
||||||
|
JS_FreeValue(js, b_val);
|
||||||
|
JS_FreeValue(js, a_val);
|
||||||
|
}
|
||||||
|
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ static int event2wota_count_props(const SDL_Event *event)
|
|||||||
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
|
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
|
||||||
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
|
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
|
||||||
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
|
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
|
||||||
count += 3; // which, data1, data2
|
count += 3; // which, orientation/data1, data2
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_EVENT_MOUSE_MOTION:
|
case SDL_EVENT_MOUSE_MOTION:
|
||||||
@@ -344,7 +344,7 @@ static int event2wota_count_props(const SDL_Event *event)
|
|||||||
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||||
case SDL_EVENT_WINDOW_DESTROYED:
|
case SDL_EVENT_WINDOW_DESTROYED:
|
||||||
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
|
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
|
||||||
// which, data1, data2 => 3 extra
|
// which, x/width/display_index, y/height => 3 extra
|
||||||
count += 3;
|
count += 3;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -427,6 +427,13 @@ static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) {
|
|||||||
wota_write_sym(wb, e->adevice.recording ? WOTA_TRUE : WOTA_FALSE);
|
wota_write_sym(wb, e->adevice.recording ? WOTA_TRUE : WOTA_FALSE);
|
||||||
break;
|
break;
|
||||||
case SDL_EVENT_DISPLAY_ORIENTATION:
|
case SDL_EVENT_DISPLAY_ORIENTATION:
|
||||||
|
wota_write_text(wb, "which");
|
||||||
|
wota_write_number(wb, (double)e->display.displayID);
|
||||||
|
wota_write_text(wb, "orientation");
|
||||||
|
wota_write_number(wb, (double)e->display.data1);
|
||||||
|
wota_write_text(wb, "data2");
|
||||||
|
wota_write_number(wb, (double)e->display.data2);
|
||||||
|
break;
|
||||||
case SDL_EVENT_DISPLAY_ADDED:
|
case SDL_EVENT_DISPLAY_ADDED:
|
||||||
case SDL_EVENT_DISPLAY_REMOVED:
|
case SDL_EVENT_DISPLAY_REMOVED:
|
||||||
case SDL_EVENT_DISPLAY_MOVED:
|
case SDL_EVENT_DISPLAY_MOVED:
|
||||||
@@ -552,10 +559,6 @@ static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) {
|
|||||||
case SDL_EVENT_WINDOW_SHOWN:
|
case SDL_EVENT_WINDOW_SHOWN:
|
||||||
case SDL_EVENT_WINDOW_HIDDEN:
|
case SDL_EVENT_WINDOW_HIDDEN:
|
||||||
case SDL_EVENT_WINDOW_EXPOSED:
|
case SDL_EVENT_WINDOW_EXPOSED:
|
||||||
case SDL_EVENT_WINDOW_MOVED:
|
|
||||||
case SDL_EVENT_WINDOW_RESIZED:
|
|
||||||
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
|
||||||
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
|
|
||||||
case SDL_EVENT_WINDOW_MINIMIZED:
|
case SDL_EVENT_WINDOW_MINIMIZED:
|
||||||
case SDL_EVENT_WINDOW_MAXIMIZED:
|
case SDL_EVENT_WINDOW_MAXIMIZED:
|
||||||
case SDL_EVENT_WINDOW_RESTORED:
|
case SDL_EVENT_WINDOW_RESTORED:
|
||||||
@@ -566,14 +569,12 @@ static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) {
|
|||||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||||
case SDL_EVENT_WINDOW_HIT_TEST:
|
case SDL_EVENT_WINDOW_HIT_TEST:
|
||||||
case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
|
case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
|
||||||
case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
|
|
||||||
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
|
|
||||||
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
|
|
||||||
case SDL_EVENT_WINDOW_OCCLUDED:
|
case SDL_EVENT_WINDOW_OCCLUDED:
|
||||||
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||||
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||||
case SDL_EVENT_WINDOW_DESTROYED:
|
case SDL_EVENT_WINDOW_DESTROYED:
|
||||||
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
|
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
|
||||||
|
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
|
||||||
wota_write_text(wb, "which");
|
wota_write_text(wb, "which");
|
||||||
wota_write_number(wb, (double)e->window.windowID);
|
wota_write_number(wb, (double)e->window.windowID);
|
||||||
wota_write_text(wb, "data1");
|
wota_write_text(wb, "data1");
|
||||||
@@ -581,6 +582,33 @@ static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) {
|
|||||||
wota_write_text(wb, "data2");
|
wota_write_text(wb, "data2");
|
||||||
wota_write_number(wb, (double)e->window.data2);
|
wota_write_number(wb, (double)e->window.data2);
|
||||||
break;
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_MOVED:
|
||||||
|
wota_write_text(wb, "which");
|
||||||
|
wota_write_number(wb, (double)e->window.windowID);
|
||||||
|
wota_write_text(wb, "x");
|
||||||
|
wota_write_number(wb, (double)e->window.data1);
|
||||||
|
wota_write_text(wb, "y");
|
||||||
|
wota_write_number(wb, (double)e->window.data2);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_RESIZED:
|
||||||
|
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||||
|
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
|
||||||
|
wota_write_text(wb, "which");
|
||||||
|
wota_write_number(wb, (double)e->window.windowID);
|
||||||
|
wota_write_text(wb, "width");
|
||||||
|
wota_write_number(wb, (double)e->window.data1);
|
||||||
|
wota_write_text(wb, "height");
|
||||||
|
wota_write_number(wb, (double)e->window.data2);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
|
||||||
|
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
|
||||||
|
wota_write_text(wb, "which");
|
||||||
|
wota_write_number(wb, (double)e->window.windowID);
|
||||||
|
wota_write_text(wb, "display_index");
|
||||||
|
wota_write_number(wb, (double)e->window.data1);
|
||||||
|
wota_write_text(wb, "data2");
|
||||||
|
wota_write_number(wb, (double)e->window.data2);
|
||||||
|
break;
|
||||||
case SDL_EVENT_JOYSTICK_ADDED:
|
case SDL_EVENT_JOYSTICK_ADDED:
|
||||||
case SDL_EVENT_JOYSTICK_REMOVED:
|
case SDL_EVENT_JOYSTICK_REMOVED:
|
||||||
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
|
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
|
||||||
|
|||||||
@@ -820,7 +820,7 @@ JSC_CCALL(renderer_point,
|
|||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(renderer_texture,
|
JSC_CCALL(renderer_texture,
|
||||||
SDL_Renderer *r = js2SDL_Renderer(js, self);
|
SDL_Renderer *ren = js2SDL_Renderer(js, self);
|
||||||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||||||
rect src = js2rect(js,argv[1]);
|
rect src = js2rect(js,argv[1]);
|
||||||
rect dst = js2rect(js,argv[2]);
|
rect dst = js2rect(js,argv[2]);
|
||||||
@@ -830,7 +830,13 @@ JSC_CCALL(renderer_texture,
|
|||||||
HMM_Vec2 anchor = js2vec2(js, argv[4]);
|
HMM_Vec2 anchor = js2vec2(js, argv[4]);
|
||||||
anchor.y = dst.h - anchor.y*dst.h;
|
anchor.y = dst.h - anchor.y*dst.h;
|
||||||
anchor.x *= dst.w;
|
anchor.x *= dst.w;
|
||||||
SDL_RenderTextureRotated(r, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE);
|
float r,g,b,a;
|
||||||
|
SDL_GetRenderDrawColorFloat(ren, &r,&g,&b,&a);
|
||||||
|
SDL_SetTextureColorModFloat(tex, r,g,b);
|
||||||
|
SDL_SetTextureAlphaModFloat(tex, a);
|
||||||
|
SDL_RenderTextureRotated(ren, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE);
|
||||||
|
SDL_SetTextureColorModFloat(tex,1,1,1);
|
||||||
|
SDL_SetTextureAlphaModFloat(tex,a);
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(renderer_rects,
|
JSC_CCALL(renderer_rects,
|
||||||
|
|||||||
Reference in New Issue
Block a user