draw2d now generates high level commands; turned into instructions by moth
This commit is contained in:
@@ -5,6 +5,7 @@ var sprite = use('sprite')
|
||||
var geom = use('geometry')
|
||||
var input = use('controller')
|
||||
var config = use('config')
|
||||
var color = use('color')
|
||||
|
||||
var bunnyTex = graphics.texture("bunny")
|
||||
|
||||
@@ -65,5 +66,5 @@ this.hud = function() {
|
||||
draw.images(bunnyTex, bunnies)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
var draw = use('draw2d')
|
||||
var input = use('controller')
|
||||
var config = use('config')
|
||||
var color = use('color')
|
||||
|
||||
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 paddles
|
||||
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: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 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
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ var render = use('render')
|
||||
var graphics = use('graphics')
|
||||
var input = use('input')
|
||||
var config = use('config')
|
||||
var color = use('color')
|
||||
|
||||
prosperon.camera.transform.pos = [0,0]
|
||||
|
||||
@@ -83,15 +84,15 @@ this.hud = function() {
|
||||
// Draw snake
|
||||
for (var i=0; i<snake.length; 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.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") {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var draw = use('draw2d')
|
||||
var input = use('input')
|
||||
var config = use('config')
|
||||
var color = use('color')
|
||||
|
||||
prosperon.camera.transform.pos = [0,0]
|
||||
|
||||
@@ -248,7 +249,7 @@ this.hud = function() {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
for (var i=0; i<nextPiece.blocks.length; i++) {
|
||||
var nx = nextPiece.blocks[i][0]
|
||||
@@ -261,10 +262,10 @@ this.hud = function() {
|
||||
|
||||
// Score & 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) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,8 +272,6 @@ globalThis.use = function use(file, ...args) {
|
||||
globalThis.json = use('json')
|
||||
var time = use('time')
|
||||
|
||||
globalThis.mixin("color")
|
||||
|
||||
var DOCPATH = 'scripts/core/doc.js'
|
||||
var script = io.slurp(DOCPATH)
|
||||
var fnname = "doc"
|
||||
@@ -706,7 +704,6 @@ function enet_check()
|
||||
$_.delay(enet_check, service_delay);
|
||||
}
|
||||
|
||||
send_messages();
|
||||
enet_check();
|
||||
|
||||
// Finally, run the program
|
||||
|
||||
@@ -5,17 +5,17 @@ function tohex(n) {
|
||||
};
|
||||
|
||||
var Color = {
|
||||
white: [255, 255, 255],
|
||||
white: [1, 1, 1],
|
||||
black: [0, 0, 0],
|
||||
blue: [0, 0, 255],
|
||||
green: [0, 255, 0],
|
||||
yellow: [255, 255, 0],
|
||||
red: [255, 0, 0],
|
||||
gray: [181, 181, 181],
|
||||
cyan: [0, 255, 255],
|
||||
purple: [162, 93, 227],
|
||||
orange: [255, 144, 64],
|
||||
magenta: [255, 0, 255],
|
||||
blue: [0, 0, 1],
|
||||
green: [0, 1, 0],
|
||||
yellow: [1, 1, 0],
|
||||
red: [1, 0, 0],
|
||||
gray: [0.71, 0.71, 0.71],
|
||||
cyan: [0, 1, 1],
|
||||
purple: [0.635, 0.365, 0.89],
|
||||
orange: [1, 0.565, 0.251],
|
||||
magenta: [1, 0, 1],
|
||||
};
|
||||
|
||||
Color.editor = {};
|
||||
@@ -41,85 +41,90 @@ esc.color = function (v) {
|
||||
esc.doc = "Functions and constants for ANSI escape sequences.";
|
||||
|
||||
Color.Arkanoid = {
|
||||
orange: [255, 143, 0],
|
||||
teal: [0, 255, 255],
|
||||
green: [0, 255, 0],
|
||||
red: [255, 0, 0],
|
||||
blue: [0, 112, 255],
|
||||
purple: [255, 0, 255],
|
||||
yellow: [255, 255, 0],
|
||||
silver: [157, 157, 157],
|
||||
gold: [188, 174, 0],
|
||||
orange: [1, 0.561, 0],
|
||||
teal: [0, 1, 1],
|
||||
green: [0, 1, 0],
|
||||
red: [1, 0, 0],
|
||||
blue: [0, 0.439, 1],
|
||||
purple: [1, 0, 1],
|
||||
yellow: [1, 1, 0],
|
||||
silver: [0.616, 0.616, 0.616],
|
||||
gold: [0.737, 0.682, 0],
|
||||
};
|
||||
|
||||
Color.Arkanoid.Powerups = {
|
||||
red: [174, 0, 0] /* laser */,
|
||||
blue: [0, 0, 174] /* enlarge */,
|
||||
green: [0, 174, 0] /* catch */,
|
||||
orange: [224, 143, 0] /* slow */,
|
||||
purple: [210, 0, 210] /* break */,
|
||||
cyan: [0, 174, 255] /* disruption */,
|
||||
gray: [143, 143, 143] /* 1up */,
|
||||
red: [0.682, 0, 0] /* laser */,
|
||||
blue: [0, 0, 0.682] /* enlarge */,
|
||||
green: [0, 0.682, 0] /* catch */,
|
||||
orange: [0.878, 0.561, 0] /* slow */,
|
||||
purple: [0.824, 0, 0.824] /* break */,
|
||||
cyan: [0, 0.682, 1] /* disruption */,
|
||||
gray: [0.561, 0.561, 0.561] /* 1up */,
|
||||
};
|
||||
|
||||
Color.Gameboy = {
|
||||
darkest: [229, 107, 26],
|
||||
dark: [229, 189, 26],
|
||||
light: [189, 229, 26],
|
||||
lightest: [107, 229, 26],
|
||||
darkest: [0.898, 0.42, 0.102],
|
||||
dark: [0.898, 0.741, 0.102],
|
||||
light: [0.741, 0.898, 0.102],
|
||||
lightest: [0.42, 0.898, 0.102],
|
||||
};
|
||||
|
||||
Color.Apple = {
|
||||
green: [94, 189, 62],
|
||||
yellow: [255, 185, 0],
|
||||
orange: [247, 130, 0],
|
||||
red: [226, 56, 56],
|
||||
purple: [151, 57, 153],
|
||||
blue: [0, 156, 223],
|
||||
green: [0.369, 0.741, 0.243],
|
||||
yellow: [1, 0.725, 0],
|
||||
orange: [0.969, 0.51, 0],
|
||||
red: [0.886, 0.22, 0.22],
|
||||
purple: [0.592, 0.224, 0.6],
|
||||
blue: [0, 0.612, 0.875],
|
||||
};
|
||||
|
||||
Color.Debug = {
|
||||
boundingbox: Color.white,
|
||||
names: [84, 110, 255],
|
||||
names: [0.329, 0.431, 1],
|
||||
};
|
||||
|
||||
Color.Editor = {
|
||||
grid: [99, 255, 128],
|
||||
select: [255, 255, 55],
|
||||
newgroup: [120, 255, 10],
|
||||
grid: [0.388, 1, 0.502],
|
||||
select: [1, 1, 0.216],
|
||||
newgroup: [0.471, 1, 0.039],
|
||||
};
|
||||
|
||||
/* Detects the format of all colors and munges them into a floating point format */
|
||||
Color.normalize = function (c) {
|
||||
var add_a = function (a) {
|
||||
var n = this.slice();
|
||||
n.a = a;
|
||||
n[3] = a;
|
||||
return n;
|
||||
};
|
||||
|
||||
for (var p of Object.keys(c)) {
|
||||
var fmt = "nrm";
|
||||
if (typeof c[p] !== "object") continue;
|
||||
if (!Array.isArray(c[p])) {
|
||||
Color.normalize(c[p]);
|
||||
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]) {
|
||||
if (color > 1) {
|
||||
fmt = "8b";
|
||||
needs_conversion = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (fmt) {
|
||||
case "8b":
|
||||
// Convert from 0-255 to 0-1 if needed
|
||||
if (needs_conversion) {
|
||||
c[p] = c[p].map(function (x) {
|
||||
return x / 255;
|
||||
});
|
||||
}
|
||||
|
||||
c[p].alpha = add_a;
|
||||
}
|
||||
};
|
||||
@@ -133,53 +138,53 @@ ColorMap.makemap = function (map) {
|
||||
return newmap;
|
||||
};
|
||||
ColorMap.Jet = ColorMap.makemap({
|
||||
0: [0, 0, 131],
|
||||
0.125: [0, 60, 170],
|
||||
0.375: [5, 255, 255],
|
||||
0.625: [255, 255, 0],
|
||||
0.875: [250, 0, 0],
|
||||
1: [128, 0, 0],
|
||||
0: [0, 0, 0.514],
|
||||
0.125: [0, 0.235, 0.667],
|
||||
0.375: [0.02, 1, 1],
|
||||
0.625: [1, 1, 0],
|
||||
0.875: [0.98, 0, 0],
|
||||
1: [0.502, 0, 0],
|
||||
});
|
||||
|
||||
ColorMap.BlueRed = ColorMap.makemap({
|
||||
0: [0, 0, 255],
|
||||
1: [255, 0, 0],
|
||||
0: [0, 0, 1],
|
||||
1: [1, 0, 0],
|
||||
});
|
||||
|
||||
ColorMap.Inferno = ColorMap.makemap({
|
||||
0: [0, 0, 4],
|
||||
0.13: [31, 12, 72],
|
||||
0.25: [85, 15, 109],
|
||||
0.38: [136, 34, 106],
|
||||
0.5: [186, 54, 85],
|
||||
0.63: [227, 89, 51],
|
||||
0.75: [249, 140, 10],
|
||||
0.88: [249, 201, 50],
|
||||
1: [252, 255, 164],
|
||||
0: [0, 0, 0.016],
|
||||
0.13: [0.122, 0.047, 0.282],
|
||||
0.25: [0.333, 0.059, 0.427],
|
||||
0.38: [0.533, 0.133, 0.416],
|
||||
0.5: [0.729, 0.212, 0.333],
|
||||
0.63: [0.89, 0.349, 0.2],
|
||||
0.75: [0.976, 0.549, 0.039],
|
||||
0.88: [0.976, 0.788, 0.196],
|
||||
1: [0.988, 1, 0.643],
|
||||
});
|
||||
|
||||
ColorMap.Bathymetry = ColorMap.makemap({
|
||||
0: [40, 26, 44],
|
||||
0.13: [59.49, 90],
|
||||
0.25: [64, 76, 139],
|
||||
0.38: [63, 110, 151],
|
||||
0.5: [72, 142, 158],
|
||||
0.63: [85, 174, 163],
|
||||
0.75: [120, 206, 163],
|
||||
0.88: [187, 230, 172],
|
||||
1: [253, 254, 204],
|
||||
0: [0.157, 0.102, 0.173],
|
||||
0.13: [0.233, 0.192, 0.353],
|
||||
0.25: [0.251, 0.298, 0.545],
|
||||
0.38: [0.247, 0.431, 0.592],
|
||||
0.5: [0.282, 0.557, 0.62],
|
||||
0.63: [0.333, 0.682, 0.639],
|
||||
0.75: [0.471, 0.808, 0.639],
|
||||
0.88: [0.733, 0.902, 0.675],
|
||||
1: [0.992, 0.996, 0.8],
|
||||
});
|
||||
|
||||
ColorMap.Viridis = ColorMap.makemap({
|
||||
0: [68, 1, 84],
|
||||
0.13: [71, 44, 122],
|
||||
0.25: [59, 81, 139],
|
||||
0.38: [44, 113, 142],
|
||||
0.5: [33, 144, 141],
|
||||
0.63: [39, 173, 129],
|
||||
0.75: [92, 200, 99],
|
||||
0.88: [170, 220, 50],
|
||||
1: [253, 231, 37],
|
||||
0: [0.267, 0.004, 0.329],
|
||||
0.13: [0.278, 0.173, 0.478],
|
||||
0.25: [0.231, 0.318, 0.545],
|
||||
0.38: [0.173, 0.443, 0.557],
|
||||
0.5: [0.129, 0.565, 0.553],
|
||||
0.63: [0.153, 0.678, 0.506],
|
||||
0.75: [0.361, 0.784, 0.388],
|
||||
0.88: [0.667, 0.863, 0.196],
|
||||
1: [0.992, 0.906, 0.145],
|
||||
});
|
||||
|
||||
Color.normalize(ColorMap);
|
||||
@@ -205,8 +210,7 @@ ColorMap.doc = {
|
||||
sample: "Sample a given colormap at the given percentage (0 to 1).",
|
||||
};
|
||||
|
||||
return {
|
||||
Color,
|
||||
esc,
|
||||
ColorMap,
|
||||
};
|
||||
Color.maps = ColorMap
|
||||
Color.utils = esc
|
||||
|
||||
return Color
|
||||
|
||||
@@ -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 util = use('util')
|
||||
var os = use('os')
|
||||
var geometry = use('geometry')
|
||||
var Color = use('color')
|
||||
var color = use('color')
|
||||
|
||||
var draw = {}
|
||||
draw[prosperon.DOC] = `
|
||||
A collection of 2D drawing functions that operate in screen space. Provides primitives
|
||||
for lines, rectangles, text, sprite drawing, etc. Immediate mode.
|
||||
A collection of 2D drawing functions that create drawing command lists.
|
||||
These are pure functions that return plain JavaScript objects representing
|
||||
drawing operations. No rendering or actor communication happens here.
|
||||
`
|
||||
|
||||
// Draw command accumulator
|
||||
var commands = []
|
||||
// Create a new command list
|
||||
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
|
||||
var batch_data = commands.map(function(cmd) {
|
||||
return {
|
||||
op: cmd.op,
|
||||
prop: cmd.prop,
|
||||
value: cmd.value,
|
||||
data: cmd.data
|
||||
}
|
||||
})
|
||||
// Add a command to this list
|
||||
push: function(cmd) {
|
||||
commands.push(cmd)
|
||||
},
|
||||
|
||||
// Send all commands in a single batch message
|
||||
send(renderer_actor, {
|
||||
kind: "renderer",
|
||||
id: renderer_id,
|
||||
op: "batch",
|
||||
data: batch_data
|
||||
})
|
||||
// Get all commands
|
||||
get: function() {
|
||||
return commands
|
||||
},
|
||||
|
||||
// Clear commands after sending
|
||||
// Clear all commands
|
||||
clear: function() {
|
||||
commands = []
|
||||
},
|
||||
|
||||
// Get command count
|
||||
length: function() {
|
||||
return commands.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default command list for convenience
|
||||
var current_list = draw.list()
|
||||
|
||||
// Set the current list
|
||||
draw.set_list = function(list) {
|
||||
current_list = list
|
||||
}
|
||||
|
||||
// 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
|
||||
function add_command(op, data, prop, value) {
|
||||
var cmd = Object.create(command_proto)
|
||||
cmd.op = op
|
||||
if (data) cmd.data = data
|
||||
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})
|
||||
}
|
||||
function add_command(type, data) {
|
||||
var cmd = {cmd: type}
|
||||
Object.assign(cmd, data)
|
||||
current_list.push(cmd)
|
||||
}
|
||||
|
||||
// Default geometry definitions
|
||||
var ellipse_def = {
|
||||
color: Color.white,
|
||||
start: 0,
|
||||
end: 1,
|
||||
mode: 'fill',
|
||||
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 = {
|
||||
color: Color.white,
|
||||
thickness: 1,
|
||||
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 = {
|
||||
thickness:1,
|
||||
color: Color.white,
|
||||
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 = {
|
||||
tile_top:true,
|
||||
@@ -441,145 +90,152 @@ var slice9_info = {
|
||||
tile_right:true,
|
||||
tile_center_x:true,
|
||||
tile_center_right:true,
|
||||
color: Color.white,
|
||||
}
|
||||
|
||||
draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info, pipeline) {
|
||||
if (!image) throw Error('Need an image to render.')
|
||||
if (typeof image === "string")
|
||||
image = graphics.texture(image)
|
||||
var image_info = {
|
||||
tile_x: false,
|
||||
tile_y: false,
|
||||
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] = `
|
||||
:param image: An image object or string path to a texture.
|
||||
: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 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
|
||||
:raises Error: If no image is provided.
|
||||
`
|
||||
|
||||
var std_sprite_cmd = {type:'sprite', color:[1,1,1,1]}
|
||||
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) {
|
||||
draw.image = function image(image, rect = [0,0], rotation = 0, anchor = [0,0], shear = [0,0], info = {}, material) {
|
||||
if (!image) throw Error('Need an image to render.')
|
||||
if (typeof image === "string")
|
||||
image = graphics.texture(image)
|
||||
|
||||
// Ensure rect has proper structure
|
||||
if (Array.isArray(rect)) {
|
||||
rect = {x: rect[0], y: rect[1], width: image.width, height: image.height}
|
||||
} else {
|
||||
rect.width ??= image.width
|
||||
rect.height ??= image.height
|
||||
rect = {x: rect[0], y: rect[1], width: 100, height: 100} // Default size
|
||||
}
|
||||
|
||||
info = Object.assign({}, image_info, info);
|
||||
|
||||
// Get the GPU texture (might be loading)
|
||||
var texture = image.gpu;
|
||||
if (!texture) {
|
||||
// Texture not loaded yet, skip drawing
|
||||
return;
|
||||
}
|
||||
|
||||
// Set texture filtering mode
|
||||
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
|
||||
add_command("draw_image", {
|
||||
image: image,
|
||||
rect: rect,
|
||||
rotation: rotation,
|
||||
anchor: anchor,
|
||||
shear: shear,
|
||||
info: info,
|
||||
material: material
|
||||
})
|
||||
}
|
||||
|
||||
function software_circle(pos, radius)
|
||||
{
|
||||
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++
|
||||
}
|
||||
draw.circle = function render_circle(pos, radius, def, material) {
|
||||
draw.ellipse(pos, [radius,radius], def, material)
|
||||
}
|
||||
|
||||
var circle_def = {
|
||||
inner_radius:1, // percentage: 1 means filled circle
|
||||
color: Color.white,
|
||||
start:0,
|
||||
end: 1,
|
||||
}
|
||||
draw.circle = function render_circle(pos, radius, def, pipeline) {
|
||||
draw.ellipse(pos, [radius,radius], def, pipeline)
|
||||
}
|
||||
|
||||
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 = function text(text, rect, font = 'fonts/c64.ttf', size = 8, color = color.white, wrap = 0) {
|
||||
add_command("draw_text", {
|
||||
text: text,
|
||||
rect: rect,
|
||||
font: font,
|
||||
size: size,
|
||||
wrap: wrap,
|
||||
material: {color}
|
||||
})
|
||||
}
|
||||
draw.text[prosperon.DOC] = `
|
||||
:param text: The string to draw.
|
||||
:param rect: A rectangle specifying draw position (and possibly wrapping area).
|
||||
:param font: A font object or string path, default sysfont.
|
||||
:param size: (Unused) Possibly intended for scaling the font size.
|
||||
:param color: The text color, default Color.white.
|
||||
:param size: Font size in pixels.
|
||||
:param color: The text color, default color.white.
|
||||
: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
|
||||
`
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var Color = use('color')
|
||||
var color = use('color')
|
||||
var os = use('os')
|
||||
var graphics = use('graphics')
|
||||
var transform = use('transform')
|
||||
@@ -104,7 +104,7 @@ ex.scale = 1
|
||||
ex.grow_for = 0
|
||||
ex.spawn_timer = 0
|
||||
ex.pps = 0
|
||||
ex.color = Color.white
|
||||
ex.color = color.white
|
||||
|
||||
ex.draw = function()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
var graphics = use('graphics')
|
||||
var color = use('color')
|
||||
|
||||
var sprite = {
|
||||
image: undefined,
|
||||
@@ -223,7 +224,7 @@ return sprite;
|
||||
|
||||
---
|
||||
|
||||
var Color = use('color')
|
||||
var color = use('color')
|
||||
var transform = use('transform')
|
||||
var sprite = use('sprite')
|
||||
|
||||
@@ -234,7 +235,7 @@ if (this.overling.transform)
|
||||
this.transform.change_hook = $.t_hook;
|
||||
var msp = new sprite
|
||||
this._sprite = msp;
|
||||
msp.color = Color.white;
|
||||
msp.color = color.white;
|
||||
this.transform.sprite = this
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
var imgui = this;
|
||||
|
||||
var color = use('color')
|
||||
|
||||
var debug = {}
|
||||
|
||||
var imdebug = function imdebug() {
|
||||
|
||||
223
scripts/modules/rasterize.js
Normal file
223
scripts/modules/rasterize.js
Normal 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
|
||||
@@ -7,6 +7,7 @@ var tracy = use('tracy')
|
||||
var graphics = use('graphics')
|
||||
var imgui = use('imgui')
|
||||
var transform = use('transform')
|
||||
var color = use('color')
|
||||
|
||||
var base_pipeline = {
|
||||
vertex: "sprite.vert",
|
||||
@@ -666,7 +667,7 @@ function mask(image, pos, scale, rotation = 0, ref = 1) {
|
||||
render.use_mat({
|
||||
diffuse:image.texture,
|
||||
rect: image.rect,
|
||||
shade: Color.white
|
||||
shade: color.white
|
||||
});
|
||||
render.draw(shape.quad);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var render = use('render')
|
||||
var os = use('os')
|
||||
var transform = use('transform')
|
||||
var color = use('color')
|
||||
|
||||
render.initialize({
|
||||
width:500,
|
||||
@@ -61,9 +62,9 @@ function loop()
|
||||
draw.point([100,100])
|
||||
draw.circle([200,200],40)
|
||||
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([100,80],[40,25], {thickness:10, color:Color.green})
|
||||
draw.ellipse([100,80], [40,25], {thickness:1,color:Color.blue})
|
||||
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:1,color:color.blue})
|
||||
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:350, y:60, width:200, height:120}, {radius:10,thickness:3})
|
||||
|
||||
@@ -60,12 +60,7 @@ function start_drawing() {
|
||||
var frame = 0;
|
||||
var start_time = os.now();
|
||||
|
||||
// Load an image
|
||||
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})
|
||||
})
|
||||
// Note: Image loading would be handled by moth in real implementation
|
||||
|
||||
function draw_frame() {
|
||||
frame++;
|
||||
@@ -183,6 +178,7 @@ function start_drawing() {
|
||||
draw2d.point(
|
||||
[px, py],
|
||||
3,
|
||||
{},
|
||||
{color: [1, 0.5 + Math.sin(t * 2 + i) * 0.5, 0.5, 1]}
|
||||
);
|
||||
}
|
||||
|
||||
220
tests/moth.js
Normal file
220
tests/moth.js
Normal 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
62
tests/prosperon.js
Normal 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())
|
||||
})
|
||||
Reference in New Issue
Block a user