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"
|
||||
|
||||
## 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
|
||||
- `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
|
||||
- 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)
|
||||
- Prefer closures and javascript objects and prototypes over ES6 style classes
|
||||
- Follow existing JavaScript patterns in the codebase
|
||||
- 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
|
||||
1. **Actor System** (scripts/core/engine.js)
|
||||
@@ -99,7 +103,7 @@ cd examples/chess
|
||||
- Documentation is found in docs
|
||||
- Documentation for the JS modules loaded with 'use' is docs/api/modules
|
||||
- .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
|
||||
- Shaders are in `shaders/` directory as HLSL
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
var input = use('input')
|
||||
return {}
|
||||
|
||||
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.')
|
||||
|
||||
add_command("draw_slice9", {
|
||||
image: image,
|
||||
rect: rect,
|
||||
slice: slice,
|
||||
info: info,
|
||||
material: material
|
||||
image,
|
||||
rect,
|
||||
slice,
|
||||
info,
|
||||
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 (!image) throw Error('Need an image to render.')
|
||||
|
||||
@@ -150,7 +150,7 @@ draw.image = function image(image, rect, rotation, anchor, shear, info, material
|
||||
anchor,
|
||||
shear,
|
||||
info,
|
||||
material,
|
||||
material
|
||||
})
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ draw.circle = function render_circle(pos, 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", {
|
||||
text,
|
||||
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
|
||||
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')
|
||||
|
||||
// Frame timing variables
|
||||
var frame_times = []
|
||||
var frame_time_index = 0
|
||||
var max_frame_samples = 60
|
||||
var frame_start_time = 0
|
||||
var average_frame_time = 0
|
||||
var framerate = 60
|
||||
|
||||
var game = args[0]
|
||||
|
||||
@@ -34,40 +30,45 @@ $_.start(e => {
|
||||
|
||||
var geometry = use('geometry')
|
||||
|
||||
function updateCameraMatrix(camera, winW, winH) {
|
||||
// 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];
|
||||
var camera = {}
|
||||
|
||||
// NDC→pixels
|
||||
def vx = camera.viewport.x * winW;
|
||||
def vy = camera.viewport.y * winH;
|
||||
def vw = camera.viewport.width * winW;
|
||||
def vh = camera.viewport.height * winH;
|
||||
function updateCameraMatrix(cam) {
|
||||
def win_w = logical.width
|
||||
def win_h = logical.height
|
||||
def view_w = (cam.size?.[0] ?? win_w) / cam.zoom
|
||||
def view_h = (cam.size?.[1] ?? win_h) / cam.zoom
|
||||
|
||||
// final “mat” coefficients
|
||||
// [ a 0 c ]
|
||||
// [ 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;
|
||||
def ox = cam.pos[0] - view_w * (cam.anchor?.[0] ?? 0)
|
||||
def oy = cam.pos[1] - view_h * (cam.anchor?.[1] ?? 0)
|
||||
|
||||
// and store the inverses so we can go back cheaply
|
||||
camera.ia = 1 / camera.a;
|
||||
camera.ic = -camera.c * camera.ia;
|
||||
camera.ie = 1 / camera.e;
|
||||
camera.if = -camera.f * camera.ie;
|
||||
def vx = (cam.viewport?.x ?? 0) * win_w
|
||||
def vy = (cam.viewport?.y ?? 0) * win_h
|
||||
def vw = (cam.viewport?.width ?? 1) * win_w
|
||||
def vh = (cam.viewport?.height ?? 1) * win_h
|
||||
|
||||
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 ----
|
||||
function worldToScreenPoint(pos, camera) {
|
||||
function worldToScreenPoint([x,y], camera) {
|
||||
return {
|
||||
x: camera.a * pos[0] + camera.c,
|
||||
y: camera.e * pos[1] + camera.f
|
||||
x: camera.a * x + camera.c,
|
||||
y: camera.e * y + camera.f
|
||||
};
|
||||
}
|
||||
|
||||
@@ -80,40 +81,21 @@ function screenToWorldPoint(pos, camera) {
|
||||
}
|
||||
|
||||
//---- rectangle (two corner) ----
|
||||
function worldToScreenRect(rect, camera) {
|
||||
function worldToScreenRect({x,y,width,height}, camera) {
|
||||
// map bottom-left and top-right
|
||||
def x1 = camera.a * rect.x + camera.c;
|
||||
def y1 = camera.e * rect.y + camera.f;
|
||||
def x2 = camera.a * (rect.x + rect.width) + camera.c;
|
||||
def y2 = camera.e * (rect.y + rect.height) + camera.f;
|
||||
def x1 = camera.a * x + camera.c;
|
||||
def y1 = camera.e * y + camera.f;
|
||||
def x2 = camera.a * (x + width) + camera.c;
|
||||
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 {
|
||||
x: x0,
|
||||
y: y0,
|
||||
width: x2 > x1 ? x2 - x1 : x1 - x2,
|
||||
height: y2 > y1 ? y2 - y1 : y1 - y2
|
||||
};
|
||||
x:Math.min(x1,x2),
|
||||
y:Math.min(y1,y2),
|
||||
width:Math.abs(x2-x1),
|
||||
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 gameactor
|
||||
@@ -122,12 +104,13 @@ var images = {}
|
||||
|
||||
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
|
||||
function translate_draw_commands(commands) {
|
||||
if (!graphics) return
|
||||
|
||||
updateCameraMatrix(camera,500,500)
|
||||
|
||||
renderer_commands.length = 0
|
||||
|
||||
commands.forEach(function(cmd) {
|
||||
@@ -140,6 +123,10 @@ function translate_draw_commands(commands) {
|
||||
}
|
||||
|
||||
switch(cmd.cmd) {
|
||||
case "camera":
|
||||
updateCameraMatrix(cmd.camera, win_size.width, win_size.height)
|
||||
break
|
||||
|
||||
case "draw_rect":
|
||||
cmd.rect = worldToScreenRect(cmd.rect, camera)
|
||||
// Handle rectangles with optional rounding and thickness
|
||||
@@ -208,7 +195,10 @@ function translate_draw_commands(commands) {
|
||||
case "draw_line":
|
||||
renderer_commands.push({
|
||||
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
|
||||
|
||||
@@ -238,13 +228,12 @@ function translate_draw_commands(commands) {
|
||||
}
|
||||
})
|
||||
|
||||
log.console(json.encode(renderer_commands[renderer_commands.length-1]))
|
||||
break
|
||||
|
||||
case "draw_text":
|
||||
if (!cmd.text) 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}
|
||||
renderer_commands.push({
|
||||
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_next = null
|
||||
var last_time = time.number()
|
||||
var frames = []
|
||||
var frame_avg = 0
|
||||
|
||||
var input = use('input')
|
||||
|
||||
var input_state = {
|
||||
poll: 1/60
|
||||
poll: 1/framerate
|
||||
}
|
||||
|
||||
// 1) input runs completely independently
|
||||
function poll_input() {
|
||||
send(video, {kind:'input', op:'get'}, 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')
|
||||
$_.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.key)
|
||||
ev.key = input.keyname(ev.key)
|
||||
@@ -339,37 +318,25 @@ function poll_input() {
|
||||
// 2) helper to build & send a batch, then call done()
|
||||
function create_batch(draw_cmds, done) {
|
||||
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'}
|
||||
]
|
||||
if (draw_cmds && draw_cmds.length)
|
||||
batch.push(...translate_draw_commands(draw_cmds))
|
||||
|
||||
batch.push(
|
||||
{op:'set', prop:'drawColor', value:[1,1,1,1]},
|
||||
{op:'debugText', data:{pos:{x:10,y:10}, text:`Fps: ${(1/frame_avg).toFixed(2)}`}},
|
||||
{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:'present'}
|
||||
)
|
||||
|
||||
send(video, {kind:'renderer', op:'batch', data:batch}, () => {
|
||||
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)
|
||||
})
|
||||
send(video, {kind:'renderer', op:'batch', data:batch}, done)
|
||||
}
|
||||
|
||||
// 3) kick off the very first update→draw
|
||||
function start_pipeline() {
|
||||
poll_input()
|
||||
send(gameactor, {kind:'update', dt:1/60}, () => {
|
||||
send(gameactor, {kind:'update', dt:0}, () => {
|
||||
send(gameactor, {kind:'draw'}, cmds => {
|
||||
pending_draw = cmds
|
||||
render_step()
|
||||
@@ -378,24 +345,26 @@ function start_pipeline() {
|
||||
}
|
||||
|
||||
function render_step() {
|
||||
// a) fire off the next update→draw immediately
|
||||
def dt = time.number() - last_time
|
||||
send(gameactor, {kind:'update', dt:1/60}, () =>
|
||||
send(gameactor, {kind:'draw'}, cmds => pending_next = cmds)
|
||||
)
|
||||
// a) Calculate actual dt since last frame
|
||||
def now = time.number()
|
||||
def dt = now - last_time
|
||||
last_time = now
|
||||
|
||||
// b) Send update with actual dt, then wait for draw response
|
||||
send(gameactor, {kind:'update', dt}, () => {
|
||||
send(gameactor, {kind:'draw'}, cmds => {
|
||||
// Only render after receiving draw commands
|
||||
pending_draw = cmds
|
||||
|
||||
// c) render the current frame
|
||||
create_batch(pending_draw, ttr => { // time to render
|
||||
// only swap in when there's a new set of commands
|
||||
if (pending_next) {
|
||||
pending_draw = pending_next
|
||||
pending_next = null
|
||||
}
|
||||
create_batch(pending_draw, _ => { // time to render
|
||||
def frame_end = time.number()
|
||||
def wait_time = Math.max(0, (frame_end - now) - 1/framerate)
|
||||
|
||||
// d) schedule the next render step
|
||||
def render_dur = time.number() - last_time
|
||||
def wait = Math.max(0, 1/60 - ttr)
|
||||
$_.delay(render_step, 0)
|
||||
// e) Schedule next frame
|
||||
$_.delay(render_step, wait_time)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -409,6 +378,15 @@ $_.receiver(e => {
|
||||
prop:'logicalPresentation',
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -87,6 +87,8 @@ $_.receiver(function(msg) {
|
||||
|
||||
var response = {};
|
||||
|
||||
// log.console(json.encode(msg))
|
||||
|
||||
try {
|
||||
switch (msg.kind) {
|
||||
case 'window':
|
||||
|
||||
@@ -14,7 +14,7 @@ var pcms = {};
|
||||
audio.pcm = function pcm(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];
|
||||
var bytes = 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 CHUNK_BYTES = FRAMES * CHANNELS * BYTES_PER_F
|
||||
|
||||
var mixview = new Float32Array(FRAMES*CHANNELS)
|
||||
var mixbuf = mixview.buffer
|
||||
|
||||
function pump()
|
||||
{
|
||||
if (feeder.queued() < CHUNK_BYTES*3) {
|
||||
|
||||
@@ -1,281 +1,79 @@
|
||||
var util = use('util')
|
||||
var Ease = use('ease')
|
||||
var time = use('time')
|
||||
|
||||
var Ease = {
|
||||
linear(t) {
|
||||
return t
|
||||
var rate = 1/240
|
||||
|
||||
var TweenEngine = {
|
||||
tweens: [],
|
||||
add(tween) {
|
||||
this.tweens.push(tween)
|
||||
},
|
||||
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
|
||||
remove(tween) {
|
||||
this.tweens = this.tweens.filter(t => t != tween)
|
||||
},
|
||||
update(dt) {
|
||||
var now = time.number()
|
||||
for (var tween of this.tweens.slice()) {
|
||||
tween._update(now)
|
||||
}
|
||||
|
||||
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(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 = {
|
||||
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()
|
||||
$_.delay(_ => TweenEngine.update(), rate)
|
||||
}
|
||||
}
|
||||
var stop = Register.update.register(update)
|
||||
return cleanup
|
||||
|
||||
function Tween(obj) {
|
||||
this.obj = obj
|
||||
this.startVals = {}
|
||||
this.endVals = {}
|
||||
this.duration = 0
|
||||
this.easing = Ease.linear
|
||||
this.startTime = 0
|
||||
this.onCompleteCallback = function() {}
|
||||
}
|
||||
|
||||
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)
|
||||
Tween.prototype.to = function(props, duration) {
|
||||
for (var key in props) {
|
||||
this.startVals[key] = this.obj[key]
|
||||
this.endVals[key] = props[key]
|
||||
}
|
||||
this.duration = duration
|
||||
this.startTime = time.number()
|
||||
|
||||
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])
|
||||
TweenEngine.add(this)
|
||||
return this
|
||||
}
|
||||
|
||||
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))
|
||||
Tween.prototype.ease = function(easingFn) {
|
||||
this.easing = easingFn
|
||||
return this
|
||||
}
|
||||
|
||||
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
|
||||
Tween.prototype.onComplete = function(callback) {
|
||||
this.onCompleteCallback = callback
|
||||
return this
|
||||
}
|
||||
|
||||
return defn
|
||||
},
|
||||
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
|
||||
}
|
||||
|
||||
Tween.make = Tween.start
|
||||
if (t == 1) {
|
||||
this.onCompleteCallback()
|
||||
TweenEngine.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
function tween(obj) {
|
||||
return new Tween(obj)
|
||||
}
|
||||
|
||||
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
|
||||
$_.delay(_ => TweenEngine.update(), rate)
|
||||
|
||||
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 }
|
||||
return tween
|
||||
@@ -43,7 +43,7 @@ var console_mod = cell.hidden.console
|
||||
globalThis.log = {}
|
||||
log.console = function(msg)
|
||||
{
|
||||
var caller = caller_data(2)
|
||||
var caller = caller_data(1)
|
||||
console_mod.print(console_rec(caller.line, caller.file, msg))
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ globalThis.use = function use(file, ...args) {
|
||||
} else {
|
||||
// Compile from source
|
||||
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)
|
||||
|
||||
// Save compiled version to .cell directory
|
||||
@@ -221,7 +221,7 @@ globalThis.use = function use(file, ...args) {
|
||||
context.__proto__ = embed_mod
|
||||
|
||||
// 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 (!ret && embed_mod) {
|
||||
|
||||
@@ -381,17 +381,37 @@ typedef HMM_Vec4 colorf;
|
||||
|
||||
colorf js2color(JSContext *js,JSValue v) {
|
||||
if (JS_IsNull(v)) return (colorf){1,1,1,1};
|
||||
|
||||
colorf color = {1,1,1,1}; // Default to white
|
||||
|
||||
if (JS_IsArray(js, v)) {
|
||||
// Handle array format: [r, g, b, a]
|
||||
JSValue c[4];
|
||||
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
|
||||
float a = JS_IsNull(c[3]) ? 1.0 : js2number(js,c[3]);
|
||||
colorf color = {
|
||||
.r = js2number(js,c[0]),
|
||||
.g = js2number(js,c[1]),
|
||||
.b = js2number(js,c[2]),
|
||||
.a = a,
|
||||
};
|
||||
|
||||
color.r = js2number(js,c[0]);
|
||||
color.g = js2number(js,c[1]);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ static int event2wota_count_props(const SDL_Event *event)
|
||||
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
|
||||
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
|
||||
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
|
||||
count += 3; // which, data1, data2
|
||||
count += 3; // which, orientation/data1, data2
|
||||
break;
|
||||
|
||||
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_DESTROYED:
|
||||
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
|
||||
// which, data1, data2 => 3 extra
|
||||
// which, x/width/display_index, y/height => 3 extra
|
||||
count += 3;
|
||||
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);
|
||||
break;
|
||||
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_REMOVED:
|
||||
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_HIDDEN:
|
||||
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_MAXIMIZED:
|
||||
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_HIT_TEST:
|
||||
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_ENTER_FULLSCREEN:
|
||||
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||
case SDL_EVENT_WINDOW_DESTROYED:
|
||||
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
|
||||
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
|
||||
wota_write_text(wb, "which");
|
||||
wota_write_number(wb, (double)e->window.windowID);
|
||||
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_number(wb, (double)e->window.data2);
|
||||
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_REMOVED:
|
||||
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
|
||||
|
||||
@@ -820,7 +820,7 @@ JSC_CCALL(renderer_point,
|
||||
)
|
||||
|
||||
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]);
|
||||
rect src = js2rect(js,argv[1]);
|
||||
rect dst = js2rect(js,argv[2]);
|
||||
@@ -830,7 +830,13 @@ JSC_CCALL(renderer_texture,
|
||||
HMM_Vec2 anchor = js2vec2(js, argv[4]);
|
||||
anchor.y = dst.h - anchor.y*dst.h;
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user