draw textures with draw2d
Some checks failed
Build and Deploy / build-macos (push) Failing after 6s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Some checks failed
Build and Deploy / build-macos (push) Failing after 6s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
This commit is contained in:
@@ -348,6 +348,22 @@ function handle_renderer(msg) {
|
||||
);
|
||||
return {success: true};
|
||||
|
||||
case 'copyTexture':
|
||||
if (!msg.data) return {error: "Missing texture data"};
|
||||
var tex_id = msg.data.texture_id;
|
||||
if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"};
|
||||
var tex = resources.texture[tex_id];
|
||||
|
||||
// Use the texture method with normalized coordinates
|
||||
ren.texture(
|
||||
tex,
|
||||
msg.data.src || {x:0, y:0, width:tex.width, height:tex.height},
|
||||
msg.data.dest || {x:0, y:0, width:tex.width, height:tex.height},
|
||||
0, // No rotation
|
||||
{x:0, y:0} // Top-left anchor
|
||||
);
|
||||
return {success: true};
|
||||
|
||||
case 'sprite':
|
||||
if (!msg.data || !msg.data.sprite) return {error: "Missing sprite data"};
|
||||
ren.sprite(msg.data.sprite);
|
||||
@@ -404,13 +420,35 @@ function handle_renderer(msg) {
|
||||
return {id: surf_id};
|
||||
|
||||
case 'loadTexture':
|
||||
if (!msg.data || !msg.data.surface_id) return {error: "Missing surface_id"};
|
||||
if (!msg.data) return {error: "Missing data"};
|
||||
|
||||
var tex;
|
||||
// Load from surface ID
|
||||
if (msg.data.surface_id) {
|
||||
var surf = resources.surface[msg.data.surface_id];
|
||||
if (!surf) return {error: "Invalid surface id"};
|
||||
var tex = ren.load_texture(surf);
|
||||
tex = ren.load_texture(surf);
|
||||
}
|
||||
// Load from raw surface object (for graphics module)
|
||||
else if (msg.data.surface) {
|
||||
tex = ren.load_texture(msg.data);
|
||||
}
|
||||
// Direct surface data
|
||||
else if (msg.data.width && msg.data.height) {
|
||||
tex = ren.load_texture(msg.data);
|
||||
}
|
||||
else {
|
||||
return {error: "Must provide surface_id or surface data"};
|
||||
}
|
||||
|
||||
if (!tex) return {error: "Failed to load texture"};
|
||||
var tex_id = allocate_id();
|
||||
resources.texture[tex_id] = tex;
|
||||
return {id: tex_id};
|
||||
return {
|
||||
id: tex_id,
|
||||
width: tex.width,
|
||||
height: tex.height
|
||||
};
|
||||
|
||||
case 'createTexture':
|
||||
if (!msg.data || !msg.data.width || !msg.data.height) {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
(function engine() {
|
||||
prosperon.DOC = Symbol('+documentation+') // Symbol for documentation references
|
||||
|
||||
globalThis.log = new Proxy({}, {
|
||||
get(target,prop,receiver) {
|
||||
return function() {}
|
||||
}
|
||||
})
|
||||
|
||||
var listeners = new Map()
|
||||
|
||||
prosperon.on = function(type, callback) {
|
||||
@@ -672,7 +678,7 @@ function handle_message(msg) {
|
||||
delete msg.data
|
||||
letter[HEADER] = msg
|
||||
if (msg.return) {
|
||||
console.log(`Received a message for the return id ${msg.return}`)
|
||||
log.trace(`Received a message for the return id ${msg.return}`)
|
||||
var fn = replies[msg.return]
|
||||
if (!fn) throw new Error(`Could not find return function for message ${msg.return}`)
|
||||
fn(letter)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
var graphics = use('graphics')
|
||||
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 draw = {}
|
||||
draw[prosperon.DOC] = `
|
||||
@@ -13,23 +17,10 @@ for lines, rectangles, text, sprite drawing, etc. Immediate mode.
|
||||
// Draw command accumulator
|
||||
var commands = []
|
||||
|
||||
// Renderer info set by moth
|
||||
var renderer_actor = null
|
||||
var renderer_id = null
|
||||
|
||||
// Prototype object for commands
|
||||
var command_proto = null
|
||||
|
||||
// Set the renderer
|
||||
draw.set_renderer = function(actor, id) {
|
||||
renderer_actor = actor
|
||||
renderer_id = id
|
||||
|
||||
// Create prototype object with common fields
|
||||
command_proto = {
|
||||
var command_proto = {
|
||||
kind: "renderer",
|
||||
id: id
|
||||
}
|
||||
id: renderer_id
|
||||
}
|
||||
|
||||
// Clear accumulated commands
|
||||
@@ -484,12 +475,58 @@ draw.image = function image(image, rect = [0,0], rotation = 0, anchor = [0,0], s
|
||||
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
|
||||
info ??= image_info;
|
||||
|
||||
// TODO: Handle texture loading and sending texture_id
|
||||
// For now, we skip image rendering as it requires texture management
|
||||
// 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
|
||||
}
|
||||
|
||||
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 * texture.width,
|
||||
y: image.rect.y * texture.height,
|
||||
width: image.rect.width * texture.width,
|
||||
height: image.rect.height * texture.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)
|
||||
|
||||
@@ -6,14 +6,18 @@ Includes both JavaScript and C-implemented routines for creating geometry buffer
|
||||
rectangle packing, etc.
|
||||
`
|
||||
|
||||
var renderer_actor = arg[0]
|
||||
var renderer_id = arg[1]
|
||||
|
||||
var io = use('io')
|
||||
var os = use('os')
|
||||
var res = use('resources')
|
||||
var render = use('render')
|
||||
var json = use('json')
|
||||
|
||||
var GPU = Symbol()
|
||||
var CPU = Symbol()
|
||||
var LASTUSE = Symbol()
|
||||
var LOADING = Symbol()
|
||||
|
||||
var cache = new Map()
|
||||
|
||||
@@ -21,9 +25,26 @@ var cache = new Map()
|
||||
graphics.Image = {
|
||||
get gpu() {
|
||||
this[LASTUSE] = os.now();
|
||||
if (!this[GPU]) {
|
||||
this[GPU] = render.load_texture(this[CPU]);
|
||||
decorate_rect_px(this);
|
||||
if (!this[GPU] && !this[LOADING]) {
|
||||
this[LOADING] = true;
|
||||
var self = this;
|
||||
|
||||
// Send message to load texture
|
||||
send(renderer_actor, {
|
||||
kind: "renderer",
|
||||
id: renderer_id,
|
||||
op: "loadTexture",
|
||||
data: this[CPU]
|
||||
}, function(response) {
|
||||
if (response.error) {
|
||||
console.error("Failed to load texture:", response.error);
|
||||
self[LOADING] = false;
|
||||
} else {
|
||||
self[GPU] = response;
|
||||
decorate_rect_px(self);
|
||||
self[LOADING] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this[GPU]
|
||||
@@ -33,18 +54,23 @@ graphics.Image = {
|
||||
|
||||
get cpu() {
|
||||
this[LASTUSE] = os.now();
|
||||
if (!this[CPU]) this[CPU] = render.read_texture(this[GPU])
|
||||
// Note: Reading texture back from GPU requires async operation
|
||||
// For now, return the CPU data if available
|
||||
return this[CPU]
|
||||
},
|
||||
|
||||
get surface() { return this.cpu },
|
||||
|
||||
get width() {
|
||||
return this[GPU].width
|
||||
if (this[GPU]) return this[GPU].width
|
||||
if (this[CPU]) return this[CPU].width
|
||||
return 0
|
||||
},
|
||||
|
||||
get height() {
|
||||
return this[GPU].height
|
||||
if (this[GPU]) return this[GPU].height
|
||||
if (this[CPU]) return this[CPU].height
|
||||
return 0
|
||||
},
|
||||
|
||||
unload_gpu() {
|
||||
@@ -79,15 +105,11 @@ function decorate_rect_px(img) {
|
||||
|
||||
function make_handle(obj)
|
||||
{
|
||||
var image = Object.create(graphics.Image);
|
||||
|
||||
if (obj.surface) {
|
||||
im
|
||||
}
|
||||
return Object.assign(Object.create(graphics.Image), {
|
||||
rect:{x:0,y:0,width:1,height:1},
|
||||
[CPU]:obj,
|
||||
[GPU]:undefined,
|
||||
[LOADING]:false,
|
||||
[LASTUSE]:os.now()
|
||||
})
|
||||
}
|
||||
@@ -188,19 +210,18 @@ graphics.texture_from_data = function(data)
|
||||
{
|
||||
if (!(data instanceof ArrayBuffer)) return undefined
|
||||
|
||||
var img = {
|
||||
surface: graphics.make_texture(data)
|
||||
}
|
||||
render.load_texture(img)
|
||||
decorate_rect_px(img)
|
||||
var surface = graphics.make_texture(data);
|
||||
var img = make_handle(surface);
|
||||
|
||||
return img
|
||||
// Trigger GPU load (async)
|
||||
img.gpu;
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
graphics.from_surface = function(id, surf)
|
||||
{
|
||||
return make_handle(surf)
|
||||
var img = { surface: surf }
|
||||
}
|
||||
|
||||
graphics.from = function(id, data)
|
||||
@@ -307,10 +328,22 @@ graphics.get_font = function get_font(path, size) {
|
||||
|
||||
var data = io.slurpbytes(fullpath)
|
||||
var font = graphics.make_font(data,size)
|
||||
font.texture = render.load_texture(font.surface)
|
||||
|
||||
console.log('loaded font texture')
|
||||
console.log(json.encode(font.texture))
|
||||
// Load font texture via renderer actor (async)
|
||||
send(renderer_actor, {
|
||||
kind: "renderer",
|
||||
id: renderer_id,
|
||||
op: "loadTexture",
|
||||
data: font.surface
|
||||
}, function(response) {
|
||||
if (response.error) {
|
||||
console.error("Failed to load font texture:", response.error);
|
||||
} else {
|
||||
font.texture = response;
|
||||
console.log('loaded font texture');
|
||||
console.log(json.encode(font.texture));
|
||||
}
|
||||
});
|
||||
|
||||
fontcache[fontstr] = font
|
||||
|
||||
@@ -341,23 +374,6 @@ Builds a single geometry mesh for all sprite-type commands in the queue, storing
|
||||
so they can be rendered in one draw call.
|
||||
`
|
||||
|
||||
graphics.make_sprite_mesh[prosperon.DOC] = `
|
||||
:param sprites: An array of sprite objects, each containing .rect (or transform), .src (UV region), .color, etc.
|
||||
:param oldMesh (optional): An existing mesh object to reuse/resize if possible.
|
||||
:return: A GPU mesh object with pos, uv, color, and indices buffers for all sprites.
|
||||
Given an array of sprites, build a single geometry mesh for rendering them.
|
||||
`
|
||||
|
||||
graphics.make_sprite_queue[prosperon.DOC] = `
|
||||
:param sprites: An array of sprite objects.
|
||||
:param camera: (unused in the C code example) Typically a camera or transform for sorting?
|
||||
:param pipeline: A pipeline object for rendering.
|
||||
:param sort: An integer or boolean for whether to sort sprites; if truthy, sorts by layer & texture.
|
||||
:return: An array of pipeline commands: geometry with mesh references, grouped by image.
|
||||
Given an array of sprites, optionally sort them, then build a queue of pipeline commands.
|
||||
Each group with a shared image becomes one command.
|
||||
`
|
||||
|
||||
graphics.make_text_buffer[prosperon.DOC] = `
|
||||
:param text: The string to render.
|
||||
:param rect: A rectangle specifying position and possibly wrapping.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
return use('sdl_render')
|
||||
@@ -1,125 +0,0 @@
|
||||
var render = {}
|
||||
|
||||
var context
|
||||
|
||||
var util = use('util')
|
||||
|
||||
render.initialize = function(config)
|
||||
{
|
||||
var default_conf = {
|
||||
title:`Prosperon [${prosperon.version}-${prosperon.revision}]`,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
// icon: graphics.make_texture(io.slurpbytes('icons/moon.gif')),
|
||||
high_dpi:0,
|
||||
alpha:1,
|
||||
fullscreen:0,
|
||||
sample_count:1,
|
||||
enable_clipboard:true,
|
||||
enable_dragndrop: true,
|
||||
max_dropped_files: 1,
|
||||
swap_interval: 1,
|
||||
name: "Prosperon",
|
||||
version:prosperon.version + "-" + prosperon.revision,
|
||||
identifier: "world.pockle.prosperon",
|
||||
creator: "Pockle World LLC",
|
||||
copyright: "Copyright Pockle World 2025",
|
||||
type: "game",
|
||||
url: "https://prosperon.dev"
|
||||
}
|
||||
|
||||
config.__proto__ = default_conf
|
||||
prosperon.window = prosperon.engine_start(config)
|
||||
context = prosperon.window.make_renderer()
|
||||
context.logical_size([config.resolution_x, config.resolution_y], config.mode)
|
||||
}
|
||||
|
||||
render.sprite = function(sprite)
|
||||
{
|
||||
context.sprite(sprite)
|
||||
}
|
||||
|
||||
// img here is the engine surface
|
||||
render.load_texture = function(surface)
|
||||
{
|
||||
return context.load_texture(surface)
|
||||
}
|
||||
|
||||
var current_color = Color.white
|
||||
|
||||
render.image = function(image, rect, rotation, anchor, shear, info)
|
||||
{
|
||||
// rect.width = image.rect_px.width;
|
||||
// rect.height = image.rect_px.height;
|
||||
image.texture.mode(info.mode)
|
||||
context.texture(image.texture, image.rect_px, rect, rotation, anchor);
|
||||
}
|
||||
|
||||
render.clip = function(rect)
|
||||
{
|
||||
context.clip(rect)
|
||||
}
|
||||
|
||||
render.line = function(points)
|
||||
{
|
||||
context.line(points)
|
||||
}
|
||||
|
||||
render.point = function(pos)
|
||||
{
|
||||
context.point(pos)
|
||||
}
|
||||
|
||||
render.rectangle = function(rect)
|
||||
{
|
||||
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(image, mesh, pipeline)
|
||||
{
|
||||
context.geometry(image, mesh)
|
||||
}
|
||||
|
||||
render.slice9 = function(image, rect, slice, info, pipeline)
|
||||
{
|
||||
context.slice9(image.texture, image.rect_px, util.normalizeSpacing(slice), rect);
|
||||
}
|
||||
|
||||
render.get_image = function(rect)
|
||||
{
|
||||
return context.get_image(rect)
|
||||
}
|
||||
|
||||
render.clear = function(color)
|
||||
{
|
||||
if (color) context.draw_color(color)
|
||||
context.clear()
|
||||
}
|
||||
|
||||
render.present = function()
|
||||
{
|
||||
context.present()
|
||||
}
|
||||
|
||||
render.camera = function(cam)
|
||||
{
|
||||
context.camera(cam);
|
||||
}
|
||||
|
||||
return render
|
||||
@@ -592,6 +592,7 @@ void script_startup(prosperon_rt *prt, void (*hook)(JSContext*))
|
||||
JS_AddIntrinsicMapSet(js);
|
||||
JS_AddIntrinsicTypedArrays(js);
|
||||
JS_AddIntrinsicWeakRef(js);
|
||||
JS_AddIntrinsicProxy(js);
|
||||
|
||||
JS_SetContextOpaque(js, prt);
|
||||
prt->context = js;
|
||||
|
||||
238
tests/draw2d.js
Normal file
238
tests/draw2d.js
Normal file
@@ -0,0 +1,238 @@
|
||||
// Test draw2d module without moth framework
|
||||
var draw2d
|
||||
var graphics
|
||||
var os = use('os');
|
||||
|
||||
use('tracy').level = 1
|
||||
|
||||
// Create SDL video actor
|
||||
var video = use('sdl_video');
|
||||
var video_actor = {__ACTORDATA__:{id:video}};
|
||||
|
||||
var window_id = null;
|
||||
var renderer_id = null;
|
||||
|
||||
// Create window
|
||||
send(video_actor, {
|
||||
kind: "window",
|
||||
op: "create",
|
||||
data: {
|
||||
title: "Draw2D Test",
|
||||
width: 800,
|
||||
height: 600
|
||||
}
|
||||
}, function(response) {
|
||||
if (response.error) {
|
||||
console.error("Failed to create window:", response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
window_id = response.id;
|
||||
console.log("Created window with id:", window_id);
|
||||
|
||||
// Create renderer
|
||||
send(video_actor, {
|
||||
kind: "window",
|
||||
op: "makeRenderer",
|
||||
id: window_id
|
||||
}, function(response) {
|
||||
if (response.error) {
|
||||
console.error("Failed to create renderer:", response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
renderer_id = response.id;
|
||||
console.log("Created renderer with id:", renderer_id);
|
||||
|
||||
// Configure draw2d and graphics
|
||||
draw2d = use('draw2d', video_actor, renderer_id)
|
||||
graphics = use('graphics', video_actor, renderer_id)
|
||||
|
||||
// Start drawing after a short delay
|
||||
$_.delay(start_drawing, 0.1);
|
||||
});
|
||||
});
|
||||
|
||||
function start_drawing() {
|
||||
var frame = 0;
|
||||
var start_time = os.now();
|
||||
|
||||
// Load an image
|
||||
var bunny_image = null;
|
||||
try {
|
||||
bunny_image = graphics.texture('tests/bunny.png');
|
||||
console.log("Loaded bunny image");
|
||||
} catch (e) {
|
||||
console.error("Failed to load bunny image:", e);
|
||||
}
|
||||
|
||||
function draw_frame() {
|
||||
frame++;
|
||||
var t = os.now() - start_time;
|
||||
|
||||
// Clear the screen with a dark background
|
||||
send(video_actor, {
|
||||
kind: "renderer",
|
||||
id: renderer_id,
|
||||
op: "set",
|
||||
prop: "drawColor",
|
||||
value: [0.1, 0.1, 0.15, 1]
|
||||
});
|
||||
send(video_actor, {
|
||||
kind: "renderer",
|
||||
id: renderer_id,
|
||||
op: "clear"
|
||||
});
|
||||
|
||||
// Clear draw2d commands
|
||||
draw2d.clear();
|
||||
|
||||
// Draw some rectangles
|
||||
draw2d.rectangle(
|
||||
{x: 50, y: 50, width: 100, height: 100},
|
||||
{thickness: 0, color: [1, 0, 0, 1]}
|
||||
);
|
||||
|
||||
draw2d.rectangle(
|
||||
{x: 200, y: 50, width: 100, height: 100},
|
||||
{thickness: 5, color: [0, 1, 0, 1]}
|
||||
);
|
||||
|
||||
draw2d.rectangle(
|
||||
{x: 350, y: 50, width: 100, height: 100},
|
||||
{thickness: 2, color: [0, 0, 1, 1], radius: 20}
|
||||
);
|
||||
|
||||
// Draw circles with animation
|
||||
var radius = 30 + Math.sin(t * 2) * 10;
|
||||
draw2d.circle(
|
||||
[100, 250],
|
||||
radius,
|
||||
{color: [1, 1, 0, 1], thickness: 0}
|
||||
);
|
||||
|
||||
draw2d.circle(
|
||||
[250, 250],
|
||||
40,
|
||||
{color: [1, 0, 1, 1], thickness: 3}
|
||||
);
|
||||
|
||||
// Draw ellipse
|
||||
draw2d.ellipse(
|
||||
[400, 250],
|
||||
[60, 30],
|
||||
{color: [0, 1, 1, 1], thickness: 2}
|
||||
);
|
||||
|
||||
// Draw lines
|
||||
var line_y = 350 + Math.sin(t * 3) * 20;
|
||||
draw2d.line(
|
||||
[[50, line_y], [150, line_y + 50], [250, line_y]],
|
||||
{color: [1, 0.5, 0, 1], thickness: 2}
|
||||
);
|
||||
|
||||
// Draw cross
|
||||
draw2d.cross(
|
||||
[350, 375],
|
||||
25,
|
||||
{color: [0.5, 1, 0.5, 1], thickness: 3}
|
||||
);
|
||||
|
||||
// Draw arrow
|
||||
draw2d.arrow(
|
||||
[450, 350],
|
||||
[550, 400],
|
||||
15,
|
||||
30,
|
||||
{color: [1, 1, 1, 1], thickness: 2}
|
||||
);
|
||||
|
||||
// Draw partial circle (arc)
|
||||
draw2d.circle(
|
||||
[150, 480],
|
||||
50,
|
||||
{
|
||||
color: [0.8, 0.8, 1, 1],
|
||||
thickness: 5,
|
||||
start: 0.25,
|
||||
end: 0.75
|
||||
}
|
||||
);
|
||||
|
||||
// Draw filled partial ellipse
|
||||
draw2d.ellipse(
|
||||
[350, 480],
|
||||
[80, 40],
|
||||
{
|
||||
color: [1, 0.8, 0.8, 1],
|
||||
thickness: 0,
|
||||
start: 0,
|
||||
end: 0.6
|
||||
}
|
||||
);
|
||||
|
||||
// Draw some points in a pattern
|
||||
var point_count = 20;
|
||||
for (var i = 0; i < point_count; i++) {
|
||||
var angle = (i / point_count) * Math.PI * 2;
|
||||
var r = 30 + Math.sin(t * 4 + i * 0.5) * 10;
|
||||
var px = 650 + Math.cos(angle) * r;
|
||||
var py = 300 + Math.sin(angle) * r;
|
||||
|
||||
draw2d.point(
|
||||
[px, py],
|
||||
3,
|
||||
{color: [1, 0.5 + Math.sin(t * 2 + i) * 0.5, 0.5, 1]}
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the bunny image if loaded
|
||||
if (bunny_image) {
|
||||
// Static bunny
|
||||
draw2d.image(bunny_image, {x: 500, y: 450, width: 64, height: 64});
|
||||
|
||||
// Rotating bunny
|
||||
var rotation = t * 0.5;
|
||||
draw2d.image(
|
||||
bunny_image,
|
||||
{x: 600, y: 450, width: 64, height: 64},
|
||||
rotation,
|
||||
[0.5, 0.5] // Center anchor
|
||||
);
|
||||
|
||||
// Bouncing bunny with tint
|
||||
var bounce_y = 500 + Math.sin(t * 3) * 20;
|
||||
draw2d.image(
|
||||
bunny_image,
|
||||
{x: 700, y: bounce_y, width: 48, height: 48},
|
||||
0,
|
||||
[0.5, 1], // Bottom center anchor
|
||||
[0, 0], // No shear
|
||||
{color: [1, 0.5, 0.5, 1]} // Red tint
|
||||
);
|
||||
}
|
||||
|
||||
// Flush all commands to renderer
|
||||
draw2d.flush();
|
||||
|
||||
// Present the frame
|
||||
send(video_actor, {
|
||||
kind: "renderer",
|
||||
id: renderer_id,
|
||||
op: "present"
|
||||
});
|
||||
|
||||
// Schedule next frame (60 FPS)
|
||||
if (frame < 600) { // Run for 10 seconds
|
||||
$_.delay(draw_frame, 1/60);
|
||||
} else {
|
||||
console.log("Test completed - drew", frame, "frames");
|
||||
$_.delay($_.stop, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
draw_frame();
|
||||
}
|
||||
|
||||
// Stop after 12 seconds if not already stopped
|
||||
$_.delay($_.stop, 12);
|
||||
Reference in New Issue
Block a user