initial commit
This commit is contained in:
		
							
								
								
									
										611
									
								
								tc.controller.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										611
									
								
								tc.controller.js
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
				
			||||||
							
								
								
									
										6187
									
								
								tc.controller_demo.maxpat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6187
									
								
								tc.controller_demo.maxpat
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user