Merge branch 'master' of https://github.com/highfidelity/hifi into red

This commit is contained in:
samcake 2016-02-11 10:05:58 -08:00
commit 31230e81bb
62 changed files with 2265 additions and 1403 deletions

View file

@ -67,8 +67,6 @@ const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer";
InboundAudioStream::Settings AudioMixer::_streamSettings;
bool AudioMixer::_printStreamStats = false;
bool AudioMixer::_enableFilter = true;
bool AudioMixer::shouldMute(float quietestFrame) {
@ -81,13 +79,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
_minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f),
_performanceThrottlingRatio(0.0f),
_attenuationPerDoublingInDistance(DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE),
_noiseMutingThreshold(DEFAULT_NOISE_MUTING_THRESHOLD),
_lastPerSecondCallbackTime(usecTimestampNow()),
_sendAudioStreamStats(false),
_datagramsReadPerCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS),
_timeSpentPerCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS),
_timeSpentPerHashMatchCallStats(0, READ_DATAGRAMS_STATS_WINDOW_SECONDS),
_readPendingCallsPerSecondStats(1, READ_DATAGRAMS_STATS_WINDOW_SECONDS)
_noiseMutingThreshold(DEFAULT_NOISE_MUTING_THRESHOLD)
{
auto nodeList = DependencyManager::get<NodeList>();
auto& packetReceiver = nodeList->getPacketReceiver();
@ -484,11 +476,22 @@ void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) {
nodeList->eachNode([injectorClientData, &streamID](const SharedNodePointer& node){
auto listenerClientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
listenerClientData->removeHRTFForStream(injectorClientData->getNodeID(), streamID);
if (listenerClientData) {
listenerClientData->removeHRTFForStream(injectorClientData->getNodeID(), streamID);
}
});
}
}
QString AudioMixer::percentageForMixStats(int counter) {
if (_totalMixes > 0) {
float mixPercentage = (float(counter) / _totalMixes) * 100.0f;
return QString::number(mixPercentage, 'f', 2);
} else {
return QString("0.0");
}
}
void AudioMixer::sendStatsPacket() {
static QJsonObject statsObject;
@ -499,11 +502,11 @@ void AudioMixer::sendStatsPacket() {
statsObject["avg_listeners_per_frame"] = (float) _sumListeners / (float) _numStatFrames;
QJsonObject mixStats;
mixStats["%_hrtf_mixes"] = (_totalMixes > 0) ? (_hrtfRenders / _totalMixes) * 100.0f : 0;
mixStats["%_hrtf_silent_mixes"] = (_totalMixes > 0) ? (_hrtfSilentRenders / _totalMixes) * 100.0f : 0;
mixStats["%_hrtf_struggle_mixes"] = (_totalMixes > 0) ? (_hrtfStruggleRenders / _totalMixes) * 100.0f : 0;
mixStats["%_manual_stereo_mixes"] = (_totalMixes > 0) ? (_manualStereoMixes / _totalMixes) * 100.0f : 0;
mixStats["%_manual_echo_mixes"] = (_totalMixes > 0) ? (_manualEchoMixes / _totalMixes) * 100.0f : 0;
mixStats["%_hrtf_mixes"] = percentageForMixStats(_hrtfRenders);
mixStats["%_hrtf_silent_mixes"] = percentageForMixStats(_hrtfSilentRenders);
mixStats["%_hrtf_struggle_mixes"] = percentageForMixStats(_hrtfStruggleRenders);
mixStats["%_manual_stereo_mixes"] = percentageForMixStats(_manualStereoMixes);
mixStats["%_manual_echo_mixes"] = percentageForMixStats(_manualEchoMixes);
mixStats["total_mixes"] = _totalMixes;
mixStats["avg_mixes_per_block"] = _totalMixes / _numStatFrames;
@ -519,42 +522,6 @@ void AudioMixer::sendStatsPacket() {
_totalMixes = 0;
_numStatFrames = 0;
QJsonObject readPendingDatagramStats;
QJsonObject rpdCallsStats;
rpdCallsStats["calls_per_sec_avg_30s"] = _readPendingCallsPerSecondStats.getWindowAverage();
rpdCallsStats["calls_last_sec"] = _readPendingCallsPerSecondStats.getLastCompleteIntervalStats().getSum() + 0.5;
readPendingDatagramStats["calls"] = rpdCallsStats;
QJsonObject packetsPerCallStats;
packetsPerCallStats["avg_30s"] = _datagramsReadPerCallStats.getWindowAverage();
packetsPerCallStats["avg_1s"] = _datagramsReadPerCallStats.getLastCompleteIntervalStats().getAverage();
readPendingDatagramStats["packets_per_call"] = packetsPerCallStats;
QJsonObject packetsTimePerCallStats;
packetsTimePerCallStats["usecs_per_call_avg_30s"] = _timeSpentPerCallStats.getWindowAverage();
packetsTimePerCallStats["usecs_per_call_avg_1s"] = _timeSpentPerCallStats.getLastCompleteIntervalStats().getAverage();
packetsTimePerCallStats["prct_time_in_call_30s"] =
_timeSpentPerCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS * USECS_PER_SECOND) * 100.0;
packetsTimePerCallStats["prct_time_in_call_1s"] =
_timeSpentPerCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0;
readPendingDatagramStats["packets_time_per_call"] = packetsTimePerCallStats;
QJsonObject hashMatchTimePerCallStats;
hashMatchTimePerCallStats["usecs_per_hashmatch_avg_30s"] = _timeSpentPerHashMatchCallStats.getWindowAverage();
hashMatchTimePerCallStats["usecs_per_hashmatch_avg_1s"]
= _timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getAverage();
hashMatchTimePerCallStats["prct_time_in_hashmatch_30s"]
= _timeSpentPerHashMatchCallStats.getWindowSum() / (READ_DATAGRAMS_STATS_WINDOW_SECONDS*USECS_PER_SECOND) * 100.0;
hashMatchTimePerCallStats["prct_time_in_hashmatch_1s"]
= _timeSpentPerHashMatchCallStats.getLastCompleteIntervalStats().getSum() / USECS_PER_SECOND * 100.0;
readPendingDatagramStats["hashmatch_time_per_call"] = hashMatchTimePerCallStats;
statsObject["read_pending_datagrams"] = readPendingDatagramStats;
// add stats for each listerner
auto nodeList = DependencyManager::get<NodeList>();
QJsonObject listenerStats;
@ -680,12 +647,6 @@ void AudioMixer::broadcastMixes() {
++framesSinceCutoffEvent;
}
quint64 now = usecTimestampNow();
if (now - _lastPerSecondCallbackTime > USECS_PER_SECOND) {
perSecondActions();
_lastPerSecondCallbackTime = now;
}
nodeList->eachNode([&](const SharedNodePointer& node) {
if (node->getLinkedData()) {
@ -741,10 +702,11 @@ void AudioMixer::broadcastMixes() {
nodeList->sendPacket(std::move(mixPacket), *node);
nodeData->incrementOutgoingMixedAudioSequenceNumber();
// send an audio stream stats packet if it's time
if (_sendAudioStreamStats) {
static const int FRAMES_PER_SECOND = int(ceilf(1.0f / AudioConstants::NETWORK_FRAME_SECS));
// send an audio stream stats packet to the client approximately every second
if (nextFrame % FRAMES_PER_SECOND == 0) {
nodeData->sendAudioStreamStatsPackets(node);
_sendAudioStreamStats = false;
}
++_sumListeners;
@ -772,64 +734,6 @@ void AudioMixer::broadcastMixes() {
}
}
void AudioMixer::perSecondActions() {
_sendAudioStreamStats = true;
int callsLastSecond = _datagramsReadPerCallStats.getCurrentIntervalSamples();
_readPendingCallsPerSecondStats.update(callsLastSecond);
if (_printStreamStats) {
printf("\n================================================================================\n\n");
printf(" readPendingDatagram() calls per second | avg: %.2f, avg_30s: %.2f, last_second: %d\n",
_readPendingCallsPerSecondStats.getAverage(),
_readPendingCallsPerSecondStats.getWindowAverage(),
callsLastSecond);
printf(" Datagrams read per call | avg: %.2f, avg_30s: %.2f, last_second: %.2f\n",
_datagramsReadPerCallStats.getAverage(),
_datagramsReadPerCallStats.getWindowAverage(),
_datagramsReadPerCallStats.getCurrentIntervalAverage());
printf(" Usecs spent per readPendingDatagram() call | avg: %.2f, avg_30s: %.2f, last_second: %.2f\n",
_timeSpentPerCallStats.getAverage(),
_timeSpentPerCallStats.getWindowAverage(),
_timeSpentPerCallStats.getCurrentIntervalAverage());
printf(" Usecs spent per packetVersionAndHashMatch() call | avg: %.2f, avg_30s: %.2f, last_second: %.2f\n",
_timeSpentPerHashMatchCallStats.getAverage(),
_timeSpentPerHashMatchCallStats.getWindowAverage(),
_timeSpentPerHashMatchCallStats.getCurrentIntervalAverage());
double WINDOW_LENGTH_USECS = READ_DATAGRAMS_STATS_WINDOW_SECONDS * USECS_PER_SECOND;
printf(" %% time spent in readPendingDatagram() calls | avg_30s: %.6f%%, last_second: %.6f%%\n",
_timeSpentPerCallStats.getWindowSum() / WINDOW_LENGTH_USECS * 100.0,
_timeSpentPerCallStats.getCurrentIntervalSum() / USECS_PER_SECOND * 100.0);
printf("%% time spent in packetVersionAndHashMatch() calls: | avg_30s: %.6f%%, last_second: %.6f%%\n",
_timeSpentPerHashMatchCallStats.getWindowSum() / WINDOW_LENGTH_USECS * 100.0,
_timeSpentPerHashMatchCallStats.getCurrentIntervalSum() / USECS_PER_SECOND * 100.0);
DependencyManager::get<NodeList>()->eachNode([](const SharedNodePointer& node) {
if (node->getLinkedData()) {
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
if (node->getType() == NodeType::Agent && node->getActiveSocket()) {
printf("\nStats for agent %s --------------------------------\n",
node->getUUID().toString().toLatin1().data());
nodeData->printUpstreamDownstreamStats();
}
}
});
}
_datagramsReadPerCallStats.currentIntervalComplete();
_timeSpentPerCallStats.currentIntervalComplete();
_timeSpentPerHashMatchCallStats.currentIntervalComplete();
}
void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
if (settingsObject.contains(AUDIO_BUFFER_GROUP_KEY)) {
QJsonObject audioBufferGroupObject = settingsObject[AUDIO_BUFFER_GROUP_KEY].toObject();
@ -894,12 +798,6 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
} else {
qDebug() << "Repetition with fade disabled";
}
const QString PRINT_STREAM_STATS_JSON_KEY = "print_stream_stats";
_printStreamStats = audioBufferGroupObject[PRINT_STREAM_STATS_JSON_KEY].toBool();
if (_printStreamStats) {
qDebug() << "Stream stats will be printed to stdout";
}
}
if (settingsObject.contains(AUDIO_ENV_GROUP_KEY)) {

View file

@ -71,6 +71,8 @@ private:
void perSecondActions();
QString percentageForMixStats(int counter);
bool shouldMute(float quietestFrame);
void parseSettingsObject(const QJsonObject& settingsObject);
@ -108,19 +110,7 @@ private:
static InboundAudioStream::Settings _streamSettings;
static bool _printStreamStats;
static bool _enableFilter;
quint64 _lastPerSecondCallbackTime;
bool _sendAudioStreamStats;
// stats
MovingMinMaxAvg<int> _datagramsReadPerCallStats; // update with # of datagrams read for each readPendingDatagrams call
MovingMinMaxAvg<quint64> _timeSpentPerCallStats; // update with usecs spent inside each readPendingDatagrams call
MovingMinMaxAvg<quint64> _timeSpentPerHashMatchCallStats; // update with usecs spent inside each packetVersionAndHashMatch call
MovingMinMaxAvg<int> _readPendingCallsPerSecondStats; // update with # of readPendingDatagrams calls in the last second
};
#endif // hifi_AudioMixer_h

View file

@ -311,48 +311,3 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
return result;
}
void AudioMixerClientData::printUpstreamDownstreamStats() {
auto streamsCopy = getAudioStreams();
// print the upstream (mic stream) stats if the mic stream exists
auto it = streamsCopy.find(QUuid());
if (it != streamsCopy.end()) {
printf("Upstream:\n");
printAudioStreamStats(it->second->getAudioStreamStats());
}
// print the downstream stats if they contain valid info
if (_downstreamAudioStreamStats._packetStreamStats._received > 0) {
printf("Downstream:\n");
printAudioStreamStats(_downstreamAudioStreamStats);
}
}
void AudioMixerClientData::printAudioStreamStats(const AudioStreamStats& streamStats) const {
printf(" Packet loss | overall: %5.2f%% (%d lost), last_30s: %5.2f%% (%d lost)\n",
(double)(streamStats._packetStreamStats.getLostRate() * 100.0f),
streamStats._packetStreamStats._lost,
(double)(streamStats._packetStreamWindowStats.getLostRate() * 100.0f),
streamStats._packetStreamWindowStats._lost);
printf(" Ringbuffer frames | desired: %u, avg_available(10s): %u, available: %u\n",
streamStats._desiredJitterBufferFrames,
streamStats._framesAvailableAverage,
streamStats._framesAvailable);
printf(" Ringbuffer stats | starves: %u, prev_starve_lasted: %u, frames_dropped: %u, overflows: %u\n",
streamStats._starveCount,
streamStats._consecutiveNotMixedCount,
streamStats._framesDropped,
streamStats._overflowCount);
printf(" Inter-packet timegaps (overall) | min: %9s, max: %9s, avg: %9s\n",
formatUsecTime(streamStats._timeGapMin).toLatin1().data(),
formatUsecTime(streamStats._timeGapMax).toLatin1().data(),
formatUsecTime(streamStats._timeGapAverage).toLatin1().data());
printf(" Inter-packet timegaps (last 30s) | min: %9s, max: %9s, avg: %9s\n",
formatUsecTime(streamStats._timeGapWindowMin).toLatin1().data(),
formatUsecTime(streamStats._timeGapWindowMax).toLatin1().data(),
formatUsecTime(streamStats._timeGapWindowAverage).toLatin1().data());
}

View file

@ -58,14 +58,9 @@ public:
void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
void printUpstreamDownstreamStats();
signals:
void injectorStreamFinished(const QUuid& streamIdentifier);
private:
void printAudioStreamStats(const AudioStreamStats& streamStats) const;
private:
QReadWriteLock _streamsLock;
AudioStreamMap _audioStreams; // microphone stream from avatar is stored under key of null UUID

View file

@ -7,8 +7,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://github.com/ValveSoftware/openvr/archive/v0.9.12.zip
URL_MD5 c08dced68ce4e341e1467e6814ae419d
URL https://github.com/ValveSoftware/openvr/archive/v0.9.15.zip
URL_MD5 0ff8560b49b6da1150fcc47360e8ceca
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -366,14 +366,6 @@
"help": "Dropped frames and mixing during starves repeat the last frame, eventually fading to silence",
"default": false,
"advanced": true
},
{
"name": "print_stream_stats",
"type": "checkbox",
"label": "Print Stream Stats",
"help": "Audio upstream and downstream stats of each agent printed to audio-mixer stdout",
"default": false,
"advanced": true
}
]
},

View file

@ -92,6 +92,12 @@ tr.new-row {
background-color: #dff0d8;
}
.graphable-stat {
text-align: center;
color: #5286BC;
cursor: pointer;
}
.highchart-modal .modal-dialog {
width: 650px;
}

View file

@ -5,10 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="/stats/css/json.human.css" rel="stylesheet" media="screen">
<link href="/css/style.css" rel="stylesheet" media="screen">
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
<link href="/stats/css/json.human.css" rel="stylesheet" media="screen">
</head>
<body>

View file

@ -1,5 +1,5 @@
$(document).ready(function(){
var currentHighchart;
// setup a function to grab the nodeStats
@ -17,12 +17,22 @@ $(document).ready(function(){
var stats = JsonHuman.format(json);
$('#stats-container').html(stats);
// add the clickable class to anything that looks like a number
$('.jh-value span').each(function(val){
console.log(val);
if (!isNaN($(this).text())) {
// this looks like a number - give it the clickable class so we can get graphs for it
$(this).addClass('graphable-stat');
}
});
if (currentHighchart) {
// get the current time to set with the point
var x = (new Date()).getTime();
// get the last value using underscore-keypath
var y = _(json).valueForKeyPath(graphKeypath);
var y = Number(_(json).valueForKeyPath(graphKeypath));
// start shifting the chart once we hit 20 data points
var shift = currentHighchart.series[0].data.length > 20;
@ -91,7 +101,7 @@ $(document).ready(function(){
}
// handle clicks on numerical values - this lets the user show a line graph in a modal
$('#stats-container').on('click', '.jh-type-number', function(){
$('#stats-container').on('click', '.graphable-stat', function(){
graphKeypath = $(this).data('keypath');
// setup the new graph modal

View file

@ -55,6 +55,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_oauthProviderURL(),
_oauthClientID(),
_hostname(),
_ephemeralACScripts(),
_webAuthenticationStateSet(),
_cookieSessionHash(),
_automaticNetworkingSetting(),
@ -1211,13 +1212,14 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
// via correct URL for the script so the client can download
QFile scriptFile(pathForAssignmentScript(matchingAssignment->getUUID()));
const auto it = _ephemeralACScripts.find(matchingAssignment->getUUID());
if (scriptFile.exists() && scriptFile.open(QIODevice::ReadOnly)) {
connection->respond(HTTPConnection::StatusCode200, scriptFile.readAll(), "application/javascript");
if (it != _ephemeralACScripts.end()) {
connection->respond(HTTPConnection::StatusCode200, it->second, "application/javascript");
} else {
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
}
return true;
}
@ -1387,29 +1389,15 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
for (int i = 0; i < numInstances; i++) {
// create an assignment for this saved script
Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType, assignmentPool);
QString newPath = pathForAssignmentScript(scriptAssignment->getUUID());
_ephemeralACScripts[scriptAssignment->getUUID()] = formData[0].second;
// create a file with the GUID of the assignment in the script host location
QFile scriptFile(newPath);
if (scriptFile.open(QIODevice::WriteOnly)) {
scriptFile.write(formData[0].second);
qDebug() << qPrintable(QString("Saved a script for assignment at %1%2")
.arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool));
// add the script assigment to the assignment queue
SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment);
_unfulfilledAssignments.enqueue(sharedScriptedAssignment);
_allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment);
} else {
// unable to save script for assignment - we shouldn't be here but debug it out
qDebug() << "Unable to save a script for assignment at" << newPath;
qDebug() << "Script will not be added to queue";
}
// add the script assigment to the assignment queue
SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment);
_unfulfilledAssignments.enqueue(sharedScriptedAssignment);
_allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment);
}
// respond with a 200 code for successful upload

View file

@ -142,6 +142,8 @@ private:
QString _oauthClientSecret;
QString _hostname;
std::unordered_map<QUuid, QByteArray> _ephemeralACScripts;
QSet<QUuid> _webAuthenticationStateSet;
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;

View file

@ -148,7 +148,7 @@ function AttachedEntitiesManager() {
this.checkIfWearable = function(grabbedEntity, releasedFromJoint) {
var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints;
var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID"]);
var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID", "parentJointIndex"]);
if (props.parentID === NULL_UUID || props.parentID === MyAvatar.sessionUUID) {
var bestJointName = "";
var bestJointIndex = -1;
@ -192,10 +192,14 @@ function AttachedEntitiesManager() {
}
Entities.editEntity(grabbedEntity, wearProps);
} else if (props.parentID != NULL_UUID) {
// drop the entity with no parent (not on the avatar)
Entities.editEntity(grabbedEntity, {
parentID: NULL_UUID
});
// drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand.
if (props.parentID === MyAvatar.sessionUUID &&
(props.parentJointIndex == MyAvatar.getJointIndex("RightHand") ||
props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) {
// this is equipped on a hand -- don't clear the parent.
} else {
Entities.editEntity(grabbedEntity, { parentID: NULL_UUID });
}
}
}
}

View file

@ -946,13 +946,15 @@ function MyController(hand) {
return;
}
// near grab or equip with action
if (near && (grabbableData.refCount < 1 || entityHasActions(this.grabbedEntity))) {
var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {});
var refCount = ("refCount" in grabData) ? grabData.refCount : 0;
if (near && (refCount < 1 || entityHasActions(this.grabbedEntity))) {
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
return;
}
// far grab or equip with action
if ((isPhysical || this.state == STATE_EQUIP_SEARCHING) && !near) {
if (entityIsGrabbedByOther(intersection.entityID)) {
if (entityIsGrabbedByOther(this.grabbedEntity)) {
// don't distance grab something that is already grabbed.
if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) {
print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': already grabbed by another.");
@ -1449,11 +1451,12 @@ function MyController(hand) {
return;
}
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID"]);
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position"]);
if (props.parentID == MyAvatar.sessionUUID &&
Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) {
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand.");
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
props.parentID + " " + vec3toStr(props.position));
this.setState(STATE_RELEASE);
this.callEntityMethodOnGrabbed(this.state == STATE_NEAR_GRABBING ? "releaseGrab" : "releaseEquip",
[JSON.stringify(this.hand)]);

View file

@ -1,5 +1,6 @@
(function () {
// See tests/performance/tribbles.js
Script.include("../libraries/virtualBaton.js");
var dimensions, oldColor, entityID,
editRate = 60,
moveRate = 1,
@ -7,7 +8,8 @@
accumulated = 0,
increment = {red: 1, green: 1, blue: 1},
hasUpdate = false,
shutdown = false;
shutdown = false,
baton;
function nextWavelength(color) {
var old = oldColor[color];
if (old === 255) {
@ -27,13 +29,37 @@
accumulated = 0;
}
}
function randomCentered() { return Math.random() - 0.5; }
function randomVector() { return {x: randomCentered() * dimensions.x, y: randomCentered() * dimensions.y, z: randomCentered() * dimensions.z}; }
function randomCentered() {
return Math.random() - 0.5;
}
function randomVector() {
return {x: randomCentered() * dimensions.x, y: randomCentered() * dimensions.y, z: randomCentered() * dimensions.z};
}
function move() {
var newData = {velocity: Vec3.sum({x: 0, y: 1, z: 0}, randomVector()), angularVelocity: Vec3.multiply(Math.PI, randomVector())};
var nextChange = Math.ceil(Math.random() * 2000 / moveRate);
Entities.editEntity(entityID, newData);
if (!shutdown) { Script.setTimeout(move, nextChange); }
if (!shutdown) {
Script.setTimeout(move, nextChange);
}
}
function startUpdate() {
print('startUpdate', entityID);
hasUpdate = true;
Script.update.connect(update);
}
function stopUpdate() {
print('stopUpdate', entityID, hasUpdate);
if (!hasUpdate) {
return;
}
hasUpdate = false;
Script.update.disconnect(update);
}
function stopUpdateAndReclaim() {
print('stopUpdateAndReclaim', entityID);
stopUpdate();
baton.claim(startUpdate, stopUpdateAndReclaim);
}
this.preload = function (givenEntityID) {
entityID = givenEntityID;
@ -41,26 +67,35 @@
var userData = properties.userData && JSON.parse(properties.userData);
var moveTimeout = userData ? userData.moveTimeout : 0;
var editTimeout = userData ? userData.editTimeout : 0;
var debug = (userData && userData.debug) || {};
editRate = (userData && userData.editRate) || editRate;
moveRate = (moveRate && userData.moveRate) || moveRate;
oldColor = properties.color;
dimensions = Vec3.multiply(scale, properties.dimensions);
baton = virtualBaton({
batonName: 'io.highfidelity.tribble:' + entityID, // One winner for each entity
debugFlow: debug.flow,
debugSend: debug.send,
debugReceive: debug.receive
});
if (editTimeout) {
hasUpdate = true;
Script.update.connect(update);
baton.claim(startUpdate, stopUpdateAndReclaim);
if (editTimeout > 0) {
Script.setTimeout(function () { Script.update.disconnect(update); hasUpdate = false; }, editTimeout * 1000);
Script.setTimeout(stopUpdate, editTimeout * 1000);
}
}
if (moveTimeout) {
Script.setTimeout(move, 1000);
if (moveTimeout > 0) {
Script.setTimeout(function () { shutdown = true; }, moveTimeout * 1000);
Script.setTimeout(function () {
shutdown = true;
}, moveTimeout * 1000);
}
}
};
this.unload = function () {
baton.unload();
shutdown = true;
if (hasUpdate) { Script.update.disconnect(update); }
stopUpdate();
};
})

View file

@ -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;
}

View file

@ -14,8 +14,8 @@ var Vec3, Quat, MyAvatar, Entities, Camera, Script, print;
// The _TIMEOUT parameters can be 0 for no activity, and -1 to be active indefinitely.
//
var NUMBER_TO_CREATE = 200;
var LIFETIME = 60; // seconds
var NUMBER_TO_CREATE = 100;
var LIFETIME = 120; // seconds
var EDIT_RATE = 60; // hz
var EDIT_TIMEOUT = -1;
var MOVE_RATE = 1; // hz
@ -68,7 +68,8 @@ Script.setInterval(function () {
moveTimeout: MOVE_TIMEOUT,
moveRate: MOVE_RATE,
editTimeout: EDIT_TIMEOUT,
editRate: EDIT_RATE
editRate: EDIT_RATE,
debug: {flow: false, send: false, receive: false}
});
for (i = 0; (i < numToCreate) && (totalCreated < NUMBER_TO_CREATE); i++) {
Entities.addEntity({

View file

@ -0,0 +1,66 @@
//
// ConfigSlider.qml
// examples/utilities/tools/render
//
// Created by Zach Pomerantz on 2/8/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
Item {
id: root
width: 400
height: 24
property bool integral: false
property var config
property string property
property alias label: labelControl.text
property alias min: sliderControl.minimumValue
property alias max: sliderControl.maximumValue
Component.onCompleted: {
// Binding favors qml value, so set it first
sliderControl.value = root.config[root.property];
bindingControl.when = true;
}
Label {
id: labelControl
text: root.label
anchors.left: root.left
anchors.leftMargin: 8
anchors.top: root.top
anchors.topMargin: 7
}
Label {
text: sliderControl.value.toFixed(root.integral ? 0 : 2)
anchors.left: root.left
anchors.leftMargin: 140
anchors.top: root.top
anchors.topMargin: 7
}
Binding {
id: bindingControl
target: root.config
property: root.property
value: sliderControl.value
when: false
}
Slider {
id: sliderControl
stepSize: root.integral ? 1.0 : 0.0
width: 192
height: 20
anchors.right: root.right
anchors.rightMargin: 8
anchors.top: root.top
anchors.topMargin: 3
}
}

View file

@ -0,0 +1,42 @@
//
// debug.js
// examples/utilities/tools/render
//
// Zach Pomerantz, created on 1/27/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
//
oldConfig = Render.toJSON();
Render.RenderShadowTask.enabled = true;
var RDT = Render.RenderDeferredTask;
RDT.AmbientOcclusion.enabled = true;
RDT.DebugDeferredBuffer.enabled = false;
["DrawOpaqueDeferred", "DrawTransparentDeferred", "DrawOverlay3DOpaque", "DrawOverlay3DTransparent"]
.map(function(name) { return RDT[name]; })
.forEach(function(job) { job.maxDrawn = job.numDrawn; });
// Set up the qml ui
var qml = Script.resolvePath('main.qml');
var window = new OverlayWindow({
title: 'Render Engine Configuration',
source: qml,
width: 400, height: 900,
});
window.setPosition(25, 50);
window.closed.connect(function() { Script.stop(); });
// Debug buffer sizing
var resizing = false;
Controller.mousePressEvent.connect(function() { resizing = true; });
Controller.mouseReleaseEvent.connect(function() { resizing = false; });
Controller.mouseMoveEvent.connect(function(e) { resizing && setDebugBufferSize(e.x); });
function setDebugBufferSize(x) {
x = (2.0 * (x / Window.innerWidth) - 1.0); // scale
x = Math.min(Math.max(-1, x), 1); // clamp
Render.RenderDeferredTask.DebugDeferredBuffer.size = {x: x, y: -1, z: 1, w: 1};
}
Script.scriptEnding.connect(function() { Render.fromJSON(oldConfig); } );

View file

@ -0,0 +1,110 @@
//
// main.qml
// examples/utilities/tools/render
//
// Created by Zach Pomerantz on 2/8/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
Column {
spacing: 8
Repeater {
model: [ "Opaque:DrawOpaqueDeferred", "Transparent:DrawTransparentDeferred",
"Opaque Overlays:DrawOverlay3DOpaque", "Transparent Overlays:DrawOverlay3DTransparent" ]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: true
config: Render.getConfig(modelData.split(":")[1])
property: "maxDrawn"
max: config.numDrawn
}
}
Row {
CheckBox {
text: qsTr("Display Status")
onCheckedChanged: { Render.getConfig("DrawStatus").showDisplay = checked }
}
CheckBox {
text: qsTr("Network/Physics Status")
onCheckedChanged: { Render.getConfig("DrawStatus").showNetwork = checked }
}
}
ConfigSlider {
label: qsTr("Tone Mapping Exposure")
config: Render.getConfig("ToneMapping")
property: "exposure"
min: -10; max: 10
}
Column {
id: ambientOcclusion
property var config: Render.getConfig("AmbientOcclusion")
Label { text: qsTr("Ambient Occlusion") }
// TODO: Add gpuTimer
CheckBox { text: qsTr("Dithering"); checked: ambientOcclusion.config.ditheringEnabled }
Repeater {
model: [
"Resolution Level:resolutionLevel:4",
"Obscurance Level:obscuranceLevel:1",
"Radius:radius:2",
"Falloff Bias:falloffBias:0.2",
"Edge Sharpness:edgeSharpness:1",
"Blur Radius:blurRadius:6",
"Blur Deviation:blurDeviation:3"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
config: ambientOcclusion.config
property: modelData.split(":")[1]
max: modelData.split(":")[2]
}
}
Repeater {
model: [
"Samples:numSamples:32",
"Spiral Turns:numSpiralTurns:30:"
]
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: true
config: ambientOcclusion.config
property: modelData.split(":")[1]
max: modelData.split(":")[2]
}
}
}
Column {
id: debug
property var config: Render.getConfig("DebugDeferredBuffer")
function setDebugMode(mode) {
debug.config.enabled = (mode != 0);
debug.config.mode = mode;
}
Label { text: qsTr("Debug Buffer") }
ExclusiveGroup { id: bufferGroup }
Repeater {
model: [
"Off", "Diffuse", "Metallic", "Roughness", "Normal", "Depth",
"Lighting", "Shadow", "Pyramid Depth", "Ambient Occlusion", "Custom Shader"
]
RadioButton {
text: qsTr(modelData)
exclusiveGroup: bufferGroup
checked: index == 0
onCheckedChanged: if (checked) debug.setDebugMode(index);
}
}
}
}

View file

@ -1,216 +0,0 @@
//
// renderEngineDebug.js
// examples/utilities/tools
//
// Sam Gateau
// Copyright 2015 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
//
Script.include("cookies.js");
var MENU = "Developer>Render>Debug Deferred Buffer";
var ACTIONS = ["Off", "Diffuse", "Metallic", "Roughness", "Normal", "Depth", "Lighting", "Shadow", "PyramidDepth", "AmbientOcclusion", "OcclusionBlurred", "Custom"];
var SETTINGS_KEY = "EngineDebugScript.DebugMode";
Number.prototype.clamp = function(min, max) {
return Math.min(Math.max(this, min), max);
};
var panel = new Panel(10, 100);
function CounterWidget(parentPanel, name, counter) {
var subPanel = parentPanel.newSubPanel(name);
var widget = parentPanel.items[name];
widget.editTitle({ width: 270 });
subPanel.newSlider('Max Drawn', -1, 1,
function(value) { counter.maxDrawn = value; }, // setter
function() { return counter.maxDrawn; }, // getter
function(value) { return value; });
var slider = subPanel.getWidget('Max Drawn');
this.update = function () {
var numDrawn = counter.numDrawn; // avoid double polling
var numMax = Math.max(numDrawn, 1);
var title = [
' ' + name,
numDrawn + ' / ' + counter.numFeed
].join('\t');
widget.editTitle({ text: title });
slider.setMaxValue(numMax);
};
};
var opaquesCounter = new CounterWidget(panel, "Opaques", Render.opaque);
var transparentsCounter = new CounterWidget(panel, "Transparents", Render.transparent);
var overlaysCounter = new CounterWidget(panel, "Overlays", Render.overlay3D);
var resizing = false;
var previousMode = Settings.getValue(SETTINGS_KEY, -1);
previousMode = 8;
Menu.addActionGroup(MENU, ACTIONS, ACTIONS[previousMode + 1]);
Render.deferredDebugMode = previousMode;
Render.deferredDebugSize = { x: 0.0, y: -1.0, z: 1.0, w: 1.0 }; // Reset to default size
function setEngineDeferredDebugSize(eventX) {
var scaledX = (2.0 * (eventX / Window.innerWidth) - 1.0).clamp(-1.0, 1.0);
Render.deferredDebugSize = { x: scaledX, y: -1.0, z: 1.0, w: 1.0 };
}
function shouldStartResizing(eventX) {
var x = Math.abs(eventX - Window.innerWidth * (1.0 + Render.deferredDebugSize.x) / 2.0);
var mode = Render.deferredDebugMode;
return mode !== -1 && x < 20;
}
function menuItemEvent(menuItem) {
var index = ACTIONS.indexOf(menuItem);
if (index >= 0) {
Render.deferredDebugMode = (index - 1);
}
}
// see libraries/render/src/render/Engine.h
var showDisplayStatusFlag = 1;
var showNetworkStatusFlag = 2;
panel.newCheckbox("Display status",
function(value) { Render.displayItemStatus = (value ?
Render.displayItemStatus | showDisplayStatusFlag :
Render.displayItemStatus & ~showDisplayStatusFlag); },
function() { return (Render.displayItemStatus & showDisplayStatusFlag) > 0; },
function(value) { return (value & showDisplayStatusFlag) > 0; }
);
panel.newCheckbox("Network/Physics status",
function(value) { Render.displayItemStatus = (value ?
Render.displayItemStatus | showNetworkStatusFlag :
Render.displayItemStatus & ~showNetworkStatusFlag); },
function() { return (Render.displayItemStatus & showNetworkStatusFlag) > 0; },
function(value) { return (value & showNetworkStatusFlag) > 0; }
);
panel.newSlider("Tone Mapping Exposure", -10, 10,
function (value) { Render.tone.exposure = value; },
function() { return Render.tone.exposure; },
function (value) { return (value); });
panel.newSlider("Ambient Occlusion Resolution Level", 0.0, 4.0,
function (value) { Render.ambientOcclusion.resolutionLevel = value; },
function() { return Render.ambientOcclusion.resolutionLevel; },
function (value) { return (value); });
panel.newSlider("Ambient Occlusion Radius", 0.0, 2.0,
function (value) { Render.ambientOcclusion.radius = value; },
function() { return Render.ambientOcclusion.radius; },
function (value) { return (value.toFixed(2)); });
panel.newSlider("Ambient Occlusion Level", 0.0, 1.0,
function (value) { Render.ambientOcclusion.level = value; },
function() { return Render.ambientOcclusion.level; },
function (value) { return (value.toFixed(2)); });
panel.newSlider("Ambient Occlusion Num Samples", 1, 32,
function (value) { Render.ambientOcclusion.numSamples = value; },
function() { return Render.ambientOcclusion.numSamples; },
function (value) { return (value); });
panel.newSlider("Ambient Occlusion Num Spiral Turns", 0.0, 30.0,
function (value) { Render.ambientOcclusion.numSpiralTurns = value; },
function() { return Render.ambientOcclusion.numSpiralTurns; },
function (value) { return (value.toFixed(2)); });
panel.newCheckbox("Ambient Occlusion Dithering",
function (value) { Render.ambientOcclusion.ditheringEnabled = value; },
function() { return Render.ambientOcclusion.ditheringEnabled; },
function (value) { return (value); });
panel.newSlider("Ambient Occlusion Falloff Bias", 0.0, 0.2,
function (value) { Render.ambientOcclusion.falloffBias = value; },
function() { return Render.ambientOcclusion.falloffBias; },
function (value) { return (value.toFixed(2)); });
panel.newSlider("Ambient Occlusion Edge Sharpness", 0.0, 1.0,
function (value) { Render.ambientOcclusion.edgeSharpness = value; },
function() { return Render.ambientOcclusion.edgeSharpness; },
function (value) { return (value.toFixed(2)); });
panel.newSlider("Ambient Occlusion Blur Radius", 0.0, 6.0,
function (value) { Render.ambientOcclusion.blurRadius = value; },
function() { return Render.ambientOcclusion.blurRadius; },
function (value) { return (value); });
panel.newSlider("Ambient Occlusion Blur Deviation", 0.0, 3.0,
function (value) { Render.ambientOcclusion.blurDeviation = value; },
function() { return Render.ambientOcclusion.blurDeviation; },
function (value) { return (value.toFixed(2)); });
panel.newSlider("Ambient Occlusion GPU time", 0.0, 10.0,
function (value) {},
function() { return Render.ambientOcclusion.gpuTime; },
function (value) { return (value.toFixed(2) + " ms"); });
var tickTackPeriod = 500;
function updateCounters() {
opaquesCounter.update();
transparentsCounter.update();
overlaysCounter.update();
panel.update("Ambient Occlusion GPU time");
}
Script.setInterval(updateCounters, tickTackPeriod);
function mouseMoveEvent(event) {
if (resizing) {
setEngineDeferredDebugSize(event.x);
} else {
panel.mouseMoveEvent(event);
}
}
function mousePressEvent(event) {
if (shouldStartResizing(event.x)) {
resizing = true;
} else {
panel.mousePressEvent(event);
}
}
function mouseReleaseEvent(event) {
if (resizing) {
resizing = false;
} else {
panel.mouseReleaseEvent(event);
}
}
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Menu.menuItemEvent.connect(menuItemEvent);
function scriptEnding() {
panel.destroy();
Menu.removeActionGroup(MENU);
// Reset
Settings.setValue(SETTINGS_KEY, Render.deferredDebugMode);
Render.deferredDebugMode = -1;
Render.deferredDebugSize = { x: 0.0, y: -1.0, z: 1.0, w: 1.0 };
Render.opaque.maxDrawn = -1;
Render.transparent.maxDrawn = -1;
Render.overlay3D.maxDrawn = -1;
}
Script.scriptEnding.connect(scriptEnding);
// Collapse items
panel.mousePressEvent({ x: panel.x, y: panel.items["Overlays"].y});
panel.mousePressEvent({ x: panel.x, y: panel.items["Transparents"].y});
panel.mousePressEvent({ x: panel.x, y: panel.items["Opaques"].y});

View file

@ -19,12 +19,40 @@ Windows.Window {
property var channel;
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property alias source: pageLoader.source
Loader {
id: pageLoader
objectName: "Loader"
focus: true
property var dialog: root
property var source;
property var component;
property var dynamicContent;
onSourceChanged: {
if (dynamicContent) {
dynamicContent.destroy();
dynamicContent = null;
}
component = Qt.createComponent(source);
console.log("Created component " + component + " from source " + source);
}
} // dialog
onComponentChanged: {
console.log("Component changed to " + component)
populate();
}
function populate() {
console.log("Populate called: dynamicContent " + dynamicContent + " component " + component);
if (!dynamicContent && component) {
if (component.status == Component.Error) {
console.log("Error loading component:", component.errorString());
} else if (component.status == Component.Ready) {
console.log("Building dynamic content");
dynamicContent = component.createObject(contentHolder);
} else {
console.log("Component not yet ready, connecting to status change");
component.statusChanged.connect(populate);
}
}
}
Item {
id: contentHolder
anchors.fill: parent
}
}

View file

@ -17,8 +17,8 @@ Fadable {
HifiConstants { id: hifi }
// The Window size is the size of the content, while the frame
// decorations can extend outside it.
implicitHeight: content.height
implicitWidth: content.width
implicitHeight: content ? content.height : 0
implicitWidth: content ? content.width : 0
x: -1; y: -1
enabled: visible

View file

@ -3158,8 +3158,9 @@ void Application::update(float deltaTime) {
PerformanceTimer perfTimer("havestChanges");
if (_physicsEngine->hasOutgoingChanges()) {
getEntities()->getTree()->withWriteLock([&] {
_entitySimulation.handleOutgoingChanges(_physicsEngine->getOutgoingChanges(), Physics::getSessionUUID());
avatarManager->handleOutgoingChanges(_physicsEngine->getOutgoingChanges());
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges();
_entitySimulation.handleOutgoingChanges(outgoingChanges, Physics::getSessionUUID());
avatarManager->handleOutgoingChanges(outgoingChanges);
});
auto collisionEvents = _physicsEngine->getCollisionEvents();
@ -3877,6 +3878,8 @@ void Application::clearDomainOctreeDetails() {
qCDebug(interfaceapp) << "Clearing domain octree details...";
// reset the environment so that we don't erroneously end up with multiple
_physicsEnabled = false;
// reset our node to stats and node to jurisdiction maps... since these must be changing...
_entityServerJurisdictions.withWriteLock([&] {
_entityServerJurisdictions.clear();

View file

@ -367,6 +367,11 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
//virtual
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
if (dt > MAX_OVERLAY_DT) {
dt = MAX_OVERLAY_DT;
}
if (_relativePoses.size() != underPoses.size()) {
loadPoses(underPoses);
} else {
@ -463,7 +468,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
// smooth transitions by relaxing _hipsOffset toward the new value
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
_hipsOffset += (newHipsOffset - _hipsOffset) * (dt / HIPS_OFFSET_SLAVE_TIMESCALE);
float tau = dt > HIPS_OFFSET_SLAVE_TIMESCALE ? 1.0f : dt / HIPS_OFFSET_SLAVE_TIMESCALE;
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
}
}
return _relativePoses;

View file

@ -27,8 +27,8 @@ namespace AudioConstants {
const int NETWORK_FRAME_SAMPLES_STEREO = NETWORK_FRAME_BYTES_STEREO / sizeof(AudioSample);
const int NETWORK_FRAME_BYTES_PER_CHANNEL = 512;
const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / sizeof(AudioSample);
const float NETWORK_FRAME_MSECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL
/ (float)AudioConstants::SAMPLE_RATE) * 1000.0f;
const float NETWORK_FRAME_SECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL / float(AudioConstants::SAMPLE_RATE));
const float NETWORK_FRAME_MSECS = NETWORK_FRAME_SECS * 1000.0f;
// be careful with overflows when using this constant
const int NETWORK_FRAME_USECS = static_cast<int>(NETWORK_FRAME_MSECS * 1000.0f);

View file

@ -41,8 +41,6 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemI
return; // bail early
}
assert(properties.getLastEdited() > 0);
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, bufferOut)) {

View file

@ -659,10 +659,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// but since we're using macros below we have to temporarily modify overwriteLocalData.
bool oldOverwrite = overwriteLocalData;
overwriteLocalData = overwriteLocalData && !weOwnSimulation;
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition);
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity);
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity);
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionFromNetwork);
READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotationFromNetwork);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityFromNetwork);
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityFromNetwork);
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration);
overwriteLocalData = oldOverwrite;
}
@ -885,83 +885,93 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) {
return;
}
if (hasAngularVelocity()) {
glm::vec3 angularVelocity = getAngularVelocity();
if (hasLocalAngularVelocity()) {
glm::vec3 localAngularVelocity = getLocalAngularVelocity();
// angular damping
if (_angularDamping > 0.0f) {
angularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
localAngularVelocity *= powf(1.0f - _angularDamping, timeElapsed);
#ifdef WANT_DEBUG
qCDebug(entities) << " angularDamping :" << _angularDamping;
qCDebug(entities) << " newAngularVelocity:" << angularVelocity;
qCDebug(entities) << " newAngularVelocity:" << localAngularVelocity;
#endif
}
float angularSpeed = glm::length(angularVelocity);
float angularSpeed = glm::length(localAngularVelocity);
const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.0017453f; // 0.0017453 rad/sec = 0.1f degrees/sec
if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) {
if (setFlags && angularSpeed > 0.0f) {
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
localAngularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else {
// for improved agreement with the way Bullet integrates rotations we use an approximation
// and break the integration into bullet-sized substeps
glm::quat rotation = getRotation();
float dt = timeElapsed;
while (dt > PHYSICS_ENGINE_FIXED_SUBSTEP) {
glm::quat dQ = computeBulletRotationStep(angularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP);
glm::quat dQ = computeBulletRotationStep(localAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP);
rotation = glm::normalize(dQ * rotation);
dt -= PHYSICS_ENGINE_FIXED_SUBSTEP;
}
// NOTE: this final partial substep can drift away from a real Bullet simulation however
// it only becomes significant for rapidly rotating objects
// (e.g. around PI/4 radians per substep, or 7.5 rotations/sec at 60 substeps/sec).
glm::quat dQ = computeBulletRotationStep(angularVelocity, dt);
glm::quat dQ = computeBulletRotationStep(localAngularVelocity, dt);
rotation = glm::normalize(dQ * rotation);
setRotation(rotation);
}
setAngularVelocity(angularVelocity);
setLocalAngularVelocity(localAngularVelocity);
}
if (hasVelocity()) {
if (hasLocalVelocity()) {
// acceleration is in the global frame, so transform it into the local frame.
// TODO: Move this into SpatiallyNestable.
bool success;
Transform transform = getParentTransform(success);
glm::vec3 localAcceleration(glm::vec3::_null);
if (success) {
localAcceleration = glm::inverse(transform.getRotation()) * getAcceleration();
} else {
localAcceleration = getAcceleration();
}
// linear damping
glm::vec3 velocity = getVelocity();
glm::vec3 localVelocity = getLocalVelocity();
if (_damping > 0.0f) {
velocity *= powf(1.0f - _damping, timeElapsed);
localVelocity *= powf(1.0f - _damping, timeElapsed);
#ifdef WANT_DEBUG
qCDebug(entities) << " damping:" << _damping;
qCDebug(entities) << " velocity AFTER dampingResistance:" << velocity;
qCDebug(entities) << " glm::length(velocity):" << glm::length(velocity);
qCDebug(entities) << " velocity AFTER dampingResistance:" << localVelocity;
qCDebug(entities) << " glm::length(velocity):" << glm::length(localVelocity);
#endif
}
// integrate position forward
glm::vec3 position = getPosition();
glm::vec3 newPosition = position + (velocity * timeElapsed);
glm::vec3 localPosition = getLocalPosition();
glm::vec3 newLocalPosition = localPosition + (localVelocity * timeElapsed) + 0.5f * localAcceleration * timeElapsed * timeElapsed;
#ifdef WANT_DEBUG
qCDebug(entities) << " EntityItem::simulate()....";
qCDebug(entities) << " timeElapsed:" << timeElapsed;
qCDebug(entities) << " old AACube:" << getMaximumAACube();
qCDebug(entities) << " old position:" << position;
qCDebug(entities) << " old velocity:" << velocity;
qCDebug(entities) << " old position:" << localPosition;
qCDebug(entities) << " old velocity:" << localVelocity;
qCDebug(entities) << " old getAABox:" << getAABox();
qCDebug(entities) << " newPosition:" << newPosition;
qCDebug(entities) << " glm::distance(newPosition, position):" << glm::distance(newPosition, position);
qCDebug(entities) << " glm::distance(newPosition, position):" << glm::distance(newLocalPosition, localPosition);
#endif
position = newPosition;
localPosition = newLocalPosition;
// apply effective acceleration, which will be the same as gravity if the Entity isn't at rest.
if (hasAcceleration()) {
velocity += getAcceleration() * timeElapsed;
}
localVelocity += localAcceleration * timeElapsed;
float speed = glm::length(velocity);
float speed = glm::length(localVelocity);
const float EPSILON_LINEAR_VELOCITY_LENGTH = 0.001f; // 1mm/sec
if (speed < EPSILON_LINEAR_VELOCITY_LENGTH) {
setVelocity(ENTITY_ITEM_ZERO_VEC3);
@ -969,8 +979,8 @@ void EntityItem::simulateKinematicMotion(float timeElapsed, bool setFlags) {
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
}
} else {
setPosition(position);
setVelocity(velocity);
setLocalPosition(localPosition);
setLocalVelocity(localVelocity);
}
#ifdef WANT_DEBUG
@ -986,6 +996,10 @@ bool EntityItem::isMoving() const {
return hasVelocity() || hasAngularVelocity();
}
bool EntityItem::isMovingRelativeToParent() const {
return hasLocalVelocity() || hasLocalAngularVelocity();
}
EntityTreePointer EntityItem::getTree() const {
EntityTreeElementPointer containingElement = getElement();
EntityTreePointer tree = containingElement ? containingElement->getTree() : nullptr;
@ -1343,9 +1357,6 @@ void EntityItem::computeShapeInfo(ShapeInfo& info) {
}
void EntityItem::updatePosition(const glm::vec3& value) {
if (shouldSuppressLocationEdits()) {
return;
}
if (getLocalPosition() != value) {
setLocalPosition(value);
_dirtyFlags |= Simulation::DIRTY_POSITION;
@ -1358,6 +1369,13 @@ void EntityItem::updatePosition(const glm::vec3& value) {
}
}
void EntityItem::updatePositionFromNetwork(const glm::vec3& value) {
if (shouldSuppressLocationEdits()) {
return;
}
updatePosition(value);
}
void EntityItem::updateDimensions(const glm::vec3& value) {
if (getDimensions() != value) {
setDimensions(value);
@ -1366,9 +1384,6 @@ void EntityItem::updateDimensions(const glm::vec3& value) {
}
void EntityItem::updateRotation(const glm::quat& rotation) {
if (shouldSuppressLocationEdits()) {
return;
}
if (getLocalOrientation() != rotation) {
setLocalOrientation(rotation);
_dirtyFlags |= Simulation::DIRTY_ROTATION;
@ -1382,6 +1397,13 @@ void EntityItem::updateRotation(const glm::quat& rotation) {
}
}
void EntityItem::updateRotationFromNetwork(const glm::quat& rotation) {
if (shouldSuppressLocationEdits()) {
return;
}
updateRotation(rotation);
}
void EntityItem::updateMass(float mass) {
// Setting the mass actually changes the _density (at fixed volume), however
// we must protect the density range to help maintain stability of physics simulation
@ -1406,9 +1428,6 @@ void EntityItem::updateMass(float mass) {
}
void EntityItem::updateVelocity(const glm::vec3& value) {
if (shouldSuppressLocationEdits()) {
return;
}
glm::vec3 velocity = getLocalVelocity();
if (velocity != value) {
const float MIN_LINEAR_SPEED = 0.001f;
@ -1422,6 +1441,13 @@ void EntityItem::updateVelocity(const glm::vec3& value) {
}
}
void EntityItem::updateVelocityFromNetwork(const glm::vec3& value) {
if (shouldSuppressLocationEdits()) {
return;
}
updateVelocity(value);
}
void EntityItem::updateDamping(float value) {
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
if (_damping != clampedDamping) {
@ -1438,9 +1464,6 @@ void EntityItem::updateGravity(const glm::vec3& value) {
}
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
if (shouldSuppressLocationEdits()) {
return;
}
glm::vec3 angularVelocity = getLocalAngularVelocity();
if (angularVelocity != value) {
const float MIN_ANGULAR_SPEED = 0.0002f;
@ -1454,6 +1477,13 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) {
}
}
void EntityItem::updateAngularVelocityFromNetwork(const glm::vec3& value) {
if (shouldSuppressLocationEdits()) {
return;
}
updateAngularVelocity(value);
}
void EntityItem::updateAngularDamping(float value) {
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
if (_angularDamping != clampedDamping) {

View file

@ -194,6 +194,7 @@ public:
float getDensity() const { return _density; }
bool hasVelocity() const { return getVelocity() != ENTITY_ITEM_ZERO_VEC3; }
bool hasLocalVelocity() const { return getLocalVelocity() != ENTITY_ITEM_ZERO_VEC3; }
const glm::vec3& getGravity() const { return _gravity; } /// get gravity in meters
void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in meters
@ -255,6 +256,7 @@ public:
}
bool hasAngularVelocity() const { return getAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; }
bool hasLocalAngularVelocity() const { return getLocalAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; }
float getAngularDamping() const { return _angularDamping; }
void setAngularDamping(float value) { _angularDamping = value; }
@ -318,16 +320,20 @@ public:
// updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags
void updatePosition(const glm::vec3& value);
void updatePositionFromNetwork(const glm::vec3& value);
void updateDimensions(const glm::vec3& value);
void updateRotation(const glm::quat& rotation);
void updateRotationFromNetwork(const glm::quat& rotation);
void updateDensity(float value);
void updateMass(float value);
void updateVelocity(const glm::vec3& value);
void updateVelocityFromNetwork(const glm::vec3& value);
void updateDamping(float value);
void updateRestitution(float value);
void updateFriction(float value);
void updateGravity(const glm::vec3& value);
void updateAngularVelocity(const glm::vec3& value);
void updateAngularVelocityFromNetwork(const glm::vec3& value);
void updateAngularDamping(float value);
void updateCollisionless(bool value);
void updateCollisionMask(uint8_t value);
@ -340,6 +346,7 @@ public:
void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; }
bool isMoving() const;
bool isMovingRelativeToParent() const;
bool isSimulated() const { return _simulated; }

View file

@ -254,7 +254,7 @@ void EntitySimulation::moveSimpleKinematics(const quint64& now) {
SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin();
while (itemItr != _simpleKinematicEntities.end()) {
EntityItemPointer entity = *itemItr;
if (entity->isMoving() && !entity->getPhysicsInfo()) {
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
entity->simulate(now);
_entitiesToSort.insert(entity);
++itemItr;

View file

@ -313,7 +313,7 @@ OffscreenQmlSurface::OffscreenQmlSurface() {
OffscreenQmlSurface::~OffscreenQmlSurface() {
_renderer->stop();
delete _rootItem;
delete _renderer;
delete _qmlComponent;
delete _qmlEngine;

View file

@ -167,11 +167,13 @@ void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionState
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
assert(motionState);
_pendingChanges.remove(motionState);
_physicalObjects.remove(motionState);
result.push_back(motionState);
_entitiesToRelease.insert(entity);
if (motionState) {
_pendingChanges.remove(motionState);
_outgoingChanges.remove(motionState);
_physicalObjects.remove(motionState);
result.push_back(motionState);
_entitiesToRelease.insert(entity);
}
if (entity->isSimulated() && entity->isDead()) {
_entitiesToDelete.insert(entity);

View file

@ -136,23 +136,19 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
motionState->clearIncomingDirtyFlags();
}
// private
void PhysicsEngine::removeObjectFromDynamicsWorld(ObjectMotionState* object) {
// wake up anything touching this object
bump(object);
removeContacts(object);
btRigidBody* body = object->getRigidBody();
assert(body);
_dynamicsWorld->removeRigidBody(body);
}
void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) {
// first bump and prune contacts for all objects in the list
for (auto object : objects) {
removeObjectFromDynamicsWorld(object);
bumpAndPruneContacts(object);
}
// then remove them
for (auto object : objects) {
btRigidBody* body = object->getRigidBody();
assert(body);
_dynamicsWorld->removeRigidBody(body);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
btRigidBody* body = object->getRigidBody();
object->setRigidBody(nullptr);
body->setMotionState(nullptr);
delete body;
@ -163,7 +159,8 @@ void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) {
void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) {
for (auto object : objects) {
btRigidBody* body = object->getRigidBody();
removeObjectFromDynamicsWorld(object);
assert(body);
_dynamicsWorld->removeRigidBody(body);
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
object->setRigidBody(nullptr);
@ -200,7 +197,13 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob
}
void PhysicsEngine::reinsertObject(ObjectMotionState* object) {
removeObjectFromDynamicsWorld(object);
// remove object from DynamicsWorld
bumpAndPruneContacts(object);
btRigidBody* body = object->getRigidBody();
assert(body);
_dynamicsWorld->removeRigidBody(body);
// add it back
addObjectToDynamicsWorld(object);
}
@ -402,7 +405,7 @@ void PhysicsEngine::dumpStatsIfNecessary() {
// CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing
// CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing
void PhysicsEngine::bump(ObjectMotionState* motionState) {
void PhysicsEngine::bumpAndPruneContacts(ObjectMotionState* motionState) {
// Find all objects that touch the object corresponding to motionState and flag the other objects
// for simulation ownership by the local simulation.
@ -434,6 +437,7 @@ void PhysicsEngine::bump(ObjectMotionState* motionState) {
}
}
}
removeContacts(motionState);
}
void PhysicsEngine::setCharacterController(CharacterController* character) {

View file

@ -78,9 +78,6 @@ public:
/// \return position of simulation origin in domain-frame
const glm::vec3& getOriginOffset() const { return _originOffset; }
/// \brief call bump on any objects that touch the object corresponding to motionState
void bump(ObjectMotionState* motionState);
void setCharacterController(CharacterController* character);
void dumpNextStats() { _dumpNextStats = true; }
@ -94,7 +91,9 @@ public:
private:
void addObjectToDynamicsWorld(ObjectMotionState* motionState);
void removeObjectFromDynamicsWorld(ObjectMotionState* motionState);
/// \brief bump any objects that touch this one, then remove contact info
void bumpAndPruneContacts(ObjectMotionState* motionState);
void removeContacts(ObjectMotionState* motionState);

View file

@ -115,8 +115,8 @@ void ThreadSafeDynamicsWorld::synchronizeMotionState(btRigidBody* body) {
}
void ThreadSafeDynamicsWorld::synchronizeMotionStates() {
_changedMotionStates.clear();
BT_PROFILE("synchronizeMotionStates");
_changedMotionStates.clear();
if (m_synchronizeAllMotionStates) {
//iterate over all collision objects
for (int i=0;i<m_collisionObjects.size();i++) {

View file

@ -40,14 +40,14 @@ public:
int stepSimulationWithSubstepCallback(btScalar timeStep, int maxSubSteps = 1,
btScalar fixedTimeStep = btScalar(1.)/btScalar(60.),
SubStepCallback onSubStep = []() { });
void synchronizeMotionStates();
virtual void synchronizeMotionStates() override;
// btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated
// but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide
// smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop).
float getLocalTimeAccumulation() const { return m_localTime; }
VectorOfMotionStates& getChangedMotionStates() { return _changedMotionStates; }
const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; }
private:
// call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState()

View file

@ -166,7 +166,8 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont
batch.setStateScissorRect(args->_viewport);
args->_batch = &batch;
config->numDrawn = (int)inItems.size();
config->setNumDrawn((int)inItems.size());
emit config->numDrawnChanged();
glm::mat4 projMat;
Transform viewMat;
@ -203,7 +204,8 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon
inItems.emplace_back(id);
}
}
config->numItems = (int)inItems.size();
config->setNumDrawn((int)inItems.size());
emit config->numDrawnChanged();
if (!inItems.empty()) {
RenderArgs* args = renderContext->args;

View file

@ -40,16 +40,21 @@ public:
class DrawConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(int numDrawn READ getNumDrawn)
Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
public:
int getNumDrawn() { return numDrawn; }
void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
int numDrawn{ 0 };
int maxDrawn{ -1 };
signals:
void numDrawnChanged();
void dirty();
protected:
int numDrawn{ 0 };
};
class DrawDeferred {
@ -87,15 +92,20 @@ public:
class DrawOverlay3DConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems)
Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
public:
int getNumItems() { return numItems; }
int getNumDrawn() { return numDrawn; }
void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
int numItems{ 0 };
int maxDrawn{ -1 };
signals:
void numDrawnChanged();
void dirty();
protected:
int numDrawn{ 0 };
};
class DrawOverlay3D {

View file

@ -71,10 +71,12 @@ public:
bool alwaysEnabled{ true };
bool enabled{ true };
// This must be named toJSON to integrate with the global scripting JSON object
Q_INVOKABLE QString toJSON() { return QJsonDocument(toJsonValue(*this).toObject()).toJson(QJsonDocument::Compact); }
Q_INVOKABLE void load(const QVariantMap& map) { qObjectFromJsonValue(QJsonObject::fromVariantMap(map), *this); }
public slots:
void load(const QJsonValue& json) { qObjectFromJsonValue(json, *this); }
void load(const QJsonObject& val) { qObjectFromJsonValue(val, *this); }
};
class TaskConfig : public JobConfig {
@ -85,6 +87,9 @@ public:
void init(Task* task) { _task = task; }
// getter for qml integration, prefer the templated getter
Q_INVOKABLE QObject* getConfig(const QString& name) { return QObject::findChild<JobConfig*>(name); }
// getter for cpp (strictly typed), prefer this getter
template <class T> typename T::Config* getConfig(std::string job = "") const {
QString name = job.empty() ? QString() : QString(job.c_str()); // an empty string is not a null string
return findChild<typename T::Config*>(name);

View file

@ -416,7 +416,7 @@ void SpatiallyNestable::setVelocity(const glm::vec3& velocity, bool& success) {
Transform parentTransform = getParentTransform(success);
_velocityLock.withWriteLock([&] {
// TODO: take parent angularVelocity into account.
_velocity = glm::inverse(parentTransform.getRotation()) * velocity - parentVelocity;
_velocity = glm::inverse(parentTransform.getRotation()) * (velocity - parentVelocity);
});
}
@ -460,7 +460,7 @@ void SpatiallyNestable::setAngularVelocity(const glm::vec3& angularVelocity, boo
glm::vec3 parentAngularVelocity = getParentAngularVelocity(success);
Transform parentTransform = getParentTransform(success);
_angularVelocityLock.withWriteLock([&] {
_angularVelocity = glm::inverse(parentTransform.getRotation()) * angularVelocity - parentAngularVelocity;
_angularVelocity = glm::inverse(parentTransform.getRotation()) * (angularVelocity - parentAngularVelocity);
});
}

View file

@ -111,10 +111,9 @@ void qObjectFromJsonValue(const QJsonValue& j, QObject& o) {
for (auto it = object.begin(); it != object.end(); it++) {
std::string key = it.key().toStdString();
if (it.value().isObject()) {
QVariant child = o.property(key.c_str());
if (child.isValid()) {
QObject* object = child.value<QObject*>();
qObjectFromJsonValue(it.value(), *object);
QObject* child = o.findChild<QObject*>(key.c_str(), Qt::FindChildOption::FindDirectChildrenOnly);
if (child) {
qObjectFromJsonValue(it.value(), *child);
}
} else {
o.setProperty(key.c_str(), it.value());

View file

@ -39,6 +39,7 @@ glm::mat4 OculusBaseDisplayPlugin::getHeadPose(uint32_t frameIndex) const {
bool OculusBaseDisplayPlugin::isSupported() const {
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
qDebug() << "OculusBaseDisplayPlugin : ovr_Initialize() failed";
return false;
}
@ -48,6 +49,7 @@ bool OculusBaseDisplayPlugin::isSupported() const {
if (!OVR_SUCCESS(result)) {
ovrErrorInfo error;
ovr_GetLastErrorInfo(&error);
qDebug() << "OculusBaseDisplayPlugin : ovr_Create() failed" << result << error.Result << error.ErrorString;
ovr_Shutdown();
return false;
}

View file

@ -6,20 +6,17 @@
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
#
# OpenVR is disabled until a) it works with threaded present and
# b) it doesn't interfere with Oculus SDK 0.8
if (FALSE)
#if (WIN32)
if (WIN32)
# we're using static GLEW, so define GLEW_STATIC
add_definitions(-DGLEW_STATIC)
set(TARGET_NAME openvr)
setup_hifi_plugin(OpenGL Script Qml Widgets)
link_hifi_libraries(shared gl networking controllers
plugins display-plugins input-plugins script-engine
link_hifi_libraries(shared gl networking controllers
plugins display-plugins input-plugins script-engine
render-utils model gpu render model-networking fbx)
include_hifi_library_headers(octree)
add_dependency_external_projects(OpenVR)
find_package(OpenVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})

View file

@ -32,12 +32,15 @@ const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)");
const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here
static vr::IVRCompositor* _compositor{ nullptr };
static vr::TrackedDevicePose_t _presentThreadTrackedDevicePose[vr::k_unMaxTrackedDeviceCount];
vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount];
mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount];
static mat4 _sensorResetMat;
static uvec2 _windowSize;
static uvec2 _renderTargetSize;
struct PerEyeData {
//uvec2 _viewportOrigin;
//uvec2 _viewportSize;
@ -69,10 +72,7 @@ mat4 toGlm(const vr::HmdMatrix34_t& m) {
}
bool OpenVrDisplayPlugin::isSupported() const {
auto hmd = acquireOpenVrSystem();
bool success = nullptr != hmd;
releaseOpenVrSystem();
return success;
return vr::VR_IsHmdPresent();
}
void OpenVrDisplayPlugin::activate() {
@ -87,11 +87,16 @@ void OpenVrDisplayPlugin::activate() {
// Recommended render target size is per-eye, so double the X size for
// left + right eyes
_renderTargetSize.x *= 2;
openvr_for_each_eye([&](vr::Hmd_Eye eye) {
PerEyeData& eyeData = _eyesData[eye];
eyeData._projectionMatrix = toGlm(_hmd->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL));
eyeData._eyeOffset = toGlm(_hmd->GetEyeToHeadTransform(eye));
});
{
Lock lock(_poseMutex);
openvr_for_each_eye([&](vr::Hmd_Eye eye) {
PerEyeData& eyeData = _eyesData[eye];
eyeData._projectionMatrix = toGlm(_hmd->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL));
eyeData._eyeOffset = toGlm(_hmd->GetEyeToHeadTransform(eye));
});
}
_compositor = vr::VRCompositor();
Q_ASSERT(_compositor);
WindowOpenGLDisplayPlugin::activate();
@ -115,6 +120,10 @@ void OpenVrDisplayPlugin::customizeContext() {
glGetError();
});
WindowOpenGLDisplayPlugin::customizeContext();
enableVsync(false);
// Only enable mirroring if we know vsync is disabled
_enablePreview = !isVsyncEnabled();
}
uvec2 OpenVrDisplayPlugin::getRecommendedRenderSize() const {
@ -126,25 +135,24 @@ mat4 OpenVrDisplayPlugin::getProjection(Eye eye, const mat4& baseProjection) con
if (eye == Mono) {
eye = Left;
}
Lock lock(_poseMutex);
return _eyesData[eye]._projectionMatrix;
}
void OpenVrDisplayPlugin::resetSensors() {
_sensorResetMat = glm::inverse(cancelOutRollAndPitch(_trackedDevicePoseMat4[0]));
Lock lock(_poseMutex);
glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking);
_sensorResetMat = glm::inverse(cancelOutRollAndPitch(m));
}
glm::mat4 OpenVrDisplayPlugin::getEyeToHeadTransform(Eye eye) const {
Lock lock(_poseMutex);
return _eyesData[eye]._eyeOffset;
}
glm::mat4 OpenVrDisplayPlugin::getHeadPose(uint32_t frameIndex) const {
glm::mat4 result;
{
Lock lock(_mutex);
result = _trackedDevicePoseMat4[0];
}
return result;
Lock lock(_poseMutex);
return _trackedDevicePoseMat4[0];
}
@ -156,17 +164,40 @@ void OpenVrDisplayPlugin::internalPresent() {
// Flip y-axis since GL UV coords are backwards.
static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 };
static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 };
vr::Texture_t texture{ (void*)_currentSceneTexture, vr::API_OpenGL, vr::ColorSpace_Auto };
{
Lock lock(_mutex);
_compositor->Submit(vr::Eye_Left, &texture, &leftBounds);
_compositor->Submit(vr::Eye_Right, &texture, &rightBounds);
// screen preview mirroring
if (_enablePreview) {
auto windowSize = toGlm(_window->size());
if (_monoPreview) {
glViewport(0, 0, windowSize.x * 2, windowSize.y);
glScissor(0, windowSize.y, windowSize.x, windowSize.y);
} else {
glViewport(0, 0, windowSize.x, windowSize.y);
}
glBindTexture(GL_TEXTURE_2D, _currentSceneTexture);
GLenum err = glGetError();
Q_ASSERT(0 == err);
drawUnitQuad();
}
vr::Texture_t texture{ (void*)_currentSceneTexture, vr::API_OpenGL, vr::ColorSpace_Auto };
_compositor->Submit(vr::Eye_Left, &texture, &leftBounds);
_compositor->Submit(vr::Eye_Right, &texture, &rightBounds);
glFinish();
if (_enablePreview) {
swapBuffers();
}
_compositor->WaitGetPoses(_presentThreadTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, nullptr, 0);
{
Lock lock(_mutex);
_compositor->WaitGetPoses(_trackedDevicePose, vr::k_unMaxTrackedDeviceCount, nullptr, 0);
// copy and process _presentThreadTrackedDevicePoses
Lock lock(_poseMutex);
for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) {
_trackedDevicePose[i] = _presentThreadTrackedDevicePose[i];
_trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking);
}
openvr_for_each_eye([&](vr::Hmd_Eye eye) {

View file

@ -45,5 +45,8 @@ protected:
private:
vr::IVRSystem* _hmd { nullptr };
static const QString NAME;
bool _enablePreview { false };
bool _monoPreview { true };
mutable Mutex _poseMutex;
};

View file

@ -23,11 +23,11 @@ using Lock = std::unique_lock<Mutex>;
static int refCount { 0 };
static Mutex mutex;
static vr::IVRSystem* activeHmd { nullptr };
static bool hmdPresent = vr::VR_IsHmdPresent();
static const uint32_t RELEASE_OPENVR_HMD_DELAY_MS = 5000;
vr::IVRSystem* acquireOpenVrSystem() {
bool hmdPresent = vr::VR_IsHmdPresent();
if (hmdPresent) {
Lock lock(mutex);
if (!activeHmd) {
@ -40,6 +40,8 @@ vr::IVRSystem* acquireOpenVrSystem() {
qCDebug(displayplugins) << "openvr: incrementing refcount";
++refCount;
}
} else {
qCDebug(displayplugins) << "openvr: no hmd present";
}
return activeHmd;
}
@ -51,24 +53,7 @@ void releaseOpenVrSystem() {
--refCount;
if (0 == refCount) {
qCDebug(displayplugins) << "openvr: zero refcount, deallocate VR system";
// Avoid spamming the VR system with activate/deactivate calls at system startup by
// putting in a delay before we destory the shutdown the VR subsystem
// FIXME releasing the VR system at all seems to trigger an exception deep inside the Oculus DLL.
// disabling for now.
//QTimer* releaseTimer = new QTimer();
//releaseTimer->singleShot(RELEASE_OPENVR_HMD_DELAY_MS, [releaseTimer] {
// Lock lock(mutex);
// qDebug() << "Delayed openvr destroy activated";
// if (0 == refCount && nullptr != activeHmd) {
// qDebug() << "Delayed openvr destroy: releasing resources";
// activeHmd = nullptr;
// vr::VR_Shutdown();
// } else {
// qDebug() << "Delayed openvr destroy: HMD still in use";
// }
// releaseTimer->deleteLater();
//});
vr::VR_Shutdown();
}
}
}

View file

@ -48,10 +48,7 @@ static const QString RENDER_CONTROLLERS = "Render Hand Controllers";
const QString ViveControllerManager::NAME = "OpenVR";
bool ViveControllerManager::isSupported() const {
auto hmd = acquireOpenVrSystem();
bool success = hmd != nullptr;
releaseOpenVrSystem();
return success;
return vr::VR_IsHmdPresent();
}
void ViveControllerManager::activate() {
@ -66,10 +63,12 @@ void ViveControllerManager::activate() {
}
Q_ASSERT(_hmd);
// OpenVR provides 3d mesh representations of the controllers
// Disabled controller rendering code
/*
auto renderModels = vr::VRRenderModels();
vr::RenderModel_t model;
/*
if (!_hmd->LoadRenderModel(CONTROLLER_MODEL_STRING, &model)) {
qDebug() << QString("Unable to load render model %1\n").arg(CONTROLLER_MODEL_STRING);
} else {
@ -145,6 +144,7 @@ void ViveControllerManager::deactivate() {
void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePointer scene, render::PendingChanges pendingChanges) {
PerformanceTimer perfTimer("ViveControllerManager::updateRendering");
/*
if (_modelLoaded) {
//auto controllerPayload = new render::Payload<ViveControllerManager>(this);
//auto controllerPayloadPointer = ViveControllerManager::PayloadPointer(controllerPayload);
@ -175,9 +175,11 @@ void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePoint
}
});
}
*/
}
void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& batch, int sign) {
/*
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
Transform transform(userInputMapper->getSensorToWorldMat());
transform.postTranslate(pose.getTranslation() + pose.getRotation() * glm::vec3(0, 0, CONTROLLER_LENGTH_OFFSET));
@ -199,6 +201,7 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch&
// mesh->getVertexBuffer()._stride);
batch.setIndexBuffer(gpu::UINT16, mesh->getIndexBuffer()._buffer, 0);
batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0);
*/
}

View file

@ -4,7 +4,7 @@ enable_testing()
# add the test directories
file(GLOB TEST_SUBDIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*")
list(REMOVE_ITEM TEST_SUBDIRS "CMakeFiles")
list(REMOVE_ITEM TEST_SUBDIRS "CMakeFiles" "mocha")
foreach(DIR ${TEST_SUBDIRS})
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${DIR}")
set(TEST_PROJ_NAME ${DIR})

5
tests/mocha/README.md Normal file
View file

@ -0,0 +1,5 @@
mocha tests of javascript code (e.g., from ../../examples/libraries/).
```
npm install
npm test
```

11
tests/mocha/package.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "HighFidelityTests",
"version": "1.0.0",
"scripts": {
"test": "mocha"
},
"license": "Apache 2.0",
"devDependencies": {
"mocha": "^2.2.1"
}
}

View file

@ -0,0 +1,248 @@
"use strict";
/*jslint nomen: true, plusplus: true, vars: true */
var assert = require('assert');
var mocha = require('mocha'), describe = mocha.describe, it = mocha.it, after = mocha.after;
var virtualBaton = require('../../../examples/libraries/virtualBaton.js');
describe('temp', function () {
var messageCount = 0, testStart = Date.now();
function makeMessager(nodes, me, mode) { // shim for High Fidelity Message system
function noopSend(channel, string, source) {
}
function hasChannel(node, channel) {
return -1 !== node.subscribed.indexOf(channel);
}
function sendSync(channel, message, nodes, skip) {
nodes.forEach(function (node) {
if (!hasChannel(node, channel) || (node === skip)) {
return;
}
node.sender(channel, message, me.name);
});
}
nodes.forEach(function (node) {
node.sender = node.sender || noopSend;
node.subscribed = node.subscribed || [];
});
return {
subscriberCount: function () {
var c = 0;
nodes.forEach(function (n) {
if (n.subscribed.length) {
c++;
}
});
return c;
},
subscribe: function (channel) {
me.subscribed.push(channel);
},
unsubscribe: function (channel) {
me.subscribed.splice(me.subscribed.indexOf(channel), 1);
},
sendMessage: function (channel, message) {
if ((mode === 'immediate2Me') && hasChannel(me, channel)) {
me.sender(channel, message, me.name);
}
if (mode === 'immediate') {
sendSync(channel, message, nodes, null);
} else {
process.nextTick(function () {
sendSync(channel, message, nodes, (mode === 'immediate2Me') ? me : null);
});
}
},
messageReceived: {
connect: function (f) {
me.sender = function (c, m, i) {
messageCount++; f(c, m, i);
};
},
disconnect: function () {
me.sender = noopSend;
}
}
};
}
var debug = {}; //{flow: true, send: false, receive: false};
function makeBaton(testKey, nodes, node, debug, mode, optimize) {
debug = debug || {};
var baton = virtualBaton({
batonName: testKey,
debugSend: debug.send,
debugReceive: debug.receive,
debugFlow: debug.flow,
useOptimizations: optimize,
connectionTest: function (id) {
return baton.validId(id);
},
globals: {
Messages: makeMessager(nodes, node, mode),
MyAvatar: {sessionUUID: node.name},
Script: {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
},
AvatarList: {
getAvatar: function (id) {
return {sessionUUID: id};
}
},
Entities: {getEntityProperties: function () {
}},
print: console.log
}
});
return baton;
}
function noRelease(batonName) {
assert.ok(!batonName, "should not release");
}
function defineABunch(mode, optimize) {
function makeKey(prefix) {
return prefix + mode + (optimize ? '-opt' : '');
}
var testKeys = makeKey('single-');
it(testKeys, function (done) {
var nodes = [{name: 'a'}];
var a = makeBaton(testKeys, nodes, nodes[0], debug, mode).claim(function (key) {
console.log('claimed a');
assert.equal(testKeys, key);
a.unload();
done();
}, noRelease);
});
var testKeydp = makeKey('dual-parallel-');
it(testKeydp, function (done) {
this.timeout(10000);
var nodes = [{name: 'ap'}, {name: 'bp'}];
var a = makeBaton(testKeydp, nodes, nodes[0], debug, mode, optimize),
b = makeBaton(testKeydp, nodes, nodes[1], debug, mode, optimize);
function accepted(key) { // Under some circumstances of network timing, either a or b can win.
console.log('claimed ap');
assert.equal(testKeydp, key);
done();
}
a.claim(accepted, noRelease);
b.claim(accepted, noRelease);
});
var testKeyds = makeKey('dual-serial-');
it(testKeyds, function (done) {
var nodes = [{name: 'as'}, {name: 'bs'}],
gotA = false,
gotB = false;
makeBaton(testKeyds, nodes, nodes[0], debug, mode, optimize).claim(function (key) {
console.log('claimed as', key);
assert.ok(!gotA, "should not get A after B");
gotA = true;
done();
}, noRelease);
setTimeout(function () {
makeBaton(testKeyds, nodes, nodes[1], debug, mode, optimize).claim(function (key) {
console.log('claimed bs', key);
assert.ok(!gotB, "should not get B after A");
gotB = true;
done();
}, noRelease);
}, 500);
});
var testKeydsl = makeKey('dual-serial-long-');
it(testKeydsl, function (done) {
this.timeout(5000);
var nodes = [{name: 'al'}, {name: 'bl'}],
gotA = false,
gotB = false,
releaseA = false;
makeBaton(testKeydsl, nodes, nodes[0], debug, mode, optimize).claim(function (key) {
console.log('claimed al', key);
assert.ok(!gotB, "should not get A after B");
gotA = true;
if (!gotB) {
done();
}
}, function () {
assert.ok(gotA, "Should claim it first");
releaseA = true;
if (gotB) {
done();
}
});
setTimeout(function () {
makeBaton(testKeydsl, nodes, nodes[1], debug, mode, optimize).claim(function (key) {
console.log('claimed bl', key);
gotB = true;
if (releaseA) {
done();
}
}, noRelease);
}, 3000);
});
var testKeydsr = makeKey('dual-serial-with-release-');
it(testKeydsr, function (done) {
this.timeout(5000);
var nodes = [{name: 'asr'}, {name: 'bsr'}],
gotClaimA = false,
gotReleaseA = false,
a = makeBaton(testKeydsr, nodes, nodes[0], debug, mode, optimize),
b = makeBaton(testKeydsr, nodes, nodes[1], debug, mode, optimize);
a.claim(function (key) {
console.log('claimed asr');
assert.equal(testKeydsr, key);
gotClaimA = true;
b.claim(function (key) {
console.log('claimed bsr');
assert.equal(testKeydsr, key);
assert.ok(gotReleaseA);
done();
}, noRelease);
a.release();
}, function (key) {
console.log('released asr');
assert.equal(testKeydsr, key);
assert.ok(gotClaimA);
gotReleaseA = true;
});
});
var testKeydpr = makeKey('dual-parallel-with-release-');
it(testKeydpr, function (done) {
this.timeout(5000);
var nodes = [{name: 'ar'}, {name: 'br'}];
var a = makeBaton(testKeydpr, nodes, nodes[0], debug, mode, optimize),
b = makeBaton(testKeydpr, nodes, nodes[1], debug, mode, optimize),
gotClaimA = false,
gotReleaseA = false,
gotClaimB = false;
a.claim(function (key) {
console.log('claimed ar');
assert.equal(testKeydpr, key);
gotClaimA = true;
assert.ok(!gotClaimB, "if b claimed, should not get a");
a.release();
}, function (key) {
console.log('released ar');
assert.equal(testKeydpr, key);
assert.ok(gotClaimA);
gotReleaseA = true;
});
b.claim(function (key) {
console.log('claimed br', gotClaimA ? 'with' : 'without', 'ar first');
assert.equal(testKeydpr, key);
gotClaimB = true;
assert.ok(!gotClaimA || gotReleaseA);
done();
}, noRelease);
});
}
function defineAllModeTests(optimize) {
defineABunch('delayed', optimize);
defineABunch('immediate2Me', optimize);
defineABunch('immediate', optimize);
}
defineAllModeTests(true);
defineAllModeTests(false);
after(function () {
console.log(messageCount, 'messages sent over', (Date.now() - testStart), 'ms.');
});
});

View file

@ -18,21 +18,46 @@
var self = this;
this.preload = function(entityId) {
this.entityId = entityId;
this.data = JSON.parse(Entities.getEntityProperties(this.entityId, "userData").userData);
this.buttonImageURL = baseURL + "GUI/GUI_jump_off.png";
this.addExitButton();
this.isRiding = false;
if (this.data && this.data.isDynein) {
this.rotation = 180;
} else {
this.rotation = 0;
}
this.initialize(entityId);
self.initTimeout = null;
}
this.initialize = function(entityId) {
//print(' should initialize' + entityId)
var properties = Entities.getEntityProperties(entityId);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
self.initTimeout = Script.setTimeout(function() {
//print(' no user data yet, try again in one second')
self.initialize(entityId);
}, 1000)
} else {
//print(' userdata before parse attempt' + properties.userData)
self.userData = null;
try {
self.userData = JSON.parse(properties.userData);
} catch (err) {
//print(' error parsing json');
//print(' properties are:' + properties.userData);
return;
}
self.data = self.userData;
self.buttonImageURL = baseURL + "GUI/GUI_jump_off.png";
self.addExitButton();
self.isRiding = false;
self.mouseIsConnected = false;
if (self.data && self.data.isDynein) {
self.rotation = 180;
} else {
self.rotation = 0;
}
}
}
this.addExitButton = function() {
this.windowDimensions = Controller.getViewportDimensions();
this.buttonWidth = 75;
@ -53,19 +78,22 @@
}
this.clickReleaseOnEntity = function(entityId, mouseEvent) {
// print('CLICKED ON MOTOR PROTEIN')
//print('CLICKED ON MOTOR PROTEIN')
return;
if (mouseEvent.isLeftButton && !self.isRiding) {
print("GET ON");
//print("GET ON");
self.isRiding = true;
if (!self.entityId) {
self.entityId = entityId;
}
self.entityLocation = Entities.getEntityProperties(this.entityId, "position").position;
self.entityLocation = Entities.getEntityProperties(self.entityId, "position").position;
self.targetLocation = Vec3.sum(self.entityLocation, TARGET_OFFSET);
Overlays.editOverlay(self.exitButton, {
visible: true
});
Controller.mousePressEvent.connect(this.onMousePress);
self.mouseIsConnected = true;
Script.update.connect(this.update);
}
}
@ -116,7 +144,7 @@
y: event.y
});
if (event.isLeftButton && clickedOverlay === self.exitButton) {
print("GET OFF");
//print("GET OFF");
Script.update.disconnect(this.update);
self.reset();
}
@ -136,8 +164,12 @@
// print("unload");
self.reset();
Controller.mousePressEvent.disconnect(this.onMousePress);
if (self.mouseIsConnected === true) {
Controller.mousePressEvent.disconnect(self.onMousePress);
}
if (self.initTimeout !== null) {
Script.clearTimeout(self.initTimeout);
}
}
});

View file

@ -1,17 +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 deleteAllInRadius(r) {
var n = 0;
var arrayFound = Entities.findEntities(MyAvatar.position, r);
for (var i = 0; i < arrayFound.length; i++) {
Entities.deleteEntity(arrayFound[i]);
}
print("deleted " + arrayFound.length + " entities");
}
deleteAllInRadius(100000);

View file

@ -1,21 +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
//
var scriptName = "Controller";
function findScriptsInRadius(r) {
var n = 0;
var arrayFound = Entities.findEntities(MyAvatar.position, r);
for (var i = 0; i < arrayFound.length; i++) {
if (Entities.getEntityProperties(arrayFound[i]).script.indexOf(scriptName) != -1) {
n++;
}
}
print("found " + n + " copies of " + scriptName);
}
findScriptsInRadius(100000);

View file

@ -9,7 +9,8 @@
var self = this;
this.preload = function(entityId) {
//print('preload move randomly')
this.isConnected = false;
this.entityId = entityId;
this.updateInterval = 100;
this.posFrame = 0;
@ -21,62 +22,161 @@
this.minAngularVelocity = 0.01;
this.maxAngularVelocity = 0.03;
this.initialize(entityId);
this.initTimeout = null;
var userData = {
ownershipKey: {
owner: MyAvatar.sessionUUID
},
grabbableKey: {
grabbable: false
}
};
Entities.editEntity(entityId, {
userData: JSON.stringify(userData)
})
}
this.initialize = function(entityId) {
//print('move randomly should initialize' + entityId)
var properties = Entities.getEntityProperties(entityId);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
self.initTimeout = Script.setTimeout(function() {
//print('no user data yet, try again in one second')
self.initialize(entityId);
}, 1000)
} else {
//print('userdata before parse attempt' + properties.userData)
self.userData = null;
try {
self.userData = JSON.parse(properties.userData);
} catch (err) {
//print('error parsing json');
//print('properties are:' + properties.userData);
return;
}
Script.update.connect(self.update);
this.isConnected = true;
}
}
this.update = function(deltaTime) {
// print('jbp in update')
var data = Entities.getEntityProperties(self.entityId, 'userData').userData;
var userData;
try {
userData = JSON.parse(data)
} catch (e) {
//print('error parsing json' + data)
return;
};
self.posFrame++;
self.rotFrame++;
if (self.posFrame > self.posInterval) {
self.posInterval = 100 * Math.random() + 300;
self.posFrame = 0;
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))
});
// print('userdata is' + data)
//if the entity doesnt have an owner set yet
if (userData.hasOwnProperty('ownershipKey') !== true) {
//print('no movement owner yet')
return;
}
if (self.rotFrame > self.rotInterval) {
//print('owner is:::' + userData.ownershipKey.owner)
//get all the avatars to see if the owner is around
var avatars = AvatarList.getAvatarIdentifiers();
var ownerIsAround = false;
self.rotInterval = 100 * Math.random() + 250;
self.rotFrame = 0;
//if the current owner is not me...
if (userData.ownershipKey.owner !== MyAvatar.sessionUUID) {
var magnitudeAV = self.maxAngularVelocity;
//look to see if the current owner is around anymore
for (var i = 0; i < avatars.length; i++) {
if (avatars[i] === userData.ownershipKey.owner) {
ownerIsAround = true
//the owner is around
return;
};
}
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))
//if the owner is not around, then take ownership
if (ownerIsAround === false) {
//print('taking ownership')
});
var userData = {
ownershipKey: {
owner: MyAvatar.sessionUUID
},
grabbableKey: {
grabbable: false
}
};
Entities.editEntity(self.entityId, {
userData: JSON.stringify(data)
})
}
}
//but if the current owner IS me, then move it
else {
//print('jbp im the owner so move it')
self.posFrame++;
self.rotFrame++;
if (self.posFrame > self.posInterval) {
self.posInterval = 100 * Math.random() + 300;
self.posFrame = 0;
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))
});
}
if (self.rotFrame > self.rotInterval) {
self.rotInterval = 100 * Math.random() + 250;
self.rotFrame = 0;
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))
});
}
}
}
this.unload = function() {
if (this.initTimeout !== null) {
Script.clearTimeout(this.initTimeout);
}
Script.update.disconnect(this.update);
if (this.isConnected === true) {
Script.update.disconnect(this.update);
}
}
Script.update.connect(this.update);
})

