diff --git a/BUILD_WIN.md b/BUILD_WIN.md index ad143ef56a..cf8308c552 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -9,8 +9,7 @@ Please read the [general build guide](BUILD.md) for information on dependencies Currently building on Windows has been tested using the following compilers: * Visual Studio 2013 - -(If anyone can test using Visual Studio 2013 Express then please update this document) +* Visual Studio 2013 Express ####Visual Studio 2013 diff --git a/CMakeLists.txt b/CMakeLists.txt index a0d463b766..367fb88a26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,17 +38,19 @@ elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-strict-aliasing") endif(WIN32) -include(CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) -CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +if (NOT MSVC12) + include(CheckCXXCompilerFlag) + CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) + CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) -if (COMPILER_SUPPORTS_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -elseif(COMPILER_SUPPORTS_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") -else() - message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") -endif() + if (COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") + else() + message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") + endif() +endif () if (APPLE) set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++0x") diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 5e5bbba0bf..eab67818c4 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -51,7 +51,7 @@ Agent::Agent(const QByteArray& packet) : void Agent::readPendingDatagrams() { QByteArray receivedPacket; HifiSockAddr senderSockAddr; - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); while (readAvailableDatagram(receivedPacket, senderSockAddr)) { if (nodeList->packetVersionAndHashMatch(receivedPacket)) { @@ -103,7 +103,7 @@ void Agent::readPendingDatagrams() { // TODO: this needs to be fixed, the goal is to test the packet version for the piggyback, but // this is testing the version and hash of the original packet // need to use numBytesArithmeticCodingFromBuffer()... - if (!NodeList::getInstance()->packetVersionAndHashMatch(receivedPacket)) { + if (!DependencyManager::get()->packetVersionAndHashMatch(receivedPacket)) { return; // bail since piggyback data doesn't match our versioning } } else { @@ -127,7 +127,7 @@ void Agent::readPendingDatagrams() { // let this continue through to the NodeList so it updates last heard timestamp // for the sending audio mixer - NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + DependencyManager::get()->processNodeData(senderSockAddr, receivedPacket); } else if (datagramPacketType == PacketTypeBulkAvatarData || datagramPacketType == PacketTypeAvatarIdentity || datagramPacketType == PacketTypeAvatarBillboard @@ -137,9 +137,9 @@ void Agent::readPendingDatagrams() { // let this continue through to the NodeList so it updates last heard timestamp // for the sending avatar-mixer - NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + DependencyManager::get()->processNodeData(senderSockAddr, receivedPacket); } else { - NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + DependencyManager::get()->processNodeData(senderSockAddr, receivedPacket); } } } @@ -150,7 +150,7 @@ const QString AGENT_LOGGING_NAME = "agent"; void Agent::run() { ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer @@ -161,7 +161,7 @@ void Agent::run() { QUrl scriptURL; if (_payload.isEmpty()) { scriptURL = QUrl(QString("http://%1:%2/assignment/%3") - .arg(NodeList::getInstance()->getDomainHandler().getIP().toString()) + .arg(DependencyManager::get()->getDomainHandler().getIP().toString()) .arg(DOMAIN_SERVER_HTTP_PORT) .arg(uuidStringWithoutCurlyBraces(_uuid))); } else { diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 386d6e8a1d..7a5ad26726 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -48,7 +49,12 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : setOrganizationDomain("highfidelity.io"); setApplicationName("assignment-client"); QSettings::setDefaultFormat(QSettings::IniFormat); - + + // create a NodeList as an unassigned client + DependencyManager::registerInheritance(); + auto addressManager = DependencyManager::set(); + auto nodeList = DependencyManager::set(NodeType::Unassigned); + // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us #ifdef _WIN32 installNativeEventFilter(&ShutdownEventListener::getInstance()); @@ -91,9 +97,6 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID); _requestAssignment.setWalletUUID(walletUUID); } - - // create a NodeList as an unassigned client - NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned); quint16 assignmentServerPort = DEFAULT_DOMAIN_SERVER_PORT; @@ -136,7 +139,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : void AssignmentClient::sendAssignmentRequest() { if (!_currentAssignment) { - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); if (_assignmentServerHostname == "localhost") { // we want to check again for the local domain-server port in case the DS has restarted @@ -173,7 +176,7 @@ void AssignmentClient::sendAssignmentRequest() { } void AssignmentClient::readPendingDatagrams() { - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); QByteArray receivedPacket; HifiSockAddr senderSockAddr; @@ -260,7 +263,7 @@ void AssignmentClient::assignmentCompleted() { qDebug("Assignment finished or never started - waiting for new assignment."); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); // have us handle incoming NodeList datagrams again disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment.data(), 0); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 6b646c1dad..b7dfdd9b24 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -442,7 +442,7 @@ int AudioMixer::prepareMixForListeningNode(Node* node) { // loop through all other nodes that have sufficient audio to mix int streamsMixed = 0; - NodeList::getInstance()->eachNode([&](const SharedNodePointer& otherNode){ + DependencyManager::get()->eachNode([&](const SharedNodePointer& otherNode){ if (otherNode->getLinkedData()) { AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); @@ -522,12 +522,12 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) { memcpy(envDataAt, &wetLevel, sizeof(float)); envDataAt += sizeof(float); } - NodeList::getInstance()->writeDatagram(clientEnvBuffer, envDataAt - clientEnvBuffer, node); + DependencyManager::get()->writeDatagram(clientEnvBuffer, envDataAt - clientEnvBuffer, node); } } void AudioMixer::readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); if (nodeList->packetVersionAndHashMatch(receivedPacket)) { // pull any new audio data from nodes off of the network stack @@ -608,7 +608,7 @@ void AudioMixer::sendStatsPacket() { somethingToSend = true; sizeOfStats += property.size() + value.size(); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); int clientNumber = 0; @@ -641,7 +641,7 @@ void AudioMixer::run() { ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); // we do not want this event loop to be the handler for UDP datagrams, so disconnect disconnect(&nodeList->getNodeSocket(), 0, this, 0); @@ -894,7 +894,7 @@ void AudioMixer::perSecondActions() { _timeSpentPerHashMatchCallStats.getWindowSum() / WINDOW_LENGTH_USECS * 100.0, _timeSpentPerHashMatchCallStats.getCurrentIntervalSum() / USECS_PER_SECOND * 100.0); - NodeList::getInstance()->eachNode([](const SharedNodePointer& node) { + DependencyManager::get()->eachNode([](const SharedNodePointer& node) { if (node->getLinkedData()) { AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 26178eb2af..61f9e544e4 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -147,7 +147,7 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& removeDeadInjectedStreams(); char packet[MAX_PACKET_SIZE]; - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); // The append flag is a boolean value that will be packed right after the header. The first packet sent // inside this method will have 0 for this flag, while every subsequent packet will have 1 for this flag. diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 671fb17bc8..9616c8cb21 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -41,7 +41,7 @@ AvatarMixer::AvatarMixer(const QByteArray& packet) : _sumIdentityPackets(0) { // make sure we hear about node kills so we can tell the other nodes - connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); + connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); } AvatarMixer::~AvatarMixer() { @@ -117,7 +117,7 @@ void AvatarMixer::broadcastAvatarData() { int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); AvatarMixerClientData* nodeData = NULL; AvatarMixerClientData* otherNodeData = NULL; @@ -222,7 +222,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar); killPacket += killedNode->getUUID().toRfc4122(); - NodeList::getInstance()->broadcastToNodes(killPacket, + DependencyManager::get()->broadcastToNodes(killPacket, NodeSet() << NodeType::Agent); } } @@ -231,7 +231,7 @@ void AvatarMixer::readPendingDatagrams() { QByteArray receivedPacket; HifiSockAddr senderSockAddr; - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); while (readAvailableDatagram(receivedPacket, senderSockAddr)) { if (nodeList->packetVersionAndHashMatch(receivedPacket)) { @@ -309,7 +309,7 @@ void AvatarMixer::sendStatsPacket() { void AvatarMixer::run() { ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); nodeList->linkedDataCreateCallback = attachAvatarDataToNode; diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index af450782a2..d882ea19ac 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -76,7 +76,7 @@ void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePo copyAt += sizeof(entityID); packetLength += sizeof(entityID); - NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, senderNode); + DependencyManager::get()->writeDatagram((char*) outputBuffer, packetLength, senderNode); } @@ -114,7 +114,8 @@ int EntityServer::sendSpecialPacket(const SharedNodePointer& node, OctreeQueryNo hasMoreToSend = tree->encodeEntitiesDeletedSince(queryNode->getSequenceNumber(), deletedEntitiesSentAt, outputBuffer, MAX_PACKET_SIZE, packetLength); - NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node)); + DependencyManager::get()->writeDatagram((char*) outputBuffer, packetLength, + SharedNodePointer(node)); queryNode->packetSent(outputBuffer, packetLength); packetsSent++; } @@ -132,7 +133,7 @@ void EntityServer::pruneDeletedEntities() { quint64 earliestLastDeletedEntitiesSent = usecTimestampNow() + 1; // in the future - NodeList::getInstance()->eachNode([&earliestLastDeletedEntitiesSent](const SharedNodePointer& node) { + DependencyManager::get()->eachNode([&earliestLastDeletedEntitiesSent](const SharedNodePointer& node) { if (node->getLinkedData()) { EntityNodeData* nodeData = static_cast(node->getLinkedData()); quint64 nodeLastDeletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 912183f5d6..f52cdf3e49 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -60,11 +60,11 @@ const QString METAVOXEL_SERVER_LOGGING_NAME = "metavoxel-server"; void MetavoxelServer::run() { commonInit(METAVOXEL_SERVER_LOGGING_NAME, NodeType::MetavoxelServer); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); - connect(nodeList, &NodeList::nodeAdded, this, &MetavoxelServer::maybeAttachSession); - connect(nodeList, &NodeList::nodeKilled, this, &MetavoxelServer::maybeDeleteSession); + connect(nodeList.data(), &NodeList::nodeAdded, this, &MetavoxelServer::maybeAttachSession); + connect(nodeList.data(), &NodeList::nodeKilled, this, &MetavoxelServer::maybeDeleteSession); // initialize Bitstream before using it in multiple threads Bitstream::preThreadingInit(); @@ -101,7 +101,7 @@ void MetavoxelServer::readPendingDatagrams() { QByteArray receivedPacket; HifiSockAddr senderSockAddr; - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); while (readAvailableDatagram(receivedPacket, senderSockAddr)) { if (nodeList->packetVersionAndHashMatch(receivedPacket)) { diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index a2ea44152b..8a560984a6 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -261,7 +261,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() { continue; } - const SharedNodePointer& destinationNode = NodeList::getInstance()->nodeWithUUID(nodeUUID); + const SharedNodePointer& destinationNode = DependencyManager::get()->nodeWithUUID(nodeUUID); // retrieve sequence number stats of node, prune its missing set SequenceNumberStats& sequenceNumberStats = nodeStats.getIncomingEditSequenceNumberStats(); @@ -299,7 +299,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() { numSequenceNumbersAvailable -= numSequenceNumbers; // send it - NodeList::getInstance()->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode); + DependencyManager::get()->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode); packetsSent++; qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index f0b4170cc6..277d4fac1f 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -179,12 +179,12 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes // actually send it OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, _node); + DependencyManager::get()->writeDatagram((char*) statsMessage, statsMessageLength, _node); packetSent = true; } else { // not enough room in the packet, send two packets OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, _node); + DependencyManager::get()->writeDatagram((char*) statsMessage, statsMessageLength, _node); // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since // there was nothing else to send. @@ -213,7 +213,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes packetsSent++; OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*)nodeData->getPacket(), nodeData->getPacketLength(), _node); + DependencyManager::get()->writeDatagram((char*)nodeData->getPacket(), nodeData->getPacketLength(), _node); packetSent = true; thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); @@ -242,7 +242,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) { // just send the octree packet OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*)nodeData->getPacket(), nodeData->getPacketLength(), _node); + DependencyManager::get()->writeDatagram((char*)nodeData->getPacket(), nodeData->getPacketLength(), _node); packetSent = true; int thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); @@ -575,7 +575,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus while (nodeData->hasNextNackedPacket() && packetsSentThisInterval < maxPacketsPerInterval) { const QByteArray* packet = nodeData->getNextNackedPacket(); if (packet) { - NodeList::getInstance()->writeDatagram(*packet, _node); + DependencyManager::get()->writeDatagram(*packet, _node); truePacketsSent++; packetsSentThisInterval++; diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index cb206f626e..aaf37c2beb 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include @@ -829,7 +830,7 @@ void OctreeServer::parsePayload() { } void OctreeServer::readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); if (nodeList->packetVersionAndHashMatch(receivedPacket)) { PacketType packetType = packetTypeForPacket(receivedPacket); @@ -865,13 +866,13 @@ void OctreeServer::readPendingDatagram(const QByteArray& receivedPacket, const H _octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket); } else { // let processNodeData handle it. - NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + DependencyManager::get()->processNodeData(senderSockAddr, receivedPacket); } } } void OctreeServer::setupDatagramProcessingThread() { - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); // we do not want this event loop to be the handler for UDP datagrams, so disconnect disconnect(&nodeList->getNodeSocket(), 0, this, 0); @@ -960,7 +961,7 @@ void OctreeServer::readConfiguration() { } // wait until we have the domain-server settings, otherwise we bail - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); DomainHandler& domainHandler = nodeList->getDomainHandler(); qDebug() << "Waiting for domain settings from domain-server."; @@ -978,6 +979,7 @@ void OctreeServer::readConfiguration() { const QJsonObject& settingsObject = domainHandler.getSettingsObject(); QString settingsKey = getMyDomainSettingsKey(); QJsonObject settingsSectionObject = settingsObject[settingsKey].toObject(); + _settings = settingsSectionObject; // keep this for later if (!readOptionString(QString("statusHost"), settingsSectionObject, _statusHost) || _statusHost.isEmpty()) { _statusHost = getLocalAddress().toString(); @@ -1042,20 +1044,8 @@ void OctreeServer::readConfiguration() { _wantBackup = !noBackup; qDebug() << "wantBackup=" << _wantBackup; - if (_wantBackup) { - _backupExtensionFormat = OctreePersistThread::DEFAULT_BACKUP_EXTENSION_FORMAT; - readOptionString(QString("backupExtensionFormat"), settingsSectionObject, _backupExtensionFormat); - qDebug() << "backupExtensionFormat=" << _backupExtensionFormat; - - _backupInterval = OctreePersistThread::DEFAULT_BACKUP_INTERVAL; - readOptionInt(QString("backupInterval"), settingsSectionObject, _backupInterval); - qDebug() << "backupInterval=" << _backupInterval; - - _maxBackupVersions = OctreePersistThread::DEFAULT_MAX_BACKUP_VERSIONS; - readOptionInt(QString("maxBackupVersions"), settingsSectionObject, _maxBackupVersions); - qDebug() << "maxBackupVersions=" << _maxBackupVersions; - } - + //qDebug() << "settingsSectionObject:" << settingsSectionObject; + } else { qDebug("persistFilename= DISABLED"); } @@ -1106,7 +1096,7 @@ void OctreeServer::run() { _tree->setIsServer(true); // make sure our NodeList knows what type we are - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); nodeList->setOwnerType(getMyNodeType()); @@ -1120,8 +1110,8 @@ void OctreeServer::run() { beforeRun(); // after payload has been processed - connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); - connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)),SLOT(nodeKilled(SharedNodePointer))); + connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); + connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)),SLOT(nodeKilled(SharedNodePointer))); // we need to ask the DS about agents so we can ping/reply with them @@ -1140,8 +1130,7 @@ void OctreeServer::run() { // now set up PersistThread _persistThread = new OctreePersistThread(_tree, _persistFilename, _persistInterval, - _wantBackup, _backupInterval, _backupExtensionFormat, - _maxBackupVersions, _debugTimestampNow); + _wantBackup, _settings, _debugTimestampNow); if (_persistThread) { _persistThread->initialize(true); } @@ -1223,7 +1212,7 @@ void OctreeServer::aboutToFinish() { qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down..."; _octreeInboundPacketProcessor->shuttingDown(); - NodeList::getInstance()->eachNode([this](const SharedNodePointer& node) { + DependencyManager::get()->eachNode([this](const SharedNodePointer& node) { qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node; forceNodeShutdown(node); }); @@ -1388,7 +1377,7 @@ void OctreeServer::sendStatsPacket() { statsObject2[baseName + QString(".2.outbound.timing.5.avgSendTime")] = getAveragePacketSendingTime(); statsObject2[baseName + QString(".2.outbound.timing.5.nodeWaitTime")] = getAverageNodeWaitTime(); - NodeList::getInstance()->sendStatsToDomainServer(statsObject2); + DependencyManager::get()->sendStatsToDomainServer(statsObject2); static QJsonObject statsObject3; @@ -1407,7 +1396,7 @@ void OctreeServer::sendStatsPacket() { statsObject3[baseName + QString(".3.inbound.timing.5.avgLockWaitTimePerElement")] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement(); - NodeList::getInstance()->sendStatsToDomainServer(statsObject3); + DependencyManager::get()->sendStatsToDomainServer(statsObject3); } QMap OctreeServer::_threadsDidProcess; diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index a467a8a650..0f90c2941e 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -150,6 +150,7 @@ protected: int _argc; const char** _argv; char** _parsedArgV; + QJsonObject _settings; HTTPManager* _httpManager; int _statusPort; diff --git a/assignment-client/src/octree/OctreeServerDatagramProcessor.cpp b/assignment-client/src/octree/OctreeServerDatagramProcessor.cpp index 0d3c622900..9f2ae72147 100644 --- a/assignment-client/src/octree/OctreeServerDatagramProcessor.cpp +++ b/assignment-client/src/octree/OctreeServerDatagramProcessor.cpp @@ -45,7 +45,7 @@ void OctreeServerDatagramProcessor::readPendingDatagrams() { PacketType packetType = packetTypeForPacket(incomingPacket); if (packetType == PacketTypePing) { - NodeList::getInstance()->processNodeData(senderSockAddr, incomingPacket); + DependencyManager::get()->processNodeData(senderSockAddr, incomingPacket); return; // don't emit } diff --git a/cmake/modules/FindRSSDK.cmake b/cmake/modules/FindRSSDK.cmake new file mode 100644 index 0000000000..c31b0efcd9 --- /dev/null +++ b/cmake/modules/FindRSSDK.cmake @@ -0,0 +1,33 @@ +# Try to find the RSSDK library +# +# You must provide a RSSDK_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# RSSDK_FOUND - system found RSSDK +# RSSDK_INCLUDE_DIRS - the RSSDK include directory +# RSSDK_LIBRARIES - Link this to use RSSDK +# +# Created on 12/7/2014 by Thijs Wenker +# Copyright (c) 2014 High Fidelity +# + +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("rssdk") + +find_path(RSSDK_INCLUDE_DIRS pxcbase.h PATH_SUFFIXES include HINTS ${RSSDK_SEARCH_DIRS}) + +if (WIN32) + find_library(RSSDK_LIBRARY_DEBUG libpxc_d PATH_SUFFIXES lib/Win32 HINTS ${RSSDK_SEARCH_DIRS}) + find_library(RSSDK_LIBRARY_RELEASE libpxc PATH_SUFFIXES lib/Win32 HINTS ${RSSDK_SEARCH_DIRS}) +endif () + +include(SelectLibraryConfigurations) +select_library_configurations(RSSDK) + +set(RSSDK_LIBRARIES "${RSSDK_LIBRARY}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(RSSDK DEFAULT_MSG RSSDK_INCLUDE_DIRS RSSDK_LIBRARIES) + +mark_as_advanced(RSSDK_INCLUDE_DIRS RSSDK_LIBRARIES RSSDK_SEARCH_DIRS) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 3435f49b71..e2f751a7f0 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -305,20 +305,66 @@ "settings": [ { "name": "persistFilename", - "label": "Persistant Filename", - "help": "the filename for your entities", + "label": "Entities Filename", + "help": "the path to the file entities are stored in. Make sure the path exists.", "placeholder": "resources/models.svo", "default": "resources/models.svo", "advanced": true }, { "name": "persistInterval", - "label": "Persist Interval", - "help": "Interval between persist checks in msecs.", + "label": "Save Check Interval", + "help": "Milliseconds between checks for saving the current state of entities.", "placeholder": "30000", "default": "30000", "advanced": true }, + { + "name": "backups", + "type": "table", + "label": "Backup Rules", + "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", + "numbered": false, + "default": [ + {"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5}, + {"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7}, + {"Name":"Weekly Rolling","backupInterval":604800,"format":".backup.weekly.%N","maxBackupVersions":4}, + {"Name":"Thirty Day Rolling","backupInterval":2592000,"format":".backup.thirtyday.%N","maxBackupVersions":12} + ], + "columns": [ + { + "name": "Name", + "label": "Name", + "can_set": true, + "placeholder": "Example", + "default": "Example" + }, + { + "name": "format", + "label": "Rule Format", + "can_set": true, + "help": "Format used to create the extension for the backup of your persisted entities. Use a format with %N to get rolling. Or use date formatting like %Y-%m-%d.%H:%M:%S.%z", + "placeholder": ".backup.example.%N", + "default": ".backup.example.%N" + }, + { + "name": "backupInterval", + "label": "Backup Interval in Seconds", + "help": "Interval between backup checks in seconds.", + "placeholder": 1800, + "default": 1800, + "can_set": true + }, + { + "name": "maxBackupVersions", + "label": "Max Rolled Backup Versions", + "help": "If your backup extension format uses 'rolling', how many versions do you want us to keep?", + "placeholder": 5, + "default": 5, + "can_set": true + } + ] + }, { "name": "NoPersist", "type": "checkbox", @@ -326,30 +372,6 @@ "default": false, "advanced": true }, - { - "name": "backupExtensionFormat", - "label": "Backup File Extension Format:", - "help": "Format used to create the extension for the backup of your persisted entities. Use a format with %N to get rolling. Or use date formatting like %Y-%m-%d.%H:%M:%S.%z", - "placeholder": ".backup.%N", - "default": ".backup.%N", - "advanced": true - }, - { - "name": "backupInterval", - "label": "Backup Interval", - "help": "Interval between backup checks in msecs.", - "placeholder": "1800000", - "default": "1800000", - "advanced": true - }, - { - "name": "maxBackupVersions", - "label": "Max Rolled Backup Versions", - "help": "If your backup extension format uses 'rolling', how many versions do you want us to keep?", - "placeholder": "5", - "default": "5", - "advanced": true - }, { "name": "NoBackup", "type": "checkbox", diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index 1e9051d717..bdd80df9ec 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -667,7 +667,7 @@ function chooseFromHighFidelityDomains(clickedButton) { modal_body = "

Choose the High Fidelity domain you want this domain-server to represent.
This will set your domain ID on the settings page.

" domain_select = $("") _.each(data.data.domains, function(domain){ - domain_select.append("") + domain_select.append(""); }) modal_body += "" + domain_select[0].outerHTML modal_buttons["success"] = { diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a094432af2..791c7e37b6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -237,7 +237,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // check for scripts the user wants to persist from their domain-server config populateStaticScriptedAssignmentsFromSettings(); - LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); + auto nodeList = DependencyManager::set(domainServerPort, domainServerDTLSPort); // no matter the local port, save it to shared mem so that local assignment clients can ask what it is QSharedMemory* sharedPortMem = new QSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this); @@ -262,11 +262,11 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { nodeList->setSessionUUID(idValueVariant->toString()); } - connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); - connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); + connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); + connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); QTimer* silentNodeTimer = new QTimer(this); - connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); + connect(silentNodeTimer, SIGNAL(timeout()), nodeList.data(), SLOT(removeSilentNodes())); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readAvailableDatagrams())); @@ -346,8 +346,7 @@ bool DomainServer::optionallySetupAssignmentPayment() { } void DomainServer::setupAutomaticNetworking() { - - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + auto nodeList = DependencyManager::get(); const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000; const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; @@ -368,8 +367,10 @@ void DomainServer::setupAutomaticNetworking() { iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); // call our sendHeartbeatToIceServer immediately anytime a local or public socket changes - connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); - connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); + connect(nodeList.data(), &LimitedNodeList::localSockAddrChanged, + this, &DomainServer::sendHeartbeatToIceServer); + connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, + this, &DomainServer::sendHeartbeatToIceServer); // attempt to update our public socket now, this will send a heartbeat once we get public socket requestCurrentPublicSocketViaSTUN(); @@ -400,7 +401,8 @@ void DomainServer::setupAutomaticNetworking() { dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); // send public socket changes to the data server so nodes can find us at our new IP - connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); + connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, + this, &DomainServer::performIPAddressUpdate); // attempt to update our sockets now requestCurrentPublicSocketViaSTUN(); @@ -617,7 +619,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock QByteArray usernameRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainConnectionDenied); // send this oauth request datagram back to the client - LimitedNodeList::getInstance()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr); + DependencyManager::get()->writeUnverifiedDatagram(usernameRequestByteArray, senderSockAddr); return; } @@ -637,7 +639,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock } - SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, + SharedNodePointer newNode = DependencyManager::get()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); // when the newNode is created the linked data is also created // if this was a static assignment set the UUID, set the sendingSockAddr @@ -670,7 +672,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username, QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); // we always let in a user who is sending a packet from our local socket or from the localhost address - if (senderSockAddr.getAddress() == LimitedNodeList::getInstance()->getLocalSockAddr().getAddress() + if (senderSockAddr.getAddress() == DependencyManager::get()->getLocalSockAddr().getAddress() || senderSockAddr.getAddress() == QHostAddress::LocalHost) { return true; } @@ -843,8 +845,8 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif int numBroadcastPacketLeadBytes = broadcastDataStream.device()->pos(); DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + auto nodeList = DependencyManager::get(); // if we've established a connection via ICE with this peer, use that socket // otherwise just try to reply back to them on their sending socket (although that may not work) @@ -910,7 +912,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif } void DomainServer::readAvailableDatagrams() { - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + auto nodeList = DependencyManager::get(); HifiSockAddr senderSockAddr; QByteArray receivedPacket; @@ -998,7 +1000,7 @@ void DomainServer::readAvailableDatagrams() { void DomainServer::setupPendingAssignmentCredits() { // enumerate the NodeList to find the assigned nodes - NodeList::getInstance()->eachNode([&](const SharedNodePointer& node){ + DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) { @@ -1112,7 +1114,7 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) { } void DomainServer::requestCurrentPublicSocketViaSTUN() { - LimitedNodeList::getInstance()->sendSTUNRequest(); + DependencyManager::get()->sendSTUNRequest(); } QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { @@ -1134,7 +1136,8 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; - const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); + auto nodeList = DependencyManager::get(); + const QUuid& domainID = nodeList->getSessionUUID(); // setup the domain object to send to the data server const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address"; @@ -1158,7 +1161,7 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { // add the number of currently connected agent users int numConnectedAuthedUsers = 0; - NodeList::getInstance()->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){ + nodeList->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){ if (node->getLinkedData() && !static_cast(node->getLinkedData())->getUsername().isEmpty()) { ++numConnectedAuthedUsers; } @@ -1187,11 +1190,11 @@ void DomainServer::performICEUpdates() { } void DomainServer::sendHeartbeatToIceServer() { - LimitedNodeList::getInstance()->sendHeartbeatToIceServer(_iceServerSocket); + DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); } void DomainServer::sendICEPingPackets() { - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + auto nodeList = DependencyManager::get(); QHash::iterator peer = _connectingICEPeers.begin(); @@ -1259,7 +1262,7 @@ void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockA } void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { - LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + auto nodeList = DependencyManager::get(); if (nodeList->packetVersionAndHashMatch(receivedPacket)) { PacketType requestType = packetTypeForPacket(receivedPacket); @@ -1410,6 +1413,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; + auto nodeList = DependencyManager::get(); + // allow sub-handlers to handle requests that do not require authentication if (_settingsManager.handlePublicHTTPRequest(connection, url)) { return true; @@ -1452,7 +1457,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString URI_ID = "/id"; if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == URI_ID) { - QUuid domainID = LimitedNodeList::getInstance()->getSessionUUID(); + QUuid domainID = nodeList->getSessionUUID(); connection->respond(HTTPConnection::StatusCode200, uuidStringWithoutCurlyBraces(domainID).toLocal8Bit()); return true; @@ -1474,7 +1479,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject assignedNodesJSON; // enumerate the NodeList to find the assigned nodes - NodeList::getInstance()->eachNode([this, &assignedNodesJSON](const SharedNodePointer& node){ + nodeList->eachNode([this, &assignedNodesJSON](const SharedNodePointer& node){ DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); if (!nodeData->getAssignmentUUID().isNull()) { @@ -1536,7 +1541,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonArray nodesJSONArray; // enumerate the NodeList to find the assigned nodes - LimitedNodeList::getInstance()->eachNode([this, &nodesJSONArray](const SharedNodePointer& node){ + nodeList->eachNode([this, &nodesJSONArray](const SharedNodePointer& node){ // add the node using the UUID as the key nodesJSONArray.append(jsonObjectForNode(node)); }); @@ -1559,7 +1564,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QUuid matchingUUID = QUuid(nodeShowRegex.cap(1)); // see if we have a node that matches this ID - SharedNodePointer matchingNode = LimitedNodeList::getInstance()->nodeWithUUID(matchingUUID); + SharedNodePointer matchingNode = nodeList->nodeWithUUID(matchingUUID); if (matchingNode) { // create a QJsonDocument with the stats QJsonObject QJsonObject statsObject = @@ -1653,14 +1658,14 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // pull the captured string, if it exists QUuid deleteUUID = QUuid(nodeDeleteRegex.cap(1)); - SharedNodePointer nodeToKill = LimitedNodeList::getInstance()->nodeWithUUID(deleteUUID); + SharedNodePointer nodeToKill = nodeList->nodeWithUUID(deleteUUID); if (nodeToKill) { // start with a 200 response connection->respond(HTTPConnection::StatusCode200); // we have a valid UUID and node - kill the node that has this assignment - QMetaObject::invokeMethod(LimitedNodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); + QMetaObject::invokeMethod(nodeList.data(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); // successfully processed request return true; @@ -1669,7 +1674,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (allNodesDeleteRegex.indexIn(url.path()) != -1) { qDebug() << "Received request to kill all nodes."; - LimitedNodeList::getInstance()->eraseAllNodes(); + nodeList->eraseAllNodes(); return true; } @@ -1989,7 +1994,7 @@ void DomainServer::nodeKilled(SharedNodePointer node) { // cleanup the connection secrets that we set up for this node (on the other nodes) foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) { - SharedNodePointer otherNode = LimitedNodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); + SharedNodePointer otherNode = DependencyManager::get()->nodeWithUUID(otherNodeSessionUUID); if (otherNode) { reinterpret_cast(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID()); } @@ -2072,7 +2077,7 @@ void DomainServer::addStaticAssignmentsToQueue() { // add any of the un-matched static assignments to the queue // enumerate the nodes and check if there is one with an attached assignment with matching UUID - if (!NodeList::getInstance()->nodeWithUUID(staticAssignment->data()->getUUID())) { + if (!DependencyManager::get()->nodeWithUUID(staticAssignment->data()->getUUID())) { // this assignment has not been fulfilled - reset the UUID and add it to the assignment queue refreshStaticAssignmentAndAddToQueue(*staticAssignment); } diff --git a/examples/acScripts/ControlACs.js b/examples/acScripts/ControlACs.js index 0edbde4ad1..4f27a36b9d 100644 --- a/examples/acScripts/ControlACs.js +++ b/examples/acScripts/ControlACs.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; // Set the following variables to the right value var NUM_AC = 3; // This is the number of AC. Their ID need to be unique and between 0 (included) and NUM_AC (excluded) diff --git a/examples/acScripts/ControlledAC.js b/examples/acScripts/ControlledAC.js index 4e400670e5..78fe3903cd 100644 --- a/examples/acScripts/ControlledAC.js +++ b/examples/acScripts/ControlledAC.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; // Set the following variables to the values needed var filename = HIFI_PUBLIC_BUCKET + "ozan/bartender.rec"; diff --git a/examples/acScripts/botProceduralWayPoints.js b/examples/acScripts/botProceduralWayPoints.js index b7c1fa1fe2..1e00c9f8b8 100644 --- a/examples/acScripts/botProceduralWayPoints.js +++ b/examples/acScripts/botProceduralWayPoints.js @@ -20,7 +20,7 @@ // //For procedural walk animation -Script.include("../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; Script.include(HIFI_PUBLIC_BUCKET + "scripts/proceduralAnimationAPI.js"); var procAnimAPI = new ProcAnimAPI(); diff --git a/examples/acScripts/bot_procedural.js b/examples/acScripts/bot_procedural.js index 8b96ed36c2..518ef8cc22 100644 --- a/examples/acScripts/bot_procedural.js +++ b/examples/acScripts/bot_procedural.js @@ -11,7 +11,7 @@ // //For procedural walk animation -Script.include("../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; Script.include("proceduralAnimationAPI.js"); var procAnimAPI = new ProcAnimAPI(); diff --git a/examples/acScripts/bot_randomExpression.js b/examples/acScripts/bot_randomExpression.js index 32bfb24065..b76e485e4a 100644 --- a/examples/acScripts/bot_randomExpression.js +++ b/examples/acScripts/bot_randomExpression.js @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; function getRandomFloat(min, max) { return Math.random() * (max - min) + min; diff --git a/examples/clap.js b/examples/clap.js index 2b011404c0..3b333e4345 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var clapAnimation = HIFI_PUBLIC_BUCKET + "animations/ClapAnimations/ClapHands_Standing.fbx"; var ANIMATION_FRAMES_PER_CLAP = 10.0; diff --git a/examples/controllers/hydra/airGuitar.js b/examples/controllers/hydra/airGuitar.js index 6d3d374f3a..29ab2f9c44 100644 --- a/examples/controllers/hydra/airGuitar.js +++ b/examples/controllers/hydra/airGuitar.js @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; function length(v) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); diff --git a/examples/controllers/hydra/drumStick.js b/examples/controllers/hydra/drumStick.js index 14e1413742..d2a948b98e 100644 --- a/examples/controllers/hydra/drumStick.js +++ b/examples/controllers/hydra/drumStick.js @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; function length(v) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); diff --git a/examples/controllers/hydra/frisbee.js b/examples/controllers/hydra/frisbee.js index a9fd74910d..461f4691da 100644 --- a/examples/controllers/hydra/frisbee.js +++ b/examples/controllers/hydra/frisbee.js @@ -15,7 +15,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; Script.include("../../libraries/toolBars.js"); const LEFT_PALM = 0; diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index de18317335..e3450b708e 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -14,7 +14,28 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var RED = { red: 255, green: 0, blue: 0 }; +var LASER_WIDTH = 2; + +var pointer = []; +pointer.push(Overlays.addOverlay("line3d", { + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: RED, + alpha: 1, + visible: true, + lineWidth: LASER_WIDTH +})); +pointer.push(Overlays.addOverlay("line3d", { + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: RED, + alpha: 1, + visible: true, + lineWidth: LASER_WIDTH +})); function getRandomFloat(min, max) { return Math.random() * (max - min) + min; @@ -32,8 +53,13 @@ var MAX_THROWER_DELAY = 1000; var LEFT_BUTTON_3 = 3; var RELOAD_INTERVAL = 5; +var KICKBACK_ANGLE = 15; +var elbowKickAngle = 0.0; +var rotationBeforeKickback; + var showScore = false; + // Load some sound to use for loading and firing var fireSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/GUN-SHOT2.raw"); var loadSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/Gun_Reload_Weapon22.raw"); @@ -48,10 +74,11 @@ var audioOptions = { } var shotsFired = 0; - var shotTime = new Date(); -// initialize our triggers +var activeControllers = 0; + +// initialize our controller triggers var triggerPulled = new Array(); var numberOfTriggers = Controller.getNumberOfTriggers(); for (t = 0; t < numberOfTriggers; t++) { @@ -59,9 +86,11 @@ for (t = 0; t < numberOfTriggers; t++) { } var isLaunchButtonPressed = false; - var score = 0; +var bulletID = false; +var targetID = false; + // Create a reticle image in center of screen var screenSize = Controller.getViewportDimensions(); var reticle = Overlays.addOverlay("image", { @@ -74,6 +103,16 @@ var reticle = Overlays.addOverlay("image", { alpha: 1 }); +var offButton = Overlays.addOverlay("image", { + x: screenSize.x - 48, + y: 96, + width: 32, + height: 32, + imageURL: HIFI_PUBLIC_BUCKET + "images/close.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + if (showScore) { var text = Overlays.addOverlay("text", { x: screenSize.x / 2 - 100, @@ -88,25 +127,27 @@ if (showScore) { }); } - - function printVector(string, vector) { print(string + " " + vector.x + ", " + vector.y + ", " + vector.z); } function shootBullet(position, velocity) { - var BULLET_SIZE = 0.01; - var BULLET_LIFETIME = 20.0; - var BULLET_GRAVITY = -0.02; - Entities.addEntity( + var BULLET_SIZE = 0.07; + var BULLET_LIFETIME = 10.0; + var BULLET_GRAVITY = 0.0; + bulletID = Entities.addEntity( { type: "Sphere", position: position, dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE }, - color: { red: 10, green: 10, blue: 10 }, + color: { red: 255, green: 0, blue: 0 }, velocity: velocity, lifetime: BULLET_LIFETIME, - gravity: { x: 0, y: BULLET_GRAVITY, z: 0 }, - damping: 0 }); + gravity: { x: 0, y: BULLET_GRAVITY, z: 0 }, + damping: 0.01, + density: 5000, + ignoreCollisions: false, + collisionsWillMove: true + }); // Play firing sounds audioOptions.position = position; @@ -115,36 +156,46 @@ function shootBullet(position, velocity) { if ((shotsFired % RELOAD_INTERVAL) == 0) { Audio.playSound(loadSound, audioOptions); } + + // Kickback the arm + rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm"); + var armRotation = MyAvatar.getJointRotation("LeftForeArm"); + armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE)); + MyAvatar.setJointData("LeftForeArm", armRotation); + elbowKickAngle = KICKBACK_ANGLE; } function shootTarget() { - var TARGET_SIZE = 0.25; - var TARGET_GRAVITY = -0.6; + var TARGET_SIZE = 0.50; + var TARGET_GRAVITY = 0.0; var TARGET_LIFETIME = 300.0; - var TARGET_UP_VELOCITY = 3.0; - var TARGET_FWD_VELOCITY = 5.0; - var DISTANCE_TO_LAUNCH_FROM = 3.0; + var TARGET_UP_VELOCITY = 0.0; + var TARGET_FWD_VELOCITY = 0.0; + var DISTANCE_TO_LAUNCH_FROM = 5.0; + var ANGLE_RANGE_FOR_LAUNCH = 20.0; var camera = Camera.getPosition(); //printVector("camera", camera); - var targetDirection = Quat.angleAxis(getRandomFloat(-20.0, 20.0), { x:0, y:1, z:0 }); + var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 }); targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection); var forwardVector = Quat.getFront(targetDirection); - //printVector("forwardVector", forwardVector); + var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM)); - //printVector("newPosition", newPosition); + var velocity = Vec3.multiply(forwardVector, TARGET_FWD_VELOCITY); velocity.y += TARGET_UP_VELOCITY; - //printVector("velocity", velocity); - - Entities.addEntity( - { type: "Sphere", + + targetID = Entities.addEntity( + { type: "Box", position: newPosition, - dimensions: { x: TARGET_SIZE, y: TARGET_SIZE, z: TARGET_SIZE }, - color: { red: 0, green: 200, blue: 200 }, + dimensions: { x: TARGET_SIZE * (0.5 + Math.random()), y: TARGET_SIZE * (0.5 + Math.random()), z: TARGET_SIZE * (0.5 + Math.random()) / 4.0 }, + color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, velocity: velocity, gravity: { x: 0, y: TARGET_GRAVITY, z: 0 }, lifetime: TARGET_LIFETIME, - damping: 0.0001 }); + rotation: Camera.getOrientation(), + damping: 0.1, + density: 100.0, + collisionsWillMove: true }); // Record start time shotTime = new Date(); @@ -154,27 +205,26 @@ function shootTarget() { Audio.playSound(targetLaunchSound, audioOptions); } - - function entityCollisionWithEntity(entity1, entity2, collision) { - score++; - if (showScore) { - Overlays.editOverlay(text, { text: "Score: " + score } ); - } - - // Sort out which entity is which - // Record shot time - var endTime = new Date(); - var msecs = endTime.valueOf() - shotTime.valueOf(); - //print("hit, msecs = " + msecs); - //Vec3.print("penetration = ", collision.penetration); - //Vec3.print("contactPoint = ", collision.contactPoint); - Entities.deleteEntity(entity1); - Entities.deleteEntity(entity2); - // play the sound near the camera so the shooter can hear it - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - Audio.playSound(targetHitSound, audioOptions); + if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) && + ((entity2.id == bulletID.id) || (entity2.id == targetID.id))) { + score++; + if (showScore) { + Overlays.editOverlay(text, { text: "Score: " + score } ); + } + + // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! + Script.setTimeout(deleteBulletAndTarget, 500); + + // Turn the target and the bullet white + Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); + Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); + + // play the sound near the camera so the shooter can hear it + audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + Audio.playSound(targetHitSound, audioOptions); + } } function keyPressEvent(event) { @@ -182,55 +232,105 @@ function keyPressEvent(event) { if (event.text == "t") { var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; Script.setTimeout(shootTarget, time); - } else if (event.text == ".") { + } else if ((event.text == ".") || (event.text == "SPACE")) { shootFromMouse(); } else if (event.text == "r") { playLoadSound(); + } else if (event.text == "s") { + // Hit this key to dump a posture from hydra to log + Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm")); + Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm")); + Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand")); + } } function playLoadSound() { audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); Audio.playSound(loadSound, audioOptions); + // Raise arm to firing posture + takeFiringPose(); +} + +function clearPose() { + MyAvatar.clearJointData("LeftForeArm"); + MyAvatar.clearJointData("LeftArm"); + MyAvatar.clearJointData("LeftHand"); +} + +function deleteBulletAndTarget() { + Entities.deleteEntity(bulletID); + Entities.deleteEntity(targetID); + bulletID = false; + targetID = false; +} + +function takeFiringPose() { + clearPose(); + if (Controller.getNumberOfSpatialControls() == 0) { + MyAvatar.setJointData("LeftForeArm", {x: -0.251919, y: -0.0415449, z: 0.499487, w: 0.827843}); + MyAvatar.setJointData("LeftArm", { x: 0.470196, y: -0.132559, z: 0.494033, w: 0.719219}); + MyAvatar.setJointData("LeftHand", { x: -0.0104815, y: -0.110551, z: -0.352111, w: 0.929333}); + } } MyAvatar.attach(gunModel, "RightHand", {x:0.02, y: 0.11, z: 0.04}, Quat.fromPitchYawRollDegrees(-0, -160, -79), 0.20); -//MyAvatar.attach(gunModel, "LeftHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20); +MyAvatar.attach(gunModel, "LeftHand", {x:-0.02, y: 0.11, z: 0.04}, Quat.fromPitchYawRollDegrees(0, 0, 79), 0.20); // Give a bit of time to load before playing sound Script.setTimeout(playLoadSound, 2000); function update(deltaTime) { + if (bulletID && !bulletID.isKnownID) { + bulletID = Entities.identifyEntity(bulletID); + } + if (targetID && !targetID.isKnownID) { + targetID = Entities.identifyEntity(targetID); + } // Check for mouseLook movement, update rotation // rotate body yaw for yaw received from mouse var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } )); - MyAvatar.orientation = newOrientation; + //MyAvatar.orientation = newOrientation; yawFromMouse = 0; // apply pitch from mouse var newPitch = MyAvatar.headPitch + pitchFromMouse; - MyAvatar.headPitch = newPitch; + //MyAvatar.headPitch = newPitch; pitchFromMouse = 0; - // Check hydra controller for launch button press - if (!isLaunchButtonPressed && Controller.isButtonPressed(LEFT_BUTTON_3)) { - isLaunchButtonPressed = true; - var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; - Script.setTimeout(shootTarget, time); - } else if (isLaunchButtonPressed && !Controller.isButtonPressed(LEFT_BUTTON_3)) { - isLaunchButtonPressed = false; - + + if (activeControllers == 0) { + if (Controller.getNumberOfSpatialControls() > 0) { + activeControllers = Controller.getNumberOfSpatialControls(); + clearPose(); + } } - // Check hydra controller for trigger press + var KICKBACK_DECAY_RATE = 0.125; + if (elbowKickAngle > 0.0) { + if (elbowKickAngle > 0.5) { + var newAngle = elbowKickAngle * KICKBACK_DECAY_RATE; + elbowKickAngle -= newAngle; + var armRotation = MyAvatar.getJointRotation("LeftForeArm"); + armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, -newAngle)); + MyAvatar.setJointData("LeftForeArm", armRotation); + } else { + MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback); + if (Controller.getNumberOfSpatialControls() > 0) { + clearPose(); + } + elbowKickAngle = 0.0; + } + } - var numberOfTriggers = Controller.getNumberOfTriggers(); - var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); - var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - // this is expected for hydras + // check for trigger press + + var numberOfTriggers = 2; + var controllersPerTrigger = 2; + if (numberOfTriggers == 2 && controllersPerTrigger == 2) { - for (var t = 0; t < numberOfTriggers; t++) { + for (var t = 0; t < 2; t++) { var shootABullet = false; var triggerValue = Controller.getTriggerValue(t); if (triggerPulled[t]) { @@ -239,21 +339,27 @@ function update(deltaTime) { triggerPulled[t] = false; // unpulled } } else { - // must pull to at least 0.9 - if (triggerValue > 0.9) { + // must pull to at least + if (triggerValue > 0.5) { triggerPulled[t] = true; // pulled shootABullet = true; } } + var palmController = t * controllersPerTrigger; + var palmPosition = Controller.getSpatialControlPosition(palmController); + var fingerTipController = palmController + 1; + var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController); + var laserTip = Vec3.sum(Vec3.multiply(100.0, Vec3.subtract(fingerTipPosition, palmPosition)), palmPosition); + // Update Lasers + Overlays.editOverlay(pointer[t], { + start: palmPosition, + end: laserTip, + alpha: 1 + }); if (shootABullet) { - var palmController = t * controllersPerTrigger; - var palmPosition = Controller.getSpatialControlPosition(palmController); - - var fingerTipController = palmController + 1; - var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController); - + var palmToFingerTipVector = { x: (fingerTipPosition.x - palmPosition.x), y: (fingerTipPosition.y - palmPosition.y), @@ -263,12 +369,8 @@ function update(deltaTime) { var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2, y: fingerTipPosition.y + palmToFingerTipVector.y/2, z: fingerTipPosition.z + palmToFingerTipVector.z/2}; - - var linearVelocity = 25; - - var velocity = { x: palmToFingerTipVector.x * linearVelocity, - y: palmToFingerTipVector.y * linearVelocity, - z: palmToFingerTipVector.z * linearVelocity }; + + var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector)); shootBullet(position, velocity); } @@ -276,16 +378,8 @@ function update(deltaTime) { } } -function mousePressEvent(event) { - isMouseDown = true; - lastX = event.x; - lastY = event.y; - //audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - //Audio.playSound(loadSound, audioOptions); -} - function shootFromMouse() { - var DISTANCE_FROM_CAMERA = 2.0; + var DISTANCE_FROM_CAMERA = 1.0; var camera = Camera.getPosition(); var forwardVector = Quat.getFront(Camera.getOrientation()); var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA)); @@ -312,14 +406,17 @@ function mouseMoveEvent(event) { function scriptEnding() { Overlays.deleteOverlay(reticle); + Overlays.deleteOverlay(offButton); + Overlays.deleteOverlay(pointer[0]); + Overlays.deleteOverlay(pointer[1]); Overlays.deleteOverlay(text); MyAvatar.detachOne(gunModel); + clearPose(); } Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); -Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js new file mode 100644 index 0000000000..4d0b873fd2 --- /dev/null +++ b/examples/controllers/hydra/hydraGrab.js @@ -0,0 +1,742 @@ +// +// hydraGrab.js +// examples +// +// Created by Clément Brisset on 4/24/14. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to edit models either with the razor hydras or with your mouse +// +// Using the hydras : +// grab models with the triggers, you can then move the models around or scale them with both hands. +// You can switch mode using the bumpers so that you can move models around more easily. +// +// 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("libraries/entityPropertyDialogBox.js"); +var entityPropertyDialogBox = EntityPropertyDialogBox; + +var LASER_WIDTH = 4; +var LASER_COLOR = { red: 255, green: 0, blue: 0 }; +var LASER_LENGTH_FACTOR = 500; + +var MIN_ANGULAR_SIZE = 2; +var MAX_ANGULAR_SIZE = 45; +var allowLargeModels = false; +var allowSmallModels = false; +var wantEntityGlow = false; + +var LEFT = 0; +var RIGHT = 1; + +var jointList = MyAvatar.getJointNames(); + +var mode = 0; + +function controller(wichSide) { + this.side = wichSide; + this.palm = 2 * wichSide; + this.tip = 2 * wichSide + 1; + this.trigger = wichSide; + this.bumper = 6 * wichSide + 5; + + this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm); + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + + this.oldTipPosition = Controller.getSpatialControlPosition(this.tip); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); + + this.oldUp = Controller.getSpatialControlNormal(this.palm); + this.up = this.oldUp; + + this.oldFront = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); + this.front = this.oldFront; + + this.oldRight = Vec3.cross(this.front, this.up); + this.right = this.oldRight; + + this.oldRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); + this.rotation = this.oldRotation; + + this.triggerValue = Controller.getTriggerValue(this.trigger); + this.bumperValue = Controller.isButtonPressed(this.bumper); + + this.pressed = false; // is trigger pressed + this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously) + + this.grabbing = false; + this.entityID = { isKnownID: false }; + this.modelURL = ""; + this.oldModelRotation; + this.oldModelPosition; + this.oldModelHalfDiagonal; + + this.positionAtGrab; + this.rotationAtGrab; + this.modelPositionAtGrab; + this.rotationAtGrab; + this.jointsIntersectingFromStart = []; + + this.laser = Overlays.addOverlay("line3d", { + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: LASER_COLOR, + alpha: 1, + visible: false, + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" + }); + + this.guideScale = 0.02; + this.ball = Overlays.addOverlay("sphere", { + position: { x: 0, y: 0, z: 0 }, + size: this.guideScale, + solid: true, + color: { red: 0, green: 255, blue: 0 }, + alpha: 1, + visible: false, + anchor: "MyAvatar" + }); + this.leftRight = Overlays.addOverlay("line3d", { + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 0, green: 0, blue: 255 }, + alpha: 1, + visible: false, + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" + }); + this.topDown = Overlays.addOverlay("line3d", { + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 0, green: 0, blue: 255 }, + alpha: 1, + visible: false, + lineWidth: LASER_WIDTH, + anchor: "MyAvatar" + }); + + + + this.grab = function (entityID, properties) { + print("Grabbing " + entityID.id); + this.grabbing = true; + this.entityID = entityID; + this.modelURL = properties.modelURL; + + this.oldModelPosition = properties.position; + this.oldModelRotation = properties.rotation; + this.oldModelHalfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + this.positionAtGrab = this.palmPosition; + this.rotationAtGrab = this.rotation; + this.modelPositionAtGrab = properties.position; + this.rotationAtGrab = properties.rotation; + this.jointsIntersectingFromStart = []; + for (var i = 0; i < jointList.length; i++) { + var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); + if (distance < this.oldModelHalfDiagonal) { + this.jointsIntersectingFromStart.push(i); + } + } + this.showLaser(false); + } + + this.release = function () { + if (this.grabbing) { + jointList = MyAvatar.getJointNames(); + + var closestJointIndex = -1; + var closestJointDistance = 10; + for (var i = 0; i < jointList.length; i++) { + var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); + if (distance < closestJointDistance) { + closestJointDistance = distance; + closestJointIndex = i; + } + } + + if (closestJointIndex != -1) { + print("closestJoint: " + jointList[closestJointIndex]); + print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelHalfDiagonal + ")"); + } + + if (closestJointDistance < this.oldModelHalfDiagonal) { + + if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 || + (leftController.grabbing && rightController.grabbing && + leftController.entityID.id == rightController.entityID.id)) { + // Do nothing + } else { + print("Attaching to " + jointList[closestJointIndex]); + var jointPosition = MyAvatar.getJointPosition(jointList[closestJointIndex]); + var jointRotation = MyAvatar.getJointCombinedRotation(jointList[closestJointIndex]); + + var attachmentOffset = Vec3.subtract(this.oldModelPosition, jointPosition); + attachmentOffset = Vec3.multiplyQbyV(Quat.inverse(jointRotation), attachmentOffset); + var attachmentRotation = Quat.multiply(Quat.inverse(jointRotation), this.oldModelRotation); + + MyAvatar.attach(this.modelURL, jointList[closestJointIndex], + attachmentOffset, attachmentRotation, 2.0 * this.oldModelHalfDiagonal, + true, false); + Entities.deleteEntity(this.entityID); + } + } + } + + this.grabbing = false; + this.entityID.isKnownID = false; + this.jointsIntersectingFromStart = []; + this.showLaser(true); + } + + this.checkTrigger = function () { + if (this.triggerValue > 0.9) { + if (this.pressed) { + this.pressing = false; + } else { + this.pressing = true; + } + this.pressed = true; + } else { + this.pressing = false; + this.pressed = false; + } + } + + this.checkEntity = function (properties) { + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| + + var A = this.palmPosition; + var B = this.front; + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var y = Vec3.dot(Vec3.subtract(P, A), this.up); + var z = Vec3.dot(Vec3.subtract(P, A), this.right); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + return { valid: true, x: x, y: y, z: z }; + } + return { valid: false }; + } + + this.glowedIntersectingModel = { isKnownID: false }; + this.moveLaser = function () { + // the overlays here are anchored to the avatar, which means they are specified in the avatar's local frame + + var inverseRotation = Quat.inverse(MyAvatar.orientation); + var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); + var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); + var distance = Vec3.length(direction); + direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / distance); + var endPosition = Vec3.sum(startPosition, direction); + + Overlays.editOverlay(this.laser, { + start: startPosition, + end: endPosition + }); + + + Overlays.editOverlay(this.ball, { + position: endPosition + }); + Overlays.editOverlay(this.leftRight, { + start: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)), + end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)) + }); + Overlays.editOverlay(this.topDown, { + start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), + end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)) + }); + this.showLaser(!this.grabbing || mode == 0); + + if (this.glowedIntersectingModel.isKnownID) { + Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); + this.glowedIntersectingModel.isKnownID = false; + } + if (!this.grabbing) { + var intersection = Entities.findRayIntersection({ + origin: this.palmPosition, + direction: this.front + }); + + var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14; + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + if (intersection.accurate && intersection.entityID.isKnownID && sizeOK) { + this.glowedIntersectingModel = intersection.entityID; + + if (wantEntityGlow) { + Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); + } + } + } + } + + this.showLaser = function (show) { + Overlays.editOverlay(this.laser, { visible: show }); + Overlays.editOverlay(this.ball, { visible: show }); + Overlays.editOverlay(this.leftRight, { visible: show }); + Overlays.editOverlay(this.topDown, { visible: show }); + } + this.moveEntity = function () { + if (this.grabbing) { + if (!this.entityID.isKnownID) { + print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); + this.entityID = Entities.findRayIntersection({ + origin: this.palmPosition, + direction: this.front + }).entityID; + print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); + } + var newPosition; + var newRotation; + + switch (mode) { + case 0: + newPosition = Vec3.sum(this.palmPosition, + Vec3.multiply(this.front, this.x)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.up, this.y)); + newPosition = Vec3.sum(newPosition, + Vec3.multiply(this.right, this.z)); + + + newRotation = Quat.multiply(this.rotation, + Quat.inverse(this.oldRotation)); + newRotation = Quat.multiply(newRotation, + this.oldModelRotation); + break; + case 1: + var forward = Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 }); + var d = Vec3.dot(forward, MyAvatar.position); + + var factor1 = Vec3.dot(forward, this.positionAtGrab) - d; + var factor2 = Vec3.dot(forward, this.modelPositionAtGrab) - d; + var vector = Vec3.subtract(this.palmPosition, this.positionAtGrab); + + if (factor2 < 0) { + factor2 = 0; + } + if (factor1 <= 0) { + factor1 = 1; + factor2 = 1; + } + + newPosition = Vec3.sum(this.modelPositionAtGrab, + Vec3.multiply(vector, + factor2 / factor1)); + + newRotation = Quat.multiply(this.rotation, + Quat.inverse(this.rotationAtGrab)); + newRotation = Quat.multiply(newRotation, + this.rotationAtGrab); + break; + } + Entities.editEntity(this.entityID, { + position: newPosition, + rotation: newRotation + }); + this.oldModelRotation = newRotation; + this.oldModelPosition = newPosition; + + var indicesToRemove = []; + for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { + var distance = Vec3.distance(MyAvatar.getJointPosition(this.jointsIntersectingFromStart[i]), this.oldModelPosition); + if (distance >= this.oldModelHalfDiagonal) { + indicesToRemove.push(this.jointsIntersectingFromStart[i]); + } + + } + for (var i = 0; i < indicesToRemove.length; ++i) { + this.jointsIntersectingFromStart.splice(this.jointsIntersectingFromStart.indexOf(indicesToRemove[i], 1)); + } + } + } + + this.update = function () { + this.oldPalmPosition = this.palmPosition; + this.oldTipPosition = this.tipPosition; + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); + + this.oldUp = this.up; + this.up = Vec3.normalize(Controller.getSpatialControlNormal(this.palm)); + + this.oldFront = this.front; + this.front = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); + + this.oldRight = this.right; + this.right = Vec3.normalize(Vec3.cross(this.front, this.up)); + + this.oldRotation = this.rotation; + this.rotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); + + this.triggerValue = Controller.getTriggerValue(this.trigger); + + var bumperValue = Controller.isButtonPressed(this.bumper); + if (bumperValue && !this.bumperValue) { + if (mode == 0) { + mode = 1; + Overlays.editOverlay(leftController.laser, { color: { red: 0, green: 0, blue: 255 } }); + Overlays.editOverlay(rightController.laser, { color: { red: 0, green: 0, blue: 255 } }); + } else { + mode = 0; + Overlays.editOverlay(leftController.laser, { color: { red: 255, green: 0, blue: 0 } }); + Overlays.editOverlay(rightController.laser, { color: { red: 255, green: 0, blue: 0 } }); + } + } + this.bumperValue = bumperValue; + + + this.checkTrigger(); + + this.moveLaser(); + + if (!this.pressed && this.grabbing) { + // release if trigger not pressed anymore. + this.release(); + } + + if (this.pressing) { + // Checking for attachments intersecting + var attachments = MyAvatar.getAttachmentData(); + var attachmentIndex = -1; + var attachmentX = LASER_LENGTH_FACTOR; + + var newModel; + var newProperties; + + for (var i = 0; i < attachments.length; ++i) { + var position = Vec3.sum(MyAvatar.getJointPosition(attachments[i].jointName), + Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[i].jointName), attachments[i].translation)); + var scale = attachments[i].scale; + + var A = this.palmPosition; + var B = this.front; + var P = position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + if (d < scale / 2.0 && 0 < x && x < attachmentX) { + attachmentIndex = i; + attachmentX = d; + } + } + + if (attachmentIndex != -1) { + print("Detaching: " + attachments[attachmentIndex].modelURL); + MyAvatar.detachOne(attachments[attachmentIndex].modelURL, attachments[attachmentIndex].jointName); + + newProperties = { + type: "Model", + position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName), + Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)), + rotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), + attachments[attachmentIndex].rotation), + + // TODO: how do we know the correct dimensions for detachment??? + dimensions: { x: attachments[attachmentIndex].scale / 2.0, + y: attachments[attachmentIndex].scale / 2.0, + z: attachments[attachmentIndex].scale / 2.0 }, + + modelURL: attachments[attachmentIndex].modelURL + }; + + newModel = Entities.addEntity(newProperties); + + + } else { + // There is none so ... + // Checking model tree + Vec3.print("Looking at: ", this.palmPosition); + var pickRay = { origin: this.palmPosition, + direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; + var foundIntersection = Entities.findRayIntersection(pickRay); + + if(!foundIntersection.accurate) { + print("No accurate intersection"); + return; + } + newModel = foundIntersection.entityID; + if (!newModel.isKnownID) { + var identify = Entities.identifyEntity(newModel); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + newModel.id + ")"); + return; + } + newModel = identify; + } + newProperties = Entities.getEntityProperties(newModel); + } + print("foundEntity.modelURL=" + newProperties.modelURL); + var check = this.checkEntity(newProperties); + if (!check.valid) { + return; + } + + this.grab(newModel, newProperties); + + this.x = check.x; + this.y = check.y; + this.z = check.z; + return; + } + } + + this.cleanup = function () { + Overlays.deleteOverlay(this.laser); + Overlays.deleteOverlay(this.ball); + Overlays.deleteOverlay(this.leftRight); + Overlays.deleteOverlay(this.topDown); + } +} + +var leftController = new controller(LEFT); +var rightController = new controller(RIGHT); + +function moveEntities() { + if (leftController.grabbing && rightController.grabbing && rightController.entityID.id == leftController.entityID.id) { + var newPosition = leftController.oldModelPosition; + var rotation = leftController.oldModelRotation; + var ratio = 1; + + + switch (mode) { + case 0: + var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); + var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); + + var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); + var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); + + + var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); + var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); + + var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); + var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); + + + ratio = length / oldLength; + newPosition = Vec3.sum(middle, + Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); + break; + case 1: + var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition)); + var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition)); + + var cos_theta = Vec3.dot(u, v); + if (cos_theta > 1) { + cos_theta = 1; + } + var angle = Math.acos(cos_theta) / Math.PI * 180; + if (angle < 0.1) { + return; + + } + var w = Vec3.normalize(Vec3.cross(u, v)); + + rotation = Quat.multiply(Quat.angleAxis(angle, w), leftController.oldModelRotation); + + + leftController.positionAtGrab = leftController.palmPosition; + leftController.rotationAtGrab = leftController.rotation; + leftController.modelPositionAtGrab = leftController.oldModelPosition; + leftController.rotationAtGrab = rotation; + rightController.positionAtGrab = rightController.palmPosition; + rightController.rotationAtGrab = rightController.rotation; + rightController.modelPositionAtGrab = rightController.oldModelPosition; + rightController.rotationAtGrab = rotation; + break; + } + Entities.editEntity(leftController.entityID, { + position: newPosition, + rotation: rotation, + // TODO: how do we know the correct dimensions for detachment??? + //radius: leftController.oldModelHalfDiagonal * ratio + dimensions: { x: leftController.oldModelHalfDiagonal * ratio, + y: leftController.oldModelHalfDiagonal * ratio, + z: leftController.oldModelHalfDiagonal * ratio } + + + }); + leftController.oldModelPosition = newPosition; + leftController.oldModelRotation = rotation; + leftController.oldModelHalfDiagonal *= ratio; + + rightController.oldModelPosition = newPosition; + rightController.oldModelRotation = rotation; + rightController.oldModelHalfDiagonal *= ratio; + return; + } + leftController.moveEntity(); + rightController.moveEntity(); +} + +var hydraConnected = false; +function checkController(deltaTime) { + var numberOfButtons = Controller.getNumberOfButtons(); + var numberOfTriggers = Controller.getNumberOfTriggers(); + var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); + var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + + // this is expected for hydras + if (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2) { + if (!hydraConnected) { + hydraConnected = true; + } + + leftController.update(); + rightController.update(); + moveEntities(); + } else { + if (hydraConnected) { + hydraConnected = false; + + leftController.showLaser(false); + rightController.showLaser(false); + } + } +} + +var glowedEntityID = { id: -1, isKnownID: false }; + +// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already +// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that +// added it. +var modelMenuAddedDelete = false; +var originalLightsArePickable = Entities.getLightsArePickable(); +function setupModelMenus() { + print("setupModelMenus()"); + // adj our menuitems + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...", + shortcutKeyEvent: { text: "`" }, afterItem: "Models" }); + if (!Menu.menuItemExists("Edit", "Delete")) { + print("no delete... adding ours"); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", + shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" }); + modelMenuAddedDelete = true; + } else { + print("delete exists... don't add ours"); + } + + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", + afterItem: "Paste Models", isCheckable: true }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S", + afterItem: "Allow Selecting of Large Models", isCheckable: true }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L", + afterItem: "Allow Selecting of Small Models", isCheckable: true }); + + Entities.setLightsArePickable(false); +} + +function cleanupModelMenus() { + Menu.removeMenuItem("Edit", "Edit Properties..."); + if (modelMenuAddedDelete) { + // delete our menuitems + Menu.removeMenuItem("Edit", "Delete"); + } + + Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); + +} + +function scriptEnding() { + leftController.cleanup(); + rightController.cleanup(); + cleanupModelMenus(); + Entities.setLightsArePickable(originalLightsArePickable); +} +Script.scriptEnding.connect(scriptEnding); + +// register the call back so it fires before each data send +Script.update.connect(checkController); + +setupModelMenus(); + +var editModelID = -1; +function showPropertiesForm(editModelID) { + entityPropertyDialogBox.openDialog(editModelID); +} + +Menu.menuItemEvent.connect(function (menuItem) { + print("menuItemEvent() in JS... menuItem=" + menuItem); + if (menuItem == "Allow Selecting of Small Models") { + allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); + } else if (menuItem == "Allow Selecting of Large Models") { + allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models"); + } else if (menuItem == "Allow Selecting of Lights") { + Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); + } else if (menuItem == "Delete") { + if (leftController.grabbing) { + print(" Delete Entity.... leftController.entityID="+ leftController.entityID); + Entities.deleteEntity(leftController.entityID); + leftController.grabbing = false; + if (glowedEntityID.id == leftController.entityID.id) { + glowedEntityID = { id: -1, isKnownID: false }; + } + } else if (rightController.grabbing) { + print(" Delete Entity.... rightController.entityID="+ rightController.entityID); + Entities.deleteEntity(rightController.entityID); + rightController.grabbing = false; + if (glowedEntityID.id == rightController.entityID.id) { + glowedEntityID = { id: -1, isKnownID: false }; + } + } else { + print(" Delete Entity.... not holding..."); + } + } else if (menuItem == "Edit Properties...") { + editModelID = -1; + if (leftController.grabbing) { + print(" Edit Properties.... leftController.entityID="+ leftController.entityID); + editModelID = leftController.entityID; + } else if (rightController.grabbing) { + print(" Edit Properties.... rightController.entityID="+ rightController.entityID); + editModelID = rightController.entityID; + } else { + print(" Edit Properties.... not holding..."); + } + if (editModelID != -1) { + print(" Edit Properties.... about to edit properties..."); + showPropertiesForm(editModelID); + } + } +}); + +Controller.keyReleaseEvent.connect(function (event) { + // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items + if (event.text == "`") { + handeMenuEvent("Edit Properties..."); + } + if (event.text == "BACKSPACE") { + handeMenuEvent("Delete"); + } +}); + diff --git a/examples/hydraMove.js b/examples/controllers/hydra/hydraMove.js similarity index 100% rename from examples/hydraMove.js rename to examples/controllers/hydra/hydraMove.js diff --git a/examples/controllers/hydra/squeezeHands.js b/examples/controllers/hydra/squeezeHands.js index 84e5aefb51..2a4756f017 100644 --- a/examples/controllers/hydra/squeezeHands.js +++ b/examples/controllers/hydra/squeezeHands.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var rightHandAnimation = HIFI_PUBLIC_BUCKET + "animations/RightHandAnimPhilip.fbx"; var leftHandAnimation = HIFI_PUBLIC_BUCKET + "animations/LeftHandAnimPhilip.fbx"; diff --git a/examples/controllers/hydra/toyball.js b/examples/controllers/hydra/toyball.js index 4dc65703b7..b2ce6c1463 100644 --- a/examples/controllers/hydra/toyball.js +++ b/examples/controllers/hydra/toyball.js @@ -15,7 +15,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; // maybe we should make these constants... var LEFT_PALM = 0; diff --git a/examples/controllers/oculus/virtualKeyboard.js b/examples/controllers/oculus/virtualKeyboard.js index dc3c2eb3cc..d17b36ae4f 100644 --- a/examples/controllers/oculus/virtualKeyboard.js +++ b/examples/controllers/oculus/virtualKeyboard.js @@ -15,7 +15,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; const KBD_UPPERCASE_DEFAULT = 0; const KBD_LOWERCASE_DEFAULT = 1; diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index cdb8e76c65..a14958dd23 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -11,7 +11,7 @@ Script.load("lookWithTouch.js"); Script.load("editEntities.js"); Script.load("selectAudioDevice.js"); -Script.load("hydraMove.js"); +Script.load("controllers/hydra/hydraMove.js"); Script.load("headMove.js"); Script.load("inspect.js"); Script.load("lobby.js"); diff --git a/examples/editEntities.js b/examples/editEntities.js index b65756e9b4..0d9c4f68c6 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; Script.include("libraries/stringHelpers.js"); Script.include("libraries/dataviewHelpers.js"); Script.include("libraries/httpMultiPart.js"); @@ -527,8 +527,15 @@ function mousePressEvent(event) { var highlightedEntityID = { isKnownID: false }; var mouseCapturedByTool = false; +var lastMousePosition = null; +var idleMouseTimerId = null; +var IDLE_MOUSE_TIMEOUT = 200; function mouseMoveEvent(event) { + if (idleMouseTimerId) { + Script.clearTimeout(idleMouseTimerId); + } + mouseHasMovedSincePress = true; if (isActive) { // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing @@ -536,36 +543,48 @@ function mouseMoveEvent(event) { return; } - var pickRay = Camera.computePickRay(event.x, event.y); - var entityIntersection = Entities.findRayIntersection(pickRay); - if (entityIntersection.accurate) { - if(highlightedEntityID.isKnownID && highlightedEntityID.id != entityIntersection.entityID.id) { - selectionDisplay.unhighlightSelectable(highlightedEntityID); - highlightedEntityID = { id: -1, isKnownID: false }; - } + lastMousePosition = { x: event.x, y: event.y }; - var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), - entityIntersection.properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (entityIntersection.entityID.isKnownID && sizeOK) { - if (wantEntityGlow) { - Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); - } - highlightedEntityID = entityIntersection.entityID; - selectionDisplay.highlightSelectable(entityIntersection.entityID); - } - - } + highlightEntityUnderCursor(lastMousePosition, false); + idleMouseTimerId = Script.setTimeout(handleIdleMouse, IDLE_MOUSE_TIMEOUT); } else { cameraManager.mouseMoveEvent(event); } } +function handleIdleMouse() { + idleMouseTimerId = null; + highlightEntityUnderCursor(lastMousePosition, true); +} + +function highlightEntityUnderCursor(position, accurateRay) { + var pickRay = Camera.computePickRay(position.x, position.y); + var entityIntersection = Entities.findRayIntersection(pickRay, accurateRay); + if (entityIntersection.accurate) { + if(highlightedEntityID.isKnownID && highlightedEntityID.id != entityIntersection.entityID.id) { + selectionDisplay.unhighlightSelectable(highlightedEntityID); + highlightedEntityID = { id: -1, isKnownID: false }; + } + + var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), + entityIntersection.properties.position)) * 180 / 3.14; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) + && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (entityIntersection.entityID.isKnownID && sizeOK) { + if (wantEntityGlow) { + Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); + } + highlightedEntityID = entityIntersection.entityID; + selectionDisplay.highlightSelectable(entityIntersection.entityID); + } + + } +} + function mouseReleaseEvent(event) { if (isActive && selectionManager.hasSelection()) { @@ -662,8 +681,6 @@ function setupModelMenus() { print("setupModelMenus()"); // adj our menuitems Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...", - shortcutKeyEvent: { text: "`" }, afterItem: "Models" }); if (!Menu.menuItemExists("Edit", "Delete")) { print("no delete... adding ours"); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", @@ -674,7 +691,7 @@ function setupModelMenus() { } Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Model List..." }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", afterItem: "Paste Models", isCheckable: true, isChecked: true }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S", @@ -696,7 +713,6 @@ setupModelMenus(); // do this when first running our script. function cleanupModelMenus() { Menu.removeSeparator("Edit", "Models"); - Menu.removeMenuItem("Edit", "Edit Properties..."); if (modelMenuAddedDelete) { // delete our menuitems Menu.removeMenuItem("Edit", "Delete"); @@ -798,22 +814,6 @@ function handeMenuEvent(menuItem) { MyAvatar.position = selectedModel.properties.position; } } - } else if (menuItem == "Edit Properties...") { - // good place to put the properties dialog - - editModelID = -1; - if (selectionManager.selections.length == 1) { - print(" Edit Properties.... selectedEntityID="+ selectedEntityID); - editModelID = selectionManager.selections[0]; - } else { - print(" Edit Properties.... not holding..."); - } - if (editModelID != -1) { - print(" Edit Properties.... about to edit properties..."); - entityPropertyDialogBox.openDialog(editModelID); - selectionManager._update(); - } - } else if (menuItem == "Paste Models") { modelImporter.paste(); } else if (menuItem == "Export Models") { @@ -841,9 +841,6 @@ Controller.keyPressEvent.connect(function(event) { Controller.keyReleaseEvent.connect(function (event) { // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items - if (event.text == "`") { - handeMenuEvent("Edit Properties..."); - } if (event.text == "BACKSPACE" || event.text == "DELETE") { handeMenuEvent("Delete"); } else if (event.text == "TAB") { @@ -1077,6 +1074,19 @@ PropertiesTool = function(opts) { pushCommandForSelections(); selectionManager._update(); } + } else if (data.action == "rescaleDimensions") { + var multiplier = data.percentage / 100; + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (var i = 0; i < selectionManager.selections.length; i++) { + var properties = selectionManager.savedProperties[selectionManager.selections[i].id]; + Entities.editEntity(selectionManager.selections[i], { + dimensions: Vec3.multiply(multiplier, properties.dimensions), + }); + } + pushCommandForSelections(); + selectionManager._update(); + } } } }); diff --git a/examples/editModels.js b/examples/editModels.js deleted file mode 100644 index 7cb911b490..0000000000 --- a/examples/editModels.js +++ /dev/null @@ -1,3041 +0,0 @@ -// -// editEntities.js -// examples -// -// Created by Clément Brisset on 4/24/14. -// Copyright 2014 High Fidelity, Inc. -// -// This script allows you to edit models either with the razor hydras or with your mouse -// -// If using the hydras : -// grab grab models with the triggers, you can then move the models around or scale them with both hands. -// You can switch mode using the bumpers so that you can move models around more easily. -// -// If using the mouse : -// - left click lets you move the model in the plane facing you. -// If pressing shift, it will move on the horizontal plane it's in. -// - right click lets you rotate the model. z and x give access to more axes of rotation while shift provides finer control. -// - left + right click lets you scale the model. -// - you can press r while holding the model to reset its rotation -// -// 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("libraries/globals.js"); -Script.include("libraries/toolBars.js"); - -Script.include("libraries/entityPropertyDialogBox.js"); -var entityPropertyDialogBox = EntityPropertyDialogBox; - -var windowDimensions = Controller.getViewportDimensions(); -var toolIconUrl = HIFI_PUBLIC_BUCKET + "images/tools/"; -var toolHeight = 50; -var toolWidth = 50; - -var LASER_WIDTH = 4; -var LASER_COLOR = { red: 255, green: 0, blue: 0 }; -var LASER_LENGTH_FACTOR = 500; - -var MIN_ANGULAR_SIZE = 2; -var MAX_ANGULAR_SIZE = 45; -var allowLargeModels = false; -var allowSmallModels = false; -var wantEntityGlow = false; - -var LEFT = 0; -var RIGHT = 1; - -var SPAWN_DISTANCE = 1; -var DEFAULT_DIMENSION = 0.20; -var DEFAULT_TEXT_DIMENSION_X = 1.0; -var DEFAULT_TEXT_DIMENSION_Y = 1.0; -var DEFAULT_TEXT_DIMENSION_Z = 0.01; - -var modelURLs = [ - HIFI_PUBLIC_BUCKET + "models/entities/2-Terrain:%20Alder.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/2-Terrain:%20Bush1.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/2-Terrain:%20Bush6.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed2.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed4.fbx", - HIFI_PUBLIC_BUCKET + "models/entities/3-Buildings-1-Rustic-Shed7.fbx" - ]; - -var jointList = MyAvatar.getJointNames(); - -var mode = 0; -var isActive = false; - - -if (typeof String.prototype.fileName !== "function") { - String.prototype.fileName = function () { - return this.replace(/^(.*[\/\\])*/, ""); - }; -} - -if (typeof String.prototype.fileBase !== "function") { - String.prototype.fileBase = function () { - var filename = this.fileName(); - return filename.slice(0, filename.indexOf(".")); - }; -} - -if (typeof String.prototype.fileType !== "function") { - String.prototype.fileType = function () { - return this.slice(this.lastIndexOf(".") + 1); - }; -} - -if (typeof String.prototype.path !== "function") { - String.prototype.path = function () { - return this.replace(/[\\\/][^\\\/]*$/, ""); - }; -} - -if (typeof String.prototype.regExpEscape !== "function") { - String.prototype.regExpEscape = function () { - return this.replace(/([$\^.+*?|\\\/{}()\[\]])/g, '\\$1'); - }; -} - -if (typeof String.prototype.toArrayBuffer !== "function") { - String.prototype.toArrayBuffer = function () { - var length, - buffer, - view, - charCode, - charCodes, - i; - - charCodes = []; - - length = this.length; - for (i = 0; i < length; i += 1) { - charCode = this.charCodeAt(i); - if (charCode <= 255) { - charCodes.push(charCode); - } else { - charCodes.push(charCode / 256); - charCodes.push(charCode % 256); - } - } - - length = charCodes.length; - buffer = new ArrayBuffer(length); - view = new Uint8Array(buffer); - for (i = 0; i < length; i += 1) { - view[i] = charCodes[i]; - } - - return buffer; - }; -} - -if (typeof DataView.prototype.indexOf !== "function") { - DataView.prototype.indexOf = function (searchString, position) { - var searchLength = searchString.length, - byteArrayLength = this.byteLength, - maxSearchIndex = byteArrayLength - searchLength, - searchCharCodes = [], - found, - i, - j; - - searchCharCodes[searchLength] = 0; - for (j = 0; j < searchLength; j += 1) { - searchCharCodes[j] = searchString.charCodeAt(j); - } - - i = position; - found = false; - while (i < maxSearchIndex && !found) { - j = 0; - while (j < searchLength && this.getUint8(i + j) === searchCharCodes[j]) { - j += 1; - } - found = (j === searchLength); - i += 1; - } - - return found ? i - 1 : -1; - }; -} - -if (typeof DataView.prototype.string !== "function") { - DataView.prototype.string = function (start, length) { - var charCodes = [], - end, - i; - - if (start === undefined) { - start = 0; - } - if (length === undefined) { - length = this.length; - } - - end = start + length; - for (i = start; i < end; i += 1) { - charCodes.push(this.getUint8(i)); - } - - return String.fromCharCode.apply(String, charCodes); - }; -} - -var progressDialog = (function () { - var that = {}, - progressBackground, - progressMessage, - cancelButton, - displayed = false, - backgroundWidth = 300, - backgroundHeight = 100, - messageHeight = 32, - cancelWidth = 70, - cancelHeight = 32, - textColor = { red: 255, green: 255, blue: 255 }, - textBackground = { red: 52, green: 52, blue: 52 }, - backgroundUrl = toolIconUrl + "progress-background.svg", - windowDimensions; - - progressBackground = Overlays.addOverlay("image", { - width: backgroundWidth, - height: backgroundHeight, - imageURL: backgroundUrl, - alpha: 0.9, - backgroundAlpha: 0.9, - visible: false - }); - - progressMessage = Overlays.addOverlay("text", { - width: backgroundWidth - 40, - height: messageHeight, - text: "", - textColor: textColor, - backgroundColor: textBackground, - alpha: 0.9, - backgroundAlpha: 0.9, - visible: false - }); - - cancelButton = Overlays.addOverlay("text", { - width: cancelWidth, - height: cancelHeight, - text: "Cancel", - textColor: textColor, - backgroundColor: textBackground, - alpha: 0.9, - backgroundAlpha: 0.9, - visible: false - }); - - function move() { - var progressX, - progressY; - - if (displayed) { - - if (windowDimensions.x === Window.innerWidth && windowDimensions.y === Window.innerHeight) { - return; - } - windowDimensions.x = Window.innerWidth; - windowDimensions.y = Window.innerHeight; - - progressX = (windowDimensions.x - backgroundWidth) / 2; // Center. - progressY = windowDimensions.y / 2 - backgroundHeight; // A little up from center. - - Overlays.editOverlay(progressBackground, { x: progressX, y: progressY }); - Overlays.editOverlay(progressMessage, { x: progressX + 20, y: progressY + 15 }); - Overlays.editOverlay(cancelButton, { - x: progressX + backgroundWidth - cancelWidth - 20, - y: progressY + backgroundHeight - cancelHeight - 15 - }); - } - } - that.move = move; - - that.onCancel = undefined; - - function open(message) { - if (!displayed) { - windowDimensions = { x: 0, y : 0 }; - displayed = true; - move(); - Overlays.editOverlay(progressBackground, { visible: true }); - Overlays.editOverlay(progressMessage, { visible: true, text: message }); - Overlays.editOverlay(cancelButton, { visible: true }); - } else { - throw new Error("open() called on progressDialog when already open"); - } - } - that.open = open; - - function isOpen() { - return displayed; - } - that.isOpen = isOpen; - - function update(message) { - if (displayed) { - Overlays.editOverlay(progressMessage, { text: message }); - } else { - throw new Error("update() called on progressDialog when not open"); - } - } - that.update = update; - - function close() { - if (displayed) { - Overlays.editOverlay(cancelButton, { visible: false }); - Overlays.editOverlay(progressMessage, { visible: false }); - Overlays.editOverlay(progressBackground, { visible: false }); - displayed = false; - } else { - throw new Error("close() called on progressDialog when not open"); - } - } - that.close = close; - - function mousePressEvent(event) { - if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === cancelButton) { - if (typeof this.onCancel === "function") { - close(); - this.onCancel(); - } - return true; - } - return false; - } - that.mousePressEvent = mousePressEvent; - - function cleanup() { - Overlays.deleteOverlay(cancelButton); - Overlays.deleteOverlay(progressMessage); - Overlays.deleteOverlay(progressBackground); - } - that.cleanup = cleanup; - - return that; -}()); - -var httpMultiPart = (function () { - var that = {}, - parts, - byteLength, - boundaryString, - crlf; - - function clear() { - boundaryString = "--boundary_" + String(Uuid.generate()).slice(1, 36) + "="; - parts = []; - byteLength = 0; - crlf = ""; - } - that.clear = clear; - - function boundary() { - return boundaryString.slice(2); - } - that.boundary = boundary; - - function length() { - return byteLength; - } - that.length = length; - - function add(object) { - // - name, string - // - name, buffer - var buffer, - string, - stringBuffer, - compressedBuffer; - - if (object.name === undefined) { - - throw new Error("Item to add to HttpMultiPart must have a name"); - - } else if (object.string !== undefined) { - //--= - //Content-Disposition: form-data; name="model_name" - // - // - - string = crlf + boundaryString + "\r\n" - + "Content-Disposition: form-data; name=\"" + object.name + "\"\r\n" - + "\r\n" - + object.string; - buffer = string.toArrayBuffer(); - - } else if (object.buffer !== undefined) { - //--= - //Content-Disposition: form-data; name="fbx"; filename="" - //Content-Type: application/octet-stream - // - // - - string = crlf + boundaryString + "\r\n" - + "Content-Disposition: form-data; name=\"" + object.name - + "\"; filename=\"" + object.buffer.filename + "\"\r\n" - + "Content-Type: application/octet-stream\r\n" - + "\r\n"; - stringBuffer = string.toArrayBuffer(); - - compressedBuffer = object.buffer.buffer.compress(); - buffer = new Uint8Array(stringBuffer.byteLength + compressedBuffer.byteLength); - buffer.set(new Uint8Array(stringBuffer)); - buffer.set(new Uint8Array(compressedBuffer), stringBuffer.byteLength); - - } else { - - throw new Error("Item to add to HttpMultiPart not recognized"); - } - - byteLength += buffer.byteLength; - parts.push(buffer); - - crlf = "\r\n"; - - return true; - } - that.add = add; - - function response() { - var buffer, - index, - str, - i; - - str = crlf + boundaryString + "--\r\n"; - buffer = str.toArrayBuffer(); - byteLength += buffer.byteLength; - parts.push(buffer); - - buffer = new Uint8Array(byteLength); - index = 0; - for (i = 0; i < parts.length; i += 1) { - buffer.set(new Uint8Array(parts[i]), index); - index += parts[i].byteLength; - } - - return buffer; - } - that.response = response; - - clear(); - - return that; -}()); - -var modelUploader = (function () { - var that = {}, - modelFile, - modelName, - modelURL, - modelCallback, - isProcessing, - fstBuffer, - fbxBuffer, - //svoBuffer, - mapping, - geometry, - API_URL = "https://data.highfidelity.io/api/v1/models", - MODEL_URL = "http://public.highfidelity.io/models/content", - NAME_FIELD = "name", - SCALE_FIELD = "scale", - FILENAME_FIELD = "filename", - TEXDIR_FIELD = "texdir", - MAX_TEXTURE_SIZE = 1024; - - function info(message) { - if (progressDialog.isOpen()) { - progressDialog.update(message); - } else { - progressDialog.open(message); - } - print(message); - } - - function error(message) { - if (progressDialog.isOpen()) { - progressDialog.close(); - } - print(message); - Window.alert(message); - } - - function randomChar(length) { - var characters = "0123457689abcdefghijklmnopqrstuvwxyz", - string = "", - i; - - for (i = 0; i < length; i += 1) { - string += characters[Math.floor(Math.random() * 36)]; - } - - return string; - } - - function resetDataObjects() { - fstBuffer = null; - fbxBuffer = null; - //svoBuffer = null; - mapping = {}; - geometry = {}; - geometry.textures = []; - geometry.embedded = []; - } - - function readFile(filename) { - var url = "file:///" + filename, - req = new XMLHttpRequest(); - - req.open("GET", url, false); - req.responseType = "arraybuffer"; - req.send(); - if (req.status !== 200) { - error("Could not read file: " + filename + " : " + req.statusText); - return null; - } - - return { - filename: filename.fileName(), - buffer: req.response - }; - } - - function readMapping(buffer) { - var dv = new DataView(buffer.buffer), - lines, - line, - tokens, - i, - name, - value, - remainder, - existing; - - mapping = {}; // { name : value | name : { value : [remainder] } } - lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/); - for (i = 0; i < lines.length; i += 1) { - line = lines[i].trim(); - if (line.length > 0 && line[0] !== "#") { - tokens = line.split(/\s*=\s*/); - if (tokens.length > 1) { - name = tokens[0]; - value = tokens[1]; - if (tokens.length > 2) { - remainder = tokens.slice(2, tokens.length).join(" = "); - } else { - remainder = null; - } - if (tokens.length === 2 && mapping[name] === undefined) { - mapping[name] = value; - } else { - if (mapping[name] === undefined) { - mapping[name] = {}; - - } else if (typeof mapping[name] !== "object") { - existing = mapping[name]; - mapping[name] = { existing : null }; - } - - if (mapping[name][value] === undefined) { - mapping[name][value] = []; - } - mapping[name][value].push(remainder); - } - } - } - } - } - - function writeMapping(buffer) { - var name, - value, - remainder, - i, - string = ""; - - for (name in mapping) { - if (mapping.hasOwnProperty(name)) { - if (typeof mapping[name] === "object") { - for (value in mapping[name]) { - if (mapping[name].hasOwnProperty(value)) { - remainder = mapping[name][value]; - if (remainder === null) { - string += (name + " = " + value + "\n"); - } else { - for (i = 0; i < remainder.length; i += 1) { - string += (name + " = " + value + " = " + remainder[i] + "\n"); - } - } - } - } - } else { - string += (name + " = " + mapping[name] + "\n"); - } - } - } - - buffer.buffer = string.toArrayBuffer(); - } - - function readGeometry(fbxBuffer) { - var textures, - view, - index, - EOF, - previousNodeFilename; - - // Reference: - // http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ - - textures = {}; - view = new DataView(fbxBuffer.buffer); - EOF = false; - - function parseBinaryFBX() { - var endOffset, - numProperties, - propertyListLength, - nameLength, - name, - filename; - - endOffset = view.getUint32(index, true); - numProperties = view.getUint32(index + 4, true); - propertyListLength = view.getUint32(index + 8, true); - nameLength = view.getUint8(index + 12); - index += 13; - - if (endOffset === 0) { - return; - } - if (endOffset < index || endOffset > view.byteLength) { - EOF = true; - return; - } - - name = view.string(index, nameLength).toLowerCase(); - index += nameLength; - - if (name === "content" && previousNodeFilename !== "") { - // Blender 2.71 exporter "embeds" external textures as empty binary blobs so ignore these - if (propertyListLength > 5) { - geometry.embedded.push(previousNodeFilename); - } - } - - if (name === "relativefilename") { - filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName(); - if (!textures.hasOwnProperty(filename)) { - textures[filename] = ""; - geometry.textures.push(filename); - } - previousNodeFilename = filename; - } else { - previousNodeFilename = ""; - } - - index += (propertyListLength); - - while (index < endOffset && !EOF) { - parseBinaryFBX(); - } - } - - function readTextFBX() { - var line, - view, - viewLength, - charCode, - charCodes, - numCharCodes, - filename, - relativeFilename = "", - MAX_CHAR_CODES = 250; - - view = new Uint8Array(fbxBuffer.buffer); - viewLength = view.byteLength; - charCodes = []; - numCharCodes = 0; - - for (index = 0; index < viewLength; index += 1) { - charCode = view[index]; - if (charCode !== 9 && charCode !== 32) { - if (charCode === 10) { // EOL. Can ignore EOF. - line = String.fromCharCode.apply(String, charCodes).toLowerCase(); - // For embedded textures, "Content:" line immediately follows "RelativeFilename:" line. - if (line.slice(0, 8) === "content:" && relativeFilename !== "") { - geometry.embedded.push(relativeFilename); - } - if (line.slice(0, 17) === "relativefilename:") { - filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); - if (!textures.hasOwnProperty(filename)) { - textures[filename] = ""; - geometry.textures.push(filename); - } - relativeFilename = filename; - } else { - relativeFilename = ""; - } - charCodes = []; - numCharCodes = 0; - } else { - if (numCharCodes < MAX_CHAR_CODES) { // Only interested in start of line - charCodes.push(charCode); - numCharCodes += 1; - } - } - } - } - } - - if (view.string(0, 18) === "Kaydara FBX Binary") { - previousNodeFilename = ""; - - index = 27; - while (index < view.byteLength - 39 && !EOF) { - parseBinaryFBX(); - } - - } else { - - readTextFBX(); - - } - } - - function readModel() { - var fbxFilename, - //svoFilename, - fileType; - - info("Reading model file"); - print("Model file: " + modelFile); - - if (modelFile.toLowerCase().fileType() === "fst") { - fstBuffer = readFile(modelFile); - if (fstBuffer === null) { - return false; - } - readMapping(fstBuffer); - fileType = mapping[FILENAME_FIELD].toLowerCase().fileType(); - if (mapping.hasOwnProperty(FILENAME_FIELD)) { - if (fileType === "fbx") { - fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; - //} else if (fileType === "svo") { - // svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; - } else { - error("Unrecognized model type in FST file!"); - return false; - } - } else { - error("Model file name not found in FST file!"); - return false; - } - } else { - fstBuffer = { - filename: "Interface." + randomChar(6), // Simulate avatar model uploading behaviour - buffer: null - }; - - if (modelFile.toLowerCase().fileType() === "fbx") { - fbxFilename = modelFile; - mapping[FILENAME_FIELD] = modelFile.fileName(); - - //} else if (modelFile.toLowerCase().fileType() === "svo") { - // svoFilename = modelFile; - // mapping[FILENAME_FIELD] = modelFile.fileName(); - - } else { - error("Unrecognized file type: " + modelFile); - return false; - } - } - - if (!isProcessing) { return false; } - - if (fbxFilename) { - fbxBuffer = readFile(fbxFilename); - if (fbxBuffer === null) { - return false; - } - - if (!isProcessing) { return false; } - - readGeometry(fbxBuffer); - } - - //if (svoFilename) { - // svoBuffer = readFile(svoFilename); - // if (svoBuffer === null) { - // return false; - // } - //} - - // Add any missing basic mappings - if (!mapping.hasOwnProperty(NAME_FIELD)) { - mapping[NAME_FIELD] = modelFile.fileName().fileBase(); - } - if (!mapping.hasOwnProperty(TEXDIR_FIELD)) { - mapping[TEXDIR_FIELD] = "."; - } - if (!mapping.hasOwnProperty(SCALE_FIELD)) { - mapping[SCALE_FIELD] = 1.0; - } - - return true; - } - - function setProperties() { - var form = [], - directory, - displayAs, - validateAs; - - progressDialog.close(); - print("Setting model properties"); - - form.push({ label: "Name:", value: mapping[NAME_FIELD] }); - - directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD]; - displayAs = new RegExp("^" + modelFile.path().regExpEscape() + "[\\\\\\\/](.*)"); - validateAs = new RegExp("^" + modelFile.path().regExpEscape() + "([\\\\\\\/].*)?"); - - form.push({ - label: "Texture directory:", - directory: modelFile.path() + "/" + mapping[TEXDIR_FIELD], - title: "Choose Texture Directory", - displayAs: displayAs, - validateAs: validateAs, - errorMessage: "Texture directory must be subdirectory of the model directory." - }); - - form.push({ button: "Cancel" }); - - if (!Window.form("Set Model Properties", form)) { - print("User cancelled uploading model"); - return false; - } - - mapping[NAME_FIELD] = form[0].value; - mapping[TEXDIR_FIELD] = form[1].directory.slice(modelFile.path().length + 1); - if (mapping[TEXDIR_FIELD] === "") { - mapping[TEXDIR_FIELD] = "."; - } - - writeMapping(fstBuffer); - - return true; - } - - function createHttpMessage(callback) { - var multiparts = [], - lodCount, - lodFile, - lodBuffer, - textureBuffer, - textureSourceFormat, - textureTargetFormat, - embeddedTextures, - i; - - info("Preparing to send model"); - - // Model name - if (mapping.hasOwnProperty(NAME_FIELD)) { - multiparts.push({ - name : "model_name", - string : mapping[NAME_FIELD] - }); - } else { - error("Model name is missing"); - httpMultiPart.clear(); - return; - } - - // FST file - if (fstBuffer) { - multiparts.push({ - name : "fst", - buffer: fstBuffer - }); - } - - // FBX file - if (fbxBuffer) { - multiparts.push({ - name : "fbx", - buffer: fbxBuffer - }); - } - - // SVO file - //if (svoBuffer) { - // multiparts.push({ - // name : "svo", - // buffer: svoBuffer - // }); - //} - - // LOD files - lodCount = 0; - for (lodFile in mapping.lod) { - if (mapping.lod.hasOwnProperty(lodFile)) { - lodBuffer = readFile(modelFile.path() + "\/" + lodFile); - if (lodBuffer === null) { - return; - } - multiparts.push({ - name: "lod" + lodCount, - buffer: lodBuffer - }); - lodCount += 1; - } - if (!isProcessing) { return; } - } - - // Textures - embeddedTextures = "|" + geometry.embedded.join("|") + "|"; - for (i = 0; i < geometry.textures.length; i += 1) { - if (embeddedTextures.indexOf("|" + geometry.textures[i].fileName() + "|") === -1) { - textureBuffer = readFile(modelFile.path() + "\/" - + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") - + geometry.textures[i]); - if (textureBuffer === null) { - return; - } - - textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); - textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png"); - textureBuffer.buffer = - textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE); - textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat; - - multiparts.push({ - name: "texture" + i, - buffer: textureBuffer - }); - } - - if (!isProcessing) { return; } - } - - // Model category - multiparts.push({ - name : "model_category", - string : "content" - }); - - // Create HTTP message - httpMultiPart.clear(); - Script.setTimeout(function addMultipart() { - var multipart = multiparts.shift(); - httpMultiPart.add(multipart); - - if (!isProcessing) { return; } - - if (multiparts.length > 0) { - Script.setTimeout(addMultipart, 25); - } else { - callback(); - } - }, 25); - } - - function sendToHighFidelity() { - var req, - uploadedChecks, - HTTP_GET_TIMEOUT = 60, // 1 minute - HTTP_SEND_TIMEOUT = 900, // 15 minutes - UPLOADED_CHECKS = 30, - CHECK_UPLOADED_TIMEOUT = 1, // 1 second - handleCheckUploadedResponses, - handleUploadModelResponses, - handleRequestUploadResponses; - - function uploadTimedOut() { - error("Model upload failed: Internet request timed out!"); - } - - function debugResponse() { - print("req.errorCode = " + req.errorCode); - print("req.readyState = " + req.readyState); - print("req.status = " + req.status); - print("req.statusText = " + req.statusText); - print("req.responseType = " + req.responseType); - print("req.responseText = " + req.responseText); - print("req.response = " + req.response); - print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders()); - } - - function checkUploaded() { - if (!isProcessing) { return; } - - info("Checking uploaded model"); - - req = new XMLHttpRequest(); - req.open("HEAD", modelURL, true); - req.timeout = HTTP_GET_TIMEOUT * 1000; - req.onreadystatechange = handleCheckUploadedResponses; - req.ontimeout = uploadTimedOut; - req.send(); - } - - handleCheckUploadedResponses = function () { - //debugResponse(); - if (req.readyState === req.DONE) { - if (req.status === 200) { - // Note: Unlike avatar models, for content models we don't need to refresh texture cache. - print("Model uploaded: " + modelURL); - progressDialog.close(); - if (Window.confirm("Your model has been uploaded as: " + modelURL + "\nDo you want to rez it?")) { - modelCallback(modelURL); - } - } else if (req.status === 404) { - if (uploadedChecks > 0) { - uploadedChecks -= 1; - Script.setTimeout(checkUploaded, CHECK_UPLOADED_TIMEOUT * 1000); - } else { - print("Error: " + req.status + " " + req.statusText); - error("We could not verify that your model was successfully uploaded but it may have been at: " - + modelURL); - } - } else { - print("Error: " + req.status + " " + req.statusText); - error("There was a problem with your upload, please try again later."); - } - } - }; - - function uploadModel(method) { - var url; - - if (!isProcessing) { return; } - - req = new XMLHttpRequest(); - if (method === "PUT") { - url = API_URL + "\/" + modelName; - req.open("PUT", url, true); //print("PUT " + url); - } else { - url = API_URL; - req.open("POST", url, true); //print("POST " + url); - } - req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\""); - req.timeout = HTTP_SEND_TIMEOUT * 1000; - req.onreadystatechange = handleUploadModelResponses; - req.ontimeout = uploadTimedOut; - req.send(httpMultiPart.response().buffer); - } - - handleUploadModelResponses = function () { - //debugResponse(); - if (req.readyState === req.DONE) { - if (req.status === 200) { - uploadedChecks = UPLOADED_CHECKS; - checkUploaded(); - } else { - print("Error: " + req.status + " " + req.statusText); - error("There was a problem with your upload, please try again later."); - } - } - }; - - function requestUpload() { - var url; - - if (!isProcessing) { return; } - - url = API_URL + "\/" + modelName; // XMLHttpRequest automatically handles authorization of API requests. - req = new XMLHttpRequest(); - req.open("GET", url, true); //print("GET " + url); - req.responseType = "json"; - req.timeout = HTTP_GET_TIMEOUT * 1000; - req.onreadystatechange = handleRequestUploadResponses; - req.ontimeout = uploadTimedOut; - req.send(); - } - - handleRequestUploadResponses = function () { - var response; - - //debugResponse(); - if (req.readyState === req.DONE) { - if (req.status === 200) { - if (req.responseType === "json") { - response = JSON.parse(req.responseText); - if (response.status === "success") { - if (response.exists === false) { - uploadModel("POST"); - } else if (response.can_update === true) { - uploadModel("PUT"); - } else { - error("This model file already exists and is owned by someone else!"); - } - return; - } - } - } else { - print("Error: " + req.status + " " + req.statusText); - } - error("Model upload failed! Something went wrong at the data server."); - } - }; - - info("Sending model to High Fidelity"); - - requestUpload(); - } - - that.upload = function (file, callback) { - - modelFile = file; - modelCallback = callback; - - isProcessing = true; - - progressDialog.onCancel = function () { - print("User cancelled uploading model"); - isProcessing = false; - }; - - resetDataObjects(); - - if (readModel()) { - if (setProperties()) { - modelName = mapping[NAME_FIELD]; - modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST - - createHttpMessage(sendToHighFidelity); - } - } - - resetDataObjects(); - }; - - return that; -}()); - -var toolBar = (function () { - var that = {}, - toolBar, - activeButton, - newModelButton, - newCubeButton, - newSphereButton, - newTextButton, - browseModelsButton, - loadURLMenuItem, - loadFileMenuItem, - menuItemWidth, - menuItemOffset, - menuItemHeight, - menuItemMargin = 5, - menuTextColor = { red: 255, green: 255, blue: 255 }, - menuBackgroundColor = { red: 18, green: 66, blue: 66 }; - - function initialize() { - toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); - - activeButton = toolBar.addTool({ - imageURL: toolIconUrl + "models-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }, true, false); - - newModelButton = toolBar.addTool({ - imageURL: toolIconUrl + "add-model-tool.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }, true, false); - - browseModelsButton = toolBar.addTool({ - imageURL: toolIconUrl + "list-icon.svg", - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }); - - menuItemOffset = toolBar.height / 3 + 2; - menuItemHeight = Tool.IMAGE_HEIGHT / 2 - 2; - - loadURLMenuItem = Overlays.addOverlay("text", { - height: menuItemHeight, - backgroundColor: menuBackgroundColor, - topMargin: menuItemMargin, - text: "Model URL", - alpha: 0.9, - backgroundAlpha: 0.9, - visible: false - }); - - loadFileMenuItem = Overlays.addOverlay("text", { - height: menuItemHeight, - backgroundColor: menuBackgroundColor, - topMargin: menuItemMargin, - text: "Model File", - alpha: 0.9, - backgroundAlpha: 0.9, - visible: false - }); - - menuItemWidth = Math.max(Overlays.textSize(loadURLMenuItem, "Model URL").width, - Overlays.textSize(loadFileMenuItem, "Model File").width) + 20; - Overlays.editOverlay(loadURLMenuItem, { width: menuItemWidth }); - Overlays.editOverlay(loadFileMenuItem, { width: menuItemWidth }); - - newCubeButton = toolBar.addTool({ - imageURL: toolIconUrl + "add-cube.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }); - - newSphereButton = toolBar.addTool({ - imageURL: toolIconUrl + "add-sphere.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }); - - newTextButton = toolBar.addTool({ - imageURL: toolIconUrl + "add-text.svg", - subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }); - } - - function toggleNewModelButton(active) { - if (active === undefined) { - active = !toolBar.toolSelected(newModelButton); - } - toolBar.selectTool(newModelButton, active); - - Overlays.editOverlay(loadURLMenuItem, { visible: active }); - Overlays.editOverlay(loadFileMenuItem, { visible: active }); - } - - var RESIZE_INTERVAL = 50; - var RESIZE_TIMEOUT = 20000; - var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL; - function addModel(url) { - var position; - - position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - - if (position.x > 0 && position.y > 0 && position.z > 0) { - var entityId = Entities.addEntity({ - type: "Model", - position: position, - dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, - modelURL: url - }); - print("Model added: " + url); - - var checkCount = 0; - function resize() { - var entityProperties = Entities.getEntityProperties(entityId); - var naturalDimensions = entityProperties.naturalDimensions; - - checkCount++; - - if (naturalDimensions.x == 0 && naturalDimensions.y == 0 && naturalDimensions.z == 0) { - if (checkCount < RESIZE_MAX_CHECKS) { - Script.setTimeout(resize, RESIZE_INTERVAL); - } else { - print("Resize failed: timed out waiting for model (" + url + ") to load"); - } - } else { - entityProperties.dimensions = naturalDimensions; - Entities.editEntity(entityId, entityProperties); - } - } - - Script.setTimeout(resize, RESIZE_INTERVAL); - - } else { - print("Can't add model: Model would be out of bounds."); - } - } - - that.move = function () { - var newViewPort, - toolsX, - toolsY; - - newViewPort = Controller.getViewportDimensions(); - - if (toolBar === undefined) { - initialize(); - - } else if (windowDimensions.x === newViewPort.x && - windowDimensions.y === newViewPort.y) { - return; - } - - windowDimensions = newViewPort; - toolsX = windowDimensions.x - 8 - toolBar.width; - toolsY = (windowDimensions.y - toolBar.height) / 2; - - toolBar.move(toolsX, toolsY); - - Overlays.editOverlay(loadURLMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset }); - Overlays.editOverlay(loadFileMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset + menuItemHeight }); - }; - - that.mousePressEvent = function (event) { - var clickedOverlay, - url, - file; - - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - - if (activeButton === toolBar.clicked(clickedOverlay)) { - isActive = !isActive; - return true; - } - - if (newModelButton === toolBar.clicked(clickedOverlay)) { - toggleNewModelButton(); - return true; - } - - if (clickedOverlay === loadURLMenuItem) { - toggleNewModelButton(false); - url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]); - if (url !== null && url !== "") { - addModel(url); - } - return true; - } - - if (clickedOverlay === loadFileMenuItem) { - toggleNewModelButton(false); - - file = Window.browse("Select your model file ...", - Settings.getValue("LastModelUploadLocation").path(), - "Model files (*.fst *.fbx)"); - //"Model files (*.fst *.fbx *.svo)"); - if (file !== null) { - Settings.setValue("LastModelUploadLocation", file); - modelUploader.upload(file, addModel); - } - return true; - } - - if (browseModelsButton === toolBar.clicked(clickedOverlay)) { - toggleNewModelButton(false); - url = Window.s3Browse(".*(fbx|FBX)"); - if (url !== null && url !== "") { - addModel(url); - } - return true; - } - - if (newCubeButton === toolBar.clicked(clickedOverlay)) { - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - - if (position.x > 0 && position.y > 0 && position.z > 0) { - Entities.addEntity({ - type: "Box", - position: position, - dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, - color: { red: 255, green: 0, blue: 0 } - - }); - } else { - print("Can't create box: Box would be out of bounds."); - } - return true; - } - - if (newSphereButton === toolBar.clicked(clickedOverlay)) { - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - - if (position.x > 0 && position.y > 0 && position.z > 0) { - Entities.addEntity({ - type: "Sphere", - position: position, - dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION }, - color: { red: 255, green: 0, blue: 0 } - }); - } else { - print("Can't create box: Box would be out of bounds."); - } - return true; - } - - - if (newTextButton === toolBar.clicked(clickedOverlay)) { - var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - - if (position.x > 0 && position.y > 0 && position.z > 0) { - Entities.addEntity({ - type: "Text", - position: position, - dimensions: { x: DEFAULT_TEXT_DIMENSION_X, y: DEFAULT_TEXT_DIMENSION_Y, z: DEFAULT_TEXT_DIMENSION_Z }, - backgroundColor: { red: 0, green: 0, blue: 0 }, - textColor: { red: 255, green: 255, blue: 255 }, - text: "some text", - lineHight: "0.1" - }); - } else { - print("Can't create box: Text would be out of bounds."); - } - return true; - } - - return false; - }; - - that.cleanup = function () { - toolBar.cleanup(); - Overlays.deleteOverlay(loadURLMenuItem); - Overlays.deleteOverlay(loadFileMenuItem); - }; - - return that; -}()); - - -var exportMenu = null; - -var ExportMenu = function (opts) { - var self = this; - - var windowDimensions = Controller.getViewportDimensions(); - var pos = { x: windowDimensions.x / 2, y: windowDimensions.y - 100 }; - - this._onClose = opts.onClose || function () { }; - this._position = { x: 0.0, y: 0.0, z: 0.0 }; - this._scale = 1.0; - - var minScale = 1; - var maxScale = 32768; - var titleWidth = 120; - var locationWidth = 100; - var scaleWidth = 144; - var exportWidth = 100; - var cancelWidth = 100; - var margin = 4; - var height = 30; - var outerHeight = height + (2 * margin); - var buttonColor = { red: 128, green: 128, blue: 128 }; - - var SCALE_MINUS = scaleWidth * 40.0 / 100.0; - var SCALE_PLUS = scaleWidth * 63.0 / 100.0; - - var fullWidth = locationWidth + scaleWidth + exportWidth + cancelWidth + (2 * margin); - var offset = fullWidth / 2; - pos.x -= offset; - - var background = Overlays.addOverlay("text", { - x: pos.x, - y: pos.y, - opacity: 1, - width: fullWidth, - height: outerHeight, - backgroundColor: { red: 200, green: 200, blue: 200 }, - text: "", - }); - - var titleText = Overlays.addOverlay("text", { - x: pos.x, - y: pos.y - height, - font: { size: 14 }, - width: titleWidth, - height: height, - backgroundColor: { red: 255, green: 255, blue: 255 }, - color: { red: 255, green: 255, blue: 255 }, - text: "Export Models" - }); - - var locationButton = Overlays.addOverlay("text", { - x: pos.x + margin, - y: pos.y + margin, - width: locationWidth, - height: height, - color: { red: 255, green: 255, blue: 255 }, - text: "0, 0, 0", - }); - var scaleOverlay = Overlays.addOverlay("image", { - x: pos.x + margin + locationWidth, - y: pos.y + margin, - width: scaleWidth, - height: height, - subImage: { x: 0, y: 3, width: 144, height: height }, - imageURL: toolIconUrl + "voxel-size-selector.svg", - alpha: 0.9, - }); - var scaleViewWidth = 40; - var scaleView = Overlays.addOverlay("text", { - x: pos.x + margin + locationWidth + SCALE_MINUS, - y: pos.y + margin, - width: scaleViewWidth, - height: height, - alpha: 0.0, - backgroundAlpha: 0.0, - color: { red: 255, green: 255, blue: 255 }, - text: "1" - }); - var exportButton = Overlays.addOverlay("text", { - x: pos.x + margin + locationWidth + scaleWidth, - y: pos.y + margin, - width: exportWidth, - height: height, - color: { red: 0, green: 255, blue: 255 }, - text: "Export" - }); - var cancelButton = Overlays.addOverlay("text", { - x: pos.x + margin + locationWidth + scaleWidth + exportWidth, - y: pos.y + margin, - width: cancelWidth, - height: height, - color: { red: 255, green: 255, blue: 255 }, - text: "Cancel" - }); - - var voxelPreview = Overlays.addOverlay("cube", { - position: { x: 0, y: 0, z: 0 }, - size: this._scale, - color: { red: 255, green: 255, blue: 0 }, - alpha: 1, - solid: false, - visible: true, - lineWidth: 4 - }); - - this.parsePosition = function (str) { - var parts = str.split(','); - if (parts.length == 3) { - var x = parseFloat(parts[0]); - var y = parseFloat(parts[1]); - var z = parseFloat(parts[2]); - if (isFinite(x) && isFinite(y) && isFinite(z)) { - return { x: x, y: y, z: z }; - } - } - return null; - }; - - this.showPositionPrompt = function () { - var positionStr = self._position.x + ", " + self._position.y + ", " + self._position.z; - while (1) { - positionStr = Window.prompt("Position to export form:", positionStr); - if (positionStr == null) { - break; - } - var position = self.parsePosition(positionStr); - if (position != null) { - self.setPosition(position.x, position.y, position.z); - break; - } - Window.alert("The position you entered was invalid."); - } - }; - - this.setScale = function (scale) { - self._scale = Math.min(maxScale, Math.max(minScale, scale)); - Overlays.editOverlay(scaleView, { text: self._scale }); - Overlays.editOverlay(voxelPreview, { size: self._scale }); - } - - this.decreaseScale = function () { - self.setScale(self._scale /= 2); - } - - this.increaseScale = function () { - self.setScale(self._scale *= 2); - } - - this.exportEntities = function() { - var x = self._position.x; - var y = self._position.y; - var z = self._position.z; - var s = self._scale; - var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo"; - filename = Window.save("Select where to save", filename, "*.svo") - if (filename) { - var success = Clipboard.exportEntities(filename, x, y, z, s); - if (!success) { - Window.alert("Export failed: no models found in selected area."); - } - } - self.close(); - }; - - this.getPosition = function () { - return self._position; - }; - - this.setPosition = function (x, y, z) { - self._position = { x: x, y: y, z: z }; - var positionStr = x + ", " + y + ", " + z; - Overlays.editOverlay(locationButton, { text: positionStr }); - Overlays.editOverlay(voxelPreview, { position: self._position }); - - }; - - this.mouseReleaseEvent = function (event) { - var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - - if (clickedOverlay == locationButton) { - self.showPositionPrompt(); - } else if (clickedOverlay == exportButton) { - self.exportEntities(); - } else if (clickedOverlay == cancelButton) { - self.close(); - } else if (clickedOverlay == scaleOverlay) { - var x = event.x - pos.x - margin - locationWidth; - print(x); - if (x < SCALE_MINUS) { - self.decreaseScale(); - } else if (x > SCALE_PLUS) { - self.increaseScale(); - } - } - }; - - this.close = function () { - this.cleanup(); - this._onClose(); - }; - - this.cleanup = function () { - Overlays.deleteOverlay(background); - Overlays.deleteOverlay(titleText); - Overlays.deleteOverlay(locationButton); - Overlays.deleteOverlay(exportButton); - Overlays.deleteOverlay(cancelButton); - Overlays.deleteOverlay(voxelPreview); - Overlays.deleteOverlay(scaleOverlay); - Overlays.deleteOverlay(scaleView); - }; - - print("CONNECTING!"); - Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); -}; - - - -var ModelImporter = function (opts) { - var self = this; - - var height = 30; - var margin = 4; - var outerHeight = height + (2 * margin); - var titleWidth = 120; - var cancelWidth = 100; - var fullWidth = titleWidth + cancelWidth + (2 * margin); - - var localModels = Overlays.addOverlay("localmodels", { - position: { x: 1, y: 1, z: 1 }, - scale: 1, - visible: false - }); - var importScale = 1; - var importBoundaries = Overlays.addOverlay("cube", { - position: { x: 0, y: 0, z: 0 }, - size: 1, - color: { red: 128, blue: 128, green: 128 }, - lineWidth: 4, - solid: false, - visible: false - }); - - var pos = { x: windowDimensions.x / 2 - (fullWidth / 2), y: windowDimensions.y - 100 }; - - var background = Overlays.addOverlay("text", { - x: pos.x, - y: pos.y, - opacity: 1, - width: fullWidth, - height: outerHeight, - backgroundColor: { red: 200, green: 200, blue: 200 }, - visible: false, - text: "", - }); - - var titleText = Overlays.addOverlay("text", { - x: pos.x + margin, - y: pos.y + margin, - font: { size: 14 }, - width: titleWidth, - height: height, - backgroundColor: { red: 255, green: 255, blue: 255 }, - color: { red: 255, green: 255, blue: 255 }, - visible: false, - text: "Import Models" - }); - var cancelButton = Overlays.addOverlay("text", { - x: pos.x + margin + titleWidth, - y: pos.y + margin, - width: cancelWidth, - height: height, - color: { red: 255, green: 255, blue: 255 }, - visible: false, - text: "Close" - }); - this._importing = false; - - this.setImportVisible = function (visible) { - Overlays.editOverlay(importBoundaries, { visible: visible }); - Overlays.editOverlay(localModels, { visible: visible }); - Overlays.editOverlay(cancelButton, { visible: visible }); - Overlays.editOverlay(titleText, { visible: visible }); - Overlays.editOverlay(background, { visible: visible }); - }; - - var importPosition = { x: 0, y: 0, z: 0 }; - this.moveImport = function (position) { - importPosition = position; - Overlays.editOverlay(localModels, { - position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } - }); - Overlays.editOverlay(importBoundaries, { - position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } - }); - } - - this.mouseMoveEvent = function (event) { - if (self._importing) { - var pickRay = Camera.computePickRay(event.x, event.y); - var intersection = false; //Voxels.findRayIntersection(pickRay); - - var distance = 2;// * self._scale; - - if (false) {//intersection.intersects) { - var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection)); - if (intersectionDistance < distance) { - distance = intersectionDistance * 0.99; - } - - } - - var targetPosition = { - x: pickRay.origin.x + (pickRay.direction.x * distance), - y: pickRay.origin.y + (pickRay.direction.y * distance), - z: pickRay.origin.z + (pickRay.direction.z * distance) - }; - - if (targetPosition.x < 0) targetPosition.x = 0; - if (targetPosition.y < 0) targetPosition.y = 0; - if (targetPosition.z < 0) targetPosition.z = 0; - - var nudgeFactor = 1; - var newPosition = { - x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor, - y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor, - z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor - } - - self.moveImport(newPosition); - } - } - - this.mouseReleaseEvent = function (event) { - var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - - if (clickedOverlay == cancelButton) { - self._importing = false; - self.setImportVisible(false); - } - }; - - // Would prefer to use {4} for the coords, but it would only capture the last digit. - var fileRegex = /__(.+)__(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)__/; - this.doImport = function () { - if (!self._importing) { - var filename = Window.browse("Select models to import", "", "*.svo") - if (filename) { - parts = fileRegex.exec(filename); - if (parts == null) { - Window.alert("The file you selected does not contain source domain or location information"); - } else { - var hostname = parts[1]; - var x = parts[2]; - var y = parts[3]; - var z = parts[4]; - var s = parts[5]; - importScale = s; - if (hostname != location.hostname) { - if (!Window.confirm(("These models were not originally exported from this domain. Continue?"))) { - return; - } - } else { - if (Window.confirm(("Would you like to import back to the source location?"))) { - var success = Clipboard.importEntities(filename); - if (success) { - Clipboard.pasteEntities(x, y, z, 1); - } else { - Window.alert("There was an error importing the entity file."); - } - return; - } - } - } - var success = Clipboard.importEntities(filename); - if (success) { - self._importing = true; - self.setImportVisible(true); - Overlays.editOverlay(importBoundaries, { size: s }); - } else { - Window.alert("There was an error importing the entity file."); - } - } - } - } - - this.paste = function () { - if (self._importing) { - // self._importing = false; - // self.setImportVisible(false); - Clipboard.pasteEntities(importPosition.x, importPosition.y, importPosition.z, 1); - } - } - - this.cleanup = function () { - Overlays.deleteOverlay(localModels); - Overlays.deleteOverlay(importBoundaries); - Overlays.deleteOverlay(cancelButton); - Overlays.deleteOverlay(titleText); - Overlays.deleteOverlay(background); - } - - Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); - Controller.mouseMoveEvent.connect(this.mouseMoveEvent); -}; - -var modelImporter = new ModelImporter(); - - -function isLocked(properties) { - // special case to lock the ground plane model in hq. - if (location.hostname == "hq.highfidelity.io" && - properties.modelURL == HIFI_PUBLIC_BUCKET + "ozan/Terrain_Reduce_forAlpha.fbx") { - return true; - } - return false; -} - - -function controller(wichSide) { - this.side = wichSide; - this.palm = 2 * wichSide; - this.tip = 2 * wichSide + 1; - this.trigger = wichSide; - this.bumper = 6 * wichSide + 5; - - this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm); - this.palmPosition = Controller.getSpatialControlPosition(this.palm); - - this.oldTipPosition = Controller.getSpatialControlPosition(this.tip); - this.tipPosition = Controller.getSpatialControlPosition(this.tip); - - this.oldUp = Controller.getSpatialControlNormal(this.palm); - this.up = this.oldUp; - - this.oldFront = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); - this.front = this.oldFront; - - this.oldRight = Vec3.cross(this.front, this.up); - this.right = this.oldRight; - - this.oldRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); - this.rotation = this.oldRotation; - - this.triggerValue = Controller.getTriggerValue(this.trigger); - this.bumperValue = Controller.isButtonPressed(this.bumper); - - this.pressed = false; // is trigger pressed - this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously) - - this.grabbing = false; - this.entityID = { isKnownID: false }; - this.modelURL = ""; - this.oldModelRotation; - this.oldModelPosition; - this.oldModelHalfDiagonal; - - this.positionAtGrab; - this.rotationAtGrab; - this.modelPositionAtGrab; - this.rotationAtGrab; - this.jointsIntersectingFromStart = []; - - this.laser = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: LASER_COLOR, - alpha: 1, - visible: false, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" - }); - - this.guideScale = 0.02; - this.ball = Overlays.addOverlay("sphere", { - position: { x: 0, y: 0, z: 0 }, - size: this.guideScale, - solid: true, - color: { red: 0, green: 255, blue: 0 }, - alpha: 1, - visible: false, - anchor: "MyAvatar" - }); - this.leftRight = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 0, green: 0, blue: 255 }, - alpha: 1, - visible: false, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" - }); - this.topDown = Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 0, green: 0, blue: 255 }, - alpha: 1, - visible: false, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" - }); - - - - this.grab = function (entityID, properties) { - if (isLocked(properties)) { - print("Model locked " + entityID.id); - } else { - print("Grabbing " + entityID.id); - this.grabbing = true; - this.entityID = entityID; - this.modelURL = properties.modelURL; - - this.oldModelPosition = properties.position; - this.oldModelRotation = properties.rotation; - this.oldModelHalfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - this.positionAtGrab = this.palmPosition; - this.rotationAtGrab = this.rotation; - this.modelPositionAtGrab = properties.position; - this.rotationAtGrab = properties.rotation; - this.jointsIntersectingFromStart = []; - for (var i = 0; i < jointList.length; i++) { - var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); - if (distance < this.oldModelHalfDiagonal) { - this.jointsIntersectingFromStart.push(i); - } - } - this.showLaser(false); - } - } - - this.release = function () { - if (this.grabbing) { - jointList = MyAvatar.getJointNames(); - - var closestJointIndex = -1; - var closestJointDistance = 10; - for (var i = 0; i < jointList.length; i++) { - var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); - if (distance < closestJointDistance) { - closestJointDistance = distance; - closestJointIndex = i; - } - } - - if (closestJointIndex != -1) { - print("closestJoint: " + jointList[closestJointIndex]); - print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelHalfDiagonal + ")"); - } - - if (closestJointDistance < this.oldModelHalfDiagonal) { - - if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 || - (leftController.grabbing && rightController.grabbing && - leftController.entityID.id == rightController.entityID.id)) { - // Do nothing - } else { - print("Attaching to " + jointList[closestJointIndex]); - var jointPosition = MyAvatar.getJointPosition(jointList[closestJointIndex]); - var jointRotation = MyAvatar.getJointCombinedRotation(jointList[closestJointIndex]); - - var attachmentOffset = Vec3.subtract(this.oldModelPosition, jointPosition); - attachmentOffset = Vec3.multiplyQbyV(Quat.inverse(jointRotation), attachmentOffset); - var attachmentRotation = Quat.multiply(Quat.inverse(jointRotation), this.oldModelRotation); - - MyAvatar.attach(this.modelURL, jointList[closestJointIndex], - attachmentOffset, attachmentRotation, 2.0 * this.oldModelHalfDiagonal, - true, false); - Entities.deleteEntity(this.entityID); - } - } - } - - this.grabbing = false; - this.entityID.isKnownID = false; - this.jointsIntersectingFromStart = []; - this.showLaser(true); - } - - this.checkTrigger = function () { - if (this.triggerValue > 0.9) { - if (this.pressed) { - this.pressing = false; - } else { - this.pressing = true; - } - this.pressed = true; - } else { - this.pressing = false; - this.pressed = false; - } - } - - this.checkEntity = function (properties) { - // special case to lock the ground plane model in hq. - if (isLocked(properties)) { - return { valid: false }; - } - - - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = this.palmPosition; - var B = this.front; - var P = properties.position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var y = Vec3.dot(Vec3.subtract(P, A), this.up); - var z = Vec3.dot(Vec3.subtract(P, A), this.right); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (0 < x && sizeOK) { - return { valid: true, x: x, y: y, z: z }; - } - return { valid: false }; - } - - this.glowedIntersectingModel = { isKnownID: false }; - this.moveLaser = function () { - // the overlays here are anchored to the avatar, which means they are specified in the avatar's local frame - - var inverseRotation = Quat.inverse(MyAvatar.orientation); - var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); - var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); - var distance = Vec3.length(direction); - direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / distance); - var endPosition = Vec3.sum(startPosition, direction); - - Overlays.editOverlay(this.laser, { - start: startPosition, - end: endPosition - }); - - - Overlays.editOverlay(this.ball, { - position: endPosition - }); - Overlays.editOverlay(this.leftRight, { - start: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)), - end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)) - }); - Overlays.editOverlay(this.topDown, { - start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)), - end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)) - }); - this.showLaser(!this.grabbing || mode == 0); - - if (this.glowedIntersectingModel.isKnownID) { - Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); - this.glowedIntersectingModel.isKnownID = false; - } - if (!this.grabbing) { - var intersection = Entities.findRayIntersection({ - origin: this.palmPosition, - direction: this.front - }); - - var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14; - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - if (intersection.accurate && intersection.entityID.isKnownID && sizeOK) { - this.glowedIntersectingModel = intersection.entityID; - - if (wantEntityGlow) { - Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); - } - } - } - } - - this.showLaser = function (show) { - Overlays.editOverlay(this.laser, { visible: show }); - Overlays.editOverlay(this.ball, { visible: show }); - Overlays.editOverlay(this.leftRight, { visible: show }); - Overlays.editOverlay(this.topDown, { visible: show }); - } - this.moveEntity = function () { - if (this.grabbing) { - if (!this.entityID.isKnownID) { - print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); - this.entityID = Entities.findRayIntersection({ - origin: this.palmPosition, - direction: this.front - }).entityID; - print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID); - } - var newPosition; - var newRotation; - - switch (mode) { - case 0: - newPosition = Vec3.sum(this.palmPosition, - Vec3.multiply(this.front, this.x)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(this.up, this.y)); - newPosition = Vec3.sum(newPosition, - Vec3.multiply(this.right, this.z)); - - - newRotation = Quat.multiply(this.rotation, - Quat.inverse(this.oldRotation)); - newRotation = Quat.multiply(newRotation, - this.oldModelRotation); - break; - case 1: - var forward = Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -1 }); - var d = Vec3.dot(forward, MyAvatar.position); - - var factor1 = Vec3.dot(forward, this.positionAtGrab) - d; - var factor2 = Vec3.dot(forward, this.modelPositionAtGrab) - d; - var vector = Vec3.subtract(this.palmPosition, this.positionAtGrab); - - if (factor2 < 0) { - factor2 = 0; - } - if (factor1 <= 0) { - factor1 = 1; - factor2 = 1; - } - - newPosition = Vec3.sum(this.modelPositionAtGrab, - Vec3.multiply(vector, - factor2 / factor1)); - - newRotation = Quat.multiply(this.rotation, - Quat.inverse(this.rotationAtGrab)); - newRotation = Quat.multiply(newRotation, - this.rotationAtGrab); - break; - } - Entities.editEntity(this.entityID, { - position: newPosition, - rotation: newRotation - }); - this.oldModelRotation = newRotation; - this.oldModelPosition = newPosition; - - var indicesToRemove = []; - for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { - var distance = Vec3.distance(MyAvatar.getJointPosition(this.jointsIntersectingFromStart[i]), this.oldModelPosition); - if (distance >= this.oldModelHalfDiagonal) { - indicesToRemove.push(this.jointsIntersectingFromStart[i]); - } - - } - for (var i = 0; i < indicesToRemove.length; ++i) { - this.jointsIntersectingFromStart.splice(this.jointsIntersectingFromStart.indexOf(indicesToRemove[i], 1)); - } - } - } - - this.update = function () { - this.oldPalmPosition = this.palmPosition; - this.oldTipPosition = this.tipPosition; - this.palmPosition = Controller.getSpatialControlPosition(this.palm); - this.tipPosition = Controller.getSpatialControlPosition(this.tip); - - this.oldUp = this.up; - this.up = Vec3.normalize(Controller.getSpatialControlNormal(this.palm)); - - this.oldFront = this.front; - this.front = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); - - this.oldRight = this.right; - this.right = Vec3.normalize(Vec3.cross(this.front, this.up)); - - this.oldRotation = this.rotation; - this.rotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); - - this.triggerValue = Controller.getTriggerValue(this.trigger); - - var bumperValue = Controller.isButtonPressed(this.bumper); - if (bumperValue && !this.bumperValue) { - if (mode == 0) { - mode = 1; - Overlays.editOverlay(leftController.laser, { color: { red: 0, green: 0, blue: 255 } }); - Overlays.editOverlay(rightController.laser, { color: { red: 0, green: 0, blue: 255 } }); - } else { - mode = 0; - Overlays.editOverlay(leftController.laser, { color: { red: 255, green: 0, blue: 0 } }); - Overlays.editOverlay(rightController.laser, { color: { red: 255, green: 0, blue: 0 } }); - } - } - this.bumperValue = bumperValue; - - - this.checkTrigger(); - - this.moveLaser(); - - if (!this.pressed && this.grabbing) { - // release if trigger not pressed anymore. - this.release(); - } - - if (this.pressing) { - // Checking for attachments intersecting - var attachments = MyAvatar.getAttachmentData(); - var attachmentIndex = -1; - var attachmentX = LASER_LENGTH_FACTOR; - - var newModel; - var newProperties; - - for (var i = 0; i < attachments.length; ++i) { - var position = Vec3.sum(MyAvatar.getJointPosition(attachments[i].jointName), - Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[i].jointName), attachments[i].translation)); - var scale = attachments[i].scale; - - var A = this.palmPosition; - var B = this.front; - var P = position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - - if (d < scale / 2.0 && 0 < x && x < attachmentX) { - attachmentIndex = i; - attachmentX = d; - } - } - - if (attachmentIndex != -1) { - print("Detaching: " + attachments[attachmentIndex].modelURL); - MyAvatar.detachOne(attachments[attachmentIndex].modelURL, attachments[attachmentIndex].jointName); - - newProperties = { - type: "Model", - position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName), - Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)), - rotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), - attachments[attachmentIndex].rotation), - - // TODO: how do we know the correct dimensions for detachment??? - dimensions: { x: attachments[attachmentIndex].scale / 2.0, - y: attachments[attachmentIndex].scale / 2.0, - z: attachments[attachmentIndex].scale / 2.0 }, - - modelURL: attachments[attachmentIndex].modelURL - }; - - newModel = Entities.addEntity(newProperties); - - - } else { - // There is none so ... - // Checking model tree - Vec3.print("Looking at: ", this.palmPosition); - var pickRay = { origin: this.palmPosition, - direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; - var foundIntersection = Entities.findRayIntersection(pickRay); - - if(!foundIntersection.accurate) { - print("No accurate intersection"); - return; - } - newModel = foundIntersection.entityID; - if (!newModel.isKnownID) { - var identify = Entities.identifyEntity(newModel); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + " (update loop " + newModel.id + ")"); - return; - } - newModel = identify; - } - newProperties = Entities.getEntityProperties(newModel); - } - print("foundEntity.modelURL=" + newProperties.modelURL); - if (isLocked(newProperties)) { - print("Model locked " + newProperties.id); - } else { - var check = this.checkEntity(newProperties); - if (!check.valid) { - return; - } - - this.grab(newModel, newProperties); - - this.x = check.x; - this.y = check.y; - this.z = check.z; - return; - } - } - } - - this.cleanup = function () { - Overlays.deleteOverlay(this.laser); - Overlays.deleteOverlay(this.ball); - Overlays.deleteOverlay(this.leftRight); - Overlays.deleteOverlay(this.topDown); - } -} - -var leftController = new controller(LEFT); -var rightController = new controller(RIGHT); - -function moveEntities() { - if (leftController.grabbing && rightController.grabbing && rightController.entityID.id == leftController.entityID.id) { - var newPosition = leftController.oldModelPosition; - var rotation = leftController.oldModelRotation; - var ratio = 1; - - - switch (mode) { - case 0: - var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x)); - var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x)); - - var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5); - var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint)); - - - var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x)); - var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x)); - - var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5); - var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint)); - - - ratio = length / oldLength; - newPosition = Vec3.sum(middle, - Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio)); - break; - case 1: - var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition)); - var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition)); - - var cos_theta = Vec3.dot(u, v); - if (cos_theta > 1) { - cos_theta = 1; - } - var angle = Math.acos(cos_theta) / Math.PI * 180; - if (angle < 0.1) { - return; - - } - var w = Vec3.normalize(Vec3.cross(u, v)); - - rotation = Quat.multiply(Quat.angleAxis(angle, w), leftController.oldModelRotation); - - - leftController.positionAtGrab = leftController.palmPosition; - leftController.rotationAtGrab = leftController.rotation; - leftController.modelPositionAtGrab = leftController.oldModelPosition; - leftController.rotationAtGrab = rotation; - rightController.positionAtGrab = rightController.palmPosition; - rightController.rotationAtGrab = rightController.rotation; - rightController.modelPositionAtGrab = rightController.oldModelPosition; - rightController.rotationAtGrab = rotation; - break; - } - Entities.editEntity(leftController.entityID, { - position: newPosition, - rotation: rotation, - // TODO: how do we know the correct dimensions for detachment??? - //radius: leftController.oldModelHalfDiagonal * ratio - dimensions: { x: leftController.oldModelHalfDiagonal * ratio, - y: leftController.oldModelHalfDiagonal * ratio, - z: leftController.oldModelHalfDiagonal * ratio } - - - }); - leftController.oldModelPosition = newPosition; - leftController.oldModelRotation = rotation; - leftController.oldModelHalfDiagonal *= ratio; - - rightController.oldModelPosition = newPosition; - rightController.oldModelRotation = rotation; - rightController.oldModelHalfDiagonal *= ratio; - return; - } - leftController.moveEntity(); - rightController.moveEntity(); -} - -var hydraConnected = false; -function checkController(deltaTime) { - var numberOfButtons = Controller.getNumberOfButtons(); - var numberOfTriggers = Controller.getNumberOfTriggers(); - var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); - var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - - if (!isActive) { - // So that we hide the lasers bellow and keep updating the overlays position - numberOfButtons = 0; - } - - // this is expected for hydras - if (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2) { - if (!hydraConnected) { - hydraConnected = true; - } - - leftController.update(); - rightController.update(); - moveEntities(); - } else { - if (hydraConnected) { - hydraConnected = false; - - leftController.showLaser(false); - rightController.showLaser(false); - } - } - toolBar.move(); - progressDialog.move(); -} - -var entitySelected = false; -var selectedEntityID; -var selectedEntityProperties; -var mouseLastPosition; -var orientation; -var intersection; - - -var SCALE_FACTOR = 200.0; - -function rayPlaneIntersection(pickRay, point, normal) { - var d = -Vec3.dot(point, normal); - var t = -(Vec3.dot(pickRay.origin, normal) + d) / Vec3.dot(pickRay.direction, normal); - - return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); -} - -function Tooltip() { - this.x = 285; - this.y = 115; - this.width = 500; - this.height = 300; // 145; - this.margin = 5; - this.decimals = 3; - - this.textOverlay = Overlays.addOverlay("text", { - x: this.x, - y: this.y, - width: this.width, - height: this.height, - margin: this.margin, - text: "", - color: { red: 228, green: 228, blue: 228 }, - alpha: 0.8, - backgroundAlpha: 0.8, - visible: false - }); - this.show = function (doShow) { - Overlays.editOverlay(this.textOverlay, { visible: doShow }); - } - this.updateText = function(properties) { - var angles = Quat.safeEulerAngles(properties.rotation); - var text = "Entity Properties:\n" - text += "type: " + properties.type + "\n" - text += "X: " + properties.position.x.toFixed(this.decimals) + "\n" - text += "Y: " + properties.position.y.toFixed(this.decimals) + "\n" - text += "Z: " + properties.position.z.toFixed(this.decimals) + "\n" - text += "Pitch: " + angles.x.toFixed(this.decimals) + "\n" - text += "Yaw: " + angles.y.toFixed(this.decimals) + "\n" - text += "Roll: " + angles.z.toFixed(this.decimals) + "\n" - text += "Dimensions: " + properties.dimensions.x.toFixed(this.decimals) + ", " - + properties.dimensions.y.toFixed(this.decimals) + ", " - + properties.dimensions.z.toFixed(this.decimals) + "\n"; - - text += "Natural Dimensions: " + properties.naturalDimensions.x.toFixed(this.decimals) + ", " - + properties.naturalDimensions.y.toFixed(this.decimals) + ", " - + properties.naturalDimensions.z.toFixed(this.decimals) + "\n"; - - text += "ID: " + properties.id + "\n" - if (properties.type == "Model") { - text += "Model URL: " + properties.modelURL + "\n" - text += "Animation URL: " + properties.animationURL + "\n" - text += "Animation is playing: " + properties.animationIsPlaying + "\n" - if (properties.sittingPoints && properties.sittingPoints.length > 0) { - text += properties.sittingPoints.length + " Sitting points: " - for (var i = 0; i < properties.sittingPoints.length; ++i) { - text += properties.sittingPoints[i].name + " " - } - } else { - text += "No sitting points" + "\n" - } - } - if (properties.lifetime > -1) { - text += "Lifetime: " + properties.lifetime + "\n" - } - text += "Age: " + properties.ageAsText + "\n" - text += "Mass: " + properties.mass + "\n" - text += "Script: " + properties.script + "\n" - - - Overlays.editOverlay(this.textOverlay, { text: text }); - } - - this.cleanup = function () { - Overlays.deleteOverlay(this.textOverlay); - } -} -var tooltip = new Tooltip(); - -function mousePressEvent(event) { - if (event.isAlt) { - return; - } - - mouseLastPosition = { x: event.x, y: event.y }; - entitySelected = false; - var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - - if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { - // Event handled; do nothing. - return; - } else { - // If we aren't active and didn't click on an overlay: quit - if (!isActive) { - return; - } - - var pickRay = Camera.computePickRay(event.x, event.y); - Vec3.print("[Mouse] Looking at: ", pickRay.origin); - var foundIntersection = Entities.findRayIntersection(pickRay, true); // we want precision picking here - - if(!foundIntersection.accurate) { - return; - } - var foundEntity = foundIntersection.entityID; - - if (!foundEntity.isKnownID) { - var identify = Entities.identifyEntity(foundEntity); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")"); - return; - } - foundEntity = identify; - } - - var properties = Entities.getEntityProperties(foundEntity); - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal); - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = pickRay.origin; - var B = Vec3.normalize(pickRay.direction); - var P = properties.position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (0 < x && sizeOK) { - entitySelected = true; - selectedEntityID = foundEntity; - selectedEntityProperties = properties; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - } - } - } - if (entitySelected) { - selectedEntityProperties.oldDimensions = selectedEntityProperties.dimensions; - selectedEntityProperties.oldPosition = { - x: selectedEntityProperties.position.x, - y: selectedEntityProperties.position.y, - z: selectedEntityProperties.position.z, - }; - selectedEntityProperties.oldRotation = { - x: selectedEntityProperties.rotation.x, - y: selectedEntityProperties.rotation.y, - z: selectedEntityProperties.rotation.z, - w: selectedEntityProperties.rotation.w, - }; - selectedEntityProperties.glowLevel = 0.0; - - print("Clicked on " + selectedEntityID.id + " " + entitySelected); - tooltip.updateText(selectedEntityProperties); - tooltip.show(true); - } -} - -var glowedEntityID = { id: -1, isKnownID: false }; -var oldModifier = 0; -var modifier = 0; -var wasShifted = false; -function mouseMoveEvent(event) { - if (event.isAlt || !isActive) { - return; - } - - var pickRay = Camera.computePickRay(event.x, event.y); - if (!entitySelected) { - var entityIntersection = Entities.findRayIntersection(pickRay); - if (entityIntersection.accurate) { - if(glowedEntityID.isKnownID && glowedEntityID.id != entityIntersection.entityID.id) { - Entities.editEntity(glowedEntityID, { glowLevel: 0.0 }); - glowedEntityID.id = -1; - glowedEntityID.isKnownID = false; - } - - var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), - entityIntersection.properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) - && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (entityIntersection.entityID.isKnownID && sizeOK) { - if (wantEntityGlow) { - Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); - } - glowedEntityID = entityIntersection.entityID; - } - - } - return; - } - - if (event.isLeftButton) { - if (event.isRightButton) { - modifier = 1; // Scale - } else { - modifier = 2; // Translate - } - } else if (event.isRightButton) { - modifier = 3; // rotate - } else { - modifier = 0; - } - pickRay = Camera.computePickRay(event.x, event.y); - if (wasShifted != event.isShifted || modifier != oldModifier) { - selectedEntityProperties.oldDimensions = selectedEntityProperties.dimensions; - - selectedEntityProperties.oldPosition = { - x: selectedEntityProperties.position.x, - y: selectedEntityProperties.position.y, - z: selectedEntityProperties.position.z, - }; - selectedEntityProperties.oldRotation = { - x: selectedEntityProperties.rotation.x, - y: selectedEntityProperties.rotation.y, - z: selectedEntityProperties.rotation.z, - w: selectedEntityProperties.rotation.w, - }; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, - selectedEntityProperties.oldPosition, - Quat.getFront(orientation)); - - mouseLastPosition = { x: event.x, y: event.y }; - wasShifted = event.isShifted; - oldModifier = modifier; - return; - } - - - switch (modifier) { - case 0: - return; - case 1: - // Let's Scale - selectedEntityProperties.dimensions = Vec3.multiply(selectedEntityProperties.dimensions, - (1.0 + (mouseLastPosition.y - event.y) / SCALE_FACTOR)); - - var halfDiagonal = Vec3.length(selectedEntityProperties.dimensions) / 2.0; - - if (halfDiagonal < 0.01) { - print("Scale too small ... bailling."); - return; - } - break; - - case 2: - // Let's translate - var newIntersection = rayPlaneIntersection(pickRay, - selectedEntityProperties.oldPosition, - Quat.getFront(orientation)); - var vector = Vec3.subtract(newIntersection, intersection) - if (event.isShifted) { - var i = Vec3.dot(vector, Quat.getRight(orientation)); - var j = Vec3.dot(vector, Quat.getUp(orientation)); - vector = Vec3.sum(Vec3.multiply(Quat.getRight(orientation), i), - Vec3.multiply(Quat.getFront(orientation), j)); - } - selectedEntityProperties.position = Vec3.sum(selectedEntityProperties.oldPosition, vector); - break; - case 3: - // Let's rotate - if (somethingChanged) { - selectedEntityProperties.oldRotation.x = selectedEntityProperties.rotation.x; - selectedEntityProperties.oldRotation.y = selectedEntityProperties.rotation.y; - selectedEntityProperties.oldRotation.z = selectedEntityProperties.rotation.z; - selectedEntityProperties.oldRotation.w = selectedEntityProperties.rotation.w; - mouseLastPosition.x = event.x; - mouseLastPosition.y = event.y; - somethingChanged = false; - } - - - var pixelPerDegrees = windowDimensions.y / (1 * 360); // the entire height of the window allow you to make 2 full rotations - - //compute delta in pixel - var cameraForward = Quat.getFront(Camera.getOrientation()); - var rotationAxis = (!zIsPressed && xIsPressed) ? { x: 1, y: 0, z: 0 } : - (!zIsPressed && !xIsPressed) ? { x: 0, y: 1, z: 0 } : - { x: 0, y: 0, z: 1 }; - rotationAxis = Vec3.multiplyQbyV(selectedEntityProperties.rotation, rotationAxis); - var orthogonalAxis = Vec3.cross(cameraForward, rotationAxis); - var mouseDelta = { x: event.x - mouseLastPosition - .x, y: mouseLastPosition.y - event.y, z: 0 }; - var transformedMouseDelta = Vec3.multiplyQbyV(Camera.getOrientation(), mouseDelta); - var delta = Math.floor(Vec3.dot(transformedMouseDelta, Vec3.normalize(orthogonalAxis)) / pixelPerDegrees); - - var STEP = 15; - if (!event.isShifted) { - delta = Math.round(delta / STEP) * STEP; - } - - var rotation = Quat.fromVec3Degrees({ - x: (!zIsPressed && xIsPressed) ? delta : 0, // x is pressed - y: (!zIsPressed && !xIsPressed) ? delta : 0, // neither is pressed - z: (zIsPressed && !xIsPressed) ? delta : 0 // z is pressed - }); - rotation = Quat.multiply(selectedEntityProperties.oldRotation, rotation); - - selectedEntityProperties.rotation.x = rotation.x; - selectedEntityProperties.rotation.y = rotation.y; - selectedEntityProperties.rotation.z = rotation.z; - selectedEntityProperties.rotation.w = rotation.w; - break; - } - - Entities.editEntity(selectedEntityID, selectedEntityProperties); - tooltip.updateText(selectedEntityProperties); -} - - -function mouseReleaseEvent(event) { - if (event.isAlt || !isActive) { - return; - } - if (entitySelected) { - tooltip.show(false); - } - - entitySelected = false; - - glowedEntityID.id = -1; - glowedEntityID.isKnownID = false; -} - -// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already -// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that -// added it. -var modelMenuAddedDelete = false; -var originalLightsArePickable = Entities.getLightsArePickable(); -function setupModelMenus() { - print("setupModelMenus()"); - // adj our menuitems - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...", - shortcutKeyEvent: { text: "`" }, afterItem: "Models" }); - if (!Menu.menuItemExists("Edit", "Delete")) { - print("no delete... adding ours"); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", - shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" }); - modelMenuAddedDelete = true; - } else { - print("delete exists... don't add ours"); - } - - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Edit Properties..." }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L", - afterItem: "Paste Models", isCheckable: true }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S", - afterItem: "Allow Selecting of Large Models", isCheckable: true }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", shortcutKey: "CTRL+SHIFT+META+L", - afterItem: "Allow Selecting of Small Models", isCheckable: true }); - - Menu.addMenuItem({ menuName: "File", menuItemName: "Models", isSeparator: true, beforeItem: "Settings" }); - Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" }); - Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" }); - - Entities.setLightsArePickable(false); - -} - -function cleanupModelMenus() { - Menu.removeSeparator("Edit", "Models"); - Menu.removeMenuItem("Edit", "Edit Properties..."); - if (modelMenuAddedDelete) { - // delete our menuitems - Menu.removeMenuItem("Edit", "Delete"); - } - - Menu.removeMenuItem("Edit", "Model List..."); - Menu.removeMenuItem("Edit", "Paste Models"); - Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); - Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); - Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); - - Menu.removeSeparator("File", "Models"); - Menu.removeMenuItem("File", "Export Models"); - Menu.removeMenuItem("File", "Import Models"); -} - -function scriptEnding() { - leftController.cleanup(); - rightController.cleanup(); - progressDialog.cleanup(); - toolBar.cleanup(); - cleanupModelMenus(); - tooltip.cleanup(); - modelImporter.cleanup(); - if (exportMenu) { - exportMenu.close(); - } - Entities.setLightsArePickable(originalLightsArePickable); -} -Script.scriptEnding.connect(scriptEnding); - -// register the call back so it fires before each data send -Script.update.connect(checkController); -Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseMoveEvent.connect(mouseMoveEvent); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); - -setupModelMenus(); - -var propertiesForEditedEntity; -var editEntityFormArray; -var editModelID = -1; -var dimensionX; -var dimensionY; -var dimensionZ; -var rescalePercentage; - -function showPropertiesForm(editModelID) { - entityPropertyDialogBox.openDialog(editModelID); -} - -function handeMenuEvent(menuItem) { - print("menuItemEvent() in JS... menuItem=" + menuItem); - if (menuItem == "Allow Selecting of Small Models") { - allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); - } else if (menuItem == "Allow Selecting of Large Models") { - allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models"); - } else if (menuItem == "Allow Selecting of Lights") { - Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); - } else if (menuItem == "Delete") { - if (leftController.grabbing) { - print(" Delete Entity.... leftController.entityID="+ leftController.entityID); - Entities.deleteEntity(leftController.entityID); - leftController.grabbing = false; - if (glowedEntityID.id == leftController.entityID.id) { - glowedEntityID = { id: -1, isKnownID: false }; - } - } else if (rightController.grabbing) { - print(" Delete Entity.... rightController.entityID="+ rightController.entityID); - Entities.deleteEntity(rightController.entityID); - rightController.grabbing = false; - if (glowedEntityID.id == rightController.entityID.id) { - glowedEntityID = { id: -1, isKnownID: false }; - } - } else if (entitySelected) { - print(" Delete Entity.... selectedEntityID="+ selectedEntityID); - Entities.deleteEntity(selectedEntityID); - entitySelected = false; - if (glowedEntityID.id == selectedEntityID.id) { - glowedEntityID = { id: -1, isKnownID: false }; - } - } else { - print(" Delete Entity.... not holding..."); - } - } else if (menuItem == "Model List...") { - var models = new Array(); - models = Entities.findEntities(MyAvatar.position, Number.MAX_VALUE); - for (var i = 0; i < models.length; i++) { - models[i].properties = Entities.getEntityProperties(models[i]); - models[i].toString = function() { - var modelname; - if (this.properties.type == "Model") { - modelname = decodeURIComponent( - this.properties.modelURL.indexOf("/") != -1 ? - this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) : - this.properties.modelURL); - } else { - modelname = this.properties.id; - } - return "[" + this.properties.type + "] " + modelname; - }; - } - var form = [{label: "Model: ", options: models}]; - form.push({label: "Action: ", options: ["Properties", "Delete", "Teleport"]}); - form.push({ button: "Cancel" }); - if (Window.form("Model List", form)) { - var selectedModel = form[0].value; - if (form[1].value == "Properties") { - editModelID = selectedModel; - showPropertiesForm(editModelID); - } else if (form[1].value == "Delete") { - Entities.deleteEntity(selectedModel); - } else if (form[1].value == "Teleport") { - MyAvatar.position = selectedModel.properties.position; - } - } - } else if (menuItem == "Edit Properties...") { - editModelID = -1; - if (leftController.grabbing) { - print(" Edit Properties.... leftController.entityID="+ leftController.entityID); - editModelID = leftController.entityID; - } else if (rightController.grabbing) { - print(" Edit Properties.... rightController.entityID="+ rightController.entityID); - editModelID = rightController.entityID; - } else if (entitySelected) { - print(" Edit Properties.... selectedEntityID="+ selectedEntityID); - editModelID = selectedEntityID; - } else { - print(" Edit Properties.... not holding..."); - } - if (editModelID != -1) { - print(" Edit Properties.... about to edit properties..."); - showPropertiesForm(editModelID); - } - } else if (menuItem == "Paste Models") { - modelImporter.paste(); - } else if (menuItem == "Export Models") { - if (!exportMenu) { - exportMenu = new ExportMenu({ - onClose: function () { - exportMenu = null; - } - }); - } - } else if (menuItem == "Import Models") { - modelImporter.doImport(); - } - tooltip.show(false); -} -Menu.menuItemEvent.connect(handeMenuEvent); - - - -// handling of inspect.js concurrence -var zIsPressed = false; -var xIsPressed = false; -var somethingChanged = false; -Controller.keyPressEvent.connect(function (event) { - if ((event.text == "z" || event.text == "Z") && !zIsPressed) { - zIsPressed = true; - somethingChanged = true; - } - if ((event.text == "x" || event.text == "X") && !xIsPressed) { - xIsPressed = true; - somethingChanged = true; - } - - // resets model orientation when holding with mouse - if (event.text == "r" && entitySelected) { - selectedEntityProperties.rotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }); - Entities.editEntity(selectedEntityID, selectedEntityProperties); - tooltip.updateText(selectedEntityProperties); - somethingChanged = true; - } -}); - -Controller.keyReleaseEvent.connect(function (event) { - if (event.text == "z" || event.text == "Z") { - zIsPressed = false; - somethingChanged = true; - } - if (event.text == "x" || event.text == "X") { - xIsPressed = false; - somethingChanged = true; - } - // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items - if (event.text == "`") { - handeMenuEvent("Edit Properties..."); - } - if (event.text == "BACKSPACE") { - handeMenuEvent("Delete"); - } -}); - diff --git a/examples/example/audio/audioBall.js b/examples/example/audio/audioBall.js index 91ef7c0759..4f352ff1a3 100644 --- a/examples/example/audio/audioBall.js +++ b/examples/example/audio/audioBall.js @@ -13,7 +13,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var sound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Animals/mexicanWhipoorwill.raw"); var CHANCE_OF_PLAYING_SOUND = 0.01; diff --git a/examples/example/audio/radio.js b/examples/example/audio/radio.js index 39409df377..7ac33675a1 100644 --- a/examples/example/audio/radio.js +++ b/examples/example/audio/radio.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var modelURL = HIFI_PUBLIC_BUCKET + "models/entities/radio/Speakers.fbx"; var soundURL = HIFI_PUBLIC_BUCKET + "sounds/family.stereo.raw"; diff --git a/examples/example/downloadInfoExample.js b/examples/example/downloadInfoExample.js new file mode 100644 index 0000000000..19995503bc --- /dev/null +++ b/examples/example/downloadInfoExample.js @@ -0,0 +1,66 @@ +// +// downloadInfoExample.js +// examples/example +// +// Created by David Rowe on 5 Jan 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Display downloads information the same as in the stats. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var downloadInfo, + downloadInfoOverlay; + +function formatInfo(info) { + var string = "Downloads: ", + i; + + for (i = 0; i < info.downloading.length; i += 1) { + string += info.downloading[i].toFixed(0) + "% "; + } + + string += "(" + info.pending.toFixed(0) + " pending)"; + + return string; +} + + +// Get and log the current downloads info ... + +downloadInfo = GlobalServices.getDownloadInfo(); +print(formatInfo(downloadInfo)); + + +// Display and update the downloads info in an overlay ... + +function setUp() { + downloadInfoOverlay = Overlays.addOverlay("text", { + x: 300, + y: 200, + width: 300, + height: 50, + color: { red: 255, green: 255, blue: 255 }, + alpha: 1.0, + backgroundColor: { red: 127, green: 127, blue: 127 }, + backgroundAlpha: 0.5, + topMargin: 15, + leftMargin: 20, + text: "" + }); +} + +function updateInfo(info) { + Overlays.editOverlay(downloadInfoOverlay, { text: formatInfo(info) }); +} + +function tearDown() { + Overlays.deleteOverlay(downloadInfoOverlay); +} + +setUp(); +GlobalServices.downloadInfoChanged.connect(updateInfo); +GlobalServices.updateDownloadInfo(); +Script.scriptEnding.connect(tearDown); diff --git a/examples/example/entities/editModelExample.js b/examples/example/entities/editModelExample.js index 474d9afe26..2a58028521 100644 --- a/examples/example/entities/editModelExample.js +++ b/examples/example/entities/editModelExample.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var count = 0; var moveUntil = 2000; diff --git a/examples/example/entities/entityModelExample.js b/examples/example/entities/entityModelExample.js index d09a349cb0..aab2324496 100644 --- a/examples/example/entities/entityModelExample.js +++ b/examples/example/entities/entityModelExample.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var count = 0; var stopAfter = 1000; diff --git a/examples/example/games/spaceInvadersExample.js b/examples/example/games/spaceInvadersExample.js index 5ad8bbe4f6..80b03354ff 100644 --- a/examples/example/games/spaceInvadersExample.js +++ b/examples/example/games/spaceInvadersExample.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var iteration = 0; diff --git a/examples/example/ui/overlaysExample.js b/examples/example/ui/overlaysExample.js index 4e85512545..e4e8bf463b 100644 --- a/examples/example/ui/overlaysExample.js +++ b/examples/example/ui/overlaysExample.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; // The "Swatches" example of this script will create 9 different image overlays, that use the color feature to // display different colors as color swatches. The overlays can be clicked on, to change the "selectedSwatch" variable diff --git a/examples/gun.js b/examples/gun.js deleted file mode 100644 index c5b7b17052..0000000000 --- a/examples/gun.js +++ /dev/null @@ -1,418 +0,0 @@ -// -// gun.js -// examples -// -// Created by Brad Hefta-Gaub on 12/31/13. -// Modified by Philip on 3/3/14 -// Copyright 2013 High Fidelity, Inc. -// -// This is an example script that turns the hydra controllers and mouse into a entity gun. -// It reads the controller, watches for trigger pulls, and launches entities. -// When entities collide with voxels they blow little holes out of the voxels. -// -// 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("libraries/globals.js"); - -function getRandomFloat(min, max) { - return Math.random() * (max - min) + min; -} - -var lastX = 0; -var lastY = 0; -var yawFromMouse = 0; -var pitchFromMouse = 0; -var isMouseDown = false; - -var BULLET_VELOCITY = 20.0; -var MIN_THROWER_DELAY = 1000; -var MAX_THROWER_DELAY = 1000; -var LEFT_BUTTON_3 = 3; -var RELOAD_INTERVAL = 5; - -var KICKBACK_ANGLE = 15; -var elbowKickAngle = 0.0; -var rotationBeforeKickback; - -var showScore = false; - - -// Load some sound to use for loading and firing -var fireSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/GUN-SHOT2.raw"); -var loadSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/Gun_Reload_Weapon22.raw"); -var impactSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/BulletImpact2.raw"); -var targetHitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/hit.raw"); -var targetLaunchSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/shoot.raw"); - -var gunModel = "http://public.highfidelity.io/models/attachments/HaloGun.fst"; - -var audioOptions = { - volume: 0.9 -} - -var shotsFired = 0; -var shotTime = new Date(); - -var activeControllers = 0; - -// initialize our controller triggers -var triggerPulled = new Array(); -var numberOfTriggers = Controller.getNumberOfTriggers(); -for (t = 0; t < numberOfTriggers; t++) { - triggerPulled[t] = false; -} - -var isLaunchButtonPressed = false; -var score = 0; - -var bulletID = false; -var targetID = false; - -// Create a reticle image in center of screen -var screenSize = Controller.getViewportDimensions(); -var reticle = Overlays.addOverlay("image", { - x: screenSize.x / 2 - 16, - y: screenSize.y / 2 - 16, - width: 32, - height: 32, - imageURL: HIFI_PUBLIC_BUCKET + "images/reticle.png", - color: { red: 255, green: 255, blue: 255}, - alpha: 1 - }); - -var offButton = Overlays.addOverlay("image", { - x: screenSize.x - 48, - y: 96, - width: 32, - height: 32, - imageURL: HIFI_PUBLIC_BUCKET + "images/close.png", - color: { red: 255, green: 255, blue: 255}, - alpha: 1 - }); - -if (showScore) { - var text = Overlays.addOverlay("text", { - x: screenSize.x / 2 - 100, - y: screenSize.y / 2 - 50, - width: 150, - height: 50, - color: { red: 0, green: 0, blue: 0}, - textColor: { red: 255, green: 0, blue: 0}, - topMargin: 4, - leftMargin: 4, - text: "Score: " + score - }); -} - - - -function printVector(string, vector) { - print(string + " " + vector.x + ", " + vector.y + ", " + vector.z); -} - -function shootBullet(position, velocity) { - var BULLET_SIZE = 0.07; - var BULLET_LIFETIME = 10.0; - var BULLET_GRAVITY = -0.02; - bulletID = Entities.addEntity( - { type: "Sphere", - position: position, - dimensions: { x: BULLET_SIZE, y: BULLET_SIZE, z: BULLET_SIZE }, - color: { red: 255, green: 0, blue: 0 }, - velocity: velocity, - lifetime: BULLET_LIFETIME, - gravity: { x: 0, y: BULLET_GRAVITY, z: 0 }, - ignoreCollisions: false, - collisionsWillMove: true - }); - - // Play firing sounds - audioOptions.position = position; - Audio.playSound(fireSound, audioOptions); - shotsFired++; - if ((shotsFired % RELOAD_INTERVAL) == 0) { - Audio.playSound(loadSound, audioOptions); - } - - // Kickback the arm - rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm"); - var armRotation = MyAvatar.getJointRotation("LeftForeArm"); - armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE)); - MyAvatar.setJointData("LeftForeArm", armRotation); - elbowKickAngle = KICKBACK_ANGLE; -} - -function shootTarget() { - var TARGET_SIZE = 0.50; - var TARGET_GRAVITY = -0.25; - var TARGET_LIFETIME = 300.0; - var TARGET_UP_VELOCITY = 0.5; - var TARGET_FWD_VELOCITY = 1.0; - var DISTANCE_TO_LAUNCH_FROM = 3.0; - var ANGLE_RANGE_FOR_LAUNCH = 20.0; - var camera = Camera.getPosition(); - //printVector("camera", camera); - var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 }); - targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection); - var forwardVector = Quat.getFront(targetDirection); - - var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM)); - - var velocity = Vec3.multiply(forwardVector, TARGET_FWD_VELOCITY); - velocity.y += TARGET_UP_VELOCITY; - - targetID = Entities.addEntity( - { type: "Box", - position: newPosition, - dimensions: { x: TARGET_SIZE, y: TARGET_SIZE, z: TARGET_SIZE }, - color: { red: 0, green: 200, blue: 200 }, - //angularVelocity: { x: 1, y: 0, z: 0 }, - velocity: velocity, - gravity: { x: 0, y: TARGET_GRAVITY, z: 0 }, - lifetime: TARGET_LIFETIME, - damping: 0.0001, - collisionsWillMove: true }); - - // Record start time - shotTime = new Date(); - - // Play target shoot sound - audioOptions.position = newPosition; - Audio.playSound(targetLaunchSound, audioOptions); -} - - - -function entityCollisionWithEntity(entity1, entity2, collision) { - - if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) && - ((entity2.id == bulletID.id) || (entity2.id == targetID.id))) { - score++; - if (showScore) { - Overlays.editOverlay(text, { text: "Score: " + score } ); - } - - // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! - Script.setTimeout(deleteBulletAndTarget, 500); - - // Turn the target and the bullet white - Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); - Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); - - // play the sound near the camera so the shooter can hear it - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - Audio.playSound(targetHitSound, audioOptions); - } -} - -function keyPressEvent(event) { - // if our tools are off, then don't do anything - if (event.text == "t") { - var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; - Script.setTimeout(shootTarget, time); - } else if (event.text == ".") { - shootFromMouse(); - } else if (event.text == "r") { - playLoadSound(); - } else if (event.text == "s") { - // Hit this key to dump a posture from hydra to log - Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm")); - Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm")); - Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand")); - - } -} - -function playLoadSound() { - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - Audio.playSound(loadSound, audioOptions); - // Raise arm to firing posture - takeFiringPose(); -} - -function clearPose() { - MyAvatar.clearJointData("LeftForeArm"); - MyAvatar.clearJointData("LeftArm"); - MyAvatar.clearJointData("LeftHand"); -} - -function deleteBulletAndTarget() { - Entities.deleteEntity(bulletID); - Entities.deleteEntity(targetID); - bulletID = false; - targetID = false; -} - -function takeFiringPose() { - clearPose(); - if (Controller.getNumberOfSpatialControls() == 0) { - MyAvatar.setJointData("LeftForeArm", {x: -0.251919, y: -0.0415449, z: 0.499487, w: 0.827843}); - MyAvatar.setJointData("LeftArm", { x: 0.470196, y: -0.132559, z: 0.494033, w: 0.719219}); - MyAvatar.setJointData("LeftHand", { x: -0.0104815, y: -0.110551, z: -0.352111, w: 0.929333}); - } -} - -MyAvatar.attach(gunModel, "LeftHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20); - -// Give a bit of time to load before playing sound -Script.setTimeout(playLoadSound, 2000); - -function update(deltaTime) { - if (bulletID && !bulletID.isKnownID) { - print("Trying to identify bullet"); - bulletID = Entities.identifyEntity(bulletID); - } - if (targetID && !targetID.isKnownID) { - targetID = Entities.identifyEntity(targetID); - } - // Check for mouseLook movement, update rotation - // rotate body yaw for yaw received from mouse - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } )); - //MyAvatar.orientation = newOrientation; - yawFromMouse = 0; - - // apply pitch from mouse - var newPitch = MyAvatar.headPitch + pitchFromMouse; - //MyAvatar.headPitch = newPitch; - pitchFromMouse = 0; - - - if (activeControllers == 0) { - if (Controller.getNumberOfSpatialControls() > 0) { - activeControllers = Controller.getNumberOfSpatialControls(); - clearPose(); - } - } - - var KICKBACK_DECAY_RATE = 0.125; - if (elbowKickAngle > 0.0) { - if (elbowKickAngle > 0.5) { - var newAngle = elbowKickAngle * KICKBACK_DECAY_RATE; - elbowKickAngle -= newAngle; - var armRotation = MyAvatar.getJointRotation("LeftForeArm"); - armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, -newAngle)); - MyAvatar.setJointData("LeftForeArm", armRotation); - } else { - MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback); - if (Controller.getNumberOfSpatialControls() > 0) { - clearPose(); - } - elbowKickAngle = 0.0; - } - } - - // Check hydra controller for launch button press - if (!isLaunchButtonPressed && Controller.isButtonPressed(LEFT_BUTTON_3)) { - isLaunchButtonPressed = true; - var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; - Script.setTimeout(shootTarget, time); - } else if (isLaunchButtonPressed && !Controller.isButtonPressed(LEFT_BUTTON_3)) { - isLaunchButtonPressed = false; - - } - - // check for trigger press - - var numberOfTriggers = 2; - var controllersPerTrigger = 2; - - if (numberOfTriggers == 2 && controllersPerTrigger == 2) { - for (var t = 0; t < 2; t++) { - var shootABullet = false; - var triggerValue = Controller.getTriggerValue(t); - if (triggerPulled[t]) { - // must release to at least 0.1 - if (triggerValue < 0.1) { - triggerPulled[t] = false; // unpulled - } - } else { - // must pull to at least - if (triggerValue > 0.5) { - triggerPulled[t] = true; // pulled - shootABullet = true; - } - } - - if (shootABullet) { - var palmController = t * controllersPerTrigger; - var palmPosition = Controller.getSpatialControlPosition(palmController); - - var fingerTipController = palmController + 1; - var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController); - - var palmToFingerTipVector = - { x: (fingerTipPosition.x - palmPosition.x), - y: (fingerTipPosition.y - palmPosition.y), - z: (fingerTipPosition.z - palmPosition.z) }; - - // just off the front of the finger tip - var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2, - y: fingerTipPosition.y + palmToFingerTipVector.y/2, - z: fingerTipPosition.z + palmToFingerTipVector.z/2}; - - var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector)); - - shootBullet(position, velocity); - } - } - } -} - -function mousePressEvent(event) { - isMouseDown = true; - lastX = event.x; - lastY = event.y; - - if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === offButton) { - Script.stop(); - } else { - shootFromMouse(); - } -} - -function shootFromMouse() { - var DISTANCE_FROM_CAMERA = 2.0; - var camera = Camera.getPosition(); - var forwardVector = Quat.getFront(Camera.getOrientation()); - var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA)); - var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY); - shootBullet(newPosition, velocity); -} - -function mouseReleaseEvent(event) { - // position - isMouseDown = false; -} - -function mouseMoveEvent(event) { - if (isMouseDown) { - var MOUSE_YAW_SCALE = -0.25; - var MOUSE_PITCH_SCALE = -12.5; - var FIXED_MOUSE_TIMESTEP = 0.016; - yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP); - pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP); - lastX = event.x; - lastY = event.y; - } -} - -function scriptEnding() { - Overlays.deleteOverlay(reticle); - Overlays.deleteOverlay(offButton); - Overlays.deleteOverlay(text); - MyAvatar.detachOne(gunModel); - clearPose(); -} - -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); -Script.scriptEnding.connect(scriptEnding); -Script.update.connect(update); -Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); -Controller.mouseMoveEvent.connect(mouseMoveEvent); -Controller.keyPressEvent.connect(keyPressEvent); - - - diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index a3644d8c3d..d572ef1ad4 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -31,8 +31,13 @@ } function createEmitTextPropertyUpdateFunction(propertyName) { return function() { + var properties = {}; + properties[propertyName] = this.value; EventBridge.emitWebEvent( - '{ "type":"update", "properties":{"' + propertyName + '":"' + this.value + '"}}' + JSON.stringify({ + type: "update", + properties: properties, + }) ); }; } @@ -84,6 +89,8 @@ var elDimensionsY = document.getElementById("property-dim-y"); var elDimensionsZ = document.getElementById("property-dim-z"); var elResetToNaturalDimensions = document.getElementById("reset-to-natural-dimensions"); + var elRescaleDimensionsPct = document.getElementById("dimension-rescale-pct"); + var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button"); var elRegistrationX = document.getElementById("property-reg-x"); var elRegistrationY = document.getElementById("property-reg-y"); @@ -107,7 +114,7 @@ var elGravityY = document.getElementById("property-grav-y"); var elGravityZ = document.getElementById("property-grav-z"); - var elMass = document.getElementById("property-mass"); + var elDensity = document.getElementById("property-density"); var elIgnoreForCollisions = document.getElementById("property-ignore-for-collisions"); var elCollisionsWillMove = document.getElementById("property-collisions-will-move"); var elLifetime = document.getElementById("property-lifetime"); @@ -144,6 +151,9 @@ var elModelAnimationPlaying = document.getElementById("property-model-animation-playing"); var elModelAnimationFPS = document.getElementById("property-model-animation-fps"); var elModelAnimationFrame = document.getElementById("property-model-animation-frame"); + var elModelAnimationSettings = document.getElementById("property-model-animation-settings"); + var elModelTextures = document.getElementById("property-model-textures"); + var elModelOriginalTextures = document.getElementById("property-model-original-textures"); var elTextSections = document.querySelectorAll(".text-section"); var elTextText = document.getElementById("property-text-text"); @@ -171,10 +181,10 @@ elLocked.checked = properties.locked; if (properties.locked) { - disableChildren(document.getElementById("properties-table"), 'input'); + disableChildren(document.getElementById("properties-list"), 'input'); elLocked.removeAttribute('disabled'); } else { - enableChildren(document.getElementById("properties-table"), 'input'); + enableChildren(document.getElementById("properties-list"), 'input'); } elVisible.checked = properties.visible; @@ -209,7 +219,7 @@ elGravityY.value = properties.gravity.y.toFixed(2); elGravityZ.value = properties.gravity.z.toFixed(2); - elMass.value = properties.mass.toFixed(2); + elDensity.value = properties.density.toFixed(2); elIgnoreForCollisions.checked = properties.ignoreForCollisions; elCollisionsWillMove.checked = properties.collisionsWillMove; elLifetime.value = properties.lifetime; @@ -221,7 +231,7 @@ } } else { for (var i = 0; i < elBoxSections.length; i++) { - elBoxSections[i].style.display = 'table-row'; + elBoxSections[i].style.display = 'block'; } elBoxColorRed.value = properties.color.red; @@ -235,13 +245,17 @@ } } else { for (var i = 0; i < elModelSections.length; i++) { - elModelSections[i].style.display = 'table-row'; + elModelSections[i].style.display = 'block'; } elModelURL.value = properties.modelURL; elModelAnimationURL.value = properties.animationURL; elModelAnimationPlaying.checked = properties.animationIsPlaying; elModelAnimationFPS.value = properties.animationFPS; + elModelAnimationFrame.value = properties.animationFrameIndex; + elModelAnimationSettings.value = properties.animationSettings; + elModelTextures.value = properties.textures; + elModelOriginalTextures.value = properties.originalTextures; } if (properties.type != "Text") { @@ -250,7 +264,7 @@ } } else { for (var i = 0; i < elTextSections.length; i++) { - elTextSections[i].style.display = 'table-row'; + elTextSections[i].style.display = 'block'; } elTextText.value = properties.text; @@ -269,7 +283,7 @@ } } else { for (var i = 0; i < elLightSections.length; i++) { - elLightSections[i].style.display = 'table-row'; + elLightSections[i].style.display = 'block'; } elLightDiffuseRed.value = properties.diffuseColor.red; @@ -342,7 +356,7 @@ elGravityY.addEventListener('change', gravityChangeFunction); elGravityZ.addEventListener('change', gravityChangeFunction); - elMass.addEventListener('change', createEmitNumberPropertyUpdateFunction('mass')); + elDensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('density')); elIgnoreForCollisions.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ignoreForCollisions')); elCollisionsWillMove.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionsWillMove')); elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime')); @@ -385,6 +399,8 @@ elModelAnimationPlaying.addEventListener('change', createEmitCheckedPropertyUpdateFunction('animationIsPlaying')); elModelAnimationFPS.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFPS')); elModelAnimationFrame.addEventListener('change', createEmitNumberPropertyUpdateFunction('animationFrameIndex')); + elModelAnimationSettings.addEventListener('change', createEmitTextPropertyUpdateFunction('animationSettings')); + elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); @@ -419,343 +435,323 @@ action: "resetToNaturalDimensions", })); }); - - - var resizing = false; - var startX = 0; - var originalWidth = 0; - var resizeHandleWidth = 10; - - var col1 = document.querySelector("#col-label"); - - document.body.addEventListener('mousemove', function(event) { - if (resizing) { - var dX = event.x - startX; - col1.style.width = (originalWidth + dX) + "px"; - } + elRescaleDimensionsButton.addEventListener("click", function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseInt(elRescaleDimensionsPct.value), + })); }); - document.body.addEventListener('mouseup', function(event) { - resizing = false; - }); - document.body.addEventListener('mouseleave', function(event) { - resizing = false; - }); - var els = document.querySelectorAll("#properties-table td"); - for (var i = 0; i < els.length; i++) { - var el = els[i]; - el.addEventListener('mousemove', function(event) { - if (!resizing) { - var distance = this.offsetWidth - event.offsetX; - if (distance < resizeHandleWidth) { - document.body.style.cursor = "ew-resize"; - } else { - document.body.style.cursor = "initial"; - } - } - }); - el.addEventListener('mousedown', function(event) { - var distance = this.offsetWidth - event.offsetX; - if (distance < resizeHandleWidth) { - startX = event.x; - originalWidth = this.offsetWidth; - resizing = true; - target = this; - } - }); - } - } - -
- -
- - - - - - - - - - - - - - - - - + + - - - - + + - - - - + + - - - - + + - - - - +
+ % +
+ + + + + - - - - + + - - - - - - - - - - - - - - - - + + - - - - + + - - - - + + - - - - + + - - - - + + - - - - + + - - - - + + - - - - + + - - - - - - - - - - - - - - - - - - - - + + +
+
Animation Settings
+
+ +
+
+
+
Textures
+
+ +
+
+
+
Original Textures
+
+ +
+
- - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ID - + +
+
+
+ +
+
+
+
-
- Type - - -
Locked + + +
+
Locked
+
-
Visible +
+
Visible
+
-
Position -
X
-
Y
-
Z
+
+
Position
+
+
X
+
Y
+
Z
-
Registration +
+
Registration
+
X
Y
Z
-
Dimensions +
+
Dimensions
+
X
Y
Z
-
Rotation +
+
Rotation
+
Pitch
Yaw
Roll
-
Linear Velocity +
+
Linear Velocity
+
X
Y
Z
-
Linear Damping + + +
+
Linear Damping
+
-
Angular Velocity + + +
+
Angular Velocity
+
Pitch
Yaw
Roll
-
Angular Damping + + +
+
Angular Damping
+
-
Gravity +
+
Gravity
+
X
Y
Z
-
Mass +
+
Mass
+
-
Ignore For Collisions +
+
Density
+
+ +
+
+ +
+
Ignore For Collisions
+
-
Collisions Will Move +
+
Collisions Will Move
+
-
Lifetime +
+
Lifetime
+
-
Script URL +
+
Script URL
+
-
Color +
+
Color
+
R
G
B
-
Model URL +
+
Model URL
+
-
Animation URL + + +
+
Animation URL
+
-
Animation Playing + + +
+
Animation Playing
+
-
Animation FPS + + +
+
Animation FPS
+
-
Animation Frame + + +
+
Animation Frame
+
-
Text +
+
Text
+
-
Line Height + + +
+
Line Height
+
-
Text Color + + +
+
Text Color
+
R
G
B
-
Background Color + + +
+
Background Color
+
R
G
B
-
Spot Light +
+
Spot Light
+
-
Diffuse + + +
+
Diffuse
+
R
G
B
-
Ambient + + +
+
Ambient
+
R
G
B
-
Specular + + +
+
Specular
+
R
G
B
-
Constant Attenuation + + +
+
Constant Attenuation
+
-
Linear Attenuation + + +
+
Linear Attenuation
+
-
Quadratic Attenuation + + +
+
Quadratic Attenuation
+
-
Exponent + + +
+
Exponent
+
-
Cutoff (degrees) + + +
+
Cutoff (degrees)
+
-
+ + + diff --git a/examples/html/style.css b/examples/html/style.css index 2f43b1c356..5023bb4609 100644 --- a/examples/html/style.css +++ b/examples/html/style.css @@ -6,8 +6,8 @@ body { padding: 0; background-color: #efefef; - font-family: Sans-Serif; - font-size: 12px; + font-family: Arial; + font-size: 11.5px; -webkit-touch-callout: none; -webkit-user-select: none; @@ -17,6 +17,12 @@ body { user-select: none; } +body.properties { + background-color: rgb(76, 76, 76); + color: rgb(204, 204, 204); + font-size: 11px; +} + .selectable { -webkit-touch-callout: text; -webkit-user-select: text; @@ -85,25 +91,33 @@ input[type=button] { border: 0; color: #fff; font-weight: bold; - margin: 0 2px; - margin-top: 5px; - font-size: .9em; } -input { +textarea, input { + margin: 0; padding: 2px; border: 1px solid #999; background-color: #eee; } +textarea { + width: 100%; + resize: vertical; +} + input.url { width: 100%; } +input.coord { + width: 7em; + display: block; +} + table#entity-table { border-collapse: collapse; font-family: Sans-Serif; - font-size: 12px; + /* font-size: 12px; */ width: 100%; } @@ -140,31 +154,62 @@ th#entity-url { div.input-area { display: inline-block; + font-size: 10px; } input { } +#type { + font-size: 14px; +} + +#type label { + color: rgb(150, 150, 150); +} + +#properties-list input, #properties-list textarea { + background-color: rgb(102, 102, 102); + color: rgb(204, 204, 204); + border: none; + font-size: 10px; +} + +#properties-list input[type=button] { + cursor: pointer; + background-color: rgb(51, 102, 102); + border-color: #608e96; + border-radius: 5px; + padding: 5px 10px; + border: 0; + color: rgb(204, 204, 204); +} + +#properties-list .property { + padding: 8px 8px; + border-top: 1px solid rgb(63, 63, 63); + min-height: 1em; +} -table#properties-table { +table#properties-list { border: none; border-collapse: collapse; width: 100%; background-color: #efefef; font-family: Arial; - font-size: 12px; table-layout: fixed; } -#properties-table tr { +#properties-list > div { + margin: 4px 0; +} + +#properties-list { border-bottom: 1px solid #e5e5e5; } -#properties-table td.label { - padding-right: 10px; - border-right: 1px solid #999; - text-align: right; +#properties-list .label { font-weight: bold; white-space: nowrap; overflow: hidden; @@ -174,8 +219,8 @@ table#properties-table { height: 1.2em; } -#properties-table td { - padding: 5px; +#properties-list .value > div{ + padding: 4px 0; } col#col-label { diff --git a/examples/libraries/ToolTip.js b/examples/libraries/ToolTip.js index 590eba36d0..f12525af57 100644 --- a/examples/libraries/ToolTip.js +++ b/examples/libraries/ToolTip.js @@ -68,7 +68,7 @@ function Tooltip() { text += "Lifetime: " + properties.lifetime + "\n" } text += "Age: " + properties.ageAsText + "\n" - text += "Mass: " + properties.mass + "\n" + text += "Density: " + properties.density + "\n" text += "Script: " + properties.script + "\n" diff --git a/examples/libraries/entityCameraTool.js b/examples/libraries/entityCameraTool.js index b6a86e7a4d..b9170dc25d 100644 --- a/examples/libraries/entityCameraTool.js +++ b/examples/libraries/entityCameraTool.js @@ -105,6 +105,8 @@ CameraManager = function() { Camera.mode = "independent"; that.updateCamera(); + + cameraTool.setVisible(true); } that.disable = function(ignoreCamera) { @@ -115,6 +117,7 @@ CameraManager = function() { if (!ignoreCamera) { Camera.mode = that.previousCameraMode; } + cameraTool.setVisible(false); } that.focus = function(position, dimensions, easeOrientation) { @@ -243,9 +246,9 @@ CameraManager = function() { } that.mousePressEvent = function(event) { - // if (cameraTool.mousePressEvent(event)) { - // return true; - // } + if (cameraTool.mousePressEvent(event)) { + return true; + } if (!that.enabled) return; @@ -291,7 +294,7 @@ CameraManager = function() { that.updateCamera = function() { if (!that.enabled || Camera.mode != "independent") { - // cameraTool.update(); + cameraTool.update(); return; } @@ -313,7 +316,7 @@ CameraManager = function() { Camera.setOrientation(q); - // cameraTool.update(); + cameraTool.update(); } function normalizeDegrees(degrees) { @@ -383,7 +386,7 @@ CameraManager = function() { Controller.wheelEvent.connect(that.wheelEvent); - // var cameraTool = new CameraTool(that); + var cameraTool = new CameraTool(that); return that; } @@ -395,43 +398,21 @@ CameraTool = function(cameraManager) { var GREEN = { red: 26, green: 193, blue: 105 }; var BLUE = { red: 0, green: 131, blue: 204 }; - var ORIENTATION_OVERLAY_SIZE = 20; + var BORDER_WIDTH = 1; + + var ORIENTATION_OVERLAY_SIZE = 26; var ORIENTATION_OVERLAY_HALF_SIZE = ORIENTATION_OVERLAY_SIZE / 2; - var ORIENTATION_OVERLAY_CUBE_SIZE = 8, + var ORIENTATION_OVERLAY_CUBE_SIZE = 10.5, var ORIENTATION_OVERLAY_OFFSET = { - x: 96, + x: 30, y: 30, } - var UI_URL = HIFI_PUBLIC_BUCKET + "images/tools/camera-controls.svg"; - - var UI_WIDTH = 128; - var UI_HEIGHT = 61; + var UI_WIDTH = 70; + var UI_HEIGHT = 70; var UI_PADDING = 10; - var UI_BUTTON_WIDTH = 64; - var UI_BUTTON_HEIGHT = 30; - - var UI_SUBIMAGE_FIRST_PERSON = { - x: 0, - y: 0, - width: UI_WIDTH, - height: UI_HEIGHT - }, - var UI_SUBIMAGE_THIRD_PERSON = { - x: 0, - y: UI_HEIGHT, - width: UI_WIDTH, - height: UI_HEIGHT - }, - var UI_SUBIMAGE_OTHER = { - x: 0, - y: UI_HEIGHT * 2, - width: UI_WIDTH, - height: UI_HEIGHT - }, - var lastKnownWidth = Window.innerWidth; var uiPosition = { @@ -439,20 +420,28 @@ CameraTool = function(cameraManager) { y: UI_PADDING, }; - var ui = Overlays.addOverlay("image", { - imageURL: UI_URL, + var backgroundBorder = Overlays.addOverlay("text", { + x: uiPosition.x - BORDER_WIDTH, + y: uiPosition.y - BORDER_WIDTH, + width: UI_WIDTH + BORDER_WIDTH * 2, + height: UI_HEIGHT + BORDER_WIDTH * 2, + alpha: 0, + text: "", + backgroundColor: { red: 101, green: 101, blue: 101 }, + backgroundAlpha: 1.0, + visible: false, + }); + + var background = Overlays.addOverlay("text", { x: uiPosition.x, y: uiPosition.y, - subImage: { - x: 0, - y: 0, - width: UI_WIDTH, - height: UI_HEIGHT - }, width: UI_WIDTH, height: UI_HEIGHT, - alpha: 1.0, - visible: true + alpha: 0, + text: "", + backgroundColor: { red: 51, green: 51, blue: 51 }, + backgroundAlpha: 1.0, + visible: false, }); var defaultCubeProps = { @@ -470,15 +459,16 @@ CameraTool = function(cameraManager) { start: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, color: { red: 255, green: 0, blue: 0 }, - visible: true, + visible: false, drawOnHUD: true, }; var orientationOverlay = OverlayGroup({ position: { - x: uiPosition.x + ORIENTATION_OVERLAY_OFFSET.x, - y: uiPosition.y + ORIENTATION_OVERLAY_OFFSET.y, - } + x: uiPosition.x + UI_WIDTH / 2, + y: uiPosition.y + UI_HEIGHT / 2, + }, + visible: false, }); var OOHS = ORIENTATION_OVERLAY_HALF_SIZE; @@ -512,7 +502,8 @@ CameraTool = function(cameraManager) { Script.scriptEnding.connect(function() { orientationOverlay.destroy(); - Overlays.deleteOverlay(ui); + Overlays.deleteOverlay(background); + Overlays.deleteOverlay(backgroundBorder); }); var flip = Quat.fromPitchYawRollDegrees(0, 180, 0); @@ -527,16 +518,20 @@ CameraTool = function(cameraManager) { x: lastKnownWidth - UI_WIDTH - UI_PADDING, y: UI_PADDING, }; - Overlays.editOverlay(ui, { - x: uiPosition.x, - y: uiPosition.y - }); orientationOverlay.setProperties({ position: { x: uiPosition.x + ORIENTATION_OVERLAY_OFFSET.x, y: uiPosition.y + ORIENTATION_OVERLAY_OFFSET.y, } }); + Overlays.editOverlay(backgroundBorder, { + x: uiPosition.x - BORDER_WIDTH, + y: uiPosition.y - BORDER_WIDTH, + }); + Overlays.editOverlay(background, { + x: uiPosition.x, + y: uiPosition.y, + }); } } @@ -558,40 +553,16 @@ CameraTool = function(cameraManager) { targetYaw = event.isLeftButton ? 0 : 180; cameraManager.setTargetPitchYaw(targetPitch, targetYaw); return true; - } else if (clickedOverlay == ui) { - var x = event.x - uiPosition.x; - var y = event.y - uiPosition.y; - - // Did we hit a button? - if (x < UI_BUTTON_WIDTH) { - if (y < UI_BUTTON_HEIGHT) { - Camera.mode = "first person"; - } else { - Camera.mode = "third person"; - } - } - return true; } }; - function updateMode() { - var mode = Camera.mode; - - var subImage = UI_SUBIMAGE_OTHER; - if (mode == "first person") { - subImage = UI_SUBIMAGE_FIRST_PERSON; - } else if (mode == "third person") { - subImage = UI_SUBIMAGE_THIRD_PERSON; - } - - Overlays.editOverlay(ui, { subImage: subImage }); - } - - Camera.modeUpdated.connect(updateMode); - updateMode(); - that.setVisible = function(visible) { + orientationOverlay.setProperties({ visible: visible }); + Overlays.editOverlay(background, { visible: visible }); + Overlays.editOverlay(backgroundBorder, { visible: visible }); }; + that.setVisible(false); + return that; }; diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index a686ebb0f2..0cb76276d8 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -164,7 +164,7 @@ EntityPropertyDialogBox = (function () { array.push({ label: "Collisions:", type: "header" }); index++; - array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) }); + array.push({ label: "Density:", value: properties.density.toFixed(decimals) }); index++; array.push({ label: "Ignore for Collisions:", type: "checkbox", value: properties.ignoreForCollisions }); index++; @@ -353,7 +353,7 @@ EntityPropertyDialogBox = (function () { properties.gravity.z = array[index++].value; index++; // skip header - properties.mass = array[index++].value; + properties.density = array[index++].value; properties.ignoreForCollisions = array[index++].value; properties.collisionsWillMove = array[index++].value; diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 2b83d7740d..4b931f0c57 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; SPACE_LOCAL = "local"; SPACE_WORLD = "world"; @@ -242,6 +242,9 @@ SelectionDisplay = (function () { var ROTATION_DISPLAY_SIZE_Y_MULTIPLIER = 0.18; var ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.17; + var ROTATE_ARROW_WEST_NORTH_URL = HIFI_PUBLIC_BUCKET + "images/rotate-arrow-west-north.png"; + var ROTATE_ARROW_WEST_SOUTH_URL = HIFI_PUBLIC_BUCKET + "images/rotate-arrow-west-south.png"; + var showExtendedStretchHandles = false; var spaceMode = SPACE_LOCAL; @@ -590,7 +593,7 @@ SelectionDisplay = (function () { }); var yawHandle = Overlays.addOverlay("billboard", { - url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png", + url: ROTATE_ARROW_WEST_NORTH_URL, position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -603,7 +606,7 @@ SelectionDisplay = (function () { var pitchHandle = Overlays.addOverlay("billboard", { - url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png", + url: ROTATE_ARROW_WEST_NORTH_URL, position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -616,7 +619,7 @@ SelectionDisplay = (function () { var rollHandle = Overlays.addOverlay("billboard", { - url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png", + url: ROTATE_ARROW_WEST_NORTH_URL, position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -835,8 +838,8 @@ SelectionDisplay = (function () { rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far }; - Overlays.editOverlay(pitchHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-south.png" }); - Overlays.editOverlay(rollHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-south.png" }); + Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_SOUTH_URL }); + Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_SOUTH_URL }); } else { @@ -867,8 +870,8 @@ SelectionDisplay = (function () { pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z }; rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near}; - Overlays.editOverlay(pitchHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png" }); - Overlays.editOverlay(rollHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); + Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); } } else { @@ -899,8 +902,8 @@ SelectionDisplay = (function () { pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z }; rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far}; - Overlays.editOverlay(pitchHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png" }); - Overlays.editOverlay(rollHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); + Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); } else { @@ -928,8 +931,8 @@ SelectionDisplay = (function () { rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near }; pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z}; - Overlays.editOverlay(pitchHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png" }); - Overlays.editOverlay(rollHandle, { url: "https://public.highfidelity.io/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); + Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); } } diff --git a/examples/libraries/overlayUtils.js b/examples/libraries/overlayUtils.js index 7623bfbb30..636ea40825 100644 --- a/examples/libraries/overlayUtils.js +++ b/examples/libraries/overlayUtils.js @@ -9,7 +9,7 @@ OverlayGroup = function(opts) { var rootPosition = opts.position || { x: 0, y: 0, z: 0 }; var rootRotation = opts.rotation || Quat.fromPitchYawRollRadians(0, 0, 0); - var visible = true; + var visible = opts.visible == true; function updateOverlays() { for (overlayID in overlays) { diff --git a/examples/lobby.js b/examples/lobby.js index e34d119502..fcac7a490b 100644 --- a/examples/lobby.js +++ b/examples/lobby.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var panelWall = false; var orbShell = false; @@ -66,20 +66,20 @@ function textOverlayPosition() { Vec3.multiply(Quat.getUp(Camera.orientation), TEXT_DISTANCE_DOWN)); } -var panelLocationOrder = [ +var panelPlaceOrder = [ 7, 8, 9, 10, 11, 12, 13, 0, 1, 2, 3, 4, 5, 6, 14, 15, 16, 17, 18, 19, 20 ]; -// Location index is 0-based -function locationIndexToPanelIndex(locationIndex) { - return panelLocationOrder.indexOf(locationIndex) + 1; +// place index is 0-based +function placeIndexToPanelIndex(placeIndex) { + return panelPlaceOrder.indexOf(placeIndex) + 1; } // Panel index is 1-based -function panelIndexToLocationIndex(panelIndex) { - return panelLocationOrder[panelIndex - 1]; +function panelIndexToPlaceIndex(panelIndex) { + return panelPlaceOrder[panelIndex - 1]; } var MAX_NUM_PANELS = 21; @@ -138,33 +138,34 @@ function drawLobby() { // add an attachment on this avatar so other people see them in the lobby MyAvatar.attach(HELMET_ATTACHMENT_URL, "Neck", {x: 0, y: 0, z: 0}, Quat.fromPitchYawRollDegrees(0, 0, 0), 1.15); - // start the drone sound - // currentDrone = Audio.playSound(droneSound, { stereo: true, loop: true, localOnly: true, volume: DRONE_VOLUME }); + if (droneSound.downloaded) { + // start the drone sound + currentDrone = Audio.playSound(droneSound, { stereo: true, loop: true, localOnly: true, volume: DRONE_VOLUME }); + } // start one of our muzak sounds - // playRandomMuzak(); + playRandomMuzak(); } } -var locations = {}; +var places = {}; function changeLobbyTextures() { var req = new XMLHttpRequest(); - req.open("GET", "https://data.highfidelity.io/api/v1/locations?limit=21", false); + req.open("GET", "https://data.highfidelity.io/api/v1/places?limit=21", false); req.send(); - locations = JSON.parse(req.responseText).data.locations; + places = JSON.parse(req.responseText).data.places; - var NUM_PANELS = locations.length; + var NUM_PANELS = places.length; var textureProp = { textures: {} }; for (var j = 0; j < NUM_PANELS; j++) { - var panelIndex = locationIndexToPanelIndex(j); - textureProp["textures"]["file" + panelIndex] = HIFI_PUBLIC_BUCKET + "images/locations/" - + locations[j].id + "/hifi-location-" + locations[j].id + "_640x360.jpg"; + var panelIndex = placeIndexToPanelIndex(j); + textureProp["textures"]["file" + panelIndex] = places[j].previews.lobby; }; Overlays.editOverlay(panelWall, textureProp); @@ -232,7 +233,7 @@ function cleanupLobby() { Audio.stopInjector(currentMuzakInjector); currentMuzakInjector = null; - locations = {}; + places = {}; toggleEnvironmentRendering(true); MyAvatar.detachOne(HELMET_ATTACHMENT_URL); @@ -250,14 +251,14 @@ function actionStartEvent(event) { var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { var panelIndex = parseInt(panelName.slice(5)); - var locationIndex = panelIndexToLocationIndex(panelIndex); - if (locationIndex < locations.length) { - var actionLocation = locations[locationIndex]; + var placeIndex = panelIndexToPlaceIndex(panelIndex); + if (placeIndex < places.length) { + var actionPlace = places[placeIndex]; - print("Jumping to " + actionLocation.name + " at " + actionLocation.path - + " in " + actionLocation.domain.name + " after click on panel " + panelIndex + " with location index " + locationIndex); + print("Jumping to " + actionPlace.name + " at " + actionPlace.address + + " after click on panel " + panelIndex + " with place index " + placeIndex); - Window.location = actionLocation; + Window.location = actionPlace.address; maybeCleanupLobby(); } } @@ -300,15 +301,15 @@ function handleLookAt(pickRay) { var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { var panelIndex = parseInt(panelName.slice(5)); - var locationIndex = panelIndexToLocationIndex(panelIndex); - if (locationIndex < locations.length) { - var actionLocation = locations[locationIndex]; + var placeIndex = panelIndexToPlaceIndex(panelIndex); + if (placeIndex < places.length) { + var actionPlace = places[placeIndex]; - if (actionLocation.description == "") { - Overlays.editOverlay(descriptionText, { text: actionLocation.name, visible: showText }); + if (actionPlace.description == "") { + Overlays.editOverlay(descriptionText, { text: actionPlace.name, visible: showText }); } else { // handle line wrapping - var allWords = actionLocation.description.split(" "); + var allWords = actionPlace.description.split(" "); var currentGoodLine = ""; var currentTestLine = ""; var formatedDescription = ""; @@ -353,9 +354,9 @@ function update(deltaTime) { Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); // if the reticle is up then we may need to play the next muzak - // if (!Audio.isInjectorPlaying(currentMuzakInjector)) { -// playNextMuzak(); -// } + if (currentMuzakInjector && !Audio.isInjectorPlaying(currentMuzakInjector)) { + playNextMuzak(); + } } } diff --git a/examples/realsenseHands.js b/examples/realsenseHands.js new file mode 100644 index 0000000000..6b1a5c0a23 --- /dev/null +++ b/examples/realsenseHands.js @@ -0,0 +1,525 @@ +// +// realsenseHands.js +// examples +// +// Created by Thijs Wenker on 18 Dec 2014. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that uses the Intel RealSense to make the avatar's hands replicate the user's hand actions. +// Most of this script is copied from leapHands.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var leapHands = (function () { + + var isOnHMD, + MotionTracker = "RealSense", + LEAP_ON_HMD_MENU_ITEM = "Leap Motion on HMD", + LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip + HMD_OFFSET = 0.070, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief + hasHandAndWristJoints, + handToWristOffset = [], // For avatars without a wrist joint we control an estimate of a proper hand joint position + HAND_OFFSET = 0.4, // Relative distance of wrist to hand versus wrist to index finger knuckle + hands, + wrists, + NUM_HANDS = 2, // 0 = left; 1 = right + fingers, + NUM_FINGERS = 5, // 0 = thumb; ...; 4 = pinky + THUMB = 0, + MIDDLE_FINGER = 2, + NUM_FINGER_JOINTS = 3, // 0 = metacarpal(hand)-proximal(finger) joint; ...; 2 = intermediate-distal joint + MAX_HAND_INACTIVE_COUNT = 20, + calibrationStatus, + UNCALIBRATED = 0, + CALIBRATING = 1, + CALIBRATED = 2, + CALIBRATION_TIME = 1000, // milliseconds + avatarScale, + avatarFaceModelURL, + avatarSkeletonModelURL, + settingsTimer; + + function printSkeletonJointNames() { + var jointNames, + i; + + print(MyAvatar.skeletonModelURL); + + print("Skeleton joint names ..."); + jointNames = MyAvatar.getJointNames(); + for (i = 0; i < jointNames.length; i += 1) { + print(i + ": " + jointNames[i]); + } + print("... skeleton joint names"); + + /* + http://public.highfidelity.io/models/skeletons/ron_standing.fst + Skeleton joint names ... + 0: Hips + 1: RightUpLeg + 2: RightLeg + 3: RightFoot + 4: RightToeBase + 5: RightToe_End + 6: LeftUpLeg + 7: LeftLeg + 8: LeftFoot + 9: LeftToeBase + 10: LeftToe_End + 11: Spine + 12: Spine1 + 13: Spine2 + 14: RightShoulder + 15: RightArm + 16: RightForeArm + 17: RightHand + 18: RightHandPinky1 + 19: RightHandPinky2 + 20: RightHandPinky3 + 21: RightHandPinky4 + 22: RightHandRing1 + 23: RightHandRing2 + 24: RightHandRing3 + 25: RightHandRing4 + 26: RightHandMiddle1 + 27: RightHandMiddle2 + 28: RightHandMiddle3 + 29: RightHandMiddle4 + 30: RightHandIndex1 + 31: RightHandIndex2 + 32: RightHandIndex3 + 33: RightHandIndex4 + 34: RightHandThumb1 + 35: RightHandThumb2 + 36: RightHandThumb3 + 37: RightHandThumb4 + 38: LeftShoulder + 39: LeftArm + 40: LeftForeArm + 41: LeftHand + 42: LeftHandPinky1 + 43: LeftHandPinky2 + 44: LeftHandPinky3 + 45: LeftHandPinky4 + 46: LeftHandRing1 + 47: LeftHandRing2 + 48: LeftHandRing3 + 49: LeftHandRing4 + 50: LeftHandMiddle1 + 51: LeftHandMiddle2 + 52: LeftHandMiddle3 + 53: LeftHandMiddle4 + 54: LeftHandIndex1 + 55: LeftHandIndex2 + 56: LeftHandIndex3 + 57: LeftHandIndex4 + 58: LeftHandThumb1 + 59: LeftHandThumb2 + 60: LeftHandThumb3 + 61: LeftHandThumb4 + 62: Neck + 63: Head + 64: HeadTop_End + 65: body + ... skeleton joint names + */ + } + + function finishCalibration() { + var avatarPosition, + handPosition, + middleFingerPosition, + leapHandHeight, + h; + + if (!isOnHMD) { + if (hands[0].controller.isActive() && hands[1].controller.isActive()) { + leapHandHeight = (hands[0].controller.getAbsTranslation().y + hands[1].controller.getAbsTranslation().y) / 2.0; + } else { + calibrationStatus = UNCALIBRATED; + return; + } + } + + avatarPosition = MyAvatar.position; + + for (h = 0; h < NUM_HANDS; h += 1) { + handPosition = MyAvatar.getJointPosition(hands[h].jointName); + if (!hasHandAndWristJoints) { + middleFingerPosition = MyAvatar.getJointPosition(fingers[h][MIDDLE_FINGER][0].jointName); + handToWristOffset[h] = Vec3.multiply(Vec3.subtract(handPosition, middleFingerPosition), 1.0 - HAND_OFFSET); + } + + if (isOnHMD) { + // Offset of Leap Motion origin from physical eye position + hands[h].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET }; + } else { + hands[h].zeroPosition = { + x: handPosition.x - avatarPosition.x, + y: handPosition.y - avatarPosition.y, + z: avatarPosition.z - handPosition.z + }; + hands[h].zeroPosition = Vec3.multiplyQbyV(MyAvatar.orientation, hands[h].zeroPosition); + hands[h].zeroPosition.y = hands[h].zeroPosition.y - leapHandHeight; + } + } + + MyAvatar.clearJointData("LeftHand"); + MyAvatar.clearJointData("LeftForeArm"); + MyAvatar.clearJointData("LeftArm"); + MyAvatar.clearJointData("RightHand"); + MyAvatar.clearJointData("RightForeArm"); + MyAvatar.clearJointData("RightArm"); + + calibrationStatus = CALIBRATED; + print("Leap Motion: Calibrated"); + } + + function calibrate() { + var jointNames, + i; + + calibrationStatus = CALIBRATING; + + avatarScale = MyAvatar.scale; + avatarFaceModelURL = MyAvatar.faceModelURL; + avatarSkeletonModelURL = MyAvatar.skeletonModelURL; + + // Does this skeleton have both wrist and hand joints? + hasHandAndWristJoints = false; + jointNames = MyAvatar.getJointNames(); + for (i = 0; i < jointNames.length; i += 1) { + hasHandAndWristJoints = hasHandAndWristJoints || jointNames[i].toLowerCase() === "leftwrist"; + } + + // Set avatar arms vertical, forearms horizontal, as "zero" position for calibration + MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, -90.0)); + MyAvatar.setJointData("LeftForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0)); + MyAvatar.setJointData("LeftHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0)); + MyAvatar.setJointData("RightArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 90.0)); + MyAvatar.setJointData("RightForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0)); + MyAvatar.setJointData("RightHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0)); + + + // Wait for arms to assume their positions before calculating + Script.setTimeout(finishCalibration, CALIBRATION_TIME); + } + + function checkCalibration() { + + if (calibrationStatus === CALIBRATED) { + return true; + } + + if (calibrationStatus !== CALIBRATING) { + calibrate(); + } + + return false; + } + + function setIsOnHMD() { + isOnHMD = Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM); + print("Leap Motion: " + (isOnHMD ? "Is on HMD" : "Is on desk")); + } + + function checkSettings() { + if (calibrationStatus > UNCALIBRATED && (MyAvatar.scale !== avatarScale + || MyAvatar.faceModelURL !== avatarFaceModelURL + || MyAvatar.skeletonModelURL !== avatarSkeletonModelURL + || Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM) !== isOnHMD)) { + print("Leap Motion: Recalibrate..."); + calibrationStatus = UNCALIBRATED; + + setIsOnHMD(); + } + } + + function setUp() { + + wrists = [ + { + jointName: "LeftWrist", + controller: Controller.createInputController(MotionTracker, "joint_L_wrist") + }, + { + jointName: "RightWrist", + controller: Controller.createInputController(MotionTracker, "joint_R_wrist") + } + ]; + + hands = [ + { + jointName: "LeftHand", + controller: Controller.createInputController(MotionTracker, "joint_L_hand"), + inactiveCount: 0 + }, + { + jointName: "RightHand", + controller: Controller.createInputController(MotionTracker, "joint_R_hand"), + inactiveCount: 0 + } + ]; + + // The Leap controller's first joint is the hand-metacarpal joint but this joint's data is not used because it's too + // dependent on the model skeleton exactly matching the Leap skeleton; using just the second and subsequent joints + // seems to work better over all. + fingers = [{}, {}]; + fingers[0] = [ + [ + { jointName: "LeftHandThumb1", controller: Controller.createInputController(MotionTracker, "joint_L_thumb2") }, + { jointName: "LeftHandThumb2", controller: Controller.createInputController(MotionTracker, "joint_L_thumb3") }, + { jointName: "LeftHandThumb3", controller: Controller.createInputController(MotionTracker, "joint_L_thumb4") } + ], + [ + { jointName: "LeftHandIndex1", controller: Controller.createInputController(MotionTracker, "joint_L_index2") }, + { jointName: "LeftHandIndex2", controller: Controller.createInputController(MotionTracker, "joint_L_index3") }, + { jointName: "LeftHandIndex3", controller: Controller.createInputController(MotionTracker, "joint_L_index4") } + ], + [ + { jointName: "LeftHandMiddle1", controller: Controller.createInputController(MotionTracker, "joint_L_middle2") }, + { jointName: "LeftHandMiddle2", controller: Controller.createInputController(MotionTracker, "joint_L_middle3") }, + { jointName: "LeftHandMiddle3", controller: Controller.createInputController(MotionTracker, "joint_L_middle4") } + ], + [ + { jointName: "LeftHandRing1", controller: Controller.createInputController(MotionTracker, "joint_L_ring2") }, + { jointName: "LeftHandRing2", controller: Controller.createInputController(MotionTracker, "joint_L_ring3") }, + { jointName: "LeftHandRing3", controller: Controller.createInputController(MotionTracker, "joint_L_ring4") } + ], + [ + { jointName: "LeftHandPinky1", controller: Controller.createInputController(MotionTracker, "joint_L_pinky2") }, + { jointName: "LeftHandPinky2", controller: Controller.createInputController(MotionTracker, "joint_L_pinky3") }, + { jointName: "LeftHandPinky3", controller: Controller.createInputController(MotionTracker, "joint_L_pinky4") } + ] + ]; + fingers[1] = [ + [ + { jointName: "RightHandThumb1", controller: Controller.createInputController(MotionTracker, "joint_R_thumb2") }, + { jointName: "RightHandThumb2", controller: Controller.createInputController(MotionTracker, "joint_R_thumb3") }, + { jointName: "RightHandThumb3", controller: Controller.createInputController(MotionTracker, "joint_R_thumb4") } + ], + [ + { jointName: "RightHandIndex1", controller: Controller.createInputController(MotionTracker, "joint_R_index2") }, + { jointName: "RightHandIndex2", controller: Controller.createInputController(MotionTracker, "joint_R_index3") }, + { jointName: "RightHandIndex3", controller: Controller.createInputController(MotionTracker, "joint_R_index4") } + ], + [ + { jointName: "RightHandMiddle1", controller: Controller.createInputController(MotionTracker, "joint_R_middle2") }, + { jointName: "RightHandMiddle2", controller: Controller.createInputController(MotionTracker, "joint_R_middle3") }, + { jointName: "RightHandMiddle3", controller: Controller.createInputController(MotionTracker, "joint_R_middle4") } + ], + [ + { jointName: "RightHandRing1", controller: Controller.createInputController(MotionTracker, "joint_R_ring2") }, + { jointName: "RightHandRing2", controller: Controller.createInputController(MotionTracker, "joint_R_ring3") }, + { jointName: "RightHandRing3", controller: Controller.createInputController(MotionTracker, "joint_R_ring4") } + ], + [ + { jointName: "RightHandPinky1", controller: Controller.createInputController(MotionTracker, "joint_R_pinky2") }, + { jointName: "RightHandPinky2", controller: Controller.createInputController(MotionTracker, "joint_R_pinky3") }, + { jointName: "RightHandPinky3", controller: Controller.createInputController(MotionTracker, "joint_R_pinky4") } + ] + ]; + + setIsOnHMD(); + + settingsTimer = Script.setInterval(checkSettings, 2000); + + calibrationStatus = UNCALIBRATED; + } + + function moveHands() { + var h, + i, + j, + side, + handOffset, + wristOffset, + handRotation, + locRotation, + cameraOrientation, + inverseAvatarOrientation; + + for (h = 0; h < NUM_HANDS; h += 1) { + side = h === 0 ? -1.0 : 1.0; + + if (hands[h].controller.isActive()) { + + // Calibrate if necessary. + if (!checkCalibration()) { + return; + } + + // Hand position ... + handOffset = hands[h].controller.getAbsTranslation(); + handRotation = hands[h].controller.getAbsRotation(); + + if (isOnHMD) { + + // Adjust to control wrist position if "hand" joint is at wrist ... + if (!hasHandAndWristJoints) { + wristOffset = Vec3.multiplyQbyV(handRotation, handToWristOffset[h]); + handOffset = Vec3.sum(handOffset, wristOffset); + } + + // Hand offset in camera coordinates ... + handOffset = { + x: hands[h].zeroPosition.x - handOffset.x, + y: hands[h].zeroPosition.y - handOffset.z, + z: hands[h].zeroPosition.z + handOffset.y + }; + handOffset.z = -handOffset.z; + + // Hand offset in world coordinates ... + cameraOrientation = Camera.getOrientation(); + handOffset = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(cameraOrientation, handOffset)); + + // Hand offset in avatar coordinates ... + inverseAvatarOrientation = Quat.inverse(MyAvatar.orientation); + handOffset = Vec3.subtract(handOffset, MyAvatar.position); + handOffset = Vec3.multiplyQbyV(inverseAvatarOrientation, handOffset); + handOffset.z = -handOffset.z; + handOffset.x = -handOffset.x; + + // Hand rotation in camera coordinates ... + handRotation = { + x: handRotation.z, + y: handRotation.y, + z: handRotation.x, + w: handRotation.w + }; + + // Hand rotation in avatar coordinates ... + if (h === 0) { + handRotation.x = -handRotation.x; + handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 1, y: 0, z: 0 }), handRotation); + handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 0, z: 1 }), handRotation); + } else { + handRotation.z = -handRotation.z; + handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 1, y: 0, z: 0 }), handRotation); + handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 0, z: 1 }), handRotation); + } + + cameraOrientation.x = -cameraOrientation.x; + cameraOrientation.z = -cameraOrientation.z; + handRotation = Quat.multiply(cameraOrientation, handRotation); + handRotation = Quat.multiply(inverseAvatarOrientation, handRotation); + + } else { + + // Hand offset in camera coordinates ... + handOffset = { + x: -handOffset.x, + y: hands[h].zeroPosition.y + handOffset.y, + z: hands[h].zeroPosition.z - handOffset.z + }; + + // Hand rotation in camera coordinates ... + handRotation = { + x: handRotation.y, + y: handRotation.z, + z: -handRotation.x, + w: handRotation.w + }; + + // Hand rotation in avatar coordinates ... + if (h === 0) { + handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 0, y: 1, z: 0 }), + handRotation); + handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 1, y: 0, z: 0 }), + handRotation); + } else { + handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 0, y: 1, z: 0 }), + handRotation); + handRotation = Quat.multiply(Quat.angleAxis(-90.0, { x: 1, y: 0, z: 0 }), + handRotation); + } + } + + // Set hand position and orientation ... + MyAvatar.setJointModelPositionAndOrientation(hands[h].jointName, handOffset, handRotation, true); + + // Set finger joints ... + for (i = 0; i < NUM_FINGERS; i += 1) { + for (j = 0; j < NUM_FINGER_JOINTS; j += 1) { + if (fingers[h][i][j].controller !== null) { + locRotation = fingers[h][i][j].controller.getLocRotation(); + if (i === THUMB) { + locRotation = { + x: side * locRotation.y, + y: side * -locRotation.z, + z: side * -locRotation.x, + w: locRotation.w + }; + } else { + locRotation = { + x: -locRotation.x, + y: -locRotation.z, + z: -locRotation.y, + w: locRotation.w + }; + } + MyAvatar.setJointData(fingers[h][i][j].jointName, locRotation); + } + } + } + + hands[h].inactiveCount = 0; + + } else { + + if (hands[h].inactiveCount < MAX_HAND_INACTIVE_COUNT) { + + hands[h].inactiveCount += 1; + + if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) { + if (h === 0) { + MyAvatar.clearJointData("LeftHand"); + MyAvatar.clearJointData("LeftForeArm"); + MyAvatar.clearJointData("LeftArm"); + } else { + MyAvatar.clearJointData("RightHand"); + MyAvatar.clearJointData("RightForeArm"); + MyAvatar.clearJointData("RightArm"); + } + } + } + } + } + } + + function tearDown() { + var h, + i, + j; + + Script.clearInterval(settingsTimer); + + for (h = 0; h < NUM_HANDS; h += 1) { + Controller.releaseInputController(hands[h].controller); + Controller.releaseInputController(wrists[h].controller); + for (i = 0; i < NUM_FINGERS; i += 1) { + for (j = 0; j < NUM_FINGER_JOINTS; j += 1) { + if (fingers[h][i][j].controller !== null) { + Controller.releaseInputController(fingers[h][i][j].controller); + } + } + } + } + } + + return { + printSkeletonJointNames: printSkeletonJointNames, + setUp : setUp, + moveHands : moveHands, + tearDown : tearDown + }; +}()); + + +//leapHands.printSkeletonJointNames(); + +leapHands.setUp(); +Script.update.connect(leapHands.moveHands); +Script.scriptEnding.connect(leapHands.tearDown); diff --git a/examples/sit.js b/examples/sit.js index 71d909d1e7..196a1a1972 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -10,7 +10,7 @@ // -var buttonImageUrl = "https://public.highfidelity.io/images/tools/sit.svg"; +var buttonImageUrl = "https://s3.amazonaws.com/hifi-public/images/tools/sit.svg"; var windowDimensions = Controller.getViewportDimensions(); diff --git a/examples/utilities/diagnostics/inWorldTestTone.js b/examples/utilities/diagnostics/inWorldTestTone.js index 1fc3cbc2c8..77ec7ba3b2 100644 --- a/examples/utilities/diagnostics/inWorldTestTone.js +++ b/examples/utilities/diagnostics/inWorldTestTone.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var sound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/220Sine.wav"); diff --git a/examples/utilities/diagnostics/playSoundLoop.js b/examples/utilities/diagnostics/playSoundLoop.js index b9d35141d1..faf23761b4 100644 --- a/examples/utilities/diagnostics/playSoundLoop.js +++ b/examples/utilities/diagnostics/playSoundLoop.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("../../libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; // A few sample files you may want to try: diff --git a/examples/utilities/diagnostics/playSoundWave.js b/examples/utilities/diagnostics/playSoundWave.js index 0741b72ef0..e9bf534e35 100644 --- a/examples/utilities/diagnostics/playSoundWave.js +++ b/examples/utilities/diagnostics/playSoundWave.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var soundClip = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Cocktail%20Party%20Snippets/Walken1.wav"); diff --git a/examples/utilities/record/recorder.js b/examples/utilities/record/recorder.js index ddfa3e0315..f3f46adf1a 100644 --- a/examples/utilities/record/recorder.js +++ b/examples/utilities/record/recorder.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/globals.js"); +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; Script.include("libraries/toolBars.js"); var recordingFile = "recording.rec"; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 8ddd1153ef..7817b9f520 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed -set(OPTIONAL_EXTERNALS "Faceshift" "LibOVR" "PrioVR" "Sixense" "LeapMotion" "RtMidi" "Qxmpp" "SDL2" "Gverb") +set(OPTIONAL_EXTERNALS "Faceshift" "LibOVR" "PrioVR" "Sixense" "LeapMotion" "RtMidi" "Qxmpp" "SDL2" "Gverb" "RSSDK") foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) diff --git a/interface/external/rssdk/readme.txt b/interface/external/rssdk/readme.txt new file mode 100644 index 0000000000..fe2246e32a --- /dev/null +++ b/interface/external/rssdk/readme.txt @@ -0,0 +1,9 @@ + +Instructions for adding the Intel Realsense (RSSDK) to Interface +Thijs Wenker, December 19, 2014 + +This is Windows only for now. + +1. Download the SDK at https://software.intel.com/en-us/intel-realsense-sdk/download + +2. Copy the `lib` and `include` folder inside this directory \ No newline at end of file diff --git a/interface/external/sdl2/readme.txt b/interface/external/sdl2/readme.txt new file mode 100644 index 0000000000..9f3bd40e15 --- /dev/null +++ b/interface/external/sdl2/readme.txt @@ -0,0 +1,13 @@ + +Instructions for adding the SDL library (SDL2) to Interface +David Rowe, 11 Jan 2015 + +You can download the SDL development library from https://www.libsdl.org/. Interface has been tested with version 2.0.3. + +1. Copy the include and lib folders into the interface/externals/sdl2 folder. + This readme.txt should be there as well. + + You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects). + If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'sdl2' that contains the two folders mentioned above. + +2. Clear your build directory, run cmake and build, and you should be all set. diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 22a2b6bc22..ff6c87123f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -85,10 +85,13 @@ #include "Util.h" #include "audio/AudioToolBox.h" +#include "audio/AudioIOStatsRenderer.h" +#include "audio/AudioScope.h" #include "devices/DdeFaceTracker.h" #include "devices/Faceshift.h" #include "devices/Leapmotion.h" +#include "devices/RealSense.h" #include "devices/MIDIManager.h" #include "devices/OculusManager.h" #include "devices/TV3DManager.h" @@ -142,8 +145,42 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt } } +bool setupEssentials(int& argc, char** argv) { + unsigned int listenPort = 0; // bind to an ephemeral port by default + const char** constArgv = const_cast(argv); + const char* portStr = getCmdOption(argc, constArgv, "--listenPort"); + if (portStr) { + listenPort = atoi(portStr); + } + + DependencyManager::registerInheritance(); + + // Set dependencies + auto glCanvas = DependencyManager::set(); + auto addressManager = DependencyManager::set(); + auto nodeList = DependencyManager::set(NodeType::Agent, listenPort); + auto geometryCache = DependencyManager::set(); + auto glowEffect = DependencyManager::set(); + auto faceshift = DependencyManager::set(); + auto audio = DependencyManager::set
- Scripts + Load Scripts @@ -391,9 +391,16 @@ font: bold 16px; - + - Load script + from URL + + + + + + + from Disk @@ -429,7 +436,7 @@ font: bold 16px; - + 0 @@ -442,6 +449,12 @@ font: bold 16px; Qt::ScrollBarAlwaysOff + + true + + + false + diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 1a68aeb908..6c02ccbd2b 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -17,7 +17,10 @@ static int animationPointerMetaTypeId = qRegisterMetaType(); AnimationCache::AnimationCache(QObject* parent) : - ResourceCache(parent) { + ResourceCache(parent) +{ + const qint64 ANIMATION_DEFAULT_UNUSED_MAX_SIZE = 50 * BYTES_PER_MEGABYTES; + setUnusedResourceCacheSize(ANIMATION_DEFAULT_UNUSED_MAX_SIZE); } AnimationPointer AnimationCache::getAnimation(const QUrl& url) { diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 4e67014822..6dbd5fdaad 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -24,9 +24,9 @@ class Animation; typedef QSharedPointer AnimationPointer; /// Scriptable interface for FBX animation loading. -class AnimationCache : public ResourceCache { +class AnimationCache : public ResourceCache, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY(AnimationCache) + SINGLETON_DEPENDENCY public: Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); } diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 57f20bfae4..1b42ff9392 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -229,7 +229,7 @@ void AudioInjector::injectToMixer() { _audioData.data() + _currentSendPosition, bytesToCopy); // grab our audio mixer from the NodeList, if it exists - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); // send off this audio packet diff --git a/libraries/audio/src/AudioScriptingInterface.cpp b/libraries/audio/src/AudioScriptingInterface.cpp index 8cd133ad40..120c7a6b5a 100644 --- a/libraries/audio/src/AudioScriptingInterface.cpp +++ b/libraries/audio/src/AudioScriptingInterface.cpp @@ -13,7 +13,8 @@ void registerAudioMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, injectorOptionsToScriptValue, injectorOptionsFromScriptValue); - qScriptRegisterMetaType(engine, soundToScriptValue, soundFromScriptValue); + qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); + qScriptRegisterMetaType(engine, soundPointerToScriptValue, soundPointerFromScriptValue); } AudioScriptingInterface& AudioScriptingInterface::getInstance() { diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 54ff61d66a..cc41a849e7 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -29,13 +29,22 @@ #include "AudioEditBuffer.h" #include "Sound.h" -QScriptValue soundToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in) { +static int soundMetaTypeId = qRegisterMetaType(); + +QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in) { return engine->newQObject(in.data()); } -void soundFromScriptValue(const QScriptValue &object, SharedSoundPointer &out) { +void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer &out) { out = SharedSoundPointer(qobject_cast(object.toQObject())); - qDebug() << "Sound from script value" << out.data(); +} + +QScriptValue soundPointerToScriptValue(QScriptEngine* engine, Sound* const& in) { + return engine->newQObject(in); +} + +void soundPointerFromScriptValue(const QScriptValue &object, Sound* &out) { + out = qobject_cast(object.toQObject()); } Sound::Sound(const QUrl& url, bool isStereo) : diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 02b75417e8..9aa92feea1 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -45,8 +45,12 @@ private: typedef QSharedPointer SharedSoundPointer; Q_DECLARE_METATYPE(SharedSoundPointer) +QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in); +void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPointer &out); + +Q_DECLARE_METATYPE(Sound*) +QScriptValue soundPointerToScriptValue(QScriptEngine* engine, Sound* const& in); +void soundPointerFromScriptValue(const QScriptValue& object, Sound* &out); -QScriptValue soundToScriptValue(QScriptEngine* engine, SharedSoundPointer const& in); -void soundFromScriptValue(const QScriptValue& object, SharedSoundPointer& out); #endif // hifi_Sound_h diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 4fbd98fea0..fe05372ce5 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -23,7 +23,8 @@ SoundCache& SoundCache::getInstance() { SoundCache::SoundCache(QObject* parent) : ResourceCache(parent) { - + const qint64 SOUND_DEFAULT_UNUSED_MAX_SIZE = 50 * BYTES_PER_MEGABYTES; + setUnusedResourceCacheSize(SOUND_DEFAULT_UNUSED_MAX_SIZE); } SharedSoundPointer SoundCache::getSound(const QUrl& url) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d2108c79e2..21bbdefc80 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -338,9 +338,12 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { } return maxAvailableSize; } - _bodyYaw = yaw; - _bodyPitch = pitch; - _bodyRoll = roll; + if (_bodyYaw != yaw || _bodyPitch != pitch || _bodyRoll != roll) { + _hasNewJointRotations = true; + _bodyYaw = yaw; + _bodyPitch = pitch; + _bodyRoll = roll; + } // scale float scale; @@ -1068,7 +1071,7 @@ void AvatarData::sendIdentityPacket() { QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); identityPacket.append(identityByteArray()); - NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer); + DependencyManager::get()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer); } void AvatarData::sendBillboardPacket() { @@ -1076,7 +1079,7 @@ void AvatarData::sendBillboardPacket() { QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); billboardPacket.append(_billboard); - NodeList::getInstance()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer); + DependencyManager::get()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer); } } diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 4f69eb2e3d..3d5af17ab9 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -18,7 +18,7 @@ AvatarHashMap::AvatarHashMap() : _avatarHash(), _lastOwnerSessionUUID() { - connect(NodeList::getInstance(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); + connect(DependencyManager::get().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); } diff --git a/libraries/avatars/src/Player.cpp b/libraries/avatars/src/Player.cpp index dfb786cbc4..e67ceb2dc3 100644 --- a/libraries/avatars/src/Player.cpp +++ b/libraries/avatars/src/Player.cpp @@ -63,7 +63,7 @@ void Player::startPlaying() { if (!isPaused()) { _currentContext.globalTimestamp = usecTimestampNow(); - _currentContext.domain = NodeList::getInstance()->getDomainHandler().getHostname(); + _currentContext.domain = DependencyManager::get()->getDomainHandler().getHostname(); _currentContext.position = _avatar->getPosition(); _currentContext.orientation = _avatar->getOrientation(); _currentContext.scale = _avatar->getTargetScale(); diff --git a/libraries/avatars/src/Recorder.cpp b/libraries/avatars/src/Recorder.cpp index b54c92744a..b90ded2e9a 100644 --- a/libraries/avatars/src/Recorder.cpp +++ b/libraries/avatars/src/Recorder.cpp @@ -42,7 +42,7 @@ void Recorder::startRecording() { RecordingContext& context = _recording->getContext(); context.globalTimestamp = usecTimestampNow(); - context.domain = NodeList::getInstance()->getDomainHandler().getHostname(); + context.domain = DependencyManager::get()->getDomainHandler().getHostname(); context.position = _avatar->getPosition(); context.orientation = _avatar->getOrientation(); context.scale = _avatar->getTargetScale(); diff --git a/libraries/avatars/src/Recording.cpp b/libraries/avatars/src/Recording.cpp index 0ba396eaa9..a258247f13 100644 --- a/libraries/avatars/src/Recording.cpp +++ b/libraries/avatars/src/Recording.cpp @@ -670,7 +670,7 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr // Fake context RecordingContext& context = recording->getContext(); context.globalTimestamp = usecTimestampNow(); - context.domain = NodeList::getInstance()->getDomainHandler().getHostname(); + context.domain = DependencyManager::get()->getDomainHandler().getHostname(); context.position = glm::vec3(144.5f, 3.3f, 181.3f); context.orientation = glm::angleAxis(glm::radians(-92.5f), glm::vec3(0, 1, 0));; context.scale = baseFrame._scale; diff --git a/libraries/entities/src/EntityCollisionSystem.cpp b/libraries/entities/src/EntityCollisionSystem.cpp index 61d8447a30..172850bc64 100644 --- a/libraries/entities/src/EntityCollisionSystem.cpp +++ b/libraries/entities/src/EntityCollisionSystem.cpp @@ -134,8 +134,8 @@ void EntityCollisionSystem::updateCollisionWithEntities(EntityItem* entityA) { glm::vec3 axis = glm::normalize(penetration); glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis; - float massA = entityA->getMass(); - float massB = entityB->getMass(); + float massA = entityA->computeMass(); + float massB = entityB->computeMass(); float totalMass = massA + massB; float massRatioA = (2.0f * massB / totalMass); float massRatioB = (2.0f * massA / totalMass); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 4bf394314b..69cd16469e 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -35,25 +35,25 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) { _created = UNKNOWN_CREATED_TIME; _changedOnServer = 0; - _position = glm::vec3(0,0,0); - _dimensions = DEFAULT_DIMENSIONS; - _rotation = DEFAULT_ROTATION; - _glowLevel = DEFAULT_GLOW_LEVEL; - _localRenderAlpha = DEFAULT_LOCAL_RENDER_ALPHA; - _mass = DEFAULT_MASS; - _velocity = DEFAULT_VELOCITY; - _gravity = DEFAULT_GRAVITY; - _damping = DEFAULT_DAMPING; - _lifetime = DEFAULT_LIFETIME; - _script = DEFAULT_SCRIPT; - _registrationPoint = DEFAULT_REGISTRATION_POINT; - _angularVelocity = DEFAULT_ANGULAR_VELOCITY; - _angularDamping = DEFAULT_ANGULAR_DAMPING; - _visible = DEFAULT_VISIBLE; - _ignoreForCollisions = DEFAULT_IGNORE_FOR_COLLISIONS; - _collisionsWillMove = DEFAULT_COLLISIONS_WILL_MOVE; - _locked = DEFAULT_LOCKED; - _userData = DEFAULT_USER_DATA; + _position = ENTITY_ITEM_ZERO_VEC3; + _dimensions = ENTITY_ITEM_DEFAULT_DIMENSIONS; + _density = ENTITY_ITEM_DEFAULT_DENSITY; + _rotation = ENTITY_ITEM_DEFAULT_ROTATION; + _glowLevel = ENTITY_ITEM_DEFAULT_GLOW_LEVEL; + _localRenderAlpha = ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA; + _velocity = ENTITY_ITEM_DEFAULT_VELOCITY; + _gravity = ENTITY_ITEM_DEFAULT_GRAVITY; + _damping = ENTITY_ITEM_DEFAULT_DAMPING; + _lifetime = ENTITY_ITEM_DEFAULT_LIFETIME; + _script = ENTITY_ITEM_DEFAULT_SCRIPT; + _registrationPoint = ENTITY_ITEM_DEFAULT_REGISTRATION_POINT; + _angularVelocity = ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY; + _angularDamping = ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING; + _visible = ENTITY_ITEM_DEFAULT_VISIBLE; + _ignoreForCollisions = ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS; + _collisionsWillMove = ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE; + _locked = ENTITY_ITEM_DEFAULT_LOCKED; + _userData = ENTITY_ITEM_DEFAULT_USER_DATA; recalculateCollisionShape(); } @@ -103,7 +103,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_POSITION; requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete requestedProperties += PROP_ROTATION; - requestedProperties += PROP_MASS; + requestedProperties += PROP_DENSITY; requestedProperties += PROP_VELOCITY; requestedProperties += PROP_GRAVITY; requestedProperties += PROP_DAMPING; @@ -219,7 +219,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet } APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation()); - APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, getMass()); + APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, getDamping()); @@ -495,7 +495,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation); - READ_ENTITY_PROPERTY_SETTER(PROP_MASS, float, updateMass); + READ_ENTITY_PROPERTY_SETTER(PROP_DENSITY, float, updateDensity); READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity); READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity); READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping); @@ -555,6 +555,46 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s } } +float EntityItem::computeMass() const { + // NOTE: we group the operations here in and attempt to reduce floating point error. + return ((_density * (_volumeMultiplier * _dimensions.x)) * _dimensions.y) * _dimensions.z; +} + +void EntityItem::setDensity(float density) { + _density = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); +} + +void EntityItem::updateDensity(float density) { + const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent + float newDensity = glm::max(glm::min(density, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); + if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) { + _density = newDensity; + _dirtyFlags |= EntityItem::DIRTY_MASS; + } +} + +void EntityItem::setMass(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 + // therefore this method might not accept the mass that is supplied. + + // NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due + // to the TREE_SCALE transformation) with the first dimension component (typically a very small number) + // in an attempt to reduce floating point error of the final result. + float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z; + + // compute new density + const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 + if (volume < 1.0e-6f) { + // avoid divide by zero + _density = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); + } else { + _density = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); + } +} + +const float ENTITY_ITEM_EPSILON_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; + // TODO: we probably want to change this to make "down" be the direction of the entity's gravity vector // for now, this is always true DOWN even if entity has non-down gravity. // TODO: the old code had "&& _velocity.y >= -EPSILON && _velocity.y <= EPSILON" --- what was I thinking? @@ -562,7 +602,7 @@ bool EntityItem::isRestingOnSurface() const { glm::vec3 downwardVelocity = glm::vec3(0.0f, _velocity.y, 0.0f); return _position.y <= getDistanceToBottomOfEntity() - && (glm::length(downwardVelocity) <= EPSILON_VELOCITY_LENGTH) + && (glm::length(downwardVelocity) <= ENTITY_ITEM_EPSILON_VELOCITY_LENGTH) && _gravity.y < 0.0f; } @@ -639,7 +679,7 @@ void EntityItem::simulate(const quint64& now) { const float EPSILON_ANGULAR_VELOCITY_LENGTH = 0.1f; // if (angularSpeed < EPSILON_ANGULAR_VELOCITY_LENGTH) { - setAngularVelocity(NO_ANGULAR_VELOCITY); + setAngularVelocity(ENTITY_ITEM_ZERO_VEC3); } else { // NOTE: angularSpeed is currently in degrees/sec!!! // TODO: Andrew to convert to radians/sec @@ -651,15 +691,7 @@ void EntityItem::simulate(const quint64& now) { } } -#ifdef USE_BULLET_PHYSICS - // When Bullet is available we assume that "zero velocity" means "at rest" - // because of collision conditions this simulation does not know about - // so we don't fall in for the non-zero gravity case here. if (hasVelocity()) { -#else // !USE_BULLET_PHYSICS - if (hasVelocity() || hasGravity()) { -#endif // USE_BULLET_PHYSICS - // linear damping glm::vec3 velocity = getVelocity(); if (_damping > 0.0f) { @@ -668,7 +700,7 @@ void EntityItem::simulate(const quint64& now) { qDebug() << " damping:" << _damping; qDebug() << " velocity AFTER dampingResistance:" << velocity; qDebug() << " glm::length(velocity):" << glm::length(velocity); - qDebug() << " EPSILON_VELOCITY_LENGTH:" << EPSILON_VELOCITY_LENGTH; + qDebug() << " velocityEspilon :" << ENTITY_ITEM_EPSILON_VELOCITY_LENGTH; } } @@ -693,14 +725,6 @@ void EntityItem::simulate(const quint64& now) { // handle bounces off the ground... We bounce at the distance to the bottom of our entity if (position.y <= getDistanceToBottomOfEntity()) { velocity = velocity * glm::vec3(1,-1,1); - -#ifndef USE_BULLET_PHYSICS - // if we've slowed considerably, then just stop moving, but only if no BULLET - if (glm::length(velocity) <= EPSILON_VELOCITY_LENGTH) { - velocity = NO_VELOCITY; - } -#endif // !USE_BULLET_PHYSICS - position.y = getDistanceToBottomOfEntity(); } @@ -716,14 +740,8 @@ void EntityItem::simulate(const quint64& now) { } } -#ifdef USE_BULLET_PHYSICS - // When Bullet is available we assume that it will tell us when velocities go to zero... -#else // !USE_BULLET_PHYSICS - // ... otherwise we help things come to rest by clamping small velocities. - if (glm::length(velocity) <= EPSILON_VELOCITY_LENGTH) { - velocity = NO_VELOCITY; - } -#endif // USE_BULLET_PHYSICS + // NOTE: we don't zero out very small velocities --> we rely on a remote Bullet simulation + // to tell us when the entity has stopped. // NOTE: the simulation should NOT set any DirtyFlags on this entity setPosition(position); // this will automatically recalculate our collision shape @@ -741,13 +759,7 @@ void EntityItem::simulate(const quint64& now) { } bool EntityItem::isMoving() const { -#ifdef USE_BULLET_PHYSICS - // When Bullet is available we assume that "zero velocity" means "at rest" - // because of collision conditions this simulation does not know about. return hasVelocity() || hasAngularVelocity(); -#else // !USE_BULLET_PHYSICS - return hasVelocity() || (hasGravity() && !isRestingOnSurface()) || hasAngularVelocity(); -#endif //USE_BULLET_PHYSICS } bool EntityItem::lifetimeHasExpired() const { @@ -769,7 +781,7 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPositionInMeters); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensionsInMeters); // NOTE: radius is obsolete COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(mass, getMass); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocityInMeters); COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravityInMeters); COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping); @@ -797,7 +809,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePositionInMeters); // this will call recalculate collision shape if needed SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, updateDimensionsInMeters); // NOTE: radius is obsolete SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, updateMass); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, updateDensity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocityInMeters); SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravityInMeters); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping); @@ -909,7 +921,7 @@ AACube EntityItem::getMinimumAACube() const { // _position represents the position of the registration point. glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; - glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint); + glm::vec3 unrotatedMinRelativeToEntity = - (_dimensions * _registrationPoint); glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); @@ -934,7 +946,7 @@ AABox EntityItem::getAABox() const { // _position represents the position of the registration point. glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; - glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint); + glm::vec3 unrotatedMinRelativeToEntity = - (_dimensions * _registrationPoint); glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); @@ -997,8 +1009,7 @@ void EntityItem::recalculateCollisionShape() { const float MIN_POSITION_DELTA = 0.0001f; const float MIN_ALIGNMENT_DOT = 0.9999f; -const float MIN_MASS_DELTA = 0.001f; -const float MIN_VELOCITY_DELTA = 0.025f; +const float MIN_VELOCITY_DELTA = 0.01f; const float MIN_DAMPING_DELTA = 0.001f; const float MIN_GRAVITY_DELTA = 0.001f; const float MIN_SPIN_DELTA = 0.0003f; @@ -1045,9 +1056,29 @@ void EntityItem::updateRotation(const glm::quat& rotation) { } } -void EntityItem::updateMass(float value) { - if (fabsf(_mass - value) > MIN_MASS_DELTA) { - _mass = value; +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 + // therefore this method might not accept the mass that is supplied. + + // NOTE: when computing the volume we group the _volumeMultiplier (typically a very large number, due + // to the TREE_SCALE transformation) with the first dimension component (typically a very small number) + // in an attempt to reduce floating point error of the final result. + float volume = (_volumeMultiplier * _dimensions.x) * _dimensions.y * _dimensions.z; + + // compute new density + float newDensity = _density; + const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 + if (volume < 1.0e-6f) { + // avoid divide by zero + newDensity = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); + } else { + newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); + } + + const float MIN_DENSITY_CHANGE_FACTOR = 0.001f; // 0.1 percent + if (fabsf(_density - newDensity) / _density > MIN_DENSITY_CHANGE_FACTOR) { + _density = newDensity; _dirtyFlags |= EntityItem::DIRTY_MASS; } } @@ -1055,7 +1086,7 @@ void EntityItem::updateMass(float value) { void EntityItem::updateVelocity(const glm::vec3& value) { if (glm::distance(_velocity, value) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { if (glm::length(value) * (float)TREE_SCALE < MIN_VELOCITY_DELTA) { - _velocity = glm::vec3(0.0f); + _velocity = ENTITY_ITEM_ZERO_VEC3; } else { _velocity = value; } @@ -1067,7 +1098,7 @@ void EntityItem::updateVelocityInMeters(const glm::vec3& value) { glm::vec3 velocity = value / (float) TREE_SCALE; if (glm::distance(_velocity, velocity) * (float)TREE_SCALE > MIN_VELOCITY_DELTA) { if (glm::length(value) < MIN_VELOCITY_DELTA) { - _velocity = glm::vec3(0.0f); + _velocity = ENTITY_ITEM_ZERO_VEC3; } else { _velocity = velocity; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 1b8afa930b..9a19fa2e5d 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -26,6 +26,7 @@ #include "EntityItemID.h" #include "EntityItemProperties.h" +#include "EntityItemPropertiesDefaults.h" #include "EntityTypes.h" class EntityTree; @@ -35,30 +36,6 @@ class EntityTreeElementExtraEncodeData; #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { }; -const glm::vec3 DEFAULT_DIMENSIONS = glm::vec3(0.1f) / (float)TREE_SCALE; -const glm::quat DEFAULT_ROTATION; -const float DEFAULT_GLOW_LEVEL = 0.0f; -const float DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; -const float DEFAULT_MASS = 1.0f; -const glm::vec3 NO_VELOCITY= glm::vec3(0.0f); -const glm::vec3 DEFAULT_VELOCITY = NO_VELOCITY; -const float EPSILON_VELOCITY_LENGTH = 0.001f / (float)TREE_SCALE; -const glm::vec3 NO_GRAVITY = glm::vec3(0.0f); -const glm::vec3 DEFAULT_GRAVITY = NO_GRAVITY; -const glm::vec3 REGULAR_GRAVITY = glm::vec3(0, -9.8f / (float)TREE_SCALE, 0); -const float DEFAULT_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) -const float IMMORTAL = -1.0f; /// special lifetime which means the entity lives for ever. default lifetime -const float DEFAULT_LIFETIME = IMMORTAL; -const QString DEFAULT_SCRIPT = QString(""); -const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f, 0.5f, 0.5f); // center -const glm::vec3 NO_ANGULAR_VELOCITY = glm::vec3(0.0f); -const glm::vec3 DEFAULT_ANGULAR_VELOCITY = NO_ANGULAR_VELOCITY; -const float DEFAULT_ANGULAR_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) -const bool DEFAULT_VISIBLE = true; -const bool DEFAULT_IGNORE_FOR_COLLISIONS = false; -const bool DEFAULT_COLLISIONS_WILL_MOVE = false; -const bool DEFAULT_LOCKED = false; -const QString DEFAULT_USER_DATA = QString(""); /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate @@ -194,20 +171,23 @@ public: float getLocalRenderAlpha() const { return _localRenderAlpha; } void setLocalRenderAlpha(float localRenderAlpha) { _localRenderAlpha = localRenderAlpha; } - float getMass() const { return _mass; } - void setMass(float value) { _mass = value; } + void setDensity(float density); + float computeMass() const; + void setMass(float mass); + + float getDensity() const { return _density; } const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second glm::vec3 getVelocityInMeters() const { return _velocity * (float) TREE_SCALE; } /// get velocity in meters void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in domain scale units (0.0-1.0) per second void setVelocityInMeters(const glm::vec3& value) { _velocity = value / (float) TREE_SCALE; } /// velocity in meters - bool hasVelocity() const { return _velocity != NO_VELOCITY; } + bool hasVelocity() const { return _velocity != ENTITY_ITEM_ZERO_VEC3; } const glm::vec3& getGravity() const { return _gravity; } /// gravity in domain scale units (0.0-1.0) per second squared glm::vec3 getGravityInMeters() const { return _gravity * (float) TREE_SCALE; } /// get gravity in meters void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in domain scale units (0.0-1.0) per second squared void setGravityInMeters(const glm::vec3& value) { _gravity = value / (float) TREE_SCALE; } /// gravity in meters - bool hasGravity() const { return _gravity != NO_GRAVITY; } + bool hasGravity() const { return _gravity != ENTITY_ITEM_ZERO_VEC3; } // TODO: this should eventually be updated to support resting on collisions with other surfaces bool isRestingOnSurface() const; @@ -220,10 +200,10 @@ public: void setLifetime(float value) { _lifetime = value; } /// set the lifetime in seconds for the entity /// is this entity immortal, in that it has no lifetime set, and will exist until manually deleted - bool isImmortal() const { return _lifetime == IMMORTAL; } + bool isImmortal() const { return _lifetime == ENTITY_ITEM_IMMORTAL_LIFETIME; } /// is this entity mortal, in that it has a lifetime set, and will automatically be deleted when that lifetime expires - bool isMortal() const { return _lifetime != IMMORTAL; } + bool isMortal() const { return _lifetime != ENTITY_ITEM_IMMORTAL_LIFETIME; } /// age of this entity in seconds float getAge() const { return (float)(usecTimestampNow() - _created) / (float)USECS_PER_SECOND; } @@ -247,7 +227,7 @@ public: const glm::vec3& getAngularVelocity() const { return _angularVelocity; } void setAngularVelocity(const glm::vec3& value) { _angularVelocity = value; } - bool hasAngularVelocity() const { return _angularVelocity != NO_ANGULAR_VELOCITY; } + bool hasAngularVelocity() const { return _angularVelocity != ENTITY_ITEM_ZERO_VEC3; } float getAngularDamping() const { return _angularDamping; } void setAngularDamping(float value) { _angularDamping = value; } @@ -283,6 +263,7 @@ public: void updateDimensions(const glm::vec3& value); void updateDimensionsInMeters(const glm::vec3& value); void updateRotation(const glm::quat& rotation); + void updateDensity(float value); void updateMass(float value); void updateVelocity(const glm::vec3& value); void updateVelocityInMeters(const glm::vec3& value); @@ -326,7 +307,12 @@ protected: glm::quat _rotation; float _glowLevel; float _localRenderAlpha; - float _mass; + float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg/m^3 + // NOTE: _volumeMultiplier is used to compute volume: + // volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z = m^3 + // DANGER: due to the size of TREE_SCALE the _volumeMultiplier is always a large number, and therefore + // will tend to introduce floating point error. We must keep this in mind when using it. + float _volumeMultiplier = (float)TREE_SCALE * (float)TREE_SCALE * (float)TREE_SCALE; glm::vec3 _velocity; glm::vec3 _gravity; float _damping; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index be6fe01841..4da903f6ba 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -19,32 +19,34 @@ #include "EntityItem.h" #include "EntityItemProperties.h" +#include "EntityItemPropertiesDefaults.h" #include "ModelEntityItem.h" #include "TextEntityItem.h" + EntityItemProperties::EntityItemProperties() : - CONSTRUCT_PROPERTY(visible, DEFAULT_VISIBLE), + CONSTRUCT_PROPERTY(visible, ENTITY_ITEM_DEFAULT_VISIBLE), CONSTRUCT_PROPERTY(position, 0), - CONSTRUCT_PROPERTY(dimensions, DEFAULT_DIMENSIONS), - CONSTRUCT_PROPERTY(rotation, DEFAULT_ROTATION), - CONSTRUCT_PROPERTY(mass, DEFAULT_MASS), - CONSTRUCT_PROPERTY(velocity, DEFAULT_VELOCITY), - CONSTRUCT_PROPERTY(gravity, DEFAULT_GRAVITY), - CONSTRUCT_PROPERTY(damping, DEFAULT_DAMPING), - CONSTRUCT_PROPERTY(lifetime, DEFAULT_LIFETIME), - CONSTRUCT_PROPERTY(script, DEFAULT_SCRIPT), + CONSTRUCT_PROPERTY(dimensions, ENTITY_ITEM_DEFAULT_DIMENSIONS), + CONSTRUCT_PROPERTY(rotation, ENTITY_ITEM_DEFAULT_ROTATION), + CONSTRUCT_PROPERTY(density, ENTITY_ITEM_DEFAULT_DENSITY), + CONSTRUCT_PROPERTY(velocity, ENTITY_ITEM_DEFAULT_VELOCITY), + CONSTRUCT_PROPERTY(gravity, ENTITY_ITEM_DEFAULT_GRAVITY), + CONSTRUCT_PROPERTY(damping, ENTITY_ITEM_DEFAULT_DAMPING), + CONSTRUCT_PROPERTY(lifetime, ENTITY_ITEM_DEFAULT_LIFETIME), + CONSTRUCT_PROPERTY(script, ENTITY_ITEM_DEFAULT_SCRIPT), CONSTRUCT_PROPERTY(color, ), CONSTRUCT_PROPERTY(modelURL, ""), CONSTRUCT_PROPERTY(animationURL, ""), CONSTRUCT_PROPERTY(animationFPS, ModelEntityItem::DEFAULT_ANIMATION_FPS), CONSTRUCT_PROPERTY(animationFrameIndex, ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX), CONSTRUCT_PROPERTY(animationIsPlaying, ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING), - CONSTRUCT_PROPERTY(registrationPoint, DEFAULT_REGISTRATION_POINT), - CONSTRUCT_PROPERTY(angularVelocity, DEFAULT_ANGULAR_VELOCITY), - CONSTRUCT_PROPERTY(angularDamping, DEFAULT_ANGULAR_DAMPING), - CONSTRUCT_PROPERTY(ignoreForCollisions, DEFAULT_IGNORE_FOR_COLLISIONS), - CONSTRUCT_PROPERTY(collisionsWillMove, DEFAULT_COLLISIONS_WILL_MOVE), + CONSTRUCT_PROPERTY(registrationPoint, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT), + CONSTRUCT_PROPERTY(angularVelocity, ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY), + CONSTRUCT_PROPERTY(angularDamping, ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING), + CONSTRUCT_PROPERTY(ignoreForCollisions, ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS), + CONSTRUCT_PROPERTY(collisionsWillMove, ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE), CONSTRUCT_PROPERTY(isSpotlight, false), CONSTRUCT_PROPERTY(diffuseColor, ), CONSTRUCT_PROPERTY(ambientColor, ), @@ -54,10 +56,10 @@ EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(quadraticAttenuation, 0.0f), CONSTRUCT_PROPERTY(exponent, 0.0f), CONSTRUCT_PROPERTY(cutoff, PI), - CONSTRUCT_PROPERTY(locked, false), + CONSTRUCT_PROPERTY(locked, ENTITY_ITEM_DEFAULT_LOCKED), CONSTRUCT_PROPERTY(textures, ""), CONSTRUCT_PROPERTY(animationSettings, ""), - CONSTRUCT_PROPERTY(userData, DEFAULT_USER_DATA), + CONSTRUCT_PROPERTY(userData, ENTITY_ITEM_DEFAULT_USER_DATA), CONSTRUCT_PROPERTY(text, TextEntityItem::DEFAULT_TEXT), CONSTRUCT_PROPERTY(lineHeight, TextEntityItem::DEFAULT_LINE_HEIGHT), CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR), @@ -173,7 +175,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_POSITION, position); CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation); - CHECK_PROPERTY_CHANGE(PROP_MASS, mass); + CHECK_PROPERTY_CHANGE(PROP_DENSITY, density); CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity); CHECK_PROPERTY_CHANGE(PROP_GRAVITY, gravity); CHECK_PROPERTY_CHANGE(PROP_DAMPING, damping); @@ -230,7 +232,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity); COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity); COPY_PROPERTY_TO_QSCRIPTVALUE(damping); - COPY_PROPERTY_TO_QSCRIPTVALUE(mass); + COPY_PROPERTY_TO_QSCRIPTVALUE(density); COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable @@ -308,7 +310,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(position, setPosition); COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(dimensions, setDimensions); COPY_PROPERTY_FROM_QSCRIPTVALUE_QUAT(rotation, setRotation); - COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(mass, setMass); + COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(density, setDensity); COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(velocity, setVelocity); COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(gravity, setGravity); COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(damping, setDamping); @@ -477,7 +479,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, properties.getPosition()); APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, properties.getRotation()); - APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, properties.getMass()); + APPEND_ENTITY_PROPERTY(PROP_DENSITY, appendValue, properties.getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, properties.getVelocity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, properties.getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, properties.getDamping()); @@ -698,7 +700,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(PROP_ROTATION, setRotation); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MASS, float, setMass); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping); @@ -779,7 +781,7 @@ void EntityItemProperties::markAllChanged() { _positionChanged = true; _dimensionsChanged = true; _rotationChanged = true; - _massChanged = true; + _densityChanged = true; _velocityChanged = true; _gravityChanged = true; _dampingChanged = true; @@ -858,7 +860,7 @@ AABox EntityItemProperties::getAABoxInMeters() const { // _position represents the position of the registration point. glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; - glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint); + glm::vec3 unrotatedMinRelativeToEntity = - (_dimensions * _registrationPoint); glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index f3424aee43..8d4bf98862 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -42,7 +42,7 @@ enum EntityPropertyList { PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams PROP_DIMENSIONS = PROP_RADIUS, PROP_ROTATION, - PROP_MASS, + PROP_DENSITY, PROP_VELOCITY, PROP_GRAVITY, PROP_DAMPING, @@ -145,7 +145,7 @@ public: DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3); DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3); DEFINE_PROPERTY_REF(PROP_ROTATION, Rotation, rotation, glm::quat); - DEFINE_PROPERTY(PROP_MASS, Mass, mass, float); + DEFINE_PROPERTY(PROP_DENSITY, Density, density, float); DEFINE_PROPERTY_REF(PROP_VELOCITY, Velocity, velocity, glm::vec3); DEFINE_PROPERTY_REF(PROP_GRAVITY, Gravity, gravity, glm::vec3); DEFINE_PROPERTY(PROP_DAMPING, Damping, damping, float); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h new file mode 100644 index 0000000000..22beb3937e --- /dev/null +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -0,0 +1,57 @@ +// +// EntityItemPropertiesDefaults.h +// libraries/entities/src +// +// Created by Andrew Meadows on 2015.01.12 +// 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 +// + +#ifndef hifi_EntityItemPropertiesDefaults_h +#define hifi_EntityItemPropertiesDefaults_h + +#include + +#include + +// There is a minor performance gain when comparing/copying an existing glm::vec3 rather than +// creating a new one on the stack so we declare the ZERO_VEC3 constant as an optimization. +const glm::vec3 ENTITY_ITEM_ZERO_VEC3(0.0f); + +const glm::vec3 REGULAR_GRAVITY = glm::vec3(0, -9.8f / (float)TREE_SCALE, 0); + +const bool ENTITY_ITEM_DEFAULT_LOCKED = false; +const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString(""); + +const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; +const float ENTITY_ITEM_DEFAULT_GLOW_LEVEL = 0.0f; +const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; + +const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); +const glm::vec3 ENTITY_ITEM_DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f, 0.5f, 0.5f); // center + +const float ENTITY_ITEM_IMMORTAL_LIFETIME = -1.0f; /// special lifetime which means the entity lives for ever +const float ENTITY_ITEM_DEFAULT_LIFETIME = ENTITY_ITEM_IMMORTAL_LIFETIME; + +const glm::quat ENTITY_ITEM_DEFAULT_ROTATION; +const float ENTITY_ITEM_DEFAULT_WIDTH = 0.1f; +const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH) / (float)TREE_SCALE; +const float ENTITY_ITEM_DEFAULT_VOLUME = ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH; + +const float ENTITY_ITEM_MAX_DENSITY = 10000.0f; // kg/m^3 density of silver +const float ENTITY_ITEM_MIN_DENSITY = 100.0f; // kg/m^3 density of balsa wood +const float ENTITY_ITEM_DEFAULT_DENSITY = 1000.0f; // density of water +const float ENTITY_ITEM_DEFAULT_MASS = ENTITY_ITEM_DEFAULT_DENSITY * ENTITY_ITEM_DEFAULT_VOLUME; + +const glm::vec3 ENTITY_ITEM_DEFAULT_VELOCITY = ENTITY_ITEM_ZERO_VEC3; +const glm::vec3 ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY = ENTITY_ITEM_ZERO_VEC3; +const glm::vec3 ENTITY_ITEM_DEFAULT_GRAVITY = ENTITY_ITEM_ZERO_VEC3; +const float ENTITY_ITEM_DEFAULT_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) +const float ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) + +const bool ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS = false; +const bool ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE = false; + +#endif // hifi_EntityItemPropertiesDefaults_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 4ced5fe844..aff6e64f57 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -665,7 +665,7 @@ const EntityItem* EntityTreeElement::getEntityWithEntityItemID(const EntityItemI } return foundEntity; } - + EntityItem* EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) { EntityItem* foundEntity = NULL; uint16_t numberOfEntities = _entityItems->size(); diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 9a9c07ef9a..7f3d619e03 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -32,6 +32,10 @@ SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const Entit { _type = EntityTypes::Sphere; setProperties(properties); + // NOTE: _volumeMultiplier is used to compute volume: + // volume = _volumeMultiplier * _dimensions.x * _dimensions.y * _dimensions.z + // The formula below looks funny because _dimension.xyz = diameter rather than radius. + _volumeMultiplier *= PI / 6.0f; } EntityItemProperties SphereEntityItem::getProperties() const { @@ -114,7 +118,7 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons glm::mat4 entityToWorldMatrix = translation * rotation * scale * registration; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 1.0f))); + glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); float localDistance; // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 9532f44acf..3015de52ff 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -38,7 +38,7 @@ struct TextureParam { glm::vec2 UVTranslation; glm::vec2 UVScaling; glm::vec4 cropping; - std::string UVSet; + QString UVSet; glm::vec3 translation; glm::vec3 rotation; @@ -753,6 +753,7 @@ public: float shininess; float opacity; QString id; + model::MaterialPointer _material; }; class Cluster { @@ -802,14 +803,14 @@ public: QVector > blendshapeIndexMaps; QVector > partMaterialTextures; QHash texcoordSetMap; - std::map texcoordSetMap2; + std::map texcoordSetMap2; }; class AttributeData { public: QVector texCoords; QVector texCoordIndices; - std::string name; + QString name; int index; }; @@ -945,12 +946,12 @@ ExtractedMesh extractMesh(const FBXNode& object) { data.texCoordIndices = getIntVector(subdata); attrib.texCoordIndices = getIntVector(subdata); } else if (subdata.name == "Name") { - attrib.name = subdata.properties.at(0).toString().toStdString(); + attrib.name = subdata.properties.at(0).toString(); } #if defined(DEBUG_FBXREADER) else { int unknown = 0; - std::string subname = subdata.name.data(); + QString subname = subdata.name.data(); if ( (subdata.name == "Version") || (subdata.name == "MappingInformationType") || (subdata.name == "ReferenceInformationType") ) { @@ -960,7 +961,7 @@ ExtractedMesh extractMesh(const FBXNode& object) { } #endif } - data.extracted.texcoordSetMap.insert(QString(attrib.name.c_str()), data.attributes.size()); + data.extracted.texcoordSetMap.insert(attrib.name, data.attributes.size()); data.attributes.push_back(attrib); } else { AttributeData attrib; @@ -971,12 +972,12 @@ ExtractedMesh extractMesh(const FBXNode& object) { } else if (subdata.name == "UVIndex") { attrib.texCoordIndices = getIntVector(subdata); } else if (subdata.name == "Name") { - attrib.name = subdata.properties.at(0).toString().toStdString(); + attrib.name = subdata.properties.at(0).toString(); } #if defined(DEBUG_FBXREADER) else { int unknown = 0; - std::string subname = subdata.name.data(); + QString subname = subdata.name.data(); if ( (subdata.name == "Version") || (subdata.name == "MappingInformationType") || (subdata.name == "ReferenceInformationType") ) { @@ -987,9 +988,9 @@ ExtractedMesh extractMesh(const FBXNode& object) { #endif } - QHash::iterator it = data.extracted.texcoordSetMap.find(QString(attrib.name.c_str())); + QHash::iterator it = data.extracted.texcoordSetMap.find(attrib.name); if (it == data.extracted.texcoordSetMap.end()) { - data.extracted.texcoordSetMap.insert(QString(attrib.name.c_str()), data.attributes.size()); + data.extracted.texcoordSetMap.insert(attrib.name, data.attributes.size()); data.attributes.push_back(attrib); } else { // WTF same names for different UVs? @@ -1198,11 +1199,11 @@ bool checkMaterialsHaveTextures(const QHash& materials, return false; } -int matchTextureUVSetToAttributeChannel(const std::string& texUVSetName, const QHash& texcoordChannels) { - if (texUVSetName.empty()) { +int matchTextureUVSetToAttributeChannel(const QString& texUVSetName, const QHash& texcoordChannels) { + if (texUVSetName.isEmpty()) { return 0; } else { - QHash::const_iterator tcUnit = texcoordChannels.find(QString(texUVSetName.c_str())); + QHash::const_iterator tcUnit = texcoordChannels.find(texUVSetName); if (tcUnit != texcoordChannels.end()) { int channel = (*tcUnit); if (channel >= 2) { @@ -1220,13 +1221,13 @@ FBXLight extractLight(const FBXNode& object) { FBXLight light; foreach (const FBXNode& subobject, object.children) { - std::string childname = QString(subobject.name).toStdString(); + QString childname = QString(subobject.name); if (subobject.name == "Properties70") { foreach (const FBXNode& property, subobject.children) { int valIndex = 4; - std::string propName = QString(property.name).toStdString(); + QString propName = QString(property.name); if (property.name == "P") { - std::string propname = property.properties.at(0).toString().toStdString(); + QString propname = property.properties.at(0).toString(); if (propname == "Intensity") { light.intensity = 0.01f * property.properties.at(valIndex).value(); } @@ -1238,13 +1239,13 @@ FBXLight extractLight(const FBXNode& object) { } #if defined(DEBUG_FBXREADER) - std::string type = object.properties.at(0).toString().toStdString(); - type = object.properties.at(1).toString().toStdString(); - type = object.properties.at(2).toString().toStdString(); + QString type = object.properties.at(0).toString(); + type = object.properties.at(1).toString(); + type = object.properties.at(2).toString(); foreach (const QVariant& prop, object.properties) { - std::string proptype = prop.typeName(); - std::string propval = prop.toString().toStdString(); + QString proptype = prop.typeName(); + QString propval = prop.toString(); if (proptype == "Properties70") { } } @@ -1281,7 +1282,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, QHash yComponents; QHash zComponents; - std::map lights; + std::map lights; QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); @@ -1369,7 +1370,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, int index = 4; foreach (const FBXNode& subobject, object.children) { if (subobject.name == propertyName) { - std::string subpropName = subobject.properties.at(0).toString().toStdString(); + QString subpropName = subobject.properties.at(0).toString(); if (subpropName == "UnitScaleFactor") { unitScaleFactor = subobject.properties.at(index).toFloat(); } else if (subpropName == "AmbientColor") { @@ -1393,8 +1394,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, QString id = getID(object.properties); modelIDsToNames.insert(id, name); - std::string modelname = name.toLower().toStdString(); - if (modelname.find("hifi") == 0) { + QString modelname = name.toLower(); + if (modelname.startsWith("hifi")) { hifiGlobalNodeID = id; } @@ -1527,19 +1528,19 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } #if defined(DEBUG_FBXREADER) else if (subobject.name == "TypeFlags") { - std::string attributetype = subobject.properties.at(0).toString().toStdString(); + QString attributetype = subobject.properties.at(0).toString(); if (!attributetype.empty()) { if (attributetype == "Light") { - std::string lightprop; + QString lightprop; foreach (const QVariant& vprop, subobject.properties) { - lightprop = vprop.toString().toStdString(); + lightprop = vprop.toString(); } FBXLight light = extractLight(object); } } } else { - std::string whatisthat = subobject.name; + QString whatisthat = subobject.name; if (whatisthat == "Shape") { } } @@ -1604,7 +1605,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, if (property.name == propertyName) { QString v = property.properties.at(0).toString(); if (property.properties.at(0) == "UVSet") { - tex.assign(tex.UVSet, property.properties.at(index).toString().toStdString()); + tex.assign(tex.UVSet, property.properties.at(index).toString()); } else if (property.properties.at(0) == "CurrentTextureBlendMode") { tex.assign(tex.currentTextureBlendMode, property.properties.at(index).value()); } else if (property.properties.at(0) == "UseMaterial") { @@ -1618,7 +1619,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } #if defined(DEBUG_FBXREADER) else { - std::string propName = v.toStdString(); + QString propName = v; unknown++; } #endif @@ -1632,7 +1633,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } else if (subobject.name == "FileName") { } else if (subobject.name == "Media") { } else { - std::string subname = subobject.name.data(); + QString subname = subobject.name.data(); unknown++; } } @@ -1693,7 +1694,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } #if defined(DEBUG_FBXREADER) else { - const std::string propname = property.properties.at(0).toString().toStdString(); + const QString propname = property.properties.at(0).toString(); if (propname == "EmissiveFactor") { } } @@ -1703,7 +1704,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } #if defined(DEBUG_FBXREADER) else { - std::string propname = subobject.name.data(); + QString propname = subobject.name.data(); int unknown = 0; if ( (propname == "Version") ||(propname == "ShadingModel") @@ -1715,25 +1716,33 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, #endif } material.id = getID(object.properties); + + material._material = model::MaterialPointer(new model::Material()); + material._material->setEmissive(material.emissive); + material._material->setDiffuse(material.diffuse); + material._material->setSpecular(material.specular); + material._material->setShininess(material.shininess); + material._material->setOpacity(material.opacity); + materials.insert(material.id, material); } else if (object.name == "NodeAttribute") { #if defined(DEBUG_FBXREADER) - std::vector properties; + std::vector properties; foreach(const QVariant& v, object.properties) { - properties.push_back(v.toString().toStdString()); + properties.push_back(v.toString()); } #endif - std::string attribID = getID(object.properties).toStdString(); - std::string attributetype; + QString attribID = getID(object.properties); + QString attributetype; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "TypeFlags") { typeFlags.insert(getID(object.properties), subobject.properties.at(0).toString()); - attributetype = subobject.properties.at(0).toString().toStdString(); + attributetype = subobject.properties.at(0).toString(); } } - if (!attributetype.empty()) { + if (!attributetype.isEmpty()) { if (attributetype == "Light") { FBXLight light = extractLight(object); lights[attribID] = light; @@ -1781,7 +1790,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } #if defined(DEBUG_FBXREADER) else { - std::string objectname = object.name.data(); + QString objectname = object.name.data(); if ( objectname == "Pose" || objectname == "AnimationStack" || objectname == "AnimationLayer" @@ -1800,7 +1809,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, QString parentID = getID(connection.properties, 2); ooChildToParent.insert(childID, parentID); if (!hifiGlobalNodeID.isEmpty() && (parentID == hifiGlobalNodeID)) { - std::map< std::string, FBXLight >::iterator lit = lights.find(childID.toStdString()); + std::map< QString, FBXLight >::iterator lit = lights.find(childID); if (lit != lights.end()) { lightmapLevel = (*lit).second.intensity; if (lightmapLevel <= 0.0f) { @@ -1842,7 +1851,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } else if (loadLightmaps && type.contains("ambient")) { ambientTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } else { - std::string typenam = type.data(); + QString typenam = type.data(); counter++; } } @@ -1853,7 +1862,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } #if defined(DEBUG_FBXREADER) else { - std::string objectname = child.name.data(); + QString objectname = child.name.data(); if ( objectname == "Pose" || objectname == "CreationTime" || objectname == "FileId" @@ -1875,7 +1884,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, // TODO: check if is code is needed if (!lights.empty()) { if (hifiGlobalNodeID.isEmpty()) { - std::map< std::string, FBXLight >::iterator l = lights.begin(); + std::map< QString, FBXLight >::iterator l = lights.begin(); lightmapLevel = (*l).second.intensity; } } @@ -2138,6 +2147,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { if (extracted.partMaterialTextures.at(j).first == materialIndex) { FBXMeshPart& part = extracted.mesh.parts[j]; + + part._material = material._material; part.diffuseColor = material.diffuse; part.specularColor = material.specular; part.emissiveColor = material.emissive; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 44b2bfe4a5..98e4d60826 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -110,7 +110,7 @@ public: Transform transform; int texcoordSet; - std::string texcoordSetName; + QString texcoordSetName; }; /// A single part of a mesh (with the same material). diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index f5f998d0d9..a01b39c35f 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -494,13 +494,16 @@ void GLBackend::do_setUniformBuffer(Batch& batch, uint32 paramOffset) { #if defined(Q_OS_MAC) GLfloat* data = (GLfloat*) (uniformBuffer->getData() + rangeStart); glUniform4fv(slot, rangeSize / sizeof(GLfloat[4]), data); -#else + + // NOT working so we ll stick to the uniform float array until we move to core profile + // GLuint bo = getBufferID(*uniformBuffer); + //glUniformBufferEXT(_shader._program, slot, bo); +#elif defined(Q_OS_WIN) GLuint bo = getBufferID(*uniformBuffer); glBindBufferRange(GL_UNIFORM_BUFFER, slot, bo, rangeStart, rangeSize); - - // glUniformBufferEXT(_shader._program, slot, bo); - - //glBindBufferBase(GL_UNIFORM_BUFFER, slot, bo); +#else + GLfloat* data = (GLfloat*) (uniformBuffer->getData() + rangeStart); + glUniform4fv(slot, rangeSize / sizeof(GLfloat[4]), data); #endif CHECK_GL_ERROR(); } diff --git a/libraries/metavoxels/src/Endpoint.cpp b/libraries/metavoxels/src/Endpoint.cpp index 5a4e74ce08..a8a8f922cd 100644 --- a/libraries/metavoxels/src/Endpoint.cpp +++ b/libraries/metavoxels/src/Endpoint.cpp @@ -54,7 +54,7 @@ int Endpoint::parseData(const QByteArray& packet) { } void Endpoint::sendDatagram(const QByteArray& data) { - NodeList::getInstance()->writeDatagram(data, _node); + DependencyManager::get()->writeDatagram(data, _node); } void Endpoint::readMessage(Bitstream& in) { diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp index e0489b4bd0..0fd845d581 100644 --- a/libraries/metavoxels/src/MetavoxelClientManager.cpp +++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp @@ -34,8 +34,10 @@ MetavoxelClientManager::~MetavoxelClientManager() { } void MetavoxelClientManager::init() { - connect(NodeList::getInstance(), &NodeList::nodeAdded, this, &MetavoxelClientManager::maybeAttachClient); - connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &MetavoxelClientManager::maybeDeleteClient); + connect(DependencyManager::get().data(), &NodeList::nodeAdded, + this, &MetavoxelClientManager::maybeAttachClient); + connect(DependencyManager::get().data(), &NodeList::nodeKilled, + this, &MetavoxelClientManager::maybeDeleteClient); } SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(const glm::vec3& origin, @@ -43,7 +45,7 @@ SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(cons SharedObjectPointer closestSpanner; float closestDistance = FLT_MAX; - NodeList::getInstance()->eachNode([&](const SharedNodePointer& node){ + DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ if (node->getType() == NodeType::MetavoxelServer) { QMutexLocker locker(&node->getMutex()); MetavoxelClient* client = static_cast(node->getLinkedData()); @@ -186,7 +188,7 @@ MetavoxelClient* MetavoxelClientManager::createClient(const SharedNodePointer& n } void MetavoxelClientManager::guide(MetavoxelVisitor& visitor) { - NodeList::getInstance()->eachNode([&visitor](const SharedNodePointer& node){ + DependencyManager::get()->eachNode([&visitor](const SharedNodePointer& node){ if (node->getType() == NodeType::MetavoxelServer) { QMutexLocker locker(&node->getMutex()); MetavoxelClient* client = static_cast(node->getLinkedData()); diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index ffd5200a2e..7e8dbc4bae 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -109,9 +109,12 @@ ScriptCache* ScriptCache::getInstance() { } ScriptCache::ScriptCache() : - _engine(NULL) { - + _engine(NULL) +{ setEngine(new QScriptEngine(this)); + + const qint64 SCRIPT_DEFAULT_UNUSED_MAX_SIZE = 50 * BYTES_PER_MEGABYTES; + setUnusedResourceCacheSize(SCRIPT_DEFAULT_UNUSED_MAX_SIZE); } void ScriptCache::setEngine(QScriptEngine* engine) { diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 23aa8dad72..17cb919b19 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -22,7 +22,8 @@ #include "AddressManager.h" AddressManager::AddressManager() : - _currentDomain(), + _rootPlaceName(), + _rootPlaceID(), _positionGetter(NULL), _orientationGetter(NULL) { @@ -30,14 +31,14 @@ AddressManager::AddressManager() : } bool AddressManager::isConnected() { - return NodeList::getInstance()->getDomainHandler().isConnected(); + return DependencyManager::get()->getDomainHandler().isConnected(); } const QUrl AddressManager::currentAddress() const { QUrl hifiURL; hifiURL.setScheme(HIFI_URL_SCHEME); - hifiURL.setHost(_currentDomain); + hifiURL.setHost(_rootPlaceName); hifiURL.setPath(currentPath()); return hifiURL; @@ -88,11 +89,6 @@ const QString AddressManager::currentPath(bool withOrientation) const { } } -QString AddressManager::getDomainID() const { - const QUuid& domainID = NodeList::getInstance()->getDomainHandler().getUUID(); - return domainID.isNull() ? "" : uuidStringWithoutCurlyBraces(domainID); -} - const JSONCallbackParameters& AddressManager::apiCallbackParameters() { static bool hasSetupParameters = false; static JSONCallbackParameters callbackParams; @@ -173,67 +169,83 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { emit lookupResultsFinished(); } -void AddressManager::goToAddressFromObject(const QVariantMap& addressMap) { - const QString ADDRESS_API_DOMAIN_KEY = "domain"; - const QString ADDRESS_API_ONLINE_KEY = "online"; +void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) { - if (!addressMap.contains(ADDRESS_API_ONLINE_KEY) - || addressMap[ADDRESS_API_ONLINE_KEY].toBool()) { + const QString DATA_OBJECT_PLACE_KEY = "place"; + const QString DATA_OBJECT_USER_LOCATION_KEY = "location"; + + QVariantMap locationMap; + if (dataObject.contains(DATA_OBJECT_PLACE_KEY)) { + locationMap = dataObject[DATA_OBJECT_PLACE_KEY].toMap(); + } else { + locationMap = dataObject[DATA_OBJECT_USER_LOCATION_KEY].toMap(); + } + + if (!locationMap.isEmpty()) { + const QString LOCATION_API_ROOT_KEY = "root"; + const QString LOCATION_API_DOMAIN_KEY = "domain"; + const QString LOCATION_API_ONLINE_KEY = "online"; - if (addressMap.contains(ADDRESS_API_DOMAIN_KEY)) { - QVariantMap domainObject = addressMap[ADDRESS_API_DOMAIN_KEY].toMap(); + if (!locationMap.contains(LOCATION_API_ONLINE_KEY) + || locationMap[LOCATION_API_ONLINE_KEY].toBool()) { - const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address"; - const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address"; - - if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { - QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); - - emit possibleDomainChangeRequired(domainHostname, DEFAULT_DOMAIN_SERVER_PORT); - } else { - QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); - - const QString DOMAIN_ID_KEY = "id"; - QString domainIDString = domainObject[DOMAIN_ID_KEY].toString(); - QUuid domainID(domainIDString); - - emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); + QVariantMap rootMap = locationMap[LOCATION_API_ROOT_KEY].toMap(); + if (rootMap.isEmpty()) { + rootMap = locationMap; } - // set our current domain to the name that came back - const QString DOMAIN_NAME_KEY = "name"; + QVariantMap domainObject = rootMap[LOCATION_API_DOMAIN_KEY].toMap(); - _currentDomain = domainObject[DOMAIN_NAME_KEY].toString(); - - // take the path that came back - const QString LOCATION_KEY = "location"; - const QString LOCATION_PATH_KEY = "path"; - QString returnedPath; - - if (domainObject.contains(LOCATION_PATH_KEY)) { - returnedPath = domainObject[LOCATION_PATH_KEY].toString(); - } else if (domainObject.contains(LOCATION_KEY)) { - returnedPath = domainObject[LOCATION_KEY].toMap()[LOCATION_PATH_KEY].toString(); - } else if (addressMap.contains(LOCATION_PATH_KEY)) { - returnedPath = addressMap[LOCATION_PATH_KEY].toString(); - } - - bool shouldFaceViewpoint = addressMap.contains(ADDRESS_API_ONLINE_KEY); - - if (!returnedPath.isEmpty()) { - // try to parse this returned path as a viewpoint, that's the only thing it could be for now - if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) { - qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath; + if (!domainObject.isEmpty()) { + const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address"; + const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address"; + + if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { + QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); + + emit possibleDomainChangeRequired(domainHostname, DEFAULT_DOMAIN_SERVER_PORT); + } else { + QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); + + const QString DOMAIN_ID_KEY = "id"; + QString domainIDString = domainObject[DOMAIN_ID_KEY].toString(); + QUuid domainID(domainIDString); + + emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); } + + // set our current root place id to the ID that came back + const QString PLACE_ID_KEY = "id"; + _rootPlaceID = rootMap[PLACE_ID_KEY].toUuid(); + + // set our current root place name to the name that came back + const QString PLACE_NAME_KEY = "name"; + QString newRootPlaceName = rootMap[PLACE_NAME_KEY].toString(); + setRootPlaceName(newRootPlaceName); + + // take the path that came back + const QString PLACE_PATH_KEY = "path"; + QString returnedPath = locationMap[PLACE_PATH_KEY].toString(); + + bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY); + + if (!returnedPath.isEmpty()) { + // try to parse this returned path as a viewpoint, that's the only thing it could be for now + if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) { + qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath; + } + } + } else { + qDebug() << "Received an address manager API response with no domain key. Cannot parse."; + qDebug() << locationMap; } - } else { - qDebug() << "Received an address manager API response with no domain key. Cannot parse."; - qDebug() << addressMap; + // we've been told that this result exists but is offline, emit our signal so the application can handle + emit lookupResultIsOffline(); } } else { - // we've been told that this result exists but is offline, emit our signal so the application can handle - emit lookupResultIsOffline(); + qDebug() << "Received an address manager API response with no location key or place key. Cannot parse."; + qDebug() << locationMap; } } @@ -365,9 +377,18 @@ bool AddressManager::handleUsername(const QString& lookupString) { return false; } +void AddressManager::setRootPlaceName(const QString& rootPlaceName) { + if (rootPlaceName != _rootPlaceName) { + _rootPlaceName = rootPlaceName; + emit rootPlaceNameChanged(_rootPlaceName); + } +} -void AddressManager::setDomainInfo(const QString& hostname, quint16 port, const QString& domainName) { - _currentDomain = domainName.isEmpty() ? hostname : domainName; + +void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { + _rootPlaceName = hostname; + _rootPlaceID = QUuid(); + emit possibleDomainChangeRequired(hostname, port); } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index dee070740f..58e4e88330 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -27,15 +27,14 @@ const QString DEFAULT_HIFI_ADDRESS = "hifi://sandbox"; typedef const glm::vec3& (*PositionGetter)(); typedef glm::quat (*OrientationGetter)(); -class AddressManager : public QObject { +class AddressManager : public QObject, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY(AddressManager) + SINGLETON_DEPENDENCY Q_PROPERTY(bool isConnected READ isConnected) Q_PROPERTY(QUrl href READ currentAddress) Q_PROPERTY(QString protocol READ getProtocol) - Q_PROPERTY(QString hostname READ getCurrentDomain) + Q_PROPERTY(QString hostname READ getRootPlaceName) Q_PROPERTY(QString pathname READ currentPath) - Q_PROPERTY(QString domainID READ getDomainID) public: bool isConnected(); const QString& getProtocol() { return HIFI_URL_SCHEME; }; @@ -43,8 +42,10 @@ public: const QUrl currentAddress() const; const QString currentPath(bool withOrientation = true) const; - const QString& getCurrentDomain() const { return _currentDomain; } - QString getDomainID() const; + const QUuid& getRootPlaceID() const { return _rootPlaceID; } + + const QString& getRootPlaceName() const { return _rootPlaceName; } + void setRootPlaceName(const QString& rootPlaceName); void attemptPlaceNameLookup(const QString& lookupString); @@ -69,13 +70,14 @@ signals: void locationChangeRequired(const glm::vec3& newPosition, bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); + void rootPlaceNameChanged(const QString& newRootPlaceName); protected: AddressManager(); private slots: void handleAPIResponse(QNetworkReply& requestReply); void handleAPIError(QNetworkReply& errorReply); private: - void setDomainInfo(const QString& hostname, quint16 port, const QString& domainName = QString()); + void setDomainInfo(const QString& hostname, quint16 port); const JSONCallbackParameters& apiCallbackParameters(); @@ -85,7 +87,8 @@ private: bool handleRelativeViewpoint(const QString& pathSubsection, bool shouldFace = false); bool handleUsername(const QString& lookupString); - QString _currentDomain; + QString _rootPlaceName; + QUuid _rootPlaceID; PositionGetter _positionGetter; OrientationGetter _orientationGetter; }; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index e4f31e666e..67cf81a73a 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -184,7 +184,7 @@ void DomainHandler::setIsConnected(bool isConnected) { } void DomainHandler::requestDomainSettings() { - NodeType_t owningNodeType = NodeList::getInstance()->getOwnerType(); + NodeType_t owningNodeType = DependencyManager::get()->getOwnerType(); if (owningNodeType == NodeType::Agent) { // for now the agent nodes don't need any settings - this allows local assignment-clients // to connect to a domain that is using automatic networking (since we don't have TCP hole punch yet) @@ -198,7 +198,7 @@ void DomainHandler::requestDomainSettings() { settingsJSONURL.setHost(_hostname); settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT); settingsJSONURL.setPath("/settings.json"); - Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType()); + Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType)); qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index b0c2defd9b..5bf416af7f 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -38,38 +38,10 @@ const char SOLO_NODE_TYPES[2] = { const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data.highfidelity.io"); -std::auto_ptr LimitedNodeList::_sharedInstance; - -LimitedNodeList* LimitedNodeList::createInstance(unsigned short socketListenPort, unsigned short dtlsPort) { - NodeType::init(); - - if (_sharedInstance.get()) { - qDebug() << "LimitedNodeList called with existing instance." << - "Releasing auto_ptr, deleting existing instance and creating a new one."; - - delete _sharedInstance.release(); - } - - _sharedInstance = std::auto_ptr(new LimitedNodeList(socketListenPort, dtlsPort)); - - // register the SharedNodePointer meta-type for signals/slots - qRegisterMetaType(); - - return _sharedInstance.get(); -} - -LimitedNodeList* LimitedNodeList::getInstance() { - if (!_sharedInstance.get()) { - qDebug("LimitedNodeList getInstance called before call to createInstance. Returning NULL pointer."); - } - - return _sharedInstance.get(); -} - - LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort) : _sessionUUID(), _nodeHash(), + _nodeMutex(QReadWriteLock::Recursive), _nodeSocket(this), _dtlsSocket(NULL), _localSockAddr(), @@ -79,6 +51,15 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _numCollectedBytes(0), _packetStatTimer() { + static bool firstCall = true; + if (firstCall) { + NodeType::init(); + + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + firstCall = false; + } + _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort(); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 9fa9ed9d91..1c841637df 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -29,6 +29,8 @@ #include +#include + #include "DomainHandler.h" #include "Node.h" #include "UUIDHasher.h" @@ -67,12 +69,11 @@ namespace PingType { const PingType_t Symmetric = 3; } -class LimitedNodeList : public QObject { +class LimitedNodeList : public QObject, public Dependency { Q_OBJECT + SINGLETON_DEPENDENCY + public: - static LimitedNodeList* createInstance(unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); - static LimitedNodeList* getInstance(); - const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); @@ -179,10 +180,9 @@ signals: void localSockAddrChanged(const HifiSockAddr& localSockAddr); void publicSockAddrChanged(const HifiSockAddr& publicSockAddr); + protected: - static std::auto_ptr _sharedInstance; - - LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort); + LimitedNodeList(unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); LimitedNodeList(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton void operator=(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index fefeb95193..bb095b2a3d 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -26,33 +26,6 @@ #include "SharedUtil.h" #include "UUID.h" -NodeList* NodeList::createInstance(char ownerType, unsigned short socketListenPort, unsigned short dtlsPort) { - - NodeType::init(); - - if (_sharedInstance.get()) { - qDebug() << "NodeList called with existing instance." << - "Releasing auto_ptr, deleting existing instance and creating a new one."; - - delete _sharedInstance.release(); - } - - _sharedInstance = std::auto_ptr(new NodeList(ownerType, socketListenPort, dtlsPort)); - - // register the SharedNodePointer meta-type for signals/slots - qRegisterMetaType(); - - return static_cast(_sharedInstance.get()); -} - -NodeList* NodeList::getInstance() { - if (!_sharedInstance.get()) { - qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer."); - } - - return static_cast(_sharedInstance.get()); -} - NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) : LimitedNodeList(socketListenPort, dtlsListenPort), _ownerType(newOwnerType), @@ -63,7 +36,14 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned _hasCompletedInitialSTUNFailure(false), _stunRequestsSinceSuccess(0) { - AddressManager::SharedPointer addressManager = DependencyManager::get(); + static bool firstCall = true; + if (firstCall) { + NodeType::init(); + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + firstCall = false; + } + auto addressManager = DependencyManager::get(); // handle domain change signals from AddressManager connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired, diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 921f33b454..6128c0f7c9 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -14,6 +14,7 @@ #include #include +#include #ifndef _WIN32 #include // not on windows, not needed for mac or windows @@ -27,6 +28,8 @@ #include #include +#include + #include "DomainHandler.h" #include "LimitedNodeList.h" #include "Node.h" @@ -39,9 +42,9 @@ class Assignment; class NodeList : public LimitedNodeList { Q_OBJECT + SINGLETON_DEPENDENCY + public: - static NodeList* createInstance(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); - static NodeList* getInstance(); NodeType_t getOwnerType() const { return _ownerType; } void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } @@ -70,7 +73,8 @@ public slots: signals: void limitOfSilentDomainCheckInsReached(); private: - NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort); + NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile + NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 710bbe3f03..5b9811a152 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -159,7 +159,7 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU char* position = packet + numTypeBytes + sizeof(PacketVersion); - QUuid packUUID = connectionUUID.isNull() ? LimitedNodeList::getInstance()->getSessionUUID() : connectionUUID; + QUuid packUUID = connectionUUID.isNull() ? DependencyManager::get()->getSessionUUID() : connectionUUID; QByteArray rfcUUID = packUUID.toRfc4122(); memcpy(position, rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 3edfc47c04..f560d94b86 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -271,7 +271,7 @@ bool PacketSender::nonThreadedProcess() { unlock(); // send the packet through the NodeList... - NodeList::getInstance()->writeDatagram(temporary.getByteArray(), temporary.getNode()); + DependencyManager::get()->writeDatagram(temporary.getByteArray(), temporary.getNode()); packetsSentThisCall++; _packetsOverCheckInterval++; _totalPacketsSent++; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 4b769c9a28..5a95bd6028 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -20,11 +20,12 @@ #include "ResourceCache.h" +#define clamp(x, min, max) (((x) < (min)) ? (min) :\ + (((x) > (max)) ? (max) :\ + (x))) + ResourceCache::ResourceCache(QObject* parent) : - QObject(parent), - _lastLRUKey(0) -{ - + QObject(parent) { } ResourceCache::~ResourceCache() { @@ -32,7 +33,7 @@ ResourceCache::~ResourceCache() { // list on destruction, so keep clearing until there are no references left while (!_unusedResources.isEmpty()) { foreach (const QSharedPointer& resource, _unusedResources) { - resource->setCache(NULL); + resource->setCache(nullptr); } _unusedResources.clear(); } @@ -72,37 +73,40 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& return resource; } +void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) { + _unusedResourcesMaxSize = clamp(unusedResourcesMaxSize, MIN_UNUSED_MAX_SIZE, MAX_UNUSED_MAX_SIZE); + reserveUnusedResource(0); +} + void ResourceCache::addUnusedResource(const QSharedPointer& resource) { - static const int BYTES_PER_MEGABYTES = 1024 * 1024; - const int RETAINED_RESOURCE_COUNT = 50; - const int RETAINED_RESOURCE_SIZE = 100 * BYTES_PER_MEGABYTES; - - while (_unusedResourcesTotalBytes + resource->getBytesTotal() > RETAINED_RESOURCE_SIZE && - !_unusedResources.empty()) { - // unload the oldest resource - QMap >::iterator it = _unusedResources.begin(); - - _unusedResourcesTotalBytes -= it.value()->getBytesTotal(); - it.value()->setCache(NULL); - _unusedResources.erase(it); + if (resource->getBytesTotal() > _unusedResourcesMaxSize) { + // If it doesn't fit anyway, let's leave whatever is already in the cache. + resource->setCache(nullptr); + return; } + reserveUnusedResource(resource->getBytesTotal()); - - if (_unusedResources.size() > RETAINED_RESOURCE_COUNT) { - // unload the oldest resource - QMap >::iterator it = _unusedResources.begin(); - it.value()->setCache(NULL); - _unusedResources.erase(it); - } resource->setLRUKey(++_lastLRUKey); _unusedResources.insert(resource->getLRUKey(), resource); - _unusedResourcesTotalBytes += resource->getBytesTotal(); + _unusedResourcesSize += resource->getBytesTotal(); } void ResourceCache::removeUnusedResource(const QSharedPointer& resource) { if (_unusedResources.contains(resource->getLRUKey())) { _unusedResources.remove(resource->getLRUKey()); - _unusedResourcesTotalBytes -= resource->getBytesTotal(); + _unusedResourcesSize -= resource->getBytesTotal(); + } +} + +void ResourceCache::reserveUnusedResource(qint64 resourceSize) { + while (!_unusedResources.empty() && + _unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) { + // unload the oldest resource + QMap >::iterator it = _unusedResources.begin(); + + _unusedResourcesSize -= it.value()->getBytesTotal(); + it.value()->setCache(nullptr); + _unusedResources.erase(it); } } @@ -150,9 +154,7 @@ QList ResourceCache::_loadingRequests; Resource::Resource(const QUrl& url, bool delayLoad) : _url(url), - _request(url), - _lruKey(0), - _reply(NULL) { + _request(url) { init(); @@ -168,6 +170,7 @@ Resource::~Resource() { if (_reply) { ResourceCache::requestCompleted(this); delete _reply; + _reply = nullptr; } } @@ -213,13 +216,13 @@ float Resource::getLoadPriority() { } void Resource::refresh() { - if (_reply == NULL && !(_loaded || _failedToLoad)) { + if (_reply == nullptr && !(_loaded || _failedToLoad)) { return; } if (_reply) { ResourceCache::requestCompleted(this); delete _reply; - _reply = NULL; + _reply = nullptr; } init(); _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); @@ -295,10 +298,10 @@ void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { } _reply->disconnect(this); QNetworkReply* reply = _reply; - _reply = NULL; + _reply = nullptr; _replyTimer->disconnect(this); _replyTimer->deleteLater(); - _replyTimer = NULL; + _replyTimer = nullptr; ResourceCache::requestCompleted(this); downloadFinished(reply); @@ -330,10 +333,10 @@ void Resource::makeRequest() { void Resource::handleReplyError(QNetworkReply::NetworkError error, QDebug debug) { _reply->disconnect(this); _reply->deleteLater(); - _reply = NULL; + _reply = nullptr; _replyTimer->disconnect(this); _replyTimer->deleteLater(); - _replyTimer = NULL; + _replyTimer = nullptr; ResourceCache::requestCompleted(this); // retry for certain types of failures diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 0a4121ca5e..519b205d46 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -27,6 +27,19 @@ class QTimer; class Resource; +static const qint64 BYTES_PER_MEGABYTES = 1024 * 1024; +static const qint64 BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES; + +// Windows can have troubles allocating that much memory in ram sometimes +// so default cache size at 100 MB on windows (1GB otherwise) +#ifdef Q_OS_WIN32 +static const qint64 DEFAULT_UNUSED_MAX_SIZE = 100 * BYTES_PER_MEGABYTES; +#else +static const qint64 DEFAULT_UNUSED_MAX_SIZE = 1024 * BYTES_PER_MEGABYTES; +#endif +static const qint64 MIN_UNUSED_MAX_SIZE = 0; +static const qint64 MAX_UNUSED_MAX_SIZE = 10 * BYTES_PER_GIGABYTES; + /// Base class for resource caches. class ResourceCache : public QObject { Q_OBJECT @@ -34,6 +47,9 @@ class ResourceCache : public QObject { public: static void setRequestLimit(int limit) { _requestLimit = limit; } static int getRequestLimit() { return _requestLimit; } + + void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize); + qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; } static const QList& getLoadingRequests() { return _loadingRequests; } @@ -45,8 +61,8 @@ public: void refresh(const QUrl& url); protected: - - qint64 _unusedResourcesTotalBytes = 0; + qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE; + qint64 _unusedResourcesSize = 0; QMap > _unusedResources; /// Loads a resource from the specified URL. @@ -62,16 +78,16 @@ protected: void addUnusedResource(const QSharedPointer& resource); void removeUnusedResource(const QSharedPointer& resource); + void reserveUnusedResource(qint64 resourceSize); static void attemptRequest(Resource* resource); static void requestCompleted(Resource* resource); private: - friend class Resource; QHash > _resources; - int _lastLRUKey; + int _lastLRUKey = 0; static int _requestLimit; static QList > _pendingRequests; @@ -152,9 +168,9 @@ protected: QUrl _url; QNetworkRequest _request; - bool _startedLoading; - bool _failedToLoad; - bool _loaded; + bool _startedLoading = false; + bool _failedToLoad = false; + bool _loaded = false; QHash, float> _loadPriorities; QWeakPointer _self; QPointer _cache; @@ -176,13 +192,12 @@ private: friend class ResourceCache; - int _lruKey; - QNetworkReply* _reply; - QTimer* _replyTimer; - int _index; - qint64 _bytesReceived; - qint64 _bytesTotal; - int _attempts; + int _lruKey = 0; + QNetworkReply* _reply = nullptr; + QTimer* _replyTimer = nullptr; + qint64 _bytesReceived = 0; + qint64 _bytesTotal = 0; + int _attempts = 0; }; uint qHash(const QPointer& value, uint seed = 0); diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 52644a9a4e..21f520babb 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -32,7 +32,7 @@ void ThreadedAssignment::setFinished(bool isFinished) { if (_isFinished) { aboutToFinish(); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); // if we have a datagram processing thread, quit it and wait on it to make sure that // the node socket is back on the same thread as the NodeList @@ -43,7 +43,7 @@ void ThreadedAssignment::setFinished(bool isFinished) { _datagramProcessingThread->wait(); // set node socket parent back to NodeList - nodeList->getNodeSocket().setParent(nodeList); + nodeList->getNodeSocket().setParent(nodeList.data()); } // move the NodeList back to the QCoreApplication instance's thread @@ -57,7 +57,7 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy // change the logging target name while the assignment is running LogHandler::getInstance().setTargetName(targetName); - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); nodeList->setOwnerType(nodeType); // this is a temp fix for Qt 5.3 - rebinding the node socket gives us readyRead for the socket on this thread @@ -68,7 +68,7 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); QTimer* silentNodeRemovalTimer = new QTimer(this); - connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); + connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList.data(), SLOT(removeSilentNodes())); silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_MSECS); if (shouldSendStats) { @@ -80,7 +80,7 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy } void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject &statsObject) { - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); float packetsPerSecond, bytesPerSecond; nodeList->getPacketStats(packetsPerSecond, bytesPerSecond); @@ -98,15 +98,15 @@ void ThreadedAssignment::sendStatsPacket() { } void ThreadedAssignment::checkInWithDomainServerOrExit() { - if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + if (DependencyManager::get()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { setFinished(true); } else { - NodeList::getInstance()->sendDomainServerCheckIn(); + DependencyManager::get()->sendDomainServerCheckIn(); } } bool ThreadedAssignment::readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr) { - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); if (nodeList->getNodeSocket().hasPendingDatagrams()) { destinationByteArray.resize(nodeList->getNodeSocket().pendingDatagramSize()); diff --git a/libraries/octree/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp index bcd5e9ac1c..c050e0bffe 100644 --- a/libraries/octree/src/JurisdictionListener.cpp +++ b/libraries/octree/src/JurisdictionListener.cpp @@ -20,10 +20,10 @@ JurisdictionListener::JurisdictionListener(NodeType_t type) : _nodeType(type), _packetSender(JurisdictionListener::DEFAULT_PACKETS_PER_SECOND) { - connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &JurisdictionListener::nodeKilled); + connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &JurisdictionListener::nodeKilled); // tell our NodeList we want to hear about nodes with our node type - NodeList::getInstance()->addNodeTypeToInterestSet(type); + DependencyManager::get()->addNodeTypeToInterestSet(type); } void JurisdictionListener::nodeKilled(SharedNodePointer node) { @@ -38,7 +38,7 @@ bool JurisdictionListener::queueJurisdictionRequest() { int sizeOut = populatePacketHeader(reinterpret_cast(bufferOut), PacketTypeJurisdictionRequest); int nodeCount = 0; - NodeList::getInstance()->eachNode([&](const SharedNodePointer& node) { + DependencyManager::get()->eachNode([&](const SharedNodePointer& node) { if (node->getType() == getNodeType() && node->getActiveSocket()) { _packetSender.queuePacketForSending(node, QByteArray(reinterpret_cast(bufferOut), sizeOut)); nodeCount++; diff --git a/libraries/octree/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp index d78d883204..f85cf406b9 100644 --- a/libraries/octree/src/JurisdictionSender.cpp +++ b/libraries/octree/src/JurisdictionSender.cpp @@ -61,7 +61,7 @@ bool JurisdictionSender::process() { QUuid nodeUUID = _nodesRequestingJurisdictions.front(); _nodesRequestingJurisdictions.pop(); - SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(nodeUUID); + SharedNodePointer node = DependencyManager::get()->nodeWithUUID(nodeUUID); if (node && node->getActiveSocket()) { _packetSender.queuePacketForSending(node, QByteArray(reinterpret_cast(bufferOut), sizeOut)); diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index cae8919a64..cc47190228 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -52,7 +52,7 @@ bool OctreeEditPacketSender::serversExist() const { bool hasServers = false; bool atLeastOneJurisdictionMissing = false; // assume the best - NodeList::getInstance()->eachNodeBreakable([&](const SharedNodePointer& node){ + DependencyManager::get()->eachNodeBreakable([&](const SharedNodePointer& node){ if (node->getType() == getMyNodeType() && node->getActiveSocket()) { QUuid nodeUUID = node->getUUID(); @@ -85,7 +85,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c size_t length, qint64 satoshiCost) { bool wantDebug = false; - NodeList::getInstance()->eachNode([&](const SharedNodePointer& node){ + DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are getMyNodeType() if (node->getType() == getMyNodeType() && ((node->getUUID() == nodeUUID) || (nodeUUID.isNull())) @@ -192,7 +192,7 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, size_t le // for a different server... So we need to actually manage multiple queued packets... one // for each server - NodeList::getInstance()->eachNode([&](const SharedNodePointer& node){ + DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are getMyNodeType() if (node->getActiveSocket() && node->getType() == getMyNodeType()) { QUuid nodeUUID = node->getUUID(); @@ -245,7 +245,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch // for each server _packetsQueueLock.lock(); - NodeList::getInstance()->eachNode([&](const SharedNodePointer& node){ + DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are getMyNodeType() if (node->getActiveSocket() && node->getType() == getMyNodeType()) { QUuid nodeUUID = node->getUUID(); @@ -380,7 +380,7 @@ void OctreeEditPacketSender::processNackPacket(const QByteArray& packet) { // retrieve packet from history const QByteArray* packet = sentPacketHistory.getPacket(sequenceNumber); if (packet) { - const SharedNodePointer& node = NodeList::getInstance()->nodeWithUUID(sendingNodeUUID); + const SharedNodePointer& node = DependencyManager::get()->nodeWithUUID(sendingNodeUUID); queuePacketForSending(node, *packet); } } diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index e0ca22e4e8..8fe4fbd4d5 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -77,7 +77,7 @@ void OctreeHeadlessViewer::queryOctree() { int inViewServers = 0; int unknownJurisdictionServers = 0; - NodeList::getInstance()->eachNode([&](const SharedNodePointer& node){ + DependencyManager::get()->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { totalServers++; @@ -140,7 +140,7 @@ void OctreeHeadlessViewer::queryOctree() { qDebug("perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer); } - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); nodeList->eachNode([&](const SharedNodePointer& node){ // only send to the NodeTypes that are serverType if (node->getActiveSocket() && node->getType() == serverType) { diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index d9ebea0c2b..f0612cc9b1 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -9,10 +9,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include +#include #include +#include #include +#include +#include #include +#include +#include #include #include @@ -20,30 +27,71 @@ #include "OctreePersistThread.h" const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds -const int OctreePersistThread::DEFAULT_BACKUP_INTERVAL = 1000 * 60 * 30; // every 30 minutes -const QString OctreePersistThread::DEFAULT_BACKUP_EXTENSION_FORMAT(".backup.%N"); -const int OctreePersistThread::DEFAULT_MAX_BACKUP_VERSIONS = 5; - OctreePersistThread::OctreePersistThread(Octree* tree, const QString& filename, int persistInterval, - bool wantBackup, int backupInterval, const QString& backupExtensionFormat, - int maxBackupVersions, bool debugTimestampNow) : + bool wantBackup, const QJsonObject& settings, bool debugTimestampNow) : _tree(tree), _filename(filename), - _backupExtensionFormat(backupExtensionFormat), - _maxBackupVersions(maxBackupVersions), _persistInterval(persistInterval), - _backupInterval(backupInterval), _initialLoadComplete(false), _loadTimeUSecs(0), _lastCheck(0), - _lastBackup(0), _wantBackup(wantBackup), _debugTimestampNow(debugTimestampNow), _lastTimeDebug(0) { + parseSettings(settings); } +void OctreePersistThread::parseSettings(const QJsonObject& settings) { + if (settings["backups"].isArray()) { + const QJsonArray& backupRules = settings["backups"].toArray(); + qDebug() << "BACKUP RULES:"; + + foreach (const QJsonValue& value, backupRules) { + QJsonObject obj = value.toObject(); + qDebug() << " Name:" << obj["Name"].toString(); + qDebug() << " format:" << obj["format"].toString(); + qDebug() << " interval:" << obj["backupInterval"].toInt(); + qDebug() << " count:" << obj["maxBackupVersions"].toInt(); + + BackupRule newRule = { obj["Name"].toString(), obj["backupInterval"].toInt(), + obj["format"].toString(), obj["maxBackupVersions"].toInt(), 0}; + + newRule.lastBackup = getMostRecentBackupTimeInUsecs(obj["format"].toString()); + + if (newRule.lastBackup > 0) { + quint64 now = usecTimestampNow(); + quint64 sinceLastBackup = now - newRule.lastBackup; + qDebug() << " lastBackup:" << qPrintable(formatUsecTime(sinceLastBackup)) << "ago"; + } else { + qDebug() << " lastBackup: NEVER"; + } + + _backupRules << newRule; + } + } else { + qDebug() << "BACKUP RULES: NONE"; + } +} + +quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& format) { + + quint64 mostRecentBackupInUsecs = 0; + + QString mostRecentBackupFileName; + QDateTime mostRecentBackupTime; + + bool recentBackup = getMostRecentBackup(format, mostRecentBackupFileName, mostRecentBackupTime); + + if (recentBackup) { + mostRecentBackupInUsecs = mostRecentBackupTime.toMSecsSinceEpoch() * USECS_PER_MSEC; + } + + return mostRecentBackupInUsecs; +} + + bool OctreePersistThread::process() { if (!_initialLoadComplete) { @@ -55,6 +103,25 @@ bool OctreePersistThread::process() { _tree->lockForWrite(); { PerformanceWarning warn(true, "Loading Octree File", true); + + // First check to make sure "lock" file doesn't exist. If it does exist, then + // our last save crashed during the save, and we want to load our most recent backup. + QString lockFileName = _filename + ".lock"; + std::ifstream lockFile(qPrintable(lockFileName), std::ios::in|std::ios::binary|std::ios::ate); + if(lockFile.is_open()) { + qDebug() << "WARNING: Octree lock file detected at startup:" << lockFileName + << "-- Attempting to restore from previous backup file."; + + // This is where we should attempt to find the most recent backup and restore from + // that file as our persist file. + restoreFromMostRecentBackup(); + + lockFile.close(); + qDebug() << "Loading Octree... lock file closed:" << lockFileName; + remove(qPrintable(lockFileName)); + qDebug() << "Loading Octree... lock file removed:" << lockFileName; + } + persistantFileRead = _tree->readFromSVOFile(_filename.toLocal8Bit().constData()); _tree->pruneTree(); } @@ -85,7 +152,13 @@ bool OctreePersistThread::process() { } _initialLoadComplete = true; - _lastBackup = _lastCheck = usecTimestampNow(); // we just loaded, no need to save again + + // Since we just loaded the persistent file, we can consider ourselves as having "just checked" for persistance. + _lastCheck = usecTimestampNow(); // we just loaded, no need to save again + + // This last persist time is not really used until the file is actually persisted. It is only + // used in formatting the backup filename in cases of non-rolling backup names. However, we don't + // want an uninitialized value for this, so we set it to the current time (startup of the server) time(&_lastPersistTime); emit loadCompleted(); @@ -142,82 +215,180 @@ void OctreePersistThread::persist() { backup(); // handle backup if requested - qDebug() << "saving Octree to file " << _filename << "..."; - _tree->writeToSVOFile(qPrintable(_filename)); - time(&_lastPersistTime); - _tree->clearDirtyBit(); // tree is clean after saving - qDebug() << "DONE saving Octree to file..."; + + // create our "lock" file to indicate we're saving. + QString lockFileName = _filename + ".lock"; + std::ofstream lockFile(qPrintable(lockFileName), std::ios::out|std::ios::binary); + if(lockFile.is_open()) { + qDebug() << "saving Octree lock file created at:" << lockFileName; + + qDebug() << "saving Octree to file " << _filename << "..."; + + _tree->writeToSVOFile(qPrintable(_filename)); + time(&_lastPersistTime); + _tree->clearDirtyBit(); // tree is clean after saving + qDebug() << "DONE saving Octree to file..."; + + lockFile.close(); + qDebug() << "saving Octree lock file closed:" << lockFileName; + remove(qPrintable(lockFileName)); + qDebug() << "saving Octree lock file removed:" << lockFileName; + } } } -void OctreePersistThread::rollOldBackupVersions() { - if (!_backupExtensionFormat.contains("%N")) { - return; // this backup extension format doesn't support rolling +void OctreePersistThread::restoreFromMostRecentBackup() { + qDebug() << "Restoring from most recent backup..."; + + QString mostRecentBackupFileName; + QDateTime mostRecentBackupTime; + + bool recentBackup = getMostRecentBackup(QString(""), mostRecentBackupFileName, mostRecentBackupTime); + + // If we found a backup file, restore from that file. + if (recentBackup) { + qDebug() << "BEST backup file:" << mostRecentBackupFileName << " last modified:" << mostRecentBackupTime.toString(); + + qDebug() << "Removing old file:" << _filename; + remove(qPrintable(_filename)); + + qDebug() << "Restoring backup file " << mostRecentBackupFileName << "..."; + bool result = QFile::copy(mostRecentBackupFileName, _filename); + if (result) { + qDebug() << "DONE restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; + } else { + qDebug() << "ERROR while restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; + } + } else { + qDebug() << "NO BEST backup file found."; } +} - qDebug() << "Rolling old backup versions..."; - for(int n = _maxBackupVersions - 1; n > 0; n--) { - QString backupExtensionN = _backupExtensionFormat; - QString backupExtensionNplusOne = _backupExtensionFormat; - backupExtensionN.replace(QString("%N"), QString::number(n)); - backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1)); - - QString backupFilenameN = _filename + backupExtensionN; - QString backupFilenameNplusOne = _filename + backupExtensionNplusOne; +bool OctreePersistThread::getMostRecentBackup(const QString& format, + QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime) { - QFile backupFileN(backupFilenameN); + // Based on our backup file name, determine the path and file name pattern for backup files + QFileInfo persistFileInfo(_filename); + QString path = persistFileInfo.path(); + QString fileNamePart = persistFileInfo.fileName(); - if (backupFileN.exists()) { - qDebug() << "rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - int result = rename(qPrintable(backupFilenameN), qPrintable(backupFilenameNplusOne)); - if (result == 0) { - qDebug() << "DONE rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - } else { - qDebug() << "ERROR in rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - } + QStringList filters; + + if (format.isEmpty()) { + // Create a file filter that will find all backup files of this extension format + foreach(const BackupRule& rule, _backupRules) { + QString backupExtension = rule.extensionFormat; + backupExtension.replace(QRegExp("%."), "*"); + QString backupFileNamePart = fileNamePart + backupExtension; + filters << backupFileNamePart; + } + } else { + QString backupExtension = format; + backupExtension.replace(QRegExp("%."), "*"); + QString backupFileNamePart = fileNamePart + backupExtension; + filters << backupFileNamePart; + } + + bool bestBackupFound = false; + QString bestBackupFile; + QDateTime bestBackupFileTime; + + // Iterate over all of the backup files in the persist location + QDirIterator dirIterator(path, filters, QDir::Files|QDir::NoSymLinks, QDirIterator::NoIteratorFlags); + while(dirIterator.hasNext()) { + + dirIterator.next(); + QDateTime lastModified = dirIterator.fileInfo().lastModified(); + + // Based on last modified date, track the most recently modified file as the best backup + if (lastModified > bestBackupFileTime) { + bestBackupFound = true; + bestBackupFile = dirIterator.filePath(); + bestBackupFileTime = lastModified; } } - qDebug() << "Done rolling old backup versions..."; + + // If we found a backup then return the results + if (bestBackupFound) { + mostRecentBackupFileName = bestBackupFile; + mostRecentBackupTime = bestBackupFileTime; + } + return bestBackupFound; +} + +void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { + + if (rule.extensionFormat.contains("%N")) { + qDebug() << "Rolling old backup versions for rule" << rule.name << "..."; + for(int n = rule.maxBackupVersions - 1; n > 0; n--) { + QString backupExtensionN = rule.extensionFormat; + QString backupExtensionNplusOne = rule.extensionFormat; + backupExtensionN.replace(QString("%N"), QString::number(n)); + backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1)); + + QString backupFilenameN = _filename + backupExtensionN; + QString backupFilenameNplusOne = _filename + backupExtensionNplusOne; + + QFile backupFileN(backupFilenameN); + + if (backupFileN.exists()) { + qDebug() << "rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; + int result = rename(qPrintable(backupFilenameN), qPrintable(backupFilenameNplusOne)); + if (result == 0) { + qDebug() << "DONE rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; + } else { + qDebug() << "ERROR in rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; + } + } + } + qDebug() << "Done rolling old backup versions..."; + } } void OctreePersistThread::backup() { if (_wantBackup) { quint64 now = usecTimestampNow(); - quint64 sinceLastBackup = now - _lastBackup; - quint64 MSECS_TO_USECS = 1000; - quint64 intervalToBackup = _backupInterval * MSECS_TO_USECS; - if (sinceLastBackup > intervalToBackup) { - qDebug() << "Time since last backup [" << sinceLastBackup << "] exceeds backup interval [" - << intervalToBackup << "] doing backup now..."; + for(int i = 0; i < _backupRules.count(); i++) { + BackupRule& rule = _backupRules[i]; - struct tm* localTime = localtime(&_lastPersistTime); + quint64 sinceLastBackup = now - rule.lastBackup; - QString backupFileName; + quint64 SECS_TO_USECS = 1000 * 1000; + quint64 intervalToBackup = rule.interval * SECS_TO_USECS; + + if (sinceLastBackup > intervalToBackup) { + qDebug() << "Time since last backup [" << sinceLastBackup << "] for rule [" << rule.name + << "] exceeds backup interval [" << intervalToBackup << "] doing backup now..."; + + struct tm* localTime = localtime(&_lastPersistTime); + + QString backupFileName; - // check to see if they asked for version rolling format - if (_backupExtensionFormat.contains("%N")) { - rollOldBackupVersions(); // rename all the old backup files accordingly - QString backupExtension = _backupExtensionFormat; - backupExtension.replace(QString("%N"), QString("1")); - backupFileName = _filename + backupExtension; - } else { - char backupExtension[256]; - strftime(backupExtension, sizeof(backupExtension), qPrintable(_backupExtensionFormat), localTime); - backupFileName = _filename + backupExtension; - } + // check to see if they asked for version rolling format + if (rule.extensionFormat.contains("%N")) { + rollOldBackupVersions(rule); // rename all the old backup files accordingly + QString backupExtension = rule.extensionFormat; + backupExtension.replace(QString("%N"), QString("1")); + backupFileName = _filename + backupExtension; + } else { + char backupExtension[256]; + strftime(backupExtension, sizeof(backupExtension), qPrintable(rule.extensionFormat), localTime); + backupFileName = _filename + backupExtension; + } - qDebug() << "backing up persist file " << _filename << "to" << backupFileName << "..."; - int result = rename(qPrintable(_filename), qPrintable(backupFileName)); - if (result == 0) { - qDebug() << "DONE backing up persist file..."; - } else { - qDebug() << "ERROR in backing up persist file..."; - } + qDebug() << "backing up persist file " << _filename << "to" << backupFileName << "..."; + bool result = QFile::copy(_filename, backupFileName); + if (result) { + qDebug() << "DONE backing up persist file..."; + } else { + qDebug() << "ERROR in backing up persist file..."; + } - _lastBackup = now; + rule.lastBackup = now; + } } } } diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index b2093b89c0..374de79f0a 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -22,15 +22,19 @@ class OctreePersistThread : public GenericThread { Q_OBJECT public: + class BackupRule { + public: + QString name; + int interval; + QString extensionFormat; + int maxBackupVersions; + quint64 lastBackup; + }; + static const int DEFAULT_PERSIST_INTERVAL; - static const int DEFAULT_BACKUP_INTERVAL; - static const QString DEFAULT_BACKUP_EXTENSION_FORMAT; - static const int DEFAULT_MAX_BACKUP_VERSIONS; OctreePersistThread(Octree* tree, const QString& filename, int persistInterval = DEFAULT_PERSIST_INTERVAL, - bool wantBackup = false, int backupInterval = DEFAULT_BACKUP_INTERVAL, - const QString& backupExtensionFormat = DEFAULT_BACKUP_EXTENSION_FORMAT, - int maxBackupVersions = DEFAULT_MAX_BACKUP_VERSIONS, + bool wantBackup = false, const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false); bool isInitialLoadComplete() const { return _initialLoadComplete; } @@ -47,21 +51,24 @@ protected: void persist(); void backup(); - void rollOldBackupVersions(); + void rollOldBackupVersions(const BackupRule& rule); + void restoreFromMostRecentBackup(); + bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); + quint64 getMostRecentBackupTimeInUsecs(const QString& format); + void parseSettings(const QJsonObject& settings); + private: Octree* _tree; QString _filename; - QString _backupExtensionFormat; - int _maxBackupVersions; int _persistInterval; - int _backupInterval; bool _initialLoadComplete; quint64 _loadTimeUSecs; - quint64 _lastCheck; - quint64 _lastBackup; - bool _wantBackup; + time_t _lastPersistTime; + quint64 _lastCheck; + bool _wantBackup; + QVector _backupRules; bool _debugTimestampNow; quint64 _lastTimeDebug; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 7aa43f944c..a15f8a9f51 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -79,28 +79,70 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { } #endif // USE_BULLET_PHYSICS -void EntityMotionState::applyVelocities() const { +void EntityMotionState::updateObjectEasy(uint32_t flags, uint32_t frame) { +#ifdef USE_BULLET_PHYSICS + if (flags & (EntityItem::DIRTY_POSITION | EntityItem::DIRTY_VELOCITY)) { + if (flags & EntityItem::DIRTY_POSITION) { + _sentPosition = _entity->getPositionInMeters() - ObjectMotionState::getWorldOffset(); + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_sentPosition)); + + _sentRotation = _entity->getRotation(); + worldTrans.setRotation(glmToBullet(_sentRotation)); + + _body->setWorldTransform(worldTrans); + } + if (flags & EntityItem::DIRTY_VELOCITY) { + updateObjectVelocities(); + } + _sentFrame = frame; + } + + // TODO: entity support for friction and restitution + //_restitution = _entity->getRestitution(); + _body->setRestitution(_restitution); + //_friction = _entity->getFriction(); + _body->setFriction(_friction); + + _linearDamping = _entity->getDamping(); + _angularDamping = _entity->getAngularDamping(); + _body->setDamping(_linearDamping, _angularDamping); + + if (flags & EntityItem::DIRTY_MASS) { + float mass = _entity->computeMass(); + btVector3 inertia(0.0f, 0.0f, 0.0f); + _body->getCollisionShape()->calculateLocalInertia(mass, inertia); + _body->setMassProps(mass, inertia); + _body->updateInertiaTensor(); + } + _body->activate(); +#endif // USE_BULLET_PHYSICS +}; + +void EntityMotionState::updateObjectVelocities() { #ifdef USE_BULLET_PHYSICS if (_body) { - setVelocity(_entity->getVelocityInMeters()); + _sentVelocity = _entity->getVelocityInMeters(); + setVelocity(_sentVelocity); + // DANGER! EntityItem stores angularVelocity in degrees/sec!!! - setAngularVelocity(glm::radians(_entity->getAngularVelocity())); + _sentAngularVelocity = glm::radians(_entity->getAngularVelocity()); + setAngularVelocity(_sentAngularVelocity); + + _sentAcceleration = _entity->getGravityInMeters(); + setGravity(_sentAcceleration); + _body->setActivationState(ACTIVE_TAG); } #endif // USE_BULLET_PHYSICS } -void EntityMotionState::applyGravity() const { -#ifdef USE_BULLET_PHYSICS - if (_body) { - setGravity(_entity->getGravityInMeters()); - _body->setActivationState(ACTIVE_TAG); - } -#endif // USE_BULLET_PHYSICS +void EntityMotionState::computeShapeInfo(ShapeInfo& shapeInfo) { + _entity->computeShapeInfo(shapeInfo); } -void EntityMotionState::computeShapeInfo(ShapeInfo& info) { - _entity->computeShapeInfo(info); +float EntityMotionState::computeMass(const ShapeInfo& shapeInfo) const { + return _entity->computeMass(); } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) { @@ -123,7 +165,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _sentAngularVelocity = bulletToGLM(_body->getAngularVelocity()); // if the speeds are very small we zero them out - const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 4.0e-6f; // 2mm/sec + const float MINIMUM_EXTRAPOLATION_SPEED_SQUARED = 1.0e-4f; // 1cm/sec bool zeroSpeed = (glm::length2(_sentVelocity) < MINIMUM_EXTRAPOLATION_SPEED_SQUARED); if (zeroSpeed) { _sentVelocity = glm::vec3(0.0f); diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 379470087f..57f3b52672 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -54,10 +54,11 @@ public: #endif // USE_BULLET_PHYSICS // these relay incoming values to the RigidBody - void applyVelocities() const; - void applyGravity() const; + void updateObjectEasy(uint32_t flags, uint32_t frame); + void updateObjectVelocities(); - void computeShapeInfo(ShapeInfo& info); + void computeShapeInfo(ShapeInfo& shapeInfo); + float computeMass(const ShapeInfo& shapeInfo) const; void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame); diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 46c67c0ec2..9b3e69eb8d 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -44,13 +44,10 @@ const glm::vec3& ObjectMotionState::getWorldOffset() { ObjectMotionState::ObjectMotionState() : - _density(DEFAULT_DENSITY), - _volume(DEFAULT_VOLUME), _friction(DEFAULT_FRICTION), _restitution(DEFAULT_RESTITUTION), _linearDamping(0.0f), _angularDamping(0.0f), - _wasInWorld(false), _motionType(MOTION_TYPE_STATIC), _body(NULL), _sentMoving(false), @@ -69,10 +66,6 @@ ObjectMotionState::~ObjectMotionState() { assert(_body == NULL); } -void ObjectMotionState::setDensity(float density) { - _density = btMax(btMin(fabsf(density), MAX_DENSITY), MIN_DENSITY); -} - void ObjectMotionState::setFriction(float friction) { _friction = btMax(btMin(fabsf(friction), MAX_FRICTION), 0.0f); } @@ -89,10 +82,6 @@ void ObjectMotionState::setAngularDamping(float damping) { _angularDamping = btMax(btMin(fabsf(damping), 1.0f), 0.0f); } -void ObjectMotionState::setVolume(float volume) { - _volume = btMax(btMin(fabsf(volume), MAX_VOLUME), MIN_VOLUME); -} - void ObjectMotionState::setVelocity(const glm::vec3& velocity) const { _body->setLinearVelocity(glmToBullet(velocity)); } @@ -123,9 +112,9 @@ bool ObjectMotionState::doesNotNeedToSendUpdate() const { const float FIXED_SUBSTEP = 1.0f / 60.0f; -bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame, float subStepRemainder) { +bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame) { assert(_body); - float dt = (float)(simulationFrame - _sentFrame) * FIXED_SUBSTEP + subStepRemainder; + float dt = (float)(simulationFrame - _sentFrame) * FIXED_SUBSTEP; _sentFrame = simulationFrame; bool isActive = _body->isActive(); @@ -183,7 +172,7 @@ bool ObjectMotionState::shouldSendUpdate(uint32_t simulationFrame, float subStep } const float MIN_ROTATION_DOT = 0.98f; glm::quat actualRotation = bulletToGLM(worldTrans.getRotation()); - return (glm::dot(actualRotation, _sentRotation) < MIN_ROTATION_DOT); + return (fabsf(glm::dot(actualRotation, _sentRotation)) < MIN_ROTATION_DOT); } #endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index cb19babc1d..ea3d5de73b 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -56,21 +56,19 @@ public: ObjectMotionState(); ~ObjectMotionState(); - virtual void applyVelocities() const = 0; - virtual void applyGravity() const = 0; - - virtual void computeShapeInfo(ShapeInfo& info) = 0; + // An EASY update does not require the object to be removed and then reinserted into the PhysicsEngine + virtual void updateObjectEasy(uint32_t flags, uint32_t frame) = 0; + virtual void updateObjectVelocities() = 0; virtual MotionType getMotionType() const { return _motionType; } - void setDensity(float density); + virtual void computeShapeInfo(ShapeInfo& info) = 0; + virtual float computeMass(const ShapeInfo& shapeInfo) const = 0; + void setFriction(float friction); void setRestitution(float restitution); void setLinearDamping(float damping); void setAngularDamping(float damping); - void setVolume(float volume); - - float getMass() const { return _volume * _density; } void setVelocity(const glm::vec3& velocity) const; void setAngularVelocity(const glm::vec3& velocity) const; @@ -84,20 +82,19 @@ public: void clearOutgoingPacketFlags(uint32_t flags) { _outgoingPacketFlags &= ~flags; } bool doesNotNeedToSendUpdate() const; - virtual bool shouldSendUpdate(uint32_t simulationFrame, float subStepRemainder); + virtual bool shouldSendUpdate(uint32_t simulationFrame); virtual void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t frame) = 0; virtual MotionType computeMotionType() const = 0; friend class PhysicsEngine; protected: - float _density; - float _volume; + // TODO: move these materials properties to EntityItem float _friction; float _restitution; float _linearDamping; float _angularDamping; - bool _wasInWorld; + MotionType _motionType; // _body has NO setters -- it is only changed by PhysicsEngine diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 4129f1c7b0..e08f271255 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -41,14 +41,12 @@ void PhysicsEngine::updateEntitiesInternal(const quint64& now) { // this is step (4) QSet::iterator stateItr = _outgoingPackets.begin(); - uint32_t frame = getFrameCount(); - float subStepRemainder = getSubStepRemainder(); while (stateItr != _outgoingPackets.end()) { ObjectMotionState* state = *stateItr; if (state->doesNotNeedToSendUpdate()) { stateItr = _outgoingPackets.erase(stateItr); - } else if (state->shouldSendUpdate(frame, subStepRemainder)) { - state->sendUpdate(_entityPacketSender, frame); + } else if (state->shouldSendUpdate(_frameCount)) { + state->sendUpdate(_entityPacketSender, _frameCount); ++stateItr; } else { ++stateItr; @@ -140,7 +138,8 @@ void PhysicsEngine::relayIncomingChangesToSimulation() { updateObjectHard(body, motionState, flags); } else if (flags) { // an EASY update does NOT require that the body be pulled out of physics engine - updateObjectEasy(body, motionState, flags); + // hence the MotionState has all the knowledge and authority to perform the update. + motionState->updateObjectEasy(flags, _frameCount); } } @@ -247,9 +246,9 @@ void PhysicsEngine::stepSimulation() { bool PhysicsEngine::addObject(ObjectMotionState* motionState) { assert(motionState); - ShapeInfo info; - motionState->computeShapeInfo(info); - btCollisionShape* shape = _shapeManager.getShape(info); + ShapeInfo shapeInfo; + motionState->computeShapeInfo(shapeInfo); + btCollisionShape* shape = _shapeManager.getShape(shapeInfo); if (shape) { btVector3 inertia(0.0f, 0.0f, 0.0f); float mass = 0.0f; @@ -264,13 +263,17 @@ bool PhysicsEngine::addObject(ObjectMotionState* motionState) { break; } case MOTION_TYPE_DYNAMIC: { - mass = motionState->getMass(); + mass = motionState->computeMass(shapeInfo); shape->calculateLocalInertia(mass, inertia); body = new btRigidBody(mass, motionState, shape, inertia); body->updateInertiaTensor(); motionState->_body = body; - motionState->applyVelocities(); - motionState->applyGravity(); + motionState->updateObjectVelocities(); + // NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds. + // (the 2 seconds is determined by: static btRigidBody::gDeactivationTime + const float LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec + const float ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec + body->setSleepingThresholds(LINEAR_VELOCITY_THRESHOLD, ANGULAR_VELOCITY_THRESHOLD); break; } case MOTION_TYPE_STATIC: @@ -298,10 +301,10 @@ bool PhysicsEngine::removeObject(ObjectMotionState* motionState) { btRigidBody* body = motionState->_body; if (body) { const btCollisionShape* shape = body->getCollisionShape(); - ShapeInfo info; - ShapeInfoUtil::collectInfoFromShape(shape, info); + ShapeInfo shapeInfo; + ShapeInfoUtil::collectInfoFromShape(shape, shapeInfo); _dynamicsWorld->removeRigidBody(body); - _shapeManager.releaseShape(info); + _shapeManager.releaseShape(shapeInfo); delete body; motionState->_body = NULL; return true; @@ -317,24 +320,35 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio _dynamicsWorld->removeRigidBody(body); if (flags & EntityItem::DIRTY_SHAPE) { + // MASS bit should be set whenever SHAPE is set + assert(flags & EntityItem::DIRTY_MASS); + + // get new shape btCollisionShape* oldShape = body->getCollisionShape(); - ShapeInfo info; - motionState->computeShapeInfo(info); - btCollisionShape* newShape = _shapeManager.getShape(info); + ShapeInfo shapeInfo; + motionState->computeShapeInfo(shapeInfo); + btCollisionShape* newShape = _shapeManager.getShape(shapeInfo); if (newShape != oldShape) { + // BUG: if shape doesn't change but density does then we won't compute new mass properties + // TODO: fix this BUG by replacing DIRTY_MASS with DIRTY_DENSITY and then fix logic accordingly. body->setCollisionShape(newShape); _shapeManager.releaseShape(oldShape); + + // compute mass properties + float mass = motionState->computeMass(shapeInfo); + btVector3 inertia(0.0f, 0.0f, 0.0f); + body->getCollisionShape()->calculateLocalInertia(mass, inertia); + body->setMassProps(mass, inertia); + body->updateInertiaTensor(); } else { // whoops, shape hasn't changed after all so we must release the reference // that was created when looking it up _shapeManager.releaseShape(newShape); } - // MASS bit should be set whenever SHAPE is set - assert(flags & EntityItem::DIRTY_MASS); } bool easyUpdate = flags & EASY_DIRTY_PHYSICS_FLAGS; if (easyUpdate) { - updateObjectEasy(body, motionState, flags); + motionState->updateObjectEasy(flags, _frameCount); } // update the motion parameters @@ -353,9 +367,11 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio int collisionFlags = body->getCollisionFlags() & ~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT); body->setCollisionFlags(collisionFlags); if (! (flags & EntityItem::DIRTY_MASS)) { - // always update mass properties when going dynamic (unless it's already been done) + // always update mass properties when going dynamic (unless it's already been done above) + ShapeInfo shapeInfo; + motionState->computeShapeInfo(shapeInfo); + float mass = motionState->computeMass(shapeInfo); btVector3 inertia(0.0f, 0.0f, 0.0f); - float mass = motionState->getMass(); body->getCollisionShape()->calculateLocalInertia(mass, inertia); body->setMassProps(mass, inertia); body->updateInertiaTensor(); @@ -385,31 +401,4 @@ void PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio body->activate(); } -// private -void PhysicsEngine::updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags) { - if (flags & EntityItem::DIRTY_POSITION) { - btTransform transform; - motionState->getWorldTransform(transform); - body->setWorldTransform(transform); - } - if (flags & EntityItem::DIRTY_VELOCITY) { - motionState->applyVelocities(); - motionState->applyGravity(); - } - body->setRestitution(motionState->_restitution); - body->setFriction(motionState->_friction); - body->setDamping(motionState->_linearDamping, motionState->_angularDamping); - - if (flags & EntityItem::DIRTY_MASS) { - float mass = motionState->getMass(); - btVector3 inertia(0.0f, 0.0f, 0.0f); - body->getCollisionShape()->calculateLocalInertia(mass, inertia); - body->setMassProps(mass, inertia); - body->updateInertiaTensor(); - } - body->activate(); - - // TODO: support collision groups -}; - #endif // USE_BULLET_PHYSICS diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index e93495e1d2..c6d6bd4626 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -71,12 +71,6 @@ public: /// \return number of simulation frames the physics engine has taken uint32_t getFrameCount() const { return _frameCount; } - /// \return substep remainder used for Bullet MotionState extrapolation - // Bullet will extrapolate the positions provided to MotionState::setWorldTransform() in an effort to provide - // smoother visible motion when the render frame rate does not match that of the simulation loop. We provide - // access to this fraction for improved filtering of update packets to interested parties. - float getSubStepRemainder() { return _dynamicsWorld->getLocalTimeAccumulation(); } - protected: void updateObjectHard(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); void updateObjectEasy(btRigidBody* body, ObjectMotionState* motionState, uint32_t flags); diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index f84bd97c13..cf3d7a6ad7 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -9,5 +9,10 @@ include_glm() link_hifi_libraries(animation fbx shared gpu) +if (WIN32) + # we're using static GLEW, so define GLEW_STATIC + add_definitions(-DGLEW_STATIC) +endif () + # call macro to include our dependency includes and bubble them up via a property on our target include_dependency_includes() \ No newline at end of file diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index c5757f4baa..c7acb90133 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -19,8 +19,8 @@ class ProgramObject; /// A screen space ambient occlusion effect. See John Chapman's tutorial at /// http://john-chapman-graphics.blogspot.co.uk/2013/01/ssao-tutorial.html for reference. -class AmbientOcclusionEffect { - SINGLETON_DEPENDENCY(AmbientOcclusionEffect) +class AmbientOcclusionEffect : public Dependency { + SINGLETON_DEPENDENCY public: diff --git a/libraries/render-utils/src/DeferredBuffer.slh b/libraries/render-utils/src/DeferredBuffer.slh new file mode 100755 index 0000000000..885fa96543 --- /dev/null +++ b/libraries/render-utils/src/DeferredBuffer.slh @@ -0,0 +1,73 @@ + +<@if not DEFERRED_BUFFER_SLH@> +<@def DEFERRED_BUFFER_SLH@> + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the normal texture +uniform sampler2D normalMap; + +// the specular texture +uniform sampler2D specularMap; + +// the depth texture +uniform sampler2D depthMap; + +// the distance to the near clip plane +uniform float near; + +// scale factor for depth: (far - near) / far +uniform float depthScale; + +// offset for depth texture coordinates +uniform vec2 depthTexCoordOffset; + +// scale for depth texture coordinates +uniform vec2 depthTexCoordScale; + +struct DeferredFragment { + float depthVal; + vec4 normalVal; + vec4 diffuseVal; + vec4 specularVal; + vec4 position; + vec3 normal; + vec3 diffuse; + float opacity; + vec3 specular; + float gloss; +}; + +DeferredFragment unpackDeferredFragment(vec2 texcoord) { + DeferredFragment frag; + frag.depthVal = texture2D(depthMap, texcoord).r; + frag.normalVal = texture2D(normalMap, texcoord); + frag.diffuseVal = texture2D(diffuseMap, texcoord); + frag.specularVal = texture2D(specularMap, texcoord); + + // compute the view space position using the depth + float z = near / (frag.depthVal * depthScale - 1.0); + frag.position = vec4((depthTexCoordOffset + texcoord * depthTexCoordScale) * z, z, 1.0); + + // Unpack the normal from the map + frag.normal = normalize(frag.normalVal.xyz * 2.0 - vec3(1.0)); + + frag.diffuse = frag.diffuseVal.xyz; + frag.opacity = frag.diffuseVal.w; + frag.specular = frag.specularVal.xyz; + frag.gloss = frag.specularVal.w; + + return frag; +} + +<@endif@> diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh new file mode 100755 index 0000000000..066e0198b1 --- /dev/null +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -0,0 +1,43 @@ + +<@if not DEFERRED_BUFFER_WRITE_SLH@> +<@def DEFERRED_BUFFER_WRITE_SLH@> + +// the glow intensity +uniform float glowIntensity; + +// the alpha threshold +uniform float alphaThreshold; + +float evalOpaqueFinalAlpha(float alpha, float mapAlpha) { + return mix(alpha * glowIntensity, 1.0 - alpha * glowIntensity, step(mapAlpha, alphaThreshold)); +} + +void packDeferredFragment(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { + gl_FragData[0] = vec4(diffuse.rgb, alpha); + gl_FragData[1] = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); + gl_FragData[2] = vec4(specular, shininess / 128.0); +} + +void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess, vec3 emissive) { + gl_FragData[0] = vec4(diffuse.rgb, alpha); + //gl_FragData[1] = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); + gl_FragData[1] = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5); + gl_FragData[2] = vec4(emissive, shininess / 128.0); +} + +void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 diffuse, vec3 specular, float shininess) { + gl_FragData[0] = vec4(diffuse.rgb, alpha); + // gl_FragData[1] = vec4(normal, 0.0) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); + // gl_FragData[2] = vec4(specular, shininess / 128.0); +} + +<@endif@> diff --git a/libraries/render-utils/src/DeferredLighting.slh b/libraries/render-utils/src/DeferredLighting.slh new file mode 100755 index 0000000000..5582ab8e77 --- /dev/null +++ b/libraries/render-utils/src/DeferredLighting.slh @@ -0,0 +1,96 @@ + +<@if not DEFERRED_LIGHTING_SLH@> +<@def DEFERRED_LIGHTING_SLH@> + +struct SphericalHarmonics { + vec4 L00; + vec4 L1m1; + vec4 L10; + vec4 L11; + vec4 L2m2; + vec4 L2m1; + vec4 L20; + vec4 L21; + vec4 L22; +}; + +vec4 evalSphericalLight(SphericalHarmonics sh, vec3 direction ) { + + const float C1 = 0.429043; + const float C2 = 0.511664; + const float C3 = 0.743125; + const float C4 = 0.886227; + const float C5 = 0.247708; + + vec4 value = C1 * sh.L22 * (direction.x * direction.x - direction.y * direction.y) + + C3 * sh.L20 * direction.z * direction.z + + C4 * sh.L00 - C5 * sh.L20 + + 2.0 * C1 * ( sh.L2m2 * direction.x * direction.y + + sh.L21 * direction.x * direction.z + + sh.L2m1 * direction.y * direction.z ) + + 2.0 * C2 * ( sh.L11 * direction.x + + sh.L1m1 * direction.y + + sh.L10 * direction.z ) ; + return value; +} + +uniform SphericalHarmonics ambientSphere; + +vec3 evalAmbientColor(vec3 normal, vec3 diffuse, vec3 specular, float gloss) { + return diffuse.rgb * gl_FrontLightProduct[0].ambient.rgb; +} + +vec3 evalAmbientSphereColor(vec3 normal, vec3 diffuse, vec3 specular, float gloss) { + vec3 ambientLight = 0.5 * evalSphericalLight(ambientSphere, normal).xyz; + + return diffuse.rgb * ambientLight; +} + +vec3 evalDirectionalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 diffuse, vec3 specular, float gloss) { + // Diffuse Lighting + float diffuseDot = dot(normal, gl_LightSource[0].position.xyz); + float facingLight = step(0.0, diffuseDot) * shadowAttenuation; + vec3 diffuseColor = diffuse * (gl_FrontLightModelProduct.sceneColor.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuseDot * facingLight)); + + // compute the specular multiplier (sans exponent) + float specularPower = facingLight * max(0.0, + dot(normalize(gl_LightSource[0].position.xyz - normalize(position)), normal)); + vec3 specularColor = pow(specularPower, gloss * 128.0) * specular; + + // add specular contribution + return vec3(diffuseColor + specularColor); +} + + +vec3 evalLightmappedColor(float shadowAttenuation, vec3 normal, vec3 diffuse, vec3 lightmap) { + + float diffuseDot = dot(normal, gl_LightSource[0].position.xyz); + + // need to catch normals perpendicular to the projection plane hence the magic number for the threshold + // it should be just 0, but we have innacurracy so we need to overshoot + const float PERPENDICULAR_THRESHOLD = -0.005; + float facingLight = step(PERPENDICULAR_THRESHOLD, diffuseDot); + + // evaluate the shadow test but only relevant for light facing fragments + float lightAttenuation = (1 - facingLight) + facingLight * shadowAttenuation; + + // diffuse light is the lightmap dimmed by shadow + vec3 diffuseLight = lightAttenuation * lightmap; + + // ambient is a tiny percentage of the lightmap and only when in the shadow + vec3 ambientLight = (1 - lightAttenuation) * 0.5 * lightmap; + + return diffuse * (ambientLight + diffuseLight); +} + + +<@endif@> diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index fc84489a2f..5cbdd04f64 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -35,9 +35,152 @@ #include "directional_light_shadow_map_frag.h" #include "directional_light_cascaded_shadow_map_frag.h" +#include "directional_ambient_light_frag.h" +#include "directional_ambient_light_shadow_map_frag.h" +#include "directional_ambient_light_cascaded_shadow_map_frag.h" + #include "point_light_frag.h" #include "spot_light_frag.h" +class SphericalHarmonics { +public: + glm::vec3 L00 ; float spare0; + glm::vec3 L1m1 ; float spare1; + glm::vec3 L10 ; float spare2; + glm::vec3 L11 ; float spare3; + glm::vec3 L2m2 ; float spare4; + glm::vec3 L2m1 ; float spare5; + glm::vec3 L20 ; float spare6; + glm::vec3 L21 ; float spare7; + glm::vec3 L22 ; float spare8; + + static const int NUM_COEFFICIENTS = 9; + + void assignPreset(int p) { + switch (p) { + case DeferredLightingEffect::OLD_TOWN_SQUARE: { + L00 = glm::vec3( 0.871297f, 0.875222f, 0.864470f); + L1m1 = glm::vec3( 0.175058f, 0.245335f, 0.312891f); + L10 = glm::vec3( 0.034675f, 0.036107f, 0.037362f); + L11 = glm::vec3(-0.004629f,-0.029448f,-0.048028f); + L2m2 = glm::vec3(-0.120535f,-0.121160f,-0.117507f); + L2m1 = glm::vec3( 0.003242f, 0.003624f, 0.007511f); + L20 = glm::vec3(-0.028667f,-0.024926f,-0.020998f); + L21 = glm::vec3(-0.077539f,-0.086325f,-0.091591f); + L22 = glm::vec3(-0.161784f,-0.191783f,-0.219152f); + } + break; + case DeferredLightingEffect::GRACE_CATHEDRAL: { + L00 = glm::vec3( 0.79f, 0.44f, 0.54f); + L1m1 = glm::vec3( 0.39f, 0.35f, 0.60f); + L10 = glm::vec3(-0.34f, -0.18f, -0.27f); + L11 = glm::vec3(-0.29f, -0.06f, 0.01f); + L2m2 = glm::vec3(-0.11f, -0.05f, -0.12f); + L2m1 = glm::vec3(-0.26f, -0.22f, -0.47f); + L20 = glm::vec3(-0.16f, -0.09f, -0.15f); + L21 = glm::vec3( 0.56f, 0.21f, 0.14f); + L22 = glm::vec3( 0.21f, -0.05f, -0.30f); + } + break; + case DeferredLightingEffect::EUCALYPTUS_GROVE: { + L00 = glm::vec3( 0.38f, 0.43f, 0.45f); + L1m1 = glm::vec3( 0.29f, 0.36f, 0.41f); + L10 = glm::vec3( 0.04f, 0.03f, 0.01f); + L11 = glm::vec3(-0.10f, -0.10f, -0.09f); + L2m2 = glm::vec3(-0.06f, -0.06f, -0.04f); + L2m1 = glm::vec3( 0.01f, -0.01f, -0.05f); + L20 = glm::vec3(-0.09f, -0.13f, -0.15f); + L21 = glm::vec3(-0.06f, -0.05f, -0.04f); + L22 = glm::vec3( 0.02f, 0.00f, -0.05f); + } + break; + case DeferredLightingEffect::ST_PETERS_BASILICA: { + L00 = glm::vec3( 0.36f, 0.26f, 0.23f); + L1m1 = glm::vec3( 0.18f, 0.14f, 0.13f); + L10 = glm::vec3(-0.02f, -0.01f, 0.00f); + L11 = glm::vec3( 0.03f, 0.02f, -0.00f); + L2m2 = glm::vec3( 0.02f, 0.01f, -0.00f); + L2m1 = glm::vec3(-0.05f, -0.03f, -0.01f); + L20 = glm::vec3(-0.09f, -0.08f, -0.07f); + L21 = glm::vec3( 0.01f, 0.00f, 0.00f); + L22 = glm::vec3(-0.08f, -0.03f, -0.00f); + } + break; + case DeferredLightingEffect::UFFIZI_GALLERY: { + L00 = glm::vec3( 0.32f, 0.31f, 0.35f); + L1m1 = glm::vec3( 0.37f, 0.37f, 0.43f); + L10 = glm::vec3( 0.00f, 0.00f, 0.00f); + L11 = glm::vec3(-0.01f, -0.01f, -0.01f); + L2m2 = glm::vec3(-0.02f, -0.02f, -0.03f); + L2m1 = glm::vec3(-0.01f, -0.01f, -0.01f); + L20 = glm::vec3(-0.28f, -0.28f, -0.32f); + L21 = glm::vec3( 0.00f, 0.00f, 0.00f); + L22 = glm::vec3(-0.24f, -0.24f, -0.28f); + } + break; + case DeferredLightingEffect::GALILEOS_TOMB: { + L00 = glm::vec3( 1.04f, 0.76f, 0.71f); + L1m1 = glm::vec3( 0.44f, 0.34f, 0.34f); + L10 = glm::vec3(-0.22f, -0.18f, -0.17f); + L11 = glm::vec3( 0.71f, 0.54f, 0.56f); + L2m2 = glm::vec3( 0.64f, 0.50f, 0.52f); + L2m1 = glm::vec3(-0.12f, -0.09f, -0.08f); + L20 = glm::vec3(-0.37f, -0.28f, -0.32f); + L21 = glm::vec3(-0.17f, -0.13f, -0.13f); + L22 = glm::vec3( 0.55f, 0.42f, 0.42f); + } + break; + case DeferredLightingEffect::VINE_STREET_KITCHEN: { + L00 = glm::vec3( 0.64f, 0.67f, 0.73f); + L1m1 = glm::vec3( 0.28f, 0.32f, 0.33f); + L10 = glm::vec3( 0.42f, 0.60f, 0.77f); + L11 = glm::vec3(-0.05f, -0.04f, -0.02f); + L2m2 = glm::vec3(-0.10f, -0.08f, -0.05f); + L2m1 = glm::vec3( 0.25f, 0.39f, 0.53f); + L20 = glm::vec3( 0.38f, 0.54f, 0.71f); + L21 = glm::vec3( 0.06f, 0.01f, -0.02f); + L22 = glm::vec3(-0.03f, -0.02f, -0.03f); + } + break; + case DeferredLightingEffect::BREEZEWAY: { + L00 = glm::vec3( 0.32f, 0.36f, 0.38f); + L1m1 = glm::vec3( 0.37f, 0.41f, 0.45f); + L10 = glm::vec3(-0.01f, -0.01f, -0.01f); + L11 = glm::vec3(-0.10f, -0.12f, -0.12f); + L2m2 = glm::vec3(-0.13f, -0.15f, -0.17f); + L2m1 = glm::vec3(-0.01f, -0.02f, 0.02f); + L20 = glm::vec3(-0.07f, -0.08f, -0.09f); + L21 = glm::vec3( 0.02f, 0.03f, 0.03f); + L22 = glm::vec3(-0.29f, -0.32f, -0.36f); + } + break; + case DeferredLightingEffect::CAMPUS_SUNSET: { + L00 = glm::vec3( 0.79f, 0.94f, 0.98f); + L1m1 = glm::vec3( 0.44f, 0.56f, 0.70f); + L10 = glm::vec3(-0.10f, -0.18f, -0.27f); + L11 = glm::vec3( 0.45f, 0.38f, 0.20f); + L2m2 = glm::vec3( 0.18f, 0.14f, 0.05f); + L2m1 = glm::vec3(-0.14f, -0.22f, -0.31f); + L20 = glm::vec3(-0.39f, -0.40f, -0.36f); + L21 = glm::vec3( 0.09f, 0.07f, 0.04f); + L22 = glm::vec3( 0.67f, 0.67f, 0.52f); + } + break; + case DeferredLightingEffect::FUNSTON_BEACH_SUNSET: { + L00 = glm::vec3( 0.68f, 0.69f, 0.70f); + L1m1 = glm::vec3( 0.32f, 0.37f, 0.44f); + L10 = glm::vec3(-0.17f, -0.17f, -0.17f); + L11 = glm::vec3(-0.45f, -0.42f, -0.34f); + L2m2 = glm::vec3(-0.17f, -0.17f, -0.15f); + L2m1 = glm::vec3(-0.08f, -0.09f, -0.10f); + L20 = glm::vec3(-0.03f, -0.02f, -0.01f); + L21 = glm::vec3( 0.16f, 0.14f, 0.10f); + L22 = glm::vec3( 0.37f, 0.31f, 0.20f); + } + break; + } + } +}; void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { _viewState = viewState; @@ -54,6 +197,13 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { _directionalLightShadowMapLocations); loadLightProgram(directional_light_cascaded_shadow_map_frag, false, _directionalLightCascadedShadowMap, _directionalLightCascadedShadowMapLocations); + + loadLightProgram(directional_ambient_light_frag, false, _directionalAmbientSphereLight, _directionalAmbientSphereLightLocations); + loadLightProgram(directional_ambient_light_shadow_map_frag, false, _directionalAmbientSphereLightShadowMap, + _directionalAmbientSphereLightShadowMapLocations); + loadLightProgram(directional_ambient_light_cascaded_shadow_map_frag, false, _directionalAmbientSphereLightCascadedShadowMap, + _directionalAmbientSphereLightCascadedShadowMapLocations); + loadLightProgram(point_light_frag, true, _pointLight, _pointLightLocations); loadLightProgram(spot_light_frag, true, _spotLight, _spotLightLocations); } @@ -139,7 +289,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu void DeferredLightingEffect::prepare() { // clear the normal and specular buffers - TextureCache::SharedPointer textureCache = DependencyManager::get(); + auto textureCache = DependencyManager::get(); textureCache->setPrimaryDrawBuffers(false, true, false); glClear(GL_COLOR_BUFFER_BIT); textureCache->setPrimaryDrawBuffers(false, false, true); @@ -161,7 +311,7 @@ void DeferredLightingEffect::render() { glDisable(GL_COLOR_MATERIAL); glDepthMask(false); - TextureCache::SharedPointer textureCache = DependencyManager::get(); + auto textureCache = DependencyManager::get(); QOpenGLFramebufferObject* primaryFBO = textureCache->getPrimaryFramebufferObject(); primaryFBO->release(); @@ -169,7 +319,8 @@ void DeferredLightingEffect::render() { QOpenGLFramebufferObject* freeFBO = DependencyManager::get()->getFreeFramebufferObject(); freeFBO->bind(); glClear(GL_COLOR_BUFFER_BIT); - + glEnable(GL_FRAMEBUFFER_SRGB); + glBindTexture(GL_TEXTURE_2D, primaryFBO->texture()); glActiveTexture(GL_TEXTURE1); @@ -205,18 +356,43 @@ void DeferredLightingEffect::render() { if (_viewState->getCascadeShadowsEnabled()) { program = &_directionalLightCascadedShadowMap; locations = &_directionalLightCascadedShadowMapLocations; - _directionalLightCascadedShadowMap.bind(); - _directionalLightCascadedShadowMap.setUniform(locations->shadowDistances, _viewState->getShadowDistances()); + if (_ambientLightMode > -1) { + program = &_directionalAmbientSphereLightCascadedShadowMap; + locations = &_directionalAmbientSphereLightCascadedShadowMapLocations; + } + program->bind(); + program->setUniform(locations->shadowDistances, _viewState->getShadowDistances()); } else { + if (_ambientLightMode > -1) { + program = &_directionalAmbientSphereLightShadowMap; + locations = &_directionalAmbientSphereLightShadowMapLocations; + } program->bind(); } program->setUniformValue(locations->shadowScale, 1.0f / textureCache->getShadowFramebufferObject()->width()); } else { + if (_ambientLightMode > -1) { + program = &_directionalAmbientSphereLight; + locations = &_directionalAmbientSphereLightLocations; + } program->bind(); } + + if (locations->ambientSphere >= 0) { + SphericalHarmonics sh; + if (_ambientLightMode < NUM_PRESET) { + sh.assignPreset(_ambientLightMode); + } else { + sh.assignPreset(0); + } + + for (int i =0; i setUniformValue(locations->ambientSphere + i, *(((QVector4D*) &sh) + i)); + } + } float left, right, bottom, top, nearVal, farVal; glm::vec4 nearClipPlane, farClipPlane; @@ -258,7 +434,7 @@ void DeferredLightingEffect::render() { const glm::vec3& eyePoint = _viewState->getCurrentViewFrustum()->getPosition(); float nearRadius = glm::distance(eyePoint, _viewState->getCurrentViewFrustum()->getNearTopLeft()); - GeometryCache::SharedPointer geometryCache = DependencyManager::get(); + auto geometryCache = DependencyManager::get(); if (!_pointLights.isEmpty()) { _pointLight.bind(); @@ -371,6 +547,7 @@ void DeferredLightingEffect::render() { glBindTexture(GL_TEXTURE_2D, 0); freeFBO->release(); + glDisable(GL_FRAMEBUFFER_SRGB); glDisable(GL_CULL_FACE); @@ -431,5 +608,12 @@ void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limit locations.depthTexCoordOffset = program.uniformLocation("depthTexCoordOffset"); locations.depthTexCoordScale = program.uniformLocation("depthTexCoordScale"); locations.radius = program.uniformLocation("radius"); + locations.ambientSphere = program.uniformLocation("ambientSphere.L00"); program.release(); } + +void DeferredLightingEffect::setAmbientLightMode(int preset) { + if ((preset >= -1) && (preset < NUM_PRESET)) { + _ambientLightMode = preset; + } +} diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index bf8bb53c92..cd8585eade 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -23,8 +23,8 @@ class AbstractViewStateInterface; class PostLightingRenderable; /// Handles deferred lighting for the bits that require it (voxels, metavoxels...) -class DeferredLightingEffect { - SINGLETON_DEPENDENCY(DeferredLightingEffect) +class DeferredLightingEffect : public Dependency { + SINGLETON_DEPENDENCY public: @@ -71,6 +71,23 @@ public: void prepare(); void render(); + enum AmbientLightPreset { + OLD_TOWN_SQUARE = 0, + GRACE_CATHEDRAL, + EUCALYPTUS_GROVE, + ST_PETERS_BASILICA, + UFFIZI_GALLERY, + GALILEOS_TOMB, + VINE_STREET_KITCHEN, + BREEZEWAY, + CAMPUS_SUNSET, + FUNSTON_BEACH_SUNSET, + + NUM_PRESET, + }; + + void setAmbientLightMode(int preset); + private: DeferredLightingEffect() { } virtual ~DeferredLightingEffect() { } @@ -84,6 +101,7 @@ private: int depthTexCoordOffset; int depthTexCoordScale; int radius; + int ambientSphere; }; static void loadLightProgram(const char* fragSource, bool limited, ProgramObject& program, LightLocations& locations); @@ -91,12 +109,20 @@ private: ProgramObject _simpleProgram; int _glowIntensityLocation; + ProgramObject _directionalAmbientSphereLight; + LightLocations _directionalAmbientSphereLightLocations; + ProgramObject _directionalAmbientSphereLightShadowMap; + LightLocations _directionalAmbientSphereLightShadowMapLocations; + ProgramObject _directionalAmbientSphereLightCascadedShadowMap; + LightLocations _directionalAmbientSphereLightCascadedShadowMapLocations; + ProgramObject _directionalLight; LightLocations _directionalLightLocations; ProgramObject _directionalLightShadowMap; LightLocations _directionalLightShadowMapLocations; ProgramObject _directionalLightCascadedShadowMap; LightLocations _directionalLightCascadedShadowMapLocations; + ProgramObject _pointLight; LightLocations _pointLightLocations; ProgramObject _spotLight; @@ -126,6 +152,8 @@ private: QVector _postLightingRenderables; AbstractViewStateInterface* _viewState; + + int _ambientLightMode = 0; }; /// Simple interface for objects that require something to be rendered after deferred lighting. diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 553e460a5c..3806599cbf 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -30,6 +30,8 @@ const int GeometryCache::UNKNOWN_ID = -1; GeometryCache::GeometryCache() : _nextID(0) { + const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; + setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE); } GeometryCache::~GeometryCache() { @@ -1762,7 +1764,7 @@ void NetworkGeometry::clearLoadPriority(const QPointer& owner) { void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& url) { if (_meshes.size() > 0) { - TextureCache::SharedPointer textureCache = DependencyManager::get(); + auto textureCache = DependencyManager::get(); for (int i = 0; i < _meshes.size(); i++) { NetworkMesh& mesh = _meshes[i]; for (int j = 0; j < mesh.parts.size(); j++) { @@ -1871,9 +1873,9 @@ void GeometryReader::run() { if (!_reply) { throw QString("Reply is NULL ?!"); } - std::string urlname = _url.path().toLower().toStdString(); + QString urlname = _url.path().toLower(); bool urlValid = true; - urlValid &= !urlname.empty(); + urlValid &= !urlname.isEmpty(); urlValid &= !_url.path().isEmpty(); urlValid &= _url.path().toLower().endsWith(".fbx") || _url.path().toLower().endsWith(".svo"); @@ -1970,7 +1972,7 @@ void NetworkGeometry::reinsert() { void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { _geometry = geometry; - TextureCache::SharedPointer textureCache = DependencyManager::get(); + auto textureCache = DependencyManager::get(); foreach (const FBXMesh& mesh, _geometry.meshes) { NetworkMesh networkMesh; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 17b23da945..dc367b1f8c 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -76,9 +76,9 @@ inline uint qHash(const Vec3PairVec2Pair& v, uint seed) { /// Stores cached geometry. -class GeometryCache : public ResourceCache { +class GeometryCache : public ResourceCache, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY(GeometryCache) + SINGLETON_DEPENDENCY public: int allocateID() { return _nextID++; } diff --git a/libraries/render-utils/src/GlowEffect.cpp b/libraries/render-utils/src/GlowEffect.cpp index fb7189f61e..22eb9e1764 100644 --- a/libraries/render-utils/src/GlowEffect.cpp +++ b/libraries/render-utils/src/GlowEffect.cpp @@ -138,7 +138,7 @@ static void maybeRelease(QOpenGLFramebufferObject* fbo) { QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) { PerformanceTimer perfTimer("glowEffect"); - TextureCache::SharedPointer textureCache = DependencyManager::get(); + auto textureCache = DependencyManager::get(); QOpenGLFramebufferObject* primaryFBO = textureCache->getPrimaryFramebufferObject(); primaryFBO->release(); glBindTexture(GL_TEXTURE_2D, primaryFBO->texture()); diff --git a/libraries/render-utils/src/GlowEffect.h b/libraries/render-utils/src/GlowEffect.h index 37f29afb62..895cd4bbce 100644 --- a/libraries/render-utils/src/GlowEffect.h +++ b/libraries/render-utils/src/GlowEffect.h @@ -25,9 +25,9 @@ class QOpenGLFramebufferObject; class ProgramObject; /// A generic full screen glow effect. -class GlowEffect : public QObject { +class GlowEffect : public QObject, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY(GlowEffect) + SINGLETON_DEPENDENCY public: diff --git a/libraries/render-utils/src/Material.slh b/libraries/render-utils/src/Material.slh new file mode 100755 index 0000000000..86c6049ec6 --- /dev/null +++ b/libraries/render-utils/src/Material.slh @@ -0,0 +1,63 @@ + +<@if not MATERIAL_SLH@> +<@def MATERIAL_SLH@> + +struct Material { + vec4 _diffuse; + vec4 _specular; + +}; + +float getMaterialOpacity(Material m) { return m._diffuse.a; } +vec3 getMaterialDiffuse(Material m) { return m._diffuse.rgb; } +vec3 getMaterialSpecular(Material m) { return m._specular.rgb; } +float getMaterialShininess(Material m) { return m._specular.a; } + + + +<@if GLPROFILE == PC_GL@> +uniform materialBuffer { + Material mat; +}; +Material getMaterial() { + return mat; +} +<@elif GLPROFILE == MAC_GL@> +uniform vec4 materialBuffer[2]; +Material getMaterial() { + Material mat; + mat._diffuse = materialBuffer[0]; + mat._specular = materialBuffer[1]; + return mat; +} + +<@else@> +uniform vec4 materialBuffer[2]; +Material getMaterial() { + Material mat; + mat._diffuse = materialBuffer[0]; + mat._specular = materialBuffer[1]; + return mat; +} +<@endif@> + + + +<@endif@> diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 46fc6a6e4d..97e6124517 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -178,7 +178,7 @@ void Model::initProgram(ProgramObject& program, Model::Locations& locations, boo locations.alphaThreshold = program.uniformLocation("alphaThreshold"); locations.texcoordMatrices = program.uniformLocation("texcoordMatrices"); locations.emissiveParams = program.uniformLocation("emissiveParams"); - + locations.glowIntensity = program.uniformLocation("glowIntensity"); program.setUniformValue("diffuseMap", 0); program.setUniformValue("normalMap", 1); @@ -198,6 +198,35 @@ void Model::initProgram(ProgramObject& program, Model::Locations& locations, boo locations.emissiveTextureUnit = -1; } + // bindable uniform version +#if defined(Q_OS_MAC) + loc = program.uniformLocation("materialBuffer"); + if (loc >= 0) { + locations.materialBufferUnit = loc; + } else { + locations.materialBufferUnit = -1; + } +#elif defined(Q_OS_WIN) + loc = glGetUniformBlockIndex(program.programId(), "materialBuffer"); + if (loc >= 0) { + glUniformBlockBinding(program.programId(), loc, 1); + locations.materialBufferUnit = 1; + } else { + locations.materialBufferUnit = -1; + } +#else + loc = program.uniformLocation("materialBuffer"); + if (loc >= 0) { + locations.materialBufferUnit = loc; + } else { + locations.materialBufferUnit = -1; + } +#endif + + if (!program.isLinked()) { + program.release(); + } + program.release(); } @@ -2263,8 +2292,8 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod Locations* locations, SkinLocations* skinLocations) { PROFILE_RANGE(__FUNCTION__); - TextureCache::SharedPointer textureCache = DependencyManager::get(); - GlowEffect::SharedPointer glowEffect = DependencyManager::get(); + auto textureCache = DependencyManager::get(); + auto glowEffect = DependencyManager::get(); QString lastMaterialID; int meshPartsRendered = 0; updateVisibleJointStates(); @@ -2348,6 +2377,7 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod for (int j = 0; j < networkMesh.parts.size(); j++) { const NetworkMeshPart& networkPart = networkMesh.parts.at(j); const FBXMeshPart& part = mesh.parts.at(j); + model::MaterialPointer material = part._material; if ((networkPart.isTranslucent() || part.opacity != 1.0f) != translucent) { offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int); continue; @@ -2365,16 +2395,17 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod qDebug() << "part INDEX:" << j; qDebug() << "NEW part.materialID:" << part.materialID; } - - glm::vec4 diffuse = glm::vec4(part.diffuseColor, part.opacity); - if (!(translucent && alphaThreshold == 0.0f)) { - GLBATCH(glAlphaFunc)(GL_EQUAL, diffuse.a = glowEffect->getIntensity()); + + if (locations->glowIntensity >= 0) { + GLBATCH(glUniform1f)(locations->glowIntensity, glowEffect->getIntensity()); + } + if (!(translucent && alphaThreshold == 0.0f)) { + GLBATCH(glAlphaFunc)(GL_EQUAL, glowEffect->getIntensity()); + } + + if (locations->materialBufferUnit >= 0) { + batch.setUniformBuffer(locations->materialBufferUnit, material->getSchemaBuffer()); } - glm::vec4 specular = glm::vec4(part.specularColor, 1.0f); - GLBATCH(glMaterialfv)(GL_FRONT, GL_AMBIENT, (const float*)&diffuse); - GLBATCH(glMaterialfv)(GL_FRONT, GL_DIFFUSE, (const float*)&diffuse); - GLBATCH(glMaterialfv)(GL_FRONT, GL_SPECULAR, (const float*)&specular); - GLBATCH(glMaterialf)(GL_FRONT, GL_SHININESS, (part.shininess > 128.0f ? 128.0f: part.shininess)); Texture* diffuseMap = networkPart.diffuseTexture.data(); if (mesh.isEye && diffuseMap) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 83ceab109f..ef96f0e05f 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -338,6 +338,8 @@ private: int specularTextureUnit; int emissiveTextureUnit; int emissiveParams; + int glowIntensity; + int materialBufferUnit; }; static Locations _locations; @@ -467,9 +469,9 @@ Q_DECLARE_METATYPE(QWeakPointer) Q_DECLARE_METATYPE(QVector) /// Handle management of pending models that need blending -class ModelBlender : public QObject { +class ModelBlender : public QObject, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY(ModelBlender) + SINGLETON_DEPENDENCY public: diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index df860338ab..3bd05a14ee 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -39,6 +39,8 @@ TextureCache::TextureCache() : _frameBufferSize(100, 100), _associatedWidget(NULL) { + const qint64 TEXTURE_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; + setUnusedResourceCacheSize(TEXTURE_DEFAULT_UNUSED_MAX_SIZE); } TextureCache::~TextureCache() { @@ -203,7 +205,7 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type texture->setCache(this); _dilatableNetworkTextures.insert(url, texture); } else { - _unusedResources.remove(texture->getLRUKey()); + removeUnusedResource(texture); } return texture; } diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index f3ad493cb4..54c98a61cb 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -30,9 +30,9 @@ typedef QSharedPointer NetworkTexturePointer; enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, SPECULAR_TEXTURE, EMISSIVE_TEXTURE, SPLAT_TEXTURE }; /// Stores cached textures, including render-to-texture targets. -class TextureCache : public ResourceCache { +class TextureCache : public ResourceCache, public Dependency { Q_OBJECT - SINGLETON_DEPENDENCY(TextureCache) + SINGLETON_DEPENDENCY public: diff --git a/libraries/render-utils/src/directional_ambient_light.slf b/libraries/render-utils/src/directional_ambient_light.slf new file mode 100755 index 0000000000..803bd5ac30 --- /dev/null +++ b/libraries/render-utils/src/directional_ambient_light.slf @@ -0,0 +1,42 @@ +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// directional_light.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/3/14. +// Copyright 2014 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 +// + +// Everything about deferred buffer +<@include DeferredBuffer.slh@> + +<@include DeferredLighting.slh@> + +void main(void) { + DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st); + + // Light mapped or not ? + if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + gl_FragColor = vec4( evalLightmappedColor( + 1.0, + frag.normal, + frag.diffuse, + frag.specularVal.xyz), + 1.0); + } else { + vec3 color = evalAmbientSphereColor(frag.normal, frag.diffuse, frag.specular, frag.gloss) + + evalDirectionalColor(1.0, + frag.position.xyz, + frag.normal, + frag.diffuse, + frag.specular, + frag.gloss); + + gl_FragColor = vec4(color, frag.normalVal.a); + } +} diff --git a/libraries/render-utils/src/directional_ambient_light_cascaded_shadow_map.slf b/libraries/render-utils/src/directional_ambient_light_cascaded_shadow_map.slf new file mode 100755 index 0000000000..5f88c558d3 --- /dev/null +++ b/libraries/render-utils/src/directional_ambient_light_cascaded_shadow_map.slf @@ -0,0 +1,49 @@ +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// directional_light.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/3/14. +// Copyright 2014 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 +// + +// Everything about deferred buffer +<@include DeferredBuffer.slh@> + +<@include DeferredLighting.slh@> + +// Everything about shadow +<@include Shadow.slh@> + +void main(void) { + DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st); + + // Eval shadow Texcoord and then Attenuation + vec4 shadowTexcoord = evalCascadedShadowTexcoord(frag.position); + float shadowAttenuation = evalShadowAttenuation(shadowTexcoord); + + // Light mapped or not ? + if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + gl_FragColor = vec4(evalLightmappedColor( + shadowAttenuation, + frag.normal, + frag.diffuse, + frag.specularVal.xyz), + 1.0); + } else { + vec3 color = evalAmbientSphereColor(frag.normal, frag.diffuse, frag.specular, frag.gloss) + + evalDirectionalColor(shadowAttenuation, + frag.position.xyz, + frag.normal, + frag.diffuse, + frag.specular, + frag.gloss); + + gl_FragColor = vec4(color, frag.normalVal.a); + } +} diff --git a/libraries/render-utils/src/directional_ambient_light_shadow_map.slf b/libraries/render-utils/src/directional_ambient_light_shadow_map.slf new file mode 100755 index 0000000000..6c241853e3 --- /dev/null +++ b/libraries/render-utils/src/directional_ambient_light_shadow_map.slf @@ -0,0 +1,50 @@ +<@include Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// directional_light.frag +// fragment shader +// +// Created by Andrzej Kapolka on 9/3/14. +// Copyright 2014 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 +// + +// Everything about deferred buffer +<@include DeferredBuffer.slh@> + +<@include DeferredLighting.slh@> + +// Everything about shadow +<@include Shadow.slh@> + + +void main(void) { + DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st); + + // Eval shadow Texcoord and then Attenuation + vec4 shadowTexcoord = evalShadowTexcoord(frag.position); + float shadowAttenuation = evalShadowAttenuation(shadowTexcoord); + + // Light mapped or not ? + if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + gl_FragColor = vec4(evalLightmappedColor( + shadowAttenuation, + frag.normal, + frag.diffuse, + frag.specularVal.xyz), + 1.0); + } else { + vec3 color = evalAmbientSphereColor(frag.normal, frag.diffuse, frag.specular, frag.gloss) + + evalDirectionalColor(shadowAttenuation, + frag.position.xyz, + frag.normal, + frag.diffuse, + frag.specular, + frag.gloss); + + gl_FragColor = vec4(color, frag.normalVal.a); + } +} diff --git a/libraries/render-utils/src/directional_light.slf b/libraries/render-utils/src/directional_light.slf index 7a02e3c3a8..8ff6cd6c87 100644 --- a/libraries/render-utils/src/directional_light.slf +++ b/libraries/render-utils/src/directional_light.slf @@ -12,59 +12,31 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the diffuse texture -uniform sampler2D diffuseMap; +// Everything about deferred buffer +<@include DeferredBuffer.slh@> -// the normal texture -uniform sampler2D normalMap; - -// the specular texture -uniform sampler2D specularMap; - -// the depth texture -uniform sampler2D depthMap; - -// the distance to the near clip plane -uniform float near; - -// scale factor for depth: (far - near) / far -uniform float depthScale; - -// offset for depth texture coordinates -uniform vec2 depthTexCoordOffset; - -// scale for depth texture coordinates -uniform vec2 depthTexCoordScale; +<@include DeferredLighting.slh@> void main(void) { - float depthVal = texture2D(depthMap, gl_TexCoord[0].st).r; - vec4 normalVal = texture2D(normalMap, gl_TexCoord[0].st); - vec4 diffuseVal = texture2D(diffuseMap, gl_TexCoord[0].st); - vec4 specularVal = texture2D(specularMap, gl_TexCoord[0].st); + DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st); - // compute the view space position using the depth - float z = near / (depthVal * depthScale - 1.0); - vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 0.0); - // Light mapped or not ? - if ((normalVal.a >= 0.45) && (normalVal.a <= 0.55)) { - gl_FragColor = vec4(diffuseVal.rgb * specularVal.rgb, 1.0); + if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + gl_FragColor = vec4( evalLightmappedColor( + 1.0, + frag.normal, + frag.diffuse, + frag.specularVal.xyz), + 1.0); } else { - // get the normal from the map - vec3 normalizedNormal = normalize(normalVal.xyz * 2.0 - vec3(1.0)); - - // compute the base color based on OpenGL lighting model - float diffuse = dot(normalizedNormal, gl_LightSource[0].position.xyz); - float facingLight = step(0.0, diffuse); - vec3 baseColor = diffuseVal.rgb * (gl_FrontLightModelProduct.sceneColor.rgb + - gl_FrontLightProduct[0].ambient.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuse * facingLight)); - - // compute the specular multiplier (sans exponent) - float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(position.xyz)), - normalizedNormal)); - - // add specular contribution - vec4 specularColor = specularVal; - gl_FragColor = vec4(baseColor.rgb + pow(specular, specularColor.a * 128.0) * specularColor.rgb, normalVal.a); + vec3 color = evalAmbientColor(frag.normal, frag.diffuse, frag.specular, frag.gloss) + + evalDirectionalColor(1.0, + frag.position.xyz, + frag.normal, + frag.diffuse, + frag.specular, + frag.gloss); + + gl_FragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/directional_light_cascaded_shadow_map.slf b/libraries/render-utils/src/directional_light_cascaded_shadow_map.slf index e2a58de14b..ccf8909b64 100644 --- a/libraries/render-utils/src/directional_light_cascaded_shadow_map.slf +++ b/libraries/render-utils/src/directional_light_cascaded_shadow_map.slf @@ -12,86 +12,38 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the diffuse texture -uniform sampler2D diffuseMap; +// Everything about deferred buffer +<@include DeferredBuffer.slh@> -// the normal texture -uniform sampler2D normalMap; - -// the specular texture -uniform sampler2D specularMap; - -// the depth texture -uniform sampler2D depthMap; +<@include DeferredLighting.slh@> // Everything about shadow <@include Shadow.slh@> -// the distance to the near clip plane -uniform float near; - -// scale factor for depth: (far - near) / far -uniform float depthScale; - -// offset for depth texture coordinates -uniform vec2 depthTexCoordOffset; - -// scale for depth texture coordinates -uniform vec2 depthTexCoordScale; - void main(void) { - float depthVal = texture2D(depthMap, gl_TexCoord[0].st).r; - vec4 normalVal = texture2D(normalMap, gl_TexCoord[0].st); - vec4 diffuseVal = texture2D(diffuseMap, gl_TexCoord[0].st); - vec4 specularVal = texture2D(specularMap, gl_TexCoord[0].st); - - // compute the view space position using the depth - float z = near / (depthVal * depthScale - 1.0); - vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 1.0); + DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st); // Eval shadow Texcoord and then Attenuation - vec4 shadowTexcoord = evalCascadedShadowTexcoord(position); + vec4 shadowTexcoord = evalCascadedShadowTexcoord(frag.position); float shadowAttenuation = evalShadowAttenuation(shadowTexcoord); - // get the normal from the map - vec3 normalizedNormal = normalize(normalVal.xyz * 2.0 - vec3(1.0)); - - // how much this fragment faces the light direction - float diffuse = dot(normalizedNormal, gl_LightSource[0].position.xyz); - // Light mapped or not ? - if ((normalVal.a >= 0.45) && (normalVal.a <= 0.55)) { - normalVal.a = 1.0; - - // need to catch normals perpendicular to the projection plane hence the magic number for the threshold - // it should be just 0, but we have innacurracy so we need to overshoot - const float PERPENDICULAR_THRESHOLD = -0.005; - float facingLight = step(PERPENDICULAR_THRESHOLD, diffuse); - - // evaluate the shadow test but only relevant for light facing fragments - float lightAttenuation = (1 - facingLight) + facingLight * shadowAttenuation; - - // diffuse light is the lightmap dimmed by shadow - vec3 diffuseLight = lightAttenuation * specularVal.rgb; - // ambient is a tiny percentage of the lightmap and only when in the shadow - vec3 ambientLight = (1 - lightAttenuation) * 0.5 * specularVal.rgb; - - gl_FragColor = vec4(diffuseVal.rgb * (ambientLight + diffuseLight), 1.0); + if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + gl_FragColor = vec4(evalLightmappedColor( + shadowAttenuation, + frag.normal, + frag.diffuse, + frag.specularVal.xyz), + 1.0); } else { + vec3 color = evalAmbientColor(frag.normal, frag.diffuse, frag.specular, frag.gloss) + + evalDirectionalColor(shadowAttenuation, + frag.position.xyz, + frag.normal, + frag.diffuse, + frag.specular, + frag.gloss); - // average values from the shadow map - float facingLight = step(0.0, diffuse) * shadowAttenuation; - - // compute the base color based on OpenGL lighting model - vec3 baseColor = diffuseVal.rgb * (gl_FrontLightModelProduct.sceneColor.rgb + - gl_FrontLightProduct[0].ambient.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuse * facingLight)); - - // compute the specular multiplier (sans exponent) - float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(position.xyz)), - normalizedNormal)); - - // add specular contribution - vec4 specularColor = specularVal; - gl_FragColor = vec4(baseColor.rgb + pow(specular, specularColor.a * 128.0) * specularColor.rgb, normalVal.a); + gl_FragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/directional_light_shadow_map.slf b/libraries/render-utils/src/directional_light_shadow_map.slf index 7a34f56f93..13435e9101 100644 --- a/libraries/render-utils/src/directional_light_shadow_map.slf +++ b/libraries/render-utils/src/directional_light_shadow_map.slf @@ -12,85 +12,39 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the diffuse texture -uniform sampler2D diffuseMap; +// Everything about deferred buffer +<@include DeferredBuffer.slh@> -// the normal texture -uniform sampler2D normalMap; - -// the specular texture -uniform sampler2D specularMap; - -// the depth texture -uniform sampler2D depthMap; +<@include DeferredLighting.slh@> // Everything about shadow -<@include Shadow.slh@> +<@include Shadow.slh@> -// the distance to the near clip plane -uniform float near; - -// scale factor for depth: (far - near) / far -uniform float depthScale; - -// offset for depth texture coordinates -uniform vec2 depthTexCoordOffset; - -// scale for depth texture coordinates -uniform vec2 depthTexCoordScale; void main(void) { - float depthVal = texture2D(depthMap, gl_TexCoord[0].st).r; - vec4 normalVal = texture2D(normalMap, gl_TexCoord[0].st); - vec4 diffuseVal = texture2D(diffuseMap, gl_TexCoord[0].st); - vec4 specularVal = texture2D(specularMap, gl_TexCoord[0].st); - - // compute the view space position using the depth - float z = near / (depthVal * depthScale - 1.0); - vec4 position = vec4((depthTexCoordOffset + gl_TexCoord[0].st * depthTexCoordScale) * z, z, 1.0); + DeferredFragment frag = unpackDeferredFragment(gl_TexCoord[0].st); // Eval shadow Texcoord and then Attenuation - vec4 shadowTexcoord = evalShadowTexcoord(position); + vec4 shadowTexcoord = evalShadowTexcoord(frag.position); float shadowAttenuation = evalShadowAttenuation(shadowTexcoord); - // get the normal from the map - vec3 normalizedNormal = normalize(normalVal.xyz * 2.0 - vec3(1.0)); - - // how much this fragment faces the light direction - float diffuse = dot(normalizedNormal, gl_LightSource[0].position.xyz); - // Light mapped or not ? - if ((normalVal.a >= 0.45) && (normalVal.a <= 0.55)) { - normalVal.a = 0.0; + if ((frag.normalVal.a >= 0.45) && (frag.normalVal.a <= 0.55)) { + gl_FragColor = vec4(evalLightmappedColor( + shadowAttenuation, + frag.normal, + frag.diffuse, + frag.specularVal.xyz), + 1.0); + } else { + vec3 color = evalAmbientColor(frag.normal, frag.diffuse, frag.specular, frag.gloss) + + evalDirectionalColor(shadowAttenuation, + frag.position.xyz, + frag.normal, + frag.diffuse, + frag.specular, + frag.gloss); - // need to catch normals perpendicular to the projection plane hence the magic number for the threshold - // it should be just 0, be we have innacurracy so we need to overshoot - const float PERPENDICULAR_THRESHOLD = -0.005; - float facingLight = step(PERPENDICULAR_THRESHOLD, diffuse); - - // evaluate the shadow test but only relevant for light facing fragments - float lightAttenuation = (1 - facingLight) + facingLight * shadowAttenuation; - - // diffuse light is the lightmap dimmed by shadow - vec3 diffuseLight = lightAttenuation * specularVal.rgb; - // ambient is a tiny percentage of the lightmap and only when in the shadow - vec3 ambientLight = (1 - lightAttenuation) * 0.5 * specularVal.rgb; - - gl_FragColor = vec4(diffuseVal.rgb * (ambientLight + diffuseLight), 1.0); - } else { - // average values from the shadow map - float facingLight = step(0.0, diffuse) * shadowAttenuation; - - // compute the base color based on OpenGL lighting model - vec3 baseColor = diffuseVal.rgb * (gl_FrontLightModelProduct.sceneColor.rgb + - gl_FrontLightProduct[0].ambient.rgb + gl_FrontLightProduct[0].diffuse.rgb * (diffuse * facingLight)); - - // compute the specular multiplier (sans exponent) - float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position.xyz - normalize(position.xyz)), - normalizedNormal)); - - // add specular contribution - vec4 specularColor = specularVal; - gl_FragColor = vec4(baseColor.rgb + pow(specular, specularColor.a * 128.0) * specularColor.rgb, normalVal.a); + gl_FragColor = vec4(color, frag.normalVal.a); } } diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index 42421c71a2..bc6f127a77 100755 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -11,19 +11,26 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 normal; void main(void) { - // set the diffuse, normal, specular data + // Fetch diffuse map vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb, mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb, gl_FrontMaterial.shininess / 128.0); + + Material mat = getMaterial(); + + packDeferredFragment( + normalize(normal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + getMaterialSpecular(mat), + getMaterialShininess(mat)); } diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index 496667a062..1c3b03a304 100755 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -23,7 +23,7 @@ void main(void) { normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0)); // pass along the diffuse color - gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse; + gl_FrontColor = gl_Color; // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); diff --git a/libraries/render-utils/src/model_lightmap.slf b/libraries/render-utils/src/model_lightmap.slf index 9feacbe057..59836e9737 100755 --- a/libraries/render-utils/src/model_lightmap.slf +++ b/libraries/render-utils/src/model_lightmap.slf @@ -12,6 +12,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; @@ -19,9 +23,6 @@ uniform sampler2D diffuseMap; uniform sampler2D emissiveMap; uniform vec2 emissiveParams; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 normal; @@ -32,7 +33,14 @@ void main(void) { // set the diffuse, normal, specular data vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb, mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5); - gl_FragData[2] = vec4((vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), gl_FrontMaterial.shininess / 128.0); + + Material mat = getMaterial(); + + packDeferredFragmentLightmap( + normalize(normal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + getMaterialSpecular(mat), + getMaterialShininess(mat), + (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); } diff --git a/libraries/render-utils/src/model_lightmap.slv b/libraries/render-utils/src/model_lightmap.slv index 9f19ae8de2..23d99b399a 100755 --- a/libraries/render-utils/src/model_lightmap.slv +++ b/libraries/render-utils/src/model_lightmap.slv @@ -29,7 +29,7 @@ void main(void) { normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0)); // pass along the diffuse color - gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse; + gl_FrontColor = gl_Color; // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slf b/libraries/render-utils/src/model_lightmap_normal_map.slf index 5bebbee3f6..6310ab0086 100755 --- a/libraries/render-utils/src/model_lightmap_normal_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_map.slf @@ -12,6 +12,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; @@ -22,9 +26,6 @@ uniform sampler2D normalMap; uniform sampler2D emissiveMap; uniform vec2 emissiveParams; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 interpolatedNormal; @@ -45,7 +46,14 @@ void main(void) { // set the diffuse, normal, specular data vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb * (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = viewNormal + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb, gl_FrontMaterial.shininess / 128.0); + + Material mat = getMaterial(); + + packDeferredFragmentLightmap( + normalize(viewNormal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + getMaterialSpecular(mat), + getMaterialShininess(mat), + (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); } diff --git a/libraries/render-utils/src/model_lightmap_normal_map.slv b/libraries/render-utils/src/model_lightmap_normal_map.slv index c19336ddae..14e1d52779 100755 --- a/libraries/render-utils/src/model_lightmap_normal_map.slv +++ b/libraries/render-utils/src/model_lightmap_normal_map.slv @@ -36,7 +36,7 @@ void main(void) { interpolatedTangent = gl_ModelViewMatrix * vec4(tangent, 0.0); // pass along the diffuse color - gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse; + gl_FrontColor = gl_Color; // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); diff --git a/libraries/render-utils/src/model_lightmap_normal_specular_map.slf b/libraries/render-utils/src/model_lightmap_normal_specular_map.slf index d2f76e4ef3..ab555ea0c0 100755 --- a/libraries/render-utils/src/model_lightmap_normal_specular_map.slf +++ b/libraries/render-utils/src/model_lightmap_normal_specular_map.slf @@ -12,6 +12,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; @@ -25,9 +29,6 @@ uniform sampler2D normalMap; // the specular map texture uniform sampler2D specularMap; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 interpolatedNormal; @@ -47,9 +48,16 @@ void main(void) { // set the diffuse, normal, specular data vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); + vec3 specular = texture2D(specularMap, gl_TexCoord[0].st).rgb; vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb * (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = viewNormal + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, - gl_FrontMaterial.shininess / 128.0); + + Material mat = getMaterial(); + + packDeferredFragmentLightmap( + normalize(viewNormal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + specular, // no use of getMaterialSpecular(mat) + getMaterialShininess(mat), + (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); } diff --git a/libraries/render-utils/src/model_lightmap_specular_map.slf b/libraries/render-utils/src/model_lightmap_specular_map.slf index 40879a8fc3..2973882e8a 100755 --- a/libraries/render-utils/src/model_lightmap_specular_map.slf +++ b/libraries/render-utils/src/model_lightmap_specular_map.slf @@ -12,6 +12,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; @@ -22,9 +26,6 @@ uniform vec2 emissiveParams; // the specular texture uniform sampler2D specularMap; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 normal; @@ -33,9 +34,16 @@ varying vec2 interpolatedTexcoord1; void main(void) { // set the diffuse, normal, specular data vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); + vec3 specular = texture2D(specularMap, gl_TexCoord[0].st).rgb; vec4 emissive = texture2D(emissiveMap, interpolatedTexcoord1.st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb * (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb), mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, - gl_FrontMaterial.shininess / 128.0); + + Material mat = getMaterial(); + + packDeferredFragmentLightmap( + normalize(normal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + specular, // no use of getMaterialSpecular(mat) + getMaterialShininess(mat), + (vec3(emissiveParams.x) + emissiveParams.y * emissive.rgb)); } diff --git a/libraries/render-utils/src/model_normal_map.slf b/libraries/render-utils/src/model_normal_map.slf index b625b346ed..c6baa6f797 100755 --- a/libraries/render-utils/src/model_normal_map.slf +++ b/libraries/render-utils/src/model_normal_map.slf @@ -12,15 +12,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; // the normal map texture uniform sampler2D normalMap; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 interpolatedNormal; @@ -35,10 +36,15 @@ void main(void) { vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) - vec3(0.5, 0.5, 0.5); vec4 viewNormal = vec4(normalizedTangent * localNormal.x + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); - - // set the diffuse, normal, specular data + vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb, mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = viewNormal + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb, gl_FrontMaterial.shininess / 128.0); + + Material mat = getMaterial(); + + packDeferredFragment( + normalize(viewNormal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + getMaterialSpecular(mat), + getMaterialShininess(mat)); } diff --git a/libraries/render-utils/src/model_normal_map.slv b/libraries/render-utils/src/model_normal_map.slv index 0eb974912f..a27cd49171 100755 --- a/libraries/render-utils/src/model_normal_map.slv +++ b/libraries/render-utils/src/model_normal_map.slv @@ -31,7 +31,7 @@ void main(void) { interpolatedTangent = gl_ModelViewMatrix * vec4(tangent, 0.0); // pass along the diffuse color - gl_FrontColor = gl_Color * gl_FrontMaterial.diffuse; + gl_FrontColor = gl_Color; // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); diff --git a/libraries/render-utils/src/model_normal_specular_map.slf b/libraries/render-utils/src/model_normal_specular_map.slf index fd288f0867..8e7c9d3293 100755 --- a/libraries/render-utils/src/model_normal_specular_map.slf +++ b/libraries/render-utils/src/model_normal_specular_map.slf @@ -12,6 +12,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; @@ -21,9 +25,6 @@ uniform sampler2D normalMap; // the specular map texture uniform sampler2D specularMap; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 interpolatedNormal; @@ -41,8 +42,14 @@ void main(void) { // set the diffuse, normal, specular data vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb, mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = viewNormal + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, - gl_FrontMaterial.shininess / 128.0); + vec3 specular = texture2D(specularMap, gl_TexCoord[0].st).rgb; + + Material mat = getMaterial(); + + packDeferredFragment( + normalize(viewNormal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + specular, //getMaterialSpecular(mat), + getMaterialShininess(mat)); } diff --git a/libraries/render-utils/src/model_specular_map.slf b/libraries/render-utils/src/model_specular_map.slf index 4428173562..bfcc69287d 100755 --- a/libraries/render-utils/src/model_specular_map.slf +++ b/libraries/render-utils/src/model_specular_map.slf @@ -12,23 +12,30 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; // the specular texture uniform sampler2D specularMap; -// the alpha threshold -uniform float alphaThreshold; - // the interpolated normal varying vec4 normal; void main(void) { // set the diffuse, normal, specular data vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); - gl_FragData[0] = vec4(gl_Color.rgb * diffuse.rgb, mix(gl_Color.a, 1.0 - gl_Color.a, step(diffuse.a, alphaThreshold))); - gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); - gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, - gl_FrontMaterial.shininess / 128.0); + vec3 specular = texture2D(specularMap, gl_TexCoord[0].st).rgb; + + Material mat = getMaterial(); + + packDeferredFragment( + normalize(normal.xyz), + evalOpaqueFinalAlpha(getMaterialOpacity(mat), diffuse.a), + getMaterialDiffuse(mat) * diffuse.rgb, + specular, //getMaterialSpecular(mat), + getMaterialShininess(mat)); } diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 497f5962bc..4d1bf7bdec 100755 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -12,10 +12,29 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + +<@include Material.slh@> + // the diffuse texture uniform sampler2D diffuseMap; +varying vec4 normal; + void main(void) { + + // Fetch diffuse map + vec4 diffuse = texture2D(diffuseMap, gl_TexCoord[0].st); + + Material mat = getMaterial(); + + packDeferredFragmentTranslucent( + normalize(normal.xyz), + getMaterialOpacity(mat) * diffuse.a, + getMaterialDiffuse(mat) * diffuse.rgb, + getMaterialSpecular(mat), + getMaterialShininess(mat)); + // set the diffuse data - gl_FragData[0] = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st); + // gl_FragData[0] = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st); } diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index 320dc93f5e..e5142b25ce 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -12,29 +12,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the diffuse texture -uniform sampler2D diffuseMap; - -// the normal texture -uniform sampler2D normalMap; - -// the specular texture -uniform sampler2D specularMap; - -// the depth texture -uniform sampler2D depthMap; - -// the distance to the near clip plane -uniform float near; - -// scale factor for depth: (far - near) / far -uniform float depthScale; - -// offset for depth texture coordinates -uniform vec2 depthTexCoordOffset; - -// scale for depth texture coordinates -uniform vec2 depthTexCoordScale; +// Everything about deferred buffer +<@include DeferredBuffer.slh@> // the radius (hard cutoff) of the light effect uniform float radius; diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 50e584669d..faa4af4573 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -12,15 +12,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + // the interpolated normal varying vec4 normal; // the glow intensity -uniform float glowIntensity; +//uniform float glowIntensity; void main(void) { - // set the diffuse, normal, specular data + /* // set the diffuse, normal, specular data gl_FragData[0] = vec4(gl_Color.rgb, glowIntensity); gl_FragData[1] = normalize(normal) * 0.5 + vec4(0.5, 0.5, 0.5, 1.0); gl_FragData[2] = vec4(gl_FrontMaterial.specular.rgb, gl_FrontMaterial.shininess / 128.0); + */ + + packDeferredFragment( + normalize(normal.xyz), + glowIntensity, + gl_Color.rgb, + gl_FrontMaterial.specular.rgb, + gl_FrontMaterial.shininess); } diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index 4144198969..4cef8fddab 100755 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -38,8 +38,8 @@ void main(void) { normal = normalize(gl_ModelViewMatrix * normal); // pass along the diffuse color - gl_FrontColor = gl_FrontMaterial.diffuse; - + gl_FrontColor = gl_Color; + // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index b021184591..c39f9cc0dd 100755 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -46,8 +46,8 @@ void main(void) { interpolatedTangent = gl_ModelViewMatrix * interpolatedTangent; // pass along the diffuse color - gl_FrontColor = gl_FrontMaterial.diffuse; - + gl_FrontColor = gl_Color; + // and the texture coordinates gl_TexCoord[0] = texcoordMatrices[0] * vec4(gl_MultiTexCoord0.xy, 0.0, 1.0); diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index 489802d061..f987760eb8 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -12,29 +12,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the diffuse texture -uniform sampler2D diffuseMap; - -// the normal texture -uniform sampler2D normalMap; - -// the specular texture -uniform sampler2D specularMap; - -// the depth texture -uniform sampler2D depthMap; - -// the distance to the near clip plane -uniform float near; - -// scale factor for depth: (far - near) / far -uniform float depthScale; - -// offset for depth texture coordinates -uniform vec2 depthTexCoordOffset; - -// scale for depth texture coordinates -uniform vec2 depthTexCoordScale; +// Everything about deferred buffer +<@include DeferredBuffer.slh@> // the radius (hard cutoff) of the light effect uniform float radius; diff --git a/libraries/script-engine/src/EventTypes.h b/libraries/script-engine/src/EventTypes.h index fc808ea560..906006e4f4 100644 --- a/libraries/script-engine/src/EventTypes.h +++ b/libraries/script-engine/src/EventTypes.h @@ -12,7 +12,7 @@ #ifndef hifi_EventTypes_h #define hifi_EventTypes_h -#include +#include void registerEventTypes(QScriptEngine* engine); diff --git a/libraries/script-engine/src/KeyEvent.h b/libraries/script-engine/src/KeyEvent.h index bdadcec374..350b733eaf 100644 --- a/libraries/script-engine/src/KeyEvent.h +++ b/libraries/script-engine/src/KeyEvent.h @@ -13,7 +13,7 @@ #define hifi_KeyEvent_h #include -#include +#include class KeyEvent { public: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 9450d99cf9..0f860208f4 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -344,7 +344,7 @@ void ScriptEngine::run() { int thisFrame = 0; - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); qint64 lastUpdate = usecTimestampNow(); @@ -459,7 +459,7 @@ void ScriptEngine::run() { } // write audio packet to AudioMixer nodes - NodeList* nodeList = NodeList::getInstance(); + auto nodeList = DependencyManager::get(); nodeList->eachNode([this, &nodeList, &audioPacket, &numPreSequenceNumberBytes](const SharedNodePointer& node){ // only send to nodes of type AudioMixer if (node->getType() == NodeType::AudioMixer) { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index a756b3fe8f..92c9c1856c 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -66,7 +66,7 @@ QScriptValue XMLHttpRequestClass::getStatus() const { return QScriptValue(200); case QNetworkReply::ContentNotFoundError: return QScriptValue(404); - case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentConflictError: return QScriptValue(409); case QNetworkReply::TimeoutError: return QScriptValue(408); @@ -89,7 +89,7 @@ QString XMLHttpRequestClass::getStatusText() const { return "OK"; case QNetworkReply::ContentNotFoundError: return "Not Found"; - case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentConflictError: return "Conflict"; case QNetworkReply::TimeoutError: return "Timeout"; @@ -196,8 +196,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a } else if (!_file->open(QIODevice::ReadOnly)) { qDebug() << "Can't open file " << _url.fileName(); abortRequest(); - //_errorCode = QNetworkReply::ContentConflictError; // TODO: Use this status when update to Qt 5.3 - _errorCode = QNetworkReply::ContentAccessDenied; + _errorCode = QNetworkReply::ContentConflictError; setReadyState(DONE); emit requestComplete(); } else { diff --git a/libraries/shared/src/DependencyManager.cpp b/libraries/shared/src/DependencyManager.cpp index 43bb92258e..5f78f6bcd5 100644 --- a/libraries/shared/src/DependencyManager.cpp +++ b/libraries/shared/src/DependencyManager.cpp @@ -11,10 +11,8 @@ #include "DependencyManager.h" -DependencyManager& DependencyManager::getInstance() { - static DependencyManager instance; - return instance; -} +DependencyManager DependencyManager::_manager; -DependencyManager::~DependencyManager() { +QSharedPointer& DependencyManager::safeGet(size_t hashCode) { + return _instanceHash[hashCode]; } \ No newline at end of file diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index 2ccbe18dd8..3868bf14da 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -12,46 +12,110 @@ #ifndef hifi_DependencyManager_h #define hifi_DependencyManager_h -#include #include +#include +#include +#include #include -#define SINGLETON_DEPENDENCY(T)\ -public:\ - typedef QSharedPointer SharedPointer;\ -private:\ - void customDeleter() {\ - QObject* thisObject = dynamic_cast(this);\ - if (thisObject && thisObject->parent()) {\ - thisObject->deleteLater();\ - qDebug() << "Delete later:" << #T;\ - } else {\ - delete this;\ - qDebug() << "Deleted:" << #T;\ - }\ - }\ +#define SINGLETON_DEPENDENCY \ friend class DependencyManager; -class QObject; +class Dependency { +protected: + virtual ~Dependency() {} + virtual void customDeleter() { + delete this; + } + friend class DependencyManager; +}; + +// usage: +// auto instance = DependencyManager::get(); +// auto instance = DependencyManager::set(Args... args); +// DependencyManager::destroy(); +// DependencyManager::registerInheritance(); class DependencyManager { public: - // Only accessible method. - // usage: T* instance = DependencyManager::get(); template static QSharedPointer get(); + template + static QSharedPointer set(Args&&... args); + + template + static void destroy(); + + template + static void registerInheritance(); + private: - static DependencyManager& getInstance(); - DependencyManager() {} - ~DependencyManager(); + static DependencyManager _manager; + + template + size_t getHashCode(); + + QSharedPointer& safeGet(size_t hashCode); + + QHash> _instanceHash; + QHash _inheritanceHash; }; template QSharedPointer DependencyManager::get() { - static QSharedPointer sharedPointer = QSharedPointer(new T(), &T::customDeleter); - return sharedPointer; + static size_t hashCode = _manager.getHashCode(); + static QWeakPointer instance; + + if (instance.isNull()) { + instance = qSharedPointerCast(_manager.safeGet(hashCode)); + + if (instance.isNull()) { + qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name(); + } + } + + return instance.toStrongRef(); +} + +template +QSharedPointer DependencyManager::set(Args&&... args) { + static size_t hashCode = _manager.getHashCode(); + + QSharedPointer& instance = _manager.safeGet(hashCode); + instance.clear(); // Clear instance before creation of new one to avoid edge cases + QSharedPointer newInstance(new T(args...), &T::customDeleter); + QSharedPointer storedInstance = qSharedPointerCast(newInstance); + instance.swap(storedInstance); + + return newInstance; +} + +template +void DependencyManager::destroy() { + static size_t hashCode = _manager.getHashCode(); + _manager.safeGet(hashCode).clear(); +} + +template +void DependencyManager::registerInheritance() { + size_t baseHashCode = typeid(Base).hash_code(); + size_t derivedHashCode = typeid(Derived).hash_code(); + _manager._inheritanceHash.insert(baseHashCode, derivedHashCode); +} + +template +size_t DependencyManager::getHashCode() { + size_t hashCode = typeid(T).hash_code(); + auto derivedHashCode = _inheritanceHash.find(hashCode); + + while (derivedHashCode != _inheritanceHash.end()) { + hashCode = derivedHashCode.value(); + derivedHashCode = _inheritanceHash.find(hashCode); + } + + return hashCode; } #endif // hifi_DependencyManager_h diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index d402a048a0..a1e72cdca0 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -24,18 +24,21 @@ void ShapeInfo::clear() { void ShapeInfo::setBox(const glm::vec3& halfExtents) { _type = BOX_SHAPE; _data.clear(); + // _data[0] = < halfX, halfY, halfZ > _data.push_back(halfExtents); } void ShapeInfo::setSphere(float radius) { _type = SPHERE_SHAPE; _data.clear(); + // _data[0] = < radius, radius, radius > _data.push_back(glm::vec3(radius)); } void ShapeInfo::setCylinder(float radius, float halfHeight) { _type = CYLINDER_SHAPE; _data.clear(); + // _data[0] = < radius, halfHeight, radius > // NOTE: default cylinder has (UpAxis = 1) axis along yAxis and radius stored in X _data.push_back(glm::vec3(radius, halfHeight, radius)); } @@ -43,6 +46,7 @@ void ShapeInfo::setCylinder(float radius, float halfHeight) { void ShapeInfo::setCapsule(float radius, float halfHeight) { _type = CAPSULE_SHAPE; _data.clear(); + // _data[0] = < radius, halfHeight, radius > _data.push_back(glm::vec3(radius, halfHeight, radius)); } @@ -58,3 +62,34 @@ glm::vec3 ShapeInfo::getBoundingBoxDiagonal() const { } return glm::vec3(0.0f); } + +float ShapeInfo::computeVolume() const { + const float DEFAULT_VOLUME = 1.0f; + float volume = DEFAULT_VOLUME; + switch(_type) { + case BOX_SHAPE: { + // factor of 8.0 because the components of _data[0] are all halfExtents + volume = 8.0f * _data[0].x * _data[0].y * _data[0].z; + break; + } + case SPHERE_SHAPE: { + float radius = _data[0].x; + volume = 4.0f * PI * radius * radius * radius / 3.0f; + break; + } + case CYLINDER_SHAPE: { + float radius = _data[0].x; + volume = PI * radius * radius * 2.0f * _data[0].y; + break; + } + case CAPSULE_SHAPE: { + float radius = _data[0].x; + volume = PI * radius * radius * (2.0f * _data[0].y + 4.0f * radius / 3.0f); + break; + } + default: + break; + } + assert(volume > 0.0f); + return volume; +} diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 7ba290122c..9b4c587c3f 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -32,6 +32,7 @@ public: const QVector& getData() const { return _data; } glm::vec3 getBoundingBoxDiagonal() const; + float computeVolume() const; protected: int _type; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 61b7365877..01dcd7ff02 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -19,7 +19,7 @@ #include // not on windows, not needed for mac or windows #endif -#include +#include const int BYTES_PER_COLOR = 3; const int BYTES_PER_FLAGS = 1; diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 0609d7bd01..106c56b0fe 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -440,7 +440,7 @@ static bool testSerialization(Bitstream::MetadataType metadataType) { } bool MetavoxelTests::run() { - LimitedNodeList::createInstance(); + DependencyManager::set(); // seed the random number generator so that our tests are reproducible srand(0xBAAAAABE);