// 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'); });