View file

@ -1,4 +1,3 @@
// Copyright 2016 High Fidelity, Inc.
//
//
@ -8,7 +7,7 @@
(function() {
var version = 1;
var version = 11;
var added = false;
this.frame = 0;
var utilsScript = Script.resolvePath('utils.js');
@ -19,35 +18,61 @@
this.preload = function(entityId) {
this.entityId = entityId;
var mySavedSettings = Settings.getValue(entityId);
this.initialize(entityId);
this.initTimeout = null;
}
if (mySavedSettings.buttons !== undefined) {
// print('NAV preload buttons'+ mySavedSettings.buttons)
mySavedSettings.buttons.forEach(function(b) {
// print('NAV deleting button'+ b)
Overlays.deleteOverlay(b);
})
Settings.setValue(entityId,'')
}
self.getUserData();
this.buttonImageURL = baseURL + "GUI/GUI_" + self.userData.name + ".png?" + version;
if (self.button === undefined) {
// print('NAV NO BUTTON ADDING ONE!!')
self.button = true;
self.addButton();
this.initialize = function(entityId) {
print('JBP nav button should initialize' + entityId)
var properties = Entities.getEntityProperties(entityId);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
self.initTimeout = Script.setTimeout(function() {
print('JBP no user data yet, try again in one second')
self.initialize(entityId);
}, 1000)
} else {
// print('NAV SELF ALREADY HAS A BUTTON!!')
}
print('JBP userdata before parse attempt' + properties.userData)
self.userData = null;
try {
self.userData = JSON.parse(properties.userData);
} catch (err) {
print('JBP error parsing json');
print('JBP properties are:' + properties.userData);
return;
}
var mySavedSettings = Settings.getValue(entityId);
if (mySavedSettings.buttons !== undefined) {
print('JBP preload buttons' + mySavedSettings.buttons)
mySavedSettings.buttons.forEach(function(b) {
print('JBP deleting button' + b)
Overlays.deleteOverlay(b);
})
Settings.setValue(entityId, '')
}
self.buttonImageURL = baseURL + "GUI/GUI_" + self.userData.name + ".png?" + version;
print('JBP BUTTON IMAGE URL:' + self.buttonImageURL)
if (self.button === undefined) {
// print('NAV NO BUTTON ADDING ONE!!')
self.button = true;
self.addButton();
} else {
// print('NAV SELF ALREADY HAS A BUTTON!!')
}
}
}
this.addButton = function() {
self.getUserData();
this.windowDimensions = Controller.getViewportDimensions();
this.buttonWidth = 150;
this.buttonHeight = 50;
@ -87,7 +112,7 @@
if (self.frame < 10) {
self.frame++;
} else {
// this.lookAt(this.userData.target);
// this.lookAt(this.userData.target);
}
}
@ -107,7 +132,7 @@
}
this.lookAtTarget = function() {
self.getUserData();
var direction = Vec3.normalize(Vec3.subtract(self.userData.entryPoint, self.userData.target));
var pitch = Quat.angleAxis(Math.asin(-direction.y) * 180.0 / Math.PI, {
x: 1,
@ -125,16 +150,6 @@
MyAvatar.headYaw = 0;
}
this.getUserData = function() {
this.properties = Entities.getEntityProperties(this.entityId);
if (self.properties.userData) {
this.userData = JSON.parse(this.properties.userData);
} else {
this.userData = {};
}
}
var buttonDeleter;
var deleterCount = 0;
this.unload = function() {
@ -144,6 +159,11 @@
Controller.mousePressEvent.disconnect(this.onClick);
// Script.update.disconnect(this.update);
if (this.initTimeout !== null) {
Script.clearTimeout(this.initTimeout);
}
}
Controller.mousePressEvent.connect(this.onClick);

View file

@ -1,64 +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;
var baseURL = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/";
var version = 9;
this.preload = function(entityId) {
self.soundPlaying = false;
self.entityId = entityId;
self.getUserData();
self.soundURL = baseURL + "Audio/" + self.userData.name + ".wav?" + version;
print("Script.clearTimeout creating WAV name location is " + baseURL + "Audio/" + self.userData.name + ".wav");
self.soundOptions = {
stereo: true,
loop: true,
localOnly: true,
volume: 0.035
};
this.sound = SoundCache.getSound(self.soundURL);
}
this.getUserData = function() {
self.properties = Entities.getEntityProperties(self.entityId);
if (self.properties.userData) {
self.userData = JSON.parse(this.properties.userData);
} else {
self.userData = {};
}
}
this.enterEntity = function(entityID) {
print("entering audio zone");
if (self.sound.downloaded) {
print("playing background audio named " + self.userData.name + "which has been downloaded");
this.soundPlaying = Audio.playSound(self.sound, self.soundOptions);
} else {
print("sound is not downloaded");
}
}
this.leaveEntity = function(entityID) {
print("leaving audio area " + self.userData.name);
if (self.soundPlaying !== false) {
print("not null");
print("Stopped sound " + self.userData.name);
self.soundPlaying.stop();
} else {
print("Sound not playing");
}
}
});

