diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index e7ac7577b9..929d6c76c8 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -96,17 +96,14 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : assignmentServerPort = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt(); } - - HifiSockAddr assignmentServerSocket(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, assignmentServerPort); // check for an overriden assignment server hostname if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION)) { - _assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString(); - // change the hostname for our assignment server - assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerSocket.getPort()); + _assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString(); } + HifiSockAddr assignmentServerSocket(_assignmentServerHostname, assignmentServerPort, true); nodeList->setAssignmentServerSocket(assignmentServerSocket); qDebug() << "Assignment server socket is" << assignmentServerSocket; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f99c1d280e..a2198370ce 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -50,6 +50,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _hostname(), _webAuthenticationStateSet(), _cookieSessionHash(), + _automaticNetworkingSetting(), _settingsManager() { LogUtils::init(); @@ -327,17 +328,17 @@ void DomainServer::setupAutomaticNetworking() { return; } - QString automaticNetworkValue = + _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); - if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || - automaticNetworkValue == FULL_AUTOMATIC_NETWORKING_VALUE) { + if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || + _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { LimitedNodeList* nodeList = LimitedNodeList::getInstance(); const QUuid& domainID = nodeList->getSessionUUID(); if (!domainID.isNull()) { - qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID" + qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; @@ -347,7 +348,7 @@ void DomainServer::setupAutomaticNetworking() { QTimer* dynamicIPTimer = new QTimer(this); connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); - if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { + if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { 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 @@ -361,11 +362,11 @@ void DomainServer::setupAutomaticNetworking() { iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); // call our sendHeartbeaToIceServer immediately anytime a local or public socket changes - connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer); - connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer); + connect(nodeList, &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); + connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); - // tell the data server which type of automatic networking we are using - updateNetworkingInfoWithDataServer(automaticNetworkValue); + // send our heartbeat to data server so it knows what our network settings are + sendHeartbeatToDataServer(); } // attempt to update our sockets now @@ -378,8 +379,17 @@ void DomainServer::setupAutomaticNetworking() { return; } } else { - updateNetworkingInfoWithDataServer(automaticNetworkValue); + sendHeartbeatToDataServer(); } + + qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; + + // no matter the auto networking settings we should heartbeat to the data-server every 15s + const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000; + + QTimer* dataHeartbeatTimer = new QTimer(this); + connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer())); + dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); } void DomainServer::loginFailed() { @@ -627,7 +637,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username, } if (allowedUsers.count() > 0) { - if (allowedUsers.contains(username)) { + if (allowedUsers.contains(username, Qt::CaseInsensitive)) { // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); @@ -647,7 +657,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username, rsaPublicKey, RSA_PKCS1_PADDING); if (decryptResult != -1) { - if (username == decryptedArray) { + if (username.toLower() == decryptedArray) { qDebug() << "Username signature matches for" << username << "- allowing connection."; // free up the public key before we return @@ -1081,10 +1091,10 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking"; void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) { - updateNetworkingInfoWithDataServer(IP_ONLY_AUTOMATIC_NETWORKING_VALUE, newPublicSockAddr.getAddress().toString()); + sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString()); } -void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress) { +void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID(); @@ -1097,9 +1107,30 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress; } - qDebug() << "Updating automatic networking setting in domain-server to" << newSetting; + domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting; - domainObject[AUTOMATIC_NETWORKING_KEY] = newSetting; + // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings + const QString RESTRICTED_ACCESS_FLAG = "restricted"; + + const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), + ALLOWED_USERS_SETTINGS_KEYPATH); + QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList(); + domainObject[RESTRICTED_ACCESS_FLAG] = (allowedUsers.size() > 0); + + // add the number of currently connected agent users + int numConnectedAuthedUsers = 0; + foreach(const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { + if (node->getLinkedData() && !static_cast(node->getLinkedData())->getUsername().isEmpty()) { + ++numConnectedAuthedUsers; + } + } + + const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; + const QString HEARTBEAT_NUM_USERS_KEY = "num_users"; + + QJsonObject heartbeatObject; + heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers; + domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject; QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); @@ -1112,11 +1143,11 @@ void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, // todo: have data-web respond with ice-server hostname to use void DomainServer::performICEUpdates() { - sendHearbeatToIceServer(); + sendHeartbeatToIceServer(); sendICEPingPackets(); } -void DomainServer::sendHearbeatToIceServer() { +void DomainServer::sendHeartbeatToIceServer() { const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT); LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR); } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 5e4da00601..de485da5e7 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -66,7 +66,8 @@ private slots: void requestCurrentPublicSocketViaSTUN(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); void performICEUpdates(); - void sendHearbeatToIceServer(); + void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } + void sendHeartbeatToIceServer(); void sendICEPingPackets(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); @@ -76,7 +77,7 @@ private: bool optionallySetupAssignmentPayment(); void setupAutomaticNetworking(); - void updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress = QString()); + void sendHeartbeatToDataServer(const QString& networkAddress); void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr); void processICEHeartbeatResponse(const QByteArray& packet); @@ -150,6 +151,8 @@ private: QHash _connectingICEPeers; QHash _connectedICEPeers; + QString _automaticNetworkingSetting; + DomainServerSettingsManager _settingsManager; }; diff --git a/examples/cameraExample.js b/examples/cameraExample.js index 332cc0fbca..4c02ce2f2e 100644 --- a/examples/cameraExample.js +++ b/examples/cameraExample.js @@ -23,7 +23,7 @@ var THRUST_CONTROLLER = 0; var VIEW_CONTROLLER = 1; function checkCamera(deltaTime) { - if (Camera.getMode() == "independent") { + if (Camera.mode == "independent") { var THRUST_MAG_UP = 800.0; var THRUST_MAG_DOWN = 300.0; var THRUST_MAG_FWD = 500.0; @@ -102,19 +102,19 @@ function keyPressEvent(event) { } if (event.text == "1") { - Camera.setMode("first person"); + Camera.mode = "first person"; } if (event.text == "2") { - Camera.setMode("mirror"); + Camera.mode = "mirror"; } if (event.text == "3") { - Camera.setMode("third person"); + Camera.mode = "third person"; } if (event.text == "4") { - Camera.setMode("independent"); + Camera.mode = "independent"; joysticksCaptured = true; Controller.captureJoystick(THRUST_CONTROLLER); Controller.captureJoystick(VIEW_CONTROLLER); diff --git a/examples/concertCamera.js b/examples/concertCamera.js index 03908d0b57..7ab7785345 100644 --- a/examples/concertCamera.js +++ b/examples/concertCamera.js @@ -20,15 +20,15 @@ var cameraLocations = [ {x: 7971.9, y: 241.3, z: 7304.1}, {x: 7973.0, y: 241.3, var cameraLookAts = [ {x: 7971.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7971.3, y: 241.3, z: 7304.2} ]; function saveCameraState() { - oldMode = Camera.getMode(); + oldMode = Camera.mode; avatarPosition = MyAvatar.position; Camera.setModeShiftPeriod(0.0); - Camera.setMode("independent"); + Camera.mode = "independent"; } function restoreCameraState() { Camera.stopLooking(); - Camera.setMode(oldMode); + Camera.mode = oldMode; } function update(deltaTime) { @@ -52,7 +52,7 @@ function keyPressEvent(event) { saveCameraState(); freeCamera = true; } - Camera.setMode("independent"); + Camera.mode = "independent"; Camera.setPosition(cameraLocations[choice - 1]); Camera.keepLookingAt(cameraLookAts[choice - 1]); } diff --git a/examples/concertCamera_kims.js b/examples/concertCamera_kims.js index 3017d3c008..ff4fb632de 100644 --- a/examples/concertCamera_kims.js +++ b/examples/concertCamera_kims.js @@ -20,15 +20,15 @@ var cameraLocations = [ {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, var cameraLookAts = [ {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7305.7}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0}, {x: 8027.5, y: 237.5, z: 7304.0} ]; function saveCameraState() { - oldMode = Camera.getMode(); + oldMode = Camera.mode; avatarPosition = MyAvatar.position; Camera.setModeShiftPeriod(0.0); - Camera.setMode("independent"); + Camera.mode = "independent"; } function restoreCameraState() { Camera.stopLooking(); - Camera.setMode(oldMode); + Camera.mode = oldMode; } function update(deltaTime) { @@ -52,7 +52,7 @@ function keyPressEvent(event) { saveCameraState(); freeCamera = true; } - Camera.setMode("independent"); + Camera.mode = "independent"; Camera.setPosition(cameraLocations[choice - 1]); Camera.keepLookingAt(cameraLookAts[choice - 1]); } diff --git a/examples/concertCamera_kyrs.js b/examples/concertCamera_kyrs.js index 2b37a84f9e..4c7c893783 100644 --- a/examples/concertCamera_kyrs.js +++ b/examples/concertCamera_kyrs.js @@ -20,15 +20,15 @@ var cameraLocations = [ {x: 2921.5, y: 251.3, z: 8254.8}, {x: 2921.5, y: 251.3, var cameraLookAts = [ {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.5, y: 251.3, z: 8255.7}, {x: 2921.4 , y: 251.3, z: 8255.1} ]; function saveCameraState() { - oldMode = Camera.getMode(); + oldMode = Camera.mode; avatarPosition = MyAvatar.position; Camera.setModeShiftPeriod(0.0); - Camera.setMode("independent"); + Camera.mode = "independent"; } function restoreCameraState() { Camera.stopLooking(); - Camera.setMode(oldMode); + Camera.mode = oldMode; } function update(deltaTime) { @@ -52,7 +52,7 @@ function keyPressEvent(event) { saveCameraState(); freeCamera = true; } - Camera.setMode("independent"); + Camera.mode = "independent"; Camera.setPosition(cameraLocations[choice - 1]); Camera.keepLookingAt(cameraLookAts[choice - 1]); } diff --git a/examples/editModels.js b/examples/editModels.js index d0e69807aa..d26a6e14b0 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -50,14 +50,14 @@ var SPAWN_DISTANCE = 1; var DEFAULT_DIMENSION = 0.20; var modelURLs = [ - HIFI_PUBLIC_BUCKET + "meshes/Feisar_Ship.FBX", - HIFI_PUBLIC_BUCKET + "meshes/birarda/birarda_head.fbx", - HIFI_PUBLIC_BUCKET + "meshes/pug.fbx", + 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 + "meshes/newInvader16x16-large-purple.svo", - HIFI_PUBLIC_BUCKET + "meshes/minotaur/mino_full.fbx", - HIFI_PUBLIC_BUCKET + "meshes/Combat_tank_V01.FBX", - HIFI_PUBLIC_BUCKET + "meshes/orc.fbx", - HIFI_PUBLIC_BUCKET + "meshes/slimer.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(); @@ -2787,13 +2787,13 @@ function setupModelMenus() { 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 Select Large Models", shortcutKey: "CTRL+META+L", afterItem: "Paste Models", isCheckable: true }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Select Small Models", shortcutKey: "CTRL+META+S", afterItem: "Allow Select Large Models", isCheckable: true }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List", afterItem: "Models" }); 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" }); @@ -2809,6 +2809,7 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", "Delete"); } + Menu.removeMenuItem("Edit", "Model List..."); Menu.removeMenuItem("Edit", "Paste Models"); Menu.removeMenuItem("Edit", "Allow Select Large Models"); Menu.removeMenuItem("Edit", "Allow Select Small Models"); @@ -2883,16 +2884,21 @@ function handeMenuEvent(menuItem) { } else { print(" Delete Entity.... not holding..."); } - } else if (menuItem == "Model List") { + } 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 = decodeURIComponent( - this.properties.modelURL.indexOf("/") != -1 ? - this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) : - this.properties.modelURL); + 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; }; } diff --git a/examples/entityScripts/changeColorOnHover.js b/examples/entityScripts/changeColorOnHover.js new file mode 100644 index 0000000000..de3f5f3784 --- /dev/null +++ b/examples/entityScripts/changeColorOnHover.js @@ -0,0 +1,38 @@ +// +// changeColorOnHover.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/1/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to a non-model entity like a box or sphere, will +// change the color of the entity when you hover over it. This script uses the JavaScript prototype/class functionality +// to construct an object that has methods for hoverEnterEntity and hoverLeaveEntity; +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function(){ + this.oldColor = {}; + this.oldColorKnown = false; + this.storeOldColor = function(entityID) { + var oldProperties = Entities.getEntityProperties(entityID); + this.oldColor = oldProperties.color; + this.oldColorKnown = true; + print("storing old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue); + }; + this.hoverEnterEntity = function(entityID, mouseEvent) { + if (!this.oldColorKnown) { + this.storeOldColor(entityID); + } + Entities.editEntity(entityID, { color: { red: 0, green: 255, blue: 255} }); + }; + this.hoverLeaveEntity = function(entityID, mouseEvent) { + if (this.oldColorKnown) { + print("leave restoring old color... this.oldColor=" + + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue); + Entities.editEntity(entityID, { color: this.oldColor }); + } + }; +}) \ No newline at end of file diff --git a/examples/entityScripts/crazylegsOnClick.js b/examples/entityScripts/crazylegsOnClick.js new file mode 100644 index 0000000000..149c8bf43e --- /dev/null +++ b/examples/entityScripts/crazylegsOnClick.js @@ -0,0 +1,57 @@ +// +// crazylegsOnClick.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, that entity will make your avatar do the +// crazyLegs dance if you click on it. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function(){ + var cumulativeTime = 0.0; + var FREQUENCY = 5.0; + var AMPLITUDE = 45.0; + var jointList = MyAvatar.getJointNames(); + var jointMappings = "\n# Joint list start"; + for (var i = 0; i < jointList.length; i++) { + jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i; + } + print(jointMappings + "\n# Joint list end"); + + this.crazyLegsUpdate = function(deltaTime) { + print("crazyLegsUpdate... deltaTime:" + deltaTime); + cumulativeTime += deltaTime; + print("crazyLegsUpdate... cumulativeTime:" + cumulativeTime); + MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees(AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0)); + MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees(-AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0)); + MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees( + AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0)); + MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees( + AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0)); + }; + + this.stopCrazyLegs = function() { + MyAvatar.clearJointData("RightUpLeg"); + MyAvatar.clearJointData("LeftUpLeg"); + MyAvatar.clearJointData("RightLeg"); + MyAvatar.clearJointData("LeftLeg"); + }; + + this.clickDownOnEntity = function(entityID, mouseEvent) { + print("clickDownOnEntity()..."); + cumulativeTime = 0.0; + Script.update.connect(this.crazyLegsUpdate); + }; + + this.clickReleaseOnEntity = function(entityID, mouseEvent) { + print("clickReleaseOnEntity()..."); + this.stopCrazyLegs(); + Script.update.disconnect(this.crazyLegsUpdate); + }; +}) + diff --git a/examples/entityScripts/playSoundOnClick.js b/examples/entityScripts/playSoundOnClick.js new file mode 100644 index 0000000000..b261bb269a --- /dev/null +++ b/examples/entityScripts/playSoundOnClick.js @@ -0,0 +1,24 @@ +// +// playSoundOnClick.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, that entity will play a sound in world when +// you click on it. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function(){ + var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw"); + this.clickDownOnEntity = function(entityID, mouseEvent) { + print("clickDownOnEntity()..."); + var options = new AudioInjectionOptions(); + var position = MyAvatar.position; + options.position = position; + options.volume = 0.5; + Audio.playSound(bird, options); + }; +}) diff --git a/examples/entityScripts/playSoundOnEnterOrLeave.js b/examples/entityScripts/playSoundOnEnterOrLeave.js new file mode 100644 index 0000000000..228a8a36d0 --- /dev/null +++ b/examples/entityScripts/playSoundOnEnterOrLeave.js @@ -0,0 +1,32 @@ +// +// playSoundOnEnterOrLeave.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, that entity will play a sound in world when +// your avatar enters or leaves the bounds of the entity. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function(){ + var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw"); + + function playSound(entityID) { + var options = new AudioInjectionOptions(); + var position = MyAvatar.position; + options.position = position; + options.volume = 0.5; + Audio.playSound(bird, options); + }; + + this.enterEntity = function(entityID) { + playSound(); + }; + + this.leaveEntity = function(entityID) { + playSound(); + }; +}) diff --git a/examples/entityScripts/teleportOnClick.js b/examples/entityScripts/teleportOnClick.js new file mode 100644 index 0000000000..11677b12d5 --- /dev/null +++ b/examples/entityScripts/teleportOnClick.js @@ -0,0 +1,18 @@ +// +// teleportOnClick.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 11/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example of an entity script which when assigned to an entity, that entity will teleport your avatar if you +// click on it the entity. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function(){ + this.clickDownOnEntity = function(entityID, mouseEvent) { + MyAvatar.position = Entities.getEntityProperties(entityID).position; + }; +}) diff --git a/examples/followThroughOverlappingAction.js b/examples/followThroughOverlappingAction.js new file mode 100644 index 0000000000..fdbd5e5550 --- /dev/null +++ b/examples/followThroughOverlappingAction.js @@ -0,0 +1,986 @@ +// +// follow-through-and-overlapping-action.js +// +// Simple demonstration showing the visual effect of adding the Disney +// follow through and overlapping action animation technique to avatar movement. +// +// Designed and created by David Wooldridge and Ozan Serim, August 2014 +// +// Version 1.001 +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// location of overlay images +var pathToOverlays = "https://s3.amazonaws.com/hifi-public/ArmSwingScript/ArmSwingOverlays/"; + +// animation +var LEFT = 1; +var RIGHT = 2; +var DIRECTION = 0; + +// min max +var weightMin = 1; +var weightMax = 20; +var jointEffectMax = 5; + +// animation effect variables +var handEffect = 3.4; // 0 to jointEffectMax +var forearmEffect = 2.5; // 0 to jointEffectMax +var limbWeight = 8; // will only use nearest integer, as defines an array length +var effectStrength = 1; // 0 to 1 - overall effect strength +// Overshoot: false uses upper arm as driver for forearm and hand +// true uses upper arm for forearm and lower arm as driver for hand. +var overShoot = false; + +// animate self (tap the 'r' key) +var animateSelf = false; +var selfAnimateFrequency = 7.5; + +// overlay values +var handleValue = 0; +var controlPosX = Window.innerWidth / 2 - 500; +var controlPosY = 0; +var minSliderX = controlPosX + 18; +var sliderRangeX = 190; +var minHandleX = controlPosX - 50; +var handleRangeX = 350 / 2; +var handlePosition = 0; + +// background overlay +var controllerBackground = Overlays.addOverlay("image", { + bounds: {x: controlPosX, y: controlPosY, width: 250, height: 380}, + imageURL: pathToOverlays + "flourish-augmentation-control-overlay.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1 + }); +var controllerRadioSelectedBackground = Overlays.addOverlay("image", { + bounds: {x: controlPosX, y: controlPosY, width: 250, height: 380}, + imageURL: pathToOverlays + "flourish-augmentation-control-radio-selected-overlay.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); +// handle overlay +var applyMotionHandle = Overlays.addOverlay("image", { + bounds: {x: minHandleX+handleRangeX-39, y: controlPosY+232, + width: 79, height: 100}, + imageURL: pathToOverlays + "flourish-augmentation-handle-overlay.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1 + }); +// slider overlays +var handEffectSlider = Overlays.addOverlay("image", { + bounds: {x: minSliderX + (handEffect / jointEffectMax * sliderRangeX), + y: controlPosY + 46, width: 25, height: 25}, + imageURL: pathToOverlays + "ddao-slider-handle.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1 + }); +var forearmEffectSlider = Overlays.addOverlay("image", { + bounds: {x: minSliderX + (forearmEffect / jointEffectMax * sliderRangeX), y: controlPosY + 86, + width: 25, height: 25}, + imageURL: pathToOverlays + "ddao-slider-handle.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1 + }); +var limbWeightSlider = Overlays.addOverlay("image", { + bounds: {x: minSliderX + (limbWeight / weightMax * sliderRangeX), y: controlPosY+126, + width: 25, height: 25}, + imageURL: pathToOverlays + "ddao-slider-handle.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1 + }); +var effectStrengthSlider = Overlays.addOverlay("image", { + bounds: {x: minSliderX + (effectStrength * sliderRangeX), y: controlPosY+206, + width: 25, height: 25}, + imageURL: pathToOverlays + "ddao-slider-handle.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +// main loop - using averaging filters to add limb element follow-through +var upperArmPDampingFilter = []; +upperArmPDampingFilter.length = parseInt(limbWeight); // sets amount of damping for upper arm pitch +var forearmPDampingFilter = []; +forearmPDampingFilter.length = parseInt(limbWeight) + 2; // sets amount of damping for lower arm pitch + +var cumulativeTime = 0; + +Script.update.connect(function(deltaTime) { + + // used for self animating (press r to invoke) + cumulativeTime += deltaTime; + + // blend three keyframes using handle position to determine progress between keyframes + var animationProgress = handleValue; + + if(animateSelf) { + animationProgress = Math.sin(cumulativeTime * selfAnimateFrequency); + animationProgress++; + animationProgress /= 2; + } + + var keyframeOneWeight = 0; + var keyframeTwoWeight = 0; + var keyframeThreeWeight = 0; + + if(movingHandle || animateSelf) { + keyframeOneWeight = 0; + keyframeTwoWeight = animationProgress; + keyframeThreeWeight = 1 - animationProgress; + } + else if(!movingHandle) { + // idle + keyframeOneWeight = 1; + keyframeTwoWeight = 0; + keyframeThreeWeight = 0; + } + + var shoulderPitch = + keyframeOneWeight * keyFrameOne.joints[8].pitchOffset + + keyframeTwoWeight * keyFrameTwo.joints[8].pitchOffset + + keyframeThreeWeight * keyFrameThree.joints[8].pitchOffset; + + var upperArmPitch = + keyframeOneWeight * keyFrameOne.joints[9].pitchOffset + + keyframeTwoWeight * keyFrameTwo.joints[9].pitchOffset + + keyframeThreeWeight * keyFrameThree.joints[9].pitchOffset; + + // get the change in upper arm pitch and use to add weight effect to forearm (always) and hand (only for overShoot) + var deltaUpperArmPitch = effectStrength * + (upperArmPDampingFilter[upperArmPDampingFilter.length - 1] - + upperArmPDampingFilter[0]); + + var forearmPitch = + keyframeOneWeight * keyFrameOne.joints[10].pitchOffset + + keyframeTwoWeight * keyFrameTwo.joints[10].pitchOffset + + keyframeThreeWeight * keyFrameThree.joints[10].pitchOffset - + (deltaUpperArmPitch/(jointEffectMax - forearmEffect)); + + // there are two methods for calculating the hand follow through + var handPitch = 0; + if(overShoot) { + + // get the change in forearm pitch and use to add weight effect to hand + var deltaForearmPitch = effectStrength * + (forearmPDampingFilter[forearmPDampingFilter.length - 1] - + forearmPDampingFilter[0]); + handPitch = + keyframeOneWeight * keyFrameOne.joints[11].pitchOffset + + keyframeTwoWeight * keyFrameTwo.joints[11].pitchOffset + + keyframeThreeWeight * keyFrameThree.joints[11].pitchOffset + + (deltaForearmPitch /(jointEffectMax - handEffect)); // hand driven by forearm + + } else { + + handPitch = + keyframeOneWeight * keyFrameOne.joints[11].pitchOffset + + keyframeTwoWeight * keyFrameTwo.joints[11].pitchOffset + + keyframeThreeWeight * keyFrameThree.joints[11].pitchOffset - + (deltaUpperArmPitch /(jointEffectMax - handEffect)); // hand driven by upper arm + } + + var shoulderYaw = + keyframeOneWeight * keyFrameOne.joints[8].yawOffset + + keyframeTwoWeight * keyFrameTwo.joints[8].yawOffset + + keyframeThreeWeight * keyFrameThree.joints[8].yawOffset; + + var upperArmYaw = + keyframeOneWeight * keyFrameOne.joints[9].yawOffset + + keyframeTwoWeight * keyFrameTwo.joints[9].yawOffset + + keyframeThreeWeight * keyFrameThree.joints[9].yawOffset; + + var lowerArmYaw = + keyframeOneWeight * keyFrameOne.joints[10].yawOffset + + keyframeTwoWeight * keyFrameTwo.joints[10].yawOffset + + keyframeThreeWeight * keyFrameThree.joints[10].yawOffset; + + var handYaw = + keyframeOneWeight * keyFrameOne.joints[11].yawOffset + + keyframeTwoWeight * keyFrameTwo.joints[11].yawOffset + + keyframeThreeWeight * keyFrameThree.joints[11].yawOffset; + + var shoulderRoll = + keyframeOneWeight * keyFrameOne.joints[8].rollOffset + + keyframeTwoWeight * keyFrameTwo.joints[8].rollOffset + + keyframeThreeWeight * keyFrameThree.joints[8].rollOffset; + + var upperArmRoll = + keyframeOneWeight * keyFrameOne.joints[9].rollOffset + + keyframeTwoWeight * keyFrameTwo.joints[9].rollOffset + + keyframeThreeWeight * keyFrameThree.joints[9].rollOffset; + + var lowerArmRoll = + keyframeOneWeight * keyFrameOne.joints[10].rollOffset + + keyframeTwoWeight * keyFrameTwo.joints[10].rollOffset + + keyframeThreeWeight * keyFrameThree.joints[10].rollOffset; + + var handRoll = + keyframeOneWeight * keyFrameOne.joints[11].rollOffset + + keyframeTwoWeight * keyFrameTwo.joints[11].rollOffset + + keyframeThreeWeight * keyFrameThree.joints[11].rollOffset; + + // filter upper arm pitch + upperArmPDampingFilter.push(upperArmPitch); + upperArmPDampingFilter.shift(); + var upperArmPitchFiltered = 0; + for(ea in upperArmPDampingFilter) upperArmPitchFiltered += upperArmPDampingFilter[ea]; + upperArmPitchFiltered /= upperArmPDampingFilter.length; + upperArmPitch = (effectStrength * upperArmPitchFiltered) + ((1 - effectStrength) * upperArmPitch); + + // filter forearm pitch only if using for hand follow-though + if(overShoot) { + + forearmPDampingFilter.push(forearmPitch); + forearmPDampingFilter.shift(); + var forearmPitchFiltered = 0; + for(ea in forearmPDampingFilter) forearmPitchFiltered += forearmPDampingFilter[ea]; + forearmPitchFiltered /= forearmPDampingFilter.length; + forearmPitch = (effectStrength*forearmPitchFiltered) + ((1-effectStrength) * forearmPitch); + } + + // apply the new rotation data to the joints + MyAvatar.setJointData("RightShoulder", Quat.fromPitchYawRollDegrees(shoulderPitch, shoulderYaw, shoulderRoll)); + MyAvatar.setJointData("RightArm", Quat.fromPitchYawRollDegrees(upperArmPitch, -upperArmYaw, upperArmRoll)); + MyAvatar.setJointData("RightForeArm", Quat.fromPitchYawRollDegrees(forearmPitch, lowerArmYaw, lowerArmRoll)); + MyAvatar.setJointData("RightHand", Quat.fromPitchYawRollDegrees(handPitch, handYaw, handRoll)); +}); + + +// mouse handling +var movingHandEffectSlider = false; +var movingForearmEffectSlider = false; +var movingLimbWeightSlider = false; +var movingDampingSlider = false; +var movingEffectStrengthSlider = false; +var movingHandle = false; + +function mousePressEvent(event) { + + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + if(clickedOverlay === applyMotionHandle) movingHandle = true; + else if(clickedOverlay === handEffectSlider) movingHandEffectSlider = true; + else if(clickedOverlay === forearmEffectSlider) movingForearmEffectSlider = true; + else if(clickedOverlay === limbWeightSlider) movingLimbWeightSlider = true; + else if(clickedOverlay === effectStrengthSlider) movingEffectStrengthSlider = true; + else if(clickedOverlay === controllerRadioSelectedBackground && + event.x > 477 && event.x < 497 && event.y > 338 && event.y < 360) { + + overShoot = false; + Overlays.editOverlay(controllerBackground, {visible: true}); + Overlays.editOverlay(controllerRadioSelectedBackground, {visible: false}); + } + else if(clickedOverlay === controllerBackground && + event.x > 477 && event.x < 497 && event.y > 338 && event.y < 360){ + + overShoot = true; + Overlays.editOverlay(controllerBackground, {visible: false}); + Overlays.editOverlay(controllerRadioSelectedBackground, {visible: true}); + } +} +function mouseMoveEvent(event) { + + if(movingHandle) { + + var thumbClickOffsetX = event.x - minHandleX; + var thumbPositionNormalised = (thumbClickOffsetX - handleRangeX) / handleRangeX; + if(thumbPositionNormalised <= -1) thumbPositionNormalised = -1; + else if(thumbPositionNormalised > 1) thumbPositionNormalised = 1; + + if(thumbPositionNormalised < 0) DIRECTION = LEFT; + else DIRECTION = RIGHT; + + handleValue = (thumbPositionNormalised + 1) / 2; + var handleX = (thumbPositionNormalised * handleRangeX) + handleRangeX - 39; + Overlays.editOverlay(applyMotionHandle, {x: handleX + minHandleX}); + return; + } + else if(movingHandEffectSlider) { + + var thumbClickOffsetX = event.x - minSliderX; + var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX; + if(thumbPositionNormalised < 0) thumbPositionNormalised = 0; + if(thumbPositionNormalised > 1) thumbPositionNormalised = 1; + handEffect = (thumbPositionNormalised - 0.08) * jointEffectMax; + var sliderX = thumbPositionNormalised * sliderRangeX ; + Overlays.editOverlay(handEffectSlider, {x: sliderX + minSliderX}); + } + else if(movingForearmEffectSlider) { + + var thumbClickOffsetX = event.x - minSliderX; + var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX; + if(thumbPositionNormalised < 0) thumbPositionNormalised = 0; + if(thumbPositionNormalised > 1) thumbPositionNormalised = 1; + forearmEffect = (thumbPositionNormalised - 0.1) * jointEffectMax; + var sliderX = thumbPositionNormalised * sliderRangeX ; + Overlays.editOverlay(forearmEffectSlider, {x: sliderX + minSliderX}); + } + else if(movingLimbWeightSlider) { + + var thumbClickOffsetX = event.x - minSliderX; + var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX; + if(thumbPositionNormalised<0) thumbPositionNormalised = 0; + if(thumbPositionNormalised>1) thumbPositionNormalised = 1; + limbWeight = thumbPositionNormalised * weightMax; + if(limbWeight < weightMin) limbWeight = weightMin; + upperArmPDampingFilter.length = parseInt(limbWeight); + var sliderX = thumbPositionNormalised * sliderRangeX ; + Overlays.editOverlay(limbWeightSlider, {x: sliderX + minSliderX}); + } + else if(movingEffectStrengthSlider) { + + var thumbClickOffsetX = event.x - minSliderX; + var thumbPositionNormalised = thumbClickOffsetX / sliderRangeX; + if(thumbPositionNormalised < 0) thumbPositionNormalised = 0; + if(thumbPositionNormalised > 1) thumbPositionNormalised = 1; + effectStrength = thumbPositionNormalised; + var sliderX = thumbPositionNormalised * sliderRangeX ; + Overlays.editOverlay(effectStrengthSlider, {x: sliderX + minSliderX}); + return; + } +} +function mouseReleaseEvent(event) { + + if(movingHandle) { + + movingHandle = false; + handleValue = 0; + Overlays.editOverlay(applyMotionHandle, {x: minHandleX+handleRangeX - 39}); + } + else if(movingHandEffectSlider) movingHandEffectSlider = false; + else if(movingForearmEffectSlider) movingForearmEffectSlider = false; + else if(movingLimbWeightSlider) movingLimbWeightSlider = false; + else if(movingEffectStrengthSlider) movingEffectStrengthSlider = false; + else if(movingDampingSlider) movingDampingSlider = false; +} + +// set up mouse and keyboard callbacks +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); + +// keyboard command +function keyPressEvent(event) { + + if (event.text == "q") { + print('hand effect = ' + handEffect + '\n'); + print('forearmEffect = ' + forearmEffect + '\n'); + print('limbWeight = ' + limbWeight + '\n'); + print('effectStrength = ' + effectStrength + '\n'); + } + else if (event.text == "r") { + animateSelf = !animateSelf; + } + else if (event.text == "[") { + selfAnimateFrequency += 0.5; + print('selfAnimateFrequency = '+selfAnimateFrequency); + } + else if (event.text == "]") { + selfAnimateFrequency -= 0.5; + print('selfAnimateFrequency = '+selfAnimateFrequency); + } +} + + +// zero out all joints +function resetJoints() { + + var avatarJointNames = MyAvatar.getJointNames(); + for (var i = 0; i < avatarJointNames.length; i++) + MyAvatar.clearJointData(avatarJointNames[i]); +} + +// Script ending +Script.scriptEnding.connect(function() { + + // delete the overlays + Overlays.deleteOverlay(controllerBackground); + Overlays.deleteOverlay(controllerRadioSelectedBackground); + Overlays.deleteOverlay(handEffectSlider); + Overlays.deleteOverlay(forearmEffectSlider); + Overlays.deleteOverlay(limbWeightSlider); + Overlays.deleteOverlay(effectStrengthSlider); + Overlays.deleteOverlay(applyMotionHandle); + + // leave the avi in zeroed out stance + resetJoints(); +}); + + +// animation data. animation keyframes produced using walk.js +MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(80,0,0)); +var keyFrameOne = +{ + "name":"FemaleStandingOne", + "settings":{ + "baseFrequency":70, + "flyingHipsPitch":60, + "takeFlightVelocity":40, + "maxBankingAngle":40 + }, + "adjusters":{ + "legsSeparation":{ + "strength":-0.03679245283018867, + "separationAngle":50 + }, + "stride":{ + "strength":0, + "upperLegsPitch":30, + "lowerLegsPitch":15, + "upperLegsPitchOffset":0.2, + "lowerLegsPitchOffset":1.5 + } + }, + "joints":[ + { + "name":"hips", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0, + "thrust":0, + "bob":0, + "sway":0, + "thrustPhase":180, + "bobPhase":0, + "swayPhase":-90, + "thrustOffset":0, + "bobOffset":0, + "swayOffset":0 + }, + { + "name":"upperLegs", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"lowerLegs", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"feet", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"toes", + "pitch":2.0377358490566038, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":4.415094339622641, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine", + "pitch":1.660377358490566, + "yaw":0, + "roll":0, + "pitchPhase":-180, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine1", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine2", + "pitch":2.1132075471698113, + "yaw":0, + "roll":0, + "pitchPhase":-0.6792452830188722, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"shoulders", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0.6792452830188678, + "yawOffset":-5.20754716981132, + "rollOffset":-2.9433962264150937 + }, + { + "name":"upperArms", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":77.77358490566039, + "yawOffset":9.169811320754715, + "rollOffset":0 + }, + { + "name":"lowerArms", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"hands", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":1.6981132075471694, + "yawOffset":-1.0188679245283017, + "rollOffset":1.0188679245283017 + }, + { + "name":"head", + "pitch":0, + "yaw":1.7358490566037734, + "roll":1.5094339622641508, + "pitchPhase":-90.33962264150944, + "yawPhase":94.41509433962267, + "rollPhase":0, + "pitchOffset":1.6981132075471694, + "yawOffset":0, + "rollOffset":0 + } + ] +}; +var keyFrameTwo = +{ + "name":"FemaleStandingOne", + "settings":{ + "baseFrequency":70, + "flyingHipsPitch":60, + "takeFlightVelocity":40, + "maxBankingAngle":40 + }, + "adjusters":{ + "legsSeparation":{ + "strength":-0.03679245283018867, + "separationAngle":50 + }, + "stride":{ + "strength":0, + "upperLegsPitch":30, + "lowerLegsPitch":15, + "upperLegsPitchOffset":0.2, + "lowerLegsPitchOffset":1.5 + } + }, + "joints":[ + { + "name":"hips", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0, + "thrust":0, + "bob":0, + "sway":0, + "thrustPhase":180, + "bobPhase":0, + "swayPhase":-90, + "thrustOffset":0, + "bobOffset":0, + "swayOffset":0 + }, + { + "name":"upperLegs", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"lowerLegs", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"feet", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"toes", + "pitch":2.0377358490566038, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":4.415094339622641, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine", + "pitch":1.660377358490566, + "yaw":0, + "roll":0, + "pitchPhase":-180, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine1", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine2", + "pitch":2.1132075471698113, + "yaw":0, + "roll":0, + "pitchPhase":-0.6792452830188722, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"shoulders", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0.6792452830188678, + "yawOffset":-5.20754716981132, + "rollOffset":-2.9433962264150937 + }, + { + "name":"upperArms", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":49.584905660377345, + "yawOffset":9.169811320754715, + "rollOffset":0 + }, + { + "name":"lowerArms", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"hands", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":-13, + "yawOffset":-1.0188679245283017, + "rollOffset":1.0188679245283017 + }, + { + "name":"head", + "pitch":0, + "yaw":1.7358490566037734, + "roll":1.5094339622641508, + "pitchPhase":-90.33962264150944, + "yawPhase":94.41509433962267, + "rollPhase":0, + "pitchOffset":1.6981132075471694, + "yawOffset":0, + "rollOffset":0 + } + ] +}; +var keyFrameThree = +{ + "name":"FemaleStandingOne", + "settings":{ + "baseFrequency":70, + "flyingHipsPitch":60, + "takeFlightVelocity":40, + "maxBankingAngle":40 + }, + "adjusters":{ + "legsSeparation":{ + "strength":-0.03679245283018867, + "separationAngle":50 + }, + "stride":{ + "strength":0, + "upperLegsPitch":30, + "lowerLegsPitch":15, + "upperLegsPitchOffset":0.2, + "lowerLegsPitchOffset":1.5 + } + }, + "joints":[ + { + "name":"hips", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0, + "thrust":0, + "bob":0, + "sway":0, + "thrustPhase":180, + "bobPhase":0, + "swayPhase":-90, + "thrustOffset":0, + "bobOffset":0, + "swayOffset":0 + }, + { + "name":"upperLegs", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"lowerLegs", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"feet", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"toes", + "pitch":2.0377358490566038, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":4.415094339622641, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine", + "pitch":1.660377358490566, + "yaw":0, + "roll":0, + "pitchPhase":-180, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine1", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"spine2", + "pitch":2.1132075471698113, + "yaw":0, + "roll":0, + "pitchPhase":-0.6792452830188722, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"shoulders", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":-21.0566037735849, + "yawOffset":-5.20754716981132, + "rollOffset":-2.9433962264150937 + }, + { + "name":"upperArms", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":-33.28301886792452, + "yawOffset":9.169811320754715, + "rollOffset":0 + }, + { + "name":"lowerArms", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":0, + "yawOffset":0, + "rollOffset":0 + }, + { + "name":"hands", + "pitch":0, + "yaw":0, + "roll":0, + "pitchPhase":0, + "yawPhase":0, + "rollPhase":0, + "pitchOffset":-13, + "yawOffset":-1.0188679245283017, + "rollOffset":1.0188679245283017 + }, + { + "name":"head", + "pitch":0, + "yaw":1.7358490566037734, + "roll":1.5094339622641508, + "pitchPhase":-90.33962264150944, + "yawPhase":94.41509433962267, + "rollPhase":0, + "pitchOffset":1.6981132075471694, + "yawOffset":0, + "rollOffset":0 + } + ] +}; \ No newline at end of file diff --git a/examples/headMove.js b/examples/headMove.js index 2c49847864..b1f1c4ab7d 100644 --- a/examples/headMove.js +++ b/examples/headMove.js @@ -53,12 +53,12 @@ var lastYawTurned = 0.0; var startPullbackPosition; function saveCameraState() { - oldMode = Camera.getMode(); - Camera.setMode("independent"); + oldMode = Camera.mode; + Camera.mode = "independent"; } function restoreCameraState() { - Camera.setMode(oldMode); + Camera.mode = oldMode; } function activateWarp() { diff --git a/examples/inspect.js b/examples/inspect.js index b9ed5a3f00..49ebc86de1 100644 --- a/examples/inspect.js +++ b/examples/inspect.js @@ -17,13 +17,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var PI = 3.14 // No need for something more precise +var PI = Math.PI; +var RAD_TO_DEG = 180.0 / PI; var AZIMUTH_RATE = 90.0; var ALTITUDE_RATE = 200.0; var RADIUS_RATE = 1.0 / 100.0; var PAN_RATE = 50.0; +var Y_AXIS = { x: 0, y: 1, z: 0 }; +var X_AXIS = { x: 1, y: 0, z: 0 }; + var alt = false; var shift = false; var control = false; @@ -53,6 +57,18 @@ var avatarPosition; var avatarOrientation; +function orientationOf(vector) { + var direction, + yaw, + pitch; + + direction = Vec3.normalize(vector); + yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS); + pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS); + return Quat.multiply(yaw, pitch); +} + + function handleRadialMode(dx, dy) { azimuth += dx / AZIMUTH_RATE; radius += radius * dy * RADIUS_RATE; @@ -65,6 +81,7 @@ function handleRadialMode(dx, dy) { z: (Math.cos(altitude) * Math.sin(azimuth)) * radius }; position = Vec3.sum(center, vector); Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } function handleOrbitMode(dx, dy) { @@ -82,6 +99,7 @@ function handleOrbitMode(dx, dy) { z:(Math.cos(altitude) * Math.sin(azimuth)) * radius }; position = Vec3.sum(center, vector); Camera.setPosition(position); + Camera.setOrientation(orientationOf(vector)); } @@ -96,19 +114,18 @@ function handlePanMode(dx, dy) { position = Vec3.sum(position, dv); Camera.setPosition(position); - Camera.keepLookingAt(center); + Camera.setOrientation(orientationOf(vector)); } function saveCameraState() { - oldMode = Camera.getMode(); + oldMode = Camera.mode; var oldPosition = Camera.getPosition(); - Camera.setMode("independent"); + Camera.mode = "independent"; Camera.setPosition(oldPosition); } function restoreCameraState() { - Camera.stopLooking(); - Camera.setMode(oldMode); + Camera.mode = oldMode; } function handleModes() { @@ -245,7 +262,6 @@ function mousePressEvent(event) { azimuth = Math.atan2(vector.z, vector.x); altitude = Math.asin(vector.y / Vec3.length(vector)); - Camera.keepLookingAt(center); print(string); isActive = true; } diff --git a/examples/leapHands.js b/examples/leapHands.js index c9c3af0063..437637dc3f 100644 --- a/examples/leapHands.js +++ b/examples/leapHands.js @@ -16,7 +16,7 @@ var leapHands = (function () { var isOnHMD, LEAP_ON_HMD_MENU_ITEM = "Leap Motion on HMD", LEAP_OFFSET = 0.019, // Thickness of Leap Motion plus HMD clip - HMD_OFFSET = 0.100, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief + HMD_OFFSET = 0.070, // Eyeballs to front surface of Oculus DK2 TODO: Confirm and make depend on device and eye relief hands, wrists, NUM_HANDS = 2, // 0 = left; 1 = right diff --git a/examples/libraries/entityCameraTool.js b/examples/libraries/entityCameraTool.js index 88d0935e72..3cd2b8a97e 100644 --- a/examples/libraries/entityCameraTool.js +++ b/examples/libraries/entityCameraTool.js @@ -34,6 +34,13 @@ var EASING_MULTIPLIER = 8; var INITIAL_ZOOM_DISTANCE = 2; var INITIAL_ZOOM_DISTANCE_FIRST_PERSON = 3; +var easeOutCubic = function(t) { + t--; + return t * t * t + 1; +}; + +EASE_TIME = 0.5; + CameraManager = function() { var that = {}; @@ -51,6 +58,10 @@ CameraManager = function() { that.focalPoint = { x: 0, y: 0, z: 0 }; that.targetFocalPoint = { x: 0, y: 0, z: 0 }; + easing = false; + easingTime = 0; + startOrientation = Quat.fromPitchYawRollDegrees(0, 0, 0); + that.previousCameraMode = null; that.lastMousePosition = { x: 0, y: 0 }; @@ -66,10 +77,6 @@ CameraManager = function() { var focalPoint = Vec3.sum(Camera.getPosition(), Vec3.multiply(that.zoomDistance, Quat.getFront(Camera.getOrientation()))); - if (Camera.getMode() == 'first person') { - that.targetZoomDistance = INITIAL_ZOOM_DISTANCE_FIRST_PERSON; - } - // Determine the correct yaw and pitch to keep the camera in the same location var dPos = Vec3.subtract(focalPoint, Camera.getPosition()); var xzDist = Math.sqrt(dPos.x * dPos.x + dPos.z * dPos.z); @@ -81,12 +88,12 @@ CameraManager = function() { that.focalPoint = focalPoint; that.setFocalPoint(focalPoint); - that.previousCameraMode = Camera.getMode(); - Camera.setMode("independent"); + that.previousCameraMode = Camera.mode; + Camera.mode = "independent"; that.updateCamera(); - cameraTool.setVisible(true); + cameraTool.setVisible(false); } that.disable = function(ignoreCamera) { @@ -95,18 +102,40 @@ CameraManager = function() { that.mode = MODE_INACTIVE; if (!ignoreCamera) { - Camera.setMode(that.previousCameraMode); + Camera.mode = that.previousCameraMode; } cameraTool.setVisible(false); } - that.focus = function(entityProperties) { - var dim = SelectionManager.worldDimensions; - var size = Math.max(dim.x, Math.max(dim.y, dim.z)); + that.focus = function(position, dimensions, easeOrientation) { + if (dimensions) { + var size = Math.max(dimensions.x, Math.max(dimensions.y, dimensions.z)); + that.targetZoomDistance = Math.max(size * FOCUS_ZOOM_SCALE, FOCUS_MIN_ZOOM); + } else { + that.targetZoomDistance = Vec3.length(Vec3.subtract(Camera.getPosition(), position)); + } - that.targetZoomDistance = Math.max(size * FOCUS_ZOOM_SCALE, FOCUS_MIN_ZOOM); + if (easeOrientation) { + // Do eased turning towards target + that.focalPoint = that.targetFocalPoint = position; - that.setFocalPoint(SelectionManager.worldPosition); + that.zoomDistance = that.targetZoomDistance = Vec3.length(Vec3.subtract(Camera.getPosition(), position)); + + var dPos = Vec3.subtract(that.focalPoint, Camera.getPosition()); + var xzDist = Math.sqrt(dPos.x * dPos.x + dPos.z * dPos.z); + + that.targetPitch = -Math.atan2(dPos.y, xzDist) * 180 / Math.PI; + that.targetYaw = Math.atan2(dPos.x, dPos.z) * 180 / Math.PI; + that.pitch = that.targetPitch; + that.yaw = that.targetYaw; + + startOrientation = Camera.getOrientation(); + + easing = true; + easingTime = 0; + } else { + that.setFocalPoint(position); + } that.updateCamera(); } @@ -242,7 +271,7 @@ CameraManager = function() { } that.updateCamera = function() { - if (!that.enabled || Camera.getMode() != "independent") return; + if (!that.enabled || Camera.mode != "independent") return; var yRot = Quat.angleAxis(that.yaw, { x: 0, y: 1, z: 0 }); var xRot = Quat.angleAxis(that.pitch, { x: 1, y: 0, z: 0 }); @@ -255,6 +284,11 @@ CameraManager = function() { xRot = Quat.angleAxis(-that.pitch, { x: 1, y: 0, z: 0 }); q = Quat.multiply(yRot, xRot); + if (easing) { + var t = easeOutCubic(easingTime / EASE_TIME); + q = Quat.slerp(startOrientation, q, t); + } + Camera.setOrientation(q); } @@ -266,10 +300,14 @@ CameraManager = function() { // Ease the position and orbit of the camera that.update = function(dt) { - if (Camera.getMode() != "independent") { + if (Camera.mode != "independent") { return; } + if (easing) { + easingTime = Math.min(EASE_TIME, easingTime + dt); + } + var scale = Math.min(dt * EASING_MULTIPLIER, 1.0); var dYaw = that.targetYaw - that.yaw; @@ -292,12 +330,15 @@ CameraManager = function() { that.zoomDistance += scale * dZoom; that.updateCamera(); + + if (easingTime >= 1) { + easing = false; + } } // Last mode that was first or third person var lastAvatarCameraMode = "first person"; Camera.modeUpdated.connect(function(newMode) { - print("Camera mode has been updated: " + newMode); if (newMode == "first person" || newMode == "third person") { lastAvatarCameraMode = newMode; that.disable(true); @@ -308,7 +349,7 @@ CameraManager = function() { Controller.keyReleaseEvent.connect(function (event) { if (event.text == "ESC" && that.enabled) { - Camera.setMode(lastAvatarCameraMode); + Camera.mode = lastAvatarCameraMode; cameraManager.disable(true); } }); diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index 2c4bf5329e..01e0c76e0d 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -140,6 +140,9 @@ EntityPropertyDialogBox = (function () { array.push({ label: "Visible:", value: properties.visible }); index++; + + array.push({ label: "Script:", value: properties.script }); + index++; if (properties.type == "Box" || properties.type == "Sphere") { array.push({ label: "Color:", type: "header" }); @@ -282,6 +285,7 @@ EntityPropertyDialogBox = (function () { properties.lifetime = array[index++].value; properties.visible = array[index++].value; + properties.script = array[index++].value; if (properties.type == "Box" || properties.type == "Sphere") { index++; // skip header diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index a325491dd3..29bf1bdd79 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -209,12 +209,6 @@ SelectionDisplay = (function () { var lastCameraOrientation = Camera.getOrientation(); var lastPlaneIntersection; - var currentSelection = { id: -1, isKnownID: false }; - var entitySelected = false; - var selectedEntityProperties; - var selectedEntityPropertiesOriginalPosition; - var selectedEntityPropertiesOriginalDimensions; - var handleHoverColor = { red: 224, green: 67, blue: 36 }; var handleHoverAlpha = 1.0; @@ -656,20 +650,12 @@ SelectionDisplay = (function () { }; that.select = function(entityID, event) { - var properties = Entities.getEntityProperties(entityID); - // if (currentSelection.isKnownID == true) { - // that.unselect(currentSelection); - // } - currentSelection = entityID; - entitySelected = true; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - // lastCameraPosition = Camera.getPosition(); + lastCameraPosition = Camera.getPosition(); lastCameraOrientation = Camera.getOrientation(); if (event !== false) { - selectedEntityProperties = properties; - selectedEntityPropertiesOriginalPosition = properties.position; - selectedEntityPropertiesOriginalDimensions = properties.dimensions; pickRay = Camera.computePickRay(event.x, event.y); lastPlaneIntersection = rayPlaneIntersection(pickRay, properties.position, Quat.getFront(lastCameraOrientation)); @@ -678,8 +664,6 @@ SelectionDisplay = (function () { print("select() with EVENT...... "); print(" event.y:" + event.y); Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); - Vec3.print(" originalDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" current position:", properties.position); } @@ -939,11 +923,6 @@ SelectionDisplay = (function () { }; that.unselectAll = function () { - if (currentSelection.isKnownID == true) { - that.unselect(currentSelection); - } - currentSelection = { id: -1, isKnownID: false }; - entitySelected = false; }; that.updateHandles = function() { @@ -952,7 +931,9 @@ SelectionDisplay = (function () { return; } + that.updateRotationHandles(); + that.highlightSelectable(); var rotation, dimensions, position; @@ -1113,12 +1094,6 @@ SelectionDisplay = (function () { }; that.unselect = function (entityID) { - that.setOverlaysVisible(false); - - Entities.editEntity(entityID, { localRenderAlpha: 1.0 }); - - currentSelection = { id: -1, isKnownID: false }; - entitySelected = false; }; var initialXZPick = null; @@ -1207,15 +1182,16 @@ SelectionDisplay = (function () { } } - tooltip.updateText(selectedEntityProperties); - that.select(currentSelection, false); // TODO: this should be more than highlighted SelectionManager._update(); } }; + var lastXYPick = null addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", onBegin: function(event) { + lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, Quat.getFront(lastCameraOrientation)); + SelectionManager.saveProperties(); // Duplicate entities if alt is pressed. This will make a @@ -1243,7 +1219,7 @@ SelectionDisplay = (function () { // translate mode left/right based on view toward entity var newIntersection = rayPlaneIntersection(pickRay, - selectedEntityPropertiesOriginalPosition, + SelectionManager.worldPosition, Quat.getFront(lastCameraOrientation)); var vector = Vec3.subtract(newIntersection, lastPlaneIntersection); @@ -1253,8 +1229,6 @@ SelectionDisplay = (function () { vector.x = 0; vector.z = 0; - // newPosition = Vec3.sum(selectedEntityPropertiesOriginalPosition, vector); - var wantDebug = false; if (wantDebug) { print("translateUpDown... "); @@ -1262,8 +1236,6 @@ SelectionDisplay = (function () { Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); - Vec3.print(" recentPosition:", selectedEntityProperties.position); Vec3.print(" newPosition:", newPosition); } for (var i = 0; i < SelectionManager.selections.length; i++) { @@ -1273,8 +1245,6 @@ SelectionDisplay = (function () { Entities.editEntity(SelectionManager.selections[i], properties); } - tooltip.updateText(selectedEntityProperties); - that.select(currentSelection, false); // TODO: this should be more than highlighted SelectionManager._update(); }, }); @@ -1306,7 +1276,7 @@ SelectionDisplay = (function () { var rotation = null; var onBegin = function(event) { - var properties = Entities.getEntityProperties(currentSelection); + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); @@ -1445,11 +1415,11 @@ SelectionDisplay = (function () { var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(p, changeInDimensions)); var newPosition = Vec3.sum(initialPosition, changeInPosition); - - selectedEntityProperties.position = newPosition; - selectedEntityProperties.dimensions = newDimensions; for (var i = 0; i < SelectionManager.selections.length; i++) { - Entities.editEntity(SelectionManager.selections[i], selectedEntityProperties); + Entities.editEntity(SelectionManager.selections[i], { + position: newPosition, + dimensions: newDimensions, + }); } var wantDebug = false; @@ -1460,18 +1430,14 @@ SelectionDisplay = (function () { Vec3.print(" vector:", vector); Vec3.print(" oldPOS:", oldPOS); Vec3.print(" newPOS:", newPOS); - Vec3.print(" oldDimensions:", selectedEntityPropertiesOriginalDimensions); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); - Vec3.print(" oldPosition:", selectedEntityPropertiesOriginalPosition); Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } - tooltip.updateText(selectedEntityProperties); SelectionManager._update(); - that.select(currentSelection, false); // TODO: this should be more than highlighted }; @@ -1590,7 +1556,6 @@ SelectionDisplay = (function () { } if (result.intersects) { - var properties = Entities.getEntityProperties(currentSelection); var center = yawCenter; var zero = yawZero; var centerToZero = Vec3.subtract(center, zero); @@ -1664,8 +1629,8 @@ SelectionDisplay = (function () { // Size the overlays to the current selection size var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - var innerRadius = diagonal; - var outerRadius = diagonal * 1.15; + innerRadius = diagonal; + outerRadius = diagonal * 1.15; var innerAlpha = 0.2; var outerAlpha = 0.2; Overlays.editOverlay(rotateOverlayInner, @@ -1721,7 +1686,7 @@ SelectionDisplay = (function () { } if (result.intersects) { - var properties = Entities.getEntityProperties(currentSelection); + var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = pitchCenter; var zero = pitchZero; var centerToZero = Vec3.subtract(center, zero); @@ -1794,8 +1759,8 @@ SelectionDisplay = (function () { // Size the overlays to the current selection size var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - var innerRadius = diagonal; - var outerRadius = diagonal * 1.15; + innerRadius = diagonal; + outerRadius = diagonal * 1.15; var innerAlpha = 0.2; var outerAlpha = 0.2; Overlays.editOverlay(rotateOverlayInner, @@ -1851,7 +1816,7 @@ SelectionDisplay = (function () { } if (result.intersects) { - var properties = Entities.getEntityProperties(currentSelection); + var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = rollCenter; var zero = rollZero; var centerToZero = Vec3.subtract(center, zero); @@ -1915,9 +1880,9 @@ SelectionDisplay = (function () { }); that.checkMove = function() { - if (currentSelection.isKnownID && + if (SelectionManager.hasSelection() && (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation))){ - that.select(currentSelection, false, false); + that.select(selectionManager.selections[0], false, false); } }; @@ -2033,7 +1998,7 @@ SelectionDisplay = (function () { var overlayOrientation; var overlayCenter; - var properties = Entities.getEntityProperties(currentSelection); + var properties = Entities.getEntityProperties(selectionManager.selections[0]); var angles = Quat.safeEulerAngles(properties.rotation); var pitch = angles.x; var yaw = angles.y; @@ -2169,12 +2134,11 @@ SelectionDisplay = (function () { if (somethingClicked) { pickRay = Camera.computePickRay(event.x, event.y); - lastPlaneIntersection = rayPlaneIntersection(pickRay, selectedEntityPropertiesOriginalPosition, + lastPlaneIntersection = rayPlaneIntersection(pickRay, selectionManager.worldPosition, Quat.getFront(lastCameraOrientation)); if (wantDebug) { print("mousePressEvent()...... " + overlayNames[result.overlayID]); Vec3.print(" lastPlaneIntersection:", lastPlaneIntersection); - Vec3.print(" originalPosition:", selectedEntityPropertiesOriginalPosition); } } @@ -2289,8 +2253,8 @@ SelectionDisplay = (function () { }; that.updateHandleSizes = function() { - if (selectedEntityProperties) { - var diff = Vec3.subtract(selectedEntityProperties.position, Camera.getPosition()); + if (selectionManager.hasSelection()) { + var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO; for (var i = 0; i < stretchHandles.length; i++) { Overlays.editOverlay(stretchHandles[i], { @@ -2336,15 +2300,10 @@ SelectionDisplay = (function () { mode = "UNKNOWN"; // if something is selected, then reset the "original" properties for any potential next click+move operation - if (entitySelected) { - + if (SelectionManager.hasSelection()) { if (showHandles) { - that.select(currentSelection, event); + that.select(SelectionManager.selections[0], event); } - - selectedEntityProperties = Entities.getEntityProperties(currentSelection); - selectedEntityPropertiesOriginalPosition = properties.position; - selectedEntityPropertiesOriginalDimensions = properties.dimensions; } }; diff --git a/examples/lobby.js b/examples/lobby.js index 4642d13387..63ea1654a9 100644 --- a/examples/lobby.js +++ b/examples/lobby.js @@ -40,20 +40,18 @@ var ORB_SHIFT = { x: 0, y: -1.4, z: -0.8}; var HELMET_ATTACHMENT_URL = "https://hifi-public.s3.amazonaws.com/models/attachments/IronManMaskOnly.fbx" function reticlePosition() { - var screenSize = Controller.getViewportDimensions(); - var reticleRay = Camera.computePickRay(screenSize.x / 2, screenSize.y / 2); var RETICLE_DISTANCE = 1; - return Vec3.sum(reticleRay.origin, Vec3.multiply(reticleRay.direction, RETICLE_DISTANCE)); + return Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), RETICLE_DISTANCE)); } function drawLobby() { if (!panelWall) { print("Adding overlays for the lobby panel wall and orb shell."); - var cameraEuler = Quat.safeEulerAngles(Camera.getOrientation()); + var cameraEuler = Quat.safeEulerAngles(Camera.orientation); var towardsMe = Quat.angleAxis(cameraEuler.y + 180, { x: 0, y: 1, z: 0}); - var orbPosition = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(towardsMe, ORB_SHIFT)); + var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT)); var panelWallProps = { url: HIFI_PUBLIC_BUCKET + "models/sets/Lobby/LobbyPrototype/Lobby5_PanelsWithFrames.fbx", @@ -76,14 +74,15 @@ function drawLobby() { orbShell = Overlays.addOverlay("model", orbShellProps); // for HMD wearers, create a reticle in center of screen - var RETICLE_SPHERE_SIZE = 0.025; + var CURSOR_SCALE = 0.025; - reticle = Overlays.addOverlay("sphere", { + reticle = Overlays.addOverlay("billboard", { + url: HIFI_PUBLIC_BUCKET + "images/cursor.svg", position: reticlePosition(), - size: RETICLE_SPHERE_SIZE, - color: { red: 0, green: 255, blue: 0 }, + ignoreRayIntersection: true, + isFacingAvatar: true, alpha: 1.0, - solid: true + scale: CURSOR_SCALE }); // add an attachment on this avatar so other people see them in the lobby @@ -130,12 +129,13 @@ function cleanupLobby() { function actionStartEvent(event) { if (panelWall) { + // we've got an action event and our panel wall is up // check if we hit a panel and if we should jump there - var pickRay = Camera.computePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); + var result = Overlays.findRayIntersection(event.actionRay); if (result.intersects && result.overlayID == panelWall) { + var panelName = result.extraInfo; var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { @@ -174,7 +174,7 @@ function maybeCleanupLobby() { function toggleEnvironmentRendering(shouldRender) { Menu.setIsOptionChecked("Voxels", shouldRender); - Menu.setIsOptionChecked("Models", shouldRender); + Menu.setIsOptionChecked("Entities", shouldRender); Menu.setIsOptionChecked("Metavoxels", shouldRender); Menu.setIsOptionChecked("Avatars", shouldRender); } diff --git a/examples/lookAtExample.js b/examples/lookAtExample.js index 1cf8aabb96..729281fa03 100644 --- a/examples/lookAtExample.js +++ b/examples/lookAtExample.js @@ -17,13 +17,13 @@ // var lookingAtSomething = false; -var oldMode = Camera.getMode(); +var oldMode = Camera.mode; function cancelLookAt() { if (lookingAtSomething) { lookingAtSomething = false; Camera.stopLooking(); - Camera.setMode(oldMode); + Camera.mode = oldMode; releaseMovementKeys(); } } @@ -65,13 +65,13 @@ function mousePressEvent(event) { if (intersection.intersects) { // remember the old mode we were in - oldMode = Camera.getMode(); + oldMode = Camera.mode; print("looking at intersection point: " + intersection.intersection.x + ", " + intersection.intersection.y + ", " + intersection.intersection.z); // switch to independent mode - Camera.setMode("independent"); + Camera.mode = "independent"; // tell the camera to fix it's look at on the point we clicked Camera.keepLookingAt(intersection.intersection); diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index 8d19a350a2..57f3f29670 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -51,15 +51,21 @@ var wantEntityGlow = false; var SPAWN_DISTANCE = 1; var DEFAULT_DIMENSION = 0.20; +var MENU_INSPECT_TOOL_ENABLED = "Inspect Tool"; +var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; + +var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled"; +var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; + var modelURLs = [ - HIFI_PUBLIC_BUCKET + "meshes/Feisar_Ship.FBX", - HIFI_PUBLIC_BUCKET + "meshes/birarda/birarda_head.fbx", - HIFI_PUBLIC_BUCKET + "meshes/pug.fbx", + 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 + "meshes/newInvader16x16-large-purple.svo", - HIFI_PUBLIC_BUCKET + "meshes/minotaur/mino_full.fbx", - HIFI_PUBLIC_BUCKET + "meshes/Combat_tank_V01.FBX", - HIFI_PUBLIC_BUCKET + "meshes/orc.fbx", - HIFI_PUBLIC_BUCKET + "meshes/slimer.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 mode = 0; @@ -256,7 +262,7 @@ var toolBar = (function () { if (activeButton === toolBar.clicked(clickedOverlay)) { isActive = !isActive; if (!isActive) { - selectionDisplay.unselectAll(); + selectionManager.clearSelections(); cameraManager.disable(); } else { cameraManager.enable(); @@ -386,10 +392,7 @@ function isLocked(properties) { } -var entitySelected = false; var selectedEntityID; -var selectedEntityProperties; -var mouseLastPosition; var orientation; var intersection; @@ -403,160 +406,158 @@ function rayPlaneIntersection(pickRay, point, normal) { return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); } +function findClickedEntity(event) { + var pickRay = Camera.computePickRay(event.x, event.y); + + var foundIntersection = Entities.findRayIntersection(pickRay); + + if (!foundIntersection.accurate) { + return null; + } + var foundEntity = foundIntersection.entityID; + + if (!foundEntity.isKnownID) { + var identify = Entities.identifyEntity(foundEntity); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")"); + selectionManager.clearSelections(); + return null; + } + foundEntity = identify; + } + + return { pickRay: pickRay, entityID: foundEntity }; +} + function mousePressEvent(event) { - mouseLastPosition = { x: event.x, y: event.y }; - var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); - - if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event) - || cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { - // Event handled; do nothing. + if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { return; - } else { - entitySelected = false; - selectionDisplay.unselectAll(); - - // If we aren't active and didn't click on an overlay: quit - if (!isActive) { + } + if (isActive) { + var entitySelected = false; + if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { + // Event handled; do nothing. return; - } - - var pickRay = Camera.computePickRay(event.x, event.y); - Vec3.print("[Mouse] Looking at: ", pickRay.origin); - var foundIntersection = Entities.findRayIntersection(pickRay); - - 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 + ")"); + } else { + var result = findClickedEntity(event); + if (result === null) { + selectionManager.clearSelections(); return; } - foundEntity = identify; - } + var pickRay = result.pickRay; + var foundEntity = result.entityID; - var properties = Entities.getEntityProperties(foundEntity); - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + 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| + 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 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 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 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); + 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 (0 < x && sizeOK) { + entitySelected = true; + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - if (!event.isShifted) { - selectionManager.clearSelections(); + if (!event.isShifted) { + selectionManager.clearSelections(); + } + selectionManager.addEntity(foundEntity); + + print("Model selected: " + foundEntity.id); } - selectionManager.addEntity(foundEntity); - - print("Model selected selectedEntityID:" + selectedEntityID.id); - } } - } - 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); - selectionDisplay.select(selectedEntityID, event); + if (entitySelected) { + selectionDisplay.select(selectedEntityID, event); + } + } else if (Menu.isOptionChecked(MENU_INSPECT_TOOL_ENABLED)) { + var result = findClickedEntity(event); + if (event.isRightButton) { + if (result !== null) { + var currentProperties = Entities.getEntityProperties(result.entityID); + cameraManager.enable(); + cameraManager.focus(currentProperties.position, null, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + cameraManager.mousePressEvent(event); + } + } else { + cameraManager.mousePressEvent(event); + } } } var highlightedEntityID = { isKnownID: false }; function mouseMoveEvent(event) { - if (!isActive) { - return; - } - - // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing - if (selectionDisplay.mouseMoveEvent(event) || cameraManager.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 }; + if (isActive) { + // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing + if (selectionDisplay.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { + return; } - 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 }); + 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 }; } - highlightedEntityID = entityIntersection.entityID; - selectionDisplay.highlightSelectable(entityIntersection.entityID); - } + 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); + } + + } + } else { + cameraManager.mouseMoveEvent(event); } } function mouseReleaseEvent(event) { - if (!isActive) { - return; - } - if (entitySelected) { + if (isActive && selectionManager.hasSelection()) { tooltip.show(false); } + cameraManager.mouseReleaseEvent(event); } @@ -584,6 +585,7 @@ function setupModelMenus() { 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 Select Large Models", shortcutKey: "CTRL+META+L", afterItem: "Paste Models", isCheckable: true }); @@ -594,6 +596,11 @@ function setupModelMenus() { 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" }); Menu.addMenuItem({ menuName: "Developer", menuItemName: "Debug Ryans Rotation Problems", isCheckable: true }); + + Menu.addMenuItem({ menuName: "View", menuItemName: MENU_INSPECT_TOOL_ENABLED, afterItem: "Edit Entities Help...", + isCheckable: true, isChecked: Settings.getValue(SETTING_INSPECT_TOOL_ENABLED) == "true" }); + Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_INSPECT_TOOL_ENABLED, + isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" }); } setupModelMenus(); // do this when first running our script. @@ -606,6 +613,7 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", "Delete"); } + Menu.removeMenuItem("Edit", "Model List..."); Menu.removeMenuItem("Edit", "Paste Models"); Menu.removeMenuItem("Edit", "Allow Select Large Models"); Menu.removeMenuItem("Edit", "Allow Select Small Models"); @@ -614,9 +622,15 @@ function cleanupModelMenus() { Menu.removeMenuItem("File", "Export Models"); Menu.removeMenuItem("File", "Import Models"); Menu.removeMenuItem("Developer", "Debug Ryans Rotation Problems"); + + Menu.removeMenuItem("View", MENU_INSPECT_TOOL_ENABLED); + Menu.removeMenuItem("View", MENU_EASE_ON_FOCUS); } Script.scriptEnding.connect(function() { + Settings.setValue(SETTING_INSPECT_TOOL_ENABLED, Menu.isOptionChecked(MENU_INSPECT_TOOL_ENABLED)); + Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + progressDialog.cleanup(); toolBar.cleanup(); cleanupModelMenus(); @@ -642,7 +656,7 @@ function handeMenuEvent(menuItem) { allowLargeModels = Menu.isOptionChecked("Allow Select Large Models"); } else if (menuItem == "Delete") { if (SelectionManager.hasSelection()) { - print(" Delete Entity.... selectedEntityID="+ selectedEntityID); + print(" Delete Entities"); SelectionManager.saveProperties(); var savedProperties = []; for (var i = 0; i < selectionManager.selections.length; i++) { @@ -657,11 +671,41 @@ function handeMenuEvent(menuItem) { } SelectionManager.clearSelections(); pushCommandForSelections([], savedProperties); - selectionDisplay.unselect(selectedEntityID); - entitySelected = 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...") { // good place to put the properties dialog @@ -706,10 +750,10 @@ Controller.keyReleaseEvent.connect(function (event) { } else if (event.text == "TAB") { selectionDisplay.toggleSpaceMode(); } else if (event.text == "f") { - if (entitySelected) { - // Get latest properties - var properties = Entities.getEntityProperties(selectedEntityID); - cameraManager.focus(properties); + if (isActive) { + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); } } else if (event.text == '[') { if (isActive) { @@ -788,7 +832,6 @@ function applyEntityProperties(data) { var properties = data.createEntities[i].properties; var newEntityID = Entities.addEntity(properties); DELETED_ENTITY_MAP[entityID.id] = newEntityID; - print(newEntityID.isKnownID); if (data.selectCreated) { selectedEntityIDs.push(newEntityID); } diff --git a/interface/resources/html/edit-entities-commands.html b/interface/resources/html/edit-entities-commands.html new file mode 100644 index 0000000000..afa662f089 --- /dev/null +++ b/interface/resources/html/edit-entities-commands.html @@ -0,0 +1,2036 @@ + +
+ + + + + +Edit Entity Help + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/interface/resources/shaders/ambient_occlusion.frag b/interface/resources/shaders/ambient_occlusion.frag index 621b123966..512922ca43 100644 --- a/interface/resources/shaders/ambient_occlusion.frag +++ b/interface/resources/shaders/ambient_occlusion.frag @@ -14,9 +14,6 @@ // the depth texture uniform sampler2D depthTexture; -// the normal texture -uniform sampler2D normalTexture; - // the random rotation texture uniform sampler2D rotationTexture; @@ -60,11 +57,10 @@ vec3 texCoordToViewSpace(vec2 texCoord) { } void main(void) { - vec3 rotationZ = texture2D(normalTexture, gl_TexCoord[0].st).xyz * 2.0 - vec3(1.0, 1.0, 1.0); - vec3 rotationY = normalize(cross(rotationZ, texture2D(rotationTexture, - gl_TexCoord[0].st * noiseScale).xyz - vec3(0.5, 0.5, 0.5))); - mat3 rotation = mat3(cross(rotationY, rotationZ), rotationY, rotationZ); - + vec3 rotationX = texture2D(rotationTexture, gl_TexCoord[0].st * noiseScale).rgb; + vec3 rotationY = normalize(cross(rotationX, vec3(0.0, 0.0, 1.0))); + mat3 rotation = mat3(rotationX, rotationY, cross(rotationX, rotationY)); + vec3 center = texCoordToViewSpace(gl_TexCoord[0].st); vec2 rdenominator = 1.0 / (rightTop - leftBottom); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index da46086a02..1209774806 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -107,8 +107,8 @@ static unsigned STARFIELD_SEED = 1; static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored -const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff - // in the idle loop? (60 FPS is default) +const unsigned MAXIMUM_CACHE_SIZE = 10737418240; // 10GB + static QTimer* idleTimer = NULL; const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml"; @@ -148,7 +148,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _voxelImporter(), _importSucceded(false), _sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard), - _entityClipboardRenderer(), + _entities(true), + _entityCollisionSystem(), + _entityClipboardRenderer(false), _entityClipboard(), _wantToKillLocalVoxels(false), _viewFrustum(), @@ -182,9 +184,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _trayIcon(new QSystemTrayIcon(_window)), _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()), - _renderTargetFramerate(0), - _isVSyncOn(true), - _renderResolutionScale(1.0f) + _isVSyncOn(true) { // read the ApplicationInfo.ini file for Name/Version/Domain information @@ -347,9 +347,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : billboardPacketTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkDiskCache* cache = new QNetworkDiskCache(); + cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); networkAccessManager.setCache(cache); @@ -585,7 +585,7 @@ void Application::initializeGL() { // update before the first render update(1.f / _fps); - InfoView::showFirstTime(); + InfoView::showFirstTime(INFO_HELP_PATH); } void Application::paintGL() { @@ -601,7 +601,7 @@ void Application::paintGL() { if (OculusManager::isConnected()) { _textureCache.setFrameBufferSize(OculusManager::getRenderTargetSize()); } else { - QSize fbSize = _glWidget->getDeviceSize() * _renderResolutionScale; + QSize fbSize = _glWidget->getDeviceSize() * getRenderResolutionScale(); _textureCache.setFrameBufferSize(fbSize); } @@ -1116,7 +1116,8 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Space: { if (!event->isAutoRepeat()) { // this starts an HFActionEvent - HFActionEvent startActionEvent(HFActionEvent::startType(), getViewportCenter()); + HFActionEvent startActionEvent(HFActionEvent::startType(), + _viewFrustum.computePickRay(0.5f, 0.5f)); sendEvent(this, &startActionEvent); } @@ -1207,7 +1208,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { case Qt::Key_Space: { if (!event->isAutoRepeat()) { // this ends the HFActionEvent - HFActionEvent endActionEvent(HFActionEvent::endType(), getViewportCenter()); + HFActionEvent endActionEvent(HFActionEvent::endType(), _viewFrustum.computePickRay(0.5f, 0.5f)); sendEvent(this, &endActionEvent); } @@ -1248,6 +1249,8 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { if (_lastMouseMoveWasSimulated) { showMouse = false; } + + _entities.mouseMoveEvent(event, deviceID); _controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts @@ -1269,6 +1272,9 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { } void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { + + _entities.mousePressEvent(event, deviceID); + _controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -1296,7 +1302,8 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { } // nobody handled this - make it an action event on the _window object - HFActionEvent actionEvent(HFActionEvent::startType(), event->localPos()); + HFActionEvent actionEvent(HFActionEvent::startType(), + _myCamera.computePickRay(event->x(), event->y())); sendEvent(this, &actionEvent); } else if (event->button() == Qt::RightButton) { @@ -1306,6 +1313,9 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { } void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { + + _entities.mouseReleaseEvent(event, deviceID); + _controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it @@ -1327,7 +1337,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { } // fire an action end event - HFActionEvent actionEvent(HFActionEvent::endType(), event->localPos()); + HFActionEvent actionEvent(HFActionEvent::endType(), + _myCamera.computePickRay(event->x(), event->y())); sendEvent(this, &actionEvent); } } @@ -1471,12 +1482,11 @@ void Application::idle() { bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "idle()"); - // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran + // Only run simulation code if more than the targetFramePeriod have passed since last time we ran double targetFramePeriod = 0.0; - if (_renderTargetFramerate > 0) { - targetFramePeriod = 1000.0 / _renderTargetFramerate; - } else if (_renderTargetFramerate < 0) { - targetFramePeriod = IDLE_SIMULATE_MSECS; + unsigned int targetFramerate = getRenderTargetFramerate(); + if (targetFramerate > 0) { + targetFramePeriod = 1000.0 / targetFramerate; } double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0; if (timeSinceLastUpdate > targetFramePeriod) { @@ -1558,12 +1568,9 @@ void Application::setEnableVRMode(bool enableVRMode) { OculusManager::disconnect(); OculusManager::connect(); } - int oculusMaxFPS = Menu::getInstance()->getOculusUIMaxFPS(); - setRenderTargetFramerate(oculusMaxFPS); OculusManager::recalibrate(); } else { OculusManager::abandonCalibration(); - setRenderTargetFramerate(0); } resizeGL(_glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); @@ -1943,6 +1950,10 @@ void Application::init() { connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity, ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity); + // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing + // of events related clicking, hovering over, and entering entities + _entities.connectSignalsToSlots(ScriptEngine::getEntityScriptingInterface()); + _entityClipboardRenderer.init(); _entityClipboardRenderer.setViewFrustum(getViewFrustum()); _entityClipboardRenderer.setTree(&_entityClipboard); @@ -2338,7 +2349,7 @@ void Application::update(float deltaTime) { if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions); } - if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) { queryOctree(NodeType::EntityServer, PacketTypeEntityQuery, _entityServerJurisdictions); } _lastQueriedViewFrustum = _viewFrustum; @@ -2985,7 +2996,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } // render models... - if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) { PerformanceTimer perfTimer("entities"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... entities..."); @@ -3179,7 +3190,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { } else { // if not rendering the billboard, the region is in device independent coordinates; must convert to device QSize size = getTextureCache()->getFrameBufferSize(); - float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * _renderResolutionScale; + float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale(); int x = region.x() * ratio, y = region.y() * ratio, width = region.width() * ratio, height = region.height() * ratio; glViewport(x, size.height() - y - height, width, height); glScissor(x, size.height() - y - height, width, height); @@ -3822,35 +3833,7 @@ void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) { out = qobject_cast(object.toQObject()); } -ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded, - bool loadScriptFromEditor, bool activateMainWindow) { - QUrl scriptUrl(scriptFilename); - const QString& scriptURLString = scriptUrl.toString(); - if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor - && !_scriptEnginesHash[scriptURLString]->isFinished()) { - - return _scriptEnginesHash[scriptURLString]; - } - - ScriptEngine* scriptEngine; - if (scriptFilename.isNull()) { - scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); - } else { - // start the script on a new thread... - scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); - - if (!scriptEngine->hasScript()) { - qDebug() << "Application::loadScript(), script failed to load..."; - QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load."); - return NULL; - } - - _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine); - _runningScriptsWidget->setRunningScripts(getRunningScripts()); - UserActivityLogger::getInstance().loadedScript(scriptURLString); - } - scriptEngine->setUserLoaded(isUserLoaded); - +void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) { // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender); @@ -3866,9 +3849,7 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features scriptEngine->setAvatarHashMap(&_avatarManager, "AvatarList"); - CameraScriptableObject* cameraScriptable = new CameraScriptableObject(&_myCamera, &_viewFrustum); - scriptEngine->registerGlobalObject("Camera", cameraScriptable); - connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater())); + scriptEngine->registerGlobalObject("Camera", &_myCamera); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) scriptEngine->registerGlobalObject("SpeechRecognizer", Menu::getInstance()->getSpeechRecognizer()); @@ -3932,6 +3913,38 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser // Starts an event loop, and emits workerThread->started() workerThread->start(); +} + +ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded, + bool loadScriptFromEditor, bool activateMainWindow) { + QUrl scriptUrl(scriptFilename); + const QString& scriptURLString = scriptUrl.toString(); + if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor + && !_scriptEnginesHash[scriptURLString]->isFinished()) { + + return _scriptEnginesHash[scriptURLString]; + } + + ScriptEngine* scriptEngine; + if (scriptFilename.isNull()) { + scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); + } else { + // start the script on a new thread... + scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); + + if (!scriptEngine->hasScript()) { + qDebug() << "Application::loadScript(), script failed to load..."; + QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load."); + return NULL; + } + + _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + UserActivityLogger::getInstance().loadedScript(scriptURLString); + } + scriptEngine->setUserLoaded(isUserLoaded); + + registerScriptEngineWithApplicationServices(scriptEngine); // restore the main window's active state if (activateMainWindow && !loadScriptFromEditor) { @@ -4237,37 +4250,56 @@ void Application::takeSnapshot() { _snapshotShareDialog->show(); } -void Application::setRenderTargetFramerate(unsigned int framerate, bool vsyncOn) { - if (vsyncOn != _isVSyncOn) { +void Application::setVSyncEnabled(bool vsyncOn) { #if defined(Q_OS_WIN) - if (wglewGetExtension("WGL_EXT_swap_control")) { - wglSwapIntervalEXT(vsyncOn); - int swapInterval = wglGetSwapIntervalEXT(); - _isVSyncOn = swapInterval; - qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } else { - qDebug("V-Sync is FORCED ON on this system\n"); - } -#elif defined(Q_OS_LINUX) - // TODO: write the poper code for linux - /* - if (glQueryExtension.... ("GLX_EXT_swap_control")) { - glxSwapIntervalEXT(vsyncOn); - int swapInterval = xglGetSwapIntervalEXT(); - _isVSyncOn = swapInterval; - qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); - } else { + if (wglewGetExtension("WGL_EXT_swap_control")) { + wglSwapIntervalEXT(vsyncOn); + int swapInterval = wglGetSwapIntervalEXT(); + qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + } else { qDebug("V-Sync is FORCED ON on this system\n"); - } - */ -#else - qDebug("V-Sync is FORCED ON on this system\n"); -#endif } - _renderTargetFramerate = framerate; +#elif defined(Q_OS_LINUX) + // TODO: write the poper code for linux + /* + if (glQueryExtension.... ("GLX_EXT_swap_control")) { + glxSwapIntervalEXT(vsyncOn); + int swapInterval = xglGetSwapIntervalEXT(); + _isVSyncOn = swapInterval; + qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + } else { + qDebug("V-Sync is FORCED ON on this system\n"); + } + */ +#else + qDebug("V-Sync is FORCED ON on this system\n"); +#endif } -bool Application::isVSyncEditable() { +bool Application::isVSyncOn() const { +#if defined(Q_OS_WIN) + if (wglewGetExtension("WGL_EXT_swap_control")) { + int swapInterval = wglGetSwapIntervalEXT(); + return (swapInterval > 0); + } else { + return true; + } +#elif defined(Q_OS_LINUX) + // TODO: write the poper code for linux + /* + if (glQueryExtension.... ("GLX_EXT_swap_control")) { + int swapInterval = xglGetSwapIntervalEXT(); + return (swapInterval > 0); + } else { + return true; + } + */ +#else + return true; +#endif +} + +bool Application::isVSyncEditable() const { #if defined(Q_OS_WIN) if (wglewGetExtension("WGL_EXT_swap_control")) { return true; @@ -4284,6 +4316,33 @@ bool Application::isVSyncEditable() { return false; } -void Application::setRenderResolutionScale(float scale) { - _renderResolutionScale = scale; +unsigned int Application::getRenderTargetFramerate() const { + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateUnlimited)) { + return 0; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) { + return 60; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) { + return 50; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) { + return 40; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) { + return 30; + } + return 0; +} + +float Application::getRenderResolutionScale() const { + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) { + return 1.f; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) { + return 0.666f; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) { + return 0.5f; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) { + return 0.333f; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) { + return 0.25f; + } else { + return 1.f; + } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 8cd321a6b3..d92333058f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -128,6 +128,9 @@ static const float MIRROR_FIELD_OF_VIEW = 30.0f; static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND; +static const QString INFO_HELP_PATH = "html/interface-welcome-allsvg.html"; +static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-entities-commands.html"; + class Application : public QApplication { Q_OBJECT @@ -284,8 +287,6 @@ public: PointShader& getPointShader() { return _pointShader; } FileLogger* getLogger() { return _logger; } - QPointF getViewportCenter() const - { return QPointF(_glWidget->getDeviceWidth() / 2.0f, _glWidget->getDeviceHeight() / 2.0f); } glm::vec2 getViewportDimensions() const { return glm::vec2(_glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); } NodeToJurisdictionMap& getVoxelServerJurisdictions() { return _voxelServerJurisdictions; } NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } @@ -300,7 +301,14 @@ public: bool isLookingAtMyAvatar(Avatar* avatar); - float getRenderResolutionScale() const { return _renderResolutionScale; } + float getRenderResolutionScale() const; + + unsigned int getRenderTargetFramerate() const; + bool isVSyncOn() const; + bool isVSyncEditable() const; + + + void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine); signals: @@ -364,12 +372,7 @@ public slots: void domainSettingsReceived(const QJsonObject& domainSettingsObject); - void setRenderTargetFramerate(unsigned int framerate, bool vsyncOn = true); - bool isVSyncOn() { return _isVSyncOn; } - bool isVSyncEditable(); - unsigned int getRenderTargetFramerate() const { return _renderTargetFramerate; } - - void setRenderResolutionScale(float scale); + void setVSyncEnabled(bool vsyncOn); void resetSensors(); @@ -621,9 +624,7 @@ private: quint64 _lastNackTime; quint64 _lastSendDownstreamAudioStats; - int _renderTargetFramerate; bool _isVSyncOn; - float _renderResolutionScale; }; #endif // hifi_Application_h diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index ceb4cb09a0..121833bd16 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -72,9 +72,9 @@ float Camera::getFarClip() const { : std::numeric_limits::max() - 1; } -void Camera::setMode(CameraMode m) { - _mode = m; - emit modeUpdated(m); +void Camera::setMode(CameraMode mode) { + _mode = mode; + emit modeUpdated(modeToString(mode)); } @@ -94,57 +94,45 @@ void Camera::setFarClip(float f) { _farClip = f; } -CameraScriptableObject::CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum) : - _camera(camera), _viewFrustum(viewFrustum) -{ - connect(_camera, &Camera::modeUpdated, this, &CameraScriptableObject::onModeUpdated); -} - -PickRay CameraScriptableObject::computePickRay(float x, float y) { +PickRay Camera::computePickRay(float x, float y) { float screenWidth = Application::getInstance()->getGLWidget()->width(); float screenHeight = Application::getInstance()->getGLWidget()->height(); PickRay result; if (OculusManager::isConnected()) { - result.origin = _camera->getPosition(); + result.origin = getPosition(); Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction); } else { - _viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction); + Application::getInstance()->getViewFrustum()->computePickRay(x / screenWidth, y / screenHeight, + result.origin, result.direction); } return result; } -QString CameraScriptableObject::getMode() const { - return modeToString(_camera->getMode()); -} - -void CameraScriptableObject::setMode(const QString& mode) { - CameraMode currentMode = _camera->getMode(); - CameraMode targetMode = currentMode; - if (mode == "third person") { - targetMode = CAMERA_MODE_THIRD_PERSON; - Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); - } else if (mode == "first person") { - targetMode = CAMERA_MODE_FIRST_PERSON; - Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true); - } else if (mode == "mirror") { - targetMode = CAMERA_MODE_MIRROR; - Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); - } else if (mode == "independent") { - targetMode = CAMERA_MODE_INDEPENDENT; - Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); +void Camera::setModeString(const QString& mode) { + CameraMode targetMode = stringToMode(mode); + + switch (targetMode) { + case CAMERA_MODE_THIRD_PERSON: + Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); + break; + case CAMERA_MODE_MIRROR: + Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); + break; + case CAMERA_MODE_INDEPENDENT: + Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); + break; + default: + break; } - if (currentMode != targetMode) { - _camera->setMode(targetMode); + + if (_mode != targetMode) { + setMode(targetMode); } } -void CameraScriptableObject::onModeUpdated(CameraMode m) { - emit modeUpdated(modeToString(m)); +QString Camera::getModeString() const { + return modeToString(_mode); } - - - diff --git a/interface/src/Camera.h b/interface/src/Camera.h index e876f70e3a..bc7a3c5687 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -32,6 +32,10 @@ static int cameraModeId = qRegisterMetaType(); class Camera : public QObject { Q_OBJECT + + Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) + Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) + Q_PROPERTY(QString mode READ getModeString WRITE setModeString) public: Camera(); @@ -39,7 +43,6 @@ public: void update( float deltaTime ); - void setPosition(const glm::vec3& p) { _position = p; } void setRotation(const glm::quat& rotation) { _rotation = rotation; }; void setHmdPosition(const glm::vec3& hmdPosition) { _hmdPosition = hmdPosition; } void setHmdRotation(const glm::quat& hmdRotation) { _hmdRotation = hmdRotation; }; @@ -66,12 +69,20 @@ public: const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; } const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation; } float getScale() const { return _scale; } +public slots: + QString getModeString() const; + void setModeString(const QString& mode); -signals: - void modeUpdated(CameraMode newMode); + void setPosition(const glm::vec3& position) { _position = position; } -private: + void setOrientation(const glm::quat& orientation) { setRotation(orientation); } + glm::quat getOrientation() const { return getRotation(); } + + PickRay computePickRay(float x, float y); +signals: + void modeUpdated(const QString& newMode); +private: CameraMode _mode; glm::vec3 _position; float _fieldOfView; // degrees @@ -88,32 +99,4 @@ private: float _scale; }; - -class CameraScriptableObject : public QObject { - Q_OBJECT -public: - CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum); - -public slots: - QString getMode() const; - void setMode(const QString& mode); - void setPosition(const glm::vec3& value) { _camera->setPosition(value);} - - glm::vec3 getPosition() const { return _camera->getPosition(); } - - void setOrientation(const glm::quat& value) { _camera->setRotation(value); } - glm::quat getOrientation() const { return _camera->getRotation(); } - - PickRay computePickRay(float x, float y); - -signals: - void modeUpdated(const QString& newMode); - -private slots: - void onModeUpdated(CameraMode m); - -private: - Camera* _camera; - ViewFrustum* _viewFrustum; -}; #endif // hifi_Camera_h diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index 4808842036..505cb6a029 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -41,7 +41,7 @@ void FileLogger::addMessage(QString message) { QFile file(_fileName); if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { QTextStream out(&file); - out << message; + out << message << "\n"; } } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6eb8145dd3..21c1cded88 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -105,7 +105,6 @@ Menu::Menu() : _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), _oculusUIAngularSize(DEFAULT_OCULUS_UI_ANGULAR_SIZE), - _oculusUIMaxFPS(DEFAULT_OCULUS_UI_MAX_FPS), _sixenseReticleMoveSpeed(DEFAULT_SIXENSE_RETICLE_MOVE_SPEED), _invertSixenseButtons(DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS), _automaticAvatarLOD(true), @@ -359,6 +358,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails())); addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, this, SLOT(octreeStatsDetails())); + addActionToQMenuAndActionHash(viewMenu, MenuOption::EditEntitiesHelp, 0, this, SLOT(showEditEntitiesHelp())); QMenu* developerMenu = addMenu("Developer"); @@ -366,7 +366,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Avatars, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Entities, 0, true); QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows"); QActionGroup* shadowGroup = new QActionGroup(shadowMenu); @@ -377,13 +377,12 @@ Menu::Menu() : { QMenu* framerateMenu = renderOptionsMenu->addMenu(MenuOption::RenderTargetFramerate); QActionGroup* framerateGroup = new QActionGroup(framerateMenu); - + framerateGroup->setExclusive(true); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerateUnlimited, 0, true)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate60, 0, false)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate50, 0, false)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate40, 0, false)); framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate30, 0, false)); - connect(framerateMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderTargetFramerate(QAction*))); #if defined(Q_OS_MAC) #else @@ -394,12 +393,12 @@ Menu::Menu() : QMenu* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu); - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, false)); + resolutionGroup->setExclusive(true); + resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, true)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionTwoThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, false)); - resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, true)); + resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); - connect(resolutionMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderResolution(QAction*))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, @@ -433,14 +432,16 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); - QMenu* modelDebugMenu = developerMenu->addMenu("Models"); - addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false); - addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false); - addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false); - QMenu* modelCullingMenu = modelDebugMenu->addMenu("Culling"); - addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false); - addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false); - addCheckableActionToQMenuAndActionHash(modelCullingMenu, MenuOption::DontReduceMaterialSwitches, 0, false); + QMenu* entitiesDebugMenu = developerMenu->addMenu("Entities"); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelBounds, 0, false); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelElementProxy, 0, false); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisableLightEntities, 0, false); + + QMenu* entityCullingMenu = entitiesDebugMenu->addMenu("Culling"); + addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false); + addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false); + addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontReduceMaterialSwitches, 0, false); QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels"); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); @@ -783,8 +784,6 @@ void Menu::loadSettings(QSettings* settings) { settings->endGroup(); _walletPrivateKey = settings->value("privateKey").toByteArray(); - - _oculusUIMaxFPS = loadSetting(settings, "oculusUIMaxFPS", 0.0f); scanMenuBar(&loadAction, settings); Application::getInstance()->getAvatar()->loadData(settings); @@ -846,9 +845,6 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("viewFrustumOffsetUp", _viewFrustumOffset.up); settings->endGroup(); settings->setValue("privateKey", _walletPrivateKey); - - // Oculus Rift settings - settings->setValue("oculusUIMaxFPS", _oculusUIMaxFPS); scanMenuBar(&saveAction, settings); Application::getInstance()->getAvatar()->saveData(settings); @@ -1117,7 +1113,11 @@ QAction* Menu::getActionForOption(const QString& menuOption) { } void Menu::aboutApp() { - InfoView::forcedShow(); + InfoView::forcedShow(INFO_HELP_PATH); +} + +void Menu::showEditEntitiesHelp() { + InfoView::forcedShow(INFO_EDIT_ENTITIES_PATH); } void Menu::bumpSettings() { @@ -1260,46 +1260,7 @@ void Menu::muteEnvironment() { } void Menu::changeVSync() { - Application::getInstance()->setRenderTargetFramerate( - Application::getInstance()->getRenderTargetFramerate(), - isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn)); -} -void Menu::changeRenderTargetFramerate(QAction* action) { - bool vsynOn = Application::getInstance()->isVSyncOn(); - - QString text = action->text(); - if (text == MenuOption::RenderTargetFramerateUnlimited) { - Application::getInstance()->setRenderTargetFramerate(0, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate60) { - Application::getInstance()->setRenderTargetFramerate(60, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate50) { - Application::getInstance()->setRenderTargetFramerate(50, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate40) { - Application::getInstance()->setRenderTargetFramerate(40, vsynOn); - } - else if (text == MenuOption::RenderTargetFramerate30) { - Application::getInstance()->setRenderTargetFramerate(30, vsynOn); - } -} - -void Menu::changeRenderResolution(QAction* action) { - QString text = action->text(); - if (text == MenuOption::RenderResolutionOne) { - Application::getInstance()->setRenderResolutionScale(1.f); - } else if (text == MenuOption::RenderResolutionTwoThird) { - Application::getInstance()->setRenderResolutionScale(0.666f); - } else if (text == MenuOption::RenderResolutionHalf) { - Application::getInstance()->setRenderResolutionScale(0.5f); - } else if (text == MenuOption::RenderResolutionThird) { - Application::getInstance()->setRenderResolutionScale(0.333f); - } else if (text == MenuOption::RenderResolutionQuarter) { - Application::getInstance()->setRenderResolutionScale(0.25f); - } else { - Application::getInstance()->setRenderResolutionScale(1.f); - } + Application::getInstance()->setVSyncEnabled(isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn)); } void Menu::displayNameLocationResponse(const QString& errorString) { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fcb01f9e9d..8af91ccae4 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -100,8 +100,6 @@ public: void setRealWorldFieldOfView(float realWorldFieldOfView) { _realWorldFieldOfView = realWorldFieldOfView; bumpSettings(); } float getOculusUIAngularSize() const { return _oculusUIAngularSize; } void setOculusUIAngularSize(float oculusUIAngularSize) { _oculusUIAngularSize = oculusUIAngularSize; bumpSettings(); } - int getOculusUIMaxFPS() const { return _oculusUIMaxFPS; } - void setOculusUIMaxFPS(int oculusUIMaxFPS) { _oculusUIMaxFPS = oculusUIMaxFPS; bumpSettings(); } float getSixenseReticleMoveSpeed() const { return _sixenseReticleMoveSpeed; } void setSixenseReticleMoveSpeed(float sixenseReticleMoveSpeed) { _sixenseReticleMoveSpeed = sixenseReticleMoveSpeed; bumpSettings(); } bool getInvertSixenseButtons() const { return _invertSixenseButtons; } @@ -208,6 +206,7 @@ public slots: private slots: void aboutApp(); + void showEditEntitiesHelp(); void bumpSettings(); void editPreferences(); void editAttachments(); @@ -231,9 +230,7 @@ private slots: void displayAddressOfflineMessage(); void displayAddressNotFoundMessage(); void muteEnvironment(); - void changeRenderTargetFramerate(QAction* action); void changeVSync(); - void changeRenderResolution(QAction* action); private: static Menu* _instance; @@ -294,7 +291,6 @@ private: int _maxVoxels; float _voxelSizeScale; float _oculusUIAngularSize; - int _oculusUIMaxFPS; float _sixenseReticleMoveSpeed; bool _invertSixenseButtons; bool _automaticAvatarLOD; @@ -386,6 +382,7 @@ namespace MenuOption { const QString DecreaseVoxelSize = "Decrease Voxel Size"; const QString DisableActivityLogger = "Disable Activity Logger"; const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; + const QString DisableLightEntities = "Disable Light Entities"; const QString DisableNackPackets = "Disable NACK Packets"; const QString DisplayFrustum = "Display Frustum"; const QString DisplayHands = "Show Hand Info"; @@ -398,9 +395,11 @@ namespace MenuOption { const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes"; const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; + const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)"; const QString EnableVRMode = "Enable VR Mode"; + const QString Entities = "Entities"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; const QString ExpandMyAvatarTiming = "Expand /myAvatar"; const QString ExpandOtherAvatarTiming = "Expand /otherAvatar"; @@ -431,8 +430,6 @@ namespace MenuOption { const QString MetavoxelEditor = "Metavoxel Editor..."; const QString Metavoxels = "Metavoxels"; const QString Mirror = "Mirror"; - const QString ModelOptions = "Model Options"; - const QString Models = "Models"; const QString MoveWithLean = "Move with Lean"; const QString MuteAudio = "Mute Microphone"; const QString MuteEnvironment = "Mute Environment"; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 20e05e779e..4c0acc553a 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -961,6 +961,7 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, _scale(scale), _heightBounds(translation, translation + glm::vec3(scale, scale, scale)), _colorBounds(_heightBounds), + _materialBounds(_heightBounds), _height(height), _color(color), _material(material), @@ -968,10 +969,12 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, _heightTextureID(0), _colorTextureID(0), _materialTextureID(0), - _heightSize(glm::sqrt(float(height.size()))), + _heightSize(glm::sqrt((float)height.size())), _heightIncrement(scale / (_heightSize - HEIGHT_EXTENSION)), - _colorSize(glm::sqrt(float(color.size() / DataBlock::COLOR_BYTES))), - _colorIncrement(scale / (_colorSize - SHARED_EDGE)) { + _colorSize(glm::sqrt((float)color.size() / DataBlock::COLOR_BYTES)), + _colorIncrement(scale / (_colorSize - SHARED_EDGE)), + _materialSize(glm::sqrt((float)material.size())), + _materialIncrement(scale / (_materialSize - SHARED_EDGE)) { _heightBounds.minimum.x -= _heightIncrement * HEIGHT_BORDER; _heightBounds.minimum.z -= _heightIncrement * HEIGHT_BORDER; @@ -980,6 +983,9 @@ HeightfieldBuffer::HeightfieldBuffer(const glm::vec3& translation, float scale, _colorBounds.maximum.x += _colorIncrement * SHARED_EDGE; _colorBounds.maximum.z += _colorIncrement * SHARED_EDGE; + + _materialBounds.maximum.x += _materialIncrement * SHARED_EDGE; + _materialBounds.maximum.z += _materialIncrement * SHARED_EDGE; } HeightfieldBuffer::~HeightfieldBuffer() { @@ -1006,16 +1012,14 @@ QByteArray HeightfieldBuffer::getUnextendedHeight() const { return unextended; } -QByteArray HeightfieldBuffer::getUnextendedColor() const { - int srcSize = glm::sqrt(float(_color.size() / DataBlock::COLOR_BYTES)); - int destSize = srcSize - 1; - QByteArray unextended(destSize * destSize * DataBlock::COLOR_BYTES, 0); - const char* src = _color.constData(); - int srcStride = srcSize * DataBlock::COLOR_BYTES; +QByteArray HeightfieldBuffer::getUnextendedColor(int x, int y) const { + int unextendedSize = _heightSize - HEIGHT_EXTENSION; + QByteArray unextended(unextendedSize * unextendedSize * DataBlock::COLOR_BYTES, 0); char* dest = unextended.data(); - int destStride = destSize * DataBlock::COLOR_BYTES; - for (int z = 0; z < destSize; z++, src += srcStride, dest += destStride) { - memcpy(dest, src, destStride); + const char* src = _color.constData() + (y * _colorSize + x) * unextendedSize * DataBlock::COLOR_BYTES; + for (int z = 0; z < unextendedSize; z++, dest += unextendedSize * DataBlock::COLOR_BYTES, + src += _colorSize * DataBlock::COLOR_BYTES) { + memcpy(dest, src, unextendedSize * DataBlock::COLOR_BYTES); } return unextended; } @@ -1702,134 +1706,246 @@ public: HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections); - void init(HeightfieldBuffer* buffer) { _buffer = buffer; } + void init(HeightfieldBuffer* buffer); virtual int visit(MetavoxelInfo& info); + virtual bool postVisit(MetavoxelInfo& info); private: const QVector& _intersections; HeightfieldBuffer* _buffer; + + QVector _depthFlags; }; +enum DepthFlags { HEIGHT_FLAG = 0x01, COLOR_FLAG = 0x02, MATERIAL_FLAG = 0x04 }; + HeightfieldFetchVisitor::HeightfieldFetchVisitor(const MetavoxelLOD& lod, const QVector& intersections) : MetavoxelVisitor(QVector() << AttributeRegistry::getInstance()->getHeightfieldAttribute() << - AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), QVector(), lod), + AttributeRegistry::getInstance()->getHeightfieldColorAttribute() << + AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), QVector(), lod), _intersections(intersections) { } +void HeightfieldFetchVisitor::init(HeightfieldBuffer* buffer) { + _buffer = buffer; +} + int HeightfieldFetchVisitor::visit(MetavoxelInfo& info) { - Box bounds = info.getBounds(); - const Box& heightBounds = _buffer->getHeightBounds(); - if (!bounds.intersects(heightBounds)) { + if (!info.getBounds().intersects(_buffer->getHeightBounds())) { return STOP_RECURSION; } - if (!info.isLeaf && info.size > _buffer->getScale()) { + if (_depthFlags.size() > _depth) { + _depthFlags[_depth] = 0; + } else { + _depthFlags.append(0); + } + if (!info.isLeaf) { return DEFAULT_ORDER; } + postVisit(info); + return STOP_RECURSION; +} + +bool HeightfieldFetchVisitor::postVisit(MetavoxelInfo& info) { HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue(); - if (!height) { - return STOP_RECURSION; + int flags = _depthFlags.at(_depth); + if (height) { + // to handle borders correctly, make sure we only sample nodes with resolution <= ours + int heightSize = glm::sqrt((float)height->getContents().size()); + float heightIncrement = info.size / heightSize; + if (heightIncrement < _buffer->getHeightIncrement() || (flags & HEIGHT_FLAG)) { + height.reset(); + } else { + flags |= HEIGHT_FLAG; + } } + HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); + if (color) { + int colorSize = glm::sqrt((float)color->getContents().size() / DataBlock::COLOR_BYTES); + float colorIncrement = info.size / colorSize; + if (colorIncrement < _buffer->getColorIncrement() || (flags & COLOR_FLAG)) { + color.reset(); + } else { + flags |= COLOR_FLAG; + } + } + HeightfieldMaterialDataPointer material = info.inputValues.at(2).getInlineValue(); + if (material) { + int materialSize = glm::sqrt((float)material->getContents().size()); + float materialIncrement = info.size / materialSize; + if (materialIncrement < _buffer->getMaterialIncrement() || (flags & MATERIAL_FLAG)) { + material.reset(); + } else { + flags |= MATERIAL_FLAG; + } + } + if (_depth > 0) { + _depthFlags[_depth - 1] |= flags; + } + if (!(height || color || material)) { + return false; + } + Box bounds = info.getBounds(); foreach (const Box& intersection, _intersections) { Box overlap = intersection.getIntersection(bounds); if (overlap.isEmpty()) { continue; } - float heightIncrement = _buffer->getHeightIncrement(); - int destX = (overlap.minimum.x - heightBounds.minimum.x) / heightIncrement; - int destY = (overlap.minimum.z - heightBounds.minimum.z) / heightIncrement; - int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / heightIncrement); - int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / heightIncrement); - int heightSize = _buffer->getHeightSize(); - char* dest = _buffer->getHeight().data() + destY * heightSize + destX; - - const QByteArray& srcHeight = height->getContents(); - int srcSize = glm::sqrt(float(srcHeight.size())); - float srcIncrement = info.size / srcSize; - - if (info.size == _buffer->getScale() && srcSize == (heightSize - HeightfieldBuffer::HEIGHT_EXTENSION)) { - // easy case: same resolution - int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; - int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + if (height) { + float heightIncrement = _buffer->getHeightIncrement(); + const Box& heightBounds = _buffer->getHeightBounds(); + int destX = (overlap.minimum.x - heightBounds.minimum.x) / heightIncrement; + int destY = (overlap.minimum.z - heightBounds.minimum.z) / heightIncrement; + int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / heightIncrement); + int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / heightIncrement); + int heightSize = _buffer->getHeightSize(); + char* dest = _buffer->getHeight().data() + destY * heightSize + destX; - const char* src = srcHeight.constData() + srcY * srcSize + srcX; - for (int y = 0; y < destHeight; y++, src += srcSize, dest += heightSize) { - memcpy(dest, src, destWidth); - } - } else { - // more difficult: different resolutions - float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; - float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; - float srcAdvance = heightIncrement / srcIncrement; - int shift = 0; - float size = _buffer->getScale(); - while (size < info.size) { - shift++; - size *= 2.0f; - } - int subtract = (_buffer->getTranslation().y - info.minimum.y) * EIGHT_BIT_MAXIMUM / _buffer->getScale(); - for (int y = 0; y < destHeight; y++, dest += heightSize, srcY += srcAdvance) { - const uchar* src = (const uchar*)srcHeight.constData() + (int)srcY * srcSize; - float lineSrcX = srcX; - for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) { - *lineDest = qMin(qMax(0, (src[(int)lineSrcX] << shift) - subtract), EIGHT_BIT_MAXIMUM); + const QByteArray& srcHeight = height->getContents(); + int srcSize = glm::sqrt((float)srcHeight.size()); + float srcIncrement = info.size / srcSize; + + if (info.size == _buffer->getScale() && srcSize == (heightSize - HeightfieldBuffer::HEIGHT_EXTENSION)) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const char* src = srcHeight.constData() + srcY * srcSize + srcX; + for (int y = 0; y < destHeight; y++, src += srcSize, dest += heightSize) { + memcpy(dest, src, destWidth); + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = heightIncrement / srcIncrement; + int shift = 0; + float size = _buffer->getScale(); + while (size < info.size) { + shift++; + size *= 2.0f; + } + int subtract = (_buffer->getTranslation().y - info.minimum.y) * EIGHT_BIT_MAXIMUM / _buffer->getScale(); + for (int y = 0; y < destHeight; y++, dest += heightSize, srcY += srcAdvance) { + const uchar* src = (const uchar*)srcHeight.constData() + (int)srcY * srcSize; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) { + *lineDest = qMin(qMax(0, (src[(int)lineSrcX] << shift) - subtract), EIGHT_BIT_MAXIMUM); + } } } } - - int colorSize = _buffer->getColorSize(); - if (colorSize == 0) { - continue; - } - HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); - if (!color) { - continue; - } - const Box& colorBounds = _buffer->getColorBounds(); - overlap = colorBounds.getIntersection(overlap); - float colorIncrement = _buffer->getColorIncrement(); - destX = (overlap.minimum.x - colorBounds.minimum.x) / colorIncrement; - destY = (overlap.minimum.z - colorBounds.minimum.z) / colorIncrement; - destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / colorIncrement); - destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / colorIncrement); - dest = _buffer->getColor().data() + (destY * colorSize + destX) * DataBlock::COLOR_BYTES; - int destStride = colorSize * DataBlock::COLOR_BYTES; - int destBytes = destWidth * DataBlock::COLOR_BYTES; - - const QByteArray& srcColor = color->getContents(); - srcSize = glm::sqrt(float(srcColor.size() / DataBlock::COLOR_BYTES)); - int srcStride = srcSize * DataBlock::COLOR_BYTES; - srcIncrement = info.size / srcSize; - - if (srcIncrement == colorIncrement) { - // easy case: same resolution - int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; - int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + if (color) { + const Box& colorBounds = _buffer->getColorBounds(); + overlap = colorBounds.getIntersection(overlap); + float colorIncrement = _buffer->getColorIncrement(); + int destX = (overlap.minimum.x - colorBounds.minimum.x) / colorIncrement; + int destY = (overlap.minimum.z - colorBounds.minimum.z) / colorIncrement; + int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / colorIncrement); + int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / colorIncrement); + int colorSize = _buffer->getColorSize(); + char* dest = _buffer->getColor().data() + (destY * colorSize + destX) * DataBlock::COLOR_BYTES; + int destStride = colorSize * DataBlock::COLOR_BYTES; + int destBytes = destWidth * DataBlock::COLOR_BYTES; - const char* src = srcColor.constData() + (srcY * srcSize + srcX) * DataBlock::COLOR_BYTES; - for (int y = 0; y < destHeight; y++, src += srcStride, dest += destStride) { - memcpy(dest, src, destBytes); - } - } else { - // more difficult: different resolutions - float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; - float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; - float srcAdvance = colorIncrement / srcIncrement; - for (int y = 0; y < destHeight; y++, dest += destStride, srcY += srcAdvance) { - const char* src = srcColor.constData() + (int)srcY * srcStride; - float lineSrcX = srcX; - for (char* lineDest = dest, *end = dest + destBytes; lineDest != end; lineDest += DataBlock::COLOR_BYTES, - lineSrcX += srcAdvance) { - const char* lineSrc = src + (int)lineSrcX * DataBlock::COLOR_BYTES; - lineDest[0] = lineSrc[0]; - lineDest[1] = lineSrc[1]; - lineDest[2] = lineSrc[2]; + const QByteArray& srcColor = color->getContents(); + int srcSize = glm::sqrt(float(srcColor.size() / DataBlock::COLOR_BYTES)); + int srcStride = srcSize * DataBlock::COLOR_BYTES; + float srcIncrement = info.size / srcSize; + + if (srcIncrement == colorIncrement) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const char* src = srcColor.constData() + (srcY * srcSize + srcX) * DataBlock::COLOR_BYTES; + for (int y = 0; y < destHeight; y++, src += srcStride, dest += destStride) { + memcpy(dest, src, destBytes); + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = colorIncrement / srcIncrement; + for (int y = 0; y < destHeight; y++, dest += destStride, srcY += srcAdvance) { + const char* src = srcColor.constData() + (int)srcY * srcStride; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destBytes; lineDest != end; lineDest += DataBlock::COLOR_BYTES, + lineSrcX += srcAdvance) { + const char* lineSrc = src + (int)lineSrcX * DataBlock::COLOR_BYTES; + lineDest[0] = lineSrc[0]; + lineDest[1] = lineSrc[1]; + lineDest[2] = lineSrc[2]; + } } } } + if (material) { + const Box& materialBounds = _buffer->getMaterialBounds(); + overlap = materialBounds.getIntersection(overlap); + float materialIncrement = _buffer->getMaterialIncrement(); + int destX = (overlap.minimum.x - materialBounds.minimum.x) / materialIncrement; + int destY = (overlap.minimum.z - materialBounds.minimum.z) / materialIncrement; + int destWidth = glm::ceil((overlap.maximum.x - overlap.minimum.x) / materialIncrement); + int destHeight = glm::ceil((overlap.maximum.z - overlap.minimum.z) / materialIncrement); + int materialSize = _buffer->getMaterialSize(); + char* dest = _buffer->getMaterial().data() + destY * materialSize + destX; + + const QByteArray& srcMaterial = material->getContents(); + const QVector srcMaterials = material->getMaterials(); + int srcSize = glm::sqrt((float)srcMaterial.size()); + float srcIncrement = info.size / srcSize; + QHash materialMappings; + + if (srcIncrement == materialIncrement) { + // easy case: same resolution + int srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + int srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + + const uchar* src = (const uchar*)srcMaterial.constData() + srcY * srcSize + srcX; + for (int y = 0; y < destHeight; y++, src += srcSize, dest += materialSize) { + const uchar* lineSrc = src; + for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrc++) { + int value = *lineSrc; + if (value != 0) { + int& mapping = materialMappings[value]; + if (mapping == 0) { + mapping = getMaterialIndex(material->getMaterials().at(value - 1), + _buffer->getMaterials(), _buffer->getMaterial()); + } + value = mapping; + } + *lineDest = value; + } + } + } else { + // more difficult: different resolutions + float srcX = (overlap.minimum.x - info.minimum.x) / srcIncrement; + float srcY = (overlap.minimum.z - info.minimum.z) / srcIncrement; + float srcAdvance = materialIncrement / srcIncrement; + for (int y = 0; y < destHeight; y++, dest += materialSize, srcY += srcAdvance) { + const uchar* src = (const uchar*)srcMaterial.constData() + (int)srcY * srcSize; + float lineSrcX = srcX; + for (char* lineDest = dest, *end = dest + destWidth; lineDest != end; lineDest++, lineSrcX += srcAdvance) { + int value = src[(int)lineSrcX]; + if (value != 0) { + int& mapping = materialMappings[value]; + if (mapping == 0) { + mapping = getMaterialIndex(material->getMaterials().at(value - 1), + _buffer->getMaterials(), _buffer->getMaterial()); + } + value = mapping; + } + *lineDest = value; + } + } + } + clearUnusedMaterials(_buffer->getMaterials(), _buffer->getMaterial()); + } } - return STOP_RECURSION; + return false; } class HeightfieldRegionVisitor : public MetavoxelVisitor { @@ -1841,11 +1957,22 @@ public: HeightfieldRegionVisitor(const MetavoxelLOD& lod); virtual int visit(MetavoxelInfo& info); + virtual bool postVisit(MetavoxelInfo& info); private: void addRegion(const Box& unextended, const Box& extended); + class DepthInfo { + public: + float minimumColorIncrement; + float minimumMaterialIncrement; + + DepthInfo() : minimumColorIncrement(FLT_MAX), minimumMaterialIncrement(FLT_MAX) { } + }; + + QVector _depthInfo; + QVector _intersections; HeightfieldFetchVisitor _fetchVisitor; }; @@ -1861,70 +1988,99 @@ HeightfieldRegionVisitor::HeightfieldRegionVisitor(const MetavoxelLOD& lod) : } int HeightfieldRegionVisitor::visit(MetavoxelInfo& info) { + DepthInfo depthInfo; + HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); + if (color) { + int colorSize = glm::sqrt((float)color->getContents().size() / DataBlock::COLOR_BYTES); + depthInfo.minimumColorIncrement = info.size / colorSize; + } + HeightfieldMaterialDataPointer material = info.inputValues.at(2).getInlineValue(); + if (material) { + int materialSize = glm::sqrt((float)material->getContents().size()); + depthInfo.minimumMaterialIncrement = info.size / materialSize; + } + if (_depth < _depthInfo.size()) { + _depthInfo[_depth] = depthInfo; + } else { + _depthInfo.append(depthInfo); + } if (!info.isLeaf) { - return DEFAULT_ORDER; + return _visitations.at(_depth).isInputLeaf(0) ? (DEFAULT_ORDER | ALL_NODES_REST) : DEFAULT_ORDER; } - HeightfieldBuffer* buffer = NULL; - HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue(); - if (height) { - const QByteArray& heightContents = height->getContents(); - int size = glm::sqrt(float(heightContents.size())); - int extendedSize = size + HeightfieldBuffer::HEIGHT_EXTENSION; - int heightContentsSize = extendedSize * extendedSize; - - HeightfieldColorDataPointer color = info.inputValues.at(1).getInlineValue(); - int colorContentsSize = 0; - if (color) { - const QByteArray& colorContents = color->getContents(); - int colorSize = glm::sqrt(float(colorContents.size() / DataBlock::COLOR_BYTES)); - int extendedColorSize = colorSize + HeightfieldBuffer::SHARED_EDGE; - colorContentsSize = extendedColorSize * extendedColorSize * DataBlock::COLOR_BYTES; - } - - HeightfieldMaterialDataPointer material = info.inputValues.at(2).getInlineValue(); - QByteArray materialContents; - QVector materials; - if (material) { - materialContents = material->getContents(); - materials = material->getMaterials(); - } - - const HeightfieldBuffer* existingBuffer = static_cast( - info.inputValues.at(3).getInlineValue().data()); - Box bounds = info.getBounds(); - if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize && - existingBuffer->getColor().size() == colorContentsSize) { - // we already have a buffer of the correct resolution - addRegion(bounds, existingBuffer->getHeightBounds()); - buffer = new HeightfieldBuffer(info.minimum, info.size, existingBuffer->getHeight(), - existingBuffer->getColor(), materialContents, materials); - - } else { - // we must create a new buffer and update its borders - buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), - QByteArray(colorContentsSize, 0), materialContents, materials); - const Box& heightBounds = buffer->getHeightBounds(); - addRegion(bounds, heightBounds); - - _intersections.clear(); - _intersections.append(Box(heightBounds.minimum, - glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); - _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), - glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); - _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), - heightBounds.maximum)); - _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), - glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); - - _fetchVisitor.init(buffer); - _data->guide(_fetchVisitor); - } - } - BufferDataPointer pointer(buffer); - info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer)); + postVisit(info); return STOP_RECURSION; } +bool HeightfieldRegionVisitor::postVisit(MetavoxelInfo& info) { + const DepthInfo& depthInfo = _depthInfo.at(_depth); + if (_depth > 0) { + DepthInfo& parentDepthInfo = _depthInfo[_depth - 1]; + parentDepthInfo.minimumColorIncrement = qMin(parentDepthInfo.minimumColorIncrement, depthInfo.minimumColorIncrement); + parentDepthInfo.minimumMaterialIncrement = qMin(parentDepthInfo.minimumMaterialIncrement, + depthInfo.minimumMaterialIncrement); + } + if (_visitations.at(_depth).isInputLeaf(0)) { + HeightfieldBuffer* buffer = NULL; + HeightfieldHeightDataPointer height = info.inputValues.at(0).getInlineValue(); + if (height) { + int heightSize = glm::sqrt((float)height->getContents().size()); + int extendedHeightSize = heightSize + HeightfieldBuffer::HEIGHT_EXTENSION; + int heightContentsSize = extendedHeightSize * extendedHeightSize; + float minimumColorIncrement = depthInfo.minimumColorIncrement; + float minimumMaterialIncrement = depthInfo.minimumMaterialIncrement; + for (int i = _depth - 1; i >= 0 && qMax(minimumColorIncrement, minimumMaterialIncrement) == FLT_MAX; i--) { + const DepthInfo& ancestorDepthInfo = _depthInfo.at(i); + minimumColorIncrement = qMin(minimumColorIncrement, ancestorDepthInfo.minimumColorIncrement); + minimumMaterialIncrement = qMin(minimumMaterialIncrement, ancestorDepthInfo.minimumMaterialIncrement); + } + int colorContentsSize = 0; + if (minimumColorIncrement != FLT_MAX) { + int colorSize = (int)glm::round(info.size / minimumColorIncrement) + HeightfieldBuffer::SHARED_EDGE; + colorContentsSize = colorSize * colorSize * DataBlock::COLOR_BYTES; + } + int materialContentsSize = 0; + if (minimumMaterialIncrement != FLT_MAX) { + int materialSize = (int)glm::round(info.size / minimumMaterialIncrement) + HeightfieldBuffer::SHARED_EDGE; + materialContentsSize = materialSize * materialSize; + } + const HeightfieldBuffer* existingBuffer = static_cast( + info.inputValues.at(3).getInlineValue().data()); + Box bounds = info.getBounds(); + if (existingBuffer && existingBuffer->getHeight().size() == heightContentsSize && + existingBuffer->getColor().size() == colorContentsSize && + existingBuffer->getMaterial().size() == materialContentsSize) { + // we already have a buffer of the correct resolution + addRegion(bounds, existingBuffer->getHeightBounds()); + buffer = new HeightfieldBuffer(info.minimum, info.size, existingBuffer->getHeight(), + existingBuffer->getColor(), existingBuffer->getMaterial(), existingBuffer->getMaterials()); + + } else { + // we must create a new buffer and update its borders + buffer = new HeightfieldBuffer(info.minimum, info.size, QByteArray(heightContentsSize, 0), + QByteArray(colorContentsSize, 0), QByteArray(materialContentsSize, 0)); + const Box& heightBounds = buffer->getHeightBounds(); + addRegion(bounds, heightBounds); + + _intersections.clear(); + _intersections.append(Box(heightBounds.minimum, + glm::vec3(bounds.maximum.x, heightBounds.maximum.y, bounds.minimum.z))); + _intersections.append(Box(glm::vec3(bounds.maximum.x, heightBounds.minimum.y, heightBounds.minimum.z), + glm::vec3(heightBounds.maximum.x, heightBounds.maximum.y, bounds.maximum.z))); + _intersections.append(Box(glm::vec3(bounds.minimum.x, heightBounds.minimum.y, bounds.maximum.z), + heightBounds.maximum)); + _intersections.append(Box(glm::vec3(heightBounds.minimum.x, heightBounds.minimum.y, bounds.minimum.z), + glm::vec3(bounds.minimum.x, heightBounds.maximum.y, heightBounds.maximum.z))); + + _fetchVisitor.init(buffer); + _data->guide(_fetchVisitor); + } + } + BufferDataPointer pointer(buffer); + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(pointer)); + } + return true; +} + void HeightfieldRegionVisitor::addRegion(const Box& unextended, const Box& extended) { regions.append(unextended); regionBounds.add(extended); diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 08cc1098ca..7e9f2ce2ca 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -236,6 +236,7 @@ public: const Box& getHeightBounds() const { return _heightBounds; } const Box& getColorBounds() const { return _colorBounds; } + const Box& getMaterialBounds() const { return _materialBounds; } QByteArray& getHeight() { return _height; } const QByteArray& getHeight() const { return _height; } @@ -246,10 +247,11 @@ public: QByteArray& getMaterial() { return _material; } const QByteArray& getMaterial() const { return _material; } + QVector& getMaterials() { return _materials; } const QVector& getMaterials() const { return _materials; } QByteArray getUnextendedHeight() const; - QByteArray getUnextendedColor() const; + QByteArray getUnextendedColor(int x = 0, int y = 0) const; int getHeightSize() const { return _heightSize; } float getHeightIncrement() const { return _heightIncrement; } @@ -257,6 +259,9 @@ public: int getColorSize() const { return _colorSize; } float getColorIncrement() const { return _colorIncrement; } + int getMaterialSize() const { return _materialSize; } + float getMaterialIncrement() const { return _materialIncrement; } + virtual void render(bool cursor = false); private: @@ -265,6 +270,7 @@ private: float _scale; Box _heightBounds; Box _colorBounds; + Box _materialBounds; QByteArray _height; QByteArray _color; QByteArray _material; @@ -277,6 +283,8 @@ private: float _heightIncrement; int _colorSize; float _colorIncrement; + int _materialSize; + float _materialIncrement; typedef QPair BufferPair; static QHash _bufferPairs; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 623631d5e5..b468f37f33 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1269,6 +1269,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe _isPushing = false; float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f); + glm::vec3 newLocalVelocity = localVelocity; float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + (fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); @@ -1285,31 +1286,47 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe if (directionLength > EPSILON) { direction /= directionLength; - // Compute the target keyboard velocity (which ramps up slowly, and damps very quickly) - // the max magnitude of which depends on what we're doing: - float motorSpeed = glm::length(_keyboardMotorVelocity); - float finalMaxMotorSpeed = hasFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED; - float speedGrowthTimescale = 2.0f; - float speedIncreaseFactor = 1.8f; - motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; - const float maxBoostSpeed = _scale * MAX_BOOST_SPEED; - if (motorSpeed < maxBoostSpeed) { - // an active keyboard motor should never be slower than this - float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; - motorSpeed += MIN_AVATAR_SPEED * boostCoefficient; - motorEfficiency += (1.0f - motorEfficiency) * boostCoefficient; - } else if (motorSpeed > finalMaxMotorSpeed) { - motorSpeed = finalMaxMotorSpeed; + if (hasFloor) { + // we're walking --> simple exponential decay toward target walk speed + const float WALK_ACCELERATION_TIMESCALE = 0.7f; // seconds to decrease delta to 1/e + _keyboardMotorVelocity = MAX_WALKING_SPEED * direction; + motorEfficiency = glm::clamp(deltaTime / WALK_ACCELERATION_TIMESCALE, 0.0f, 1.0f); + + } else { + // we're flying --> more complex curve + float motorSpeed = glm::length(_keyboardMotorVelocity); + float finalMaxMotorSpeed = _scale * MAX_KEYBOARD_MOTOR_SPEED; + float speedGrowthTimescale = 2.0f; + float speedIncreaseFactor = 1.8f; + motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; + const float maxBoostSpeed = _scale * MAX_BOOST_SPEED; + if (motorSpeed < maxBoostSpeed) { + // an active keyboard motor should never be slower than this + float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; + motorSpeed += MIN_AVATAR_SPEED * boostCoefficient; + motorEfficiency += (1.0f - motorEfficiency) * boostCoefficient; + } else if (motorSpeed > finalMaxMotorSpeed) { + motorSpeed = finalMaxMotorSpeed; + } + _keyboardMotorVelocity = motorSpeed * direction; } - _keyboardMotorVelocity = motorSpeed * direction; _isPushing = true; } + newLocalVelocity = localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity); } else { _keyboardMotorVelocity = glm::vec3(0.0f); + newLocalVelocity = (1.0f - motorEfficiency) * localVelocity; + if (hasFloor && !_wasPushing) { + float speed = glm::length(newLocalVelocity); + if (speed > MIN_AVATAR_SPEED) { + // add small constant friction to help avatar drift to a stop sooner at low speeds + const float CONSTANT_FRICTION_DECELERATION = MIN_AVATAR_SPEED / 0.20f; + newLocalVelocity *= (speed - timescale * CONSTANT_FRICTION_DECELERATION) / speed; + } + } } - // apply keyboard motor - return localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity); + return newLocalVelocity; } glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVelocity) { diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 709080e354..23cd52d946 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -420,7 +420,7 @@ void OculusManager::endFrameTiming() { //Sets the camera FoV and aspect ratio void OculusManager::configureCamera(Camera& camera, int screenWidth, int screenHeight) { #ifdef HAVE_LIBOVR - camera.setAspectRatio(_renderTargetSize.w / _renderTargetSize.h); + camera.setAspectRatio((float)_renderTargetSize.w / _renderTargetSize.h); camera.setFieldOfView(atan(_eyeFov[0].UpTan) * DEGREES_PER_RADIAN * 2.0f); #endif } @@ -511,12 +511,13 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p _camera->update(1.0f / Application::getInstance()->getFps()); - Matrix4f proj = ovrMatrix4f_Projection(_eyeRenderDesc[eye].Fov, whichCamera.getNearClip(), whichCamera.getFarClip(), true); - proj.Transpose(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glLoadMatrixf((GLfloat *)proj.M); - + const ovrFovPort& port = _eyeFov[_activeEyeIndex]; + float nearClip = whichCamera.getNearClip(), farClip = whichCamera.getFarClip(); + glFrustum(-nearClip * port.LeftTan, nearClip * port.RightTan, -nearClip * port.DownTan, + nearClip * port.UpTan, nearClip, farClip); + glViewport(_eyeRenderViewport[eye].Pos.x, _eyeRenderViewport[eye].Pos.y, _eyeRenderViewport[eye].Size.w, _eyeRenderViewport[eye].Size.h); diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index 94521dc927..20e43d572c 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -21,7 +21,6 @@ #include "ui/overlays/Text3DOverlay.h" const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f; -const int DEFAULT_OCULUS_UI_MAX_FPS = 75; class Camera; class PalmData; diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index e8644c00d0..872f4ce7e6 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -91,8 +91,10 @@ void TV3DManager::display(Camera& whichCamera) { // left eye portal int portalX = 0; int portalY = 0; - int portalW = Application::getInstance()->getGLWidget()->getDeviceWidth() / 2; - int portalH = Application::getInstance()->getGLWidget()->getDeviceHeight(); + QSize deviceSize = Application::getInstance()->getGLWidget()->getDeviceSize() * + Application::getInstance()->getRenderResolutionScale(); + int portalW = deviceSize.width() / 2; + int portalH = deviceSize.height(); ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay(); @@ -135,7 +137,7 @@ void TV3DManager::display(Camera& whichCamera) { glDisable(GL_SCISSOR_TEST); // render right side view - portalX = Application::getInstance()->getGLWidget()->getDeviceWidth() / 2; + portalX = deviceSize.width() / 2; glEnable(GL_SCISSOR_TEST); // render left side view glViewport(portalX, portalY, portalW, portalH); @@ -165,8 +167,7 @@ void TV3DManager::display(Camera& whichCamera) { glDisable(GL_SCISSOR_TEST); // reset the viewport to how we started - glViewport(0, 0, Application::getInstance()->getGLWidget()->getDeviceWidth(), - Application::getInstance()->getGLWidget()->getDeviceHeight()); + glViewport(0, 0, deviceSize.width(), deviceSize.height()); Application::getInstance()->getGlowEffect()->render(); } diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index e6b640011d..e447c703fb 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -11,19 +11,25 @@ #include +#include + #include #include "InterfaceConfig.h" #include #include +#include #include #include #include "Menu.h" +#include "NetworkAccessManager.h" #include "EntityTreeRenderer.h" +#include "devices/OculusManager.h" + #include "RenderableBoxEntityItem.h" #include "RenderableLightEntityItem.h" #include "RenderableModelEntityItem.h" @@ -36,15 +42,21 @@ QThread* EntityTreeRenderer::getMainThread() { -EntityTreeRenderer::EntityTreeRenderer() : - OctreeRenderer() { +EntityTreeRenderer::EntityTreeRenderer(bool wantScripts) : + OctreeRenderer(), + _wantScripts(wantScripts), + _entitiesScriptEngine(NULL) { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) + + _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID + _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID } EntityTreeRenderer::~EntityTreeRenderer() { + // do we need to delete the _entitiesScriptEngine?? or is it deleted by default } void EntityTreeRenderer::clear() { @@ -54,6 +66,117 @@ void EntityTreeRenderer::clear() { void EntityTreeRenderer::init() { OctreeRenderer::init(); static_cast(_tree)->setFBXService(this); + + if (_wantScripts) { + _entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities", + Application::getInstance()->getControllerScriptingInterface()); + Application::getInstance()->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); + } + + // make sure our "last avatar position" is something other than our current position, so that on our + // first chance, we'll check for enter/leave entity events. + glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition(); + _lastAvatarPosition = avatarPosition + glm::vec3(1.f, 1.f, 1.f); +} + +QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) { + EntityItem* entity = static_cast(_tree)->findEntityByEntityItemID(entityItemID); + return loadEntityScript(entity); +} + + +QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText) { + QUrl url(scriptMaybeURLorText); + + // If the url is not valid, this must be script text... + if (!url.isValid()) { + return scriptMaybeURLorText; + } + + QString scriptContents; // assume empty + + // if the scheme length is one or lower, maybe they typed in a file, let's try + const int WINDOWS_DRIVE_LETTER_SIZE = 1; + if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { + url = QUrl::fromLocalFile(scriptMaybeURLorText); + } + + // ok, let's see if it's valid... and if so, load it + if (url.isValid()) { + if (url.scheme() == "file") { + QString fileName = url.toLocalFile(); + QFile scriptFile(fileName); + if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { + qDebug() << "Loading file:" << fileName; + QTextStream in(&scriptFile); + scriptContents = in.readAll(); + } else { + qDebug() << "ERROR Loading file:" << fileName; + } + } else { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url)); + qDebug() << "Downloading script at" << url; + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { + scriptContents = reply->readAll(); + } else { + qDebug() << "ERROR Loading file:" << url.toString(); + } + } + } + + return scriptContents; +} + + +QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { + if (!entity) { + return QScriptValue(); // no entity... + } + + EntityItemID entityID = entity->getEntityItemID(); + if (_entityScripts.contains(entityID)) { + EntityScriptDetails details = _entityScripts[entityID]; + + // check to make sure our script text hasn't changed on us since we last loaded it + if (details.scriptText == entity->getScript()) { + return details.scriptObject; // previously loaded + } + + // if we got here, then we previously loaded a script, but the entity's script value + // has changed and so we need to reload it. + _entityScripts.remove(entityID); + } + if (entity->getScript().isEmpty()) { + return QScriptValue(); // no script + } + + QString scriptContents = loadScriptContents(entity->getScript()); + + if (QScriptEngine::checkSyntax(scriptContents).state() != QScriptSyntaxCheckResult::Valid) { + qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; + qDebug() << " INVALID SYNTAX"; + qDebug() << " SCRIPT:" << scriptContents; + return QScriptValue(); // invalid script + } + + QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents); + + if (!entityScriptConstructor.isFunction()) { + qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; + qDebug() << " NOT CONSTRUCTOR"; + qDebug() << " SCRIPT:" << scriptContents; + return QScriptValue(); // invalid script + } + + QScriptValue entityScriptObject = entityScriptConstructor.construct(); + EntityScriptDetails newDetails = { entity->getScript(), entityScriptObject }; + _entityScripts[entityID] = newDetails; + + return entityScriptObject; // newly constructed } void EntityTreeRenderer::setTree(Octree* newTree) { @@ -65,6 +188,58 @@ void EntityTreeRenderer::update() { if (_tree) { EntityTree* tree = static_cast(_tree); tree->update(); + + // check to see if the avatar has moved and if we need to handle enter/leave entity logic + checkEnterLeaveEntities(); + } +} + +void EntityTreeRenderer::checkEnterLeaveEntities() { + if (_tree) { + glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition() / (float) TREE_SCALE; + + if (avatarPosition != _lastAvatarPosition) { + float radius = 1.0f / (float) TREE_SCALE; // for now, assume 1 meter radius + QVector foundEntities; + QVector entitiesContainingAvatar; + + // find the entities near us + static_cast(_tree)->findEntities(avatarPosition, radius, foundEntities); + + // create a list of entities that actually contain the avatar's position + foreach(const EntityItem* entity, foundEntities) { + if (entity->contains(avatarPosition)) { + entitiesContainingAvatar << entity->getEntityItemID(); + } + } + + // for all of our previous containing entities, if they are no longer containing then send them a leave event + foreach(const EntityItemID& entityID, _currentEntitiesInside) { + if (!entitiesContainingAvatar.contains(entityID)) { + emit leaveEntity(entityID); + QScriptValueList entityArgs = createEntityArgs(entityID); + QScriptValue entityScript = loadEntityScript(entityID); + if (entityScript.property("leaveEntity").isValid()) { + entityScript.property("leaveEntity").call(entityScript, entityArgs); + } + + } + } + + // for all of our new containing entities, if they weren't previously containing then send them an enter event + foreach(const EntityItemID& entityID, entitiesContainingAvatar) { + if (!_currentEntitiesInside.contains(entityID)) { + emit enterEntity(entityID); + QScriptValueList entityArgs = createEntityArgs(entityID); + QScriptValue entityScript = loadEntityScript(entityID); + if (entityScript.property("enterEntity").isValid()) { + entityScript.property("enterEntity").call(entityScript, entityArgs); + } + } + } + _currentEntitiesInside = entitiesContainingAvatar; + _lastAvatarPosition = avatarPosition; + } } } @@ -101,8 +276,8 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI } void renderElementProxy(EntityTreeElement* entityTreeElement) { - glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float)TREE_SCALE; - float elementSize = entityTreeElement->getScale() * (float)TREE_SCALE; + glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float) TREE_SCALE; + float elementSize = entityTreeElement->getScale() * (float) TREE_SCALE; glColor3f(1.0f, 0.0f, 0.0f); glPushMatrix(); glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z); @@ -175,9 +350,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg AACube minCube = entity->getMinimumAACube(); AABox entityBox = entity->getAABox(); - maxCube.scale((float)TREE_SCALE); - minCube.scale((float)TREE_SCALE); - entityBox.scale((float)TREE_SCALE); + maxCube.scale((float) TREE_SCALE); + minCube.scale((float) TREE_SCALE); + entityBox.scale((float) TREE_SCALE); glm::vec3 maxCenter = maxCube.calcCenter(); glm::vec3 minCenter = minCube.calcCenter(); @@ -207,9 +382,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg glPopMatrix(); - glm::vec3 position = entity->getPosition() * (float)TREE_SCALE; - glm::vec3 center = entity->getCenter() * (float)TREE_SCALE; - glm::vec3 dimensions = entity->getDimensions() * (float)TREE_SCALE; + glm::vec3 position = entity->getPosition() * (float) TREE_SCALE; + glm::vec3 center = entity->getCenter() * (float) TREE_SCALE; + glm::vec3 dimensions = entity->getDimensions() * (float) TREE_SCALE; glm::quat rotation = entity->getRotation(); glColor4f(1.0f, 0.0f, 1.0f, 1.0f); @@ -382,4 +557,207 @@ void EntityTreeRenderer::deleteReleasedModels() { } } +PickRay EntityTreeRenderer::computePickRay(float x, float y) { + float screenWidth = Application::getInstance()->getGLWidget()->width(); + float screenHeight = Application::getInstance()->getGLWidget()->height(); + PickRay result; + if (OculusManager::isConnected()) { + Camera* camera = Application::getInstance()->getCamera(); + result.origin = camera->getPosition(); + Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction); + } else { + ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum(); + viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction); + } + return result; +} + + +RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType) { + RayToEntityIntersectionResult result; + if (_tree) { + EntityTree* entityTree = static_cast(_tree); + + OctreeElement* element; + EntityItem* intersectedEntity = NULL; + result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, + (void**)&intersectedEntity, lockType, &result.accurate); + if (result.intersects && intersectedEntity) { + result.entityID = intersectedEntity->getEntityItemID(); + result.properties = intersectedEntity->getProperties(); + result.intersection = ray.origin + (ray.direction * result.distance); + result.entity = intersectedEntity; + } + } + return result; +} + +void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) { + connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); + connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); + connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); + + connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity); + connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity); + connect(this, &EntityTreeRenderer::clickReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickReleaseOnEntity); + + connect(this, &EntityTreeRenderer::hoverEnterEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity); + connect(this, &EntityTreeRenderer::hoverOverEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); + connect(this, &EntityTreeRenderer::hoverLeaveEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); + + connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity); + connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity); +} + +QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) { + QScriptValueList args; + args << entityID.toScriptValue(_entitiesScriptEngine); + args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine); + return args; +} + +QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) { + QScriptValueList args; + args << entityID.toScriptValue(_entitiesScriptEngine); + return args; +} + +void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { + PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); + PickRay ray = computePickRay(event->x(), event->y()); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock); + if (rayPickResult.intersects) { + //qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID; + emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + + QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); + QScriptValue entityScript = loadEntityScript(rayPickResult.entity); + if (entityScript.property("mousePressOnEntity").isValid()) { + entityScript.property("mousePressOnEntity").call(entityScript, entityScriptArgs); + } + + _currentClickingOnEntityID = rayPickResult.entityID; + emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + if (entityScript.property("clickDownOnEntity").isValid()) { + entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs); + } + } +} + +void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { + PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent"); + PickRay ray = computePickRay(event->x(), event->y()); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock); + if (rayPickResult.intersects) { + //qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID; + emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + + QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); + QScriptValue entityScript = loadEntityScript(rayPickResult.entity); + if (entityScript.property("mouseReleaseOnEntity").isValid()) { + entityScript.property("mouseReleaseOnEntity").call(entityScript, entityScriptArgs); + } + } + + // Even if we're no longer intersecting with an entity, if we started clicking on it, and now + // we're releasing the button, then this is considered a clickOn event + if (!_currentClickingOnEntityID.isInvalidID()) { + emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + + QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID); + QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); + if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) { + currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs); + } + } + + // makes it the unknown ID, we just released so we can't be clicking on anything + _currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); +} + +void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { + PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); + PickRay ray = computePickRay(event->x(), event->y()); + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock); + if (rayPickResult.intersects) { + QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID); + + // load the entity script if needed... + QScriptValue entityScript = loadEntityScript(rayPickResult.entity); + if (entityScript.property("mouseMoveEvent").isValid()) { + entityScript.property("mouseMoveEvent").call(entityScript, entityScriptArgs); + } + + //qDebug() << "mouseMoveEvent over entity:" << rayPickResult.entityID; + emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + if (entityScript.property("mouseMoveOnEntity").isValid()) { + entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs); + } + + // handle the hover logic... + + // if we were previously hovering over an entity, and this new entity is not the same as our previous entity + // then we need to send the hover leave. + if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { + emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + + QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID); + + QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); + if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { + currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs); + } + } + + // If the new hover entity does not match the previous hover entity then we are entering the new one + // this is true if the _currentHoverOverEntityID is known or unknown + if (rayPickResult.entityID != _currentHoverOverEntityID) { + emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + if (entityScript.property("hoverEnterEntity").isValid()) { + entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs); + } + } + + // and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and + // we should send our hover over event + emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID)); + if (entityScript.property("hoverOverEntity").isValid()) { + entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs); + } + + // remember what we're hovering over + _currentHoverOverEntityID = rayPickResult.entityID; + + } else { + // handle the hover logic... + // if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to + // send the hover leave for our previous entity + if (!_currentHoverOverEntityID.isInvalidID()) { + emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID)); + + QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID); + + QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID); + if (currentHoverEntity.property("hoverLeaveEntity").isValid()) { + currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs); + } + + _currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID + } + } + + // Even if we're no longer intersecting with an entity, if we started clicking on an entity and we have + // not yet released the hold then this is still considered a holdingClickOnEntity event + if (!_currentClickingOnEntityID.isInvalidID()) { + emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID)); + + QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID); + + QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID); + if (currentClickingEntity.property("holdingClickOnEntity").isValid()) { + currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs); + } + } +} + diff --git a/interface/src/entities/EntityTreeRenderer.h b/interface/src/entities/EntityTreeRenderer.h index 09d931541d..b8acb28c3b 100644 --- a/interface/src/entities/EntityTreeRenderer.h +++ b/interface/src/entities/EntityTreeRenderer.h @@ -16,6 +16,7 @@ #include #include +#include // for RayToEntityIntersectionResult #include #include #include @@ -26,11 +27,17 @@ #include "renderer/Model.h" +class EntityScriptDetails { +public: + QString scriptText; + QScriptValue scriptObject; +}; + // Generic client side Octree renderer class. class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService { Q_OBJECT public: - EntityTreeRenderer(); + EntityTreeRenderer(bool wantScripts); virtual ~EntityTreeRenderer(); virtual Octree* createTree() { return new EntityTree(true); } @@ -57,12 +64,6 @@ public: /// clears the tree virtual void clear(); - //Q_INVOKABLE Model* getModel(const ModelEntityItem* modelEntityItem); - - // renderers for various types of entities - void renderEntityTypeBox(EntityItem* entity, RenderArgs* args); - void renderEntityTypeModel(EntityItem* entity, RenderArgs* args); - static QThread* getMainThread(); /// if a renderable entity item needs a model, we will allocate it for them @@ -75,9 +76,55 @@ public: void releaseModel(Model* model); void deleteReleasedModels(); + + // event handles which may generate entity related events + void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID); + void mousePressEvent(QMouseEvent* event, unsigned int deviceID); + void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID); + + /// connect our signals to anEntityScriptingInterface for firing of events related clicking, + /// hovering over, and entering entities + void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface); + +signals: + void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void enterEntity(const EntityItemID& entityItemID); + void leaveEntity(const EntityItemID& entityItemID); + private: QList _releasedModels; void renderProxies(const EntityItem* entity, RenderArgs* args); + PickRay computePickRay(float x, float y); + RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType); + + EntityItemID _currentHoverOverEntityID; + EntityItemID _currentClickingOnEntityID; + + QScriptValueList createEntityArgs(const EntityItemID& entityID); + void checkEnterLeaveEntities(); + glm::vec3 _lastAvatarPosition; + QVector _currentEntitiesInside; + + bool _wantScripts; + ScriptEngine* _entitiesScriptEngine; + + QScriptValue loadEntityScript(EntityItem* entity); + QScriptValue loadEntityScript(const EntityItemID& entityItemID); + QString loadScriptContents(const QString& scriptMaybeURLorText); + QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); + + QHash _entityScripts; }; #endif // hifi_EntityTreeRenderer_h diff --git a/interface/src/entities/RenderableLightEntityItem.cpp b/interface/src/entities/RenderableLightEntityItem.cpp index bf9939e164..e3e8f61e58 100644 --- a/interface/src/entities/RenderableLightEntityItem.cpp +++ b/interface/src/entities/RenderableLightEntityItem.cpp @@ -61,15 +61,18 @@ void RenderableLightEntityItem::render(RenderArgs* args) { float exponent = getExponent(); float cutoff = glm::radians(getCutoff()); - if (_isSpotlight) { - Application::getInstance()->getDeferredLightingEffect()->addSpotLight(position, largestDiameter / 2.0f, - ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation, - direction, exponent, cutoff); - } else { - Application::getInstance()->getDeferredLightingEffect()->addPointLight(position, largestDiameter / 2.0f, - ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation); - } + bool disableLights = Menu::getInstance()->isOptionChecked(MenuOption::DisableLightEntities); + if (!disableLights) { + if (_isSpotlight) { + Application::getInstance()->getDeferredLightingEffect()->addSpotLight(position, largestDiameter / 2.0f, + ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation, + direction, exponent, cutoff); + } else { + Application::getInstance()->getDeferredLightingEffect()->addPointLight(position, largestDiameter / 2.0f, + ambient, diffuse, specular, constantAttenuation, linearAttenuation, quadraticAttenuation); + } + } bool wantDebug = false; if (wantDebug) { glColor4f(diffuseR, diffuseG, diffuseB, 1.0f); diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 5a3ee61bbe..ce8d497da3 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -38,12 +38,9 @@ RenderableModelEntityItem::~RenderableModelEntityItem() { bool RenderableModelEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) { QString oldModelURL = getModelURL(); - QString oldTextures = getTextures(); bool somethingChanged = ModelEntityItem::setProperties(properties, forceCopy); - if (somethingChanged) { - if ((oldModelURL != getModelURL()) || (oldTextures != getTextures())) { - _needsModelReload = true; - } + if (somethingChanged && oldModelURL != getModelURL()) { + _needsModelReload = true; } return somethingChanged; } @@ -60,6 +57,47 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c return bytesRead; } +void RenderableModelEntityItem::remapTextures() { + if (!_model) { + return; // nothing to do if we don't have a model + } + + if (_currentTextures == _textures) { + return; // nothing to do if our recently mapped textures match our desired textures + } + qDebug() << "void RenderableModelEntityItem::remapTextures()...."; + + // since we're changing here, we need to run through our current texture map + // and any textures in the recently mapped texture, that is not in our desired + // textures, we need to "unset" + QJsonDocument currentTexturesAsJson = QJsonDocument::fromJson(_currentTextures.toUtf8()); + QJsonObject currentTexturesAsJsonObject = currentTexturesAsJson.object(); + QVariantMap currentTextureMap = currentTexturesAsJsonObject.toVariantMap(); + + QJsonDocument texturesAsJson = QJsonDocument::fromJson(_textures.toUtf8()); + QJsonObject texturesAsJsonObject = texturesAsJson.object(); + QVariantMap textureMap = texturesAsJsonObject.toVariantMap(); + + foreach(const QString& key, currentTextureMap.keys()) { + // if the desired texture map (what we're setting the textures to) doesn't + // contain this texture, then remove it by setting the URL to null + if (!textureMap.contains(key)) { + QUrl noURL; + qDebug() << "Removing texture named" << key << "by replacing it with no URL"; + _model->setTextureWithNameToURL(key, noURL); + } + } + + // here's where we remap any textures if needed... + foreach(const QString& key, textureMap.keys()) { + QUrl newTextureURL = textureMap[key].toUrl(); + qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL; + _model->setTextureWithNameToURL(key, newTextureURL); + } + + _currentTextures = _textures; +} + void RenderableModelEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RMEIrender"); @@ -72,6 +110,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE; if (drawAsModel) { + remapTextures(); glPushMatrix(); { float alpha = getLocalRenderAlpha(); @@ -159,6 +198,10 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { } assert(_myRenderer == renderer); // you should only ever render on one renderer + if (QThread::currentThread() != _myRenderer->thread()) { + return _model; + } + _needsModelReload = false; // this is the reload // if we have a URL, then we will want to end up returning a model... @@ -183,18 +226,6 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { } } - // here's where we remap any textures if needed... - if (!_textures.isEmpty() && _model) { - QJsonDocument texturesAsJson = QJsonDocument::fromJson(_textures.toUtf8()); - QJsonObject texturesAsJsonObject = texturesAsJson.object(); - QVariantMap textureMap = texturesAsJsonObject.toVariantMap(); - foreach(const QString& key, textureMap.keys()) { - QUrl newTextureURL = textureMap[key].toUrl(); - qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL; - _model->setTextureWithNameToURL(key, newTextureURL); - } - } - return result; } diff --git a/interface/src/entities/RenderableModelEntityItem.h b/interface/src/entities/RenderableModelEntityItem.h index f36b66dba4..dbc77b7e48 100644 --- a/interface/src/entities/RenderableModelEntityItem.h +++ b/interface/src/entities/RenderableModelEntityItem.h @@ -51,12 +51,14 @@ public: virtual void render(RenderArgs* args); Model* getModel(EntityTreeRenderer* renderer); private: + void remapTextures(); bool needsSimulation() const; Model* _model; bool _needsInitialSimulation; bool _needsModelReload; EntityTreeRenderer* _myRenderer; + QString _currentTextures; }; #endif // hifi_RenderableModelEntityItem_h diff --git a/interface/src/renderer/AmbientOcclusionEffect.cpp b/interface/src/renderer/AmbientOcclusionEffect.cpp index 33a0b6e77d..f75ed7e8c3 100644 --- a/interface/src/renderer/AmbientOcclusionEffect.cpp +++ b/interface/src/renderer/AmbientOcclusionEffect.cpp @@ -51,8 +51,7 @@ void AmbientOcclusionEffect::init() { _occlusionProgram->bind(); _occlusionProgram->setUniformValue("depthTexture", 0); - _occlusionProgram->setUniformValue("normalTexture", 1); - _occlusionProgram->setUniformValue("rotationTexture", 2); + _occlusionProgram->setUniformValue("rotationTexture", 1); _occlusionProgram->setUniformValueArray("sampleKernel", sampleKernel, SAMPLE_KERNEL_SIZE); _occlusionProgram->setUniformValue("radius", 0.1f); _occlusionProgram->release(); @@ -102,9 +101,6 @@ void AmbientOcclusionEffect::render() { glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryDepthTextureID()); glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryNormalTextureID()); - - glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, _rotationTextureID); // render with the occlusion shader to the secondary/tertiary buffer @@ -142,9 +138,6 @@ void AmbientOcclusionEffect::render() { glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); // now render secondary to primary with 4x4 blur diff --git a/interface/src/scripting/JoystickScriptingInterface.cpp b/interface/src/scripting/JoystickScriptingInterface.cpp index f2c7ef4579..68affeda5b 100644 --- a/interface/src/scripting/JoystickScriptingInterface.cpp +++ b/interface/src/scripting/JoystickScriptingInterface.cpp @@ -129,9 +129,8 @@ void JoystickScriptingInterface::update() { : HFActionEvent::endType(); // global action events fire in the center of the screen - QPointF centerPoint = Application::getInstance()->getViewportCenter(); - HFActionEvent actionEvent(actionType, centerPoint); - + HFActionEvent actionEvent(actionType, + Application::getInstance()->getViewFrustum()->computePickRay(0.5f, 0.5f)); qApp->sendEvent(qApp, &actionEvent); } diff --git a/interface/src/ui/InfoView.cpp b/interface/src/ui/InfoView.cpp index 06de3fdf2e..f306514e80 100644 --- a/interface/src/ui/InfoView.cpp +++ b/interface/src/ui/InfoView.cpp @@ -20,24 +20,24 @@ #define SETTINGS_VERSION_KEY "info-version" #define MAX_DIALOG_HEIGHT_RATIO 0.9 -InfoView::InfoView(bool forced) : +InfoView::InfoView(bool forced, QString path) : _forced(forced) { setWindowFlags(Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); - QString absPath = QFileInfo(Application::resourcesPath() + "html/interface-welcome-allsvg.html").absoluteFilePath(); + QString absPath = QFileInfo(Application::resourcesPath() + path).absoluteFilePath(); QUrl url = QUrl::fromLocalFile(absPath); load(url); connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loaded(bool))); } -void InfoView::showFirstTime() { - new InfoView(false); +void InfoView::showFirstTime(QString path) { + new InfoView(false, path); } -void InfoView::forcedShow() { - new InfoView(true); +void InfoView::forcedShow(QString path) { + new InfoView(true, path); } bool InfoView::shouldShow() { diff --git a/interface/src/ui/InfoView.h b/interface/src/ui/InfoView.h index 94d18ff6a1..47d5dac9ce 100644 --- a/interface/src/ui/InfoView.h +++ b/interface/src/ui/InfoView.h @@ -17,11 +17,11 @@ class InfoView : public QWebView { Q_OBJECT public: - static void showFirstTime(); - static void forcedShow(); + static void showFirstTime(QString path); + static void forcedShow(QString path); private: - InfoView(bool forced); + InfoView(bool forced, QString path); bool _forced; bool shouldShow(); diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 8c5080c6f8..e5a1f7f7a4 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1038,26 +1038,42 @@ void ImportHeightfieldTool::apply() { data.setRoot(AttributeRegistry::getInstance()->getHeightfieldAttribute(), new MetavoxelNode(AttributeValue( AttributeRegistry::getInstance()->getHeightfieldAttribute(), encodeInline(heightPointer)))); - QByteArray color; - if (buffer->getColor().isEmpty()) { - const unsigned char WHITE_VALUE = 0xFF; - color = QByteArray(height.size() * DataBlock::COLOR_BYTES, WHITE_VALUE); - } else { - color = buffer->getUnextendedColor(); - } - HeightfieldColorDataPointer colorPointer(new HeightfieldColorData(color)); - data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode(AttributeValue( - AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), encodeInline(colorPointer)))); - - int size = glm::sqrt(float(height.size())); - QByteArray material(size * size, 0); - HeightfieldMaterialDataPointer materialPointer(new HeightfieldMaterialData(material)); - data.setRoot(AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), new MetavoxelNode(AttributeValue( - AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), encodeInline(materialPointer)))); - MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit( _translation->getValue() + buffer->getTranslation() * scale, data)) }; Application::getInstance()->getMetavoxels()->applyEdit(message, true); + + int colorUnits = buffer->getColor().isEmpty() ? 1 : (buffer->getColorSize() - HeightfieldBuffer::SHARED_EDGE) / + (buffer->getHeightSize() - HeightfieldBuffer::HEIGHT_EXTENSION); + float colorScale = scale / colorUnits; + + for (int y = 0; y < colorUnits; y++) { + for (int x = 0; x < colorUnits; x++) { + MetavoxelData data; + data.setSize(colorScale); + + QByteArray color; + if (buffer->getColor().isEmpty()) { + const unsigned char WHITE_VALUE = 0xFF; + color = QByteArray(height.size() * DataBlock::COLOR_BYTES, WHITE_VALUE); + } else { + color = buffer->getUnextendedColor(x, y); + } + HeightfieldColorDataPointer colorPointer(new HeightfieldColorData(color)); + data.setRoot(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), new MetavoxelNode( + AttributeValue(AttributeRegistry::getInstance()->getHeightfieldColorAttribute(), + encodeInline(colorPointer)))); + + QByteArray material(height.size(), 0); + HeightfieldMaterialDataPointer materialPointer(new HeightfieldMaterialData(material)); + data.setRoot(AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), new MetavoxelNode( + AttributeValue(AttributeRegistry::getInstance()->getHeightfieldMaterialAttribute(), + encodeInline(materialPointer)))); + + MetavoxelEditMessage message = { QVariant::fromValue(SetDataEdit( + _translation->getValue() + buffer->getTranslation() * scale + glm::vec3(x, 0.0f, y) * colorScale, data)) }; + Application::getInstance()->getMetavoxels()->applyEdit(message, true); + } + } } } @@ -1143,7 +1159,9 @@ void ImportHeightfieldTool::updatePreview() { float z = 0.0f; int blockSize = pow(2.0, _blockSize->value()); int heightSize = blockSize + HeightfieldBuffer::HEIGHT_EXTENSION; - int colorSize = blockSize + HeightfieldBuffer::SHARED_EDGE; + int colorScale = glm::round(glm::log(_colorImage.height() / (float)_heightImage.height()) / glm::log(2.0f)); + int colorBlockSize = blockSize * pow(2.0, qMax(colorScale, 0)); + int colorSize = colorBlockSize + HeightfieldBuffer::SHARED_EDGE; for (int i = 0; i < _heightImage.height(); i += blockSize, z++) { float x = 0.0f; for (int j = 0; j < _heightImage.width(); j += blockSize, x++) { @@ -1164,12 +1182,14 @@ void ImportHeightfieldTool::updatePreview() { } QByteArray color; if (!_colorImage.isNull()) { + int colorI = (i / blockSize) * colorBlockSize; + int colorJ = (j / blockSize) * colorBlockSize; color = QByteArray(colorSize * colorSize * DataBlock::COLOR_BYTES, 0); - rows = qMax(0, qMin(colorSize, _colorImage.height() - i)); - columns = qMax(0, qMin(colorSize, _colorImage.width() - j)); + rows = qMax(0, qMin(colorSize, _colorImage.height() - colorI)); + columns = qMax(0, qMin(colorSize, _colorImage.width() - colorJ)); for (int y = 0; y < rows; y++) { memcpy(color.data() + y * colorSize * DataBlock::COLOR_BYTES, - _colorImage.scanLine(i + y) + j * DataBlock::COLOR_BYTES, + _colorImage.scanLine(colorI + y) + colorJ * DataBlock::COLOR_BYTES, columns * DataBlock::COLOR_BYTES); } } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 42256bc498..61cc9718b3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -147,8 +147,6 @@ void PreferencesDialog::loadPreferences() { ui.maxVoxelsPPSSpin->setValue(menuInstance->getMaxVoxelPacketsPerSecond()); ui.oculusUIAngularSizeSpin->setValue(menuInstance->getOculusUIAngularSize()); - - ui.oculusUIMaxFPSSpin->setValue(menuInstance->getOculusUIMaxFPS()); ui.sixenseReticleMoveSpeedSpin->setValue(menuInstance->getSixenseReticleMoveSpeed()); @@ -231,8 +229,6 @@ void PreferencesDialog::savePreferences() { Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value()); Menu::getInstance()->setOculusUIAngularSize(ui.oculusUIAngularSizeSpin->value()); - - Menu::getInstance()->setOculusUIMaxFPS(ui.oculusUIMaxFPSSpin->value()); Menu::getInstance()->setSixenseReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value()); diff --git a/interface/src/voxels/OctreePacketProcessor.cpp b/interface/src/voxels/OctreePacketProcessor.cpp index b3cf012869..9f53492e70 100644 --- a/interface/src/voxels/OctreePacketProcessor.cpp +++ b/interface/src/voxels/OctreePacketProcessor.cpp @@ -86,13 +86,13 @@ void OctreePacketProcessor::processPacket(const SharedNodePointer& sendingNode, switch(voxelPacketType) { case PacketTypeEntityErase: { - if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) { app->_entities.processEraseMessage(mutablePacket, sendingNode); } } break; case PacketTypeEntityData: { - if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::Entities)) { app->_entities.processDatagram(mutablePacket, sendingNode); } } break; diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index 2dda1a24b1..0108437c1f 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -61,7 +61,7 @@ 0 0 500 - 1459 + 1386 @@ -1772,24 +1772,24 @@ - - - Arial - - - - Qt::LeftToRight - - - - - - localhost - - + + + Arial + + + + Qt::LeftToRight + + + + + + localhost + + - + @@ -2084,85 +2084,6 @@ - - - - 0 - - - 7 - - - 0 - - - 7 - - - - - - Arial - - - - Oculus Rift FPS - - - 0 - - - maxVoxelsSpin - - - - - - - - Arial - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 100 - 0 - - - - - Arial - - - - 30 - - - 95 - - - 1 - - - 75 - - - - - diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 65da3c964a..2117ec25b0 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -253,6 +253,7 @@ public: void applyHardCollision(const CollisionInfo& collisionInfo); virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; } + virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); } protected: virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index c730e44322..aaf6e33128 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -124,6 +124,10 @@ void EntityItemID::handleAddEntityResponse(const QByteArray& packet) { _tokenIDsToIDs[creatorTokenID] = entityID; } +QScriptValue EntityItemID::toScriptValue(QScriptEngine* engine) const { + return EntityItemIDtoScriptValue(engine, *this); +} + QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& id) { QScriptValue obj = engine->newObject(); obj.setProperty("id", id.id.toString()); diff --git a/libraries/entities/src/EntityItemID.h b/libraries/entities/src/EntityItemID.h index 7a3f822c7c..1ac8a67fcd 100644 --- a/libraries/entities/src/EntityItemID.h +++ b/libraries/entities/src/EntityItemID.h @@ -49,11 +49,16 @@ public: EntityItemID convertToKnownIDVersion() const; EntityItemID convertToCreatorTokenVersion() const; + bool isInvalidID() const { return id == UNKNOWN_ENTITY_ID && creatorTokenID == UNKNOWN_ENTITY_TOKEN && isKnownID == false; } + // these methods allow you to create models, and later edit them. + static EntityItemID createInvalidEntityID() { return EntityItemID(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false); } static EntityItemID getIDfromCreatorTokenID(uint32_t creatorTokenID); static uint32_t getNextCreatorTokenID(); static void handleAddEntityResponse(const QByteArray& packet); static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead); + + QScriptValue toScriptValue(QScriptEngine* engine) const; private: friend class EntityTree; @@ -68,12 +73,22 @@ inline bool operator<(const EntityItemID& a, const EntityItemID& b) { } inline bool operator==(const EntityItemID& a, const EntityItemID& b) { + if (a.isInvalidID() && b.isInvalidID()) { + return true; + } + if (a.isInvalidID() != b.isInvalidID()) { + return false; + } if (a.id == UNKNOWN_ENTITY_ID || b.id == UNKNOWN_ENTITY_ID) { return a.creatorTokenID == b.creatorTokenID; } return a.id == b.id; } +inline bool operator!=(const EntityItemID& a, const EntityItemID& b) { + return !(a == b); +} + inline uint qHash(const EntityItemID& a, uint seed) { QUuid temp; if (a.id == UNKNOWN_ENTITY_ID) { @@ -94,5 +109,4 @@ Q_DECLARE_METATYPE(QVector); QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& properties); void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties); - #endif // hifi_EntityItemID_h diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 8113168655..1658764c71 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -217,7 +217,8 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() : entityID(), properties(), distance(0), - face() + face(), + entity(NULL) { } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 1497b2966e..2150fa51da 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -25,6 +25,7 @@ class EntityTree; +class MouseEvent; class RayToEntityIntersectionResult { @@ -37,6 +38,7 @@ public: float distance; BoxFace face; glm::vec3 intersection; + EntityItem* entity; }; Q_DECLARE_METATYPE(RayToEntityIntersectionResult) @@ -101,6 +103,21 @@ signals: void entityCollisionWithVoxel(const EntityItemID& entityID, const VoxelDetail& voxel, const CollisionInfo& collision); void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const CollisionInfo& collision); + void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event); + void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event); + + void enterEntity(const EntityItemID& entityItemID); + void leaveEntity(const EntityItemID& entityItemID); + private: void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index d57ada760b..21cb58223b 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -53,6 +53,9 @@ public: virtual const Shape& getCollisionShapeInMeters() const { return _sphereShape; } + // TODO: implement proper contains for 3D ellipsoid + //virtual bool contains(const glm::vec3& point) const; + protected: virtual void recalculateCollisionShape(); diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index a625c71d6a..c06e376a25 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -1198,6 +1198,10 @@ bool HeightfieldAttribute::merge(void*& parent, void* children[], bool postRead) return false; } +AttributeValue HeightfieldAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + HeightfieldColorAttribute::HeightfieldColorAttribute(const QString& name) : InlineAttribute(name) { } @@ -1337,6 +1341,10 @@ bool HeightfieldColorAttribute::merge(void*& parent, void* children[], bool post return false; } +AttributeValue HeightfieldColorAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + HeightfieldMaterialAttribute::HeightfieldMaterialAttribute(const QString& name) : InlineAttribute(name) { } @@ -1404,6 +1412,71 @@ bool HeightfieldMaterialAttribute::merge(void*& parent, void* children[], bool p return maxSize == 0; } +AttributeValue HeightfieldMaterialAttribute::inherit(const AttributeValue& parentValue) const { + return AttributeValue(parentValue.getAttribute()); +} + +static QHash countIndices(const QByteArray& contents) { + QHash counts; + for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) { + if (*src != 0) { + counts[*src]++; + } + } + return counts; +} + +uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, QByteArray& contents) { + if (!(material && static_cast(material.data())->getDiffuse().isValid())) { + return 0; + } + // first look for a matching existing material, noting the first reusable slot + int firstEmptyIndex = -1; + for (int i = 0; i < materials.size(); i++) { + const SharedObjectPointer& existingMaterial = materials.at(i); + if (existingMaterial) { + if (existingMaterial->equals(material.data())) { + return i + 1; + } + } else if (firstEmptyIndex == -1) { + firstEmptyIndex = i; + } + } + // if nothing found, use the first empty slot or append + if (firstEmptyIndex != -1) { + materials[firstEmptyIndex] = material; + return firstEmptyIndex + 1; + } + if (materials.size() < EIGHT_BIT_MAXIMUM) { + materials.append(material); + return materials.size(); + } + // last resort: find the least-used material and remove it + QHash counts = countIndices(contents); + uchar materialIndex = 0; + int lowestCount = INT_MAX; + for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { + if (it.value() < lowestCount) { + materialIndex = it.key(); + lowestCount = it.value(); + } + } + contents.replace((char)materialIndex, (char)0); + return materialIndex; +} + +void clearUnusedMaterials(QVector& materials, const QByteArray& contents) { + QHash counts = countIndices(contents); + for (int i = 0; i < materials.size(); i++) { + if (counts.value(i + 1) == 0) { + materials[i] = SharedObjectPointer(); + } + } + while (!(materials.isEmpty() || materials.last())) { + materials.removeLast(); + } +} + const int VOXEL_COLOR_HEADER_SIZE = sizeof(qint32) * 6; static QByteArray encodeVoxelColor(int offsetX, int offsetY, int offsetZ, diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index cee01cdbef..945c4df94b 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -536,6 +536,8 @@ public: virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; typedef QExplicitlySharedDataPointer HeightfieldColorDataPointer; @@ -580,6 +582,8 @@ public: virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; typedef QExplicitlySharedDataPointer HeightfieldMaterialDataPointer; @@ -646,8 +650,17 @@ public: virtual void writeDelta(Bitstream& out, void* value, void* reference, bool isLeaf) const; virtual bool merge(void*& parent, void* children[], bool postRead = false) const; + + virtual AttributeValue inherit(const AttributeValue& parentValue) const; }; +/// Utility method for editing: given a material pointer and a list of materials, returns the corresponding material index, +/// creating a new entry in the list if necessary. +uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, QByteArray& contents); + +/// Utility method for editing: removes any unused materials from the supplied list. +void clearUnusedMaterials(QVector& materials, const QByteArray& contents); + typedef QExplicitlySharedDataPointer VoxelColorDataPointer; /// Contains a block of voxel color data. diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 2f43f8f4ae..b79903ba8a 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -1509,7 +1509,7 @@ bool MetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) { DefaultMetavoxelGuide::DefaultMetavoxelGuide() { } -static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, float lodBase, int encodedOrder) { +static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, int encodedOrder) { MetavoxelVisitation& nextVisitation = visitation.visitor->acquireVisitation(); nextVisitation.info.size = visitation.info.size * 0.5f; for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) { @@ -1519,14 +1519,14 @@ static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, float for (int j = 0; j < visitation.inputNodes.size(); j++) { MetavoxelNode* node = visitation.inputNodes.at(j); const AttributeValue& parentValue = visitation.info.inputValues.at(j); - MetavoxelNode* child = (node && (visitation.info.size >= lodBase * + MetavoxelNode* child = (node && (visitation.info.size >= visitation.info.lodBase * parentValue.getAttribute()->getLODThresholdMultiplier())) ? node->getChild(index) : NULL; nextVisitation.info.inputValues[j] = ((nextVisitation.inputNodes[j] = child)) ? child->getAttributeValue(parentValue.getAttribute()) : parentValue.getAttribute()->inherit(parentValue); } for (int j = 0; j < visitation.outputNodes.size(); j++) { MetavoxelNode* node = visitation.outputNodes.at(j); - MetavoxelNode* child = (node && (visitation.info.size >= lodBase * + MetavoxelNode* child = (node && (visitation.info.size >= visitation.info.lodBase * visitation.visitor->getOutputs().at(j)->getLODThresholdMultiplier())) ? node->getChild(index) : NULL; nextVisitation.outputNodes[j] = child; } @@ -1604,9 +1604,10 @@ static inline bool defaultGuideToChildren(MetavoxelVisitation& visitation, float bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { // save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute - float lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) * + visitation.info.lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) * visitation.visitor->getLOD().threshold; - visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor->getMinimumLODThresholdMultiplier()); + visitation.info.isLODLeaf = (visitation.info.size < visitation.info.lodBase * + visitation.visitor->getMinimumLODThresholdMultiplier()); visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves(); int encodedOrder = visitation.visitor->visit(visitation.info); if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) { @@ -1628,14 +1629,15 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { if (encodedOrder == MetavoxelVisitor::STOP_RECURSION) { return true; } - return (encodedOrder == MetavoxelVisitor::STOP_RECURSION || defaultGuideToChildren(visitation, lodBase, encodedOrder)); + return (encodedOrder == MetavoxelVisitor::STOP_RECURSION || defaultGuideToChildren(visitation, encodedOrder)); } bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) { // save the core of the LOD calculation; we'll reuse it to determine whether to subdivide each attribute - float lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) * + visitation.info.lodBase = glm::distance(visitation.visitor->getLOD().position, visitation.info.getCenter()) * visitation.visitor->getLOD().threshold; - visitation.info.isLODLeaf = (visitation.info.size < lodBase * visitation.visitor->getMinimumLODThresholdMultiplier()); + visitation.info.isLODLeaf = (visitation.info.size < visitation.info.lodBase * + visitation.visitor->getMinimumLODThresholdMultiplier()); visitation.info.isLeaf = visitation.info.isLODLeaf || visitation.allInputNodesLeaves(); int encodedOrder = visitation.visitor->visit(visitation.info); if (encodedOrder == MetavoxelVisitor::SHORT_CIRCUIT) { @@ -1658,7 +1660,7 @@ bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) { return true; } if (encodedOrder & MetavoxelVisitor::ALL_NODES_REST) { - return defaultGuideToChildren(visitation, lodBase, encodedOrder); + return defaultGuideToChildren(visitation, encodedOrder); } bool onlyVisitDifferent = !(encodedOrder & MetavoxelVisitor::ALL_NODES); MetavoxelVisitation& nextVisitation = visitation.visitor->acquireVisitation(); @@ -1672,7 +1674,8 @@ bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) { for (int j = 0; j < visitation.inputNodes.size(); j++) { MetavoxelNode* node = visitation.inputNodes.at(j); const AttributeValue& parentValue = visitation.info.inputValues.at(j); - bool expand = (visitation.info.size >= lodBase * parentValue.getAttribute()->getLODThresholdMultiplier()); + bool expand = (visitation.info.size >= visitation.info.lodBase * + parentValue.getAttribute()->getLODThresholdMultiplier()); MetavoxelNode* child = (node && expand) ? node->getChild(index) : NULL; nextVisitation.info.inputValues[j] = ((nextVisitation.inputNodes[j] = child)) ? child->getAttributeValue(parentValue.getAttribute()) : parentValue.getAttribute()->inherit(parentValue); @@ -1686,7 +1689,7 @@ bool DefaultMetavoxelGuide::guideToDifferent(MetavoxelVisitation& visitation) { } for (int j = 0; j < visitation.outputNodes.size(); j++) { MetavoxelNode* node = visitation.outputNodes.at(j); - MetavoxelNode* child = (node && (visitation.info.size >= lodBase * + MetavoxelNode* child = (node && (visitation.info.size >= visitation.info.lodBase * visitation.visitor->getOutputs().at(j)->getLODThresholdMultiplier())) ? node->getChild(index) : NULL; nextVisitation.outputNodes[j] = child; } @@ -1911,6 +1914,12 @@ MetavoxelVisitation::MetavoxelVisitation(MetavoxelVisitation* previous, MetavoxelVisitation::MetavoxelVisitation() { } +bool MetavoxelVisitation::isInputLeaf(int index) const { + MetavoxelNode* node = inputNodes.at(index); + return !node || node->isLeaf() || info.size < info.lodBase * + info.inputValues.at(index).getAttribute()->getLODThresholdMultiplier(); +} + bool MetavoxelVisitation::allInputNodesLeaves() const { foreach (MetavoxelNode* node, inputNodes) { if (node && !node->isLeaf()) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 8a240b9d7b..3efb0fc8f2 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -278,6 +278,7 @@ public: float size; ///< the size of the voxel in all dimensions QVector inputValues; QVector outputValues; + float lodBase; bool isLODLeaf; bool isLeaf; @@ -537,6 +538,7 @@ public: MetavoxelVisitation(MetavoxelVisitation* previous, MetavoxelVisitor* visitor, int inputNodesSize, int outputNodesSize); MetavoxelVisitation(); + bool isInputLeaf(int index) const; bool allInputNodesLeaves() const; AttributeValue getInheritedOutputValue(int index) const; }; diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index 549931e030..c9c2d32b7e 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -445,71 +445,11 @@ PaintHeightfieldMaterialEditVisitor::PaintHeightfieldMaterialEditVisitor(const g _material(material), _color(color) { - glm::vec3 extents(_radius, _radius, _radius); + const float LARGE_EXTENT = 100000.0f; + glm::vec3 extents(_radius, LARGE_EXTENT, _radius); _bounds = Box(_position - extents, _position + extents); } -static QHash countIndices(const QByteArray& contents) { - QHash counts; - for (const uchar* src = (const uchar*)contents.constData(), *end = src + contents.size(); src != end; src++) { - if (*src != 0) { - counts[*src]++; - } - } - return counts; -} - -uchar getMaterialIndex(const SharedObjectPointer& material, QVector& materials, QByteArray& contents) { - if (!(material && static_cast(material.data())->getDiffuse().isValid())) { - return 0; - } - // first look for a matching existing material, noting the first reusable slot - int firstEmptyIndex = -1; - for (int i = 0; i < materials.size(); i++) { - const SharedObjectPointer& existingMaterial = materials.at(i); - if (existingMaterial) { - if (existingMaterial->equals(material.data())) { - return i + 1; - } - } else if (firstEmptyIndex == -1) { - firstEmptyIndex = i; - } - } - // if nothing found, use the first empty slot or append - if (firstEmptyIndex != -1) { - materials[firstEmptyIndex] = material; - return firstEmptyIndex + 1; - } - if (materials.size() < EIGHT_BIT_MAXIMUM) { - materials.append(material); - return materials.size(); - } - // last resort: find the least-used material and remove it - QHash counts = countIndices(contents); - uchar materialIndex = 0; - int lowestCount = INT_MAX; - for (QHash::const_iterator it = counts.constBegin(); it != counts.constEnd(); it++) { - if (it.value() < lowestCount) { - materialIndex = it.key(); - lowestCount = it.value(); - } - } - contents.replace((char)materialIndex, (char)0); - return materialIndex; -} - -void clearUnusedMaterials(QVector& materials, QByteArray& contents) { - QHash counts = countIndices(contents); - for (int i = 0; i < materials.size(); i++) { - if (counts.value(i + 1) == 0) { - materials[i] = SharedObjectPointer(); - } - } - while (!(materials.isEmpty() || materials.last())) { - materials.removeLast(); - } -} - int PaintHeightfieldMaterialEditVisitor::visit(MetavoxelInfo& info) { if (!info.getBounds().intersects(_bounds)) { return STOP_RECURSION; @@ -581,7 +521,6 @@ int PaintHeightfieldMaterialEditVisitor::visit(MetavoxelInfo& info) { uchar* lineDest = (uchar*)contents.data() + (int)z * size + (int)startX; float squaredRadius = scaledRadius * scaledRadius; bool changed = false; - QHash counts; for (float endZ = qMin(end.z, (float)highest); z <= endZ; z += 1.0f) { uchar* dest = lineDest; for (float x = startX; x <= endX; x += 1.0f, dest++) { diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 600eb57166..9b513c96f1 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -130,11 +130,11 @@ const QByteArray& DataServerAccountInfo::getUsernameSignature() { reinterpret_cast(&privateKeyData), _privateKey.size()); if (rsaPrivateKey) { - QByteArray usernameByteArray = _username.toUtf8(); + QByteArray lowercaseUsername = _username.toLower().toUtf8(); _usernameSignature.resize(RSA_size(rsaPrivateKey)); - int encryptReturn = RSA_private_encrypt(usernameByteArray.size(), - reinterpret_cast(usernameByteArray.constData()), + int encryptReturn = RSA_private_encrypt(lowercaseUsername.size(), + reinterpret_cast(lowercaseUsername.constData()), reinterpret_cast(_usernameSignature.data()), rsaPrivateKey, RSA_PKCS1_PADDING); diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index 97e9721356..11674a948e 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -9,9 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include +#include +#include +#include #include "HifiSockAddr.h" @@ -36,13 +36,20 @@ HifiSockAddr::HifiSockAddr(const HifiSockAddr& otherSockAddr) { _port = otherSockAddr._port; } -HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) { - // lookup the IP by the hostname - QHostInfo hostInfo = QHostInfo::fromName(hostname); - foreach(const QHostAddress& address, hostInfo.addresses()) { - if (address.protocol() == QAbstractSocket::IPv4Protocol) { - _address = address; - _port = hostOrderPort; +HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup) : + _address(hostname), + _port(hostOrderPort) +{ + // if we parsed an IPv4 address out of the hostname, don't look it up + if (_address.protocol() != QAbstractSocket::IPv4Protocol) { + // lookup the IP by the hostname + if (shouldBlockForLookup) { + qDebug() << "Asynchronously looking up IP address for hostname" << hostname; + QHostInfo result = QHostInfo::fromName(hostname); + handleLookupResult(result); + } else { + int lookupID = QHostInfo::lookupHost(hostname, this, SLOT(handleLookupResult(QHostInfo))); + qDebug() << "Synchronously looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID; } } } @@ -75,6 +82,22 @@ bool HifiSockAddr::operator==(const HifiSockAddr& rhsSockAddr) const { return _address == rhsSockAddr._address && _port == rhsSockAddr._port; } +void HifiSockAddr::handleLookupResult(const QHostInfo& hostInfo) { + if (hostInfo.error() != QHostInfo::NoError) { + qDebug() << "Lookup failed for" << hostInfo.lookupId() << ":" << hostInfo.errorString(); + } + + foreach(const QHostAddress& address, hostInfo.addresses()) { + // just take the first IPv4 address + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + _address = address; + qDebug() << "QHostInfo lookup result for" + << hostInfo.hostName() << "with lookup ID" << hostInfo.lookupId() << "is" << address.toString(); + break; + } + } +} + QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr) { debug.nospace() << sockAddr._address.toString().toLocal8Bit().constData() << ":" << sockAddr._port; return debug.space(); diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index 42f815390a..064f8032ca 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -19,14 +19,15 @@ #include #endif -#include +#include -class HifiSockAddr { +class HifiSockAddr : public QObject { + Q_OBJECT public: HifiSockAddr(); HifiSockAddr(const QHostAddress& address, quint16 port); HifiSockAddr(const HifiSockAddr& otherSockAddr); - HifiSockAddr(const QString& hostname, quint16 hostOrderPort); + HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false); HifiSockAddr(const sockaddr* sockaddr); bool isNull() const { return _address.isNull() && _port == 0; } @@ -51,6 +52,8 @@ public: friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr); friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr); friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr); +private slots: + void handleLookupResult(const QHostInfo& hostInfo); private: QHostAddress _address; quint16 _port; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 5c4dc6cea2..043f0621bb 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -73,6 +73,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short _dtlsSocket(NULL), _localSockAddr(), _publicSockAddr(), + _stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT), _numCollectedPackets(0), _numCollectedBytes(0), _packetStatTimer() @@ -583,11 +584,8 @@ void LimitedNodeList::sendSTUNRequest() { QUuid randomUUID = QUuid::createUuid(); memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); - // lookup the IP for the STUN server - HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), - stunSockAddr.getAddress(), stunSockAddr.getPort()); + _stunSockAddr.getAddress(), _stunSockAddr.getPort()); } void LimitedNodeList::rebindNodeSocket() { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 73381d01a5..64495fbd34 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -163,6 +163,7 @@ protected: QUdpSocket* _dtlsSocket; HifiSockAddr _localSockAddr; HifiSockAddr _publicSockAddr; + HifiSockAddr _stunSockAddr; int _numCollectedPackets; int _numCollectedBytes; QElapsedTimer _packetStatTimer; diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index c1348e28c7..0549c60134 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -583,6 +583,13 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const return result; } +PickRay ViewFrustum::computePickRay(float x, float y) { + glm::vec3 pickRayOrigin; + glm::vec3 pickRayDirection; + computePickRay(x, y, pickRayOrigin, pickRayDirection); + return PickRay(pickRayOrigin, pickRayDirection); +} + void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const { origin = _nearTopLeft + x*(_nearTopRight - _nearTopLeft) + y*(_nearBottomLeft - _nearTopLeft); direction = glm::normalize(origin - (_position + _orientation * _eyeOffsetPosition)); diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index f1894c7cab..1fd306617b 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -17,6 +17,8 @@ #include #include +#include + #include "AABox.h" #include "AACube.h" #include "Plane.h" @@ -105,6 +107,7 @@ public: bool isVerySimilar(const ViewFrustum& compareTo, bool debug = false) const; bool isVerySimilar(const ViewFrustum* compareTo, bool debug = false) const { return isVerySimilar(*compareTo, debug); } + PickRay computePickRay(float x, float y); void computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const; void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearValue, float& farValue, diff --git a/libraries/script-engine/src/HFActionEvent.cpp b/libraries/script-engine/src/HFActionEvent.cpp index 8a966a3fb3..780788cfca 100644 --- a/libraries/script-engine/src/HFActionEvent.cpp +++ b/libraries/script-engine/src/HFActionEvent.cpp @@ -11,9 +11,9 @@ #include "HFActionEvent.h" -HFActionEvent::HFActionEvent(QEvent::Type type, const QPointF& localPosition) : +HFActionEvent::HFActionEvent(QEvent::Type type, const PickRay& actionRay) : HFMetaEvent(type), - localPosition(localPosition) + actionRay(actionRay) { } @@ -30,8 +30,7 @@ QEvent::Type HFActionEvent::endType() { QScriptValue HFActionEvent::toScriptValue(QScriptEngine* engine, const HFActionEvent& event) { QScriptValue obj = engine->newObject(); - obj.setProperty("x", event.localPosition.x()); - obj.setProperty("y", event.localPosition.y()); + obj.setProperty("actionRay", pickRayToScriptValue(engine, event.actionRay)); return obj; } diff --git a/libraries/script-engine/src/HFActionEvent.h b/libraries/script-engine/src/HFActionEvent.h index c094161053..c16b165ae3 100644 --- a/libraries/script-engine/src/HFActionEvent.h +++ b/libraries/script-engine/src/HFActionEvent.h @@ -12,14 +12,17 @@ #ifndef hifi_HFActionEvent_h #define hifi_HFActionEvent_h -#include "HFMetaEvent.h" #include +#include + +#include "HFMetaEvent.h" + class HFActionEvent : public HFMetaEvent { public: HFActionEvent() {}; - HFActionEvent(QEvent::Type type, const QPointF& localPosition); + HFActionEvent(QEvent::Type type, const PickRay& actionRay); static QEvent::Type startType(); static QEvent::Type endType(); @@ -27,7 +30,7 @@ public: static QScriptValue toScriptValue(QScriptEngine* engine, const HFActionEvent& event); static void fromScriptValue(const QScriptValue& object, HFActionEvent& event); - QPointF localPosition; + PickRay actionRay; }; Q_DECLARE_METATYPE(HFActionEvent) diff --git a/libraries/script-engine/src/MouseEvent.h b/libraries/script-engine/src/MouseEvent.h index 7555f2ea5a..1936e6b58e 100644 --- a/libraries/script-engine/src/MouseEvent.h +++ b/libraries/script-engine/src/MouseEvent.h @@ -21,6 +21,8 @@ public: static QScriptValue toScriptValue(QScriptEngine* engine, const MouseEvent& event); static void fromScriptValue(const QScriptValue& object, MouseEvent& event); + + QScriptValue toScriptValue(QScriptEngine* engine) const { return MouseEvent::toScriptValue(engine, *this); } int x; int y; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 7fe662740a..b8884be845 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -53,7 +53,8 @@ void qURLFromScriptValue(const QScriptValue& object, QUrl& url); class PickRay { public: - PickRay() : origin(0.0f), direction(0.0f) { } + PickRay() : origin(0.0f), direction(0.0f) { } + PickRay(const glm::vec3& origin, const glm::vec3 direction) : origin(origin), direction(direction) {} glm::vec3 origin; glm::vec3 direction; };