From 41663c58b8c547ad32da2b4813b5598c8b782c6d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 12:09:37 -0700 Subject: [PATCH] test app for bullet dynamics --- .../dynamics/dynamics-tests-interface.js | 64 ++ .../tests/dynamics/dynamics-tests.html | 37 + .../developer/tests/dynamics/dynamicsTests.js | 749 ++++++++++++++++++ .../tests/dynamics/dynamicsTests.svg | 69 ++ 4 files changed, 919 insertions(+) create mode 100644 scripts/developer/tests/dynamics/dynamics-tests-interface.js create mode 100644 scripts/developer/tests/dynamics/dynamics-tests.html create mode 100644 scripts/developer/tests/dynamics/dynamicsTests.js create mode 100644 scripts/developer/tests/dynamics/dynamicsTests.svg diff --git a/scripts/developer/tests/dynamics/dynamics-tests-interface.js b/scripts/developer/tests/dynamics/dynamics-tests-interface.js new file mode 100644 index 0000000000..53517fab24 --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamics-tests-interface.js @@ -0,0 +1,64 @@ +"use strict"; +/* globals $, EventBridge */ + +var parameters = { + "lifetime":"integer" +}; + + +function getQueryArgByName(name, url) { + if (!url) { + url = window.location.href; + } + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); +} + + +function addCommandParameters(params) { + // copy from html elements into an associative-array which will get passed (as JSON) through the EventBridge + for (var parameterName in parameters) { + if (parameters.hasOwnProperty(parameterName)) { + var parameterType = parameters[parameterName]; + var strVal = $("#" + parameterName).val(); + if (parameterType == "integer") { + params[parameterName] = parseInt(strVal); + } else if (parameterType == "float") { + params[parameterName] = parseFloat(strVal); + } else { + params[parameterName] = strVal; + } + } + } + return params; +} + + +$(document).ready(function() { + // hook all buttons to EventBridge + $(":button").each(function(index) { + $(this).click(function() { + EventBridge.emitWebEvent(JSON.stringify(addCommandParameters({ "dynamics-tests-command": this.id }))); + }); + }); + + // copy parameters from query-args into elements + for (var parameterName in parameters) { + if (parameters.hasOwnProperty(parameterName)) { + var val = getQueryArgByName(parameterName); + if (val) { + var parameterType = parameters[parameterName]; + if (parameterType == "integer") { + val = parseInt(val); + } else if (parameterType == "float") { + val = parseFloat(val); + } + $("#" + parameterName).val(val.toString()); + } + } + } +}); diff --git a/scripts/developer/tests/dynamics/dynamics-tests.html b/scripts/developer/tests/dynamics/dynamics-tests.html new file mode 100644 index 0000000000..0f324e121c --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamics-tests.html @@ -0,0 +1,37 @@ + + + Dynamics Tests + + + + + + + lifetime: +
+
+ A platform with a lever. The lever can be moved in a cone and rotated. A spring brings it back to its neutral position. +
+
+ A grabbable door with a hinge between it and world-space. +
+
+ A chain of blocks connected by hinges. +
+
+ The block can only move up and down over a range of 1/2 meter. +
+
+ A chain of blocks connected by slider constraints. +
+
+ A chain of spheres connected by ball-and-socket joints between the spheres. +
+
+ A chain of spheres connected by ball-and-socket joints coincident-with the spheres. +
+
+ A self-righting ragdoll. The head is on a weak spring vs the body. + + + diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js new file mode 100644 index 0000000000..18585ef152 --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -0,0 +1,749 @@ + +"use strict"; + +/* global Entities, Script, Tablet, MyAvatar, Vec3 */ + +(function() { // BEGIN LOCAL_SCOPE + + var DYNAMICS_TESTS_URL = Script.resolvePath("dynamics-tests.html"); + var DEFAULT_LIFETIME = 120; // seconds + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + var button = tablet.addButton({ + icon: Script.resolvePath("dynamicsTests.svg"), + text: "Dynamics", + sortOrder: 15 + }); + + + + function coneTwistAndSpringLeverTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: -0.5, z: -2})); + var lifetime = params.lifetime; + + var baseID = Entities.addEntity({ + name: "cone-twist test -- base", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: 0.5, y: 0.2, z: 0.5 }, + position: Vec3.sum(pos, { x: 0, y: 0, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + var leverID = Entities.addEntity({ + name: "cone-twist test -- lever", + type: "Box", + color: { blue: 128, green: 100, red: 200 }, + dimensions: { x: 0.05, y: 1, z: 0.05 }, + position: Vec3.sum(pos, { x: 0, y: 0.7, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addEntity({ + name: "cone-twist test -- handle", + type: "Box", + color: { blue: 30, green: 100, red: 200 }, + dimensions: { x: 0.1, y: 0.08, z: 0.08 }, + position: Vec3.sum(pos, { x: 0, y: 0.7 + 0.5, z:0 }), + dynamic: false, + collisionless: true, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + parentID: leverID, + userData: "{ \"grabbableKey\": { \"grabbable\": false } }" + }); + + Entities.addAction("cone-twist", baseID, { + pivot: { x: 0, y: 0.2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: leverID, + otherPivot: { x: 0, y: -0.55, z: 0 }, + otherAxis: { x: 0, y: 1, z: 0 }, + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: Math.PI / 2, + tag: "cone-twist test" + }); + + Entities.addAction("spring", leverID, { + targetRotation: { x: 0, y: 0, z: 0, w: 1 }, + angularTimeScale: 0.2, + tag: "cone-twist test spring" + }); + + + Entities.editEntity(baseID, { gravity: { x: 0, y: -5, z: 0 } }); + } + + function doorVSWorldTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var doorID = Entities.addEntity({ + name: "door test", + type: "Box", + color: { blue: 128, green: 20, red: 20 }, + dimensions: { x: 1.0, y: 2, z: 0.1 }, + position: pos, + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", doorID, { + pivot: { x: -0.5, y: 0, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + low: 0, + high: Math.PI, + tag: "door hinge test" + }); + } + + function hingeChainTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.28; + var prevEntityID = null; + for (var i = 0; i < 5; i++) { + var newID = Entities.addEntity({ + name: "hinge test " + i, + type: "Box", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: 0.2, y: 0.2, z: 0.1 }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("hinge", prevEntityID, { + pivot: { x: 0, y: offset / 2, z: 0 }, + axis: { x: 1, y: 0, z: 0 }, + otherEntityID: newID, + otherPivot: { x: 0, y: -offset / 2, z: 0 }, + otherAxis: { x: 1, y: 0, z: 0 }, + tag: "A/B hinge test " + i + }); + } + prevEntityID = newID; + } + } + + function sliderVSWorldTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var sliderEntityID = Entities.addEntity({ + name: "slider test", + type: "Box", + color: { blue: 128, green: 20, red: 20 }, + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + position: pos, + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("slider", sliderEntityID, { + point: { x: -0.5, y: 0, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + linearLow: 0, + linearHigh: 0.6, + tag: "slider test" + }); + } + + function sliderChainTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.28; + var prevEntityID = null; + for (var i = 0; i < 7; i++) { + var newID = Entities.addEntity({ + name: "hinge test " + i, + type: "Box", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: 0.2, y: 0.1, z: 0.2 }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("slider", prevEntityID, { + point: { x: 0, y: 0, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: newID, + otherPoint: { x: 0, y: -offset / 2, z: 0 }, + otherAxis: { x: 0, y: 1, z: 0 }, + linearLow: 0, + linearHigh: 0.6, + tag: "A/B slider test " + i + }); + } + prevEntityID = newID; + } + } + + function ballSocketBetweenTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.2; + var diameter = offset - 0.01; + var prevEntityID = null; + for (var i = 0; i < 7; i++) { + var newID = Entities.addEntity({ + name: "ball and socket test " + i, + type: "Sphere", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: diameter, y: diameter, z: diameter }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("ball-socket", prevEntityID, { + pivot: { x: 0, y: offset / 2, z: 0 }, + otherEntityID: newID, + otherPivot: { x: 0, y: -offset / 2, z: 0 }, + tag: "A/B ball-and-socket test " + i + }); + } + prevEntityID = newID; + } + } + + function ballSocketCoincidentTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.2; + var diameter = offset - 0.01; + var prevEntityID = null; + for (var i = 0; i < 7; i++) { + var newID = Entities.addEntity({ + name: "ball and socket test " + i, + type: "Sphere", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: diameter, y: diameter, z: diameter }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("ball-socket", prevEntityID, { + pivot: { x: 0, y: 0, z: 0 }, + otherEntityID: newID, + otherPivot: { x: 0, y: offset, z: 0 }, + tag: "A/B ball-and-socket test " + i + }); + } + prevEntityID = newID; + } + } + + function ragdollTest(params) { + var scale = 1.6; + var lifetime = 120; + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 1.0, z: -2})); + + var neckLength = scale * 0.05; + var shoulderGap = scale * 0.1; + var elbowGap = scale * 0.06; + var hipGap = scale * 0.07; + var kneeGap = scale * 0.08; + var ankleGap = scale * 0.06; + var ankleMin = 0; + var ankleMax = Math.PI / 4; + + var headSize = scale * 0.2; + + var bodyHeight = scale * 0.4; + var bodyWidth = scale * 0.3; + var bodyDepth = scale * 0.2; + + var upperArmThickness = scale * 0.05; + var upperArmLength = scale * 0.2; + + var lowerArmThickness = scale * 0.05; + var lowerArmLength = scale * 0.2; + + var legLength = scale * 0.3; + var legThickness = scale * 0.08; + + var shinLength = scale * 0.2; + var shinThickness = scale * 0.06; + + var footLength = scale * 0.2; + var footThickness = scale * 0.03; + var footWidth = scale * 0.08; + + + // + // body + // + + var bodyID = Entities.addEntity({ + name: "ragdoll body", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: bodyDepth, y: bodyHeight, z: bodyWidth }, + position: Vec3.sum(pos, { x: 0, y: scale * 0.0, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + // + // head + // + + var headID = Entities.addEntity({ + name: "ragdoll head", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: headSize, y: headSize, z: headSize }, + position: Vec3.sum(pos, { x: 0, y: bodyHeight / 2 + headSize / 2 + neckLength, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0.5, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("spring", headID, { + targetRotation: { x: 0, y: 0, z: 0, w: 1 }, + angularTimeScale: 2.0, + otherID: bodyID, + tag: "cone-twist test spring" + }); + + + var noseID = Entities.addEntity({ + name: "ragdoll nose", + type: "Box", + color: { blue: 128, green: 100, red: 100 }, + dimensions: { x: headSize / 5, y: headSize / 5, z: headSize / 5 }, + localPosition: { x: headSize / 2 + headSize / 10, y: 0, z: 0 }, + dynamic: false, + collisionless: true, + lifetime: lifetime, + parentID: headID, + userData: "{ \"grabbableKey\": { \"grabbable\": false } }" + }); + + Entities.addAction("cone-twist", headID, { + pivot: { x: 0, y: -headSize / 2 - neckLength / 2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: bodyID, + otherPivot: { x: 0, y: bodyHeight / 2 + neckLength / 2, z: 0 }, + otherAxis: { x: 0, y: 1, z: 0 }, + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: Math.PI / 2, + tag: "ragdoll neck joint" + }); + + // + // right upper arm + // + + var rightUpperArmID = Entities.addEntity({ + name: "ragdoll right arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: upperArmThickness, y: upperArmThickness, z: upperArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 + upperArmThickness / 2, + z: bodyWidth / 2 + shoulderGap + upperArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", bodyID, { + pivot: { x: 0, y: bodyHeight / 2 + upperArmThickness / 2, z: bodyWidth / 2 + shoulderGap / 2 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: rightUpperArmID, + otherPivot: { x: 0, y: 0, z: -upperArmLength / 2 - shoulderGap / 2 }, + otherAxis: { x: 0, y: 0, z: 1 }, + swingSpan1: Math.PI / 2, + swingSpan2: Math.PI / 2, + twistSpan: 0, + tag: "ragdoll right shoulder joint" + }); + + // + // left upper arm + // + + var leftUpperArmID = Entities.addEntity({ + name: "ragdoll left arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: upperArmThickness, y: upperArmThickness, z: upperArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 + upperArmThickness / 2, + z: -bodyWidth / 2 - shoulderGap - upperArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", bodyID, { + pivot: { x: 0, y: bodyHeight / 2 + upperArmThickness / 2, z: -bodyWidth / 2 - shoulderGap / 2 }, + axis: { x: 0, y: 0, z: -1 }, + otherEntityID: leftUpperArmID, + otherPivot: { x: 0, y: 0, z: upperArmLength / 2 + shoulderGap / 2 }, + otherAxis: { x: 0, y: 0, z: -1 }, + swingSpan1: Math.PI / 2, + swingSpan2: Math.PI / 2, + twistSpan: 0, + tag: "ragdoll left shoulder joint" + }); + + // + // right lower arm + // + + var rightLowerArmID = Entities.addEntity({ + name: "ragdoll right lower arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: lowerArmThickness, y: lowerArmThickness, z: lowerArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 - upperArmThickness / 2, + z: bodyWidth / 2 + shoulderGap + upperArmLength + elbowGap + lowerArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -1, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", rightLowerArmID, { + pivot: { x: 0, y: 0, z: -lowerArmLength / 2 - elbowGap / 2 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: rightUpperArmID, + otherPivot: { x: 0, y: 0, z: upperArmLength / 2 + elbowGap / 2 }, + otherAxis: { x: 0, y: 1, z: 0 }, + low: Math.PI / -2, + high: 0, + tag: "ragdoll right elbow joint" + }); + + // + // left lower arm + // + + var leftLowerArmID = Entities.addEntity({ + name: "ragdoll left lower arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: lowerArmThickness, y: lowerArmThickness, z: lowerArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 - upperArmThickness / 2, + z: -bodyWidth / 2 - shoulderGap - upperArmLength - elbowGap - lowerArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -1, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", leftLowerArmID, { + pivot: { x: 0, y: 0, z: lowerArmLength / 2 + elbowGap / 2 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: leftUpperArmID, + otherPivot: { x: 0, y: 0, z: -upperArmLength / 2 - elbowGap / 2 }, + otherAxis: { x: 0, y: 1, z: 0 }, + low: 0, + high: Math.PI / 2, + tag: "ragdoll left elbow joint" + }); + + // + // right leg + // + + var rightLegID = Entities.addEntity({ + name: "ragdoll right arm", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: legThickness, y: legLength, z: legThickness }, + position: Vec3.sum(pos, { x: 0, y: -bodyHeight / 2 - hipGap - legLength / 2, z: bodyWidth / 2 - legThickness / 2 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", rightLegID, { + pivot: { x: 0, y: legLength / 2 + hipGap / 2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: bodyID, + otherPivot: { x: 0, y: -bodyHeight / 2 - hipGap / 2, z: bodyWidth / 2 - legThickness / 2 }, + otherAxis: Vec3.normalize({ x: -1, y: 1, z: 0 }), + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: 0, + tag: "ragdoll right hip joint" + }); + + // + // left leg + // + + var leftLegID = Entities.addEntity({ + name: "ragdoll left arm", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: legThickness, y: legLength, z: legThickness }, + position: Vec3.sum(pos, { x: 0, y: -bodyHeight / 2 - hipGap - legLength / 2, z: -bodyWidth / 2 + legThickness / 2 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", leftLegID, { + pivot: { x: 0, y: legLength / 2 + hipGap / 2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: bodyID, + otherPivot: { x: 0, y: -bodyHeight / 2 - hipGap / 2, z: -bodyWidth / 2 + legThickness / 2 }, + otherAxis: Vec3.normalize({ x: -1, y: 1, z: 0 }), + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: 0, + tag: "ragdoll left hip joint" + }); + + // + // right shin + // + + var rightShinID = Entities.addEntity({ + name: "ragdoll right shin", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: shinThickness, y: shinLength, z: shinThickness }, + position: Vec3.sum(pos, { x: 0, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength / 2, + z: bodyWidth / 2 - legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -2, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", rightShinID, { + pivot: { x: 0, y: shinLength / 2 + kneeGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: rightLegID, + otherPivot: { x: 0, y: -legLength / 2 - kneeGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: 0, + high: Math.PI / 2, + tag: "ragdoll right knee joint" + }); + + + // + // left shin + // + + var leftShinID = Entities.addEntity({ + name: "ragdoll left shin", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: shinThickness, y: shinLength, z: shinThickness }, + position: Vec3.sum(pos, { x: 0, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength / 2, + z: -bodyWidth / 2 + legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -2, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", leftShinID, { + pivot: { x: 0, y: shinLength / 2 + kneeGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: leftLegID, + otherPivot: { x: 0, y: -legLength / 2 - kneeGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: 0, + high: Math.PI / 2, + tag: "ragdoll left knee joint" + }); + + // + // right foot + // + + var rightFootID = Entities.addEntity({ + name: "ragdoll right foot", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: footLength, y: footThickness, z: footWidth }, + position: Vec3.sum(pos, { x: -shinThickness / 2 + footLength / 2, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength - ankleGap - footThickness / 2, + z: bodyWidth / 2 - legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -5, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", rightFootID, { + pivot: { x: -footLength / 2 + shinThickness / 2, y: ankleGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: rightShinID, + otherPivot: { x: 0, y: -shinLength / 2 - ankleGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: ankleMin, + high: ankleMax, + tag: "ragdoll right ankle joint" + }); + + // + // left foot + // + + var leftFootID = Entities.addEntity({ + name: "ragdoll left foot", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: footLength, y: footThickness, z: footWidth }, + position: Vec3.sum(pos, { x: -shinThickness / 2 + footLength / 2, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength - ankleGap - footThickness / 2, + z: bodyWidth / 2 - legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -5, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", leftFootID, { + pivot: { x: -footLength / 2 + shinThickness / 2, y: ankleGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: leftShinID, + otherPivot: { x: 0, y: -shinLength / 2 - ankleGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: ankleMin, + high: ankleMax, + tag: "ragdoll left ankle joint" + }); + + } + + function onWebEventReceived(eventString) { + print("received web event: " + JSON.stringify(eventString)); + if (typeof eventString === "string") { + var event; + try { + event = JSON.parse(eventString); + } catch(e) { + return; + } + + if (event["dynamics-tests-command"]) { + var commandToFunctionMap = { + "cone-twist-and-spring-lever-test": coneTwistAndSpringLeverTest, + "door-vs-world-test": doorVSWorldTest, + "hinge-chain-test": hingeChainTest, + "slider-vs-world-test": sliderVSWorldTest, + "slider-chain-test": sliderChainTest, + "ball-socket-between-test": ballSocketBetweenTest, + "ball-socket-coincident-test": ballSocketCoincidentTest, + "ragdoll-test": ragdollTest + }; + + var cmd = event["dynamics-tests-command"]; + if (commandToFunctionMap.hasOwnProperty(cmd)) { + var func = commandToFunctionMap[cmd]; + func(event); + } + } + } + } + + + var onDynamicsTestsScreen = false; + var shouldActivateButton = false; + + function onClicked() { + if (onDynamicsTestsScreen) { + tablet.gotoHomeScreen(); + } else { + shouldActivateButton = true; + tablet.gotoWebScreen(DYNAMICS_TESTS_URL + + "?lifetime=" + DEFAULT_LIFETIME.toString() + ); + onDynamicsTestsScreen = true; + } + } + + function onScreenChanged() { + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: shouldActivateButton}); + shouldActivateButton = false; + onDynamicsTestsScreen = shouldActivateButton; + } + + function cleanup() { + button.clicked.disconnect(onClicked); + tablet.removeButton(button); + } + + button.clicked.connect(onClicked); + tablet.webEventReceived.connect(onWebEventReceived); + tablet.screenChanged.connect(onScreenChanged); + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE diff --git a/scripts/developer/tests/dynamics/dynamicsTests.svg b/scripts/developer/tests/dynamics/dynamicsTests.svg new file mode 100644 index 0000000000..a1407e87d4 --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamicsTests.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + +