draw2d now generates high level commands; turned into instructions by moth

This commit is contained in:
2025-05-27 13:46:56 -05:00
parent ad182d68ec
commit 01df337ccc
16 changed files with 797 additions and 630 deletions

View File

@@ -5,6 +5,7 @@ var sprite = use('sprite')
var geom = use('geometry') var geom = use('geometry')
var input = use('controller') var input = use('controller')
var config = use('config') var config = use('config')
var color = use('color')
var bunnyTex = graphics.texture("bunny") var bunnyTex = graphics.texture("bunny")
@@ -65,5 +66,5 @@ this.hud = function() {
draw.images(bunnyTex, bunnies) draw.images(bunnyTex, bunnies)
var msg = 'FPS: ' + fpsAvg.toFixed(2) + ' Bunnies: ' + bunnies.length var msg = 'FPS: ' + fpsAvg.toFixed(2) + ' Bunnies: ' + bunnies.length
draw.text(msg, {x:0, y:0, width:config.width, height:40}, undefined, 0, Color.white, 0) draw.text(msg, {x:0, y:0, width:config.width, height:40}, undefined, 0, color.white, 0)
} }

View File

@@ -2,6 +2,7 @@
var draw = use('draw2d') var draw = use('draw2d')
var input = use('controller') var input = use('controller')
var config = use('config') var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0] prosperon.camera.transform.pos = [0,0]
@@ -73,13 +74,13 @@ this.hud = function() {
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1]) draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
// Draw paddles // Draw paddles
draw.rectangle({x:p1.x - paddleW*0.5, y:p1.y - paddleH*0.5, width:paddleW, height:paddleH}, Color.white) draw.rectangle({x:p1.x - paddleW*0.5, y:p1.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
draw.rectangle({x:p2.x - paddleW*0.5, y:p2.y - paddleH*0.5, width:paddleW, height:paddleH}, Color.white) draw.rectangle({x:p2.x - paddleW*0.5, y:p2.y - paddleH*0.5, width:paddleW, height:paddleH}, color.white)
// Draw ball // Draw ball
draw.rectangle({x:ball.x - ball.size*0.5, y:ball.y - ball.size*0.5, width:ball.size, height:ball.size}, Color.white) draw.rectangle({x:ball.x - ball.size*0.5, y:ball.y - ball.size*0.5, width:ball.size, height:ball.size}, color.white)
// Simple score display // Simple score display
var msg = score1 + " " + score2 var msg = score1 + " " + score2
draw.text(msg, {x:0, y:10, width:config.width, height:40}, undefined, 0, Color.white, 0) draw.text(msg, {x:0, y:10, width:config.width, height:40}, undefined, 0, color.white, 0)
} }

View File

@@ -4,6 +4,7 @@ var render = use('render')
var graphics = use('graphics') var graphics = use('graphics')
var input = use('input') var input = use('input')
var config = use('config') var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0] prosperon.camera.transform.pos = [0,0]
@@ -83,15 +84,15 @@ this.hud = function() {
// Draw snake // Draw snake
for (var i=0; i<snake.length; i++) { for (var i=0; i<snake.length; i++) {
var s = snake[i] var s = snake[i]
draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, Color.green) draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, color.green)
} }
// Draw apple // Draw apple
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, Color.red) draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, color.red)
if (gameState === "gameover") { if (gameState === "gameover") {
var msg = "GAME OVER! Press SPACE to restart." var msg = "GAME OVER! Press SPACE to restart."
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, undefined, 0, Color.white) draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, undefined, 0, color.white)
} }
} }

View File

@@ -1,6 +1,7 @@
var draw = use('draw2d') var draw = use('draw2d')
var input = use('input') var input = use('input')
var config = use('config') var config = use('config')
var color = use('color')
prosperon.camera.transform.pos = [0,0] prosperon.camera.transform.pos = [0,0]
@@ -248,7 +249,7 @@ this.hud = function() {
} }
// Next piece window // Next piece window
draw.text("Next", {x:70, y:5, width:50, height:10}, undefined, 0, Color.white) draw.text("Next", {x:70, y:5, width:50, height:10}, undefined, 0, color.white)
if (nextPiece) { if (nextPiece) {
for (var i=0; i<nextPiece.blocks.length; i++) { for (var i=0; i<nextPiece.blocks.length; i++) {
var nx = nextPiece.blocks[i][0] var nx = nextPiece.blocks[i][0]
@@ -261,10 +262,10 @@ this.hud = function() {
// Score & Level // Score & Level
var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level
draw.text(info, {x:70, y:30, width:90, height:50}, undefined, 0, Color.white) draw.text(info, {x:70, y:30, width:90, height:50}, undefined, 0, color.white)
if (gameOver) { if (gameOver) {
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, undefined, 0, Color.red) draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, undefined, 0, color.red)
} }
} }

View File

@@ -272,8 +272,6 @@ globalThis.use = function use(file, ...args) {
globalThis.json = use('json') globalThis.json = use('json')
var time = use('time') var time = use('time')
globalThis.mixin("color")
var DOCPATH = 'scripts/core/doc.js' var DOCPATH = 'scripts/core/doc.js'
var script = io.slurp(DOCPATH) var script = io.slurp(DOCPATH)
var fnname = "doc" var fnname = "doc"
@@ -706,7 +704,6 @@ function enet_check()
$_.delay(enet_check, service_delay); $_.delay(enet_check, service_delay);
} }
send_messages();
enet_check(); enet_check();
// Finally, run the program // Finally, run the program

View File

