From 4e0598e3211a0df5780aa0c88d4f419b2da069d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Clet?= Date: Wed, 8 Jul 2026 01:09:08 +0200 Subject: [PATCH] cleaned code, custom boundbox (works for anim.nodes too), targetting nodes in the middle of a hierarchy or nodes with multiple childs is now possible --- help/tc.controller_demo.maxhelp | 271 +++++++++++++++++-------- javascript/tc.controller.js | 349 ++++++++++++++------------------ 2 files changed, 346 insertions(+), 274 deletions(-) diff --git a/help/tc.controller_demo.maxhelp b/help/tc.controller_demo.maxhelp index f60dbf3..292eda0 100644 --- a/help/tc.controller_demo.maxhelp +++ b/help/tc.controller_demo.maxhelp @@ -9,18 +9,40 @@ "modernui": 1 }, "classnamespace": "box", - "rect": [ 131.0, 100.0, 1444.0, 848.0 ], + "rect": [ 400.0, 100.0, 967.0, 848.0 ], "integercoordinates": 1, "boxes": [ { "box": { - "id": "obj-9", + "id": "obj-46", "maxclass": "message", "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 361.0, 405.0, 75.0, 22.0 ], - "text": "camera cam" + "patching_rect": [ 160.0, 70.0, 48.0, 22.0 ], + "text": "fps 120" + } + }, + { + "box": { + "id": "obj-25", + "maxclass": "message", + "numinlets": 2, + "numoutlets": 1, + "outlettype": [ "" ], + "patching_rect": [ 287.0, 86.0, 107.0, 22.0 ], + "text": "sendnode getanim" + } + }, + { + "box": { + "id": "obj-22", + "maxclass": "message", + "numinlets": 2, + "numoutlets": 1, + "outlettype": [ "" ], + "patching_rect": [ 203.0, 78.0, 41.0, 22.0 ], + "text": "fps 30" } }, { @@ -29,7 +51,7 @@ "maxclass": "newobj", "numinlets": 1, "numoutlets": 0, - "patching_rect": [ 1395.0, 188.0, 88.0, 22.0 ], + "patching_rect": [ 1408.0, 188.0, 88.0, 22.0 ], "text": "s object_select" } }, @@ -50,7 +72,7 @@ "numinlets": 0, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 273.0, 493.0, 86.0, 22.0 ], + "patching_rect": [ 327.0, 482.0, 86.0, 22.0 ], "text": "r object_select" } }, @@ -61,7 +83,7 @@ "maxclass": "newobj", "numinlets": 1, "numoutlets": 0, - "patching_rect": [ 1299.0, 188.0, 93.0, 22.0 ], + "patching_rect": [ 1312.0, 188.0, 93.0, 22.0 ], "text": "s to_tc.fpcontrol" } }, @@ -871,7 +893,7 @@ } ] }, - "patching_rect": [ 1299.0, 155.0, 115.0, 22.0 ], + "patching_rect": [ 1312.0, 155.0, 115.0, 22.0 ], "text": "p gamepad_buttons" } }, @@ -893,8 +915,8 @@ "maxclass": "comment", "numinlets": 1, "numoutlets": 0, - "patching_rect": [ 315.0, 532.0, 256.0, 24.0 ], - "text": "Select which object to control in view space" + "patching_rect": [ 369.0, 521.0, 181.0, 24.0 ], + "text": "Select which object to control" } }, { @@ -904,21 +926,10 @@ "numinlets": 1, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 53.0, 612.0, 72.0, 22.0 ], + "patching_rect": [ 53.0, 595.0, 72.0, 22.0 ], "text": "prepend set" } }, - { - "box": { - "color": [ 0.282352941176471, 0.537254901960784, 0.905882352941176, 1.0 ], - "id": "obj-32", - "maxclass": "newobj", - "numinlets": 1, - "numoutlets": 0, - "patching_rect": [ 211.0, 589.0, 93.0, 22.0 ], - "text": "s to_tc.fpcontrol" - } - }, { "box": { "id": "obj-31", @@ -926,7 +937,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 211.0, 561.0, 61.0, 22.0 ], + "patching_rect": [ 265.0, 550.0, 61.0, 22.0 ], "text": "control $2" } }, @@ -980,7 +991,7 @@ "numinlets": 1, "numoutlets": 1, "outlettype": [ "int" ], - "patching_rect": [ 249.0, 218.0, 22.0, 22.0 ], + "patching_rect": [ 208.0, 217.0, 22.0, 22.0 ], "text": "t 1" } }, @@ -1013,8 +1024,8 @@ "numinlets": 2, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 184.0, 186.0, 310.0, 22.0 ], - "text": "zl.sect jit.gl.skybox jit.gl.sketch jit.gl.camera jit.anim.node" + "patching_rect": [ 184.0, 186.0, 239.0, 22.0 ], + "text": "zl.sect jit.gl.skybox jit.gl.sketch jit.gl.camera" } }, { @@ -1025,7 +1036,7 @@ "maxclass": "comment", "numinlets": 1, "numoutlets": 0, - "patching_rect": [ 499.0, 165.0, 200.0, 64.0 ], + "patching_rect": [ 425.0, 165.0, 200.0, 64.0 ], "text": "For the purpose of a seamless experience, filtering out objects we don't want to control in this patch, even if you could!" } }, @@ -1149,7 +1160,7 @@ } ] }, - "patching_rect": [ 129.0, 493.0, 133.0, 22.0 ], + "patching_rect": [ 183.0, 482.0, 133.0, 22.0 ], "text": "p controllable_populate" } }, @@ -1181,9 +1192,42 @@ "modernui": 1 }, "classnamespace": "box", - "rect": [ 478.0, 100.0, 405.0, 755.0 ], + "rect": [ 573.0, 140.0, 866.0, 755.0 ], "integercoordinates": 1, "boxes": [ + { + "box": { + "id": "obj-15", + "maxclass": "newobj", + "numinlets": 1, + "numoutlets": 2, + "outlettype": [ "", "" ], + "patching_rect": [ 448.0, 296.0, 275.0, 22.0 ], + "text": "jit.anim.node @name plato_anim @position -4 0 0" + } + }, + { + "box": { + "id": "obj-14", + "maxclass": "newobj", + "numinlets": 1, + "numoutlets": 2, + "outlettype": [ "", "" ], + "patching_rect": [ 27.0, 296.0, 272.0, 22.0 ], + "text": "jit.anim.node @name torus_anim @position 4 0 0" + } + }, + { + "box": { + "id": "obj-3", + "maxclass": "newobj", + "numinlets": 1, + "numoutlets": 2, + "outlettype": [ "", "" ], + "patching_rect": [ 27.0, 231.0, 199.0, 22.0 ], + "text": "jit.anim.node @name torus_&_plato" + } + }, { "box": { "id": "obj-6", @@ -1191,7 +1235,7 @@ "numinlets": 0, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 59.0, 595.0, 61.0, 22.0 ], + "patching_rect": [ 20.0, 635.0, 61.0, 22.0 ], "text": "r reset_all" } }, @@ -1203,7 +1247,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 59.0, 625.0, 91.0, 35.0 ], + "patching_rect": [ 20.0, 665.0, 91.0, 35.0 ], "text": "anim_reset, position 0 0 0" } }, @@ -1214,7 +1258,7 @@ "numinlets": 1, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 163.0, 514.0, 54.0, 22.0 ], + "patching_rect": [ 124.0, 554.0, 54.0, 22.0 ], "text": "deferlow" } }, @@ -1227,7 +1271,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 531.0, 638.0, 137.0, 23.0 ], + "patching_rect": [ 492.0, 678.0, 137.0, 23.0 ], "text": "read baluster-vase.gltf" } }, @@ -1240,7 +1284,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 330.0, 638.0, 189.0, 23.0 ], + "patching_rect": [ 291.0, 678.0, 189.0, 23.0 ], "text": "read pocillopora-damicornis.gltf" } }, @@ -1251,7 +1295,7 @@ "numinlets": 1, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 483.0, 547.0, 70.0, 22.0 ], + "patching_rect": [ 444.0, 587.0, 70.0, 22.0 ], "text": "loadmess 2" } }, @@ -1264,7 +1308,7 @@ "numoutlets": 2, "outlettype": [ "", "bang" ], "parameter_enable": 0, - "patching_rect": [ 576.0, 576.0, 50.0, 22.0 ] + "patching_rect": [ 537.0, 616.0, 50.0, 22.0 ] } }, { @@ -1274,7 +1318,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 576.0, 603.0, 86.0, 22.0 ], + "patching_rect": [ 537.0, 643.0, 86.0, 22.0 ], "text": "metalness $1" } }, @@ -1287,7 +1331,7 @@ "numoutlets": 2, "outlettype": [ "", "bang" ], "parameter_enable": 0, - "patching_rect": [ 483.0, 576.0, 50.0, 22.0 ] + "patching_rect": [ 444.0, 616.0, 50.0, 22.0 ] } }, { @@ -1297,7 +1341,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 483.0, 603.0, 87.0, 22.0 ], + "patching_rect": [ 444.0, 643.0, 87.0, 22.0 ], "text": "roughness $1" } }, @@ -1308,7 +1352,7 @@ "numinlets": 1, "numoutlets": 1, "outlettype": [ "bang" ], - "patching_rect": [ 163.0, 482.0, 58.0, 22.0 ], + "patching_rect": [ 124.0, 522.0, 58.0, 22.0 ], "text": "loadbang" } }, @@ -1319,7 +1363,7 @@ "numinlets": 2, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 163.0, 603.0, 264.0, 22.0 ], + "patching_rect": [ 124.0, 643.0, 264.0, 22.0 ], "text": "substitute jit_gl_texture environment_texture" } }, @@ -1330,7 +1374,7 @@ "numinlets": 1, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 163.0, 638.0, 145.0, 22.0 ], + "patching_rect": [ 124.0, 678.0, 145.0, 22.0 ], "text": "prepend sendmaterial 1" } }, @@ -1341,7 +1385,7 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 163.0, 671.0, 378.0, 22.0 ], + "patching_rect": [ 124.0, 711.0, 378.0, 22.0 ], "text": "jit.gl.model @file baluster-vase.gltf @material_mode 3 @name model" } }, @@ -1352,7 +1396,7 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 249.0, 514.0, 100.0, 22.0 ], + "patching_rect": [ 210.0, 554.0, 100.0, 22.0 ], "text": "jit.gl.skybox" } }, @@ -1363,7 +1407,7 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "jit_gl_texture", "" ], - "patching_rect": [ 163.0, 547.0, 293.0, 22.0 ], + "patching_rect": [ 124.0, 587.0, 293.0, 22.0 ], "text": "jit.gl.environment @file umhlanga_sunrise_2k.exr" } }, @@ -1374,7 +1418,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 210.0, 301.0, 265.0, 22.0 ], + "patching_rect": [ 780.0, 351.0, 265.0, 22.0 ], "text": "position 0 -2 0, rotatexyz -90 0 0, scale 10 10 10" } }, @@ -1385,7 +1429,7 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "bang", "bang" ], - "patching_rect": [ 27.0, 236.0, 32.0, 22.0 ], + "patching_rect": [ 780.0, 187.0, 32.0, 22.0 ], "text": "t b b" } }, @@ -1396,7 +1440,7 @@ "numinlets": 0, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 27.0, 209.0, 61.0, 22.0 ], + "patching_rect": [ 780.0, 160.0, 61.0, 22.0 ], "text": "r reset_all" } }, @@ -1407,7 +1451,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 119.0, 301.0, 84.0, 22.0 ], + "patching_rect": [ 448.0, 267.0, 84.0, 22.0 ], "text": "position -4 0 0" } }, @@ -1418,7 +1462,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 27.0, 301.0, 80.0, 22.0 ], + "patching_rect": [ 220.0, 267.0, 80.0, 22.0 ], "text": "position 4 0 0" } }, @@ -1440,7 +1484,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 40.0, 268.0, 68.0, 22.0 ], + "patching_rect": [ 793.0, 216.0, 68.0, 22.0 ], "text": "anim_reset" } }, @@ -1451,8 +1495,8 @@ "numinlets": 1, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 440.0, 140.0, 559.0, 22.0 ], - "text": "jit.gl.light @shadows 1 @shadowquality hi @direction 0.4 -0.8 -1. @shadowblur 0.1 @shadowrange 50." + "patching_rect": [ 364.0, 110.0, 650.0, 22.0 ], + "text": "jit.gl.light @name dir_light @shadows 1 @shadowquality hi @direction 0.4 -0.8 -1. @shadowblur 0.1 @shadowrange 50." } }, { @@ -1473,7 +1517,7 @@ "numinlets": 8, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 210.0, 236.0, 117.0, 22.0 ], + "patching_rect": [ 27.0, 453.0, 117.0, 22.0 ], "text": "jit.gl.pbr @name pbr" } }, @@ -1484,7 +1528,7 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 27.0, 404.0, 613.0, 22.0 ], + "patching_rect": [ 27.0, 402.0, 613.0, 22.0 ], "text": "jit.gl.gridshape @name plane @shape plane @material pbr @position 0 -2 0 @rotatexyz -90 0 0 @scale 10 10 10" } }, @@ -1495,8 +1539,8 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 27.0, 368.0, 383.0, 22.0 ], - "text": "jit.gl.plato @name plato @shape icosa @material pbr @position -4 0 0", + "patching_rect": [ 448.0, 351.0, 293.0, 22.0 ], + "text": "jit.gl.plato @name plato @shape icosa @material pbr", "varname": "icosa" } }, @@ -1507,8 +1551,8 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 27.0, 332.0, 405.0, 22.0 ], - "text": "jit.gl.gridshape @name torus @shape torus @material pbr @position 4 0 0", + "patching_rect": [ 27.0, 351.0, 319.0, 22.0 ], + "text": "jit.gl.gridshape @name torus @shape torus @material pbr", "varname": "torus" } }, @@ -1533,7 +1577,7 @@ "numoutlets": 1, "outlettype": [ "" ], "parameter_enable": 0, - "patching_rect": [ 440.0, 109.0, 206.0, 22.0 ] + "patching_rect": [ 364.0, 79.0, 206.0, 22.0 ] } }, { @@ -1557,7 +1601,7 @@ "numoutlets": 1, "outlettype": [ "" ], "parameter_enable": 0, - "patching_rect": [ 654.0, 109.0, 150.0, 22.0 ] + "patching_rect": [ 585.0, 79.0, 150.0, 22.0 ] } }, { @@ -1569,7 +1613,7 @@ "numoutlets": 1, "outlettype": [ "" ], "parameter_enable": 0, - "patching_rect": [ 812.0, 109.0, 210.0, 22.0 ] + "patching_rect": [ 736.0, 79.0, 210.0, 22.0 ] } } ], @@ -1592,6 +1636,12 @@ "source": [ "obj-12", 0 ] } }, + { + "patchline": { + "destination": [ "obj-5", 0 ], + "source": [ "obj-14", 0 ] + } + }, { "patchline": { "destination": [ "obj-30", 0 ], @@ -1606,13 +1656,19 @@ }, { "patchline": { - "destination": [ "obj-5", 0 ], + "destination": [ "obj-7", 0 ], + "source": [ "obj-15", 0 ] + } + }, + { + "patchline": { + "destination": [ "obj-14", 0 ], "source": [ "obj-170", 0 ] } }, { "patchline": { - "destination": [ "obj-7", 0 ], + "destination": [ "obj-15", 0 ], "source": [ "obj-172", 0 ] } }, @@ -1640,6 +1696,20 @@ "source": [ "obj-29", 0 ] } }, + { + "patchline": { + "destination": [ "obj-14", 0 ], + "order": 1, + "source": [ "obj-3", 0 ] + } + }, + { + "patchline": { + "destination": [ "obj-15", 0 ], + "order": 0, + "source": [ "obj-3", 0 ] + } + }, { "patchline": { "destination": [ "obj-4", 0 ], @@ -1714,22 +1784,29 @@ }, { "patchline": { - "destination": [ "obj-5", 0 ], + "destination": [ "obj-14", 0 ], "order": 2, "source": [ "obj-83", 0 ] } }, { "patchline": { - "destination": [ "obj-7", 0 ], - "order": 1, + "destination": [ "obj-15", 0 ], + "order": 0, + "source": [ "obj-83", 0 ] + } + }, + { + "patchline": { + "destination": [ "obj-3", 0 ], + "order": 3, "source": [ "obj-83", 0 ] } }, { "patchline": { "destination": [ "obj-8", 0 ], - "order": 0, + "order": 1, "source": [ "obj-83", 0 ] } }, @@ -1866,13 +1943,13 @@ { "box": { "id": "obj-56", - "items": [ "jit.gl.gridshape", "torus", ",", "jit.gl.plato", "plato", ",", "jit.gl.gridshape", "plane", ",", "jit.gl.light", "u034001065", ",", "jit.gl.model", "model" ], + "items": [ "jit.gl.gridshape", "torus", ",", "jit.gl.plato", "plato", ",", "jit.gl.gridshape", "plane", ",", "jit.gl.light", "dir_light", ",", "jit.gl.model", "model", ",", "jit.anim.node", "torus_&_plato", ",", "jit.anim.node", "torus_anim", ",", "jit.anim.node", "plato_anim" ], "maxclass": "umenu", "numinlets": 1, "numoutlets": 3, "outlettype": [ "int", "", "" ], "parameter_enable": 0, - "patching_rect": [ 129.0, 533.0, 184.0, 22.0 ] + "patching_rect": [ 183.0, 522.0, 184.0, 22.0 ] } }, { @@ -1897,7 +1974,7 @@ "maxclass": "comment", "numinlets": 1, "numoutlets": 0, - "patching_rect": [ 18.0, 684.0, 257.0, 66.0 ], + "patching_rect": [ 36.0, 654.0, 257.0, 66.0 ], "text": "'control' outputs the given object name to be controlled (jit.gl or jit.anim.node), followed by the actual one being controlled (jit.anim.node)" } }, @@ -2438,8 +2515,8 @@ "numinlets": 1, "numoutlets": 2, "outlettype": [ "", "" ], - "patching_rect": [ 720.0, 121.0, 598.0, 22.0 ], - "text": "gamepad.drive @l_deadzone 0.1 @r_deadzone 0.2 @l_sensitivity 0.4 @r_sensitivity 3 @trigger_sensitivity 0.4" + "patching_rect": [ 720.0, 121.0, 611.0, 22.0 ], + "text": "gamepad.drive @l_deadzone 0.15 @r_deadzone 0.15 @l_sensitivity 0.4 @r_sensitivity 3 @trigger_sensitivity 0.4" } }, { @@ -2507,8 +2584,7 @@ "numinlets": 2, "numoutlets": 1, "outlettype": [ "" ], - "patching_rect": [ 53.0, 647.0, 222.0, 22.0 ], - "text": "control u998010725 u998010725" + "patching_rect": [ 53.0, 630.0, 222.0, 22.0 ] } }, { @@ -2568,7 +2644,7 @@ "numoutlets": 1, "outlettype": [ "" ], "parameter_enable": 0, - "patching_rect": [ 53.0, 313.0, 97.0, 22.0 ], + "patching_rect": [ 53.0, 274.0, 97.0, 22.0 ], "text_width": 46.0 } }, @@ -2581,7 +2657,7 @@ "numoutlets": 1, "outlettype": [ "" ], "parameter_enable": 0, - "patching_rect": [ 53.0, 285.0, 87.0, 22.0 ], + "patching_rect": [ 53.0, 298.0, 87.0, 22.0 ], "text_width": 63.0 } }, @@ -2594,7 +2670,8 @@ "numoutlets": 1, "outlettype": [ "" ], "parameter_enable": 0, - "patching_rect": [ 53.0, 343.0, 121.0, 22.0 ] + "patching_rect": [ 53.0, 346.0, 121.0, 22.0 ], + "text_width": 97.0 } }, { @@ -2619,9 +2696,22 @@ "numoutlets": 1, "outlettype": [ "" ], "parameter_enable": 0, - "patching_rect": [ 53.0, 371.0, 186.0, 22.0 ], + "patching_rect": [ 53.0, 370.0, 186.0, 22.0 ], "text_width": 61.0 } + }, + { + "box": { + "attr": "controlmode", + "id": "obj-8", + "maxclass": "attrui", + "numinlets": 1, + "numoutlets": 1, + "outlettype": [ "" ], + "parameter_enable": 0, + "patching_rect": [ 53.0, 322.0, 186.0, 22.0 ], + "text_width": 87.0 + } } ], "lines": [ @@ -2691,6 +2781,18 @@ "source": [ "obj-202", 0 ] } }, + { + "patchline": { + "destination": [ "obj-3", 0 ], + "source": [ "obj-22", 0 ] + } + }, + { + "patchline": { + "destination": [ "obj-3", 0 ], + "source": [ "obj-25", 0 ] + } + }, { "patchline": { "destination": [ "obj-56", 0 ], @@ -2705,7 +2807,8 @@ }, { "patchline": { - "destination": [ "obj-32", 0 ], + "destination": [ "obj-109", 0 ], + "midpoints": [ 274.5, 573.0, 39.0, 573.0, 39.0, 444.0, 62.5, 444.0 ], "source": [ "obj-31", 0 ] } }, @@ -2733,6 +2836,12 @@ "source": [ "obj-4", 0 ] } }, + { + "patchline": { + "destination": [ "obj-3", 0 ], + "source": [ "obj-46", 0 ] + } + }, { "patchline": { "destination": [ "obj-109", 0 ], @@ -2772,7 +2881,7 @@ { "patchline": { "destination": [ "obj-109", 0 ], - "source": [ "obj-9", 0 ] + "source": [ "obj-8", 0 ] } } ], diff --git a/javascript/tc.controller.js b/javascript/tc.controller.js index c56141e..face97a 100644 --- a/javascript/tc.controller.js +++ b/javascript/tc.controller.js @@ -22,9 +22,6 @@ autowatch = 1; outlets = 2; -// var drawto = ""; -// declareattribute({ name: "drawto", setter: "setdrawto" }); - let world_bangs = false; // is the world banging? const ctx_finder = require("implicit.context.js"); ctx_finder.register_drawto(this, dosetdrawto, set_root); @@ -94,7 +91,6 @@ const ANIMABLE_GL_OBJECTS = [ "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; @@ -107,13 +103,15 @@ class Controller { this.flymode_applyed = false; // Flag this.control_obj_type; // 'cam' if the camera is the target, 'obj' otherwise + this.root_anim_node = null; // The implicit drawto context anim node + this.world_up = [0, 1, 0]; this.camera_target = { name: '', class: '', anim: '', } - this.camera_node_implicit = new JitterObject("jit.anim.node"); // Used if camera isn't attached to an anim.node + this.camera_node_implicit = new JitterObject("jit.anim.node"); // Used if camera isn't attached to an anim.node (using the pre-made anim node of a jit.gl.camera doesn't work for some reason, so we use our own "implicit" one) this.camera_node_implicit.name = "camera_node_implicit"; this.camera_node = new JitterObject("jit.proxy"); // Either proxy of a jit.anim.node defined by user, or of this.camera_node_implicit this.cam_direction, this.cam_up, this.cam_right; @@ -134,7 +132,8 @@ class Controller { 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 = new JitterObject("jit.anim.node"); + this.pitch_node.name = "fp_pitch_node"; this.pitch_node.anim = this.main_node.name; this.pitch_node.turnmode = "parent"; this.pitch_node.movemode = "parent"; @@ -144,7 +143,27 @@ class Controller { 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 + this.target = new JitterObject("jit.proxy"); // Stores the object to control (always an anim.node) + this.last_target_anim_node = null; // Stores which parent anim.node was bound to our target, so we can restore it later + this.target_type; // Class of the target object + + // For displaying bounds, we rely on our own jit.gl.mesh cube. It's simpler than keeping track of each objects draw_bounds attribute, and we can customize its appearance. + this.show_bounds = show_bounds; + this.cube_matrix = new JitterMatrix(3, "float32", 8); + const cube_points = new Float32Array([-1., -1., 1., 1., -1., 1., 1., 1., 1., -1., 1., 1., -1., -1., -1., 1., -1., -1., 1., 1., -1., -1., 1., -1.]); + this.cube_matrix.copyarraytomatrix(cube_points); + this.cube_indexes = new JitterMatrix(1, "char", 24); + const cube_point_indexes = new Uint8Array([0, 1, 1, 2, 2, 3, 3, 0, 0, 4, 1, 5, 2, 6, 3, 7, 4, 5, 5, 6, 6, 7, 7, 4]); + this.cube_indexes.copyarraytomatrix(cube_point_indexes); + this.bounds = new JitterObject("jit.gl.mesh"); + this.bounds.drawto = drawto; + this.bounds.draw_mode = "lines"; + this.bounds.vertex_matrix(this.cube_matrix.name); + this.bounds.index_matrix(this.cube_indexes.name); + this.bounds.anim = this.pitch_node.name; + this.bounds.enable = 0; + + this.dummy_node = new JitterObject("jit.anim.node"); // Only used for quat to euler conversion } // Taking control over a new target @@ -152,52 +171,82 @@ class Controller { if (!this.camera_target.name) { error("No camera defined. Cannot control object.\n"); return false; - } + } + // Make sure the requested object is a jit.anim.node or a controllable object (a jit.gl object with an anim attribute) let new_target; if (obj_name != undefined) { - new_target = get_anim_node(obj_name, this.pitch_node.name); + const p = new JitterObject("jit.proxy"); + p.name = obj_name; + if (p.class === "jit_anim_node") { + new_target = obj_name; + } else if (p.class && is_controllable(p.class)) { + new_target = p.send("getanim"); + }; + this.target_type = p.class; + p.freepeer(); } if ( new_target != undefined && (this.target.name != "" || this.target.name != new_target) - ) { + ) { + // New object to target this.control_obj_type = obj_type; + if (this.target.name != "") { + // If there is already a targeted object, we unbind it and make it preserve its transform + const world_pos = this.target.send("getworldpos"); + const world_quat = this.target.send("getworldquat"); + const world_scale = this.target.send("getworldscale"); + + if (this.last_target_anim_node) { + this.target.send("anim", this.last_target_anim_node); + } else { + this.target.send("anim"); + } + this.target.send("anim_reset"); + + const local = this.world_to_local(this.last_target_anim_node, world_pos, world_quat); + this.target.send("position", local.pos); + this.target.send("quat", local.quat); + this.target.send("scale", world_scale); + } - 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 controller anim nodes - 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"); + // Get target transform + const world_pos = this.target.send("getworldpos"); + const world_quat = this.target.send("getworldquat"); + const world_scale = this.target.send("getworldscale"); + + this.dummy_node.quat = world_quat; + const world_rotatexyz = this.dummy_node.rotatexyz; - // And select the new target - this.target.send("anim_reset"); - this.target.send("anim", this.pitch_node.name); + // Apply it to our control nodes + this.main_node.position = world_pos; + this.main_node.rotatexyz = [0, world_rotatexyz[1], 0]; + this.pitch_node.rotatexyz = [world_rotatexyz[0], 0, world_rotatexyz[2]]; + this.pitch_node.scale = world_scale; + + // And select the new target + this.last_target_anim_node = this.target.send("getanim"); + this.target.send("anim_reset"); + this.target.send("anim", this.pitch_node.name); + this.target.send("update_node"); //Sometimes targetting an anim.node parent with anim.node children doesn't work (children don't get their transform updated). This solves it for some reason. // Apply specific rules depending on if controlling the camera or an object if (this.control_obj_type == "cam") { - // If this.camera() is set before the jit.world starts, there is a chance that camera_node is bound to the wrong node. - // this.camera(this.camera_target.name); this.main_node.movemode = "local"; this.main_node.turnmode = "local"; this.pitch_node.turnmode = "parent"; - this.set_flymode(this.flymode); + 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 if (controlmode == 0) { + // 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"; @@ -209,13 +258,28 @@ class Controller { } } - // 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 = ""; - } + } else if (obj_name == undefined && this.target.name != "") { + // No target, unbinding previous target + const world_pos = this.target.send("getworldpos"); + const world_quat = this.target.send("getworldquat"); + const world_scale = this.target.send("getworldscale"); + + if (this.last_target_anim_node) { + this.target.send("anim", this.last_target_anim_node); + } else { + this.target.send("anim"); + } + this.target.send("anim_reset"); + + const local = this.world_to_local(this.last_target_anim_node, world_pos, world_quat); + this.target.send("position", local.pos); + this.target.send("quat", local.quat); + this.target.send("scale", world_scale); + + this.last_target_anim_node = null; + this.target.name = ""; + } + this.set_bounds(); outlet(0, "control", obj_name, this.target.name); return true; } @@ -237,7 +301,6 @@ class Controller { const transform = cam_anim.send("getworldtransform"); cam_anim.freepeer(); // Then we attach our camera_node_implicit to the camera and restore its world transform - // this.camera_node_implicit.anim_reset(); proxy.send("anim_reset"); proxy.send("anim", this.camera_node_implicit.name); this.camera_node_implicit.transform = transform; @@ -246,7 +309,7 @@ class Controller { if (target) { this.camera_node.name = target; this.camera_node.send("animmode", "world"); - this.get_cam_base_matrix(); + // this.get_cam_base_matrix(); this.camera_target.name = proxy.name; this.camera_target.class = proxy.class; this.camera_target.anim = proxy.anim; @@ -295,12 +358,13 @@ class Controller { this.cam_up, this.cam_direction, ]; - } + } } move(x, y, z) { - let translat_vec = [x, y, z]; - if (this.control_obj_type == "obj" && controlmode == 0) { + let translat_vec = [x, y, z]; + if (this.control_obj_type == "obj" && controlmode == 0) { + this.get_cam_base_matrix(); 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) { @@ -313,8 +377,9 @@ class Controller { } turn(x, y, z) { - let rot_vec = [x, y, z]; - if (this.control_obj_type == "obj") { + let rot_vec = [x, y, z]; + if (this.control_obj_type == "obj") { + this.get_cam_base_matrix(); rot_vec = multiplyMatrixVector(this.camera_base_matrix, [-x, 0, 0]); rot_vec[1] = -y; } @@ -368,7 +433,8 @@ class Controller { set_drawto(v) { this.main_drive.drawto = v; - this.pitch_drive.drawto = v; + this.pitch_drive.drawto = v; + this.bounds.drawto = v; } set_ease(v) { @@ -379,14 +445,47 @@ class Controller { reset() { this.main_node.anim_reset(); - this.pitch_node.anim_reset(); + this.pitch_node.anim_reset(); } resync() { if (this.target.name != "") { this.control(this.target.name, this.control_obj_type); } - } + } + + set_show_bounds(v) { + this.show_bounds = v ? 1 : 0; + this.set_bounds(); + } + + set_bounds() { + if (this.show_bounds && this.control_obj_type === "obj") { + switch (this.target_type) { + case "jit_anim_node": + this.bounds.color = [1, 0, 1, 1]; + break; + default: + this.bounds.color = [1, 1, 1, 1]; + break; + } + this.bounds.enable = 1; + } else { + this.bounds.enable = 0; + } + } + + world_to_local(anim_node_name, world_pos, world_quat) { + if (!anim_node_name) { + return { pos: world_pos, quat: world_quat }; + } + const parent = new JitterObject("jit.proxy"); + parent.name = anim_node_name; + const pos = parent.send("worldtolocal", world_pos); + const quat = parent.send("worldtolocal_quat", world_quat); + parent.freepeer(); + return { pos, quat }; + } destroy() { if (this.target.name != "") { @@ -398,7 +497,11 @@ class Controller { this.main_drive.freepeer(); this.pitch_node.freepeer(); this.pitch_drive.freepeer(); - this.target.freepeer(); + this.target.freepeer(); + this.cube_indexes.freepeer(); + this.cube_matrix.freepeer(); + this.bounds.freepeer(); + this.dummy_node.freepeer(); } } @@ -415,21 +518,13 @@ function set_camera(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) { const answer = ctlr.control(obj_name, "cam"); - if (answer) do_show_bounds(0); + // if (answer) do_show_bounds(0); } } @@ -501,8 +596,7 @@ set_flymode.local = 1; function set_show_bounds(v) { show_bounds = v == true; - // ctlr.show_bounds = show_bounds; - do_show_bounds(show_bounds); + ctlr.set_show_bounds(show_bounds); } set_show_bounds.local = 1; @@ -513,20 +607,6 @@ function set_controlmode(v) { ctlr.control(ctlr.target.name, ctlr.control_obj_type); } -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); } @@ -549,56 +629,12 @@ function notifydeleted() { // 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; @@ -610,37 +646,6 @@ function get_top_level_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 ///////////////////////////////////////////// @@ -699,20 +704,6 @@ function ctx_callback(event) { set_camera(camera); } break; - // case "mouse": - - // break; - // case "mouseidle": - - // break; - - // case "keydown": - // // FF_Utils.Print("KEYDOWN", (event.args[0])); - // break; - - // case "willfree": - // break; - default: // post(event.args); post(); break; @@ -720,34 +711,6 @@ function ctx_callback(event) { } ctx_callback.local = 1; -// let implicitdrawto = ""; -// let explicitdrawto = false; -// const implicit_tracker = new JitterObject("jit_gl_implicit"); -// const implicit_lstnr = new JitterListener( -// implicit_tracker.name, -// implicit_callback -// ); -// const ctx_proxy = new JitterObject("jit.proxy"); - -// 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) { -// if (val) { -// explicitdrawto = true; -// dosetdrawto(val); -// } else { -// explicitdrawto = false; -// } -// } -// setdrawto.local = 1; - function dosetdrawto(arg) { if (arg === drawto || !arg) { // bounce @@ -759,4 +722,4 @@ function dosetdrawto(arg) { ctx_proxy.name = drawto; } -// dosetdrawto.local = 1; +dosetdrawto.local = 1;