xxxxxxxxxx
function preload() {
f = loadFont('https://cdnjs.cloudflare.com/ajax/libs/ink/3.1.10/fonts/Roboto/roboto-regular-webfont.ttf');
}
function round2_arr(arr, d) {
// Jon Bolmstedt 2023-02-11 v1.0
// Rounds array elements to nbr of digits and returns string.
let s = ""
for (let i = 0; i < arr.length; i++) {
s += round(arr[i], d)
if (i < arr.length-1) {
s += ", "
}
}
return s
}
class DebugAxes {
// Jon Bolmstedt 2023-02-11 v 1.0
// Draws axes at point. x red, y green, z blue.
// x increases to the right of the canvas.
// y increases down in the canvas.
// z increases towards the cameras default view.
constructor(l = 100) {
this.l = l
this.lb = l/320
this.xcol = [255, 0, 0]
this.ycol = [0, 255, 0]
this.zcol = [0, 0, 255]
this.cb = this.l/40
this.ch = this.l/10
}
draw() {
// Axis lines are cylinders. They are drawn from center point out in p5.
// X
push()
stroke(this.xcol)
translate(createVector(1, 0, 0).setMag(this.l/2))
// Rotation of XY plane around Z axis.
// If the Z-axis is poining towards you, a positive rotation is CW
rotateZ(-PI/2)
cylinder(this.lb, this.l)
pop()
// Y
push()
stroke(this.ycol)
translate(createVector(0, 1, 0).setMag(this.l/2))
// Rotation of XY plane around Z axis.
// If the Z-axis is poining towards you, a positive rotation is CW
//rotateZ(-PI/2)
cylinder(this.lb, this.l)
pop()
// Z
push()
stroke(this.zcol)
translate(createVector(0, 0, 1).setMag(this.l/2))
// Rotation of XY plane around Z axis.
// If the Z-axis is poining towards you, a positive rotation is CW
rotateX(PI/2)
cylinder(this.lb, this.l)
pop()
// Arrows
// Drawing a cone(b, h) draws it in the XY plane.
// Pointing down (y axis direction) and with the center inside the cone.
// X
push()
translate(this.l + this.ch/2, 0, 0)
// Rotation of XY plane around Z axis.
// If the Z-axis is poining towards you, a positive rotation is CW
rotateZ(-PI/2)
noStroke()
fill(this.xcol)
cone(this.cb, this.ch)
pop()
// Y
push()
translate(0, this.l + this.ch/2, 0)
//rotateZ(-PI/2) // No rotation needed.
noStroke()
fill(this.ycol)
cone(this.cb, this.ch)
pop()
// Z
push()
translate(0, 0, this.l + this.ch/2)
// Rotation of YZ plane around X axis.
// If the X-axis is poining towards you, a positive rotation is CW.
rotateX(PI/2)
noStroke()
fill(this.zcol)
cone(this.cb, this.ch)
pop()
}
}
class CamControl {
// Jon Bolmstedt 2023-02-11 v1.0
// Camera movement for p5 camera.
// wasd move in direction
// arrows rotate
// space/shift up/down
// p5 camera does not pan around its Y vector, it pans around the world Y vector.
// So it doesnt really work as intended.
// Call update() repeatedly to process input commands.
// If easycam, how to get local axes? This gives quaternions.
// localX = Dw.Rotation.applyToRotation(cam.state.rotation, [1, 0, 0, 0])
// localY = Dw.Rotation.applyToRotation(cam.state.rotation, [0, 1, 0, 0])
// localZ = Dw.Rotation.applyToRotation(cam.state.rotation, [0, 0, 1, 0])
// And camRUP is inverted to world space. RUP [0, 1, 0] byt world y-axis points down.
constructor(camera) {
this.easycam = false
if (!camera._renderer) {
this.easycam = true
}
this.cam = camera
this.pitchPerSecond = 1
this.yawPerSecond = 1
this.movePerSecond = 100
}
update() {
if (this.easycam) return
if (keyIsDown(UP_ARROW)) {
this.pitch_down()
}
if (keyIsDown(DOWN_ARROW)) {
this.pitch_up()
}
if (keyIsDown(LEFT_ARROW)) {
this.yaw_left()
}
if (keyIsDown(RIGHT_ARROW)) {
this.yaw_right()
}
if (keyIsDown(65)) { // A
this.move_left()
}
if (keyIsDown(68)) { // D
this.move_right()
}
if (keyIsDown(87)) { // W
this.move_fwd()
}
if (keyIsDown(83)) { // S
this.move_back()
}
if (keyIsDown(32)) { // Space
this.move_up()
}
if (keyIsDown(16)) { // L Shift
this.move_down()
}
}
move_up() {
// Move along the camera Y axis (assuming that the cam.up has not been modified).
this.cam.move( 0, -this.movePerSecond/1000*deltaTime, 0 )
}
move_down() {
// Move along the camera Y axis
this.cam.move( 0, this.movePerSecond/1000*deltaTime, 0 )
}
move_left() {
this.cam.move( -this.movePerSecond/1000*deltaTime, 0, 0 )
}
move_right() {
this.cam.move( this.movePerSecond/1000*deltaTime, 0, 0 )
}
move_fwd() {
this.cam.move( 0, 0, -this.movePerSecond/1000*deltaTime )
}
move_back() {
this.cam.move( 0, 0, this.movePerSecond/1000*deltaTime )
}
pitch_up() {
this.cam.tilt(-this.pitchPerSecond/1000*deltaTime)
}
pitch_down() {
this.cam.tilt(this.pitchPerSecond/1000*deltaTime)
}
yaw_left() {
this.cam.pan(this.yawPerSecond/1000*deltaTime)
}
yaw_right() {
this.cam.pan(-this.yawPerSecond/1000*deltaTime)
}
}
class EasyHUD {
// Jon Bolmstedt 2023-02-03
constructor(camera) {
this.renderer = camera._renderer
if (!this.renderer) {
this.renderer = camera.renderer
}
this.w = this.renderer.width
this.h = this.renderer.height
}
beginHUD() {
let gl = this.renderer.drawingContext;
let d = Number.MAX_VALUE;
let w = this.w
let h = this.h
gl.flush();
// 1) disable DEPTH_TEST
gl.disable(gl.DEPTH_TEST);
// 2) push modelview/projection
// p5 is not creating a push/pop stack
this.pushed_uMVMatrix = this.renderer.uMVMatrix.copy();
this.pushed_uPMatrix = this.renderer.uPMatrix .copy();
// 3) set new modelview (identity)
this.renderer.resetMatrix();
// 4) set new projection (ortho)
ortho(0, w, -h, 0, -d, +d);
}
endHUD() {
let gl = this.renderer.drawingContext;
gl.flush();
// 2) restore modelview/projection
this.renderer.uMVMatrix.set(this.pushed_uMVMatrix);
this.renderer.uPMatrix .set(this.pushed_uPMatrix );
// 1) enable DEPTH_TEST
gl.enable(gl.DEPTH_TEST);
}
}
class Projections {
// Jon Bolmstedt 2023-02-11 v 1.0
//
// Provides some transformations between world and screen space for WEBGL sketches,
// using the methods in the camera.
//
// Usage:
//
// let cam = createCamera()
// let P = new Projections(cam)
//
// P.world_to_screen(world_pos : array[3], screen_w, screen_h) -> array[3]
// Returns the screen position of a world coordinate.
// If small view angle then the screen coord is a little bit wrong, closer to center of screen.
//
// P.screen_hitbox_size(object_screen_pos : array[3], object_hit_sphere)
// Returns the diameter of the screen circle for the object's hit box sphere diameter.
//
// P.cursor_hit_object(screen x, screen y, object_screen_pos : array[3], object_hit_sphere, f = 1.0) -> bool
// Returns true if the screen position intersects with the object.
// The hitbox is a sphere in world space.
// The screen projected hitbox becomes smaller as the object is farther away.
// 3D graphics
// In a rendering, each mesh of the scene is transformed by
// the model matrix, the view matrix and the projection matrix.
// Projection
// The projection matrix describes the mapping from 3D points of a scene,
// to 2D points of the viewport. It transforms from eye space to the clip space,
// and the coordinates in the clip space are transformed to the normalized
// device coordinates (NDC) by dividing with the w component of the clip coordinates.
// The NDC are in range (-1,-1,-1) to (1,1,1).
// Every geometry which is out of the NDC is clipped.
// The objects between the near plane and the far plane of the camera
// frustum are mapped to the range (-1, 1) of the NDC.
// If the projection is symmetric, where the line of sight is in the center of the
// view port and the field of view is not displaced, then the matrix can be simplified:
// 1/(ta*a) 0 0 0
// 0 1/ta 0 0
// 0 0 -(f+n)/(f-n) -1
// 0 0 -2*f*n/(f-n) 0
// r = right, l = left, b = bottom, t = top, n = near, f = far
// a = w / h
// ta = tan( fov_y / 2 );
// View matrix:
// The view matrix describes the direction and position from which the scene is looked at.
// The view matrix transforms from the world space to the view (eye) space.
// In the coordinate system on the viewport,
// the X-axis points to the left, the Y-axis up and the Z-axis out of the view
// (Note in a right hand system the Z-Axis is the cross product of the X-Axis and the Y-Axis).
// Model matrix:
// The model matrix defines the location, orientation and the relative size of an mesh
// in the scene. The model matrix transforms the vertex positions from of the mesh to the world space.
// The model matrix looks like this:
// ( X-axis.x, X-axis.y, X-axis.z, 0 )
// ( Y-axis.x, Y-axis.y, Y-axis.z, 0 )
// ( Z-axis.x, Z-axis.y, Z-axis.z, 0 )
// ( trans.x, trans.y, trans.z, 1 )
// Finally a model can be transformed for rendering chaining
// [View To Projection] x [World To View] x [Model to World] = [ModelViewProjectionMatrix].
// This is how p5.camera uses matrices:
// (the multiplication order seems to be backwards!): MV*P
// var viewMatrix = this._renderer._curCamera.cameraMatrix;
// var projectionMatrix = this._renderer.uPMatrix;
// var modelViewMatrix = this._renderer.uMVMatrix;
// var modelViewProjectionMatrix = modelViewMatrix.copy();
// modelViewProjectionMatrix.mult(projectionMatrix);
constructor(cam) {
this.cam = cam
this.easycam = false
if (cam.cameraNear) {
// p5.camera
this.cam.renderer = this.cam._renderer
}
else {
// easycam
this.easycam = true
}
this.raw_screen_coords = new Float32Array(3)
this.normalized_screen_coords = new Float32Array(3)
this.scaled = new Float32Array(3)
this.MVP = new Float32Array(16)
// console.log("projections: easycam: " + this.easycam)
}
// p5 camera and easycam uses the same matrices for rendering but have some different variables.
update_cam_var() {
if (!this.easycam) {
// p5.camera
this.near = this.cam.cameraNear
this.far = this.cam.cameraFar
this.fov = this.cam.cameraFOV
this.camEye = [this.cam.eyeX, this.cam.eyeY, this.cam.eyeZ]
this.camUp = [this.cam.upX, this.cam.upY, this.cam.upZ]
this.camCenter = [this.cam.centerX, this.cam.centerY, this.cam.centerZ]
}
else {
// easycam
this.near = this.cam.distance_min
this.far = this.cam.distance_max
this.fov = PI/3
this.camEye = this.cam.camEYE
this.camUp = this.cam.camRUP
this.camCenter = this.cam.camLAT
}
}
print_matrix(M) {
let matstring = M.mat4.toString()
let sarr = matstring.split(",")
for (let i = 0; i < 4; i++) {
let k = i*4
console.log(round(sarr[0+k], 3) + " " + round(sarr[1+k], 3) + " " + round(sarr[2+k], 3) + " " + round(sarr[3+k], 3))
}
}
print_cam() {
console.log(this.cam.state)
}
transpose(M) {
let t;
// The diagonal values don't move; 6 non-diagonal elements are swapped.
t = M[1]; M[1] = M[4]; M[4] = t;
t = M[2]; M[2] = M[8]; M[8] = t;
t = M[3]; M[3] = M[12]; M[12] = t;
t = M[6]; M[6] = M[9]; M[9] = t;
t = M[7]; M[7] = M[13]; M[13] = t;
t = M[11]; M[11] = M[14]; M[14] = t;
}
mult_mat(R, A, B) {
// Row order defined matrices.
R[0] = A[0] * B[0] + A[1] * B[4] + A[2] * B[8] + A[3] * B[12];
R[1] = A[0] * B[1] + A[1] * B[5] + A[2] * B[9] + A[3] * B[13];
R[2] = A[0] * B[2] + A[1] * B[6] + A[2] * B[10] + A[3] * B[14];
R[3] = A[0] * B[3] + A[1] * B[7] + A[2] * B[11] + A[3] * B[15];
R[4] = A[4] * B[0] + A[5] * B[4] + A[6] * B[8] + A[7] * B[12];
R[5] = A[4] * B[1] + A[5] * B[5] + A[6] * B[9] + A[7] * B[13];
R[6] = A[4] * B[2] + A[5] * B[6] + A[6] * B[10] + A[7] * B[14];
R[7] = A[4] * B[3] + A[5] * B[7] + A[6] * B[11] + A[7] * B[15];
R[8] = A[8] * B[0] + A[9] * B[4] + A[10] * B[8] + A[11] * B[12];
R[9] = A[8] * B[1] + A[9] * B[5] + A[10] * B[9] + A[11] * B[13];
R[10] = A[8] * B[2] + A[9] * B[6] + A[10] * B[10] + A[11] * B[14];
R[11] = A[8] * B[3] + A[9] * B[7] + A[10] * B[11] + A[11] * B[15];
R[12] = A[12] * B[0] + A[13] * B[4] + A[14] * B[8] + A[15] * B[12];
R[13] = A[12] * B[1] + A[13] * B[5] + A[14] * B[9] + A[15] * B[13];
R[14] = A[12] * B[2] + A[13] * B[6] + A[14] * B[10] + A[15] * B[14];
R[15] = A[12] * B[3] + A[13] * B[7] + A[14] * B[11] + A[15] * B[15];
}
mult_mat_gl(R, A, B) {
// Column order defined matrices.
R[0] = A[0] * B[0] + A[4] * B[1] + A[8] * B[2] + A[12] * B[3];
R[1] = A[1] * B[0] + A[5] * B[1] + A[9] * B[2] + A[13] * B[3];
R[2] = A[2] * B[0] + A[6] * B[1] + A[10] * B[2] + A[14] * B[3];
R[3] = A[3] * B[0] + A[7] * B[1] + A[11] * B[2] + A[15] * B[3];
R[4] = A[0] * B[4] + A[4] * B[5] + A[8] * B[6] + A[12] * B[7];
R[5] = A[1] * B[4] + A[5] * B[5] + A[9] * B[6] + A[13] * B[7];
R[6] = A[2] * B[4] + A[6] * B[5] + A[10] * B[6] + A[14] * B[7];
R[7] = A[3] * B[4] + A[7] * B[5] + A[11] * B[6] + A[15] * B[7];
R[8] = A[0] * B[8] + A[4] * B[9] + A[8] * B[10] + A[12] * B[11];
R[9] = A[1] * B[8] + A[5] * B[9] + A[9] * B[10] + A[13] * B[11];
R[10] = A[2] * B[8] + A[6] * B[9] + A[10] * B[10] + A[14] * B[11];
R[11] = A[3] * B[8] + A[7] * B[9] + A[11] * B[10] + A[15] * B[11];
R[12] = A[0] * B[12] + A[4] * B[13] + A[8] * B[14] + A[12] * B[15];
R[13] = A[1] * B[12] + A[5] * B[13] + A[9] * B[14] + A[13] * B[15];
R[14] = A[2] * B[12] + A[6] * B[13] + A[10] * B[14] + A[14] * B[15];
R[15] = A[3] * B[12] + A[7] * B[13] + A[11] * B[14] + A[15] * B[15];
}
mult_vec_mat(v, M) {
// Multiplies 1x4 vector with mat4 4x4 matrix row wise defined i.e.,
// 0, 1, 2, 3
// 4, 5, 6, 7
// ...
let R = []
R.push( v[0]*M[0] + v[1]*M[4] + v[2]*M[8] + v[3]*M[12] )
R.push( v[0]*M[1] + v[1]*M[5] + v[2]*M[9] + v[3]*M[13] )
R.push( v[0]*M[2] + v[1]*M[6] + v[2]*M[10] + v[3]*M[14] )
R.push( v[0]*M[3] + v[1]*M[7] + v[2]*M[11] + v[3]*M[15] )
return R
}
mult_vec_mat_gl(v, M) {
// Multiplies 1x4 vector with mat4 4x4 matrix in gl column format, i.e.,
// 0, 4, 8, 12
// 1, 5, 9, 13
// ...
let R = []
R.push( v[0]*M[0] + v[1]*M[1] + v[2]*M[3] + v[3]*M[4] )
R.push( v[0]*M[4] + v[1]*M[5] + v[2]*M[6] + v[3]*M[7] )
R.push( v[0]*M[8] + v[1]*M[9] + v[2]*M[10] + v[3]*M[11] )
R.push( v[0]*M[12] + v[1]*M[13] + v[2]*M[14] + v[3]*M[15] )
return R
}
mult_mat_vec(M, v) {
// console.log(M)
// console.log(v)
// Multiplies .mat4 4x4 matrix with a 4x1 vector.
// mat 4 defined row-wise, i.e., first row are elements 0, 1, 2, 3
let R = []
R.push( M[0]*v[0] + M[1]*v[1] + M[2]*v[2] + M[3]*v[3] )
R.push( M[4]*v[0] + M[5]*v[1] + M[6]*v[2] + M[7]*v[3] )
R.push( M[8]*v[0] + M[9]*v[1] + M[10]*v[2] + M[11]*v[3] )
R.push( M[12]*v[0] + M[13]*v[1] + M[14]*v[2] + M[15]*v[3] )
return R
}
mult_mat_vec_gl(M, v) {
// Multiplies GL .mat4 4x4 matrix with a 4x1 vector.
// GL matrices are defined column wise, i.e., first row are elements 0, 4, 8, 12
let R = []
R.push( M[0]*v[0] + M[4]*v[1] + M[8]*v[2] + M[12]*v[3] )
R.push( M[1]*v[0] + M[5]*v[1] + M[9]*v[2] + M[13]*v[3] )
R.push( M[2]*v[0] + M[6]*v[1] + M[10]*v[2] + M[14]*v[3] )
R.push( M[3]*v[0] + M[7]*v[1] + M[11]*v[2] + M[15]*v[3] )
return R
}
get_MVP(transpose_mv, transpose_p, p5_order) {
// Matrix definition and multiplication order seems backward in p5 camera.
// (the multiplication order seems to be backwards!): MV*P
// var viewMatrix = this._renderer._curCamera.cameraMatrix;
// var projectionMatrix = this._renderer.uPMatrix;
// var modelViewMatrix = this._renderer.uMVMatrix;
// var modelViewProjectionMatrix = modelViewMatrix.copy();
// modelViewProjectionMatrix.mult(projectionMatrix);
// The MV matrix seems to need transpose to have the correct format.
let MV = this.cam.renderer.uMVMatrix.copy()
if (transpose_mv) MV = MV.transpose(MV.mat4)
// But P doesn't.
let P = this.cam.renderer.uPMatrix.copy()
if (transpose_p) P = P.transpose(P.mat4)
if (p5_order) {
// Seems to be this way around in the p5.camera
this.mult_mat(this.MVP, MV.mat4, P.mat4)
}
else {
this.mult_mat(this.MVP, P.mat4, MV.mat4)
}
}
world_to_screen(world_pos, w, h) {
let dolog = false
// Returns the screen position of an onject in world position.
// Conversion from world space to screen space.
//
// Calculate P*MV*translated object world coord.
// Divide x, y by the translated objects z giving normalised screen coords (-1 left side, +1 right side)
// Invert sign of y to have it 0 at top of screen and -1 at the bottom.
this.update_cam_var()
if (dolog) {
console.log("\nworld_to_screen pos: " + world_pos)
console.log("MV:")
this.print_matrix(this.cam.renderer.uMVMatrix)
console.log("P:")
this.print_matrix(this.cam.renderer.uPMatrix)
}
// w is called the homogenous coordinate.
// w = 1 for positions (which can be translated) and 0 for vectors (which are unaffected by translations).
let wp = [world_pos, 1]
// Calculate P*MV*translated object world coord. Under the hood, a p5 camera sends MV*P to the GL program.
// renderer._curCamera.cameraMatrix is same as MVMatrix.
// p5.camera seems to invert the order of operations, using screen_coord = [vertex]*[mv]*[p]
// Can't make p5 version work.
let p5_order = false
if (p5_order) {
let transpose_mv = false
let transpose_p = false
let invert_order = true
// this.get_MVP(transpose_mv, transpose_p, invert_order)
this.mult_mat(this.MVP, this.cam.renderer.uMVMatrix.copy().mat4, this.cam.renderer.uPMatrix.copy().mat4)
// this.raw_screen_coords = this.mult_mat_vec(this.MVP, wp) // Use if normal multiplication.
this.raw_screen_coords = this.mult_vec_mat(wp, this.MVP) // Use if inverted multiplication.
// this.raw_screen_coords = this.mult_mat_vec_gl(this.MVP.mat4, wp)
}
else {
let transpose_mv = true
let transpose_p = false
let invert_order = false
this.get_MVP(transpose_mv, transpose_p, invert_order)
this.raw_screen_coords = this.mult_mat_vec(this.MVP, wp) // Use if normal multiplication.
}
if (dolog) {
console.log("MVP ")
this.print_matrix(this.MVP)
console.log("\n")
console.log("\nraw sc: " + this.raw_screen_coords)
}
// Divide x, y by the translated objects z giving normalised screen coords (-1 left side, +1 right side)
let raw = [this.raw_screen_coords] // This copies the array.
let d = this.raw_screen_coords[2]
// Then normalize z by the near/far plane (but this normalized z value is never used).
let z_norm = (raw[2]-this.near)/(this.far-this.near)*2 - 1
this.normalized_screen_coords = [this.raw_screen_coords[0]/d, this.raw_screen_coords[1]/d, z_norm]
if (dolog) {
console.log("d: " + d)
}
// Scale to actual screen pos and position for 2D canvas,
// so that 0, 0 is top left and w, h is bottom right. Invert y.
// Add a small scale factor. Otherwise wrong in edges of screen. Not sure why,
// but there must be different matrix calculations by GL than in js. For the z or d.
let f = 1.02
if (p5_order) f = 1
this.scaled = [
map(this.normalized_screen_coords[0]*f, -1, 1, 0, width),
map(this.normalized_screen_coords[1]*f, -1, 1, height, 0),
raw[2]
]
if (dolog) console.log("scaled sc: " + round2_arr(this.scaled))
return this.scaled
}
screen_hitbox_size(object_screen_pos, object_hit_sphere) {
// Returns the size of hitbox circle on screen coordinates
// given hitbox sphere in world coordinates.
//
// object_screen_pos : array[3] calculated object screen coordinates from world_to_screen()
// object_hit_sphere : spherical hitbox diameter in world coordinates.
this.update_cam_var()
let scale_factor = 1/(tan(this.fov/2)) / 1.732050808
let screen_dia = 2 * scale_factor * (object_hit_sphere / 10) / (0.000289*object_screen_pos[2]*400/height + 0.0000459)
return screen_dia
}
cursor_hit_object(x, y, object_screen_pos, object_hit_sphere, f = 1.0) {
// Returns true if screen coordinates intersects an objects hitbox.
//
// x, y: screen position
// object_screen_pos: array[3] calculated object screen coordinates from world_to_screen()
// object_hit_sphere: spherical hitbox diameter in world coordinates.
// f: modifier of screen circle hitbox. Default 1.
let screen_dia = this.screen_hitbox_size(object_screen_pos, object_hit_sphere, f)
// Only accept objects in front.
if (screen_dia > 0 && object_screen_pos[2] > 0) {
// Compare distance to circle in 2D
let d = sqrt((object_screen_pos[0]-x)**2 + (object_screen_pos[1]-y)**2)
if (d < f*screen_dia/2) {
return true
}
}
return false
}
}
class CheckBox {
constructor(x, y, size, checked = false, text_side = "left") {
this.size = size
this.center = [x, y]
this.text = ""
this.text_side = text_side
this.checked = checked
this.half_size = this.size/2
}
update(click) {
if (click && this.mouse_is_over()) {
this.checked = !this.checked
}
this.draw()
}
draw() {
push()
let tp
if (this.text_side === "left") {
textAlign(RIGHT, CENTER)
tp = [this.center]
tp[0] -= (10 + this.half_size)
}
else {
textAlign(LEFT, CENTER)
tp = [this.center]
tp[0] += (10 + this.half_size)
}
text(this.text, tp[0], tp[1])
rectMode(CENTER)
strokeWeight(4)
if (!this.checked) {
noFill()
}
square(this.center[0], this.center[1], this.size)//, 15, 15, 15, 15)
pop()
}
mouse_is_over() {
return (
mouseX >= this.center[0]-this.half_size && mouseX <= this.center[0]+this.half_size &&
mouseY >= this.center[1]-this.half_size && mouseY <= this.center[1]+this.half_size
)
}
}
let mc
function mouseClicked() {
mc = true
}
let P // Projection handler for selecting 3D object on 2D screen.
let axes // Debug axes
let hud // HUD
let cam_control // wasd + arrows movement.
let cam
let t = ""
let targets = []
let tracked_targets = []
let cb_show_hitbox
let cb_show_axes
let cb_freeze_objects
let cb_freeze_cam
let vcam_look
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
cam = createCamera()
P = new Projections(cam)
hud = new EasyHUD(cam)
cam_control = new CamControl(cam)
axes = new DebugAxes()
vcam_look = createVector()
let cb_size = height/30
cb_show_hitbox = new CheckBox(width-10-cb_size/2, cb_size, cb_size)
cb_show_hitbox.text = "Show hitboxes"
cb_show_axes = new CheckBox(width-10-cb_size/2, cb_size*2+cb_size*0.5, cb_size, true)
cb_show_axes.text = "Show axes"
cb_freeze_objects = new CheckBox(width-10-cb_size/2, cb_size*3+cb_size*1, cb_size, true)
cb_freeze_objects.text = "Freeze objects"
cb_freeze_cam = new CheckBox(width-10-cb_size/2, cb_size*4+cb_size*1.5, cb_size)
cb_freeze_cam.text = "Control cam"
textFont(f)
// Set perspective (optional)
let eyeZ = ((height/2) / tan(PI/6))
perspective(PI/3, width/height, eyeZ/10, eyeZ*10) // Default perspective if not set.
// perspective(PI/2, width/height, eyeZ/10, eyeZ*10) // wide angle
// perspective(PI/6, width/height, eyeZ/10, eyeZ*10) // zoom angle
// Define target objects
let z = -1000
targets.push(
{pos : [50, 0, 0], size : 10, selected : false, targeted : false},
{pos : [150, 0, 0], size : 10, selected : false, targeted : false},
{pos : [250, 0, 0], size : 10, selected : false, targeted : false},
{pos : [250, -100, 0], size : 10, selected : false, targeted : false},
{pos : [150, -200, 0], size : 10, selected : false, targeted : false},
{pos : [0, -200, 50], size : 10, selected : false, targeted : false},
{pos : [0, -100, 150], size : 10, selected : false, targeted : false},
{pos : [0, 0, 150], size : 10, selected : false, targeted : false},
{pos : [0, -100, 250], size : 10, selected : false, targeted : false},
{pos : [0, -200, 250], size : 10, selected : false, targeted : false},
{pos : [0, 0, -50], size : 10, selected : false, targeted : false},
{pos : [0, -200, -50], size : 10, selected : false, targeted : false},
{pos : [0, -100, -150], size : 10, selected : false, targeted : false},
{pos : [0, 0, -150], size : 10, selected : false, targeted : false},
{pos : [0, -100, -250], size : 10, selected : false, targeted : false},
{pos : [-50, -100, 0], size : 10, selected : false, targeted : false},
{pos : [-150, 0, 0], size : 10, selected : false, targeted : false},
{pos : [-250, 0, 0], size : 10, selected : false, targeted : false},
{pos : [-250, -100, 0], size : 10, selected : false, targeted : false},
{pos : [-250, -200, 0], size : 10, selected : false, targeted : false},
)
}
function draw() {
background(227, 200, 166);
cam_control.update()
// Lights
vcam_look.setMag(0)
vcam_look.add(cam.centerX, cam.centerY, cam.centerZ)
vcam_look.sub(cam.eyeX, cam.eyeY, cam.eyeZ)
let ls = 255
directionalLight(ls, ls, ls, vcam_look.x, vcam_look.y, vcam_look.z)
ambientLight(100)
// Move camera
if (!cb_freeze_cam.checked) {
let cam_rotate_x = 0.0003
let cam_rotate_y = 0.00005
cam.eyeX = 500*sin(cam_rotate_x*millis())
cam.eyeZ = 500*cos(cam_rotate_x*millis())
cam.eyeY = -250*sin(cam_rotate_y*millis())
cam.lookAt(0,0,0)
}
// Move objects
if (!cb_freeze_objects.checked) {
for (let i = 0; i < targets.length; i++) {
f = 0.5
targets[i].pos[0] += random(-f, f)
targets[i].pos[1] += random(-f, f)
targets[i].pos[2] += random(-f, f)
}
}
// Draw targets
for (let i = 0; i < targets.length; i++) {
push()
noFill()
noStroke()
strokeWeight(1)
stroke(0)
let col = color(90, 200, 200)
if (targets[i].selected) {
col = color(200, 100, 50)
fill(255)
}
if (targets[i].targeted) {
strokeWeight(2)
stroke(255)
}
ambientMaterial(red(col), green(col), blue(col))
translate(targets[i].pos[0], targets[i].pos[1], targets[i].pos[2])
box(targets[i].size*1.4)
pop()
}
// Convert all targets world positions to screen position so they can be interacted with.
tracked_targets = []
for (let i = 0; i < targets.length; i++) {
let c = targets[i].pos
tracked_targets.push(
{
target : targets[i],
pos : P.world_to_screen([c[0], c[1], c[2]], width, height),
size : targets[i].size
}
)
}
// Lights off for axes and HUD
noLights()
if (cb_show_axes.checked) {
axes.draw()
}
hud.beginHUD()
// Since tracked targets exist in 2D, they are handled inside the HUD.
for (let i = 0; i < tracked_targets.length; i++) {
let screen_dia = P.screen_hitbox_size(tracked_targets[i].pos, tracked_targets[i].size)
// Draw the object hitbox
if (cb_show_hitbox.checked) {
if (screen_dia > 0 && tracked_targets[i].pos[2] > 0) {
noFill()
stroke(255, 0, 0)
strokeWeight(2)
circle(tracked_targets[i].pos[0], tracked_targets[i].pos[1], screen_dia)
}
}
// Detect mouse over object.
if (P.cursor_hit_object(mouseX, mouseY, tracked_targets[i].pos, tracked_targets[i].size, 1.0)) {
tracked_targets[i].target.targeted = true
// Clicked
if (mc) {
tracked_targets[i].target.selected = !tracked_targets[i].target.selected
fill(255, 100, 0)
}
else {
fill(255, 255, 0)
}
}
else {
tracked_targets[i].target.targeted = false
}
// Display target info
fill(0)
noStroke()
textSize(13)
let ot = "World: " + round2_arr(tracked_targets[i].target.pos) + "\nScreen: " + round2_arr([tracked_targets[i].pos[0], tracked_targets[i].pos[1]] , 0)
text(ot, tracked_targets[i].pos[0]+screen_dia/2, tracked_targets[i].pos[1])
}
// HUD background
if (1) {
fill(0, 0, 0, 75)
noStroke()
rect(0, 0, width, height/4)
textSize(32*windowWidth/1300)
fill(220, 240, 0)
t = "Move cam with wasd, shift, space and rotate with arrow keys."
t += "\n" + "Click to select targets."
t += "\n" + "Copy the Projection class to your program. "
t += "\n" + ""
t += "\n" + "cam center: " + round2_arr(P.camCenter)
t += "\n" + "cam eye: " + round2_arr(P.camEye)
t += "\n" + "cam up: " + round2_arr(P.camUp)
t += "\n" + "mouse: " + round2_arr([mouseX, mouseY])
text(t, 20, 20)
}
// Checkboxes
if (1) {
stroke(200, 220, 0)
cb_show_hitbox.update(mc)
cb_show_axes.update(mc)
cb_freeze_objects.update(mc)
cb_freeze_cam.update(mc)
}
hud.endHUD()
mc = false
}