@@ -5,17 +5,17 @@ function tohex(n) {
}; };
var Color = { var Color = {
white: [255, 255, 255], white: [1, 1, 1],
black: [0, 0, 0], black: [0, 0, 0],
blue: [0, 0, 255], blue: [0, 0, 1],
green: [0, 255, 0], green: [0, 1, 0],
yellow: [255, 255, 0], yellow: [1, 1, 0],
red: [255, 0, 0], red: [1, 0, 0],
gray: [181, 181, 181], gray: [0.71, 0.71, 0.71],
cyan: [0, 255, 255], cyan: [0, 1, 1],
purple: [162, 93, 227], purple: [0.635, 0.365, 0.89],
orange: [255, 144, 64], orange: [1, 0.565, 0.251],
magenta: [255, 0, 255], magenta: [1, 0, 1],
}; };
Color.editor = {}; Color.editor = {};
@@ -41,85 +41,90 @@ esc.color = function (v) {
esc.doc = "Functions and constants for ANSI escape sequences."; esc.doc = "Functions and constants for ANSI escape sequences.";
Color.Arkanoid = { Color.Arkanoid = {
orange: [255, 143, 0], orange: [1, 0.561, 0],
teal: [0, 255, 255], teal: [0, 1, 1],
green: [0, 255, 0], green: [0, 1, 0],
red: [255, 0, 0], red: [1, 0, 0],
blue: [0, 112, 255], blue: [0, 0.439, 1],
purple: [255, 0, 255], purple: [1, 0, 1],
yellow: [255, 255, 0], yellow: [1, 1, 0],
silver: [157, 157, 157], silver: [0.616, 0.616, 0.616],
gold: [188, 174, 0], gold: [0.737, 0.682, 0],
}; };
Color.Arkanoid.Powerups = { Color.Arkanoid.Powerups = {
red: [174, 0, 0] /* laser */, red: [0.682, 0, 0] /* laser */,
blue: [0, 0, 174] /* enlarge */, blue: [0, 0, 0.682] /* enlarge */,
green: [0, 174, 0] /* catch */, green: [0, 0.682, 0] /* catch */,
orange: [224, 143, 0] /* slow */, orange: [0.878, 0.561, 0] /* slow */,
purple: [210, 0, 210] /* break */, purple: [0.824, 0, 0.824] /* break */,
cyan: [0, 174, 255] /* disruption */, cyan: [0, 0.682, 1] /* disruption */,
gray: [143, 143, 143] /* 1up */, gray: [0.561, 0.561, 0.561] /* 1up */,
}; };
Color.Gameboy = { Color.Gameboy = {
darkest: [229, 107, 26], darkest: [0.898, 0.42, 0.102],
dark: [229, 189, 26], dark: [0.898, 0.741, 0.102],
light: [189, 229, 26], light: [0.741, 0.898, 0.102],
lightest: [107, 229, 26], lightest: [0.42, 0.898, 0.102],
}; };
Color.Apple = { Color.Apple = {
green: [94, 189, 62], green: [0.369, 0.741, 0.243],
yellow: [255, 185, 0], yellow: [1, 0.725, 0],
orange: [247, 130, 0], orange: [0.969, 0.51, 0],
red: [226, 56, 56], red: [0.886, 0.22, 0.22],
purple: [151, 57, 153], purple: [0.592, 0.224, 0.6],
blue: [0, 156, 223], blue: [0, 0.612, 0.875],
}; };
Color.Debug = { Color.Debug = {
boundingbox: Color.white, boundingbox: Color.white,
names: [84, 110, 255], names: [0.329, 0.431, 1],
}; };
Color.Editor = { Color.Editor = {
grid: [99, 255, 128], grid: [0.388, 1, 0.502],
select: [255, 255, 55], select: [1, 1, 0.216],
newgroup: [120, 255, 10], newgroup: [0.471, 1, 0.039],
}; };
/* Detects the format of all colors and munges them into a floating point format */ /* Detects the format of all colors and munges them into a floating point format */
Color.normalize = function (c) { Color.normalize = function (c) {
var add_a = function (a) { var add_a = function (a) {
var n = this.slice(); var n = this.slice();
n.a = a; n[3] = a;
return n; return n;
}; };
for (var p of Object.keys(c)) { for (var p of Object.keys(c)) {
var fmt = "nrm";
if (typeof c[p] !== "object") continue; if (typeof c[p] !== "object") continue;
if (!Array.isArray(c[p])) { if (!Array.isArray(c[p])) {
Color.normalize(c[p]); Color.normalize(c[p]);
continue; continue;
} }
c[p][3] = 255; // Add alpha channel if not present
if (c[p].length === 3) {
c[p][3] = 1;
}
// Check if any values are > 1 (meaning they're in 0-255 format)
var needs_conversion = false;
for (var color of c[p]) { for (var color of c[p]) {
if (color > 1) { if (color > 1) {
fmt = "8b"; needs_conversion = true;
break; break;
} }
} }
switch (fmt) { // Convert from 0-255 to 0-1 if needed
case "8b": if (needs_conversion) {
c[p] = c[p].map(function (x) { c[p] = c[p].map(function (x) {
return x / 255; return x / 255;
}); });
} }
c[p].alpha = add_a; c[p].alpha = add_a;
} }
}; };
@@ -133,53 +138,53 @@ ColorMap.makemap = function (map) {
return newmap; return newmap;
}; };
ColorMap.Jet = ColorMap.makemap({ ColorMap.Jet = ColorMap.makemap({
0: [0, 0, 131], 0: [0, 0, 0.514],
0.125: [0, 60, 170], 0.125: [0, 0.235, 0.667],
0.375: [5, 255, 255], 0.375: [0.02, 1, 1],
0.625: [255, 255, 0], 0.625: [1, 1, 0],
0.875: [250, 0, 0], 0.875: [0.98, 0, 0],
1: [128, 0, 0], 1: [0.502, 0, 0],
}); });
ColorMap.BlueRed = ColorMap.makemap({ ColorMap.BlueRed = ColorMap.makemap({
0: [0, 0, 255], 0: [0, 0, 1],
1: [255, 0, 0], 1: [1, 0, 0],
}); });
ColorMap.Inferno = ColorMap.makemap({ ColorMap.Inferno = ColorMap.makemap({
0: [0, 0, 4], 0: [0, 0, 0.016],
0.13: [31, 12, 72], 0.13: [0.122, 0.047, 0.282],
0.25: [85, 15, 109], 0.25: [0.333, 0.059, 0.427],
0.38: [136, 34, 106], 0.38: [0.533, 0.133, 0.416],
0.5: [186, 54, 85], 0.5: [0.729, 0.212, 0.333],
0.63: [227, 89, 51], 0.63: [0.89, 0.349, 0.2],
0.75: [249, 140, 10], 0.75: [0.976, 0.549, 0.039],
0.88: [249, 201, 50], 0.88: [0.976, 0.788, 0.196],
1: [252, 255, 164], 1: [0.988, 1, 0.643],
}); });
ColorMap.Bathymetry = ColorMap.makemap({ ColorMap.Bathymetry = ColorMap.makemap({
0: [40, 26, 44], 0: [0.157, 0.102, 0.173],
0.13: [59.49, 90], 0.13: [0.233, 0.192, 0.353],
0.25: [64, 76, 139], 0.25: [0.251, 0.298, 0.545],
0.38: [63, 110, 151], 0.38: [0.247, 0.431, 0.592],
0.5: [72, 142, 158], 0.5: [0.282, 0.557, 0.62],
0.63: [85, 174, 163], 0.63: [0.333, 0.682, 0.639],
0.75: [120, 206, 163], 0.75: [0.471, 0.808, 0.639],
0.88: [187, 230, 172], 0.88: [0.733, 0.902, 0.675],
1: [253, 254, 204], 1: [0.992, 0.996, 0.8],
}); });
ColorMap.Viridis = ColorMap.makemap({ ColorMap.Viridis = ColorMap.makemap({
0: [68, 1, 84], 0: [0.267, 0.004, 0.329],
0.13: [71, 44, 122], 0.13: [0.278, 0.173, 0.478],
0.25: [59, 81, 139], 0.25: [0.231, 0.318, 0.545],
0.38: [44, 113, 142], 0.38: [0.173, 0.443, 0.557],
0.5: [33, 144, 141], 0.5: [0.129, 0.565, 0.553],
0.63: [39, 173, 129], 0.63: [0.153, 0.678, 0.506],
0.75: [92, 200, 99], 0.75: [0.361, 0.784, 0.388],
0.88: [170, 220, 50], 0.88: [0.667, 0.863, 0.196],
1: [253, 231, 37], 1: [0.992, 0.906, 0.145],
}); });
Color.normalize(ColorMap); Color.normalize(ColorMap);
@@ -205,8 +210,7 @@ ColorMap.doc = {
sample: "Sample a given colormap at the given percentage (0 to 1).", sample: "Sample a given colormap at the given percentage (0 to 1).",
}; };
return { Color.maps = ColorMap
Color, Color.utils = esc
esc,
ColorMap, return Color
};

