154 lines
4.1 KiB
Plaintext
154 lines
4.1 KiB
Plaintext
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 camera’s 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
|