View file

@ -6,97 +6,117 @@
//
(function() {
var baseURL = "https://hifi-content.s3.amazonaws.com/hifi-content/DomainContent/CellScience/";
var self = this;
this.buttonImageURL = baseURL + "GUI/play_audio.svg?2";
var baseURL = "https://hifi-content.s3.amazonaws.com/hifi-content/DomainContent/CellScience/";
var self = this;
this.buttonImageURL = baseURL + "GUI/play_audio.svg?2";
this.preload = function(entityId) {
this.entityId = entityId;
self.addButton();
this.buttonShowing = false;
self.getUserData();
this.showDistance = self.userData.showDistance;
this.soundURL = baseURL + "Audio/" + self.userData.soundName + ".wav";
print("distance = " + self.userData.showDistance + ", sound = " + this.soundURL);
this.soundOptions = {
stereo: true,
loop: false,
localOnly: true,
volume: 0.035
};
this.sound = SoundCache.getSound(this.soundURL);
}
this.addButton = function() {
this.windowDimensions = Controller.getViewportDimensions();
this.buttonWidth = 100;
this.buttonHeight = 100;
this.buttonPadding = 0;
this.buttonPositionX = (self.windowDimensions.x - self.buttonPadding) / 2 - self.buttonWidth;
this.buttonPositionY = (self.windowDimensions.y - self.buttonHeight) - (self.buttonHeight + self.buttonPadding);
this.button = Overlays.addOverlay("image", {
x: self.buttonPositionX,
y: self.buttonPositionY,
width: self.buttonWidth,
height: self.buttonHeight,
imageURL: self.buttonImageURL,
visible: false,
alpha: 1.0
});
}
this.getUserData = function() {
this.properties = Entities.getEntityProperties(this.entityId);
if (self.properties.userData) {
this.userData = JSON.parse(this.properties.userData);
} else {
this.userData = {};
this.preload = function(entityId) {
this.entityId = entityId;
this.initialize(entityId)
this.initTimeout = null;
}
}
this.update = function(deltaTime) {
this.initialize = function(entityId) {
//print(' should initialize' + entityId)
var properties = Entities.getEntityProperties(entityId);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
self.initTimeout = Script.setTimeout(function() {
// print(' no user data yet, try again in one second')
self.initialize(entityId);
}, 1000)
self.distance = Vec3.distance(MyAvatar.position, Entities.getEntityProperties(self.entityId).position);
//print(self.distance);
if (!self.buttonShowing && self.distance < self.userData.showDistance) {
self.buttonShowing = true;
Overlays.editOverlay(self.button, {
visible: true
});
} else if (self.buttonShowing && self.distance > self.userData.showDistance) {
self.buttonShowing = false;
Overlays.editOverlay(self.button, {
visible: false
});
}
}
this.onClick = function(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({
x: event.x,
y: event.y
});
if (clickedOverlay === self.button) {
print("button was clicked");
if (self.sound.downloaded) {
print("play sound");
Audio.playSound(self.sound, self.soundOptions);
} else {
print("not downloaded");
//print(' userdata before parse attempt' + properties.userData)
self.userData = null;
try {
self.userData = JSON.parse(properties.userData);
} catch (err) {
// print(' error parsing json');
// print(' properties are:' + properties.userData);
return;
}
self.addButton();
self.buttonShowing = false;
self.showDistance = self.userData.showDistance;
self.soundURL = baseURL + "Audio/" + self.userData.soundName + ".wav";
// print("distance = " + self.userData.showDistance + ", sound = " + self.soundURL);
self.soundOptions = {
stereo: true,
loop: false,
localOnly: true,
volume: 1
};
self.sound = SoundCache.getSound(this.soundURL);
}
}
}
this.unload = function() {
Overlays.deleteOverlay(self.button);
Controller.mousePressEvent.disconnect(this.onClick);
Script.update.disconnect(this.update);
}
this.addButton = function() {
this.windowDimensions = Controller.getViewportDimensions();
this.buttonWidth = 100;
this.buttonHeight = 100;
this.buttonPadding = 0;
Controller.mousePressEvent.connect(this.onClick);
Script.update.connect(this.update);
this.buttonPositionX = (self.windowDimensions.x - self.buttonPadding) / 2 - self.buttonWidth;
this.buttonPositionY = (self.windowDimensions.y - self.buttonHeight) - (self.buttonHeight + self.buttonPadding);
this.button = Overlays.addOverlay("image", {
x: self.buttonPositionX,
y: self.buttonPositionY,
width: self.buttonWidth,
height: self.buttonHeight,
imageURL: self.buttonImageURL,
visible: false,
alpha: 1.0
});
}
});
this.update = function(deltaTime) {
self.distance = Vec3.distance(MyAvatar.position, Entities.getEntityProperties(self.entityId).position);
//print(self.distance);
if (!self.buttonShowing && self.distance < self.userData.showDistance) {
self.buttonShowing = true;
Overlays.editOverlay(self.button, {
visible: true
});
} else if (self.buttonShowing && self.distance > self.userData.showDistance) {
self.buttonShowing = false;
Overlays.editOverlay(self.button, {
visible: false
});
}
}
this.onClick = function(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({
x: event.x,
y: event.y
});
if (clickedOverlay === self.button) {
//print("button was clicked");
if (self.sound.downloaded) {
//print("play sound");
Audio.playSound(self.sound, self.soundOptions);
} else {
//print("not downloaded");
}
}
}
this.unload = function() {
Overlays.deleteOverlay(self.button);
Controller.mousePressEvent.disconnect(this.onClick);
Script.update.disconnect(this.update);
if (this.initTimeout !== null) {
Script.clearTimeout(this.initTimeout);
}
}
Controller.mousePressEvent.connect(this.onClick);
Script.update.connect(this.update);
});