View File

@@ -1,438 +1,87 @@
var renderer_actor = arg[0]
var renderer_id = arg[1]
var graphics = use('graphics', renderer_actor, renderer_id)
var math = use('math') var math = use('math')
var util = use('util') var color = use('color')
var os = use('os')
var geometry = use('geometry')
var Color = use('color')
var draw = {} var draw = {}
draw[prosperon.DOC] = ` draw[prosperon.DOC] = `
A collection of 2D drawing functions that operate in screen space. Provides primitives A collection of 2D drawing functions that create drawing command lists.
for lines, rectangles, text, sprite drawing, etc. Immediate mode. These are pure functions that return plain JavaScript objects representing
drawing operations. No rendering or actor communication happens here.
` `
// Draw command accumulator // Create a new command list
var commands = [] draw.list = function() {
var commands = []
// Prototype object for commands
var command_proto = {
kind: "renderer",
id: renderer_id
}
// Clear accumulated commands
draw.clear = function() {
commands = []
}
// Get accumulated commands
draw.get_commands = function() {
return commands
}
// Flush all commands to renderer
draw.flush = function() {
if (!renderer_actor || !renderer_id || commands.length === 0) return
// Convert commands to batch format return {
var batch_data = commands.map(function(cmd) { // Add a command to this list
return { push: function(cmd) {
op: cmd.op, commands.push(cmd)
prop: cmd.prop, },
value: cmd.value,
data: cmd.data // Get all commands
get: function() {
return commands
},
// Clear all commands
clear: function() {
commands = []
},
// Get command count
length: function() {
return commands.length
} }
}) }
}
// Send all commands in a single batch message
send(renderer_actor, { // Default command list for convenience
kind: "renderer", var current_list = draw.list()
id: renderer_id,
op: "batch", // Set the current list
data: batch_data draw.set_list = function(list) {
}) current_list = list
}
// Clear commands after sending
commands = [] // Get current list
draw.get_list = function() {
return current_list
}
// Clear current list
draw.clear = function() {
current_list.clear()
}
// Get commands from current list
draw.get_commands = function() {
return current_list.get()
} }
// Helper to add a command // Helper to add a command
function add_command(op, data, prop, value) { function add_command(type, data) {
var cmd = Object.create(command_proto) var cmd = {cmd: type}
cmd.op = op Object.assign(cmd, data)
if (data) cmd.data = data current_list.push(cmd)
if (prop) cmd.prop = prop
if (value !== undefined) cmd.value = value
commands.push(cmd)
}
// Drawing functions
draw.point = function(pos, size, opt = {color: Color.white}, pipeline) {
if (opt.color) {
add_command("set", null, "drawColor", opt.color)
}
add_command("point", {points: [pos]})
}
draw.point[prosperon.DOC] = `
:param pos: A 2D position ([x, y]) where the point should be drawn.
:param size: The size of the point.
:param color: The color of the point, defaults to white.
:return: None
`
/* ------------------------------------------------------------------------
* helper is (dx,dy) inside the desired wedge?
* -------------------------------------------------------------------- */
function within_wedge(dx, dy, start, end, full_circle)
{
if (full_circle) return true
var ang = Math.atan2(dy, dx) // [-π,π]
if (ang < 0) ang += Math.PI * 2
var t = ang / (Math.PI * 2) // turn ∈ [0,1)
if (start <= end) return t >= start && t <= end
return t >= start || t <= end // wrap-around arc
}
/* ------------------------------------------------------------------------
* software ellipse outline / ring / fill via inner_radius
* -------------------------------------------------------------------- */
function software_ellipse(pos, radii, opt)
{
var rx = radii[0], ry = radii[1]
if (rx <= 0 || ry <= 0) return
var cx = pos[0], cy = pos[1]
var raw_start = opt.start
var raw_end = opt.end
var full_circle = Math.abs(raw_end - raw_start) >= 1 - 1e-9
var start = (raw_start % 1 + 1) % 1
var end = (raw_end % 1 + 1) % 1
var thickness = Math.max(1, opt.thickness|0)
/* inner ellipse radii (may be zero or negative → treat as no hole) */
var rx_i = rx - thickness,
ry_i = ry - thickness
var hole = (rx_i > 0 && ry_i > 0)
/* fast one-pixel outline ? --------------------------------------- */
if (!hole && thickness === 1) {
/* (same midpoint algorithm as before, filtered by wedge) --------*/
var rx_sq = rx * rx, ry_sq = ry * ry
var two_rx_sq = rx_sq << 1, two_ry_sq = ry_sq << 1
var x = 0, y = ry, px = 0, py = two_rx_sq * y
var p = ry_sq - rx_sq * ry + 0.25 * rx_sq
function plot_pts(x, y) {
var pts = [
[cx + x, cy + y], [cx - x, cy + y],
[cx + x, cy - y], [cx - x, cy - y]
].filter(pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle))
if (pts.length) {
add_command("point", {points: pts})
}
}
while (px < py) {
plot_pts(x, y)
++x; px += two_ry_sq
if (p < 0) p += ry_sq + px
else { --y; py -= two_rx_sq; p += ry_sq + px - py }
}
p = ry_sq*(x+.5)*(x+.5) + rx_sq*(y-1)*(y-1) - rx_sq*ry_sq
while (y >= 0) {
plot_pts(x, y)
--y; py -= two_rx_sq
if (p > 0) p += rx_sq - py
else { ++x; px += two_ry_sq; p += rx_sq - py + px }
}
return
}
/* ------------------------------------------------------------------
* FILL or RING (thickness ≥ 2 OR hole == false -> full fill)
* ---------------------------------------------------------------- */
var strips = []
var rx_sq = rx * rx, ry_sq = ry * ry
var rx_i_sq = rx_i * rx_i, ry_i_sq = ry_i * ry_i
for (var dy = -ry; dy <= ry; ++dy) {
var yy = dy * dy
var x_out = Math.floor(rx * Math.sqrt(1 - yy / ry_sq))
var y_screen = cy + dy
/* optional inner span */
var x_in = hole ? Math.floor(rx_i * Math.sqrt(1 - yy / ry_i_sq)) : -1
var run_start = null
for (var dx = -x_out; dx <= x_out; ++dx) {
if (hole && Math.abs(dx) <= x_in) { run_start = null; continue }
if (!within_wedge(dx, dy, start, end, full_circle)) { run_start = null; continue }
if (run_start === null) run_start = cx + dx
var last = (dx === x_out)
var next_in_ring =
!last &&
!(hole && Math.abs(dx+1) <= x_in) &&
within_wedge(dx+1, dy, start, end, full_circle)
if (last || !next_in_ring) {
strips.push({
x: run_start,
y: y_screen,
width: (cx + dx) - run_start + 1,
height: 1
})
run_start = null
}
}
}
if (strips.length) {
add_command("rects", {rects: strips})
}
} }
// Default geometry definitions
var ellipse_def = { var ellipse_def = {
color: Color.white,
start: 0, start: 0,
end: 1, end: 1,
mode: 'fill', mode: 'fill',
thickness: 1, thickness: 1,
} }
draw.ellipse = function(pos, radii, def, pipeline) {
var opt = def ? {...ellipse_def, ...def} : ellipse_def
if (opt.color) {
add_command("set", null, "drawColor", opt.color)
}
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
software_ellipse(pos, radii, opt)
}
var line_def = { var line_def = {
color: Color.white,
thickness: 1, thickness: 1,
cap:"butt", cap:"butt",
} }
draw.line = function(points, def, pipeline)
{
var opt
if (def)
opt = {...line_def, ...def}
else
opt = line_def
if (opt.color) {
add_command("set", null, "drawColor", opt.color)
}
add_command("line", {points: points})
}
draw.cross = function render_cross(pos, size, def, pipe) {
var a = [pos.add([0, size]), pos.add([0, -size])]
var b = [pos.add([size, 0]), pos.add([-size, 0])]
draw.line(a, def, pipe)
draw.line(b, def,pipe)
}
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, def, pipe) {
var dir = math.norm(end.sub(start))
var wing1 = [math.rotate(dir, wingangle).scale(wingspan).add(end), end]
var wing2 = [math.rotate(dir, -wingangle).scale(wingspan).add(end), end]
draw.line([start, end], def, pipe)
draw.line(wing1, def, pipe)
draw.line(wing2, def, pipe)
}
/* ------------------------------------------------------------------------
* helper plain rectangle outline of arbitrary thickness (radius=0)
* -------------------------------------------------------------------- */
function software_outline_rect(rect, thickness)
{
if (thickness <= 0) {
add_command("fillRect", {rect: rect})
return;
}
/* stroke swallows the whole thing → fill instead */
if ((thickness << 1) >= rect.width ||
(thickness << 1) >= rect.height) {
add_command("fillRect", {rect: rect})
return
}
const x0 = rect.x,
y0 = rect.y,
x1 = rect.x + rect.width,
y1 = rect.y + rect.height
add_command("rects", {
rects: [
{ x:x0, y:y0, width:rect.width, height:thickness }, // top
{ x:x0, y:y1-thickness, width:rect.width, height:thickness }, // bottom
{ x:x0, y:y0+thickness, width:thickness,
height:rect.height - (thickness<<1) }, // left
{ x:x1-thickness, y:y0+thickness, width:thickness,
height:rect.height - (thickness<<1) } // right
]
})
}
/* ------------------------------------------------------------------------
* ROUNDED rectangle outline (was software_round_rect)
* -------------------------------------------------------------------- */
function software_round_rect(rect, radius, thickness = 1)
{
if (thickness <= 0) {
software_fill_round_rect(rect, radius)
return
}
radius = Math.min(radius, rect.width >> 1, rect.height >> 1)
/* stroke covers whole rect → fall back to fill ------------------- */
if ((thickness << 1) >= rect.width ||
(thickness << 1) >= rect.height ||
thickness >= radius) {
software_fill_round_rect(rect, radius)
return
}
const x0 = rect.x,
y0 = rect.y,
x1 = rect.x + rect.width - 1, // inclusive
y1 = rect.y + rect.height - 1
const cx_l = x0 + radius, cx_r = x1 - radius
const cy_t = y0 + radius, cy_b = y1 - radius
const r_out = radius
const r_in = radius - thickness
/* straight bands (top/bottom/left/right) ------------------------- */
add_command("rects", {
rects: [
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
height:thickness }, // top
{ x:x0 + radius, y:y1 - thickness + 1,
width:rect.width - (radius << 1), height:thickness }, // bottom
{ x:x0, y:y0 + radius, width:thickness,
height:rect.height - (radius << 1) }, // left
{ x:x1 - thickness + 1, y:y0 + radius, width:thickness,
height:rect.height - (radius << 1) } // right
]
})
/* corner arcs ---------------------------------------------------- */
const strips = []
for (let dy = 0; dy < radius; ++dy) {
const dy_sq = dy * dy
const dx_out = Math.floor(Math.sqrt(r_out * r_out - dy_sq))
const dx_in = (r_in > 0 && dy < r_in)
? Math.floor(Math.sqrt(r_in * r_in - dy_sq))
: -1 // no inner rim
const w = dx_out - dx_in // strip width
if (w <= 0) continue
/* top */
strips.push(
{ x:cx_l - dx_out, y:cy_t - dy, width:w, height:1 }, // NW
{ x:cx_r + dx_in + 1, y:cy_t - dy, width:w, height:1 } // NE
)
/* bottom */
strips.push(
{ x:cx_l - dx_out, y:cy_b + dy, width:w, height:1 }, // SW
{ x:cx_r + dx_in + 1, y:cy_b + dy, width:w, height:1 } // SE
)
}
add_command("rects", {rects: strips})
}
/* ------------------------------------------------------------------------
* filled rounded rect (unchanged)
* -------------------------------------------------------------------- */
function software_fill_round_rect(rect, radius)
{
radius = Math.min(radius, rect.width >> 1, rect.height >> 1)
const x0 = rect.x,
y0 = rect.y,
x1 = rect.x + rect.width - 1,
y1 = rect.y + rect.height - 1
/* main column */
add_command("rects", {
rects: [
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
height:rect.height }
]
})
/* side columns */
add_command("rects", {
rects: [
{ x:x0, y:y0 + radius, width:radius,
height:rect.height - (radius << 1) },
{ x:x1 - radius + 1, y:y0 + radius, width:radius,
height:rect.height - (radius << 1) }
]
})
/* corner caps */
const cx_l = x0 + radius, cx_r = x1 - radius
const cy_t = y0 + radius, cy_b = y1 - radius
const caps = []
for (let dy = 0; dy < radius; ++dy) {
const dx = Math.floor(Math.sqrt(radius * radius - dy * dy))
const w = (dx << 1) + 1
caps.push(
{ x:cx_l - dx, y:cy_t - dy, width:w, height:1 },
{ x:cx_r - dx, y:cy_t - dy, width:w, height:1 },
{ x:cx_l - dx, y:cy_b + dy, width:w, height:1 },
{ x:cx_r - dx, y:cy_b + dy, width:w, height:1 }
)
}
add_command("rects", {rects: caps})
}
var rect_def = { var rect_def = {
thickness:1, thickness:1,
color: Color.white,
radius: 0 radius: 0
} }
draw.rectangle = function render_rectangle(rect, def, pipeline) {
var opt = def ? {...rect_def, ...def} : rect_def
if (opt.color) {
add_command("set", null, "drawColor", opt.color)
}
var t = opt.thickness|0
if (t <= 0) {
if (opt.radius)
software_fill_round_rect(rect, opt.radius)
else
add_command("fillRect", {rect: rect})
return
}
if (opt.radius)
software_round_rect(rect, opt.radius, t)
else
software_outline_rect(rect,t)
}
var slice9_info = { var slice9_info = {
tile_top:true, tile_top:true,
@@ -441,145 +90,152 @@ var slice9_info = {
tile_right:true, tile_right:true,
tile_center_x:true, tile_center_x:true,
tile_center_right:true, tile_center_right:true,
color: Color.white,
} }
draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info, pipeline) { var image_info = {
if (!image) throw Error('Need an image to render.') tile_x: false,
if (typeof image === "string") tile_y: false,
image = graphics.texture(image) flip_x: false,
flip_y: false,
mode: 'linear'
}
// TODO: Implement slice9 rendering via SDL video actor var circle_def = {
inner_radius:1, // percentage: 1 means filled circle
start:0,
end: 1,
}
// Drawing functions
draw.point = function(pos, size, opt = {}, material) {
add_command("draw_point", {
pos: pos,
size: size,
opt: opt,
material: material
})
}
draw.point[prosperon.DOC] = `
:param pos: A 2D position ([x, y]) where the point should be drawn.
:param size: The size of the point.
:param opt: Optional geometry properties.
:param material: Material/styling information (color, shaders, etc.)
:return: None
`
draw.ellipse = function(pos, radii, def, material) {
var opt = def ? {...ellipse_def, ...def} : ellipse_def
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
add_command("draw_ellipse", {
pos: pos,
radii: radii,
opt: opt,
material: material
})
}
draw.line = function(points, def, material)
{
var opt = def ? {...line_def, ...def} : line_def
add_command("draw_line", {
points: points,
opt: opt,
material: material
})
}
draw.cross = function render_cross(pos, size, def, material) {
var a = [pos.add([0, size]), pos.add([0, -size])]
var b = [pos.add([size, 0]), pos.add([-size, 0])]
draw.line(a, def, material)
draw.line(b, def, material)
}
draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, def, material) {
var dir = math.norm(end.sub(start))
var wing1 = [math.rotate(dir, wingangle).scale(wingspan).add(end), end]
var wing2 = [math.rotate(dir, -wingangle).scale(wingspan).add(end), end]
draw.line([start, end], def, material)
draw.line(wing1, def, material)
draw.line(wing2, def, material)
}
draw.rectangle = function render_rectangle(rect, def, material) {
var opt = def ? {...rect_def, ...def} : rect_def
add_command("draw_rect", {
rect: rect,
opt: opt,
material: material
})
}
draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info, material) {
if (!image) throw Error('Need an image to render.')
add_command("draw_slice9", {
image: image,
rect: rect,
slice: slice,
info: info,
material: material
})
} }
draw.slice9[prosperon.DOC] = ` draw.slice9[prosperon.DOC] = `
:param image: An image object or string path to a texture. :param image: An image object or string path to a texture.
:param rect: A rectangle specifying draw location/size, default [0, 0]. :param rect: A rectangle specifying draw location/size, default [0, 0].
:param slice: The pixel inset or spacing for the 9-slice (number or object). :param slice: The pixel inset or spacing for the 9-slice (number or object).
:param info: A slice9 info object controlling tiling of edges/corners. :param info: A slice9 info object controlling tiling of edges/corners.
:param pipeline: (Optional) A pipeline or rendering state object. :param material: Material/styling information
:return: None :return: None
:raises Error: If no image is provided. :raises Error: If no image is provided.
` `
var std_sprite_cmd = {type:'sprite', color:[1,1,1,1]} draw.image = function image(image, rect = [0,0], rotation = 0, anchor = [0,0], shear = [0,0], info = {}, material) {
var image_info = {
tile_x: false,
tile_y: false,
flip_x: false,
flip_y: false,
color: Color.white,
mode: 'linear'
}
draw.image = function image(image, rect = [0,0], rotation = 0, anchor = [0,0], shear = [0,0], info = {}, pipeline) {
if (!image) throw Error('Need an image to render.') if (!image) throw Error('Need an image to render.')
if (typeof image === "string")
image = graphics.texture(image)
// Ensure rect has proper structure // Ensure rect has proper structure
if (Array.isArray(rect)) { if (Array.isArray(rect)) {
rect = {x: rect[0], y: rect[1], width: image.width, height: image.height} rect = {x: rect[0], y: rect[1], width: 100, height: 100} // Default size
} else {
rect.width ??= image.width
rect.height ??= image.height
} }
info = Object.assign({}, image_info, info); info = Object.assign({}, image_info, info);
// Get the GPU texture (might be loading) add_command("draw_image", {
var texture = image.gpu; image: image,
if (!texture) { rect: rect,
// Texture not loaded yet, skip drawing rotation: rotation,
return; anchor: anchor,
} shear: shear,
info: info,
// Set texture filtering mode material: material
if (info.mode) {
add_command("set", null, "textureFilter", info.mode === 'linear' ? 'linear' : 'nearest')
}
// Set color if specified
if (info.color) {
add_command("set", null, "drawColor", info.color)
}
// Calculate source rectangle from image.rect (UV coords)
var src_rect = {
x: image.rect.x * image.width,
y: image.rect.y * image.height,
width: image.rect.width * image.width,
height: image.rect.height * image.height
}
// Handle flipping
if (info.flip_x) {
src_rect.x += src_rect.width;
src_rect.width = -src_rect.width;
}
if (info.flip_y) {
src_rect.y += src_rect.height;
src_rect.height = -src_rect.height;
}
// Draw the texture
add_command("copyTexture", {
texture_id: texture.id,
src: src_rect,
dest: rect
}) })
} }
function software_circle(pos, radius) draw.circle = function render_circle(pos, radius, def, material) {
{ draw.ellipse(pos, [radius,radius], def, material)
if (radius <= 0) return // nothing to draw
var cx = pos[0], cy = pos[1]
var x = 0, y = radius
var d = 3 - (radius << 1) // decision parameter
while (x <= y) {
draw.point([
[cx + x, cy + y], [cx - x, cy + y],
[cx + x, cy - y], [cx - x, cy - y],
[cx + y, cy + x], [cx - y, cy + x],
[cx + y, cy - x], [cx - y, cy - x]
])
if (d < 0) d += (x << 2) + 6
else {
d += ((x - y) << 2) + 10
y--
}
x++
}
} }
var circle_def = { draw.text = function text(text, rect, font = 'fonts/c64.ttf', size = 8, color = color.white, wrap = 0) {
inner_radius:1, // percentage: 1 means filled circle add_command("draw_text", {
color: Color.white, text: text,
start:0, rect: rect,
end: 1, font: font,
} size: size,
draw.circle = function render_circle(pos, radius, def, pipeline) { wrap: wrap,
draw.ellipse(pos, [radius,radius], def, pipeline) material: {color}
} })
var sysfont = graphics.get_font('fonts/c64.ttf', 8)
draw.text = function text(text, rect, font = sysfont, size = 0, color = Color.white, wrap = 0, pipeline) {
if (typeof font === 'string') font = graphics.get_font(font)
var mesh = graphics.make_text_buffer(text, rect, 0, color, wrap, font)
// TODO: Handle text rendering via geometry
} }
draw.text[prosperon.DOC] = ` draw.text[prosperon.DOC] = `
:param text: The string to draw. :param text: The string to draw.
:param rect: A rectangle specifying draw position (and possibly wrapping area). :param rect: A rectangle specifying draw position (and possibly wrapping area).
:param font: A font object or string path, default sysfont. :param font: A font object or string path, default sysfont.
:param size: (Unused) Possibly intended for scaling the font size. :param size: Font size in pixels.
:param color: The text color, default Color.white. :param color: The text color, default color.white.
:param wrap: Pixel width for text wrapping, default 0 (no wrap). :param wrap: Pixel width for text wrapping, default 0 (no wrap).
:param pipeline: (Optional) A pipeline or rendering state object. :param material: Material/styling information
:return: None :return: None
` `

