Files
prosperon/camera.cm
2026-02-25 16:58:06 -06:00

154 lines
4.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

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

var backend = use('sdl_gpu')
var cam = {}
/*
presentation can be one of
letterbox
overscan
stretch
.. or simply 'null' for no presentation
*/
function rect_contains_pt(rect, x, y) {
return x >= rect.x && x <= rect.x + rect.width &&
y >= rect.y && y <= rect.y + rect.height
}
function place_rect(src, dst, mode) {
if (mode == 'stretch')
return {x: 0, y: 0, width: dst.width, height: dst.height}
var sx = 0, sy = 0, s = 0, w = 0, h = 0, scale = 0
if (mode == 'integer_scale') {
sx = floor(dst.width / src.width)
sy = floor(dst.height / src.height)
s = max(1, min(sx, sy))
w = src.width * s
h = src.height * s
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
}
// letterbox (default)
scale = min(dst.width / src.width, dst.height / src.height)
w = src.width * scale
h = src.height * scale
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
}
var basecam = {
pos: {x:0,y:0},
ortho:true,
width: 1,
height: 1,
fov:50,
near_z:0,
far_z:1000,
anchor: {x:0.5,y:0.5},
rotation:[0,0,0,1],
presentation: "letterbox",
background: {r:1,g:1,b:1,a:0},
viewport: {x:0,y:0,width:1,height:1},
aspect_ratio() {
return this.y/this.x;
},
rect() {
return {x:0, y:0, width: this.width, height: this.height}
},
sensor() {
var ws = backend.get_window_size()
return {
width: ws.width,
height: ws.height,
}
},
// The rectangle (in window pixels) where this cameras target will be drawn
screen_rect() {
var src = { width: this.width, height: this.height }
var ws = backend.get_window_size()
var dst = { x: 0, y: 0, width: ws.width, height: ws.height }
return place_rect(src, dst, this.presentation)
},
// --- Space converters ---------------------------------------------------
// World -> View UV [0..1] independent of pixels/letterbox
world_to_view_uv(wx, wy) {
var ax = this.anchor.x, ay = this.anchor.y
var u = (wx - this.pos.x) / this.width + ax
var v = (wy - this.pos.y) / this.height + ay
// apply viewport (maps camera-local UV into sub-rect)
var vp = this.viewport
u = vp.x + u * vp.width
v = vp.y + v * vp.height
return { x: u, y: v }
},
// View UV -> World
view_uv_to_world(u, v) {
var vp = this.viewport
var uu = (u - vp.x) / vp.width
var vv = (v - vp.y) / vp.height
var ax = this.anchor.x, ay = this.anchor.y
var wx = this.pos.x + (uu - ax) * this.width
var wy = this.pos.y + (vv - ay) * this.height
return { x: wx, y: wy }
},
// World -> Window pixels (what you want for hit-tests)
world_to_window(wx, wy) {
var uv = this.world_to_view_uv(wx, wy)
var sr = this.screen_rect()
var sx = sr.x + uv.x * sr.width
var sy = sr.y + uv.y * sr.height
return { x: sx, y: sy }
},
// Window pixels -> World (mouse picking)
window_to_world(sx, sy) {
var sr = this.screen_rect()
var u = (sx - sr.x) / sr.width
var v = 1 - (sy - sr.y) / sr.height
return this.view_uv_to_world(u, v)
},
// World -> normalized window [0..1]
world_to_screen_norm(wx, wy) {
var p = this.world_to_window(wx, wy)
var ws = backend.get_window_size()
return { x: p.x / ws.width, y: p.y / ws.height }
},
// Normalized window [0..1] -> World
screen_norm_to_world(nx, ny) {
var ws = backend.get_window_size()
var sx = nx * ws.width
var sy = ny * ws.height
return this.window_to_world(sx, sy)
},
screen_to_world: function(sx, sy) {
// sx, sy are normalized screen coordinates (0-1), bottom left [0,0], top right [1,1]
var screen_rect = this.screen_rect()
var ws = backend.get_window_size()
var pixel_x = sx * ws.width
var pixel_y = sy * ws.height
var rel_x = (pixel_x - screen_rect.x) / screen_rect.width
var rel_y = (pixel_y - screen_rect.y) / screen_rect.height
var ax = this.anchor.x
var ay = this.anchor.y
var world_x = this.pos.x + (rel_x - ax) * this.width
var world_y = this.pos.y + (rel_y - ay) * this.height
return {x: world_x, y: world_y}
},
}
cam.make = function(config) {
return meme(basecam, config || {})
}
return cam