commit
caf9f41ee0
2 changed files with 6798 additions and 0 deletions
@ -0,0 +1,611 @@ |
|||||||
|
/* SPDX-License-Identifier: GPL-3.0-or-later */ |
||||||
|
/* |
||||||
|
This js file is meant to be used in Cycling'74 Max with the [v8] object. |
||||||
|
It provides a way to control cameras in a typical first-person fashion, |
||||||
|
and move and turn objects in view-space. |
||||||
|
|
||||||
|
Copyright (C) 2025 Théophile Clet <contact@tflcl.xyz> - https://tflcl.xyz
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details: |
||||||
|
<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
autowatch = 1; |
||||||
|
|
||||||
|
outlets = 2; |
||||||
|
|
||||||
|
var drawto = ""; |
||||||
|
declareattribute({ name: "drawto", setter: "setdrawto" }); |
||||||
|
|
||||||
|
var ease = 0.1; |
||||||
|
declareattribute({ |
||||||
|
name: "ease", |
||||||
|
setter: "set_ease", |
||||||
|
label: "Ease", |
||||||
|
type: "float", |
||||||
|
min: 0.0, |
||||||
|
default: 0.1, |
||||||
|
}); |
||||||
|
|
||||||
|
var camera_node = ""; |
||||||
|
declareattribute({ |
||||||
|
name: "camera_node", |
||||||
|
setter: "camera", |
||||||
|
label: "Camera anim node", |
||||||
|
type: "string", |
||||||
|
}); |
||||||
|
|
||||||
|
var flymode = 0; |
||||||
|
declareattribute({ |
||||||
|
name: "flymode", |
||||||
|
setter: "set_flymode", |
||||||
|
style: "onoff", |
||||||
|
default: 0, |
||||||
|
}); |
||||||
|
|
||||||
|
var show_bounds = 1; |
||||||
|
declareattribute({ |
||||||
|
name: "show_bounds", |
||||||
|
setter: "set_show_bounds", |
||||||
|
style: "onoff", |
||||||
|
default: 1, |
||||||
|
}); |
||||||
|
|
||||||
|
const ANIMABLE_GL_OBJECTS = [ |
||||||
|
"jit_gl_node", |
||||||
|
"jit_gl_mesh", |
||||||
|
"jit_gl_gridshape", |
||||||
|
"jit_gl_model", |
||||||
|
"jit_gl_plato", |
||||||
|
"jit_gl_nurbs", |
||||||
|
"jit_gl_text", |
||||||
|
"jit_gl_videoplane", |
||||||
|
"jit_gl_graph", |
||||||
|
"jit_gl_sketch", |
||||||
|
"jit_gl_path", |
||||||
|
"jit_gl_lua", |
||||||
|
"jit_gl_isosurf", |
||||||
|
"jit_gl_cornerpin", |
||||||
|
"jit_gl_skybox", |
||||||
|
"jit_gl_volume", |
||||||
|
"jit_gl_multiple", |
||||||
|
"jit_gl_light", |
||||||
|
"jit_gl_camera", |
||||||
|
]; |
||||||
|
|
||||||
|
let controlled_gl_objects = []; // Used for enabling.disabling drawbounds on object(s) animated by the controller
|
||||||
|
let controllable_objects = []; // Used for listing all controllable objects in a patch
|
||||||
|
|
||||||
|
let top_level_patcher; |
||||||
|
let ctlr; |
||||||
|
|
||||||
|
class Controller { |
||||||
|
constructor(drawto) { |
||||||
|
this.ease = 0.1; // anim.drive easing
|
||||||
|
this.flymode = false; |
||||||
|
this.flymode_applyed = false; // Flag
|
||||||
|
this.control_mode; // 'cam' if the camera is the target, 'obj' otherwise
|
||||||
|
|
||||||
|
this.world_up = [0, 1, 0]; |
||||||
|
this.camera_node = new JitterObject("jit.proxy"); |
||||||
|
this.cam_direction, this.cam_up, this.cam_right; |
||||||
|
|
||||||
|
this.camera_base_matrix; |
||||||
|
this.get_cam_base_matrix(); |
||||||
|
|
||||||
|
// main_node always has its Y axis aligned with world_up direction
|
||||||
|
// It is used with main_drive for up/down movements, Y rotation (yaw) and X-Y movements (accross an horizontal plan)
|
||||||
|
this.main_node = new JitterObject("jit.anim.node"); |
||||||
|
this.main_node.turnmode = "local"; |
||||||
|
this.main_node.movemode = "local"; |
||||||
|
this.main_node.tripod = 1; // for flymode
|
||||||
|
|
||||||
|
this.main_drive = new JitterObject("jit.anim.drive"); |
||||||
|
this.main_drive.drawto = drawto; |
||||||
|
this.main_drive.ease = this.ease; |
||||||
|
this.main_drive.targetname = this.main_node.name; |
||||||
|
|
||||||
|
// pitch_node used with pitch_drive for pitch rotation (over the objects local x axis)
|
||||||
|
this.pitch_node = new JitterObject("jit.anim.node"); |
||||||
|
this.pitch_node.anim = this.main_node.name; |
||||||
|
this.pitch_node.turnmode = "parent"; |
||||||
|
this.pitch_node.movemode = "parent"; |
||||||
|
|
||||||
|
this.pitch_drive = new JitterObject("jit.anim.drive"); |
||||||
|
this.pitch_drive.drawto = drawto; |
||||||
|
this.pitch_drive.ease = this.ease; |
||||||
|
this.pitch_drive.targetname = this.pitch_node.name; |
||||||
|
|
||||||
|
this.target = new JitterObject("jit.proxy"); // Stores the object to control
|
||||||
|
} |
||||||
|
|
||||||
|
// Taking control over a new target
|
||||||
|
control(obj_name, ctrl_mode) { |
||||||
|
if (this.camera_node.class == "") { |
||||||
|
error("No camera defined. Cannot control object.\n"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let new_target; |
||||||
|
if (obj_name != undefined) { |
||||||
|
new_target = get_anim_node(obj_name, this.pitch_node.name); |
||||||
|
} |
||||||
|
|
||||||
|
if ( |
||||||
|
new_target != undefined && |
||||||
|
(this.target.name != "" || this.target.name != new_target) |
||||||
|
) { |
||||||
|
this.control_mode = ctrl_mode; |
||||||
|
|
||||||
|
if (this.target.name != "") { |
||||||
|
const transform = this.target.send("getworldtransform"); // Get the current targets tranfsorm,
|
||||||
|
this.target.send("anim"); // Unbind it (makes it loose its transform part coming from Controller nodes)
|
||||||
|
this.target.send("anim_reset"); // Reset it
|
||||||
|
this.target.send("transform", transform); // And re-apply the transform so it stays in place
|
||||||
|
} |
||||||
|
|
||||||
|
this.target.name = new_target; // Bind to new target
|
||||||
|
this.reset(); // Reset this.Controller anim nodes
|
||||||
|
this.main_node.position = this.target.send("getworldpos"); // Take the target position, rotatexyz and scale and pass them to this.Controller anim nodes
|
||||||
|
let target_rotatexyz = this.target.send("getrotatexyz"); |
||||||
|
this.main_node.rotatexyz = [0, target_rotatexyz[1], 0]; |
||||||
|
this.pitch_node.rotatexyz = [target_rotatexyz[0], 0, target_rotatexyz[2]]; |
||||||
|
this.pitch_node.scale = this.target.send("getscale"); |
||||||
|
|
||||||
|
// And select the new target
|
||||||
|
this.target.send("anim_reset"); |
||||||
|
this.target.send("anim", this.pitch_node.name); |
||||||
|
|
||||||
|
// Apply specific rules depending on if controlling the camera or an object
|
||||||
|
if (this.control_mode == "cam") { |
||||||
|
this.main_node.movemode = "local"; |
||||||
|
this.main_node.turnmode = "local"; |
||||||
|
this.pitch_node.turnmode = "parent"; |
||||||
|
this.has_rotated = false; |
||||||
|
this.set_flymode(this.flymode); |
||||||
|
} else { |
||||||
|
this.flymode_applyed = false; |
||||||
|
// When controlling an object, we convert the translation vector
|
||||||
|
// from screen space to world space in this.move
|
||||||
|
// So we need the cam base matrix
|
||||||
|
this.main_node.movemode = "world"; |
||||||
|
this.main_node.turnmode = "world"; |
||||||
|
this.pitch_node.turnmode = "world"; |
||||||
|
this.get_cam_base_matrix(); |
||||||
|
} |
||||||
|
|
||||||
|
// No target
|
||||||
|
} else if (obj_name == undefined && this.target.name != "") { |
||||||
|
this.target.send("anim"); |
||||||
|
this.target.send("anim_reset"); |
||||||
|
this.target.send("transform", this.pitch_node.worldtransform); |
||||||
|
this.target.name = ""; |
||||||
|
} |
||||||
|
outlet(0, "control", obj_name, this.target.name); |
||||||
|
} |
||||||
|
|
||||||
|
camera(obj_name) { |
||||||
|
const cam_anim_node_name = get_anim_node(obj_name, this.pitch_node.name); |
||||||
|
if (cam_anim_node_name != undefined) { |
||||||
|
this.camera_node.name = cam_anim_node_name; |
||||||
|
camera_node = this.camera_node.name; |
||||||
|
this.camera_node.send("animmode", "parent"); |
||||||
|
this.get_cam_base_matrix(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
get_cam_base_matrix() { |
||||||
|
if (this.camera_node.name != "") { |
||||||
|
this.cam_direction = this.camera_node.send("getdirection"); |
||||||
|
this.cam_direction[1] = 0; |
||||||
|
this.cam_direction = normalize(this.cam_direction); |
||||||
|
this.cam_right = normalize(cross(this.cam_direction, this.world_up)); |
||||||
|
this.cam_up = normalize(cross(this.cam_right, this.cam_direction)); |
||||||
|
this.camera_base_matrix = [ |
||||||
|
this.cam_right, |
||||||
|
this.cam_up, |
||||||
|
this.cam_direction, |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
move(x, y, z) { |
||||||
|
let translat_vec = [x, y, z]; |
||||||
|
if (this.control_mode == "obj") { |
||||||
|
let cam_x_rot = this.camera_node.send("getrotatexyz")[0]; |
||||||
|
// Inverse translation direction if cam is upside down
|
||||||
|
if (cam_x_rot < -90 || cam_x_rot > 90) { |
||||||
|
x *= -1; |
||||||
|
z *= -1; |
||||||
|
} |
||||||
|
translat_vec = multiplyMatrixVector(this.camera_base_matrix, [x, 0, -z]); |
||||||
|
} |
||||||
|
this.main_drive.move(translat_vec[0], y, translat_vec[2]); |
||||||
|
} |
||||||
|
|
||||||
|
turn(x, y, z) { |
||||||
|
let rot_vec = [x, y, z]; |
||||||
|
if (this.control_mode == "obj") { |
||||||
|
rot_vec = multiplyMatrixVector(this.camera_base_matrix, [-x, 0, 0]); |
||||||
|
rot_vec[1] = -y; |
||||||
|
} |
||||||
|
if (this.flymode && this.control_mode == "cam") { |
||||||
|
// When flymode is enabled, we apply all rotations to the main_node
|
||||||
|
// so that the cam can move toward the direction it is pointing to
|
||||||
|
this.main_drive.turn(rot_vec[0], rot_vec[1], 0); |
||||||
|
this.pitch_drive.turn(0, 0, 0); |
||||||
|
} else { |
||||||
|
this.main_drive.turn(0, rot_vec[1], 0); |
||||||
|
this.pitch_drive.turn(rot_vec[0], 0, rot_vec[2]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
elev(y) { |
||||||
|
this.main_drive.move([0, y, 0]); |
||||||
|
} |
||||||
|
|
||||||
|
set_flymode(v) { |
||||||
|
// When enabled, the camera can move toward any direction it faces.
|
||||||
|
// When disabled, it should not move on the world y axis ("walk mode") unless explicitely sending move messages with non-null Y component.
|
||||||
|
if (v == 1) { |
||||||
|
this.flymode = true; |
||||||
|
} else { |
||||||
|
this.flymode = false; |
||||||
|
} |
||||||
|
this.apply_flymode(); |
||||||
|
} |
||||||
|
|
||||||
|
apply_flymode() { |
||||||
|
if (this.control_mode == "cam") { |
||||||
|
if (this.flymode) { |
||||||
|
// When enabling flymode, we move all rotations to main_node
|
||||||
|
this.main_node.rotatexyz = [ |
||||||
|
this.pitch_node.rotatexyz[0], |
||||||
|
this.main_node.rotatexyz[1], |
||||||
|
0, |
||||||
|
]; |
||||||
|
this.pitch_node.anim_reset(); |
||||||
|
this.flymode_applyed = true; |
||||||
|
} else if (this.flymode_applyed) { |
||||||
|
// And we revert when disabling
|
||||||
|
let rotatexyz = this.main_node.rotatexyz; |
||||||
|
this.pitch_node.anim_reset(); |
||||||
|
this.pitch_node.rotatexyz = [rotatexyz[0], 0, 0]; |
||||||
|
this.main_node.rotatexyz = [0, rotatexyz[1], rotatexyz[2]]; |
||||||
|
this.flymode_applyed = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set_drawto(v) { |
||||||
|
this.main_drive.drawto = v; |
||||||
|
this.pitch_drive.drawto = v; |
||||||
|
} |
||||||
|
|
||||||
|
set_ease(v) { |
||||||
|
this.ease = v; |
||||||
|
this.main_drive.ease = this.ease; |
||||||
|
this.pitch_drive.ease = this.ease; |
||||||
|
} |
||||||
|
|
||||||
|
reset() { |
||||||
|
this.main_node.anim_reset(); |
||||||
|
this.pitch_node.anim_reset(); |
||||||
|
} |
||||||
|
|
||||||
|
resync() { |
||||||
|
if (this.target.name != "") { |
||||||
|
this.control(this.target.name, this.control_mode); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
destroy() { |
||||||
|
if (this.target.name != "") { |
||||||
|
this.target.send("anim"); |
||||||
|
this.target.send("transform", this.pitch_node.worldtransform); |
||||||
|
} |
||||||
|
this.main_node.freepeer(); |
||||||
|
this.main_drive.freepeer(); |
||||||
|
this.pitch_node.freepeer(); |
||||||
|
this.pitch_drive.freepeer(); |
||||||
|
this.target.freepeer(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
loadbang(); |
||||||
|
|
||||||
|
// Select the object to use as basis for view-space transforms
|
||||||
|
function camera(name) { |
||||||
|
ctlr.camera(name); |
||||||
|
camera_node = ctlr.camera_node.name; |
||||||
|
} |
||||||
|
|
||||||
|
// Select which object to control in view-space
|
||||||
|
function control(obj_name) { |
||||||
|
ctlr.control(obj_name, "obj"); |
||||||
|
if (show_bounds) { |
||||||
|
do_show_bounds(0); |
||||||
|
} |
||||||
|
controlled_gl_objects = []; |
||||||
|
get_gl_obj_controlled_by_ctlr(); |
||||||
|
if (show_bounds) { |
||||||
|
do_show_bounds(show_bounds); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Select which camera to control
|
||||||
|
function control_camera(obj_name) { |
||||||
|
if (obj_name !== undefined) { |
||||||
|
ctlr.control(obj_name, "cam"); |
||||||
|
do_show_bounds(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Output from the second outlet a list of all controllable jit.gl and jit.anim.node objects in the entire patcher hierarchy
|
||||||
|
function controllable() { |
||||||
|
controllable_objects = []; |
||||||
|
top_level_patcher.applydeepif( |
||||||
|
add_to_controllable_list, |
||||||
|
is_controllable_applydeepif |
||||||
|
); |
||||||
|
outlet(1, "controllable"); |
||||||
|
for (let obj of controllable_objects) { |
||||||
|
outlet(1, obj.maxclass, obj.getattr("name")); |
||||||
|
} |
||||||
|
outlet(1, "done"); |
||||||
|
} |
||||||
|
|
||||||
|
function add_to_controllable_list(obj) { |
||||||
|
controllable_objects.push(obj); |
||||||
|
} |
||||||
|
add_to_controllable_list.local = 1; |
||||||
|
|
||||||
|
function is_controllable_applydeepif(obj) { |
||||||
|
return ( |
||||||
|
obj.maxclass == "jit.anim.node" || |
||||||
|
is_controllable(obj.maxclass.replaceAll(".", "_")) |
||||||
|
); |
||||||
|
} |
||||||
|
is_controllable_applydeepif.local = 1; |
||||||
|
|
||||||
|
function resync() { |
||||||
|
ctlr.resync(); |
||||||
|
} |
||||||
|
|
||||||
|
function reset() { |
||||||
|
ctlr.reset(); |
||||||
|
} |
||||||
|
|
||||||
|
function move(x, y, z) { |
||||||
|
ctlr.move(x, y, z); |
||||||
|
} |
||||||
|
|
||||||
|
function turn(x, y, z) { |
||||||
|
ctlr.turn(x, y, z); |
||||||
|
} |
||||||
|
|
||||||
|
function elev(v) { |
||||||
|
ctlr.elev(v); |
||||||
|
} |
||||||
|
|
||||||
|
function set_ease(v) { |
||||||
|
ctlr.set_ease(v); |
||||||
|
ease = v; |
||||||
|
} |
||||||
|
|
||||||
|
function set_flymode(v) { |
||||||
|
flymode = v == undefined ? !flymode : v == 1; // If flymode with no argument is provided, act as a toggle
|
||||||
|
ctlr.set_flymode(flymode); |
||||||
|
} |
||||||
|
|
||||||
|
function set_show_bounds(v) { |
||||||
|
show_bounds = v == true; |
||||||
|
// ctlr.show_bounds = show_bounds;
|
||||||
|
do_show_bounds(show_bounds); |
||||||
|
} |
||||||
|
|
||||||
|
function do_show_bounds(v) { |
||||||
|
const objects_without_bounds = [ |
||||||
|
"jit.gl.sketch", |
||||||
|
"jit.gl.skybox", |
||||||
|
"jit.gl.camera", |
||||||
|
]; |
||||||
|
for (const obj of controlled_gl_objects) { |
||||||
|
if (!objects_without_bounds.includes(obj.maxclass)) { |
||||||
|
obj.setattr("drawbounds", v); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
do_show_bounds.local = 1; |
||||||
|
|
||||||
|
function bang() { |
||||||
|
outlet(0, ctlr.pitch_node.worldtransform); |
||||||
|
} |
||||||
|
|
||||||
|
function loadbang() { |
||||||
|
if (!top_level_patcher) { |
||||||
|
top_level_patcher = get_top_level_patcher(); |
||||||
|
ctlr = new Controller(drawto); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function notifydeleted() { |
||||||
|
ctlr.destroy(); |
||||||
|
implicit_tracker.freepeer(); |
||||||
|
} |
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// HELPER METHODS
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
|
||||||
|
function get_anim_node(jit_obj_name, ctlr_node) { |
||||||
|
const proxy = new JitterObject("jit.proxy"); |
||||||
|
proxy.name = jit_obj_name; |
||||||
|
const proxy_class = proxy.class; |
||||||
|
proxy.freepeer(); |
||||||
|
if (proxy_class == "jit_anim_node") { |
||||||
|
return jit_obj_name; |
||||||
|
} else if (proxy_class != "" && is_controllable(proxy_class)) { |
||||||
|
const context_anim = get_context_anim(jit_obj_name); |
||||||
|
return get_top_level_anim_node(jit_obj_name, context_anim, ctlr_node); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
get_anim_node.local = 1; |
||||||
|
|
||||||
|
function is_controllable(obj_class) { |
||||||
|
// Using jit.proxy syntax (ie 'jit_gl_mesh' and not 'jit.gl.mesh')
|
||||||
|
return ANIMABLE_GL_OBJECTS.includes(obj_class); |
||||||
|
} |
||||||
|
is_controllable.local = 1; |
||||||
|
|
||||||
|
function get_context_anim(jit_obj_name) { |
||||||
|
// Get the first implicit jit.anim.node level for the object's rendering context
|
||||||
|
const proxy = new JitterObject("jit.proxy"); |
||||||
|
proxy.name = jit_obj_name; |
||||||
|
proxy.name = proxy.send("getdrawto"); |
||||||
|
const context_name = proxy.send("getanim"); |
||||||
|
proxy.freepeer(); |
||||||
|
return context_name; |
||||||
|
} |
||||||
|
get_context_anim.local = 1; |
||||||
|
|
||||||
|
function get_top_level_anim_node(jit_obj_name, context_anim, ctlr_node) { |
||||||
|
// Recursively get the top level jit.anim.node before reaching context_anim, or no anim.node, or this.pitch_node (if trying to controller currently controlled object)
|
||||||
|
const proxy = new JitterObject("jit.proxy"); |
||||||
|
proxy.name = jit_obj_name; |
||||||
|
const parent_name = proxy.send("getanim").toString(); |
||||||
|
proxy.freepeer(); |
||||||
|
if ( |
||||||
|
parent_name == "" || |
||||||
|
parent_name == context_anim || |
||||||
|
parent_name == ctlr_node |
||||||
|
) { |
||||||
|
return jit_obj_name; |
||||||
|
} else { |
||||||
|
return get_top_level_anim_node(parent_name, context_anim, ctlr_node); |
||||||
|
} |
||||||
|
} |
||||||
|
get_top_level_anim_node.local = 1; |
||||||
|
|
||||||
|
function get_top_level_patcher() { |
||||||
|
let prev = 0; |
||||||
|
let owner = this.patcher.box; |
||||||
|
while (owner) { |
||||||
|
prev = owner; |
||||||
|
owner = owner.patcher.box; |
||||||
|
} |
||||||
|
return prev ? prev.patcher : this.patcher; |
||||||
|
} |
||||||
|
get_top_level_patcher.local = 1; |
||||||
|
|
||||||
|
// Get gl object(s) controlled by ctlr (for the sole purpose of drawing bounds of selected object)
|
||||||
|
function get_gl_obj_controlled_by_ctlr() { |
||||||
|
top_level_patcher.applydeepif( |
||||||
|
add_to_controlled_obj_list, |
||||||
|
is_controlled_by_ctlr |
||||||
|
); |
||||||
|
} |
||||||
|
get_gl_obj_controlled_by_ctlr.local = 1; |
||||||
|
|
||||||
|
function is_controlled_by_ctlr(obj) { |
||||||
|
if (is_controllable(obj.maxclass.replaceAll(".", "_"))) { |
||||||
|
const obj_anim = obj.getattr("anim"); |
||||||
|
if (obj_anim == ctlr.pitch_node.name) { |
||||||
|
return true; |
||||||
|
} else if (obj_anim != "") { |
||||||
|
const proxy = new JitterObject("jit.proxy"); |
||||||
|
proxy.name = obj_anim; |
||||||
|
const parent_name = proxy.send("getanim").toString(); |
||||||
|
proxy.freepeer(); |
||||||
|
return parent_name == ctlr.pitch_node.name; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
is_controlled_by_ctlr.local = 1; |
||||||
|
|
||||||
|
function add_to_controlled_obj_list(obj) { |
||||||
|
controlled_gl_objects.push(obj); |
||||||
|
} |
||||||
|
add_to_controlled_obj_list.local = 1; |
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// MATHS
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
|
||||||
|
function normalize(v) { |
||||||
|
const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); |
||||||
|
if (len === 0) return v; |
||||||
|
return [v[0] / len, v[1] / len, v[2] / len]; |
||||||
|
} |
||||||
|
normalize.local = 1; |
||||||
|
|
||||||
|
function cross(a, b) { |
||||||
|
return [ |
||||||
|
a[1] * b[2] - a[2] * b[1], |
||||||
|
a[2] * b[0] - a[0] * b[2], |
||||||
|
a[0] * b[1] - a[1] * b[0], |
||||||
|
]; |
||||||
|
} |
||||||
|
cross.local = 1; |
||||||
|
|
||||||
|
function dot(a, b) { |
||||||
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; |
||||||
|
} |
||||||
|
dot.local = 1; |
||||||
|
|
||||||
|
function multiplyMatrixVector(matrix, vector) { |
||||||
|
return [ |
||||||
|
dot(matrix[0], vector), |
||||||
|
dot(matrix[1], vector), |
||||||
|
dot(matrix[2], vector), |
||||||
|
]; |
||||||
|
} |
||||||
|
multiplyMatrixVector.local = 1; |
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// GL Context
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
|
||||||
|
let implicitdrawto = ""; |
||||||
|
let explicitdrawto = false; |
||||||
|
const implicit_tracker = new JitterObject("jit_gl_implicit"); |
||||||
|
const implicit_lstnr = new JitterListener( |
||||||
|
implicit_tracker.name, |
||||||
|
implicit_callback |
||||||
|
); |
||||||
|
|
||||||
|
function implicit_callback(event) { |
||||||
|
if (!explicitdrawto && implicitdrawto != implicit_tracker.drawto[0]) { |
||||||
|
// important! drawto is an array so get first element
|
||||||
|
implicitdrawto = implicit_tracker.drawto[0]; |
||||||
|
dosetdrawto(implicitdrawto); |
||||||
|
} |
||||||
|
} |
||||||
|
implicit_callback.local = 1; |
||||||
|
|
||||||
|
function setdrawto(val) { |
||||||
|
explicitdrawto = true; |
||||||
|
dosetdrawto(val); |
||||||
|
} |
||||||
|
setdrawto.local = 1; |
||||||
|
|
||||||
|
function dosetdrawto(arg) { |
||||||
|
if (arg === drawto || !arg) { |
||||||
|
// bounce
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
drawto = arg; |
||||||
|
ctlr.set_drawto(drawto); |
||||||
|
} |
||||||
|
dosetdrawto.local = 1; |
Loading…
Reference in new issue