View File

@@ -1,4 +1,4 @@
var Color = use('color') var color = use('color')
var os = use('os') var os = use('os')
var graphics = use('graphics') var graphics = use('graphics')
var transform = use('transform') var transform = use('transform')
@@ -104,7 +104,7 @@ ex.scale = 1
ex.grow_for = 0 ex.grow_for = 0
ex.spawn_timer = 0 ex.spawn_timer = 0
ex.pps = 0 ex.pps = 0
ex.color = Color.white ex.color = color.white
ex.draw = function() ex.draw = function()
{ {

View File

@@ -1,4 +1,5 @@
var graphics = use('graphics') var graphics = use('graphics')
var color = use('color')
var sprite = { var sprite = {
image: undefined, image: undefined,
@@ -223,7 +224,7 @@ return sprite;
--- ---
var Color = use('color') var color = use('color')
var transform = use('transform') var transform = use('transform')
var sprite = use('sprite') var sprite = use('sprite')
@@ -234,7 +235,7 @@ if (this.overling.transform)
this.transform.change_hook = $.t_hook; this.transform.change_hook = $.t_hook;
var msp = new sprite var msp = new sprite
this._sprite = msp; this._sprite = msp;
msp.color = Color.white; msp.color = color.white;
this.transform.sprite = this this.transform.sprite = this

View File

@@ -1,5 +1,7 @@
var imgui = this; var imgui = this;
var color = use('color')
var debug = {} var debug = {}
var imdebug = function imdebug() { var imdebug = function imdebug() {

View File

@@ -0,0 +1,223 @@
/**
* Rasterization module for converting shapes to pixels/rects
* Used for software rendering of complex shapes
*/
var math = use('math')
var rasterize = {}
function within_wedge(dx, dy, start, end, full_circle) {
if (full_circle) return true
var ang = Math.atan2(dy, dx)
if (ang < 0) ang += Math.PI * 2
var t = ang / (Math.PI * 2)
if (start <= end) return t >= start && t <= end
return t >= start || t <= end
}
rasterize.ellipse = function(pos, radii, opt) {
opt = opt || {}
var rx = radii[0], ry = radii[1]
if (rx <= 0 || ry <= 0) return []
var cx = pos[0], cy = pos[1]
var raw_start = opt.start || 0
var raw_end = opt.end || 1
var full_circle = Math.abs(raw_end - raw_start) >= 1 - 1e-9
var start = (raw_start % 1 + 1) % 1
var end = (raw_end % 1 + 1) % 1
var thickness = Math.max(1, opt.thickness || 1)
var rx_i = rx - thickness,
ry_i = ry - thickness
var hole = (rx_i > 0 && ry_i > 0)
if (!hole && thickness === 1) {
var points = []
var rx_sq = rx * rx, ry_sq = ry * ry
var two_rx_sq = rx_sq << 1, two_ry_sq = ry_sq << 1
var x = 0, y = ry, px = 0, py = two_rx_sq * y
var p = ry_sq - rx_sq * ry + 0.25 * rx_sq
function add_pts(x, y) {
var pts = [
[cx + x, cy + y], [cx - x, cy + y],
[cx + x, cy - y], [cx - x, cy - y]
].filter(pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle))
points = points.concat(pts)
}
while (px < py) {
add_pts(x, y)
++x; px += two_ry_sq
if (p < 0) p += ry_sq + px
else { --y; py -= two_rx_sq; p += ry_sq + px - py }
}
p = ry_sq*(x+.5)*(x+.5) + rx_sq*(y-1)*(y-1) - rx_sq*ry_sq
while (y >= 0) {
add_pts(x, y)
--y; py -= two_rx_sq
if (p > 0) p += rx_sq - py
else { ++x; px += two_ry_sq; p += rx_sq - py + px }
}
return {type: 'points', data: points}
}
var strips = []
var rx_sq = rx * rx, ry_sq = ry * ry
var rx_i_sq = rx_i * rx_i, ry_i_sq = ry_i * ry_i
for (var dy = -ry; dy <= ry; ++dy) {
var yy = dy * dy
var x_out = Math.floor(rx * Math.sqrt(1 - yy / ry_sq))
var y_screen = cy + dy
var x_in = hole ? Math.floor(rx_i * Math.sqrt(1 - yy / ry_i_sq)) : -1
var run_start = null
for (var dx = -x_out; dx <= x_out; ++dx) {
if (hole && Math.abs(dx) <= x_in) { run_start = null; continue }
if (!within_wedge(dx, dy, start, end, full_circle)) { run_start = null; continue }
if (run_start === null) run_start = cx + dx
var last = (dx === x_out)
var next_in_ring =
!last &&
!(hole && Math.abs(dx+1) <= x_in) &&
within_wedge(dx+1, dy, start, end, full_circle)
if (last || !next_in_ring) {
strips.push({
x: run_start,
y: y_screen,
width: (cx + dx) - run_start + 1,
height: 1
})
run_start = null
}
}
}
return {type: 'rects', data: strips}
}
rasterize.circle = function(pos, radius, opt) {
return rasterize.ellipse(pos, [radius, radius], opt)
}
rasterize.outline_rect = function(rect, thickness) {
if (thickness <= 0) {
return {type: 'rect', data: rect}
}
if ((thickness << 1) >= rect.width ||
(thickness << 1) >= rect.height) {
return {type: 'rect', data: rect}
}
var x0 = rect.x,
y0 = rect.y,
x1 = rect.x + rect.width,
y1 = rect.y + rect.height
return {type: 'rects', data: [
{ x:x0, y:y0, width:rect.width, height:thickness },
{ x:x0, y:y1-thickness, width:rect.width, height:thickness },
{ x:x0, y:y0+thickness, width:thickness,
height:rect.height - (thickness<<1) },
{ x:x1-thickness, y:y0+thickness, width:thickness,
height:rect.height - (thickness<<1) }
]}
}
rasterize.round_rect = function(rect, radius, thickness) {
thickness = thickness || 1
if (thickness <= 0) {
return rasterize.fill_round_rect(rect, radius)
}
radius = Math.min(radius, rect.width >> 1, rect.height >> 1)
if ((thickness << 1) >= rect.width ||
(thickness << 1) >= rect.height ||
thickness >= radius) {
return rasterize.fill_round_rect(rect, radius)
}
var x0 = rect.x,
y0 = rect.y,
x1 = rect.x + rect.width - 1,
y1 = rect.y + rect.height - 1
var cx_l = x0 + radius, cx_r = x1 - radius
var cy_t = y0 + radius, cy_b = y1 - radius
var r_out = radius
var r_in = radius - thickness
var rects = [
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1), height:thickness },
{ x:x0 + radius, y:y1 - thickness + 1, width:rect.width - (radius << 1), height:thickness },
{ x:x0, y:y0 + radius, width:thickness, height:rect.height - (radius << 1) },
{ x:x1 - thickness + 1, y:y0 + radius, width:thickness, height:rect.height - (radius << 1) }
]
var strips = []
for (var dy = 0; dy < radius; ++dy) {
var dy_sq = dy * dy
var dx_out = Math.floor(Math.sqrt(r_out * r_out - dy_sq))
var dx_in = (r_in > 0 && dy < r_in)
? Math.floor(Math.sqrt(r_in * r_in - dy_sq))
: -1
var w = dx_out - dx_in
if (w <= 0) continue
strips.push(
{ x:cx_l - dx_out, y:cy_t - dy, width:w, height:1 },
{ x:cx_r + dx_in + 1, y:cy_t - dy, width:w, height:1 },
{ x:cx_l - dx_out, y:cy_b + dy, width:w, height:1 },
{ x:cx_r + dx_in + 1, y:cy_b + dy, width:w, height:1 }
)
}
return {type: 'rects', data: rects.concat(strips)}
}
rasterize.fill_round_rect = function(rect, radius) {
radius = Math.min(radius, rect.width >> 1, rect.height >> 1)
var x0 = rect.x,
y0 = rect.y,
x1 = rect.x + rect.width - 1,
y1 = rect.y + rect.height - 1
var rects = [
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1), height:rect.height },
{ x:x0, y:y0 + radius, width:radius, height:rect.height - (radius << 1) },
{ x:x1 - radius + 1, y:y0 + radius, width:radius, height:rect.height - (radius << 1) }
]
var cx_l = x0 + radius, cx_r = x1 - radius
var cy_t = y0 + radius, cy_b = y1 - radius
var caps = []
for (var dy = 0; dy < radius; ++dy) {
var dx = Math.floor(Math.sqrt(radius * radius - dy * dy))
var w = (dx << 1) + 1
caps.push(
{ x:cx_l - dx, y:cy_t - dy, width:w, height:1 },
{ x:cx_r - dx, y:cy_t - dy, width:w, height:1 },
{ x:cx_l - dx, y:cy_b + dy, width:w, height:1 },
{ x:cx_r - dx, y:cy_b + dy, width:w, height:1 }
)
}
return {type: 'rects', data: rects.concat(caps)}
}
return rasterize

View File

@@ -7,6 +7,7 @@ var tracy = use('tracy')
var graphics = use('graphics') var graphics = use('graphics')
var imgui = use('imgui') var imgui = use('imgui')
var transform = use('transform') var transform = use('transform')
var color = use('color')
var base_pipeline = { var base_pipeline = {
vertex: "sprite.vert", vertex: "sprite.vert",
@@ -666,7 +667,7 @@ function mask(image, pos, scale, rotation = 0, ref = 1) {
render.use_mat({ render.use_mat({
diffuse:image.texture, diffuse:image.texture,
rect: image.rect, rect: image.rect,
shade: Color.white shade: color.white
}); });
render.draw(shape.quad); render.draw(shape.quad);
} }

View File

@@ -1,6 +1,7 @@
var render = use('render') var render = use('render')
var os = use('os') var os = use('os')
var transform = use('transform') var transform = use('transform')
var color = use('color')
render.initialize({ render.initialize({
width:500, width:500,
@@ -61,9 +62,9 @@ function loop()
draw.point([100,100]) draw.point([100,100])
draw.circle([200,200],40) draw.circle([200,200],40)
draw.ellipse([300,300],[20,40], {start:0,end:1, thickness:0}) draw.ellipse([300,300],[20,40], {start:0,end:1, thickness:0})
draw.ellipse([350,350], [30,30], {start:0.1,end:-0.1, thickness:30, color: Color.yellow}) draw.ellipse([350,350], [30,30], {start:0.1,end:-0.1, thickness:30, color: color.yellow})
draw.ellipse([100,80],[40,25], {thickness:10, color:Color.green}) draw.ellipse([100,80],[40,25], {thickness:10, color:color.green})
draw.ellipse([100,80], [40,25], {thickness:1,color:Color.blue}) draw.ellipse([100,80], [40,25], {thickness:1,color:color.blue})
draw.rectangle({x:150,y:150,width:50,height:50}) draw.rectangle({x:150,y:150,width:50,height:50})
draw.rectangle({x:100, y:60, width:200, height:60}, {radius: 20, thickness:-3}) draw.rectangle({x:100, y:60, width:200, height:60}, {radius: 20, thickness:-3})
draw.rectangle({x:350, y:60, width:200, height:120}, {radius:10,thickness:3}) draw.rectangle({x:350, y:60, width:200, height:120}, {radius:10,thickness:3})

View File

@@ -60,12 +60,7 @@ function start_drawing() {
var frame = 0; var frame = 0;
var start_time = os.now(); var start_time = os.now();
// Load an image // Note: Image loading would be handled by moth in real implementation
var bunny_image = graphics.texture('tests/bunny.png')
send(video_actor, {kind: "cursor", op: "create", data: bunny_image.cpu}, ({id}) => {
send(video_actor, {kind:"cursor", op: "set", id})
})
function draw_frame() { function draw_frame() {
frame++; frame++;
@@ -183,6 +178,7 @@ function start_drawing() {
draw2d.point( draw2d.point(
[px, py], [px, py],
3, 3,
{},
{color: [1, 0.5 + Math.sin(t * 2 + i) * 0.5, 0.5, 1]} {color: [1, 0.5 + Math.sin(t * 2 + i) * 0.5, 0.5, 1]}
); );
} }

220
tests/moth.js Normal file
View File

@@ -0,0 +1,220 @@
/**
* Moth Game Framework
* Higher-level game development framework built on top of Prosperon
*/
var os = use('os');
var io = use('io');
var transform = use('transform');
var rasterize = use('rasterize');
var video_actor = use('sdl_video')
var window
var render
var gameactor
$_.start(e => {
if (gameactor) return
gameactor = e.actor
loop()
}, 'tests/prosperon.js')
send(video_actor, {
kind: "window",
op:"create"
}, e => {
if (e.error) {
console.error(e.error)
os.exit(1)
}
window = e.id
send(video_actor,{
kind:"window",
op:"makeRenderer",
id:window
}, e => {
if (e.error) {
console.error(e.error)
os.exit(1)
}
render = e.id
console.log(`Created window and renderer id ${render}`)
})
})
var last = os.now()
// Engine state
var camera = {
x: 0,
y: 0,
scale: 1,
rotation: 0
}
// Convert high-level draw commands to low-level renderer commands
function translate_draw_commands(commands) {
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":
// 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') {
// SDL video expects 'rects' operation, not 'fillRects'
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') {
// SDL video expects 'rects' operation with array
renderer_commands.push({
op: "rects",
data: {rects: raster_result.data}
})
}
} else {
// Filled rectangle
renderer_commands.push({
op: "fillRect",
data: {rect: cmd.rect}
})
}
break
case "draw_circle":
case "draw_ellipse":
// 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}
})
break
case "draw_point":
renderer_commands.push({
op: "point",
data: {points: [cmd.pos]}
})
break
case "draw_image":
// TODO: Handle image loading and texture management
renderer_commands.push({
op: "texture",
data: cmd
})
break
case "draw_text":
// Use debugText for now
renderer_commands.push({
op: "debugText",
data: {
pos: cmd.pos || {x: 0, y: 0},
text: cmd.text || ""
}
})
break
}
})
return renderer_commands
}
function loop()
{
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_actor, {
kind: "renderer",
id: render,
op: "batch",
data: batch_commands
})
})
})
$_.delay(loop, 1/60)
}

