From 6bec948457bc49c0a83bdeb2098a93d90cfcee0c Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Sun, 9 Apr 2017 21:29:33 -0700 Subject: [PATCH] springhold, touch, nBody tutorial scripts --- scripts/tutorials/createSwords.js | 59 +++++ .../tutorials/entity_scripts/springHold.js | 121 ++++++++++ scripts/tutorials/entity_scripts/touch.js | 207 ++++++++++++++++++ scripts/tutorials/nBody.js | 66 ++++++ 4 files changed, 453 insertions(+) create mode 100644 scripts/tutorials/createSwords.js create mode 100644 scripts/tutorials/entity_scripts/springHold.js create mode 100644 scripts/tutorials/entity_scripts/touch.js create mode 100644 scripts/tutorials/nBody.js diff --git a/scripts/tutorials/createSwords.js b/scripts/tutorials/createSwords.js new file mode 100644 index 0000000000..89597353a9 --- /dev/null +++ b/scripts/tutorials/createSwords.js @@ -0,0 +1,59 @@ +// createSwords.js +// +// Created by Philip Rosedale on April 9, 2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Makes two grabbable 'swords' in front of the user, that can be held and used +// to hit the other. Demonstration of an action that would allow two people to hold +// entities that are also colliding. +// + +var COLOR = { red: 255, green: 0, blue: 0 }; +var SIZE = { x: 0.10, y: 1.5, z: 0.10 }; + +var SCRIPT_URL = Script.resolvePath("entity_scripts/springHold.js"); + +function inFrontOfMe(distance) { + return Vec3.sum(Camera.getPosition(), Vec3.multiply(distance, Quat.getForward(Camera.getOrientation()))); +} + +var sword1 = Entities.addEntity({ + type: "Box", + name: "Sword1", + position: inFrontOfMe(2 * SIZE.y), + dimensions: SIZE, + color: COLOR, + angularDamping: 0, + damping: 0, + script: SCRIPT_URL, + userData:JSON.stringify({ + grabbableKey:{ + grabbable:true + } + }) + }) + +var sword2 = Entities.addEntity({ + type: "Box", + name: "Sword2", + position: inFrontOfMe(3 * SIZE.y), + dimensions: SIZE, + color: COLOR, + angularDamping: 0, + damping: 0, + script: SCRIPT_URL, + userData:JSON.stringify({ + grabbableKey:{ + grabbable:true + } + }) + }) + +Script.scriptEnding.connect(function scriptEnding() { + Entities.deleteEntity(sword1); + Entities.deleteEntity(sword2); +}); + diff --git a/scripts/tutorials/entity_scripts/springHold.js b/scripts/tutorials/entity_scripts/springHold.js new file mode 100644 index 0000000000..144f2dda19 --- /dev/null +++ b/scripts/tutorials/entity_scripts/springHold.js @@ -0,0 +1,121 @@ +// +// springHold.js +// +// Created by Philip Rosedale on March 18, 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Attach this entity script to a model or basic shape and grab it. The object will +// follow your hand like a spring, allowing you, for example, to swordfight with others. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var TIMESCALE = 0.03; + var ACTION_TTL = 10; + var _this; + + function SpringHold() { + _this = this; + } + + function updateSpringAction(timescale) { + + var targetProps = Entities.getEntityProperties(_this.entityID); + var props = { + targetPosition: targetProps.position, + targetRotation: targetProps.rotation, + linearTimeScale: timescale, + angularTimeScale: timescale, + ttl: ACTION_TTL + }; + var success = Entities.updateAction(_this.copy, _this.actionID, props); + return; + } + + function createSpringAction(timescale) { + + var targetProps = Entities.getEntityProperties(_this.entityID); + var props = { + targetPosition: targetProps.position, + targetRotation: targetProps.rotation, + linearTimeScale: timescale, + angularTimeScale: timescale, + ttl: ACTION_TTL + }; + _this.actionID = Entities.addAction("spring", _this.copy, props); + return; + } + + function createCopy() { + var originalProps = Entities.getEntityProperties(_this.entityID); + var props = { + type: originalProps.type, + color: originalProps.color, + modelURL: originalProps.modelURL, + dimensions: originalProps.dimensions, + dynamic: true, + damping: 0.0, + angularDamping: 0.0, + collidesWith: 'dynamic,static,kinematic', + rotation: originalProps.rotation, + position: originalProps.position, + shapeType: originalProps.shapeType, + visible: true, + userData:JSON.stringify({ + grabbableKey:{ + grabbable:false + } + }) + } + _this.copy = Entities.addEntity(props); + } + + function deleteCopy() { + print("Delete copy"); + Entities.deleteEntity(_this.copy); + } + + function makeOriginalInvisible() { + Entities.editEntity(_this.entityID, { + visible: false, + collisionless: true + }); + } + + function makeOriginalVisible() { + Entities.editEntity(_this.entityID, { + visible: true, + collisionless: false + }); + } + + function deleteSpringAction() { + Entities.deleteAction(_this.copy, _this.actionID); + } + + SpringHold.prototype = { + preload: function(entityID) { + _this.entityID = entityID; + }, + startNearGrab: function(entityID, data) { + print("start spring grab"); + createCopy(); + createSpringAction(TIMESCALE); + makeOriginalInvisible(); + }, + continueNearGrab: function() { + updateSpringAction(TIMESCALE); + }, + releaseGrab: function() { + print("end spring grab"); + deleteSpringAction(); + deleteCopy(); + makeOriginalVisible(); + } + }; + + return new SpringHold(); +}); \ No newline at end of file diff --git a/scripts/tutorials/entity_scripts/touch.js b/scripts/tutorials/entity_scripts/touch.js new file mode 100644 index 0000000000..08681a7d3c --- /dev/null +++ b/scripts/tutorials/entity_scripts/touch.js @@ -0,0 +1,207 @@ +(function() { + + // touch.js + // + // Sample file using spring action, haptic vibration, and color change to demonstrate two spheres + // That can give a sense of touch to the holders. + // Create two standard spheres, make them grabbable, and attach this entity script. Grab them and touch them together. + // + // Created by Philip Rosedale on March 18, 2017 + // Copyright 2017 High Fidelity, Inc. + // + // Distributed under the Apache License, Version 2.0. + // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + // + + var TIMESCALE = 0.03; + var ACTION_TTL = 10; + var _this; + + var RIGHT_HAND = 1; + var LEFT_HAND = 0; + var HAPTIC_PULSE_FIRST_STRENGTH = 0.5; + var HAPTIC_PULSE_MIN_STRENGTH = 0.20; + var HAPTIC_PULSE_MAX_STRENGTH = 0.5; + var HAPTIC_PULSE_FIRST_DURATION = 1.0; + var HAPTIC_PULSE_DURATION = 16.0; + var HAPTIC_PULSE_DISTANCE = 0.0; + var MAX_PENETRATION = 0.02; + var HAPTIC_MIN_VELOCITY = 0.002; + var HAPTIC_MAX_VELOCITY = 0.5; + var PENETRATION_PULLBACK_FACTOR = 0.65; + var FRAME_TIME = 0.016; + + var isColliding = false; + + var hand = LEFT_HAND; + var lastHapticPulseLocation = { x:0, y:0, z:0 }; + + + var GRAY = { red: 128, green: 128, blue: 128 }; + var RED = { red: 255, green: 0, blue: 0 }; + + var targetColor = { x: GRAY.red, y: GRAY.green, z: GRAY.blue }; + + var lastPosition = { x: 0, y: 0, z: 0 }; + var velocity = { x: 0, y: 0, z: 0 }; + + var lastOtherPosition = { x: 0, y: 0, z: 0 }; + var otherVelocity = { x: 0, y: 0, z: 0 }; + + + function TouchExample() { + _this = this; + } + + function updateSpringAction(timescale) { + var targetProps = Entities.getEntityProperties(_this.entityID); + // + // Look for nearby entities to touch + // + var copyProps = Entities.getEntityProperties(_this.copy); + var nearbyEntities = Entities.findEntities(copyProps.position, copyProps.dimensions.x * 2); + var wasColliding = isColliding; + var targetAdjust = { x: 0, y: 0, z: 0 }; + + isColliding = false; + for (var i = 0; i < nearbyEntities.length; i++) { + if (_this.copy != nearbyEntities[i] && _this.entityID != nearbyEntities[i]) { + var otherProps = Entities.getEntityProperties(nearbyEntities[i]); + var penetration = Vec3.distance(copyProps.position, otherProps.position) - (copyProps.dimensions.x / 2 + otherProps.dimensions.x / 2); + if (otherProps.type === 'Sphere' && penetration < 0 && penetration > -copyProps.dimensions.x * 3) { + isColliding = true; + targetAdjust = Vec3.sum(targetAdjust, Vec3.multiply(Vec3.normalize(Vec3.subtract(targetProps.position, otherProps.position)), -penetration * PENETRATION_PULLBACK_FACTOR)); + if (!wasColliding && false) { + targetColor = { x: RED.red, y: RED.green, z: RED.blue }; + } else { + targetColor = { x: 200 + Math.min(-penetration / MAX_PENETRATION, 1.0) * 55, y: GRAY.green, z: GRAY.blue }; + } + if (Vec3.distance(targetProps.position, lastHapticPulseLocation) > HAPTIC_PULSE_DISTANCE || !wasColliding) { + if (!wasColliding) { + velocity = { x: 0, y: 0, z: 0}; + otherVelocity = { x: 0, y: 0, z: 0 }; + Controller.triggerHapticPulse(HAPTIC_PULSE_FIRST_STRENGTH, HAPTIC_PULSE_FIRST_DURATION, hand); + } else { + velocity = Vec3.distance(targetProps.position, lastPosition) / FRAME_TIME; + otherVelocity = Vec3.distance(otherProps.position, lastOtherPosition) / FRAME_TIME; + var velocityStrength = Math.min(velocity + otherVelocity / HAPTIC_MAX_VELOCITY, 1.0); + var strength = HAPTIC_PULSE_MIN_STRENGTH + Math.min(-penetration / MAX_PENETRATION, 1.0) * (HAPTIC_PULSE_MAX_STRENGTH - HAPTIC_PULSE_MIN_STRENGTH); + Controller.triggerHapticPulse(velocityStrength * strength, HAPTIC_PULSE_DURATION, hand); + } + lastPosition = targetProps.position; + lastOtherPosition = otherProps.position; + lastHapticPulseLocation = targetProps.position; + } + } + } + } + if ((wasColliding != isColliding) && !isColliding) { + targetColor = { x: GRAY.red, y: GRAY.green, z: GRAY.blue }; + } + // Interpolate color toward target color + var currentColor = { x: copyProps.color.red, y: copyProps.color.green, z: copyProps.color.blue }; + var newColor = Vec3.sum(currentColor, Vec3.multiply(Vec3.subtract(targetColor, currentColor), 0.1)); + Entities.editEntity(_this.copy, { color: { red: newColor.x, green: newColor.y, blue: newColor.z } }); + + var props = { + targetPosition: Vec3.sum(targetProps.position, targetAdjust), + targetRotation: targetProps.rotation, + linearTimeScale: timescale, + angularTimeScale: timescale, + ttl: ACTION_TTL + }; + var success = Entities.updateAction(_this.copy, _this.actionID, props); + return; + } + + function createSpringAction(timescale) { + + var targetProps = Entities.getEntityProperties(_this.entityID); + var props = { + targetPosition: targetProps.position, + targetRotation: targetProps.rotation, + linearTimeScale: timescale, + angularTimeScale: timescale, + ttl: ACTION_TTL + }; + _this.actionID = Entities.addAction("spring", _this.copy, props); + return; + } + + function createCopy() { + var originalProps = Entities.getEntityProperties(_this.entityID); + var props = { + type: originalProps.type, + modelURL: originalProps.modelURL, + dimensions: originalProps.dimensions, + color: GRAY, + dynamic: true, + damping: 0.0, + angularDamping: 0.0, + collidesWith: 'static', + rotation: originalProps.rotation, + position: originalProps.position, + shapeType: originalProps.shapeType, + visible: true, + userData:JSON.stringify({ + grabbableKey:{ + grabbable:false + } + }) + } + _this.copy = Entities.addEntity(props); + } + + function deleteCopy() { + print("Delete copy"); + Entities.deleteEntity(_this.copy); + } + + function makeOriginalInvisible() { + Entities.editEntity(_this.entityID, { + visible: false, + collisionless: true + }); + } + + function makeOriginalVisible() { + Entities.editEntity(_this.entityID, { + visible: true, + collisionless: false + }); + } + + function deleteSpringAction() { + Entities.deleteAction(_this.copy, _this.actionID); + } + + function setHand(position) { + if (Vec3.distance(MyAvatar.getLeftPalmPosition(), position) < Vec3.distance(MyAvatar.getRightPalmPosition(), position)) { + hand = LEFT_HAND; + } else { + hand = RIGHT_HAND; + } + } + + TouchExample.prototype = { + preload: function(entityID) { + _this.entityID = entityID; + }, + startNearGrab: function(entityID, data) { + createCopy(); + createSpringAction(TIMESCALE); + makeOriginalInvisible(); + setHand(Entities.getEntityProperties(_this.entityID).position); + }, + continueNearGrab: function() { + updateSpringAction(TIMESCALE); + }, + releaseGrab: function() { + deleteSpringAction(); + deleteCopy(); + makeOriginalVisible(); + } + }; + + return new TouchExample(); +}); \ No newline at end of file diff --git a/scripts/tutorials/nBody.js b/scripts/tutorials/nBody.js new file mode 100644 index 0000000000..58fc89d553 --- /dev/null +++ b/scripts/tutorials/nBody.js @@ -0,0 +1,66 @@ +// +// nBody.js +// examples +// +// Created by Philip Rosedale on March 18, 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Create some spheres that obey gravity, which is great to teach physics. +// You can control how many to create by changing the value of 'n' below. +// Grab them and watch the others move around. +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var bodies = []; +var n = 3; +var radius = 0.1; +var G = 0.25; +var EARTH = "https://s3-us-west-1.amazonaws.com/hifi-content/seth/production/NBody/earth.fbx"; +var MOON = "https://s3-us-west-1.amazonaws.com/hifi-content/seth/production/NBody/moon.fbx"; + +var COLOR1 = { red: 51, green: 51, blue: 255 }; +var COLOR2 = { red: 51, green: 51, blue: 255 }; +var COLOR3 = { red: 51, green: 51, blue: 255 }; + +var inFront = Vec3.sum(Camera.getPosition(), Vec3.multiply(radius * 20, Quat.getFront(Camera.getOrientation()))); + +for (var i = 0; i < n; i++) { + bodies.push(Entities.addEntity({ + type: "Model", + modelURL: (i == 0) ? EARTH : MOON, + shapeType: "sphere", + dimensions: { x: radius * 2, y: radius * 2, z: radius * 2}, + position: Vec3.sum(inFront, { x: 0, y: i * 2 * radius, z: 0 }), + gravity: { x: 0, y: 0, z: 0 }, + damping: 0.0, + angularDamping: 0.0, + dynamic: true + })); +} + +Script.update.connect(function(dt) { + var props = []; + for (var i = 0; i < n; i++) { + props.push(Entities.getEntityProperties(bodies[i])); + } + for (var i = 0; i < n; i++) { + if (props[i].dynamic) { + var dv = { x: 0, y: 0, z: 0}; + for (var j = 0; j < n; j++) { + if (i != j) { + dv = Vec3.sum(dv, Vec3.multiply(G * dt / Vec3.distance(props[i].position, props[j].position), Vec3.normalize(Vec3.subtract(props[j].position, props[i].position)))); + } + } + Entities.editEntity(bodies[i], { velocity: Vec3.sum(props[i].velocity, dv)}); + } + } +}); + +Script.scriptEnding.connect(function scriptEnding() { + for (var i = 0; i < n; i++) { + Entities.deleteEntity(bodies[i]); + } +}); \ No newline at end of file