refactor draw2d and render
Some checks failed
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Some checks failed
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
This commit is contained in:
@@ -16,9 +16,14 @@ whiteimage.surface = graphics.make_surface([1,1])
|
||||
whiteimage.surface.rect({x:0,y:0,width:1,height:1}, [1,1,1,1])
|
||||
render.load_texture(whiteimage)
|
||||
|
||||
draw.point = function (pos, size, color = Color.blue) {
|
||||
draw.rectangle({x:pos.x,y:pos.y, width:size,height:size})
|
||||
}
|
||||
if (render.point)
|
||||
draw.point = function(pos,size,opt = {color:Color.white}, pipeline) {
|
||||
render.settings(opt)
|
||||
render.pipeline(pipeline)
|
||||
render.point([pos])
|
||||
}
|
||||
else
|
||||
draw.point = function() { throw new Error('Backend cannot draw points.') }
|
||||
draw.point[prosperon.DOC] = `
|
||||
:param pos: A 2D position ([x, y]) where the point should be drawn.
|
||||
:param size: The size of the point.
|
||||
@@ -26,100 +31,347 @@ draw.point[prosperon.DOC] = `
|
||||
:return: None
|
||||
`
|
||||
|
||||
draw.line = function render_line(points, color = Color.white, thickness = 1, pipeline) {
|
||||
var mesh = graphics.make_line_prim(points, thickness, 0, 0, color)
|
||||
render.queue({
|
||||
type: 'geometry',
|
||||
mesh,
|
||||
pipeline,
|
||||
first_index: 0,
|
||||
num_indices: mesh.num_indices,
|
||||
image:whiteimage
|
||||
})
|
||||
}
|
||||
draw.line[prosperon.DOC] = `
|
||||
:param points: An array of 2D positions representing the line vertices.
|
||||
:param color: The color of the line, default Color.white.
|
||||
:param thickness: The line thickness, default 1.
|
||||
:param pipeline: (Optional) A pipeline or rendering state object.
|
||||
:return: None
|
||||
`
|
||||
/* ------------------------------------------------------------------------
|
||||
* helper – is (dx,dy) inside the desired wedge?
|
||||
* -------------------------------------------------------------------- */
|
||||
function within_wedge(dx, dy, start, end, full_circle)
|
||||
{
|
||||
if (full_circle) return true
|
||||
|
||||
draw.cross = function render_cross(pos, size, color = Color.red, thickness = 1, pipe) {
|
||||
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) render.point(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) render.rects(strips)
|
||||
}
|
||||
|
||||
var ellipse_def = {
|
||||
color: Color.white,
|
||||
start: 0,
|
||||
end: 1,
|
||||
mode: 'fill',
|
||||
thickness: 1,
|
||||
}
|
||||
|
||||
if (render.ellipse)
|
||||
draw.ellipse = function(pos, radii, def, pipeline) {
|
||||
}
|
||||
else
|
||||
draw.ellipse = function(pos, radii, def, pipeline) {
|
||||
var opt = def ? {...ellipse_def, ...def} : ellipse_def
|
||||
render.settings(opt)
|
||||
render.pipeline(pipeline)
|
||||
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
|
||||
|
||||
render.settings(opt)
|
||||
render.pipeline(pipeline)
|
||||
render.line(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, color, thickness)
|
||||
draw.line(b, color, thickness)
|
||||
draw.line(a, def, pipe)
|
||||
draw.line(b, def,pipe)
|
||||
}
|
||||
draw.cross[prosperon.DOC] = `
|
||||
:param pos: The center of the cross as a 2D position ([x, y]).
|
||||
:param size: Half the size of each cross arm.
|
||||
:param color: The color of the cross, default Color.red.
|
||||
:param thickness: The thickness of each line, default 1.
|
||||
:param pipe: (Optional) A pipeline or rendering state object.
|
||||
:return: None
|
||||
`
|
||||
|
||||
draw.arrow = function render_arrow(start, end, color = Color.red, wingspan = 4, wingangle = 10, 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], color)
|
||||
draw.line(wing1, color)
|
||||
draw.line(wing2, color)
|
||||
draw.line([start, end], def, pipe)
|
||||
draw.line(wing1, def, pipe)
|
||||
draw.line(wing2, def, pipe)
|
||||
}
|
||||
draw.arrow[prosperon.DOC] = `
|
||||
:param start: The start position of the arrow ([x, y]).
|
||||
:param end: The end (tip) position of the arrow ([x, y]).
|
||||
:param color: The color, default Color.red.
|
||||
:param wingspan: The length of each arrowhead 'wing', default 4.
|
||||
:param wingangle: Wing rotation in degrees, default 10.
|
||||
:param pipe: (Optional) A pipeline or rendering state object.
|
||||
:return: None
|
||||
`
|
||||
|
||||
draw.rectangle = function render_rectangle(rect, color = Color.white, pipeline) {
|
||||
render.queue({
|
||||
type: 'rectangle',
|
||||
material: {
|
||||
color,
|
||||
},
|
||||
rect,
|
||||
pipeline,
|
||||
})
|
||||
/* ------------------------------------------------------------------------
|
||||
* helper – plain rectangle outline of arbitrary thickness (radius=0)
|
||||
* -------------------------------------------------------------------- */
|
||||
function software_outline_rect(rect, thickness)
|
||||
{
|
||||
if (thickness <= 0) {
|
||||
render.rectangle(rect);
|
||||
return;
|
||||
}
|
||||
|
||||
/* stroke swallows the whole thing → fill instead */
|
||||
if ((thickness << 1) >= rect.width ||
|
||||
(thickness << 1) >= rect.height) {
|
||||
render.rectangle(rect) // filled
|
||||
return
|
||||
}
|
||||
|
||||
const x0 = rect.x,
|
||||
y0 = rect.y,
|
||||
x1 = rect.x + rect.width,
|
||||
y1 = rect.y + rect.height
|
||||
|
||||
render.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
|
||||
])
|
||||
}
|
||||
draw.rectangle[prosperon.DOC] = `
|
||||
:param rect: A rectangle object with {x, y, width, height}.
|
||||
:param color: The fill color, default Color.white.
|
||||
:param pipeline: (Optional) A pipeline or rendering state object.
|
||||
:return: None
|
||||
`
|
||||
|
||||
var tile_def = {repeat_x:true, repeat_y:true}
|
||||
/* ------------------------------------------------------------------------
|
||||
* 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
|
||||
}
|
||||
|
||||
draw.tile = function(image, rect, color = Color.white, tile = tile_def, pipeline) {
|
||||
if (!image) throw Error('Need an image to render.')
|
||||
if (typeof image === "string")
|
||||
image = graphics.texture(image)
|
||||
var mesh = geometry.tile(image.texture, {x:0,y:0,width:image.texture.width,height:image.texture.height}, rect, tile)
|
||||
render.queue({
|
||||
type:'geometry',
|
||||
mesh,
|
||||
image,
|
||||
pipeline,
|
||||
first_index:0,
|
||||
num_indices:mesh.num_indices
|
||||
})
|
||||
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) ------------------------- */
|
||||
render.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
|
||||
)
|
||||
}
|
||||
|
||||
render.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 */
|
||||
render.rects([
|
||||
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
|
||||
height:rect.height }
|
||||
])
|
||||
|
||||
/* side columns */
|
||||
render.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 }
|
||||
)
|
||||
}
|
||||
|
||||
render.rects(caps)
|
||||
}
|
||||
|
||||
var rect_def = {
|
||||
thickness:1,
|
||||
mode: 'fill',
|
||||
color: Color.white,
|
||||
radius: 0,
|
||||
thickness:1
|
||||
}
|
||||
draw.rectangle = function render_rectangle(rect, def, pipeline) {
|
||||
var opt = def ? {...rect_def, ...def} : rect_def
|
||||
render.settings(opt)
|
||||
render.pipeline(pipeline)
|
||||
|
||||
var t = opt.thickness|0
|
||||
|
||||
if (t <= 0) {
|
||||
if (opt.radius)
|
||||
software_fill_round_rect(rect, opt.radius)
|
||||
else
|
||||
render.rectangle(rect)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (opt.radius)
|
||||
software_round_rect(rect, opt.radius, t)
|
||||
else
|
||||
software_outline_rect(rect,t)
|
||||
}
|
||||
draw.tile[prosperon.DOC] = `
|
||||
:param image: An image object or string path to a texture.
|
||||
:param rect: A rectangle specifying draw location/size ({x, y, width, height}).
|
||||
:param color: The color tint, default Color.white.
|
||||
:param tile: A tiling definition ({repeat_x, repeat_y}), default tile_def.
|
||||
:param pipeline: (Optional) A pipeline or rendering state object.
|
||||
:return: None
|
||||
:raises Error: If no image is provided.
|
||||
`
|
||||
|
||||
var slice9_info = {
|
||||
tile_top:true,
|
||||
@@ -127,10 +379,11 @@ var slice9_info = {
|
||||
tile_left:true,
|
||||
tile_right: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, color = Color.white, info = slice9_info, pipeline) {
|
||||
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)
|
||||
@@ -148,7 +401,6 @@ 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 color: The color tint, default Color.white.
|
||||
:param info: A slice9 info object controlling tiling of edges/corners.
|
||||
:param pipeline: (Optional) A pipeline or rendering state object.
|
||||
:return: None
|
||||
@@ -156,33 +408,24 @@ draw.slice9[prosperon.DOC] = `
|
||||
`
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
draw.image = function image(image, rect = [0,0], rotation = 0, color, pipeline) {
|
||||
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 (typeof image === "string")
|
||||
image = graphics.texture(image)
|
||||
rect.width ??= image.texture.width
|
||||
rect.height ??= image.texture.height
|
||||
var cmd = Object.create(std_sprite_cmd)
|
||||
cmd.image = image
|
||||
cmd.rect = rect
|
||||
if (pipeline) cmd.pipeline = pipeline
|
||||
if (color) cmd.color = color
|
||||
render.queue(cmd)
|
||||
var sprite = graphics.make_sprite()
|
||||
sprite.set_image(image)
|
||||
sprite.set_rect(rect)
|
||||
return sprite
|
||||
info ??= image_info;
|
||||
render.settings(info)
|
||||
render.image(image, rect, rotation, anchor, shear, info)
|
||||
}
|
||||
draw.image[prosperon.DOC] = `
|
||||
:param image: An image object or string path to a texture.
|
||||
:param rect: A rectangle specifying draw location/size, default [0,0]; width/height default to image size.
|
||||
:param rotation: Rotation in degrees (not currently used).
|
||||
:param color: The color tint, default none.
|
||||
:param pipeline: (Optional) A pipeline or rendering state object.
|
||||
:return: A sprite object that was created for this draw call.
|
||||
:raises Error: If no image is provided.
|
||||
`
|
||||
|
||||
draw.images = function images(image, rects, config) {
|
||||
if (!image) throw Error('Need an image to render.')
|
||||
@@ -223,17 +466,40 @@ draw.sprites[prosperon.DOC] = `
|
||||
:return: None
|
||||
`
|
||||
|
||||
draw.circle = function render_circle(pos, radius, color, inner_radius = 1, pipeline) {
|
||||
draw.rectangle({x:pos.x, y:pos.y, width:radius*2,height:radius*2}, color, circle_pipeline)
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
draw.circle[prosperon.DOC] = `
|
||||
:param pos: Center of the circle ([x, y]).
|
||||
:param radius: The circle radius.
|
||||
:param color: The fill color of the circle, default none.
|
||||
:param inner_radius: (Unused) Possibly ring thickness, default 1.
|
||||
:param pipeline: (Optional) A pipeline or rendering state object.
|
||||
:return: None
|
||||
`
|
||||
|
||||
var sysfont = graphics.get_font('fonts/c64.ttf', 8)
|
||||
|
||||
|
||||
@@ -15,6 +15,22 @@ function calc_image_size(img) {
|
||||
return [img.texture.width * img.rect.width, img.texture.height * img.rect.height]
|
||||
}
|
||||
|
||||
function decorate_rect_px(img) {
|
||||
// needs a GPU texture to measure
|
||||
if (!img || !img.texture) return
|
||||
|
||||
// default UV rect is the whole image if none supplied
|
||||
img.rect ??= {x:0, y:0, width:1, height:1} // [u0,v0,uw,vh] in 0-1
|
||||
|
||||
// store pixel-space version: [x, y, w, h] in texels
|
||||
img.rect_px = {
|
||||
x:Math.round(img.rect.x * img.texture.width),
|
||||
y:Math.round(img.rect.y * img.texture.height),
|
||||
width:Math.round(img.rect.width * img.texture.width),
|
||||
height:Math.round(img.rect.height * img.texture.height)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Internally loads image data from disk and prepares a GPU texture. Used by graphics.texture().
|
||||
Not intended for direct user calls.
|
||||
@@ -27,24 +43,32 @@ function create_image(path) {
|
||||
switch (path.ext()) {
|
||||
case 'gif':
|
||||
newimg = graphics.make_gif(data);
|
||||
if (newimg.surface)
|
||||
if (newimg.surface) {
|
||||
render.load_texture(newimg)
|
||||
decorate_rect_px(newimg)
|
||||
}
|
||||
else {
|
||||
for (var frame of newimg.frames)
|
||||
for (var frame of newimg.frames) {
|
||||
render.load_texture(frame)
|
||||
decorate_rect_px(frame)
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ase':
|
||||
case 'aseprite':
|
||||
newimg = graphics.make_aseprite(data);
|
||||
if (newimg.surface)
|
||||
if (newimg.surface) {
|
||||
render.load_texture(newimg)
|
||||
decorate_rect_px(newimg)
|
||||
}
|
||||
else {
|
||||
for (var anim in newimg) {
|
||||
var a = newimg[anim];
|
||||
for (var frame of a.frames)
|
||||
for (var frame of a.frames) {
|
||||
render.load_texture(frame)
|
||||
decorate_rect_px(frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -54,6 +78,7 @@ function create_image(path) {
|
||||
surface: graphics.make_texture(data)
|
||||
};
|
||||
render.load_texture(newimg)
|
||||
decorate_rect_px(newimg)
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ render.initialize = function(config)
|
||||
context = prosperon.window.make_renderer()
|
||||
}
|
||||
|
||||
render.sprite = function(sprite)
|
||||
{
|
||||
|
||||
context.sprite(sprite)
|
||||
}
|
||||
|
||||
// img here is the engine surface
|
||||
render.load_texture = function(img)
|
||||
{
|
||||
@@ -48,15 +54,11 @@ render.load_texture = function(img)
|
||||
|
||||
var current_color = Color.white
|
||||
|
||||
render.image = function(image, rect)
|
||||
render.image = function(image, rect, rotation, anchor, shear, info)
|
||||
{
|
||||
if (typeof image === 'string')
|
||||
image = graphics.texture(image)
|
||||
|
||||
rect.width ??= image.texture.width
|
||||
rect.height ??= image.texture.height
|
||||
var T =
|
||||
context.texture(image.texture, rect);
|
||||
rect.width = image.rect_px.width;
|
||||
rect.height = image.rect_px.height;
|
||||
context.texture(image.texture, image.rect_px, rect, rotation * 360, anchor);
|
||||
}
|
||||
|
||||
render.clip = function(rect)
|
||||
@@ -64,228 +66,37 @@ render.clip = function(rect)
|
||||
context.clip(rect)
|
||||
}
|
||||
|
||||
render.circle = function(pos, radius, color = Color.white)
|
||||
render.line = function(points)
|
||||
{
|
||||
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
|
||||
|
||||
context.draw_color(color)
|
||||
|
||||
while (x <= y) {
|
||||
context.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++
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------*
|
||||
* Rounded-rect outline – any stroke width ≥ 1 *
|
||||
*------------------------------------------------------------*/
|
||||
render.round_rect = function(rect, radius, line_width = 1,
|
||||
color = Color.white)
|
||||
{
|
||||
if (line_width <= 0) return
|
||||
|
||||
radius = Math.min(radius, rect.width >> 1, rect.height >> 1)
|
||||
if (radius <= 0) { render.rectangle(rect, color); return }
|
||||
|
||||
const x0 = rect.x,
|
||||
y0 = rect.y,
|
||||
x1 = rect.x + rect.width - 1, // inclusive
|
||||
y1 = rect.y + rect.height - 1
|
||||
|
||||
/* fast-path: stroke swallows the whole thing → just fill */
|
||||
if ((line_width << 1) >= rect.width ||
|
||||
(line_width << 1) >= rect.height) {
|
||||
render.fill_round_rect(rect, radius, color)
|
||||
return
|
||||
}
|
||||
|
||||
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 = Math.max(radius - line_width, 0)
|
||||
|
||||
context.draw_color(color)
|
||||
|
||||
/* straight bands ---------------------------------------------------------*/
|
||||
context.rects([
|
||||
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
|
||||
height:line_width }, // top
|
||||
{ x:x0 + radius, y:y1 - line_width + 1,
|
||||
width:rect.width - (radius << 1), height:line_width }, // bottom
|
||||
{ x:x0, y:y0 + radius, width:line_width,
|
||||
height:rect.height - (radius << 1) }, // left
|
||||
{ x:x1 - line_width + 1, y:y0 + radius, width:line_width,
|
||||
height:rect.height - (radius << 1) } // right
|
||||
])
|
||||
|
||||
/* corner arcs ------------------------------------------------------------*/
|
||||
const strips = [] // batch for speed
|
||||
|
||||
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 && 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
|
||||
)
|
||||
}
|
||||
|
||||
context.rects(strips)
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------*
|
||||
* Filled rounded-rect *
|
||||
*------------------------------------------------------------*/
|
||||
render.fill_round_rect = function(rect, radius,
|
||||
color = Color.white)
|
||||
{
|
||||
radius = Math.min(radius, rect.width >> 1, rect.height >> 1)
|
||||
if (radius <= 0) { context.draw_color(color); context.rects([rect]); return }
|
||||
|
||||
const x0 = rect.x,
|
||||
y0 = rect.y,
|
||||
x1 = rect.x + rect.width - 1,
|
||||
y1 = rect.y + rect.height - 1
|
||||
|
||||
context.draw_color(color)
|
||||
|
||||
/* main rectangle column (everything between the caps) ----*/
|
||||
context.rects([
|
||||
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
|
||||
height:rect.height }
|
||||
])
|
||||
|
||||
/* side columns (left & right) -----------------------------*/
|
||||
context.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
|
||||
|
||||
/* top */
|
||||
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 }
|
||||
)
|
||||
|
||||
/* bottom */
|
||||
caps.push(
|
||||
{ x:cx_l - dx, y:cy_b + dy, width:w, height:1 },
|
||||
{ x:cx_r - dx, y:cy_b + dy, width:w, height:1 }
|
||||
)
|
||||
}
|
||||
|
||||
context.rects(caps)
|
||||
}
|
||||
|
||||
render.ellipse = function(pos, radiuses, color = Color.white)
|
||||
{
|
||||
var rx = radiuses[0], ry = radiuses[1]
|
||||
if (rx <= 0 || ry <= 0) return // nothing to draw
|
||||
|
||||
var cx = pos[0], cy = pos[1]
|
||||
|
||||
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
|
||||
var px = 0, py = two_rx_sq * y
|
||||
|
||||
context.draw_color(color)
|
||||
|
||||
// Region 1
|
||||
var p = ry_sq - rx_sq * ry + (0.25 * rx_sq)
|
||||
while (px < py) {
|
||||
context.point([
|
||||
[cx + x, cy + y], [cx - x, cy + y],
|
||||
[cx + x, cy - y], [cx - x, cy - y]
|
||||
])
|
||||
|
||||
x++
|
||||
px += two_ry_sq
|
||||
if (p < 0) p += ry_sq + px
|
||||
else {
|
||||
y--
|
||||
py -= two_rx_sq
|
||||
p += ry_sq + px - py
|
||||
}
|
||||
}
|
||||
|
||||
// Region 2
|
||||
p = ry_sq * (x + 0.5) * (x + 0.5) + rx_sq * (y - 1) * (y - 1) - rx_sq * ry_sq
|
||||
while (y >= 0) {
|
||||
context.point([
|
||||
[cx + x, cy + y], [cx - x, cy + y],
|
||||
[cx + x, cy - y], [cx - x, cy - y]
|
||||
])
|
||||
|
||||
y--
|
||||
py -= two_rx_sq
|
||||
if (p > 0) p += rx_sq - py
|
||||
else {
|
||||
x++
|
||||
px += two_ry_sq
|
||||
p += rx_sq - py + px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render.line = function(points, color = Color.white)
|
||||
{
|
||||
context.draw_color(color)
|
||||
context.line(points)
|
||||
}
|
||||
|
||||
render.point = function(pos, color = Color.white)
|
||||
render.point = function(pos)
|
||||
{
|
||||
context.draw_color(color)
|
||||
context.point([pos])
|
||||
context.point(pos)
|
||||
}
|
||||
|
||||
render.rectangle = function(rect, color = Color.white)
|
||||
render.rectangle = function(rect)
|
||||
{
|
||||
context.draw_color(color)
|
||||
context.rects([rect])
|
||||
}
|
||||
|
||||
render.rects = function(rects)
|
||||
{
|
||||
context.rects(rects)
|
||||
}
|
||||
|
||||
render.pipeline = function(pipe)
|
||||
{
|
||||
// any changes here
|
||||
}
|
||||
|
||||
render.settings = function(set)
|
||||
{
|
||||
if (!set.color) return
|
||||
context.draw_color(set.color)
|
||||
}
|
||||
|
||||
render.geometry = function(mesh, pipeline)
|
||||
{
|
||||
|
||||
|
||||
@@ -3056,39 +3056,26 @@ JSC_CCALL(renderer_texture_9grid,
|
||||
|
||||
JSC_CCALL(renderer_texture,
|
||||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||||
SDL_Renderer *r = ctx->sdl;
|
||||
|
||||
SDL_Texture *tex = js2SDL_Texture(js, argv[0]);
|
||||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||||
rect src = js2rect(js,argv[1]);
|
||||
transform *xf = js2transform (js, argv[2]);
|
||||
HMM_Vec2 k = js2vec2 (js, argv[3]); /* default (0,0) */
|
||||
rect dst = js2rect(js,argv[2]);
|
||||
double angle;
|
||||
JS_ToFloat64(js, &angle, argv[3]);
|
||||
HMM_Vec2 anchor = js2vec2(js, argv[4]);
|
||||
SDL_RenderTextureRotated(ctx->sdl, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE);
|
||||
)
|
||||
|
||||
HMM_Mat3 m3 = transform2mat3(xf); /* includes rot & scale */
|
||||
JSC_CCALL(renderer_sprite,
|
||||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||||
sprite *sp = js2sprite(js,argv[0]);
|
||||
|
||||
HMM_Vec2 right = mat_right(m3); /* local +X in world */
|
||||
HMM_Vec2 up = mat_up (m3); /* local +Y in world (Y-up)*/
|
||||
SDL_Texture *tex;
|
||||
JS_GETATOM(js, tex, sp->image, texture, SDL_Texture);
|
||||
|
||||
HMM_Vec2 r2 = HMM_AddV2(right, HMM_MulV2F(up, k.x));
|
||||
HMM_Vec2 u2 = HMM_AddV2(up, HMM_MulV2F(right,k.y));
|
||||
|
||||
r2 = HMM_MulV2F(r2, src.w);
|
||||
u2 = HMM_MulV2F(u2, src.h);
|
||||
|
||||
HMM_Vec2 origin_w = { xf->pos.x, xf->pos.y };
|
||||
HMM_Vec2 right_w = HMM_AddV2(origin_w, r2);
|
||||
HMM_Vec2 down_w = HMM_AddV2(origin_w, u2); /* SDL wants “down” */
|
||||
|
||||
SDL_FPoint origin = renderer_world_to_screen(ctx, origin_w);
|
||||
SDL_FPoint rightp = renderer_world_to_screen(ctx, right_w);
|
||||
SDL_FPoint downp = renderer_world_to_screen(ctx, down_w);
|
||||
|
||||
if (!SDL_RenderTextureAffine(r, tex,
|
||||
&src, /* full image */
|
||||
&origin,
|
||||
&rightp,
|
||||
&downp))
|
||||
return JS_ThrowReferenceError(js,
|
||||
"SDL_RenderTextureAffine: %s", SDL_GetError());
|
||||
// rect dst = renderer_worldrect_to_screen(ctx, sp->affine);
|
||||
rect dst = sp->affine;
|
||||
rect uv = {x:0,y:0,w:48,h:24};
|
||||
SDL_RenderTexture(ctx->sdl, tex, &uv, &dst);
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_renderer_ctx_funcs[] = {
|
||||
@@ -3097,9 +3084,10 @@ static const JSCFunctionListEntry js_renderer_ctx_funcs[] = {
|
||||
MIST_FUNC_DEF(SDL_Renderer, clear, 0),
|
||||
MIST_FUNC_DEF(renderer, line, 1),
|
||||
MIST_FUNC_DEF(renderer, point, 1),
|
||||
MIST_FUNC_DEF(renderer, texture, 2),
|
||||
MIST_FUNC_DEF(renderer, texture, 5),
|
||||
MIST_FUNC_DEF(renderer, rects, 1),
|
||||
MIST_FUNC_DEF(renderer, geometry, 2),
|
||||
MIST_FUNC_DEF(renderer, sprite, 1),
|
||||
MIST_FUNC_DEF(renderer, load_texture, 1),
|
||||
MIST_FUNC_DEF(renderer, get_image, 1),
|
||||
|
||||
|
||||
@@ -39,19 +39,19 @@ function loop()
|
||||
{
|
||||
render.clear(Color.red)
|
||||
render.camera(hudcam)
|
||||
render.line([[0,0],[100,50]])
|
||||
render.point([100,100])
|
||||
render.circle([200,200],40)
|
||||
render.ellipse([300,300],[20,40])
|
||||
render.rectangle({x:150,y:150,width:50,height:50})
|
||||
render.round_rect({x:100, y:60, width:200, height:60}, 20, 2)
|
||||
render.fill_round_rect({x:350, y:60, width:200, height:120}, 10)
|
||||
//render.image("button_grey", [100,100])
|
||||
// draw.rectangle({x:50,y:-50,width:50,height:50})
|
||||
/* draw.line([[0,0],[100,50]])
|
||||
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.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})*/
|
||||
draw.image("button_grey", [100,100],0.25)
|
||||
render.present()
|
||||
$_.delay(loop, 1/60)
|
||||
}
|
||||
|
||||
loop()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user