62
tests/prosperon.js Normal file
View File

@@ -0,0 +1,62 @@
var draw2d = use('draw2d')
var color = use('color')
function update(dt)
{
// Update game logic here
return {}
}
function draw()
{
// Clear the draw list
draw2d.clear()
// Draw a red outlined rectangle
draw2d.rectangle(
{x: 100, y: 100, width: 200, height: 150}, // rect
{thickness: 3}, // geometry options
{color: color.red} // material
)
// Draw a filled green circle
draw2d.circle(
[300, 300], // position
50, // radius
{thickness: 0}, // geometry (0 = filled)
{color: color.green} // material
)
// Draw blue rounded rectangle
draw2d.rectangle(
{x: 350, y: 200, width: 150, height: 100},
{thickness: 2, radius: 15}, // rounded corners
{color: color.blue}
)
// Draw text
draw2d.text(
"Hello from Prosperon!",
{x: 50, y: 50},
'fonts/c64.ttf',
16,
color.white, // no wrap
)
// Draw a line
draw2d.line(
[[50, 400], [150, 450], [250, 400]],
{thickness: 2},
{color: color.yellow}
)
// Return the draw commands
return draw2d.get_commands()
}
$_.receiver(e => {
if (e.kind == 'update')
send(e, update(e.dt))
else if (e.kind == 'draw')
send(e, draw())
})