Files
cell/prosperon/prosperon.ce
John Alanbrook 3e87bfd6cc
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
add to look for same folder name as well as main
2025-06-04 13:22:58 -05:00

320 lines
8.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var os = use('os');
var io = use('io');
var transform = use('transform');
var rasterize = use('rasterize');
var game = args[0]
var video
$_.start(e => {
if (e.type !== 'greet') return
video = e.actor
graphics = use('graphics', video)
send(video, {kind:"window", op:"makeRenderer"}, e => {
$_.start(e => {
if (gameactor) return
gameactor = e.actor
loop()
}, args[0])
})
}, 'prosperon/sdl_video', {
title: "Prosperon",
width:500,
height:500
})
var input = use('input')
input.watch($_)
var geometry = use('geometry')
function worldToScreenRect({x,y,width,height}, camera, winW, winH) {
var bl = worldToScreenPoint([x,y], camera, winW, winH)
var tr = worldToScreenPoint([x+width, y+height], camera, winW, winH)
return {
x: Math.min(bl.x, tr.x),
y: Math.min(bl.y, tr.y),
width: Math.abs(tr.x - bl.x),
height: Math.abs(tr.y - bl.y)
}
}
function worldToScreenPoint([wx, wy], camera, winW, winH) {
// 1) worldwindow origin (bottomleft)
const worldX0 = camera.pos[0] - camera.size[0] * camera.anchor[0];
const worldY0 = camera.pos[1] - camera.size[1] * camera.anchor[1];
// 2) normalized device coords [0..1]
const ndcX = (wx - worldX0) / camera.size[0];
const ndcY = (wy - worldY0) / camera.size[1];
// 3) map into pixelspace via the fractional viewport
const px = camera.viewport.x * winW
+ ndcX * (camera.viewport.width * winW);
const py = camera.viewport.y * winH
+ (1 - ndcY) * (camera.viewport.height * winH);
return [ px, py ];
}
function screenToWorldPoint([px, py], camera, winW, winH) {
// 1) undo pixel→NDC within the cameras viewport
const ndcX = (px - camera.viewport.x * winW)
/ (camera.viewport.width * winW)
const ndcY = 1 - (py - camera.viewport.y * winH)
/ (camera.viewport.height * winH)
// 2) compute the worldwindow origin (bottomleft)
const worldX0 = camera.pos[0]
- camera.size[0] * camera.anchor[0]
const worldY0 = camera.pos[1]
- camera.size[1] * camera.anchor[1]
// 3) map NDC back to world coords
return [
ndcX * camera.size[0] + worldX0,
ndcY * camera.size[1] + worldY0
]
}
var camera = {
size: [500,500],//{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: undefined
}
var util = use('util')
var cammy = util.camera_globals(camera)
var graphics
var gameactor
var last = os.now()
// FPS tracking
var fps_samples = []
var fps_sample_count = 60
var fps_sum = 0
var images = {}
// Convert high-level draw commands to low-level renderer commands
function translate_draw_commands(commands) {
if (!graphics) return
var renderer_commands = []
commands.forEach(function(cmd) {
if (cmd.material && cmd.material.color) {
renderer_commands.push({
op: "set",
prop: "drawColor",
value: cmd.material.color
})
}
switch(cmd.cmd) {
case "draw_rect":
cmd.rect = worldToScreenRect(cmd.rect, camera,500, 500)
// Handle rectangles with optional rounding and thickness
if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) {
// Rounded rectangle
var thickness = (cmd.opt.thickness === 0) ? 0 : (cmd.opt.thickness || 1)
var raster_result = rasterize.round_rect(cmd.rect, cmd.opt.radius, thickness)
if (raster_result.type === 'rect') {
renderer_commands.push({
op: "fillRect",
data: {rect: raster_result.data}
})
} else if (raster_result.type === 'rects') {
raster_result.data.forEach(function(rect) {
renderer_commands.push({
op: "fillRect",
data: {rect: rect}
})
})
}
} else if (cmd.opt && cmd.opt.thickness && cmd.opt.thickness > 0) {
// Outlined rectangle
var raster_result = rasterize.outline_rect(cmd.rect, cmd.opt.thickness)
if (raster_result.type === 'rect') {
renderer_commands.push({
op: "fillRect",
data: {rect: raster_result.data}
})
} else if (raster_result.type === 'rects') {
renderer_commands.push({
op: "rects",
data: {rects: raster_result.data}
})
}
} else {
renderer_commands.push({
op: "fillRect",
data: {rect: cmd.rect}
})
}
break
case "draw_circle":
case "draw_ellipse":
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
// Rasterize ellipse to points or rects
var radii = cmd.radii || [cmd.radius, cmd.radius]
var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {})
if (raster_result.type === 'points') {
renderer_commands.push({
op: "point",
data: {points: raster_result.data}
})
} else if (raster_result.type === 'rects') {
// Use 'rects' operation for multiple rectangles
renderer_commands.push({
op: "rects",
data: {rects: raster_result.data}
})
}
break
case "draw_line":
renderer_commands.push({
op: "line",
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera, 500, 500))}
})
break
case "draw_point":
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
renderer_commands.push({
op: "point",
data: {points: [cmd.pos]}
})
break
case "draw_image":
var img = graphics.texture(cmd.image)
if (!img.gpu) break
cmd.rect.width ??= img.width
cmd.rect.height ??= img.height
cmd.rect = worldToScreenRect(cmd.rect, camera, 500, 500)
renderer_commands.push({
op: "texture",
data: {
texture_id: img.gpu.id,
dst: cmd.rect,
src: {x:0,y:0,width:img.width,height:img.height},
}
})
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 pos = {x: rect.x, y: rect.y}
renderer_commands.push({
op: "debugText",
data: {
pos,
text: cmd.text
}
})
break
}
})
return renderer_commands
}
function loop(time)
{
$_.delay(loop, 1/60)
os.frame()
var now = os.now()
var dt = now - last
last = now
// Update the game
send(gameactor, {kind:'update', dt:dt}, e => {
// Get draw commands from game
send(gameactor, {kind:'draw'}, draw_commands => {
var batch_commands = []
batch_commands.push({
op: "set",
prop: "drawColor",
value: [0.1,0.1,0.15,1]
})
// Clear the screen
batch_commands.push({
op: "clear"
})
if (draw_commands && draw_commands.length > 0) {
var renderer_commands = translate_draw_commands(draw_commands)
batch_commands = batch_commands.concat(renderer_commands)
}
batch_commands.push({
op: "present"
})
send(video, {
kind: "renderer",
op: "batch",
data: batch_commands
}, _ => {
var diff = os.now() - now
// Calculate and track FPS
var frame_time = os.now() - last
if (frame_time > 0) {
var current_fps = 1 / frame_time
// Add to samples
fps_samples.push(current_fps)
fps_sum += current_fps
// Keep only the last N samples
if (fps_samples.length > fps_sample_count) {
fps_sum -= fps_samples.shift()
}
// Calculate average FPS
var avg_fps = fps_sum / fps_samples.length
}
})
})
})
}
$_.receiver(e => {
if (e.type === 'quit')
$_.stop()
if (e.type.includes('mouse')) {
if (e.pos)
e.pos = screenToWorldPoint(e.pos, camera, 500, 500)
if (e.d_pos)
e.d_pos.y *= -1
}
send(gameactor, e)
})