diff --git a/scripts/tutorials/NBody/gravity.js b/scripts/tutorials/NBody/gravity.js new file mode 100644 index 0000000000..b6bdb9d20b --- /dev/null +++ b/scripts/tutorials/NBody/gravity.js @@ -0,0 +1,181 @@ +// gravity.js +// +// Created by Philip Rosedale on March 29, 2016 +// 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 +// +// This entity script causes the object to move with gravitational force and be attracted to other spheres nearby. +// The force is scaled by GRAVITY_STRENGTH, and only entities of type "Sphere" within GRAVITY_RANGE will affect it. +// The person who has most recently grabbed this object will simulate it. +// + +function Timer() { + var time; + var count = 0; + var totalTime = 0; + this.reset = function() { + count = 0; + totalTime = 0; + } + this.start = function() { + time = new Date().getTime(); + } + this.record = function() { + var elapsed = new Date().getTime() - time; + totalTime += elapsed; + count++; + return elapsed; + } + this.count = function() { + return count; + } + this.average = function() { + return (count == 0) ? 0 : totalTime / count; + } + this.elapsed = function() { + return new Date().getTime() - time; + } +} + +(function () { + var entityID, + wantDebug = true, + CHECK_INTERVAL = 10.00, + SEARCH_INTERVAL = 1000, + GRAVITY_RANGE = 20.0, + GRAVITY_STRENGTH = 1.0, + MIN_VELOCITY = 0.01, + timeoutID = null, + timeSinceLastSearch = 0, + timer = new Timer(), + simulate = false, + spheres = []; + + var printDebug = function(message) { + if (wantDebug) { + print(message); + } + } + + var greatestDimension = function(dimensions) { + return Math.max(Math.max(dimensions.x, dimensions.y), dimensions.z); + } + + var mass2 = function(dimensions) { + return dimensions.x * dimensions.y * dimensions.z; + } + + var findSpheres = function(position) { + var entities = Entities.findEntities(position, GRAVITY_RANGE); + spheres = []; + for (var i = 0; i < entities.length; i++) { + if (entityID == spheres[i]) { + // this entity doesn't experience its own gravity. + continue; + } + var props = Entities.getEntityProperties(entities[i]); + if (props && (props.shapeType == "sphere" || props.type == "Sphere")) { + spheres.push(entities[i]); + } + } + // print("FOUND " + spheres.length + " SPHERES"); + } + + var applyGravity = function() { + if (!simulate) { + return; + } + + var properties = Entities.getEntityProperties(entityID); + if (!properties || !properties.position) { + return; + } + + // update the list of nearby spheres + var deltaTime = timer.elapsed() / 1000.0; + if (deltaTime == 0.0) { + return; + } + timeSinceLastSearch += CHECK_INTERVAL; + if (timeSinceLastSearch >= SEARCH_INTERVAL) { + findSpheres(properties.position); + timeSinceLastSearch = 0; + } + + var deltaVelocity = { x: 0, y: 0, z: 0 }; + var otherCount = 0; + var mass = mass2(properties.dimensions); + + for (var i = 0; i < spheres.length; i++) { + otherProperties = Entities.getEntityProperties(spheres[i]); + if (!otherProperties || !otherProperties.position) { + continue; // sphere was deleted + } + otherCount++; + var radius = Vec3.distance(properties.position, otherProperties.position); + var otherMass = mass2(otherProperties.dimensions); + var r = (greatestDimension(properties.dimensions) + greatestDimension(otherProperties.dimensions)) / 2; + if (radius > r) { + var n0 = Vec3.normalize(Vec3.subtract(otherProperties.position, properties.position)); + var n1 = Vec3.multiply(deltaTime * GRAVITY_STRENGTH * otherMass / (radius * radius), n0); + deltaVelocity = Vec3.sum(deltaVelocity, n1); + } + } + Entities.editEntity(entityID, { velocity: Vec3.sum(properties.velocity, deltaVelocity) }); + if (Vec3.length(properties.velocity) < MIN_VELOCITY) { + print("Gravity simulation stopped due to velocity"); + simulate = false; + } else { + timer.start(); + timeoutID = Script.setTimeout(applyGravity, CHECK_INTERVAL); + } + } + this.applyGravity = applyGravity; + + var releaseGrab = function() { + printDebug("Gravity simulation started."); + var properties = Entities.getEntityProperties(entityID); + findSpheres(properties.position); + timer.start(); + timeoutID = Script.setTimeout(applyGravity, CHECK_INTERVAL); + simulate = true; + } + this.releaseGrab = releaseGrab; + + var preload = function (givenEntityID) { + printDebug("load gravity..."); + entityID = givenEntityID; + }; + this.preload = preload; + + var unload = function () { + printDebug("Unload gravity..."); + if (timeoutID !== undefined) { + Script.clearTimeout(timeoutID); + } + if (simulate) { + Entities.editEntity(entityID, { velocity: { x: 0, y: 0, z: 0 } }); + } + }; + this.unload = unload; + + var handleMessages = function(channel, message, sender) { + if (channel === 'Hifi-Object-Manipulation') { + try { + var parsedMessage = JSON.parse(message); + if (parsedMessage.action === 'grab' && parsedMessage.grabbedEntity == entityID) { + print("Gravity simulation stopped due to grab"); + simulate = false; + } + } catch (e) { + print('error parsing Hifi-Object-Manipulation message: ' + message); + } + } + } + this.handleMessages = handleMessages; + + Messages.messageReceived.connect(this.handleMessages); + Messages.subscribe('Hifi-Object-Manipulation'); +}); diff --git a/scripts/tutorials/NBody/gravity.svg b/scripts/tutorials/NBody/gravity.svg new file mode 100644 index 0000000000..9bac2d65e1 --- /dev/null +++ b/scripts/tutorials/NBody/gravity.svg @@ -0,0 +1,72 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/tutorials/NBody/makePlanets.js b/scripts/tutorials/NBody/makePlanets.js new file mode 100644 index 0000000000..58a3c7cc2d --- /dev/null +++ b/scripts/tutorials/NBody/makePlanets.js @@ -0,0 +1,166 @@ +// makePlanets.js +// +// Created by Philip Rosedale on March 29, 2016 +// 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 +// +// Make an earth and moon, where you can grab and throw moon into orbit. Entity +// script attached to moon gives it gravitation behavior and will also make it attracted to +// other spheres placed nearby. +// + +var SCALE = 3.0; +var EARTH_SIZE = 3.959 / SCALE; +var MOON_SIZE = 1.079 / SCALE; + +var BUTTON_SIZE = 32; +var PADDING = 3; + +var earth = null; +var moon = null; + +var SCRIPT_URL = Script.resolvePath("gravity.js"); + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +Script.include(["/~/system/libraries/toolBars.js"]); +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.makePlanets.js"); + +var makePlanetsIconURL = Script.resolvePath("gravity.svg"); +var button = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: makePlanetsIconURL, + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); + +var deleteButton = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/delete.png", + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); + +function inFrontOfMe(distance) { + return Vec3.sum(Camera.getPosition(), Vec3.multiply(distance, Quat.getFront(Camera.getOrientation()))); +} + +function onButtonClick() { + earth = Entities.addEntity({ + type: "Model", + name: "Earth", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/seth/production/NBody/earth.fbx", + position: inFrontOfMe(2 * EARTH_SIZE), + dimensions: { x: EARTH_SIZE, y: EARTH_SIZE, z: EARTH_SIZE }, + shapeType: "sphere", + lifetime: 86400, // 1 day + angularDamping: 0, + angularVelocity: { x: 0, y: 0.1, z: 0 }, + }); + moon = Entities.addEntity({ + type: "Model", + name: "Moon", + modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/seth/production/NBody/moon.fbx", + position: inFrontOfMe(EARTH_SIZE - MOON_SIZE), + dimensions: { x: MOON_SIZE, y: MOON_SIZE, z: MOON_SIZE }, + dynamic: true, + damping: 0, // 0.01, + angularDamping: 0, // 0.01, + script: SCRIPT_URL, + shapeType: "sphere" + }); + Entities.addEntity({ + "accelerationSpread": { + "x": 0, + "y": 0, + "z": 0 + }, + "alpha": 1, + "alphaFinish": 0, + "alphaStart": 1, + "azimuthFinish": 0, + "azimuthStart": 0, + "color": { + "blue": 255, + "green": 255, + "red": 255 + }, + "colorFinish": { + "blue": 255, + "green": 255, + "red": 255 + }, + "colorStart": { + "blue": 255, + "green": 255, + "red": 255 + }, + "dimensions": { + "x": 0.10890001058578491, + "y": 0.10890001058578491, + "z": 0.10890001058578491 + }, + "emitAcceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitOrientation": { + "w": 0.99999994039535522, + "x": 0, + "y": 0, + "z": 0 + }, + "emitRate": 300, + "emitSpeed": 0, + "emitterShouldTrail": 1, + "maxParticles": 10000, + "name": "moon trail", + "parentID": moon, + "particleRadius": 0.005, + "radiusFinish": 0.005, + "radiusSpread": 0.005, + "radiusStart": 0.005, + "speedSpread": 0, + "lifespan": 20, + "textures": "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + "type": "ParticleEffect", + "userData": "{\"grabbableKey\":{\"grabbable\":false}}" + }); +} + +function onDeleteButton() { + Entities.deleteEntity(earth); + Entities.deleteEntity(moon); +} + +function mousePressEvent(event) { + var clickedText = false; + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == button) { + onButtonClick(); + } else if (clickedOverlay == deleteButton) { + onDeleteButton(); + } +} + +function scriptEnding() { + toolBar.cleanup(); +} + +Controller.mousePressEvent.connect(mousePressEvent); +Script.scriptEnding.connect(scriptEnding);