View file

@ -10,24 +10,51 @@
var self = this;
var baseURL = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/";
var version = 1;
var version = 2;
this.preload = function(entityId) {
this.soundPlaying = null;
this.entityId = entityId;
self.getUserData();
this.labelURL = baseURL + "GUI/labels_" + self.userData.name + ".png?" + version;
this.showDistance = self.userData.showDistance;
this.soundURL = baseURL + "Audio/" + self.userData.name + ".wav";
this.soundOptions = {
stereo: true,
loop: false,
localOnly: true,
volume: 0.035,
position: this.position
};
this.sound = SoundCache.getSound(this.soundURL);
this.buttonImageURL = baseURL + "GUI/GUI_audio.png?" + version;
self.addButton();
self.initTimeout = null;
this.initialize(entityId);
}
this.initialize = function(entityId) {
//print(' should initialize' + entityId)
var properties = Entities.getEntityProperties(entityId);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
self.initTimeout = Script.setTimeout(function() {
//print(' no user data yet, try again in one second')
self.initialize(entityId);
}, 1000)
} else {
//print(' userdata before parse attempt' + properties.userData)
self.userData = null;
try {
self.userData = JSON.parse(properties.userData);
} catch (err) {
//print(' error parsing json');
//print(' properties are:' + properties.userData);
return;
}
self.labelURL = baseURL + "GUI/labels_" + self.userData.name + ".png?" + version;
self.showDistance = self.userData.showDistance;
self.soundURL = baseURL + "Audio/" + self.userData.name + ".wav";
self.soundOptions = {
stereo: true,
loop: false,
localOnly: true,
volume: 0.035,
position: properties.position
};
self.sound = SoundCache.getSound(self.soundURL);
self.buttonImageURL = baseURL + "GUI/GUI_audio.png?" + version;
self.addButton();
}
}
this.addButton = function() {
@ -78,9 +105,8 @@
this.enterEntity = function(entityID) {
// self.getUserData();
print("entering entity and showing" + self.labelURL);
//self.buttonShowing = true;
// print("entering entity and showing" + self.labelURL);
Overlays.editOverlay(self.button, {
visible: true
});
@ -92,9 +118,8 @@
this.leaveEntity = function(entityID) {
// self.getUserData();
// print("leaving entity " + self.userData.name);
//self.buttonShowing = false;
// print("leaving entity " + self.userData.name);
print(Overlays);
Overlays.editOverlay(self.button, {
visible: false
@ -110,16 +135,16 @@
y: event.y
});
if (clickedOverlay == self.button) {
print("button was clicked");
//print("button was clicked");
if (self.sound.downloaded) {
print("play sound");
// print("play sound");
Overlays.editOverlay(self.button, {
visible: false
});
this.soundPlaying = Audio.playSound(self.sound, self.soundOptions);
} else {
print("not downloaded");
// print("not downloaded");
}
}
}
@ -129,7 +154,9 @@
if (this.soundPlaying !== null) {
this.soundPlaying.stop();
}
if (self.initTimeout !== null) {
Script.clearTimeout(self.initTimeout);
}
Controller.mousePressEvent.disconnect(this.onClick);
}

View file

@ -20,8 +20,8 @@ getEntityUserData = function(id) {
try {
results = JSON.parse(properties.userData);
} catch (err) {
logDebug(err);
logDebug(properties.userData);
// print('error parsing json');
// print('properties are:'+ properties.userData);
}
}
return results ? results : {};

View file

@ -14,50 +14,58 @@
this.entered = true;
this.preload = function(entityID) {
this.entityId = entityID;
this.initialize(entityID);
this.initTimeout = null;
}
this.initialize = function(entityID) {
// print(' should initialize')
var properties = Entities.getEntityProperties(entityID);
portalDestination = properties.userData;
animationURL = properties.modelURL;
this.soundOptions = {
stereo: true,
loop: false,
localOnly: false,
position: this.position,
volume: 0.035
};
this.teleportSound = SoundCache.getSound("https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Audio/whoosh.wav");
//print('Script.clearTimeout PRELOADING A ZOOM ENTITY')
print(" portal destination is " + portalDestination);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
self.initTimeout = Script.setTimeout(function() {
// print(' no user data yet, try again in one second')
self.initialize(entityID);
}, 1000)
} else {
// print(' has userData')
self.portalDestination = properties.userData;
animationURL = properties.modelURL;
self.soundOptions = {
stereo: true,
loop: false,
localOnly: false,
position: properties.position,
volume: 0.5
};
self.teleportSound = SoundCache.getSound("https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Audio/whoosh.wav");
// print(" portal destination is " + self.portalDestination);
}
}
this.enterEntity = function(entityID) {
print('Script.clearTimeout ENTERED A BOUNDARY ENTITY, SHOULD ZOOM', entityID)
//print('ENTERED A BOUNDARY ENTITY, SHOULD ZOOM', entityID)
var data = JSON.parse(Entities.getEntityProperties(this.entityId).userData);
//print('DATA IS::' + data)
if (data != null) {
print("Teleporting to (" + data.location.x + ", " + data.location.y + ", " + data.location.z + ")");
if (self.teleportSound.downloaded) {
//print("play sound");
Audio.playSound(self.teleportSound, self.soundOptions);
} else {
//print("not downloaded");
}
this.lookAt(data.target, data.location);
MyAvatar.position = data.location;
// if (data.hasOwnProperty('entryPoint') && data.hasOwnProperty('target')) {
// this.lookAtTarget(data.entryPoint, data.target);
// }
// else{
// }
}
}
this.lookAt = function(targetPosition, avatarPosition) {
var direction = Vec3.normalize(Vec3.subtract(MyAvatar.position, targetPosition));
this.lookAtTarget = function(entryPoint,target) {
//print('SHOULD LOOK AT TARGET')
var direction = Vec3.normalize(Vec3.subtract(entryPoint, target));
var pitch = Quat.angleAxis(Math.asin(-direction.y) * 180.0 / Math.PI, {
x: 1,
y: 0,
@ -69,8 +77,10 @@
z: 0
});
MyAvatar.goToLocation(avatarPosition, true, yaw);
MyAvatar.goToLocation(entryPoint, true, yaw);
MyAvatar.headYaw = 0;
}
@ -81,9 +91,18 @@
animationSettings: '{ "frameIndex": 1, "running": false }'
});
this.entered = false;
if (this.initTimeout !== null) {
Script.clearTimeout(this.initTimeout);
}
//playSound();
}
this.unload = function() {
if (this.initTimeout !== null) {
Script.clearTimeout(this.initTimeout);
}
}
this.hoverEnterEntity = function(entityID) {
Entities.editEntity(entityID, {
animationURL: animationURL,

View file

@ -0,0 +1,116 @@
var soundMap = [{
name: 'Cells',
url: "http://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Audio/Cells.wav",
audioOptions: {
position: {
x: 15850,
y: 15850,
z: 15850
},
volume: 0.1,
loop: true
}
}, {
name: 'Cell Layout',
url: "http://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Audio/CellLayout.wav",
audioOptions: {
position: {
x: 15950,
y: 15950,
z: 15950
},
volume: 0.1,
loop: true
}
}, {
name: 'Ribsome',
url: "http://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Audio/Ribosome.wav",
audioOptions: {
position: {
x: 15650,
y: 15650,
z: 15650
},
volume: 0.1,
loop: true
}
}, {
name: 'Hexokinase',
url: "http://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Audio/Hexokinase.wav",
audioOptions: {
position: {
x: 15750,
y: 15750,
z: 15750
},
volume: 0.1,
loop: true
}
}
];
function loadSounds() {
soundMap.forEach(function(soundData) {
soundData.sound = SoundCache.getSound(soundData.url);
});
}
function playSound(soundData) {
if (soundData.injector) {
// try/catch in case the injector QObject has been deleted already
try {
soundData.injector.stop();
} catch (e) {
print('error playing sound' + e)
}
}
soundData.injector = Audio.playSound(soundData.sound, soundData.audioOptions);
}
function checkDownloaded(soundData) {
if (soundData.sound.downloaded) {
Script.clearInterval(soundData.downloadTimer);
if (soundData.hasOwnProperty('playAtInterval')) {
soundData.playingInterval = Script.setInterval(function() {
playSound(soundData)
}, soundData.playAtInterval);
} else {
playSound(soundData);
}
}
}
function startCheckDownloadedTimers() {
soundMap.forEach(function(soundData) {
soundData.downloadTimer = Script.setInterval(function() {
checkDownloaded(soundData);
}, 1000);
});
}
Script.scriptEnding.connect(function() {
soundMap.forEach(function(soundData) {
if (soundData.hasOwnProperty("injector")) {
soundData.injector.stop();
}
if (soundData.hasOwnProperty("downloadTimer")) {
Script.clearInterval(soundData.downloadTimer);
}
if (soundData.hasOwnProperty("playingInterval")) {
Script.clearInterval(soundData.playingInterval);
}
});
});
loadSounds();
startCheckDownloadedTimers();

View file

@ -5,7 +5,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var version = 1004;
var version = 1035;
var cellLayout;
var baseLocation = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/";
@ -79,398 +79,138 @@ var locations = {
}, 1000]
};
var scenes = [
{
name: "Cells",
objects: "",
location: locations.cells[0],
entryPoint: locations.cells[1],
zone: {
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
light: {
r: 255,
g: 200,
b: 200
},
intensity: 1.1,
ambient: 0.7,
sun: true,
skybox: "cells_skybox_cross"
var scenes = [{
name: "Cells",
objects: "",
location: locations.cells[0],
entryPoint: locations.cells[1],
zone: {
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
instances: [{
model: "Cell",
light: {
r: 255,
g: 200,
b: 200
},
intensity: 1.1,
ambient: 0.7,
sun: true,
skybox: "cells_skybox_cross"
},
instances: [{
model: "Cell",
dimensions: {
x: 550,
y: 620,
z: 550
},
offset: {
x: 0,
y: 0,
z: 0
},
radius: 500,
number: 10,
userData: JSON.stringify({
entryPoint: locations.cellLayout[1],
target: locations.cellLayout[1],
location: locations.cellLayout[1],
baseURL: baseLocation
}),
script: "zoom.js?" + version,
visible: true
}],
boundary: {
radius: locations.cells[2],
center: locations.cells[0],
location: locations.cellLayout[1],
target: locations.cellLayout[0]
}
}, {
name: "CellLayout",
objects: cellLayout,
location: locations.cellLayout[0],
entryPoint: locations.cellLayout[1],
zone: {
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
light: {
r: 247,
g: 233,
b: 220
},
intensity: 2.3,
ambient: 0.7,
sun: true,
skybox: "cosmos_skybox_blurred"
},
instances: [{
model: "translation",
dimensions: {
x: 550,
y: 620,
z: 550
x: 10,
y: 16,
z: 10
},
offset: {
x: 0,
y: 0,
z: 0
},
radius: 500,
number: 10,
radius: 300,
number: 7,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
target: locations.ribosome[1],
location: locations.ribosome[0],
baseURL: baseLocation
}),
script: "zoom.js?" + version,
visible: true
}, {
model: "vesicle",
dimensions: {
x: 60,
y: 60,
z: 60
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1000,
number: 22,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
}],
boundary: {
radius: locations.cells[2],
center: locations.cells[0],
location: locations.cellLayout[1],
target: locations.cellLayout[0]
}
}, {
name: "CellLayout",
objects: cellLayout,
location: locations.cellLayout[0],
entryPoint: locations.cellLayout[1],
zone: {
}, { //golgi vesicles
model: "vesicle",
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
light: {
r: 247,
g: 233,
b: 220
},
intensity: 2.3,
ambient: 0.7,
sun: true,
skybox: "cosmos_skybox_blurred"
},
instances: [{
model: "translation",
dimensions: {
x: 10,
y: 16,
z: 10
},
offset: {
x: 0,
y: 0,
z: 0
},
radius: 300,
number: 15,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
target: locations.ribosome[1],
location: locations.ribosome[0],
baseURL: baseLocation
}),
script: "zoom.js?" + version,
visible: true
}, {
model: "vesicle",
dimensions: {
x: 60,
y: 60,
z: 60
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1000,
number: 45,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
}, { //golgi vesicles
model: "vesicle",
dimensions: {
x: 10,
y: 10,
z: 10
},
randomSize: 10,
offset: {
x: -319,
y: 66,
z: 976
},
radius: 140,
number: 20,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}, { //golgi vesicles
model: "vesicle",
dimensions: {
x: 15,
y: 15,
z: 15
},
randomSize: 10,
offset: {
x: -319,
y: 66,
z: 976
},
radius: 115,
number: 15,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
}, {
model: "vesicle",
dimensions: {
x: 50,
y: 50,
z: 50
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 600,
number: 30,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}, { //outer vesicles
model: "vesicle",
dimensions: {
x: 60,
y: 60,
z: 60
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1600,
number: 45,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}, { //outer vesicles
model: "vesicle",
dimensions: {
x: 40,
y: 40,
z: 40
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1400,
number: 45,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
}, { //outer vesicles
model: "vesicle",
dimensions: {
x: 80,
y: 80,
z: 80
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1800,
number: 45,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
},
// {//wigglies
// model:"wiggly",
// dimensions:{x:320,y:40,z:160},
// randomSize: 10,
// offset:{x:0,y:0,z:0},
// radius:1800,
// number:50,
// userData:"",
// script:"moveRandomly",
// visible:true
// },
//// {//wigglies
// model:"wiggly",
// dimensions:{x:640,y:80,z:320},
// randomSize: 10,
// offset:{x:0,y:0,z:0},
// radius:2100,
// number:50,
// userData:"",
// script:"moveRandomly",
// visible:true
// },
{
model: "hexokinase",
dimensions: {
x: 3,
y: 4,
z: 3
},
randomSize: 10,
offset: {
x: 236,
y: 8,
z: 771
},
radius: 80,
number: 15,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
target: locations.hexokinase[1],
location: locations.hexokinase[0],
baseURL: baseLocation
}),
script: "zoom.js?" + version,
visible: true
}, {
model: "pfructo_kinase",
dimensions: {
x: 3,
y: 4,
z: 3
},
randomSize: 10,
offset: {
x: 236,
y: 8,
z: 771
},
radius: 60,
number: 15,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}, {
model: "glucose_isomerase",
dimensions: {
x: 3,
y: 4,
z: 3
},
randomSize: 10,
offset: {
x: 236,
y: 8,
z: 771
},
radius: 70,
number: 15,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}
// {
// model:"NPC",
// dimensions:{x:20,y:20,z:20},
// randomSize: 10,
// offset:{x:208.593693,y:6.113100222,z:153.3202277},
// radius:520,
// number:25,
// userData: "",
// script:"",
// visible:true
// }
],
boundary: {
radius: locations.cellLayout[2],
center: locations.cellLayout[0],
location: locations.cells[1],
target: locations.cells[0]
}
}, {
name: "Ribosome",
objects: "",
location: locations.ribosome[0],
entryPoint: locations.ribosome[1],
zone: {
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
light: {
r: 250,
g: 185,
b: 182
},
intensity: 0.6,
ambient: 2.9,
sun: true,
skybox: "ribosome_skybox"
},
instances: [{
model: "translation_highres",
dimensions: {
x: 500,
y: 500,
z: 200
x: 10,
y: 10,
z: 10
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
x: -319,
y: 66,
z: 976
},
radius: 1,
number: 1,
radius: 140,
number: 10,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
@ -478,48 +218,43 @@ var scenes = [
}),
script: "",
visible: true
}],
boundary: {
radius: locations.ribosome[2],
center: locations.ribosome[0],
location: locations.translation[1],
target: locations.translation[0]
}
}, {
name: "Hexokinase",
objects: "",
location: locations.hexokinase[0],
entryPoint: locations.hexokinase[1],
zone: {
}, { //golgi vesicles
model: "vesicle",
dimensions: {
x: 4000,
y: 4000,
z: 4000
x: 15,
y: 15,
z: 15
},
light: {
r: 255,
g: 255,
b: 255
randomSize: 10,
offset: {
x: -319,
y: 66,
z: 976
},
intensity: 0.6,
ambient: 0.6,
sun: true,
skybox: "hexokinase_skybox"
},
instances: [{
model: "hexokinase_highres",
radius: 115,
number: 7,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
}, {
model: "vesicle",
dimensions: {
x: 600,
y: 600,
z: 600
x: 50,
y: 50,
z: 50
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1,
number: 1,
radius: 600,
number: 15,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
@ -527,15 +262,288 @@ var scenes = [
}),
script: "",
visible: true
}],
boundary: {
radius: locations.hexokinase[2],
center: locations.hexokinase[0],
location: locations.mitochondria[1],
target: locations.mitochondria[0]
}, { //outer vesicles
model: "vesicle",
dimensions: {
x: 60,
y: 60,
z: 60
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1600,
number: 22,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}, { //outer vesicles
model: "vesicle",
dimensions: {
x: 40,
y: 40,
z: 40
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1400,
number: 22,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
}, { //outer vesicles
model: "vesicle",
dimensions: {
x: 80,
y: 80,
z: 80
},
randomSize: 10,
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1800,
number: 22,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "moveRandomly.js?" + version,
visible: true
},
// {//wigglies
// model:"wiggly",
// dimensions:{x:320,y:40,z:160},
// randomSize: 10,
// offset:{x:0,y:0,z:0},
// radius:1800,
// number:50,
// userData:"",
// script:"moveRandomly",
// visible:true
// },
//// {//wigglies
// model:"wiggly",
// dimensions:{x:640,y:80,z:320},
// randomSize: 10,
// offset:{x:0,y:0,z:0},
// radius:2100,
// number:50,
// userData:"",
// script:"moveRandomly",
// visible:true
// },
{
model: "hexokinase",
dimensions: {
x: 3,
y: 4,
z: 3
},
randomSize: 10,
offset: {
x: 236,
y: 8,
z: 771
},
radius: 80,
number: 7,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
target: locations.hexokinase[1],
location: locations.hexokinase[0],
baseURL: baseLocation
}),
script: "zoom.js?" + version,
visible: true
}, {
model: "pfructo_kinase",
dimensions: {
x: 3,
y: 4,
z: 3
},
randomSize: 10,
offset: {
x: 236,
y: 8,
z: 771
},
radius: 60,
number: 7,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
target: locations.hexokinase[1],
location: locations.hexokinase[0],
}),
script: "zoom.js?" + version,
visible: true
}, {
model: "glucose_isomerase",
dimensions: {
x: 3,
y: 4,
z: 3
},
randomSize: 10,
offset: {
x: 236,
y: 8,
z: 771
},
radius: 70,
number: 7,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
},
target: locations.hexokinase[1],
location: locations.hexokinase[0],
}),
script: "zoom.js?" + version,
visible: true
}
// {
// model:"NPC",
// dimensions:{x:20,y:20,z:20},
// randomSize: 10,
// offset:{x:208.593693,y:6.113100222,z:153.3202277},
// radius:520,
// number:25,
// userData: "",
// script:"",
// visible:true
// }
],
boundary: {
radius: locations.cellLayout[2],
center: locations.cellLayout[0],
location: locations.cells[1],
target: locations.cells[0]
}
];
}, {
name: "Ribosome",
objects: "",
location: locations.ribosome[0],
entryPoint: locations.ribosome[1],
zone: {
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
light: {
r: 250,
g: 185,
b: 182
},
intensity: 0.6,
ambient: 2.9,
sun: true,
skybox: "ribosome_skybox"
},
instances: [{
model: "translation_highres",
dimensions: {
x: 500,
y: 500,
z: 200
},
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1,
number: 1,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}],
boundary: {
radius: locations.ribosome[2],
center: locations.ribosome[0],
location: locations.translation[1],
target: locations.translation[0]
}
}, {
name: "Hexokinase",
objects: "",
location: locations.hexokinase[0],
entryPoint: locations.hexokinase[1],
zone: {
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
light: {
r: 255,
g: 255,
b: 255
},
intensity: 0.6,
ambient: 0.6,
sun: true,
skybox: "hexokinase_skybox"
},
instances: [{
model: "hexokinase_highres",
dimensions: {
x: 600,
y: 600,
z: 600
},
offset: {
x: 0,
y: 0,
z: 0
},
radius: 1,
number: 1,
userData: JSON.stringify({
grabbableKey: {
grabbable: false
}
}),
script: "",
visible: true
}],
boundary: {
radius: locations.hexokinase[2],
center: locations.hexokinase[0],
location: locations.mitochondria[1],
target: locations.mitochondria[0]
}
}];
function ImportScene(scene) {
@ -594,8 +602,6 @@ function ImportScene(scene) {
CreateInstances(scene);
CreateBoundary(scene);
CreateBackgroundAudio(scene.name, scene.location, scene.dimensions);
// print("done " + scene.name);
}
@ -637,12 +643,13 @@ function createLayoutLights() {
})
}
function CreateNavigationButton(scene, number) {
// print('NAV NAVIGATION CREATING NAV!!' +scene.name + " " + number)
Entities.addEntity({
type: "Sphere",
type: "Box",
name: scene.name + " navigation button",
color: {
red: 200,
@ -650,9 +657,9 @@ function CreateNavigationButton(scene, number) {
blue: 0
},
dimensions: {
x: 10,
y: 10,
z: 10
x: 16000,
y: 16000,
z: 16000
},
visible: false,
userData: JSON.stringify({
@ -665,10 +672,13 @@ function CreateNavigationButton(scene, number) {
grabbable: false
}
}),
// position:{x:3000,y:13500,z:3000},
position: {
x: 0,
y: 0,
z: 0
},
script: baseLocation + "Scripts/navigationButton.js?" + version,
collisionless: true,
});
}
@ -811,7 +821,7 @@ function CreateInstances(scene) {
}, idBounds, 150);
}
print('Script.clearTimeout SCRIPT AT CREATE ENTITY: ' + script)
//print('SCRIPT AT CREATE ENTITY: ' + script)
CreateEntity(scene.instances[i].model, position, rotation, scene.instances[i].dimensions, url, script, scene.instances[i].userData, scene.instances[i].visible);
}
}
@ -845,27 +855,6 @@ function CreateIdentification(name, position, rotation, dimensions, showDistance
}
function CreateBackgroundAudio(name, position) {
Entities.addEntity({
type: "Sphere",
name: "Location " + name + " background audio",
dimensions: {
x: 4000,
y: 4000,
z: 4000
},
position: position,
visible: false,
userData: JSON.stringify({
name: name,
baseURL: baseLocation
}),
script: baseLocation + "Scripts/playBackgroundAudio.js?" + version,
collisionless: true,
});
}
function getPointOnSphereOfRadius(radius, number, totalNumber) {
@ -890,7 +879,7 @@ function getPointOnSphereOfRadius(radius, number, totalNumber) {
// print("inc " + inc + " off " + off + " y " + y + " r " + r + " phi " + phi);
if (isNaN(r)) {
print("r is not a number");
//print("r is not a number");
r = 1;
}
@ -913,7 +902,7 @@ function CreateEntity(name, position, rotation, dimensions, url, script, userDat
scriptLocation = baseLocation + "Scripts/" + script;
}
print('Script.clearTimeout SCRIPT LOCATION IN CREATE ENTITY' + scriptLocation)
//print(' SCRIPT LOCATION IN CREATE ENTITY' + scriptLocation)
Entities.addEntity({
type: "Model",
name: name,