diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/colorRandomly.js b/unpublishedScripts/DomainContent/CellScience/Scripts/colorRandomly.js new file mode 100644 index 0000000000..cf4ee810e7 --- /dev/null +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/colorRandomly.js @@ -0,0 +1,104 @@ +// 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 +// +(function() { + + Script.include(Script.resolvePath('virtualBaton.js')); + + var self = this; + + var baton; + var iOwn = false; + var currentInterval; + var _entityId; + + function startUpdate() { + iOwn = true; + print('i am the owner ' + _entityId) + } + + function stopUpdateAndReclaim() { + print('i released the object ' + _entityId) + iOwn = false; + baton.claim(startUpdate, stopUpdateAndReclaim); + } + + this.preload = function(entityId) { + this.isConnected = false; + this.entityId = entityId; + _entityId = entityId; + this.minVelocity = 1; + this.maxVelocity = 5; + this.minAngularVelocity = 0.01; + this.maxAngularVelocity = 0.03; + baton = virtualBaton({ + batonName: 'io.highfidelity.vesicles:' + entityId, // One winner for each entity + }); + stopUpdateAndReclaim(); + currentInterval = Script.setInterval(self.move, self.getTotalWait()) + } + + + this.getTotalWait = function() { + return (Math.random() * 5000) * 2; + // var avatars = AvatarList.getAvatarIdentifiers(); + // var avatarCount = avatars.length; + // var random = Math.random() * 5000; + // var totalWait = random * (avatarCount * 2); + // print('cellscience color avatarcount, totalwait: ', avatarCount, totalWait) + // return totalWait + } + + + this.move = function() { + if (!iOwn) { + return; + } + + var properties = Entities.getEntityProperties(self.entityId); + var color = properties.color; + + var newColor; + var red = { + red: 255, + green: 0, + blue: 0 + } + var green = { + red: 0, + green: 255, + blue: 0 + } + var blue = { + red: 0, + green: 0, + blue: 255 + } + if (color.red > 0) { + newColor = green; + } + if (color.green > 0) { + newColor = blue; + } + if (color.blue > 0) { + newColor = red + } + Entities.editEntity(self.entityId, { + color: newColor + }); + + + } + + + this.unload = function() { + baton.release(function() {}); + Script.clearTimeout(currentInterval); + } + + + +}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js b/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js deleted file mode 100644 index cc822ddf8a..0000000000 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js +++ /dev/null @@ -1,71 +0,0 @@ -// 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 -// -(function() { - - var self = this; - - this.preload=function(entityId){ - this.isConnected = false; - this.entityId = entityId; - this.minVelocity = 1; - this.maxVelocity = 5; - this.minAngularVelocity = 0.01; - this.maxAngularVelocity = 0.03; - Script.setTimeout(self.move,self.getTotalWait()) - } - - - this.getTotalWait = function() { - var avatars = AvatarList.getAvatarIdentifiers(); - var avatarCount = avatars.length; - var random = Math.random() * 5000; - var totalWait = random * (avatarCount * 2); - - return totalWait - } - - - this.move = function() { - var magnitudeV = self.maxVelocity; - var directionV = { - x: Math.random() - 0.5, - y: Math.random() - 0.5, - z: Math.random() - 0.5 - }; - - //print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x); - Entities.editEntity(self.entityId, { - velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)) - }); - - - var magnitudeAV = self.maxAngularVelocity; - - var directionAV = { - x: Math.random() - 0.5, - y: Math.random() - 0.5, - z: Math.random() - 0.5 - }; - //print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x); - Entities.editEntity(self.entityId, { - angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) - - }); - - - - Script.setTimeout(self.move,self.getTotalWait()) - } - - - this.unload = function() { - - } - - - -}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly2.js b/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly2.js new file mode 100644 index 0000000000..f720ca91cc --- /dev/null +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly2.js @@ -0,0 +1,95 @@ +// 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 +// +(function() { + + Script.include('virtualBaton.js'); + + var self = this; + + var baton; + var iOwn = false; + var currentInterval; + var _entityId; + + function startUpdate() { + iOwn = true; + print('i am the owner ' + _entityId) + } + + function stopUpdateAndReclaim() { + print('i released the object ' + _entityId) + iOwn = false; + baton.claim(startUpdate, stopUpdateAndReclaim); + } + + this.preload = function(entityId) { + this.isConnected = false; + this.entityId = entityId; + _entityId = entityId; + this.minVelocity = 1; + this.maxVelocity = 5; + this.minAngularVelocity = 0.01; + this.maxAngularVelocity = 0.03; + baton = virtualBaton({ + batonName: 'io.highfidelity.vesicles:' + entityId, // One winner for each entity + }); + stopUpdateAndReclaim(); + currentInterval = Script.setInterval(self.move, self.getTotalWait()) + } + + + this.getTotalWait = function() { + return (Math.random() * 5000) * 2; + // var avatars = AvatarList.getAvatarIdentifiers(); + // var avatarCount = avatars.length; + // var random = Math.random() * 5000; + // var totalWait = random * (avatarCount * 2); + // print('cellscience color avatarcount, totalwait: ', avatarCount, totalWait) + // return totalWait + } + + + this.move = function() { + if (!iOwn) { + return; + } + + var magnitudeV = self.maxVelocity; + var directionV = { + x: Math.random() - 0.5, + y: Math.random() - 0.5, + z: Math.random() - 0.5 + }; + + //print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x); + + var magnitudeAV = self.maxAngularVelocity; + + var directionAV = { + x: Math.random() - 0.5, + y: Math.random() - 0.5, + z: Math.random() - 0.5 + }; + //print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x); + Entities.editEntity(self.entityId, { + velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)), + angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) + + }); + + + } + + + this.unload = function() { + baton.release(function() {}); + Script.clearInterval(currentInterval); + } + + + +}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/virtualBaton.js b/unpublishedScripts/DomainContent/CellScience/Scripts/virtualBaton.js new file mode 100644 index 0000000000..63f96a5c1e --- /dev/null +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/virtualBaton.js @@ -0,0 +1,381 @@ +"use strict"; +/*jslint nomen: true, plusplus: true, vars: true */ +/*global Messages, Script, MyAvatar, AvatarList, Entities, print */ + +// Created by Howard Stearns +// 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 +// +// Allows cooperating scripts to pass a "virtual baton" between them, +// which is useful when part of a script should only be executed by +// the one participant that is holding this particular baton. +// +// A virtual baton is simply any string agreed upon by the scripts +// that use it. Only one script at a time can hold the baton, and it +// holds it until that script releases it, or the other scripts +// determine that the holding script is not responding. The script +// automatically determines who among claimants has the baton, if anyone, +// and holds an "election" if necessary. +// +// See entityScript/tribble.js as an example, and the functions +// virtualBaton(), claim(), release(). +// + +// Answers a new virtualBaton for the given parameters, of which 'key' +// is required. +function virtualBatonf(options) { + // Answer averages (number +/- variability). Avoids having everyone act in lockstep. + function randomize(number, variability) { + var allowedDeviation = number * variability; // one side of the deviation range + var allowedDeviationRange = allowedDeviation * 2; // total range for +/- deviation + var randomDeviation = Math.random() * allowedDeviationRange; + var result = number - allowedDeviation + randomDeviation; + return result; + } + // Allow testing outside in a harness outside of High Fidelity. + // See sourceCodeSandbox/tests/mocha/test/testVirtualBaton.js + var globals = options.globals || {}, + messages = globals.Messages || Messages, + myAvatar = globals.MyAvatar || MyAvatar, + avatarList = globals.AvatarList || AvatarList, + entities = globals.Entities || Entities, + timers = globals.Script || Script, + log = globals.print || print; + + var batonName = options.batonName, // The identify of the baton. + // instanceId is the identify of this particular copy of the script among all copies using the same batonName + // in the domain. For example, if you wanted only one entity among multiple entity scripts to hold the baton, + // you could specify virtualBaton({batonName: 'someBatonName', instanceId: MyAvatar.sessionUUID + entityID}). + instanceId = options.instanceId || myAvatar.sessionUUID, + // virtualBaton() returns the exports object with properties. You can pass in an object to be side-effected. + exports = options.exports || {}, + // Handy to set false if we believe the optimizations are wrong, or to use both values in a test harness. + useOptimizations = (options.useOptimizations === undefined) ? true : options.useOptimizations, + electionTimeout = options.electionTimeout || randomize(500, 0.2), // ms. If no winner in this time, hold a new election. + recheckInterval = options.recheckInterval || randomize(500, 0.2), // ms. Check that winners remain connected. + // If you supply your own instanceId, you might also supply a connectionTest that answers + // truthy iff the given id is still valid and connected, and is run at recheckInterval. You + // can use exports.validId (see below), and the default answers truthy if id is valid or a + // concatenation of two valid ids. (This handles the most common cases of instanceId being + // either (the default) MyAvatar.sessionUUID, an entityID, or the concatenation (in either + // order) of both.) + connectionTest = options.connectionTest || function connectionTest(id) { + var idLength = 38; + if (id.length === idLength) { + return exports.validId(id); + } + return (id.length === 2 * idLength) && exports.validId(id.slice(0, idLength)) && exports.validId(id.slice(idLength)); + }; + + if (!batonName) { + throw new Error("A virtualBaton must specify a batonName."); + } + // Truthy if id exists as either a connected avatar or valid entity. + exports.validId = function validId(id) { + var avatar = avatarList.getAvatar(id); + if (avatar && (avatar.sessionUUID === id)) { + return true; + } + var properties = entities.getEntityProperties(id, ['type']); + return properties && properties.type; + }; + + // Various logging, controllable through options. + function debug() { // Display the arguments not just [Object object]. + log.apply(null, [].map.call(arguments, JSON.stringify)); + } + function debugFlow() { + if (options.debugFlow) { + debug.apply(null, arguments); + } + } + function debugSend(destination, operation, data) { + if (options.debugSend) { + debug('baton:', batonName, instanceId, 's=>', destination, operation, data); + } + } + function debugReceive(senderID, operation, data) { // senderID is client sessionUUID -- not necessarily instanceID! + if (options.debugReceive) { + debug('baton:', batonName, senderID, '=>r', instanceId, operation, data); + } + } + + // Messages: Just synactic sugar for hooking things up to Messages system. + // We create separate subchannel strings for each operation within our general channelKey, instead of using + // a switch in the receiver. + var channelKey = "io.highfidelity.virtualBaton:" + batonName, + subchannelHandlers = {}, // Message channel string => {receiver, op} + subchannelKeys = {}; // operation => Message channel string + function subchannelKey(operation) { + return channelKey + ':' + operation; + } + function receive(operation, handler) { // Record a handler for an operation on our channelKey + var subKey = subchannelKey(operation); + subchannelHandlers[subKey] = {receiver: handler, op: operation}; + subchannelKeys[operation] = subKey; + messages.subscribe(subKey); + } + function sendHelper(subchannel, data) { + var message = JSON.stringify(data); + messages.sendMessage(subchannel, message); + } + function send1(operation, destination, data) { // Send data for an operation to just one destination on our channelKey. + debugSend(destination, operation, data); + sendHelper(subchannelKey(operation) + destination, data); + } + function send(operation, data) { // Send data for an operation on our channelKey. + debugSend('-', operation, data); + sendHelper(subchannelKeys[operation], data); + } + function messageHandler(channel, messageString, senderID) { + var handler = subchannelHandlers[channel]; + if (!handler) { + return; + } + var data = JSON.parse(messageString); + debugReceive(senderID, handler.op, data); + handler.receiver(data); + } + messages.messageReceived.connect(messageHandler); + + var nPromises = 0, nAccepted = 0, electionWatchdog; + + // It would be great if we had a way to know how many subscribers our channel has. Failing that... + var nNack = 0, previousNSubscribers = 0, lastGathering = 0, thisTimeout = electionTimeout; + function nSubscribers() { // Answer the number of subscribers. + // To find nQuorum, we need to know how many scripts are being run using this batonName, which isn't + // the same as the number of clients! + // + // If we overestimate by too much, we may fail to reach consensus, which triggers a new + // election proposal, so we take the number of acceptors to be the max(nPromises, nAccepted) + // + nNack reported in the previous round. + // + // If we understimate by too much, there can be different pockets on the Internet that each + // believe they have agreement on different holders of the baton, which is precisely what + // the virtualBaton is supposed to avoid. Therefore we need to allow 'nack' to gather stragglers. + + var now = Date.now(), elapsed = now - lastGathering; + if (elapsed >= thisTimeout) { + previousNSubscribers = Math.max(nPromises, nAccepted) + nNack; + lastGathering = now; + } // ...otherwise we use the previous value unchanged. + + // On startup, we do one proposal that we cannot possibly close, so that we'll + // lock things up for the full electionTimeout to gather responses. + if (!previousNSubscribers) { + var LARGE_INTEGER = Number.MAX_SAFE_INTEGER || (-1 >>> 1); // QT doesn't define the ECMA constant. Max int will do for our purposes. + previousNSubscribers = LARGE_INTEGER; + } + return previousNSubscribers; + } + + // MAIN ALGORITHM + // + // Internally, this uses the Paxos algorith to hold elections. + // Alternatively, we could have the message server pick and maintain a winner, but it would + // still have to deal with the same issues of verification in the presence of lost/delayed/reordered messages. + // Paxos is known to be optimal under these circumstances, except that its best to have a dedicated proposer + // (such as the server). + function betterNumber(number, best) { + return (number.number || 0) > best.number; + } + // Paxos Proposer behavior + var proposalNumber = 0, + nQuorum = 0, + bestPromise = {number: 0}, + claimCallback, + releaseCallback; + function propose() { // Make a new proposal, so that we learn/update the proposalNumber and winner. + // Even though we send back a 'nack' if the proposal is obsolete, with network errors + // there's no way to know for certain that we've failed. The electionWatchdog will try a new + // proposal if we have not been accepted by a quorum after election Timeout. + if (electionWatchdog) { + // If we had a means of determining nSubscribers other than by counting, we could just + // timers.clearTimeout(electionWatchdog) and not return. + return; + } + thisTimeout = randomize(electionTimeout, 0.5); // Note use in nSubcribers. + electionWatchdog = timers.setTimeout(function () { + electionWatchdog = null; + propose(); + }, thisTimeout); + var nAcceptors = nSubscribers(); + nQuorum = Math.floor(nAcceptors / 2) + 1; + + proposalNumber = Math.max(proposalNumber, bestPromise.number) + 1; + debugFlow('baton:', batonName, instanceId, 'propose', proposalNumber, + 'claim:', !!claimCallback, 'nAcceptors:', nAcceptors, nPromises, nAccepted, nNack); + nPromises = nAccepted = nNack = 0; + send('prepare!', {number: proposalNumber, proposerId: instanceId}); + } + // We create a distinguished promise subchannel for our id, because promises need only be sent to the proposer. + receive('promise' + instanceId, function (data) { + if (betterNumber(data, bestPromise)) { + bestPromise = data; + } + if ((data.proposalNumber === proposalNumber) && (++nPromises >= nQuorum)) { // Note check for not being a previous round + var answer = {number: data.proposalNumber, proposerId: data.proposerId, winner: bestPromise.winner}; // Not data.number. + if (!answer.winner || (answer.winner === instanceId)) { // We get to pick. + answer.winner = claimCallback ? instanceId : null; + } + send('accept!', answer); + } + }); + receive('nack' + instanceId, function (data) { // An acceptor reports more recent data... + if (data.proposalNumber === proposalNumber) { + nNack++; // For updating nQuorum. + // IWBNI if we started our next proposal right now/here, but we need a decent nNack count. + // Lets save that optimization for another day... + } + }); + // Paxos Acceptor behavior + var bestProposal = {number: 0}, accepted = {}; + function acceptedId() { + return accepted && accepted.winner; + } + receive('prepare!', function (data) { + var response = {proposalNumber: data.number, proposerId: data.proposerId}; + if (betterNumber(data, bestProposal)) { + bestProposal = data; + if (accepted.winner && connectionTest(accepted.winner)) { + response.number = accepted.number; + response.winner = accepted.winner; + } + send1('promise', data.proposerId, response); + } else { + send1('nack', data.proposerId, response); + } + }); + receive('accept!', function (data) { + if (!betterNumber(bestProposal, data)) { + bestProposal = accepted = data; // Update both with current data. Might have missed the proposal earlier. + if (useOptimizations) { + // The Paxos literature describes every acceptor sending 'accepted' to + // every proposer and learner. In our case, these are the same nodes that received + // the 'accept!' message, so we can send to just the originating proposer and invoke + // our own accepted handler directly. + // Note that this optimization cannot be used with Byzantine Paxos (which needs another + // multi-broadcast to detect lying and collusion). + debugSend('/', 'accepted', data); + debugReceive(instanceId, 'accepted', data); // direct on next line, which doesn't get logging. + subchannelHandlers[subchannelKey('accepted') + instanceId].receiver(data); + if (data.proposerId !== instanceId) { // i.e., we didn't already do it directly on the line above. + send1('accepted', data.proposerId, data); + } + } else { + send('accepted', data); + } + } else { + send1('nack', data.proposerId, {proposalNumber: data.number}); + } + }); + // Paxos Learner behavior. + function localRelease() { + var callback = releaseCallback; + debugFlow('baton:', batonName, 'localRelease', 'callback:', !!releaseCallback); + if (!releaseCallback) { + return; + } // Already released, but we might still receive a stale message. That's ok. + releaseCallback = undefined; + callback(batonName); // Pass batonName so that clients may use the same handler for different batons. + } + receive('accepted' + (useOptimizations ? instanceId : ''), function (data) { // See note in 'accept!' regarding use of instanceId here. + if (betterNumber(accepted, data)) { // Especially when !useOptimizations, we can receive other acceptances late. + return; + } + var oldAccepted = accepted; + debugFlow('baton:', batonName, instanceId, 'accepted', data.number, data.winner); + accepted = data; + // If we are proposer, make sure we get a quorum of acceptances. + if ((data.proposerId === instanceId) && (data.number === proposalNumber) && (++nAccepted >= nQuorum)) { + if (electionWatchdog) { + timers.clearTimeout(electionWatchdog); + electionWatchdog = null; + } + } + // If we are the winner -- regardless of whether we were the proposer. + if (acceptedId() === instanceId) { + if (claimCallback) { + var callback = claimCallback; + claimCallback = undefined; + callback(batonName); + } else if (!releaseCallback) { // We won, but have been released and are no longer interested. + // Propose that someone else take the job. + timers.setTimeout(propose, 0); // Asynchronous to queue message handling if some are synchronous and others not. + } + } else if (releaseCallback && (oldAccepted.winner === instanceId)) { // We've been released by someone else! + localRelease(); // This can happen if enough people thought we'd disconnected. + } + }); + + // Public Interface + // + // Registers an intent to hold the baton: + // Calls onElection(batonName) once, if you are elected by the scripts + // to be the unique holder of the baton, which may be never. + // Calls onRelease(batonName) once, if the baton held by you is released, + // whether this is by you calling release(), or by losing + // an election when you become disconnected. + // You may claim again at any time after the start of onRelease + // being called. + exports.claim = function claim(onElection, onRelease) { + debugFlow('baton:', batonName, instanceId, 'claim'); + if (claimCallback) { + log("Ignoring attempt to claim virtualBaton " + batonName + ", which is already waiting for claim."); + return; + } + if (releaseCallback) { + log("Ignoring attempt to claim virtualBaton " + batonName + ", which is somehow incorrect released, and that should not happen."); + return; + } + claimCallback = onElection; + releaseCallback = onRelease; + propose(); + return exports; // Allows chaining. e.g., var baton = virtualBaton({batonName: 'foo'}.claim(onClaim, onRelease); + }; + // Release the baton you hold, or just log that you are not holding it. + exports.release = function release(optionalReplacementOnRelease) { + debugFlow('baton:', batonName, instanceId, 'release'); + if (optionalReplacementOnRelease) { // If you want to change. + releaseCallback = optionalReplacementOnRelease; + } + if (acceptedId() !== instanceId) { + log("Ignoring attempt to release virtualBaton " + batonName + ", which is not being held."); + return; + } + localRelease(); + if (!claimCallback) { // No claim set in release callback. + propose(); + } + return exports; + }; + exports.recheckWatchdog = timers.setInterval(function recheck() { + var holder = acceptedId(); // If we're waiting and we notice the holder is gone, ... + if (holder && claimCallback && !electionWatchdog && !connectionTest(holder)) { + bestPromise.winner = null; // used if the quorum agrees that old winner is not there + propose(); // ... propose an election. + } + }, recheckInterval); + exports.unload = function unload() { // Disconnect from everything. + messages.messageReceived.disconnect(messageHandler); + timers.clearInterval(exports.recheckWatchdog); + if (electionWatchdog) { + timers.clearTimeout(electionWatchdog); + } + electionWatchdog = claimCallback = releaseCallback = null; + Object.keys(subchannelHandlers).forEach(messages.unsubscribe); + debugFlow('baton:', batonName, instanceId, 'unload'); + return exports; + }; + + // Gather nAcceptors by making two proposals with some gathering time, even without a claim. + propose(); + return exports; +} +if (typeof module !== 'undefined') { // Allow testing in nodejs. + module.exports = virtualBatonf; +} else { + virtualBaton = virtualBatonf; +} diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/zoomAndMoveRandomly.js b/unpublishedScripts/DomainContent/CellScience/Scripts/zoomAndMoveRandomly.js index e4ee59cb6b..16c83b29dd 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/zoomAndMoveRandomly.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/zoomAndMoveRandomly.js @@ -9,19 +9,42 @@ var teleport; var portalDestination; var animationURL; - var self = this; this.entered = true; + Script.include('virtualBaton.js'); + + var self = this; + var baton; + var iOwn = false; + var currentInterval; + var _entityId; + + function startUpdate() { + iOwn = true; + print('i am the owner ' + _entityId) + } + + function stopUpdateAndReclaim() { + print('i released the object ' + _entityId) + iOwn = false; + baton.claim(startUpdate, stopUpdateAndReclaim); + } this.preload = function(entityID) { this.entityId = entityID; + _entityId = entityID; this.initialize(entityID); this.initTimeout = null; this.minVelocity = 1; this.maxVelocity = 5; this.minAngularVelocity = 0.01; this.maxAngularVelocity = 0.03; - Script.setTimeout(self.move, self.getTotalWait()) + baton = virtualBaton({ + batonName: 'io.highfidelity.vesicles:' + entityId, // One winner for each entity + }); + stopUpdateAndReclaim(); + currentInterval = Script.setInterval(self.move, self.getTotalWait()) + } this.initialize = function(entityID) { @@ -104,6 +127,8 @@ if (this.initTimeout !== null) { Script.clearTimeout(this.initTimeout); } + baton.release(function() {}); + Script.clearInterval(currentInterval); } this.hoverEnterEntity = function(entityID) { @@ -114,16 +139,13 @@ } this.getTotalWait = function() { - var avatars = AvatarList.getAvatarIdentifiers(); - var avatarCount = avatars.length; - var random = Math.random() * 2000; - var totalWait = random * (avatarCount * 2); - - return totalWait + return (Math.random() * 5000) * 2; } - this.move = function() { + if (!iOwn) { + return; + } var magnitudeV = self.maxVelocity; var directionV = { @@ -133,11 +155,6 @@ }; //print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x); - Entities.editEntity(self.entityId, { - velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)) - }); - - var magnitudeAV = self.maxAngularVelocity; @@ -148,11 +165,12 @@ }; //print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x); Entities.editEntity(self.entityId, { + velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)), angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) }); - Script.setTimeout(self.move, self.getTotalWait()) + } }) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/importCellScience.js b/unpublishedScripts/DomainContent/CellScience/importCellScience.js index db50635f93..63f99c53a3 100644 --- a/unpublishedScripts/DomainContent/CellScience/importCellScience.js +++ b/unpublishedScripts/DomainContent/CellScience/importCellScience.js @@ -5,7 +5,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var version = 1043; +var version = 1049; var cellLayout; var baseLocation = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/"; @@ -194,7 +194,7 @@ var scenes = [{ grabbable: false } }), - script: "moveRandomly.js?" + version, + script: "moveRandomly2.js?" + version, visible: true }, { //golgi vesicles model: "vesicle", @@ -238,7 +238,7 @@ var scenes = [{ grabbable: false } }), - script: "moveRandomly.js?" + version, + script: "moveRandomly2.js?" + version, visible: true }, { model: "vesicle", @@ -304,7 +304,7 @@ var scenes = [{ grabbable: false } }), - script: "moveRandomly.js?" + version, + script: "moveRandomly2.js?" + version, visible: true }, { //outer vesicles model: "vesicle", @@ -326,7 +326,7 @@ var scenes = [{ grabbable: false } }), - script: "moveRandomly.js?" + version, + script: "moveRandomly2.js?" + version, visible: true }, // {//wigglies