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

Conflicts:
	interface/src/ui/MetavoxelEditor.cpp
	interface/src/ui/overlays/Sphere3DOverlay.cpp
This commit is contained in:
Sam Gateau 2014-10-03 17:31:05 -07:00
commit bd5c6517a5
120 changed files with 7360 additions and 2552 deletions

View file

@ -58,6 +58,7 @@ endforeach()
# targets on all platforms
add_subdirectory(assignment-client)
add_subdirectory(domain-server)
add_subdirectory(ice-server)
add_subdirectory(interface)
add_subdirectory(tests)
add_subdirectory(tools)

View file

@ -12,7 +12,28 @@
"name": "id",
"label": "Domain ID",
"help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank."
}
},
{
"name": "automatic_networking",
"label": "Automatic Networking",
"help": "This defines how other nodes in the High Fidelity metaverse will be able to reach your domain-server.<br/>If you don't want to deal with any network settings, use full automatic networking.",
"default": "disabled",
"type": "select",
"options": [
{
"value": "full",
"label": "Full: update both the IP address and port to reach my server"
},
{
"value": "ip",
"label": "IP Only: update just my IP address, I will open the port manually"
},
{
"value": "disabled",
"label": "None: use the network information I have entered for this domain at data.highfidelity.io"
}
]
}
]
},
{
@ -53,11 +74,11 @@
},
{
"name": "attenuation_per_doubling_in_distance",
"label": "Attenuattion per doubling in distance",
"label": "Attenuation per doubling in distance",
"help": "Factor between 0.0 and 1.0 (0.0: No attenuation, 1.0: extreme attenuation)",
"placeholder": "0.18",
"default": "0.18",
"advanced": true
"advanced": false
},
{
"name": "dynamic_jitter_buffer",

View file

@ -64,7 +64,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
@ -74,8 +74,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
loadExistingSessionsFromSettings();
// check if we have the flag that enables dynamic IP
setupDynamicIPAddressUpdating();
// setup automatic networking settings with data server
setupAutomaticNetworking();
}
}
@ -156,6 +156,16 @@ bool DomainServer::optionallySetupOAuth() {
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
_oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
_oauthProviderURL = DEFAULT_NODE_AUTH_URL;
}
AccountManager& accountManager = AccountManager::getInstance();
accountManager.disableSettingsFilePersistence();
accountManager.setAuthURL(_oauthProviderURL);
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString();
@ -230,41 +240,37 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
addStaticAssignmentsToQueue();
}
const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
bool DomainServer::hasOAuthProviderAndAuthInformation() {
bool DomainServer::didSetupAccountManagerWithAccessToken() {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.hasValidAccessToken()) {
// we already gave the account manager a valid access token
return true;
}
if (!_oauthProviderURL.isEmpty()) {
// check for an access-token in our settings, can optionally be overidden by env value
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
const QString ENV_ACCESS_TOKEN_KEY = "DOMAIN_SERVER_ACCESS_TOKEN";
static bool hasAttemptedAuthWithOAuthProvider = false;
QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY);
if (!hasAttemptedAuthWithOAuthProvider) {
AccountManager& accountManager = AccountManager::getInstance();
accountManager.setAuthURL(_oauthProviderURL);
if (accessToken.isEmpty()) {
const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accountManager.hasValidAccessToken()) {
// we don't have a valid access token so we need to get one
// check if we have a username and password set via env
QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
if (!username.isEmpty() && !password.isEmpty()) {
accountManager.requestAccessToken(username, password);
// connect to loginFailed signal from AccountManager so we can quit if that is the case
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
} else {
qDebug() << "Missing access-token or username and password combination. domain-server will now quit.";
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
return false;
}
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
accessToken = accessTokenVariant->toString();
} else {
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."
<< "Set an access token via the web interface, in your user or master config"
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
return false;
}
hasAttemptedAuthWithOAuthProvider = true;
}
// give this access token to the AccountManager
accountManager.setAccessTokenForCurrentAuthURL(accessToken);
return true;
} else {
@ -282,7 +288,7 @@ bool DomainServer::optionallySetupAssignmentPayment() {
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
didSetupAccountManagerWithAccessToken()) {
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
@ -304,49 +310,71 @@ bool DomainServer::optionallySetupAssignmentPayment() {
return true;
}
void DomainServer::setupDynamicIPAddressUpdating() {
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full";
const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip";
const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled";
void DomainServer::setupAutomaticNetworking() {
const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking";
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
if (!didSetupAccountManagerWithAccessToken()) {
qDebug() << "Cannot setup domain-server automatic networking without an access token.";
qDebug() << "Please add an access token to your config file or via the web interface.";
return;
}
if (settingsMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
settingsMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
QString automaticNetworkValue =
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
if (automaticNetworkValue == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
automaticNetworkValue == FULL_AUTOMATIC_NETWORKING_VALUE) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
const QUuid& domainID = nodeList->getSessionUUID();
if (!domainID.isNull()) {
qDebug() << "domain-server IP address will be updated for domain with ID"
qDebug() << "domain-server" << automaticNetworkValue << "automatic networking enabled for ID"
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000;
// setup our timer to check our IP via stun every 30 seconds
// setup our timer to check our IP via stun every X seconds
QTimer* dynamicIPTimer = new QTimer(this);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentIPAddressViaSTUN);
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
// send public socket changes to the data server so nodes can find us at our new IP
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer);
if (!AccountManager::getInstance().hasValidAccessToken()) {
// we don't have an access token to talk to data-web yet, so
// check our IP address as soon as we get an AccountManager access token
connect(&AccountManager::getInstance(), &AccountManager::loginComplete,
this, &DomainServer::requestCurrentIPAddressViaSTUN);
if (automaticNetworkValue == 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
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate);
} else {
// access token good to go, attempt to update our IP now
requestCurrentIPAddressViaSTUN();
dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS);
// setup a timer to heartbeat with the ice-server every so often
QTimer* iceHeartbeatTimer = new QTimer(this);
connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates);
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
// call our sendHeartbeaToIceServer immediately anytime a public address changes
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHearbeatToIceServer);
// tell the data server which type of automatic networking we are using
updateNetworkingInfoWithDataServer(automaticNetworkValue);
}
} else {
qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID."
<< "Please add an id to your config.json or pass it with the command line argument --id.";
qDebug() << "Failed dynamic IP address update setup. domain-server will now quit.";
// attempt to update our sockets now
requestCurrentPublicSocketViaSTUN();
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
} else {
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
<< "Please add an ID to your config file or via the web interface.";
return;
}
} else {
updateNetworkingInfoWithDataServer(automaticNetworkValue);
}
}
@ -556,9 +584,17 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|| (isAssignment && matchingQueuedAssignment)) {
// this was either not a static assignment or it was and we had a matching one in the queue
QUuid nodeUUID;
// create a new session UUID for this node
QUuid nodeUUID = QUuid::createUuid();
if (_connectingICEPeers.contains(packetUUID) || _connectedICEPeers.contains(packetUUID)) {
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
nodeUUID = packetUUID;
} else {
// we got a packetUUID we didn't recognize, just add the node
nodeUUID = QUuid::createUuid();
}
SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType,
publicSockAddr, localSockAddr);
@ -675,6 +711,13 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
// if we've established a connection via ICE with this peer, use that socket
// otherwise just try to reply back to them on their sending socket (although that may not work)
HifiSockAddr destinationSockAddr = _connectedICEPeers.value(node->getUUID());
if (destinationSockAddr.isNull()) {
destinationSockAddr = senderSockAddr;
}
if (nodeInterestList.size() > 0) {
@ -912,18 +955,45 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) {
}
}
void DomainServer::requestCurrentIPAddressViaSTUN() {
void DomainServer::requestCurrentPublicSocketViaSTUN() {
LimitedNodeList::getInstance()->sendSTUNRequest();
}
void DomainServer::sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr) {
QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
const QString SOCKET_NETWORK_ADDRESS_KEY = "network_address";
const QString SOCKET_PORT_KEY = "port";
QJsonObject socketObject;
socketObject[SOCKET_NETWORK_ADDRESS_KEY] = socket.getAddress().toString();
socketObject[SOCKET_PORT_KEY] = socket.getPort();
return socketObject;
}
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) {
updateNetworkingInfoWithDataServer(IP_ONLY_AUTOMATIC_NETWORKING_VALUE, newPublicSockAddr.getAddress().toString());
}
void DomainServer::updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress) {
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID();
// setup the domain object to send to the data server
const QString DOMAIN_JSON_OBJECT = "{\"domain\":{\"network_address\":\"%1\"}}";
const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
QString domainUpdateJSON = DOMAIN_JSON_OBJECT.arg(newPublicSockAddr.getAddress().toString());
QJsonObject domainObject;
if (!networkAddress.isEmpty()) {
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
}
qDebug() << "Updating automatic networking setting in domain-server to" << newSetting;
domainObject[AUTOMATIC_NETWORKING_KEY] = newSetting;
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
QNetworkAccessManager::PutOperation,
@ -931,6 +1001,86 @@ void DomainServer::sendNewPublicSocketToDataServer(const HifiSockAddr& newPublic
domainUpdateJSON.toUtf8());
}
// todo: have data-web respond with ice-server hostname to use
void DomainServer::performICEUpdates() {
sendHearbeatToIceServer();
sendICEPingPackets();
}
void DomainServer::sendHearbeatToIceServer() {
const HifiSockAddr ICE_SERVER_SOCK_ADDR = HifiSockAddr("ice.highfidelity.io", ICE_SERVER_DEFAULT_PORT);
LimitedNodeList::getInstance()->sendHeartbeatToIceServer(ICE_SERVER_SOCK_ADDR);
}
void DomainServer::sendICEPingPackets() {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
QHash<QUuid, NetworkPeer>::iterator peer = _connectingICEPeers.begin();
while (peer != _connectingICEPeers.end()) {
if (peer->getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) {
// we've already tried to connect to this peer enough times
// remove it from our list - if it wants to re-connect it'll come back through ice-server
peer = _connectingICEPeers.erase(peer);
} else {
// send ping packets to this peer's interfaces
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
<< peer->getUUID();
// send the ping packet to the local and public sockets for this node
QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false);
nodeList->writeUnverifiedDatagram(localPingPacket, peer->getLocalSocket());
QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false);
nodeList->writeUnverifiedDatagram(publicPingPacket, peer->getPublicSocket());
peer->incrementConnectionAttempts();
// go to next peer in hash
++peer;
}
}
}
void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) {
// loop through the packet and pull out network peers
// any peer we don't have we add to the hash, otherwise we update
QDataStream iceResponseStream(packet);
iceResponseStream.skipRawData(numBytesForPacketHeader(packet));
NetworkPeer receivedPeer;
while (!iceResponseStream.atEnd()) {
iceResponseStream >> receivedPeer;
if (!_connectedICEPeers.contains(receivedPeer.getUUID())) {
if (!_connectingICEPeers.contains(receivedPeer.getUUID())) {
qDebug() << "New peer requesting connection being added to hash -" << receivedPeer;
}
_connectingICEPeers[receivedPeer.getUUID()] = receivedPeer;
}
}
}
void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
QUuid nodeUUID = uuidFromPacketHeader(packet);
NetworkPeer sendingPeer = _connectingICEPeers.take(nodeUUID);
if (!sendingPeer.isNull()) {
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
if (senderSockAddr == sendingPeer.getLocalSocket()) {
qDebug() << "Activating local socket for communication with network peer -" << sendingPeer;
_connectedICEPeers.insert(nodeUUID, sendingPeer.getLocalSocket());
} else if (senderSockAddr == sendingPeer.getPublicSocket()) {
qDebug() << "Activating public socket for communication with network peer -" << sendingPeer;
_connectedICEPeers.insert(nodeUUID, sendingPeer.getPublicSocket());
}
}
}
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
@ -972,6 +1122,19 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
case PacketTypeStunResponse:
nodeList->processSTUNResponse(receivedPacket);
break;
case PacketTypeUnverifiedPing: {
QByteArray pingReplyPacket = nodeList->constructPingReplyPacket(receivedPacket);
nodeList->writeUnverifiedDatagram(pingReplyPacket, senderSockAddr);
break;
}
case PacketTypeUnverifiedPingReply: {
processICEPingReply(receivedPacket, senderSockAddr);
break;
}
case PacketTypeIceServerHeartbeatResponse:
processICEHeartbeatResponse(receivedPacket);
break;
default:
break;
}
@ -1676,6 +1839,10 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
}
void DomainServer::nodeKilled(SharedNodePointer node) {
// remove this node from the connecting / connected ICE lists (if they exist)
_connectingICEPeers.remove(node->getUUID());
_connectedICEPeers.remove(node->getUUID());
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());

View file

@ -62,15 +62,22 @@ private slots:
void setupPendingAssignmentCredits();
void sendPendingTransactionsToServer();
void requestCurrentIPAddressViaSTUN();
void sendNewPublicSocketToDataServer(const HifiSockAddr& newPublicSockAddr);
void requestCurrentPublicSocketViaSTUN();
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void performICEUpdates();
void sendHearbeatToIceServer();
void sendICEPingPackets();
private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
bool optionallySetupOAuth();
bool optionallyReadX509KeyAndCertificate();
bool hasOAuthProviderAndAuthInformation();
bool didSetupAccountManagerWithAccessToken();
bool optionallySetupAssignmentPayment();
void setupDynamicIPAddressUpdating();
void setupAutomaticNetworking();
void updateNetworkingInfoWithDataServer(const QString& newSetting, const QString& networkAddress = QString());
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
void processICEHeartbeatResponse(const QByteArray& packet);
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
@ -130,7 +137,13 @@ private:
QSet<QUuid> _webAuthenticationStateSet;
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
HifiSockAddr _localSockAddr;
QHash<QUuid, NetworkPeer> _connectingICEPeers;
QHash<QUuid, HifiSockAddr> _connectedICEPeers;
DomainServerSettingsManager _settingsManager;
};
#endif // hifi_DomainServer_h

View file

@ -26,6 +26,10 @@
const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json";
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
const QString DESCRIPTION_NAME_KEY = "name";
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
_configMap()
@ -63,6 +67,36 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
}
}
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
if (foundValue) {
return *foundValue;
} else {
int dotIndex = keyPath.indexOf('.');
QString groupKey = keyPath.mid(0, dotIndex);
QString settingKey = keyPath.mid(dotIndex + 1);
foreach(const QVariant& group, _descriptionArray.toVariantList()) {
QVariantMap groupMap = group.toMap();
if (groupMap[DESCRIPTION_NAME_KEY].toString() == groupKey) {
foreach(const QVariant& setting, groupMap[DESCRIPTION_SETTINGS_KEY].toList()) {
QVariantMap settingMap = setting.toMap();
if (settingMap[DESCRIPTION_NAME_KEY].toString() == settingKey) {
return settingMap[SETTING_DEFAULT_KEY];
}
}
return QVariant();
}
}
}
return QVariant();
}
const QString SETTINGS_PATH = "/settings.json";
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
@ -127,10 +161,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
return false;
}
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
const QString DESCRIPTION_NAME_KEY = "name";
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
QJsonObject responseObject;

View file

@ -26,6 +26,7 @@ public:
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList);
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
private:

View file

@ -18,7 +18,7 @@ var controlVoxelSize = 0.25;
var controlVoxelPosition = { x: 2000 , y: 0, z: 0 };
// Script. DO NOT MODIFY BEYOND THIS LINE.
Script.include("toolBars.js");
Script.include("libraries/toolBars.js");
var DO_NOTHING = 0;
var PLAY = 1;

View file

@ -9,7 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("toolBars.js");
Script.include("libraries/toolBars.js");
var recordingFile = "recording.rec";

View file

@ -22,8 +22,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("toolBars.js");
Script.include("libraries/toolBars.js");
var windowDimensions = Controller.getViewportDimensions();
var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/";
var toolHeight = 50;
@ -35,6 +34,9 @@ var LASER_LENGTH_FACTOR = 500;
var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;
var allowLargeModels = false;
var allowSmallModels = false;
var wantEntityGlow = false;
var LEFT = 0;
var RIGHT = 1;
@ -1568,6 +1570,8 @@ var ExportMenu = function (opts) {
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
};
var ModelImporter = function (opts) {
var self = this;
@ -1974,12 +1978,11 @@ function controller(wichSide) {
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
if (angularSize > MAX_ANGULAR_SIZE) {
print("Angular size too big: " + angularSize);
return { valid: false };
}
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (0 < x && sizeOK) {
return { valid: true, x: x, y: y, z: z };
}
return { valid: false };
@ -2027,9 +2030,14 @@ function controller(wichSide) {
var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14;
if (intersection.accurate && intersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (intersection.accurate && intersection.entityID.isKnownID && sizeOK) {
this.glowedIntersectingModel = intersection.entityID;
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 });
if (wantEntityGlow) {
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 });
}
}
}
}
@ -2526,16 +2534,16 @@ function mousePressEvent(event) {
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
if (angularSize < MAX_ANGULAR_SIZE) {
entitySelected = true;
selectedEntityID = foundEntity;
selectedEntityProperties = properties;
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
} else {
print("Angular size too big: " + angularSize);
}
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));
}
}
}
@ -2583,11 +2591,17 @@ function mouseMoveEvent(event) {
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(),
entityIntersection.properties.position)) * 180 / 3.14;
if (entityIntersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 });
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (entityIntersection.entityID.isKnownID && sizeOK) {
if (wantEntityGlow) {
Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 });
}
glowedEntityID = entityIntersection.entityID;
}
}
return;
}
@ -2745,10 +2759,17 @@ function setupModelMenus() {
}
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" });
}
function cleanupModelMenus() {
@ -2760,6 +2781,8 @@ function cleanupModelMenus() {
}
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Select Large Models");
Menu.removeMenuItem("Edit", "Allow Select Small Models");
Menu.removeSeparator("File", "Models");
Menu.removeMenuItem("File", "Export Models");
@ -2920,7 +2943,11 @@ function showPropertiesForm() {
function handeMenuEvent(menuItem) {
print("menuItemEvent() in JS... menuItem=" + menuItem);
if (menuItem == "Delete") {
if (menuItem == "Allow Select Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Select Small Models");
} else if (menuItem == "Allow Select Large Models") {
allowLargeModels = Menu.isOptionChecked("Allow Select Large Models");
} else if (menuItem == "Delete") {
if (leftController.grabbing) {
print(" Delete Entity.... leftController.entityID="+ leftController.entityID);
Entities.deleteEntity(leftController.entityID);

View file

@ -14,7 +14,7 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("toolBars.js");
Script.include("libraries/toolBars.js");
const LEFT_PALM = 0;
const LEFT_TIP = 1;

View file

@ -14,6 +14,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
hands,
@ -30,7 +31,11 @@ var leapHands = (function () {
CALIBRATED = 2,
CALIBRATION_TIME = 1000, // milliseconds
PI = 3.141593,
isWindows;
isWindows,
avatarScale,
avatarFaceModelURL,
avatarSkeletonModelURL,
settingsTimer;
function printSkeletonJointNames() {
var jointNames,
@ -164,6 +169,10 @@ var leapHands = (function () {
calibrationStatus = CALIBRATING;
avatarScale = MyAvatar.scale;
avatarFaceModelURL = MyAvatar.faceModelURL;
avatarSkeletonModelURL = MyAvatar.skeletonModelURL;
// Set avatar arms vertical, forearms horizontal, as "zero" position for calibration
MyAvatar.setJointData("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, -90.0));
MyAvatar.setJointData("LeftForeArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 180.0));
@ -189,6 +198,37 @@ var leapHands = (function () {
return false;
}
function setIsOnHMD() {
isOnHMD = Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM);
if (isOnHMD) {
print("Leap Motion: Is on HMD");
// Offset of Leap Motion origin from physical eye position
hands[0].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET };
hands[1].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET };
calibrationStatus = CALIBRATED;
} else {
print("Leap Motion: Is on desk");
calibrationStatus = UNCALIBRATED;
}
}
function checkSettings() {
// There is no "scale changed" event so we need check periodically.
if (!isOnHMD && calibrationStatus > UNCALIBRATED && (MyAvatar.scale !== avatarScale
|| MyAvatar.faceModelURL !== avatarFaceModelURL
|| MyAvatar.skeletonModelURL !== avatarSkeletonModelURL)) {
print("Leap Motion: Recalibrate because avatar body or scale changed");
calibrationStatus = UNCALIBRATED;
}
// There is a "menu changed" event but we may as well check here.
if (isOnHMD !== Menu.isOptionChecked(LEAP_ON_HMD_MENU_ITEM)) {
setIsOnHMD();
}
}
function setUp() {
// TODO: Leap Motion controller joint naming doesn't match up with skeleton joint naming; numbers are out by 1.
@ -267,19 +307,9 @@ var leapHands = (function () {
]
];
isOnHMD = Menu.isOptionChecked("Leap Motion on HMD");
if (isOnHMD) {
print("Leap Motion is on HMD");
setIsOnHMD();
// Offset of Leap Motion origin from physical eye position
hands[0].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET };
hands[1].zeroPosition = { x: 0.0, y: 0.0, z: HMD_OFFSET + LEAP_OFFSET };
calibrationStatus = CALIBRATED;
} else {
print("Leap Motion is on desk");
calibrationStatus = UNCALIBRATED;
}
settingsTimer = Script.setInterval(checkSettings, 2000);
}
function moveHands() {
@ -302,7 +332,7 @@ var leapHands = (function () {
if (hands[h].controller.isActive()) {
// Calibrate when and if a controller is first active.
// Calibrate if necessary.
if (!checkCalibration()) {
return;
}
@ -430,6 +460,8 @@ var leapHands = (function () {
i,
j;
Script.clearInterval(settingsTimer);
for (h = 0; h < NUM_HANDS; h += 1) {
Controller.releaseInputController(hands[h].controller);
Controller.releaseInputController(wrists[h].controller);

View file

@ -0,0 +1,224 @@
//
// ExportMenu.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
ExportMenu = function (opts) {
var self = this;
var windowDimensions = Controller.getViewportDimensions();
var pos = { x: windowDimensions.x / 2, y: windowDimensions.y - 100 };
this._onClose = opts.onClose || function () { };
this._position = { x: 0.0, y: 0.0, z: 0.0 };
this._scale = 1.0;
var minScale = 1;
var maxScale = 32768;
var titleWidth = 120;
var locationWidth = 100;
var scaleWidth = 144;
var exportWidth = 100;
var cancelWidth = 100;
var margin = 4;
var height = 30;
var outerHeight = height + (2 * margin);
var buttonColor = { red: 128, green: 128, blue: 128 };
var SCALE_MINUS = scaleWidth * 40.0 / 100.0;
var SCALE_PLUS = scaleWidth * 63.0 / 100.0;
var fullWidth = locationWidth + scaleWidth + exportWidth + cancelWidth + (2 * margin);
var offset = fullWidth / 2;
pos.x -= offset;
var background = Overlays.addOverlay("text", {
x: pos.x,
y: pos.y,
opacity: 1,
width: fullWidth,
height: outerHeight,
backgroundColor: { red: 200, green: 200, blue: 200 },
text: "",
});
var titleText = Overlays.addOverlay("text", {
x: pos.x,
y: pos.y - height,
font: { size: 14 },
width: titleWidth,
height: height,
backgroundColor: { red: 255, green: 255, blue: 255 },
color: { red: 255, green: 255, blue: 255 },
text: "Export Models"
});
var locationButton = Overlays.addOverlay("text", {
x: pos.x + margin,
y: pos.y + margin,
width: locationWidth,
height: height,
color: { red: 255, green: 255, blue: 255 },
text: "0, 0, 0",
});
var scaleOverlay = Overlays.addOverlay("image", {
x: pos.x + margin + locationWidth,
y: pos.y + margin,
width: scaleWidth,
height: height,
subImage: { x: 0, y: 3, width: 144, height: height },
imageURL: toolIconUrl + "voxel-size-selector.svg",
alpha: 0.9,
});
var scaleViewWidth = 40;
var scaleView = Overlays.addOverlay("text", {
x: pos.x + margin + locationWidth + SCALE_MINUS,
y: pos.y + margin,
width: scaleViewWidth,
height: height,
alpha: 0.0,
color: { red: 255, green: 255, blue: 255 },
text: "1"
});
var exportButton = Overlays.addOverlay("text", {
x: pos.x + margin + locationWidth + scaleWidth,
y: pos.y + margin,
width: exportWidth,
height: height,
color: { red: 0, green: 255, blue: 255 },
text: "Export"
});
var cancelButton = Overlays.addOverlay("text", {
x: pos.x + margin + locationWidth + scaleWidth + exportWidth,
y: pos.y + margin,
width: cancelWidth,
height: height,
color: { red: 255, green: 255, blue: 255 },
text: "Cancel"
});
var voxelPreview = Overlays.addOverlay("cube", {
position: { x: 0, y: 0, z: 0 },
size: this._scale,
color: { red: 255, green: 255, blue: 0 },
alpha: 1,
solid: false,
visible: true,
lineWidth: 4
});
this.parsePosition = function (str) {
var parts = str.split(',');
if (parts.length == 3) {
var x = parseFloat(parts[0]);
var y = parseFloat(parts[1]);
var z = parseFloat(parts[2]);
if (isFinite(x) && isFinite(y) && isFinite(z)) {
return { x: x, y: y, z: z };
}
}
return null;
};
this.showPositionPrompt = function () {
var positionStr = self._position.x + ", " + self._position.y + ", " + self._position.z;
while (1) {
positionStr = Window.prompt("Position to export form:", positionStr);
if (positionStr == null) {
break;
}
var position = self.parsePosition(positionStr);
if (position != null) {
self.setPosition(position.x, position.y, position.z);
break;
}
Window.alert("The position you entered was invalid.");
}
};
this.setScale = function (scale) {
self._scale = Math.min(maxScale, Math.max(minScale, scale));
Overlays.editOverlay(scaleView, { text: self._scale });
Overlays.editOverlay(voxelPreview, { size: self._scale });
}
this.decreaseScale = function () {
self.setScale(self._scale /= 2);
}
this.increaseScale = function () {
self.setScale(self._scale *= 2);
}
this.exportEntities = function() {
var x = self._position.x;
var y = self._position.y;
var z = self._position.z;
var s = self._scale;
var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo";
filename = Window.save("Select where to save", filename, "*.svo")
if (filename) {
var success = Clipboard.exportEntities(filename, x, y, z, s);
if (!success) {
Window.alert("Export failed: no models found in selected area.");
}
}
self.close();
};
this.getPosition = function () {
return self._position;
};
this.setPosition = function (x, y, z) {
self._position = { x: x, y: y, z: z };
var positionStr = x + ", " + y + ", " + z;
Overlays.editOverlay(locationButton, { text: positionStr });
Overlays.editOverlay(voxelPreview, { position: self._position });
};
this.mouseReleaseEvent = function (event) {
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (clickedOverlay == locationButton) {
self.showPositionPrompt();
} else if (clickedOverlay == exportButton) {
self.exportEntities();
} else if (clickedOverlay == cancelButton) {
self.close();
} else if (clickedOverlay == scaleOverlay) {
var x = event.x - pos.x - margin - locationWidth;
print(x);
if (x < SCALE_MINUS) {
self.decreaseScale();
} else if (x > SCALE_PLUS) {
self.increaseScale();
}
}
};
this.close = function () {
this.cleanup();
this._onClose();
};
this.cleanup = function () {
Overlays.deleteOverlay(background);
Overlays.deleteOverlay(titleText);
Overlays.deleteOverlay(locationButton);
Overlays.deleteOverlay(exportButton);
Overlays.deleteOverlay(cancelButton);
Overlays.deleteOverlay(voxelPreview);
Overlays.deleteOverlay(scaleOverlay);
Overlays.deleteOverlay(scaleView);
};
print("CONNECTING!");
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
};

View file

@ -0,0 +1,200 @@
//
// ModelImporter.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
ModelImporter = function (opts) {
var self = this;
var windowDimensions = Controller.getViewportDimensions();
var height = 30;
var margin = 4;
var outerHeight = height + (2 * margin);
var titleWidth = 120;
var cancelWidth = 100;
var fullWidth = titleWidth + cancelWidth + (2 * margin);
var localModels = Overlays.addOverlay("localmodels", {
position: { x: 1, y: 1, z: 1 },
scale: 1,
visible: false
});
var importScale = 1;
var importBoundaries = Overlays.addOverlay("cube", {
position: { x: 0, y: 0, z: 0 },
size: 1,
color: { red: 128, blue: 128, green: 128 },
lineWidth: 4,
solid: false,
visible: false
});
var pos = { x: windowDimensions.x / 2 - (fullWidth / 2), y: windowDimensions.y - 100 };
var background = Overlays.addOverlay("text", {
x: pos.x,
y: pos.y,
opacity: 1,
width: fullWidth,
height: outerHeight,
backgroundColor: { red: 200, green: 200, blue: 200 },
visible: false,
text: "",
});
var titleText = Overlays.addOverlay("text", {
x: pos.x + margin,
y: pos.y + margin,
font: { size: 14 },
width: titleWidth,
height: height,
backgroundColor: { red: 255, green: 255, blue: 255 },
color: { red: 255, green: 255, blue: 255 },
visible: false,
text: "Import Models"
});
var cancelButton = Overlays.addOverlay("text", {
x: pos.x + margin + titleWidth,
y: pos.y + margin,
width: cancelWidth,
height: height,
color: { red: 255, green: 255, blue: 255 },
visible: false,
text: "Close"
});
this._importing = false;
this.setImportVisible = function (visible) {
Overlays.editOverlay(importBoundaries, { visible: visible });
Overlays.editOverlay(localModels, { visible: visible });
Overlays.editOverlay(cancelButton, { visible: visible });
Overlays.editOverlay(titleText, { visible: visible });
Overlays.editOverlay(background, { visible: visible });
};
var importPosition = { x: 0, y: 0, z: 0 };
this.moveImport = function (position) {
importPosition = position;
Overlays.editOverlay(localModels, {
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
});
Overlays.editOverlay(importBoundaries, {
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
});
}
this.mouseMoveEvent = function (event) {
if (self._importing) {
var pickRay = Camera.computePickRay(event.x, event.y);
var intersection = Voxels.findRayIntersection(pickRay);
var distance = 2;// * self._scale;
if (false) {//intersection.intersects) {
var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection));
if (intersectionDistance < distance) {
distance = intersectionDistance * 0.99;
}
}
var targetPosition = {
x: pickRay.origin.x + (pickRay.direction.x * distance),
y: pickRay.origin.y + (pickRay.direction.y * distance),
z: pickRay.origin.z + (pickRay.direction.z * distance)
};
if (targetPosition.x < 0) targetPosition.x = 0;
if (targetPosition.y < 0) targetPosition.y = 0;
if (targetPosition.z < 0) targetPosition.z = 0;
var nudgeFactor = 1;
var newPosition = {
x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor,
y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor,
z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor
}
self.moveImport(newPosition);
}
}
this.mouseReleaseEvent = function (event) {
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (clickedOverlay == cancelButton) {
self._importing = false;
self.setImportVisible(false);
}
};
// Would prefer to use {4} for the coords, but it would only capture the last digit.
var fileRegex = /__(.+)__(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)__/;
this.doImport = function () {
if (!self._importing) {
var filename = Window.browse("Select models to import", "", "*.svo")
if (filename) {
parts = fileRegex.exec(filename);
if (parts == null) {
Window.alert("The file you selected does not contain source domain or location information");
} else {
var hostname = parts[1];
var x = parts[2];
var y = parts[3];
var z = parts[4];
var s = parts[5];
importScale = s;
if (hostname != location.hostname) {
if (!Window.confirm(("These models were not originally exported from this domain. Continue?"))) {
return;
}
} else {
if (Window.confirm(("Would you like to import back to the source location?"))) {
var success = Clipboard.importEntities(filename);
if (success) {
Clipboard.pasteEntities(x, y, z, 1);
} else {
Window.alert("There was an error importing the entity file.");
}
return;
}
}
}
var success = Clipboard.importEntities(filename);
if (success) {
self._importing = true;
self.setImportVisible(true);
Overlays.editOverlay(importBoundaries, { size: s });
} else {
Window.alert("There was an error importing the entity file.");
}
}
}
}
this.paste = function () {
if (self._importing) {
// self._importing = false;
// self.setImportVisible(false);
Clipboard.pasteEntities(importPosition.x, importPosition.y, importPosition.z, 1);
}
}
this.cleanup = function () {
Overlays.deleteOverlay(localModels);
Overlays.deleteOverlay(importBoundaries);
Overlays.deleteOverlay(cancelButton);
Overlays.deleteOverlay(titleText);
Overlays.deleteOverlay(background);
}
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
Controller.mouseMoveEvent.connect(this.mouseMoveEvent);
};

View file

@ -0,0 +1,82 @@
//
// ToolTip.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function Tooltip() {
this.x = 285;
this.y = 115;
this.width = 500;
this.height = 180; // 145;
this.margin = 5;
this.decimals = 3;
this.textOverlay = Overlays.addOverlay("text", {
x: this.x,
y: this.y,
width: this.width,
height: this.height,
margin: this.margin,
text: "",
color: { red: 128, green: 128, blue: 128 },
alpha: 0.2,
visible: false
});
this.show = function (doShow) {
Overlays.editOverlay(this.textOverlay, { visible: doShow });
}
this.updateText = function(properties) {
var angles = Quat.safeEulerAngles(properties.rotation);
var text = "Entity Properties:\n"
text += "type: " + properties.type + "\n"
text += "X: " + properties.position.x.toFixed(this.decimals) + "\n"
text += "Y: " + properties.position.y.toFixed(this.decimals) + "\n"
text += "Z: " + properties.position.z.toFixed(this.decimals) + "\n"
text += "Pitch: " + angles.x.toFixed(this.decimals) + "\n"
text += "Yaw: " + angles.y.toFixed(this.decimals) + "\n"
text += "Roll: " + angles.z.toFixed(this.decimals) + "\n"
text += "Dimensions: " + properties.dimensions.x.toFixed(this.decimals) + ", "
+ properties.dimensions.y.toFixed(this.decimals) + ", "
+ properties.dimensions.z.toFixed(this.decimals) + "\n";
text += "Natural Dimensions: " + properties.naturalDimensions.x.toFixed(this.decimals) + ", "
+ properties.naturalDimensions.y.toFixed(this.decimals) + ", "
+ properties.naturalDimensions.z.toFixed(this.decimals) + "\n";
text += "ID: " + properties.id + "\n"
if (properties.type == "Model") {
text += "Model URL: " + properties.modelURL + "\n"
text += "Animation URL: " + properties.animationURL + "\n"
text += "Animation is playing: " + properties.animationIsPlaying + "\n"
if (properties.sittingPoints && properties.sittingPoints.length > 0) {
text += properties.sittingPoints.length + " Sitting points: "
for (var i = 0; i < properties.sittingPoints.length; ++i) {
text += properties.sittingPoints[i].name + " "
}
} else {
text += "No sitting points" + "\n"
}
}
if (properties.lifetime > -1) {
text += "Lifetime: " + properties.lifetime + "\n"
}
text += "Age: " + properties.ageAsText + "\n"
text += "Mass: " + properties.mass + "\n"
text += "Script: " + properties.script + "\n"
Overlays.editOverlay(this.textOverlay, { text: text });
}
this.cleanup = function () {
Overlays.deleteOverlay(this.textOverlay);
}
}
tooltip = new Tooltip();

View file

@ -0,0 +1,62 @@
//
// dataViewHelpers.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
if (typeof DataView.prototype.indexOf !== "function") {
DataView.prototype.indexOf = function (searchString, position) {
var searchLength = searchString.length,
byteArrayLength = this.byteLength,
maxSearchIndex = byteArrayLength - searchLength,
searchCharCodes = [],
found,
i,
j;
searchCharCodes[searchLength] = 0;
for (j = 0; j < searchLength; j += 1) {
searchCharCodes[j] = searchString.charCodeAt(j);
}
i = position;
found = false;
while (i < maxSearchIndex && !found) {
j = 0;
while (j < searchLength && this.getUint8(i + j) === searchCharCodes[j]) {
j += 1;
}
found = (j === searchLength);
i += 1;
}
return found ? i - 1 : -1;
};
}
if (typeof DataView.prototype.string !== "function") {
DataView.prototype.string = function (start, length) {
var charCodes = [],
end,
i;
if (start === undefined) {
start = 0;
}
if (length === undefined) {
length = this.length;
}
end = start + length;
for (i = start; i < end; i += 1) {
charCodes.push(this.getUint8(i));
}
return String.fromCharCode.apply(String, charCodes);
};
}

View file

@ -0,0 +1,255 @@
//
// entityPropertyDialogBox.js
// examples
//
// Created by Brad hefta-Gaub on 10/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// This script implements a class useful for building tools for editing entities.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
EntityPropertyDialogBox = (function () {
var that = {};
var propertiesForEditedEntity;
var editEntityFormArray;
var decimals = 3;
var dimensionX;
var dimensionY;
var dimensionZ;
var rescalePercentage;
var editModelID = -1;
that.cleanup = function () {
};
that.openDialog = function (entityID) {
print(" Edit Properties.... about to edit properties...");
editModelID = entityID;
propertiesForEditedEntity = Entities.getEntityProperties(editModelID);
var properties = propertiesForEditedEntity;
var array = new Array();
var index = 0;
if (properties.type == "Model") {
array.push({ label: "Model URL:", value: properties.modelURL });
index++;
array.push({ label: "Animation URL:", value: properties.animationURL });
index++;
array.push({ label: "Animation is playing:", value: properties.animationIsPlaying });
index++;
array.push({ label: "Animation FPS:", value: properties.animationFPS });
index++;
array.push({ label: "Animation Frame:", value: properties.animationFrameIndex });
index++;
}
array.push({ label: "Position:", type: "header" });
index++;
array.push({ label: "X:", value: properties.position.x.toFixed(decimals) });
index++;
array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) });
index++;
array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) });
index++;
array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) });
index++;
array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) });
index++;
array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) });
index++;
array.push({ label: "Rotation:", type: "header" });
index++;
var angles = Quat.safeEulerAngles(properties.rotation);
array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) });
index++;
array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) });
index++;
array.push({ label: "Roll:", value: angles.z.toFixed(decimals) });
index++;
array.push({ label: "Dimensions:", type: "header" });
index++;
array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) });
dimensionX = index;
index++;
array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) });
dimensionY = index;
index++;
array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) });
dimensionZ = index;
index++;
array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" });
index++;
array.push({ label: "Rescale Percentage:", value: 100 });
rescalePercentage = index;
index++;
array.push({ label: "", type: "inlineButton", buttonLabel: "Rescale", name: "rescaleDimensions" });
index++;
array.push({ label: "Velocity:", type: "header" });
index++;
array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) });
index++;
array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) });
index++;
array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) });
index++;
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
index++;
array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
index++;
array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
index++;
array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) });
index++;
array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) });
index++;
array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) });
index++;
array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) });
index++;
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
index++;
array.push({ label: "Collisions:", type: "header" });
index++;
array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) });
index++;
array.push({ label: "Ignore for Collisions:", value: properties.ignoreForCollisions });
index++;
array.push({ label: "Collisions Will Move:", value: properties.collisionsWillMove });
index++;
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++;
array.push({ label: "Visible:", value: properties.visible });
index++;
if (properties.type == "Box" || properties.type == "Sphere") {
array.push({ label: "Color:", type: "header" });
index++;
array.push({ label: "Red:", value: properties.color.red });
index++;
array.push({ label: "Green:", value: properties.color.green });
index++;
array.push({ label: "Blue:", value: properties.color.blue });
index++;
}
array.push({ button: "Cancel" });
index++;
editEntityFormArray = array;
Window.nonBlockingForm("Edit Properties", array);
};
Window.inlineButtonClicked.connect(function (name) {
if (name == "resetDimensions") {
Window.reloadNonBlockingForm([
{ value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX },
{ value: propertiesForEditedEntity.naturalDimensions.y.toFixed(decimals), oldIndex: dimensionY },
{ value: propertiesForEditedEntity.naturalDimensions.z.toFixed(decimals), oldIndex: dimensionZ }
]);
}
if (name == "rescaleDimensions") {
var peekValues = editEntityFormArray;
Window.peekNonBlockingFormResult(peekValues);
var peekX = peekValues[dimensionX].value;
var peekY = peekValues[dimensionY].value;
var peekZ = peekValues[dimensionZ].value;
var peekRescale = peekValues[rescalePercentage].value;
var rescaledX = peekX * peekRescale / 100.0;
var rescaledY = peekY * peekRescale / 100.0;
var rescaledZ = peekZ * peekRescale / 100.0;
Window.reloadNonBlockingForm([
{ value: rescaledX.toFixed(decimals), oldIndex: dimensionX },
{ value: rescaledY.toFixed(decimals), oldIndex: dimensionY },
{ value: rescaledZ.toFixed(decimals), oldIndex: dimensionZ },
{ value: 100, oldIndex: rescalePercentage }
]);
}
});
Window.nonBlockingFormClosed.connect(function() {
array = editEntityFormArray;
if (Window.getNonBlockingFormResult(array)) {
var properties = propertiesForEditedEntity;
var index = 0;
if (properties.type == "Model") {
properties.modelURL = array[index++].value;
properties.animationURL = array[index++].value;
properties.animationIsPlaying = array[index++].value;
properties.animationFPS = array[index++].value;
properties.animationFrameIndex = array[index++].value;
}
index++; // skip header
properties.position.x = array[index++].value;
properties.position.y = array[index++].value;
properties.position.z = array[index++].value;
properties.registrationPoint.x = array[index++].value;
properties.registrationPoint.y = array[index++].value;
properties.registrationPoint.z = array[index++].value;
index++; // skip header
var angles = Quat.safeEulerAngles(properties.rotation);
angles.x = array[index++].value;
angles.y = array[index++].value;
angles.z = array[index++].value;
properties.rotation = Quat.fromVec3Degrees(angles);
index++; // skip header
properties.dimensions.x = array[index++].value;
properties.dimensions.y = array[index++].value;
properties.dimensions.z = array[index++].value;
index++; // skip reset button
index++; // skip rescale percentage
index++; // skip rescale button
index++; // skip header
properties.velocity.x = array[index++].value;
properties.velocity.y = array[index++].value;
properties.velocity.z = array[index++].value;
properties.damping = array[index++].value;
properties.angularVelocity.x = array[index++].value;
properties.angularVelocity.y = array[index++].value;
properties.angularVelocity.z = array[index++].value;
properties.angularDamping = array[index++].value;
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
index++; // skip header
properties.mass = array[index++].value;
properties.ignoreForCollisions = array[index++].value;
properties.collisionsWillMove = array[index++].value;
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;
if (properties.type == "Box" || properties.type == "Sphere") {
index++; // skip header
properties.color.red = array[index++].value;
properties.color.green = array[index++].value;
properties.color.blue = array[index++].value;
}
Entities.editEntity(editModelID, properties);
selectionDisplay.highlightSelectable(editModelID, propeties);
}
modelSelected = false;
});
return that;
}());

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,119 @@
//
// httpMultiPart.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
httpMultiPart = (function () {
var that = {},
parts,
byteLength,
boundaryString,
crlf;
function clear() {
boundaryString = "--boundary_" + String(Uuid.generate()).slice(1, 36) + "=";
parts = [];
byteLength = 0;
crlf = "";
}
that.clear = clear;
function boundary() {
return boundaryString.slice(2);
}
that.boundary = boundary;
function length() {
return byteLength;
}
that.length = length;
function add(object) {
// - name, string
// - name, buffer
var buffer,
string,
stringBuffer,
compressedBuffer;
if (object.name === undefined) {
throw new Error("Item to add to HttpMultiPart must have a name");
} else if (object.string !== undefined) {
//--<boundary>=
//Content-Disposition: form-data; name="model_name"
//
//<string>
string = crlf + boundaryString + "\r\n"
+ "Content-Disposition: form-data; name=\"" + object.name + "\"\r\n"
+ "\r\n"
+ object.string;
buffer = string.toArrayBuffer();
} else if (object.buffer !== undefined) {
//--<boundary>=
//Content-Disposition: form-data; name="fbx"; filename="<filename>"
//Content-Type: application/octet-stream
//
//<buffer>
string = crlf + boundaryString + "\r\n"
+ "Content-Disposition: form-data; name=\"" + object.name
+ "\"; filename=\"" + object.buffer.filename + "\"\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "\r\n";
stringBuffer = string.toArrayBuffer();
compressedBuffer = object.buffer.buffer.compress();
buffer = new Uint8Array(stringBuffer.byteLength + compressedBuffer.byteLength);
buffer.set(new Uint8Array(stringBuffer));
buffer.set(new Uint8Array(compressedBuffer), stringBuffer.byteLength);
} else {
throw new Error("Item to add to HttpMultiPart not recognized");
}
byteLength += buffer.byteLength;
parts.push(buffer);
crlf = "\r\n";
return true;
}
that.add = add;
function response() {
var buffer,
index,
str,
i;
str = crlf + boundaryString + "--\r\n";
buffer = str.toArrayBuffer();
byteLength += buffer.byteLength;
parts.push(buffer);
buffer = new Uint8Array(byteLength);
index = 0;
for (i = 0; i < parts.length; i += 1) {
buffer.set(new Uint8Array(parts[i]), index);
index += parts[i].byteLength;
}
return buffer;
}
that.response = response;
clear();
return that;
}());

View file

@ -0,0 +1,701 @@
//
// modelUploader.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
modelUploader = (function () {
var that = {},
modelFile,
modelName,
modelURL,
modelCallback,
isProcessing,
fstBuffer,
fbxBuffer,
//svoBuffer,
mapping,
geometry,
API_URL = "https://data.highfidelity.io/api/v1/models",
MODEL_URL = "http://public.highfidelity.io/models/content",
NAME_FIELD = "name",
SCALE_FIELD = "scale",
FILENAME_FIELD = "filename",
TEXDIR_FIELD = "texdir",
MAX_TEXTURE_SIZE = 1024;
function info(message) {
if (progressDialog.isOpen()) {
progressDialog.update(message);
} else {
progressDialog.open(message);
}
print(message);
}
function error(message) {
if (progressDialog.isOpen()) {
progressDialog.close();
}
print(message);
Window.alert(message);
}
function randomChar(length) {
var characters = "0123457689abcdefghijklmnopqrstuvwxyz",
string = "",
i;
for (i = 0; i < length; i += 1) {
string += characters[Math.floor(Math.random() * 36)];
}
return string;
}
function resetDataObjects() {
fstBuffer = null;
fbxBuffer = null;
//svoBuffer = null;
mapping = {};
geometry = {};
geometry.textures = [];
geometry.embedded = [];
}
function readFile(filename) {
var url = "file:///" + filename,
req = new XMLHttpRequest();
req.open("GET", url, false);
req.responseType = "arraybuffer";
req.send();
if (req.status !== 200) {
error("Could not read file: " + filename + " : " + req.statusText);
return null;
}
return {
filename: filename.fileName(),
buffer: req.response
};
}
function readMapping(buffer) {
var dv = new DataView(buffer.buffer),
lines,
line,
tokens,
i,
name,
value,
remainder,
existing;
mapping = {}; // { name : value | name : { value : [remainder] } }
lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/);
for (i = 0; i < lines.length; i += 1) {
line = lines[i].trim();
if (line.length > 0 && line[0] !== "#") {
tokens = line.split(/\s*=\s*/);
if (tokens.length > 1) {
name = tokens[0];
value = tokens[1];
if (tokens.length > 2) {
remainder = tokens.slice(2, tokens.length).join(" = ");
} else {
remainder = null;
}
if (tokens.length === 2 && mapping[name] === undefined) {
mapping[name] = value;
} else {
if (mapping[name] === undefined) {
mapping[name] = {};
} else if (typeof mapping[name] !== "object") {
existing = mapping[name];
mapping[name] = { existing : null };
}
if (mapping[name][value] === undefined) {
mapping[name][value] = [];
}
mapping[name][value].push(remainder);
}
}
}
}
}
function writeMapping(buffer) {
var name,
value,
remainder,
i,
string = "";
for (name in mapping) {
if (mapping.hasOwnProperty(name)) {
if (typeof mapping[name] === "object") {
for (value in mapping[name]) {
if (mapping[name].hasOwnProperty(value)) {
remainder = mapping[name][value];
if (remainder === null) {
string += (name + " = " + value + "\n");
} else {
for (i = 0; i < remainder.length; i += 1) {
string += (name + " = " + value + " = " + remainder[i] + "\n");
}
}
}
}
} else {
string += (name + " = " + mapping[name] + "\n");
}
}
}
buffer.buffer = string.toArrayBuffer();
}
function readGeometry(fbxBuffer) {
var textures,
view,
index,
EOF,
previousNodeFilename;
// Reference:
// http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/
textures = {};
view = new DataView(fbxBuffer.buffer);
EOF = false;
function parseBinaryFBX() {
var endOffset,
numProperties,
propertyListLength,
nameLength,
name,
filename;
endOffset = view.getUint32(index, true);
numProperties = view.getUint32(index + 4, true);
propertyListLength = view.getUint32(index + 8, true);
nameLength = view.getUint8(index + 12);
index += 13;
if (endOffset === 0) {
return;
}
if (endOffset < index || endOffset > view.byteLength) {
EOF = true;
return;
}
name = view.string(index, nameLength).toLowerCase();
index += nameLength;
if (name === "content" && previousNodeFilename !== "") {
// Blender 2.71 exporter "embeds" external textures as empty binary blobs so ignore these
if (propertyListLength > 5) {
geometry.embedded.push(previousNodeFilename);
}
}
if (name === "relativefilename") {
filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName();
if (!textures.hasOwnProperty(filename)) {
textures[filename] = "";
geometry.textures.push(filename);
}
previousNodeFilename = filename;
} else {
previousNodeFilename = "";
}
index += (propertyListLength);
while (index < endOffset && !EOF) {
parseBinaryFBX();
}
}
function readTextFBX() {
var line,
view,
viewLength,
charCode,
charCodes,
numCharCodes,
filename,
relativeFilename = "",
MAX_CHAR_CODES = 250;
view = new Uint8Array(fbxBuffer.buffer);
viewLength = view.byteLength;
charCodes = [];
numCharCodes = 0;
for (index = 0; index < viewLength; index += 1) {
charCode = view[index];
if (charCode !== 9 && charCode !== 32) {
if (charCode === 10) { // EOL. Can ignore EOF.
line = String.fromCharCode.apply(String, charCodes).toLowerCase();
// For embedded textures, "Content:" line immediately follows "RelativeFilename:" line.
if (line.slice(0, 8) === "content:" && relativeFilename !== "") {
geometry.embedded.push(relativeFilename);
}
if (line.slice(0, 17) === "relativefilename:") {
filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName();
if (!textures.hasOwnProperty(filename)) {
textures[filename] = "";
geometry.textures.push(filename);
}
relativeFilename = filename;
} else {
relativeFilename = "";
}
charCodes = [];
numCharCodes = 0;
} else {
if (numCharCodes < MAX_CHAR_CODES) { // Only interested in start of line
charCodes.push(charCode);
numCharCodes += 1;
}
}
}
}
}
if (view.string(0, 18) === "Kaydara FBX Binary") {
previousNodeFilename = "";
index = 27;
while (index < view.byteLength - 39 && !EOF) {
parseBinaryFBX();
}
} else {
readTextFBX();
}
}
function readModel() {
var fbxFilename,
//svoFilename,
fileType;
info("Reading model file");
print("Model file: " + modelFile);
if (modelFile.toLowerCase().fileType() === "fst") {
fstBuffer = readFile(modelFile);
if (fstBuffer === null) {
return false;
}
readMapping(fstBuffer);
fileType = mapping[FILENAME_FIELD].toLowerCase().fileType();
if (mapping.hasOwnProperty(FILENAME_FIELD)) {
if (fileType === "fbx") {
fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD];
//} else if (fileType === "svo") {
// svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD];
} else {
error("Unrecognized model type in FST file!");
return false;
}
} else {
error("Model file name not found in FST file!");
return false;
}
} else {
fstBuffer = {
filename: "Interface." + randomChar(6), // Simulate avatar model uploading behaviour
buffer: null
};
if (modelFile.toLowerCase().fileType() === "fbx") {
fbxFilename = modelFile;
mapping[FILENAME_FIELD] = modelFile.fileName();
//} else if (modelFile.toLowerCase().fileType() === "svo") {
// svoFilename = modelFile;
// mapping[FILENAME_FIELD] = modelFile.fileName();
} else {
error("Unrecognized file type: " + modelFile);
return false;
}
}
if (!isProcessing) { return false; }
if (fbxFilename) {
fbxBuffer = readFile(fbxFilename);
if (fbxBuffer === null) {
return false;
}
if (!isProcessing) { return false; }
readGeometry(fbxBuffer);
}
//if (svoFilename) {
// svoBuffer = readFile(svoFilename);
// if (svoBuffer === null) {
// return false;
// }
//}
// Add any missing basic mappings
if (!mapping.hasOwnProperty(NAME_FIELD)) {
mapping[NAME_FIELD] = modelFile.fileName().fileBase();
}
if (!mapping.hasOwnProperty(TEXDIR_FIELD)) {
mapping[TEXDIR_FIELD] = ".";
}
if (!mapping.hasOwnProperty(SCALE_FIELD)) {
mapping[SCALE_FIELD] = 1.0;
}
return true;
}
function setProperties() {
var form = [],
directory,
displayAs,
validateAs;
progressDialog.close();
print("Setting model properties");
form.push({ label: "Name:", value: mapping[NAME_FIELD] });
directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD];
displayAs = new RegExp("^" + modelFile.path().regExpEscape() + "[\\\\\\\/](.*)");
validateAs = new RegExp("^" + modelFile.path().regExpEscape() + "([\\\\\\\/].*)?");
form.push({
label: "Texture directory:",
directory: modelFile.path() + "/" + mapping[TEXDIR_FIELD],
title: "Choose Texture Directory",
displayAs: displayAs,
validateAs: validateAs,
errorMessage: "Texture directory must be subdirectory of the model directory."
});
form.push({ button: "Cancel" });
if (!Window.form("Set Model Properties", form)) {
print("User cancelled uploading model");
return false;
}
mapping[NAME_FIELD] = form[0].value;
mapping[TEXDIR_FIELD] = form[1].directory.slice(modelFile.path().length + 1);
if (mapping[TEXDIR_FIELD] === "") {
mapping[TEXDIR_FIELD] = ".";
}
writeMapping(fstBuffer);
return true;
}
function createHttpMessage(callback) {
var multiparts = [],
lodCount,
lodFile,
lodBuffer,
textureBuffer,
textureSourceFormat,
textureTargetFormat,
embeddedTextures,
i;
info("Preparing to send model");
// Model name
if (mapping.hasOwnProperty(NAME_FIELD)) {
multiparts.push({
name : "model_name",
string : mapping[NAME_FIELD]
});
} else {
error("Model name is missing");
httpMultiPart.clear();
return;
}
// FST file
if (fstBuffer) {
multiparts.push({
name : "fst",
buffer: fstBuffer
});
}
// FBX file
if (fbxBuffer) {
multiparts.push({
name : "fbx",
buffer: fbxBuffer
});
}
// SVO file
//if (svoBuffer) {
// multiparts.push({
// name : "svo",
// buffer: svoBuffer
// });
//}
// LOD files
lodCount = 0;
for (lodFile in mapping.lod) {
if (mapping.lod.hasOwnProperty(lodFile)) {
lodBuffer = readFile(modelFile.path() + "\/" + lodFile);
if (lodBuffer === null) {
return;
}
multiparts.push({
name: "lod" + lodCount,
buffer: lodBuffer
});
lodCount += 1;
}
if (!isProcessing) { return; }
}
// Textures
embeddedTextures = "|" + geometry.embedded.join("|") + "|";
for (i = 0; i < geometry.textures.length; i += 1) {
if (embeddedTextures.indexOf("|" + geometry.textures[i].fileName() + "|") === -1) {
textureBuffer = readFile(modelFile.path() + "\/"
+ (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "")
+ geometry.textures[i]);
if (textureBuffer === null) {
return;
}
textureSourceFormat = geometry.textures[i].fileType().toLowerCase();
textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png");
textureBuffer.buffer =
textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE);
textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat;
multiparts.push({
name: "texture" + i,
buffer: textureBuffer
});
}
if (!isProcessing) { return; }
}
// Model category
multiparts.push({
name : "model_category",
string : "content"
});
// Create HTTP message
httpMultiPart.clear();
Script.setTimeout(function addMultipart() {
var multipart = multiparts.shift();
httpMultiPart.add(multipart);
if (!isProcessing) { return; }
if (multiparts.length > 0) {
Script.setTimeout(addMultipart, 25);
} else {
callback();
}
}, 25);
}
function sendToHighFidelity() {
var req,
uploadedChecks,
HTTP_GET_TIMEOUT = 60, // 1 minute
HTTP_SEND_TIMEOUT = 900, // 15 minutes
UPLOADED_CHECKS = 30,
CHECK_UPLOADED_TIMEOUT = 1, // 1 second
handleCheckUploadedResponses,
handleUploadModelResponses,
handleRequestUploadResponses;
function uploadTimedOut() {
error("Model upload failed: Internet request timed out!");
}
function debugResponse() {
print("req.errorCode = " + req.errorCode);
print("req.readyState = " + req.readyState);
print("req.status = " + req.status);
print("req.statusText = " + req.statusText);
print("req.responseType = " + req.responseType);
print("req.responseText = " + req.responseText);
print("req.response = " + req.response);
print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders());
}
function checkUploaded() {
if (!isProcessing) { return; }
info("Checking uploaded model");
req = new XMLHttpRequest();
req.open("HEAD", modelURL, true);
req.timeout = HTTP_GET_TIMEOUT * 1000;
req.onreadystatechange = handleCheckUploadedResponses;
req.ontimeout = uploadTimedOut;
req.send();
}
handleCheckUploadedResponses = function () {
//debugResponse();
if (req.readyState === req.DONE) {
if (req.status === 200) {
// Note: Unlike avatar models, for content models we don't need to refresh texture cache.
print("Model uploaded: " + modelURL);
progressDialog.close();
if (Window.confirm("Your model has been uploaded as: " + modelURL + "\nDo you want to rez it?")) {
modelCallback(modelURL);
}
} else if (req.status === 404) {
if (uploadedChecks > 0) {
uploadedChecks -= 1;
Script.setTimeout(checkUploaded, CHECK_UPLOADED_TIMEOUT * 1000);
} else {
print("Error: " + req.status + " " + req.statusText);
error("We could not verify that your model was successfully uploaded but it may have been at: "
+ modelURL);
}
} else {
print("Error: " + req.status + " " + req.statusText);
error("There was a problem with your upload, please try again later.");
}
}
};
function uploadModel(method) {
var url;
if (!isProcessing) { return; }
req = new XMLHttpRequest();
if (method === "PUT") {
url = API_URL + "\/" + modelName;
req.open("PUT", url, true); //print("PUT " + url);
} else {
url = API_URL;
req.open("POST", url, true); //print("POST " + url);
}
req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\"");
req.timeout = HTTP_SEND_TIMEOUT * 1000;
req.onreadystatechange = handleUploadModelResponses;
req.ontimeout = uploadTimedOut;
req.send(httpMultiPart.response().buffer);
}
handleUploadModelResponses = function () {
//debugResponse();
if (req.readyState === req.DONE) {
if (req.status === 200) {
uploadedChecks = UPLOADED_CHECKS;
checkUploaded();
} else {
print("Error: " + req.status + " " + req.statusText);
error("There was a problem with your upload, please try again later.");
}
}
};
function requestUpload() {
var url;
if (!isProcessing) { return; }
url = API_URL + "\/" + modelName; // XMLHttpRequest automatically handles authorization of API requests.
req = new XMLHttpRequest();
req.open("GET", url, true); //print("GET " + url);
req.responseType = "json";
req.timeout = HTTP_GET_TIMEOUT * 1000;
req.onreadystatechange = handleRequestUploadResponses;
req.ontimeout = uploadTimedOut;
req.send();
}
handleRequestUploadResponses = function () {
var response;
//debugResponse();
if (req.readyState === req.DONE) {
if (req.status === 200) {
if (req.responseType === "json") {
response = JSON.parse(req.responseText);
if (response.status === "success") {
if (response.exists === false) {
uploadModel("POST");
} else if (response.can_update === true) {
uploadModel("PUT");
} else {
error("This model file already exists and is owned by someone else!");
}
return;
}
}
} else {
print("Error: " + req.status + " " + req.statusText);
}
error("Model upload failed! Something went wrong at the data server.");
}
};
info("Sending model to High Fidelity");
requestUpload();
}
that.upload = function (file, callback) {
modelFile = file;
modelCallback = callback;
isProcessing = true;
progressDialog.onCancel = function () {
print("User cancelled uploading model");
isProcessing = false;
};
resetDataObjects();
if (readModel()) {
if (setProperties()) {
modelName = mapping[NAME_FIELD];
modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST
createHttpMessage(sendToHighFidelity);
}
}
resetDataObjects();
};
return that;
}());

View file

@ -0,0 +1,143 @@
//
// progressDialog.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
progressDialog = (function () {
var that = {},
progressBackground,
progressMessage,
cancelButton,
displayed = false,
backgroundWidth = 300,
backgroundHeight = 100,
messageHeight = 32,
cancelWidth = 70,
cancelHeight = 32,
textColor = { red: 255, green: 255, blue: 255 },
textBackground = { red: 52, green: 52, blue: 52 },
backgroundUrl = toolIconUrl + "progress-background.svg",
windowDimensions;
progressBackground = Overlays.addOverlay("image", {
width: backgroundWidth,
height: backgroundHeight,
imageURL: backgroundUrl,
alpha: 0.9,
visible: false
});
progressMessage = Overlays.addOverlay("text", {
width: backgroundWidth - 40,
height: messageHeight,
text: "",
textColor: textColor,
backgroundColor: textBackground,
alpha: 0.9,
visible: false
});
cancelButton = Overlays.addOverlay("text", {
width: cancelWidth,
height: cancelHeight,
text: "Cancel",
textColor: textColor,
backgroundColor: textBackground,
alpha: 0.9,
visible: false
});
function move() {
var progressX,
progressY;
if (displayed) {
if (windowDimensions.x === Window.innerWidth && windowDimensions.y === Window.innerHeight) {
return;
}
windowDimensions.x = Window.innerWidth;
windowDimensions.y = Window.innerHeight;
progressX = (windowDimensions.x - backgroundWidth) / 2; // Center.
progressY = windowDimensions.y / 2 - backgroundHeight; // A little up from center.
Overlays.editOverlay(progressBackground, { x: progressX, y: progressY });
Overlays.editOverlay(progressMessage, { x: progressX + 20, y: progressY + 15 });
Overlays.editOverlay(cancelButton, {
x: progressX + backgroundWidth - cancelWidth - 20,
y: progressY + backgroundHeight - cancelHeight - 15
});
}
}
that.move = move;
that.onCancel = undefined;
function open(message) {
if (!displayed) {
windowDimensions = { x: 0, y : 0 };
displayed = true;
move();
Overlays.editOverlay(progressBackground, { visible: true });
Overlays.editOverlay(progressMessage, { visible: true, text: message });
Overlays.editOverlay(cancelButton, { visible: true });
} else {
throw new Error("open() called on progressDialog when already open");
}
}
that.open = open;
function isOpen() {
return displayed;
}
that.isOpen = isOpen;
function update(message) {
if (displayed) {
Overlays.editOverlay(progressMessage, { text: message });
} else {
throw new Error("update() called on progressDialog when not open");
}
}
that.update = update;
function close() {
if (displayed) {
Overlays.editOverlay(cancelButton, { visible: false });
Overlays.editOverlay(progressMessage, { visible: false });
Overlays.editOverlay(progressBackground, { visible: false });
displayed = false;
} else {
throw new Error("close() called on progressDialog when not open");
}
}
that.close = close;
function mousePressEvent(event) {
if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === cancelButton) {
if (typeof this.onCancel === "function") {
close();
this.onCancel();
}
return true;
}
return false;
}
that.mousePressEvent = mousePressEvent;
function cleanup() {
Overlays.deleteOverlay(cancelButton);
Overlays.deleteOverlay(progressMessage);
Overlays.deleteOverlay(progressBackground);
}
that.cleanup = cleanup;
return that;
}());

View file

@ -0,0 +1,66 @@
if (typeof String.prototype.fileName !== "function") {
String.prototype.fileName = function () {
return this.replace(/^(.*[\/\\])*/, "");
};
}
if (typeof String.prototype.fileBase !== "function") {
String.prototype.fileBase = function () {
var filename = this.fileName();
return filename.slice(0, filename.indexOf("."));
};
}
if (typeof String.prototype.fileType !== "function") {
String.prototype.fileType = function () {
return this.slice(this.lastIndexOf(".") + 1);
};
}
if (typeof String.prototype.path !== "function") {
String.prototype.path = function () {
return this.replace(/[\\\/][^\\\/]*$/, "");
};
}
if (typeof String.prototype.regExpEscape !== "function") {
String.prototype.regExpEscape = function () {
return this.replace(/([$\^.+*?|\\\/{}()\[\]])/g, '\\$1');
};
}
if (typeof String.prototype.toArrayBuffer !== "function") {
String.prototype.toArrayBuffer = function () {
var length,
buffer,
view,
charCode,
charCodes,
i;
charCodes = [];
length = this.length;
for (i = 0; i < length; i += 1) {
charCode = this.charCodeAt(i);
if (charCode <= 255) {
charCodes.push(charCode);
} else {
charCodes.push(charCode / 256);
charCodes.push(charCode % 256);
}
}
length = charCodes.length;
buffer = new ArrayBuffer(length);
view = new Uint8Array(buffer);
for (i = 0; i < length; i += 1) {
view[i] = charCodes[i];
}
return buffer;
};
}

601
examples/newEditEntities.js Normal file
View file

@ -0,0 +1,601 @@
//
// newEditEntities.js
// examples
//
// Created by Brad Hefta-Gaub on 10/2/14.
// Copyright 2014 High Fidelity, Inc.
//
// This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("libraries/stringHelpers.js");
Script.include("libraries/dataviewHelpers.js");
Script.include("libraries/httpMultiPart.js");
Script.include("libraries/modelUploader.js");
Script.include("libraries/toolBars.js");
Script.include("libraries/progressDialog.js");
Script.include("libraries/entitySelectionTool.js");
var selectionDisplay = SelectionDisplay;
Script.include("libraries/ModelImporter.js");
var modelImporter = new ModelImporter();
Script.include("libraries/ExportMenu.js");
Script.include("libraries/ToolTip.js");
Script.include("libraries/entityPropertyDialogBox.js");
var entityPropertyDialogBox = EntityPropertyDialogBox;
var windowDimensions = Controller.getViewportDimensions();
var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/";
var toolHeight = 50;
var toolWidth = 50;
var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;
var allowLargeModels = false;
var allowSmallModels = false;
var wantEntityGlow = false;
var SPAWN_DISTANCE = 1;
var DEFAULT_DIMENSION = 0.20;
var modelURLs = [
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx",
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx"
];
var mode = 0;
var isActive = false;
var toolBar = (function () {
var that = {},
toolBar,
activeButton,
newModelButton,
newCubeButton,
newSphereButton,
browseModelsButton,
loadURLMenuItem,
loadFileMenuItem,
menuItemWidth = 125,
menuItemOffset,
menuItemHeight,
menuItemMargin = 5,
menuTextColor = { red: 255, green: 255, blue: 255 },
menuBackgoundColor = { red: 18, green: 66, blue: 66 };
function initialize() {
toolBar = new ToolBar(0, 0, ToolBar.VERTICAL);
activeButton = toolBar.addTool({
imageURL: toolIconUrl + "models-tool.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true
}, true, false);
newModelButton = toolBar.addTool({
imageURL: toolIconUrl + "add-model-tool.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true
}, true, false);
browseModelsButton = toolBar.addTool({
imageURL: toolIconUrl + "list-icon.svg",
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true
});
menuItemOffset = toolBar.height / 3 + 2;
menuItemHeight = Tool.IMAGE_HEIGHT / 2 - 2;
loadURLMenuItem = Overlays.addOverlay("text", {
x: newModelButton.x - menuItemWidth,
y: newModelButton.y + menuItemOffset,
width: menuItemWidth,
height: menuItemHeight,
backgroundColor: menuBackgoundColor,
topMargin: menuItemMargin,
text: "Model URL",
alpha: 0.9,
visible: false
});
loadFileMenuItem = Overlays.addOverlay("text", {
x: newModelButton.x - menuItemWidth,
y: newModelButton.y + menuItemOffset + menuItemHeight,
width: menuItemWidth,
height: menuItemHeight,
backgroundColor: menuBackgoundColor,
topMargin: menuItemMargin,
text: "Model File",
alpha: 0.9,
visible: false
});
newCubeButton = toolBar.addTool({
imageURL: toolIconUrl + "add-cube.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true
});
newSphereButton = toolBar.addTool({
imageURL: toolIconUrl + "add-sphere.svg",
subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true
});
}
function toggleNewModelButton(active) {
if (active === undefined) {
active = !toolBar.toolSelected(newModelButton);
}
toolBar.selectTool(newModelButton, active);
Overlays.editOverlay(loadURLMenuItem, { visible: active });
Overlays.editOverlay(loadFileMenuItem, { visible: active });
}
function addModel(url) {
var position;
position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
if (position.x > 0 && position.y > 0 && position.z > 0) {
Entities.addEntity({
type: "Model",
position: position,
dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION },
modelURL: url
});
print("Model added: " + url);
} else {
print("Can't add model: Model would be out of bounds.");
}
}
that.move = function () {
var newViewPort,
toolsX,
toolsY;
newViewPort = Controller.getViewportDimensions();
if (toolBar === undefined) {
initialize();
} else if (windowDimensions.x === newViewPort.x &&
windowDimensions.y === newViewPort.y) {
return;
}
windowDimensions = newViewPort;
toolsX = windowDimensions.x - 8 - toolBar.width;
toolsY = (windowDimensions.y - toolBar.height) / 2;
toolBar.move(toolsX, toolsY);
Overlays.editOverlay(loadURLMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset });
Overlays.editOverlay(loadFileMenuItem, { x: toolsX - menuItemWidth, y: toolsY + menuItemOffset + menuItemHeight });
};
that.mousePressEvent = function (event) {
var clickedOverlay,
url,
file;
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (activeButton === toolBar.clicked(clickedOverlay)) {
isActive = !isActive;
if (!isActive) {
selectionDisplay.unselectAll();
}
return true;
}
if (newModelButton === toolBar.clicked(clickedOverlay)) {
toggleNewModelButton();
return true;
}
if (clickedOverlay === loadURLMenuItem) {
toggleNewModelButton(false);
url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]);
if (url !== null && url !== "") {
addModel(url);
}
return true;
}
if (clickedOverlay === loadFileMenuItem) {
toggleNewModelButton(false);
// TODO BUG: this is bug, if the user has never uploaded a model, this will throw an JS exception
file = Window.browse("Select your model file ...",
Settings.getValue("LastModelUploadLocation").path(),
"Model files (*.fst *.fbx)");
//"Model files (*.fst *.fbx *.svo)");
if (file !== null) {
Settings.setValue("LastModelUploadLocation", file);
modelUploader.upload(file, addModel);
}
return true;
}
if (browseModelsButton === toolBar.clicked(clickedOverlay)) {
toggleNewModelButton(false);
url = Window.s3Browse(".*(fbx|FBX)");
if (url !== null && url !== "") {
addModel(url);
}
return true;
}
if (newCubeButton === toolBar.clicked(clickedOverlay)) {
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
if (position.x > 0 && position.y > 0 && position.z > 0) {
Entities.addEntity({
type: "Box",
position: position,
dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION },
color: { red: 255, green: 0, blue: 0 }
});
} else {
print("Can't create box: Box would be out of bounds.");
}
return true;
}
if (newSphereButton === toolBar.clicked(clickedOverlay)) {
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
if (position.x > 0 && position.y > 0 && position.z > 0) {
Entities.addEntity({
type: "Sphere",
position: position,
dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION },
color: { red: 255, green: 0, blue: 0 }
});
} else {
print("Can't create box: Box would be out of bounds.");
}
return true;
}
return false;
};
that.cleanup = function () {
toolBar.cleanup();
Overlays.deleteOverlay(loadURLMenuItem);
Overlays.deleteOverlay(loadFileMenuItem);
};
return that;
}());
var exportMenu = null;
function isLocked(properties) {
// special case to lock the ground plane model in hq.
if (location.hostname == "hq.highfidelity.io" &&
properties.modelURL == "https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/Terrain_Reduce_forAlpha.fbx") {
return true;
}
return false;
}
var entitySelected = false;
var selectedEntityID;
var selectedEntityProperties;
var mouseLastPosition;
var orientation;
var intersection;
var SCALE_FACTOR = 200.0;
function rayPlaneIntersection(pickRay, point, normal) {
var d = -Vec3.dot(point, normal);
var t = -(Vec3.dot(pickRay.origin, normal) + d) / Vec3.dot(pickRay.direction, normal);
return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t));
}
function mousePressEvent(event) {
mouseLastPosition = { x: event.x, y: event.y };
entitySelected = false;
var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
if (toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) {
// Event handled; do nothing.
return;
} else {
// If we aren't active and didn't click on an overlay: quit
if (!isActive) {
return;
}
var pickRay = Camera.computePickRay(event.x, event.y);
Vec3.print("[Mouse] Looking at: ", pickRay.origin);
var foundIntersection = Entities.findRayIntersection(pickRay);
if(!foundIntersection.accurate) {
return;
}
var foundEntity = foundIntersection.entityID;
if (!foundEntity.isKnownID) {
var identify = Entities.identifyEntity(foundEntity);
if (!identify.isKnownID) {
print("Unknown ID " + identify.id + " (update loop " + foundEntity.id + ")");
return;
}
foundEntity = identify;
}
var properties = Entities.getEntityProperties(foundEntity);
if (isLocked(properties)) {
print("Model locked " + properties.id);
} else {
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal);
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
// / | X - base of the perpendicular line
// A---X----->B d - distance fom axis
// x x - distance from A
//
// |X-A| = (P-A).B
// X == A + ((P-A).B)B
// d = |P-X|
var A = pickRay.origin;
var B = Vec3.normalize(pickRay.direction);
var P = properties.position;
var x = Vec3.dot(Vec3.subtract(P, A), B);
var X = Vec3.sum(A, Vec3.multiply(B, x));
var d = Vec3.length(Vec3.subtract(P, X));
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (0 < x && sizeOK) {
entitySelected = true;
selectedEntityID = foundEntity;
selectedEntityProperties = properties;
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
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);
}
}
var highlightedEntityID = { isKnownID: false };
function mouseMoveEvent(event) {
if (!isActive) {
return;
}
var pickRay = Camera.computePickRay(event.x, event.y);
if (!entitySelected) {
var entityIntersection = Entities.findRayIntersection(pickRay);
if (entityIntersection.accurate) {
if(highlightedEntityID.isKnownID && highlightedEntityID.id != entityIntersection.entityID.id) {
selectionDisplay.unhighlightSelectable(highlightedEntityID);
highlightedEntityID = { id: -1, isKnownID: false };
}
var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(),
entityIntersection.properties.position)) * 180 / 3.14;
var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
&& (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
if (entityIntersection.entityID.isKnownID && sizeOK) {
if (wantEntityGlow) {
Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 });
}
highlightedEntityID = entityIntersection.entityID;
selectionDisplay.highlightSelectable(entityIntersection.entityID);
}
}
return;
}
}
function mouseReleaseEvent(event) {
if (!isActive) {
return;
}
if (entitySelected) {
tooltip.show(false);
}
}
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
// added it.
var modelMenuAddedDelete = false;
function setupModelMenus() {
print("setupModelMenus()");
// adj our menuitems
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Edit Properties...",
shortcutKeyEvent: { text: "`" }, afterItem: "Models" });
if (!Menu.menuItemExists("Edit", "Delete")) {
print("no delete... adding ours");
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete",
shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" });
modelMenuAddedDelete = true;
} else {
print("delete exists... don't add ours");
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "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: "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" });
}
setupModelMenus(); // do this when first running our script.
function cleanupModelMenus() {
Menu.removeSeparator("Edit", "Models");
Menu.removeMenuItem("Edit", "Edit Properties...");
if (modelMenuAddedDelete) {
// delete our menuitems
Menu.removeMenuItem("Edit", "Delete");
}
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Select Large Models");
Menu.removeMenuItem("Edit", "Allow Select Small Models");
Menu.removeSeparator("File", "Models");
Menu.removeMenuItem("File", "Export Models");
Menu.removeMenuItem("File", "Import Models");
}
Script.scriptEnding.connect(function() {
progressDialog.cleanup();
toolBar.cleanup();
cleanupModelMenus();
tooltip.cleanup();
modelImporter.cleanup();
selectionDisplay.cleanup();
if (exportMenu) {
exportMenu.close();
}
});
// Do some stuff regularly, like check for placement of various overlays
Script.update.connect(function (deltaTime) {
toolBar.move();
progressDialog.move();
selectionDisplay.checkMove();
});
function handeMenuEvent(menuItem) {
if (menuItem == "Allow Select Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Select Small Models");
} else if (menuItem == "Allow Select Large Models") {
allowLargeModels = Menu.isOptionChecked("Allow Select Large Models");
} else if (menuItem == "Delete") {
if (entitySelected) {
print(" Delete Entity.... selectedEntityID="+ selectedEntityID);
Entities.deleteEntity(selectedEntityID);
selectionDisplay.unselect(selectedEntityID);
entitySelected = false;
} else {
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Edit Properties...") {
// good place to put the properties dialog
} else if (menuItem == "Paste Models") {
modelImporter.paste();
} else if (menuItem == "Export Models") {
if (!exportMenu) {
exportMenu = new ExportMenu({
onClose: function () {
exportMenu = null;
}
});
}
} else if (menuItem == "Import Models") {
modelImporter.doImport();
}
tooltip.show(false);
}
Menu.menuItemEvent.connect(handeMenuEvent);
Controller.keyReleaseEvent.connect(function (event) {
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
if (event.text == "`") {
handeMenuEvent("Edit Properties...");
}
if (event.text == "BACKSPACE") {
handeMenuEvent("Delete");
}
});

View file

@ -0,0 +1,9 @@
set(TARGET_NAME ice-server)
# setup the project and link required Qt modules
setup_hifi_project(Network)
# link the shared hifi libraries
link_hifi_libraries(networking shared)
link_shared_dependencies()

View file

@ -0,0 +1,167 @@
//
// IceServer.cpp
// ice-server/src
//
// Created by Stephen Birarda on 2014-10-01.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QTimer>
#include <LimitedNodeList.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include "IceServer.h"
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
IceServer::IceServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_id(QUuid::createUuid()),
_serverSocket(),
_activePeers()
{
// start the ice-server socket
qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT;
_serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT);
// call our process datagrams slot when the UDP socket has packets ready
connect(&_serverSocket, &QUdpSocket::readyRead, this, &IceServer::processDatagrams);
// setup our timer to clear inactive peers
QTimer* inactivePeerTimer = new QTimer(this);
connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);
inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);
}
void IceServer::processDatagrams() {
HifiSockAddr sendingSockAddr;
QByteArray incomingPacket;
while (_serverSocket.hasPendingDatagrams()) {
incomingPacket.resize(_serverSocket.pendingDatagramSize());
_serverSocket.readDatagram(incomingPacket.data(), incomingPacket.size(),
sendingSockAddr.getAddressPointer(), sendingSockAddr.getPortPointer());
if (packetTypeForPacket(incomingPacket) == PacketTypeIceServerHeartbeat) {
QUuid senderUUID = uuidFromPacketHeader(incomingPacket);
// pull the public and private sock addrs for this peer
HifiSockAddr publicSocket, localSocket;
QDataStream hearbeatStream(incomingPacket);
hearbeatStream.skipRawData(numBytesForPacketHeader(incomingPacket));
hearbeatStream >> publicSocket >> localSocket;
// make sure we have this sender in our peer hash
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
if (!matchingPeer) {
// if we don't have this sender we need to create them now
matchingPeer = SharedNetworkPeer(new NetworkPeer(senderUUID, publicSocket, localSocket));
_activePeers.insert(senderUUID, matchingPeer);
qDebug() << "Added a new network peer" << *matchingPeer;
} else {
// we already had the peer so just potentially update their sockets
matchingPeer->setPublicSocket(publicSocket);
matchingPeer->setLocalSocket(localSocket);
qDebug() << "Matched hearbeat to existing network peer" << *matchingPeer;
}
// update our last heard microstamp for this network peer to now
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
// check if this node also included a UUID that they would like to connect to
QUuid connectRequestID;
hearbeatStream >> connectRequestID;
// get the peers asking for connections with this peer
QSet<QUuid>& requestingConnections = _currentConnections[senderUUID];
if (!connectRequestID.isNull()) {
qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestID);
// ensure this peer is in the set of current connections for the peer with ID it wants to connect with
_currentConnections[connectRequestID].insert(senderUUID);
// add the ID of the node they have said they would like to connect to
requestingConnections.insert(connectRequestID);
}
if (requestingConnections.size() > 0) {
// send a heartbeart response based on the set of connections
qDebug() << "Sending a heartbeat response to" << senderUUID << "who has" << requestingConnections.size()
<< "potential connections";
sendHeartbeatResponse(sendingSockAddr, requestingConnections);
}
}
}
}
void IceServer::sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet<QUuid>& connections) {
QSet<QUuid>::iterator peerID = connections.begin();
QByteArray outgoingPacket(MAX_PACKET_SIZE, 0);
int currentPacketSize = populatePacketHeader(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id);
int numHeaderBytes = currentPacketSize;
// go through the connections, sending packets containing connection information for those nodes
while (peerID != connections.end()) {
SharedNetworkPeer matchingPeer = _activePeers.value(*peerID);
// if this node is inactive we remove it from the set
if (!matchingPeer) {
peerID = connections.erase(peerID);
} else {
// get the byte array for this peer
QByteArray peerBytes = matchingPeer->toByteArray();
if (currentPacketSize + peerBytes.size() > MAX_PACKET_SIZE) {
// write the current packet
_serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize,
destinationSockAddr.getAddress(), destinationSockAddr.getPort());
// reset the packet size to our number of header bytes
currentPacketSize = populatePacketHeader(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id);
}
// append the current peer bytes
outgoingPacket.insert(currentPacketSize, peerBytes);
currentPacketSize += peerBytes.size();
++peerID;
}
}
if (currentPacketSize > numHeaderBytes) {
// write the last packet, if there is data in it
_serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize,
destinationSockAddr.getAddress(), destinationSockAddr.getPort());
}
}
void IceServer::clearInactivePeers() {
NetworkPeerHash::iterator peerItem = _activePeers.begin();
while (peerItem != _activePeers.end()) {
SharedNetworkPeer peer = peerItem.value();
if ((usecTimestampNow() - peer->getLastHeardMicrostamp()) > (PEER_SILENCE_THRESHOLD_MSECS * 1000)) {
qDebug() << "Removing peer from memory for inactivity -" << *peer;
peerItem = _activePeers.erase(peerItem);
} else {
// we didn't kill this peer, push the iterator forwards
++peerItem;
}
}
}

View file

@ -0,0 +1,39 @@
//
// IceServer.h
// ice-server/src
//
// Created by Stephen Birarda on 2014-10-01.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_IceServer_h
#define hifi_IceServer_h
#include <qcoreapplication.h>
#include <qsharedpointer.h>
#include <qudpsocket.h>
#include <NetworkPeer.h>
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
class IceServer : public QCoreApplication {
public:
IceServer(int argc, char* argv[]);
private slots:
void processDatagrams();
void clearInactivePeers();
private:
void sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet<QUuid>& connections);
QUuid _id;
QUdpSocket _serverSocket;
NetworkPeerHash _activePeers;
QHash<QUuid, QSet<QUuid> > _currentConnections;
};
#endif // hifi_IceServer_h

27
ice-server/src/main.cpp Normal file
View file

@ -0,0 +1,27 @@
//
// main.cpp
// ice-server/src
//
// Created by Stephen Birarda on 10/01/12.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QCoreApplication>
#include <Logging.h>
#include "IceServer.h"
int main(int argc, char* argv[]) {
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
qInstallMessageHandler(Logging::verboseMessageHandler);
IceServer iceServer(argc, argv);
return iceServer.exec();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1,52 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Hifi by highfidelity</title>
<link rel="stylesheet" href="stylesheets/styles.css">
<link rel="stylesheet" href="stylesheets/pygment_trac.css">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="wrapper">
<header>
<h1>Hifi</h1>
<p>Open, decentralized virtual worlds using sensors to control avatars and dynamically assigned devices as servers. San Francisco based startup, we are hiring: http://highfidelity.io/jobs You can also contribute by doing jobs listed at http://worklist.net -</p>
<p class="view"><a href="https://github.com/highfidelity/hifi">View the Project on GitHub <small>highfidelity/hifi</small></a></p>
<ul>
<li><a href="https://github.com/highfidelity/hifi/zipball/master">Download <strong>ZIP File</strong></a></li>
<li><a href="https://github.com/highfidelity/hifi/tarball/master">Download <strong>TAR Ball</strong></a></li>
<li><a href="https://github.com/highfidelity/hifi">View On <strong>GitHub</strong></a></li>
</ul>
</header>
<section>
<h3>
<a name="avatar-documentation" class="anchor" href="#avatar-documentation"><span class="octicon octicon-link"></span></a>Avatar Documentation</h3>
<ul>
<li><a href="https://github.com/highfidelity/hifi/wiki/Exporting-Your-Rigged-Avatar-From-Faceshift">Exporting your Rigged Avatar from Faceshift</a></li>
<li><a href="https://github.com/highfidelity/hifi/wiki/Creating-Blendshapes-for-your-Avatar">Creating Blendshapes for your Avatar</a></li>
<li><a href="https://github.com/highfidelity/hifi/wiki/How-to-Rig-a-Character-for-Faceshift">How to Rig a Character for Faceshift</a></li>
<li><a href="https://github.com/highfidelity/hifi/wiki/Naming-Your-Skeletal-Joints">Naming your Skeletal Joints</a></li>
<li><a href="https://github.com/highfidelity/hifi/wiki/The-FST-file">The FST File</a></li>
<li><a href="https://github.com/highfidelity/hifi/wiki/Training-in-Faceshift">Training in Faceshift</a></li>
<li><a href="https://github.com/highfidelity/hifi/wiki/Uploading-Your-Models">Uploading your Models</a></li>
</ul>
</section>
<footer>
<p>This project is maintained by <a href="https://github.com/highfidelity">highfidelity</a></p>
<p><small>Hosted on GitHub Pages &mdash; Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
</footer>
</div>
<script src="javascripts/scale.fix.js"></script>
</body>
</html>

View file

@ -26,11 +26,12 @@ varying vec4 alphaValues;
void main(void) {
// determine the cube face to use for texture coordinate generation
vec3 absNormal = abs(normal);
vec2 parameters = step(absNormal.yy, absNormal.xz) * step(absNormal.zx, absNormal.xz);
vec3 steps = step(absNormal.zzy, absNormal.xyx);
vec2 parameters = mix(vec2(0.0, steps.y), vec2(steps.x, steps.x), steps.z);
// blend the splat textures
gl_FragColor = (texture2D(diffuseMaps[0], mix(gl_TexCoord[0].xw, gl_TexCoord[0].zy, parameters)) * alphaValues.x +
texture2D(diffuseMaps[1], mix(gl_TexCoord[1].xw, gl_TexCoord[1].zy, parameters)) * alphaValues.y +
texture2D(diffuseMaps[2], mix(gl_TexCoord[2].xw, gl_TexCoord[2].zy, parameters)) * alphaValues.z +
texture2D(diffuseMaps[3], mix(gl_TexCoord[3].xw, gl_TexCoord[3].zy, parameters)) * alphaValues.w);
gl_FragColor = (texture2D(diffuseMaps[0], mix(gl_TexCoord[0].xy, gl_TexCoord[0].zw, parameters)) * alphaValues.x +
texture2D(diffuseMaps[1], mix(gl_TexCoord[1].xy, gl_TexCoord[1].zw, parameters)) * alphaValues.y +
texture2D(diffuseMaps[2], mix(gl_TexCoord[2].xy, gl_TexCoord[2].zw, parameters)) * alphaValues.z +
texture2D(diffuseMaps[3], mix(gl_TexCoord[3].xy, gl_TexCoord[3].zw, parameters)) * alphaValues.w);
}

View file

@ -299,9 +299,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
AddressManager& addressManager = AddressManager::getInstance();
// connect to the domainChangeRequired signal on AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequired,
// use our MyAvatar position and quat for address manager path
addressManager.setPositionGetter(getPositionForPath);
addressManager.setOrientationGetter(getOrientationForPath);
// handle domain change signals from AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequiredToHostname,
this, &Application::changeDomainHostname);
connect(&addressManager, &AddressManager::possibleDomainChangeRequiredViaICEForID,
&domainHandler, &DomainHandler::setIceServerHostnameAndID);
_settings = new QSettings(this);
_numChangedSettings = 0;
@ -1796,16 +1803,25 @@ void Application::init() {
Menu::getInstance()->loadSettings();
_audio.setReceivedAudioStreamSettings(Menu::getInstance()->getReceivedAudioStreamSettings());
qDebug() << "Loaded settings";
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
if (urlIndex != -1) {
AddressManager::getInstance().handleLookupString(arguments().value(urlIndex + 1));
} else {
// check if we have a URL in settings to load to jump back to
// we load this separate from the other settings so we don't double lookup a URL
QSettings* interfaceSettings = lockSettings();
QUrl addressURL = interfaceSettings->value(SETTINGS_ADDRESS_KEY).toUrl();
AddressManager::getInstance().handleLookupString(addressURL.toString());
unlockSettings();
}
qDebug() << "Loaded settings";
#ifdef __APPLE__
if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) {
// on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash
@ -3426,7 +3442,7 @@ void Application::updateWindowTitle(){
QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) ";
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
+ nodeList->getDomainHandler().getHostname() + connectionStatus + buildVersion;
+ AddressManager::getInstance().getCurrentDomain() + connectionStatus + buildVersion;
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.getAccountInfo().hasBalance()) {
@ -3448,25 +3464,23 @@ void Application::updateWindowTitle(){
void Application::updateLocationInServer() {
AccountManager& accountManager = AccountManager::getInstance();
const QUuid& domainUUID = NodeList::getInstance()->getDomainHandler().getUUID();
DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler();
if (accountManager.isLoggedIn() && !domainUUID.isNull()) {
if (accountManager.isLoggedIn() && domainHandler.isConnected() && !domainHandler.getUUID().isNull()) {
// construct a QJsonObject given the user's current address information
QJsonObject rootObject;
QJsonObject locationObject;
QString pathString = AddressManager::pathForPositionAndOrientation(_myAvatar->getPosition(),
true,
_myAvatar->getOrientation());
QString pathString = AddressManager::getInstance().currentPath();
const QString LOCATION_KEY_IN_ROOT = "location";
const QString PATH_KEY_IN_LOCATION = "path";
const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainUUID.toString());
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainHandler.getUUID().toString());
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
@ -3876,6 +3890,7 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
connect(scriptEngine, SIGNAL(loadScript(const QString&, bool)), this, SLOT(loadScript(const QString&, bool)));
scriptEngine->registerGlobalObject("Overlays", &_overlays);
qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue);
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,

View file

@ -140,6 +140,8 @@ class Application : public QApplication {
public:
static Application* getInstance() { return static_cast<Application*>(QCoreApplication::instance()); }
static QString& resourcesPath();
static const glm::vec3& getPositionForPath() { return getInstance()->_myAvatar->getPosition(); }
static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); }
Application(int& argc, char** argv, QElapsedTimer &startup_time);
~Application();

View file

@ -754,7 +754,6 @@ void Menu::loadSettings(QSettings* settings) {
scanMenuBar(&loadAction, settings);
Application::getInstance()->getAvatar()->loadData(settings);
Application::getInstance()->updateWindowTitle();
NodeList::getInstance()->loadData(settings);
// notify that a settings has changed
connect(&NodeList::getInstance()->getDomainHandler(), &DomainHandler::hostnameChanged, this, &Menu::bumpSettings);
@ -815,7 +814,8 @@ void Menu::saveSettings(QSettings* settings) {
scanMenuBar(&saveAction, settings);
Application::getInstance()->getAvatar()->saveData(settings);
NodeList::getInstance()->saveData(settings);
settings->setValue(SETTINGS_ADDRESS_KEY, AddressManager::getInstance().currentAddress());
if (lockedSettings) {
Application::getInstance()->unlockSettings();

View file

@ -51,6 +51,8 @@ const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f;
const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f;
const QString SETTINGS_ADDRESS_KEY = "address";
enum FrustumDrawMode {
FRUSTUM_DRAW_MODE_ALL,
FRUSTUM_DRAW_MODE_VECTORS,

View file

@ -33,6 +33,7 @@
REGISTER_META_OBJECT(DefaultMetavoxelRendererImplementation)
REGISTER_META_OBJECT(SphereRenderer)
REGISTER_META_OBJECT(CuboidRenderer)
REGISTER_META_OBJECT(StaticModelRenderer)
static int bufferPointVectorMetaTypeId = qRegisterMetaType<BufferPointVector>();
@ -963,6 +964,12 @@ void HeightfieldPreview::render(const glm::vec3& translation, float scale) const
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true, false);
}
void VoxelPoint::setNormal(const glm::vec3& normal) {
this->normal[0] = (char)(normal.x * 127.0f);
this->normal[1] = (char)(normal.y * 127.0f);
this->normal[2] = (char)(normal.z * 127.0f);
}
VoxelBuffer::VoxelBuffer(const QVector<VoxelPoint>& vertices, const QVector<int>& indices,
const QVector<SharedObjectPointer>& materials) :
_vertices(vertices),
@ -1534,8 +1541,21 @@ public:
glm::vec3 normal;
QRgb color;
char material;
int axis;
};
class AxisIndex {
public:
int x, y, z;
AxisIndex(int x = -1, int y = -1, int z = -1) : x(x), y(y), z(z) { }
};
static glm::vec3 safeNormalize(const glm::vec3& vector) {
float length = glm::length(vector);
return (length > 0.0f) ? (vector / length) : vector;
}
int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
if (!info.isLeaf) {
return DEFAULT_ORDER;
@ -1574,10 +1594,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
// as we scan down the cube generating vertices between grid points, we remember the indices of the last
// (element, line, section--x, y, z) so that we can connect generated vertices as quads
int expanded = size + 1;
QVector<int> lineIndices(expanded, -1);
QVector<int> lastLineIndices(expanded, -1);
QVector<int> planeIndices(expanded * expanded, -1);
QVector<int> lastPlaneIndices(expanded * expanded, -1);
QVector<AxisIndex> lineIndices(expanded);
QVector<AxisIndex> lastLineIndices(expanded);
QVector<AxisIndex> planeIndices(expanded * expanded);
QVector<AxisIndex> lastPlaneIndices(expanded * expanded);
const int EDGES_PER_CUBE = 12;
EdgeCrossing crossings[EDGES_PER_CUBE];
@ -1588,7 +1608,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
for (int z = 0; z < expanded; z++) {
const QRgb* colorY = colorZ;
for (int y = 0; y < expanded; y++) {
int lastIndex = 0;
AxisIndex lastIndex;
const QRgb* colorX = colorY;
for (int x = 0; x < expanded; x++) {
int alpha0 = colorX[0] >> ALPHA_OFFSET;
@ -1662,6 +1682,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[0];
}
crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f, 0.0f);
crossing.axis = 0;
}
if (middleY) {
if (alpha1 != alpha3) {
@ -1676,6 +1697,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[1];
}
crossing.point = glm::vec3(1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f);
crossing.axis = 1;
}
if (alpha2 != alpha3) {
QRgb hermite = hermiteBase[hermiteStride];
@ -1689,6 +1711,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[size];
}
crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f, 0.0f);
crossing.axis = 0;
}
if (middleZ) {
if (alpha3 != alpha7) {
@ -1703,6 +1726,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[offset3];
}
crossing.point = glm::vec3(1.0f, 1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL);
crossing.axis = 2;
}
if (alpha5 != alpha7) {
QRgb hermite = hermiteBase[hermiteArea + VoxelHermiteData::EDGE_COUNT + 1];
@ -1716,6 +1740,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[offset5];
}
crossing.point = glm::vec3(1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f);
crossing.axis = 1;
}
if (alpha6 != alpha7) {
QRgb hermite = hermiteBase[hermiteArea + hermiteStride];
@ -1729,6 +1754,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[offset6];
}
crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f, 1.0f);
crossing.axis = 0;
}
}
}
@ -1745,6 +1771,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[1];
}
crossing.point = glm::vec3(1.0f, 0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL);
crossing.axis = 2;
}
if (alpha4 != alpha5) {
QRgb hermite = hermiteBase[hermiteArea];
@ -1758,6 +1785,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[area];
}
crossing.point = glm::vec3(qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f, 1.0f);
crossing.axis = 0;
}
}
}
@ -1774,6 +1802,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[0];
}
crossing.point = glm::vec3(0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 0.0f);
crossing.axis = 1;
}
if (middleZ) {
if (alpha2 != alpha6) {
@ -1788,6 +1817,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[size];
}
crossing.point = glm::vec3(0.0f, 1.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL);
crossing.axis = 2;
}
if (alpha4 != alpha6) {
QRgb hermite = hermiteBase[hermiteArea + 1];
@ -1801,6 +1831,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[area];
}
crossing.point = glm::vec3(0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL, 1.0f);
crossing.axis = 1;
}
}
}
@ -1816,11 +1847,12 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
crossing.material = materialBase[0];
}
crossing.point = glm::vec3(0.0f, 0.0f, qAlpha(hermite) * EIGHT_BIT_MAXIMUM_RECIPROCAL);
crossing.axis = 2;
}
// at present, we simply average the properties of each crossing as opposed to finding the vertex that
// minimizes the quadratic error function as described in the reference paper
glm::vec3 center;
glm::vec3 normal;
glm::vec3 axisNormals[3];
const int MAX_MATERIALS_PER_VERTEX = 4;
quint8 materials[] = { 0, 0, 0, 0 };
glm::vec4 materialWeights;
@ -1829,7 +1861,7 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
for (int i = 0; i < crossingCount; i++) {
const EdgeCrossing& crossing = crossings[i];
center += crossing.point;
normal += crossing.normal;
axisNormals[crossing.axis] += crossing.normal;
red += qRed(crossing.color);
green += qGreen(crossing.color);
blue += qBlue(crossing.color);
@ -1852,16 +1884,18 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
}
}
}
normal = glm::normalize(normal);
glm::vec3 normal = safeNormalize(axisNormals[0] + axisNormals[1] + axisNormals[2]);
center /= crossingCount;
// use a sequence of Givens rotations to perform a QR decomposition
// see http://www.cs.rice.edu/~jwarren/papers/techreport02408.pdf
glm::mat4 r(0.0f);
glm::vec4 bottom;
float smallestCosNormal = 1.0f;
for (int i = 0; i < crossingCount; i++) {
const EdgeCrossing& crossing = crossings[i];
bottom = glm::vec4(crossing.normal, glm::dot(crossing.normal, crossing.point - center));
smallestCosNormal = qMin(smallestCosNormal, glm::dot(crossing.normal, normal));
for (int j = 0; j < 4; j++) {
float angle = glm::atan(-bottom[j], r[j][j]);
@ -1899,12 +1933,9 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
int largestI = (largestIndex == 0) ? 1 : 2;
float sjj = d[largestJ][largestJ];
float sii = d[largestI][largestI];
float angle = (sii == sjj ? PI_OVER_TWO : glm::atan(2.0f * d[largestJ][largestI], sjj - sii)) / 2.0f;
float angle = glm::atan(2.0f * d[largestJ][largestI], sjj - sii) / 2.0f;
glm::quat rotation = glm::angleAxis(angle, largestIndex == 0 ? glm::vec3(0.0f, 0.0f, -1.0f) :
(largestIndex == 1 ? glm::vec3(0.0f, 1.0f, 0.0f) : glm::vec3(-1.0f, 0.0f, 0.0f)));
if (rotation.w == 0.0f) {
break;
}
combinedRotation = glm::normalize(rotation * combinedRotation);
glm::mat3 matrix = glm::mat3_cast(combinedRotation);
d = matrix * ata * glm::transpose(matrix);
@ -1933,17 +1964,69 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
{ materials[0], materials[1], materials[2], materials[3] },
{ (quint8)materialWeights[0], (quint8)materialWeights[1], (quint8)materialWeights[2],
(quint8)materialWeights[3] } };
int index = vertices.size();
vertices.append(point);
// determine whether we must "crease" by generating directional normals
const float CREASE_COS_NORMAL = glm::cos(glm::radians(40.0f));
AxisIndex index(vertices.size(), vertices.size(), vertices.size());
if (smallestCosNormal > CREASE_COS_NORMAL) {
vertices.append(point);
} else {
axisNormals[0] = safeNormalize(axisNormals[0]);
axisNormals[1] = safeNormalize(axisNormals[1]);
axisNormals[2] = safeNormalize(axisNormals[2]);
glm::vec3 normalXY(safeNormalize(axisNormals[0] + axisNormals[1]));
glm::vec3 normalXZ(safeNormalize(axisNormals[0] + axisNormals[2]));
glm::vec3 normalYZ(safeNormalize(axisNormals[1] + axisNormals[2]));
if (glm::dot(axisNormals[0], normalXY) > CREASE_COS_NORMAL &&
glm::dot(axisNormals[1], normalXY) > CREASE_COS_NORMAL) {
point.setNormal(normalXY);
vertices.append(point);
point.setNormal(axisNormals[2]);
index.z = vertices.size();
vertices.append(point);
} else if (glm::dot(axisNormals[0], normalXZ) > CREASE_COS_NORMAL &&
glm::dot(axisNormals[2], normalXZ) > CREASE_COS_NORMAL) {
point.setNormal(normalXZ);
vertices.append(point);
point.setNormal(axisNormals[1]);
index.y = vertices.size();
vertices.append(point);
} else if (glm::dot(axisNormals[1], normalYZ) > CREASE_COS_NORMAL &&
glm::dot(axisNormals[2], normalYZ) > CREASE_COS_NORMAL) {
point.setNormal(normalYZ);
vertices.append(point);
point.setNormal(axisNormals[0]);
index.x = vertices.size();
vertices.append(point);
} else {
point.setNormal(axisNormals[0]);
vertices.append(point);
point.setNormal(axisNormals[1]);
index.y = vertices.size();
vertices.append(point);
point.setNormal(axisNormals[2]);
index.z = vertices.size();
vertices.append(point);
}
}
// the first x, y, and z are repeated for the boundary edge; past that, we consider generating
// quads for each edge that includes a transition, using indices of previously generated vertices
if (x != 0 && y != 0 && z != 0) {
if (alpha0 != alpha1) {
indices.append(index);
int index1 = lastLineIndices.at(x);
int index2 = lastPlaneIndices.at((y - 1) * expanded + x);
int index3 = lastPlaneIndices.at(y * expanded + x);
indices.append(index.x);
int index1 = lastLineIndices.at(x).x;
int index2 = lastPlaneIndices.at((y - 1) * expanded + x).x;
int index3 = lastPlaneIndices.at(y * expanded + x).x;
if (alpha0 == 0) { // quad faces negative x
indices.append(index3);
indices.append(index2);
@ -1956,10 +2039,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
}
if (alpha0 != alpha2) {
indices.append(index);
int index1 = lastIndex;
int index2 = lastPlaneIndices.at(y * expanded + x - 1);
int index3 = lastPlaneIndices.at(y * expanded + x);
indices.append(index.y);
int index1 = lastIndex.y;
int index2 = lastPlaneIndices.at(y * expanded + x - 1).y;
int index3 = lastPlaneIndices.at(y * expanded + x).y;
if (alpha0 == 0) { // quad faces negative y
indices.append(index1);
indices.append(index2);
@ -1972,10 +2055,10 @@ int VoxelAugmentVisitor::visit(MetavoxelInfo& info) {
}
if (alpha0 != alpha4) {
indices.append(index);
int index1 = lastIndex;
int index2 = lastLineIndices.at(x - 1);
int index3 = lastLineIndices.at(x);
indices.append(index.z);
int index1 = lastIndex.z;
int index2 = lastLineIndices.at(x - 1).z;
int index3 = lastLineIndices.at(x).z;
if (alpha0 == 0) { // quad faces negative z
indices.append(index3);
indices.append(index2);
@ -2119,7 +2202,8 @@ int SpannerRenderVisitor::visit(MetavoxelInfo& info) {
}
bool SpannerRenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) {
spanner->getRenderer()->render(1.0f, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize);
const glm::vec4 OPAQUE_WHITE(1.0f, 1.0f, 1.0f, 1.0f);
spanner->getRenderer()->render(OPAQUE_WHITE, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize);
return true;
}
@ -2285,9 +2369,9 @@ static void enableClipPlane(GLenum plane, float x, float y, float z, float w) {
glEnable(plane);
}
void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
void ClippedRenderer::render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
if (clipSize == 0.0f) {
renderUnclipped(alpha, mode);
renderUnclipped(color, mode);
return;
}
enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipSize);
@ -2297,7 +2381,7 @@ void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimu
enableClipPlane(GL_CLIP_PLANE4, 0.0f, 0.0f, -1.0f, clipMinimum.z + clipSize);
enableClipPlane(GL_CLIP_PLANE5, 0.0f, 0.0f, 1.0f, -clipMinimum.z);
renderUnclipped(alpha, mode);
renderUnclipped(color, mode);
glDisable(GL_CLIP_PLANE0);
glDisable(GL_CLIP_PLANE1);
@ -2310,9 +2394,9 @@ void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimu
SphereRenderer::SphereRenderer() {
}
void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
void SphereRenderer::render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
if (clipSize == 0.0f) {
renderUnclipped(alpha, mode);
renderUnclipped(color, mode);
return;
}
// slight performance optimization: don't render if clip bounds are entirely within sphere
@ -2321,29 +2405,50 @@ void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum
for (int i = 0; i < Box::VERTEX_COUNT; i++) {
const float CLIP_PROPORTION = 0.95f;
if (glm::distance(sphere->getTranslation(), clipBox.getVertex(i)) >= sphere->getScale() * CLIP_PROPORTION) {
ClippedRenderer::render(alpha, mode, clipMinimum, clipSize);
ClippedRenderer::render(color, mode, clipMinimum, clipSize);
return;
}
}
}
void SphereRenderer::renderUnclipped(float alpha, Mode mode) {
void SphereRenderer::renderUnclipped(const glm::vec4& color, Mode mode) {
Sphere* sphere = static_cast<Sphere*>(_spanner);
const QColor& color = sphere->getColor();
glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF() * alpha);
const QColor& ownColor = sphere->getColor();
glColor4f(ownColor.redF() * color.r, ownColor.greenF() * color.g, ownColor.blueF() * color.b, ownColor.alphaF() * color.a);
glPushMatrix();
const glm::vec3& translation = sphere->getTranslation();
glTranslatef(translation.x, translation.y, translation.z);
glm::quat rotation = sphere->getRotation();
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
Application::getInstance()->getGeometryCache()->renderSphere(sphere->getScale(), 10, 10);
glPopMatrix();
}
CuboidRenderer::CuboidRenderer() {
}
void CuboidRenderer::renderUnclipped(const glm::vec4& color, Mode mode) {
Cuboid* cuboid = static_cast<Cuboid*>(_spanner);
const QColor& ownColor = cuboid->getColor();
glColor4f(ownColor.redF() * color.r, ownColor.greenF() * color.g, ownColor.blueF() * color.b, ownColor.alphaF() * color.a);
glPushMatrix();
const glm::vec3& translation = cuboid->getTranslation();
glTranslatef(translation.x, translation.y, translation.z);
glm::quat rotation = cuboid->getRotation();
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glScalef(1.0f, cuboid->getAspectY(), cuboid->getAspectZ());
glutSolidCube(cuboid->getScale() * 2.0f);
glPopMatrix();
}
StaticModelRenderer::StaticModelRenderer() :
_model(new Model(this)) {
}
@ -2377,21 +2482,21 @@ void StaticModelRenderer::simulate(float deltaTime) {
_model->simulate(deltaTime);
}
void StaticModelRenderer::renderUnclipped(float alpha, Mode mode) {
void StaticModelRenderer::renderUnclipped(const glm::vec4& color, Mode mode) {
switch (mode) {
case DIFFUSE_MODE:
_model->render(alpha, Model::DIFFUSE_RENDER_MODE);
_model->render(color.a, Model::DIFFUSE_RENDER_MODE);
break;
case NORMAL_MODE:
_model->render(alpha, Model::NORMAL_RENDER_MODE);
_model->render(color.a, Model::NORMAL_RENDER_MODE);
break;
default:
_model->render(alpha);
_model->render(color.a);
break;
}
_model->render(alpha);
_model->render(color.a);
}
bool StaticModelRenderer::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,

View file

@ -226,6 +226,8 @@ public:
char normal[3];
quint8 materials[4];
quint8 materialWeights[4];
void setNormal(const glm::vec3& normal);
};
/// Contains the information necessary to render a voxel block.
@ -338,11 +340,11 @@ class ClippedRenderer : public SpannerRenderer {
public:
virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize);
virtual void render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize);
protected:
virtual void renderUnclipped(float alpha, Mode mode) = 0;
virtual void renderUnclipped(const glm::vec4& color, Mode mode) = 0;
};
/// Renders spheres.
@ -353,11 +355,24 @@ public:
Q_INVOKABLE SphereRenderer();
virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize);
virtual void render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize);
protected:
virtual void renderUnclipped(float alpha, Mode mode);
virtual void renderUnclipped(const glm::vec4& color, Mode mode);
};
/// Renders cuboids.
class CuboidRenderer : public ClippedRenderer {
Q_OBJECT
public:
Q_INVOKABLE CuboidRenderer();
protected:
virtual void renderUnclipped(const glm::vec4& color, Mode mode);
};
/// Renders static models.
@ -375,7 +390,7 @@ public:
protected:
virtual void renderUnclipped(float alpha, Mode mode);
virtual void renderUnclipped(const glm::vec4& color, Mode mode);
private slots:

View file

@ -740,16 +740,8 @@ AnimationDetails MyAvatar::getAnimationDetails(const QString& url) {
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");
settings->setValue("bodyYaw", _bodyYaw);
settings->setValue("bodyPitch", _bodyPitch);
settings->setValue("bodyRoll", _bodyRoll);
settings->setValue("headPitch", getHead()->getBasePitch());
settings->setValue("position_x", _position.x);
settings->setValue("position_y", _position.y);
settings->setValue("position_z", _position.z);
settings->setValue("pupilDilation", getHead()->getPupilDilation());
settings->setValue("leanScale", _leanScale);
@ -800,19 +792,8 @@ void MyAvatar::saveData(QSettings* settings) {
void MyAvatar::loadData(QSettings* settings) {
settings->beginGroup("Avatar");
// in case settings is corrupt or missing loadSetting() will check for NaN
_bodyYaw = loadSetting(settings, "bodyYaw", 0.0f);
_bodyPitch = loadSetting(settings, "bodyPitch", 0.0f);
_bodyRoll = loadSetting(settings, "bodyRoll", 0.0f);
getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
glm::vec3 newPosition;
newPosition.x = loadSetting(settings, "position_x", START_LOCATION.x);
newPosition.y = loadSetting(settings, "position_y", START_LOCATION.y);
newPosition.z = loadSetting(settings, "position_z", START_LOCATION.z);
slamPosition(newPosition);
getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f));
_leanScale = loadSetting(settings, "leanScale", 0.05f);

View file

@ -101,10 +101,10 @@ void SixenseManager::initialize() {
_sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME);
#else
const QString SIXENSE_LIBRARY_NAME = "libsixense_x64";
QFileInfo frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "../Frameworks/"
QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "/../Frameworks/"
+ SIXENSE_LIBRARY_NAME;
_sixenseLibrary = new QLibrary(frameworkSixenseLibrary.fileName());
_sixenseLibrary = new QLibrary(frameworkSixenseLibrary);
#endif
}

View file

@ -39,9 +39,12 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
glm::quat rotation = getRotation();
const bool useGlutCube = true;
const float MAX_COLOR = 255.0f;
if (useGlutCube) {
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR,
getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha());
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
@ -84,7 +87,8 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
glNormalPointer(GL_FLOAT, 0, normals);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR,
getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha());
Application::getInstance()->getDeferredLightingEffect()->bindSimpleProgram();

View file

@ -71,8 +71,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
if (drawAsModel) {
glPushMatrix();
{
const float alpha = 1.0f;
float alpha = getLocalRenderAlpha();
if (!_model || _needsModelReload) {
// TODO: this getModel() appears to be about 3% of model render time. We should optimize
PerformanceTimer perfTimer("getModel");

View file

@ -36,7 +36,10 @@ void RenderableSphereEntityItem::render(RenderArgs* args) {
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::quat rotation = getRotation();
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
const float MAX_COLOR = 255.0f;
glColor4f(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR,
getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha());
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);

View file

@ -29,9 +29,7 @@ QString LocationScriptingInterface::getHref() {
}
QString LocationScriptingInterface::getPathname() {
MyAvatar* applicationAvatar = Application::getInstance()->getAvatar();
return AddressManager::pathForPositionAndOrientation(applicationAvatar->getPosition(),
true, applicationAvatar->getOrientation());
return AddressManager::getInstance().currentPath();
}
QString LocationScriptingInterface::getHostname() {

View file

@ -124,14 +124,11 @@ MetavoxelEditor::MetavoxelEditor() :
addTool(new ClearSpannersTool(this));
addTool(new SetSpannerTool(this));
addTool(new HeightfieldHeightBrushTool(this));
addTool(new HeightfieldColorBrushTool(this));
addTool(new HeightfieldMaterialBrushTool(this));
addTool(new ImportHeightfieldTool(this));
addTool(new EraseHeightfieldTool(this));
addTool(new VoxelColorBoxTool(this));
addTool(new VoxelMaterialBoxTool(this));
addTool(new VoxelColorSphereTool(this));
addTool(new VoxelMaterialSphereTool(this));
addTool(new VoxelMaterialSpannerTool(this));
updateAttributes();
@ -435,7 +432,7 @@ void BoxTool::render() {
glm::quat inverseRotation = glm::inverse(rotation);
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
float spacing = _editor->getGridSpacing();
float spacing = shouldSnapToGrid() ? _editor->getGridSpacing() : 0.0f;
float position = _editor->getGridPosition();
if (_state == RAISING_STATE) {
// find the plane at the mouse position, orthogonal to the plane, facing the eye position
@ -448,13 +445,13 @@ void BoxTool::render() {
if (fabs(divisor) > EPSILON) {
float distance = (glm::dot(normal, mousePoint) - glm::dot(normal, rayOrigin)) / divisor;
float projection = rayOrigin.z + distance * rayDirection.z;
_height = spacing * roundf(projection / spacing) - position;
_height = (spacing == 0.0f ? projection : spacing * roundf(projection / spacing)) - position;
}
} else if (fabs(rayDirection.z) > EPSILON) {
// find the intersection of the rotated mouse ray with the plane
float distance = (position - rayOrigin.z) / rayDirection.z;
_mousePosition = glm::vec2(rayOrigin + rayDirection * distance);
glm::vec2 snappedPosition = spacing * glm::floor(_mousePosition / spacing);
glm::vec2 snappedPosition = (spacing == 0.0f) ? _mousePosition : spacing * glm::floor(_mousePosition / spacing);
if (_state == HOVERING_STATE) {
_startPosition = _endPosition = snappedPosition;
@ -523,7 +520,7 @@ bool BoxTool::eventFilter(QObject* watched, QEvent* event) {
float top = base + _height;
glm::quat rotation = _editor->getGridRotation();
glm::vec3 start = rotation * glm::vec3(glm::min(_startPosition, _endPosition), glm::min(base, top));
float spacing = _editor->getGridSpacing();
float spacing = shouldSnapToGrid() ? _editor->getGridSpacing() : 0.0f;
glm::vec3 end = rotation * glm::vec3(glm::max(_startPosition, _endPosition) +
glm::vec2(spacing, spacing), glm::max(base, top));
@ -538,6 +535,10 @@ bool BoxTool::eventFilter(QObject* watched, QEvent* event) {
return false;
}
bool BoxTool::shouldSnapToGrid() {
return true;
}
void BoxTool::resetState() {
_state = HOVERING_STATE;
_startPosition = INVALID_VECTOR;
@ -581,20 +582,21 @@ void GlobalSetTool::apply() {
Application::getInstance()->getMetavoxels()->applyEdit(message);
}
PlaceSpannerTool::PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, const QString& placeText) :
MetavoxelTool(editor, name) {
PlaceSpannerTool::PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, const QString& placeText, bool usesValue) :
MetavoxelTool(editor, name, usesValue) {
QPushButton* button = new QPushButton(placeText);
layout()->addWidget(button);
connect(button, SIGNAL(clicked()), SLOT(place()));
if (!placeText.isEmpty()) {
QPushButton* button = new QPushButton(placeText);
layout()->addWidget(button);
connect(button, SIGNAL(clicked()), SLOT(place()));
}
}
void PlaceSpannerTool::simulate(float deltaTime) {
if (Application::getInstance()->isMouseHidden()) {
return;
}
_editor->detachValue();
Spanner* spanner = static_cast<Spanner*>(_editor->getValue().value<SharedObjectPointer>().data());
Spanner* spanner = static_cast<Spanner*>(getSpanner(true).data());
Transformable* transformable = qobject_cast<Transformable*>(spanner);
if (transformable) {
// find the intersection of the mouse ray with the grid and place the transformable there
@ -614,9 +616,11 @@ void PlaceSpannerTool::render() {
if (Application::getInstance()->isMouseHidden()) {
return;
}
Spanner* spanner = static_cast<Spanner*>(_editor->getValue().value<SharedObjectPointer>().data());
Spanner* spanner = static_cast<Spanner*>(getSpanner().data());
const float SPANNER_ALPHA = 0.25f;
spanner->getRenderer()->render(SPANNER_ALPHA, SpannerRenderer::DEFAULT_MODE, glm::vec3(), 0.0f);
QColor color = getColor();
spanner->getRenderer()->render(glm::vec4(color.redF(), color.greenF(), color.blueF(), SPANNER_ALPHA),
SpannerRenderer::DEFAULT_MODE, glm::vec3(), 0.0f);
}
bool PlaceSpannerTool::appliesTo(const AttributePointer& attribute) const {
@ -631,10 +635,21 @@ bool PlaceSpannerTool::eventFilter(QObject* watched, QEvent* event) {
return false;
}
SharedObjectPointer PlaceSpannerTool::getSpanner(bool detach) {
if (detach) {
_editor->detachValue();
}
return _editor->getValue().value<SharedObjectPointer>();
}
QColor PlaceSpannerTool::getColor() {
return Qt::white;
}
void PlaceSpannerTool::place() {
AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute());
if (attribute) {
applyEdit(attribute, _editor->getValue().value<SharedObjectPointer>());
applyEdit(attribute, getSpanner());
}
}
@ -903,7 +918,8 @@ void SetSpannerTool::applyEdit(const AttributePointer& attribute, const SharedOb
Application::getInstance()->updateUntranslatedViewMatrix();
spannerData->getRenderer()->render(1.0f, SpannerRenderer::DIFFUSE_MODE, glm::vec3(), 0.0f);
const glm::vec4 OPAQUE_WHITE(1.0f, 1.0f, 1.0f, 1.0f);
spannerData->getRenderer()->render(OPAQUE_WHITE, SpannerRenderer::DIFFUSE_MODE, glm::vec3(), 0.0f);
DirectionImages images = { QImage(width, height, QImage::Format_ARGB32),
QVector<float>(width * height), minima, maxima, glm::vec3(width / (maxima.x - minima.x),
@ -1277,20 +1293,11 @@ QVariant HeightfieldHeightBrushTool::createEdit(bool alternate) {
alternate ? -_height->value() : _height->value()));
}
HeightfieldColorBrushTool::HeightfieldColorBrushTool(MetavoxelEditor* editor) :
HeightfieldBrushTool(editor, "Color Brush") {
_form->addRow("Color:", _color = new QColorEditor(this));
}
QVariant HeightfieldColorBrushTool::createEdit(bool alternate) {
return QVariant::fromValue(PaintHeightfieldColorEdit(_position, _radius->value(),
alternate ? QColor() : _color->getColor()));
}
HeightfieldMaterialBrushTool::HeightfieldMaterialBrushTool(MetavoxelEditor* editor) :
HeightfieldBrushTool(editor, "Material Brush") {
_form->addRow("Color:", _color = new QColorEditor(this));
connect(_color, &QColorEditor::colorChanged, this, &HeightfieldMaterialBrushTool::clearTexture);
_form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &HeightfieldMaterialBrushTool::updateTexture);
}
@ -1300,43 +1307,41 @@ QVariant HeightfieldMaterialBrushTool::createEdit(bool alternate) {
return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), SharedObjectPointer(), QColor()));
} else {
SharedObjectPointer material = _materialEditor->getObject();
_materialEditor->detachObject();
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
material = SharedObjectPointer();
}
return QVariant::fromValue(PaintHeightfieldMaterialEdit(_position, _radius->value(), material,
_texture ? _texture->getAverageColor() : QColor()));
_color->getColor()));
}
}
void HeightfieldMaterialBrushTool::clearTexture() {
_materialEditor->setObject(new MaterialObject());
}
void HeightfieldMaterialBrushTool::updateTexture() {
if (_texture) {
_texture->disconnect(this);
}
MaterialObject* material = static_cast<MaterialObject*>(_materialEditor->getObject().data());
if (!material->getDiffuse().isValid()) {
_texture.clear();
return;
}
_texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE);
if (_texture) {
if (_texture->isLoaded()) {
textureLoaded();
} else {
connect(_texture.data(), &Resource::loaded, this, &HeightfieldMaterialBrushTool::textureLoaded);
}
}
}
VoxelColorBoxTool::VoxelColorBoxTool(MetavoxelEditor* editor) :
BoxTool(editor, "Set Voxel Color (Box)", false) {
QWidget* widget = new QWidget();
QFormLayout* form = new QFormLayout();
widget->setLayout(form);
layout()->addWidget(widget);
form->addRow("Color:", _color = new QColorEditor(this));
}
bool VoxelColorBoxTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("VoxelColorAttribute");
}
QColor VoxelColorBoxTool::getColor() {
return _color->getColor();
}
void VoxelColorBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
// ensure that color is either 100% transparent or 100% opaque
QColor color = _color->getColor();
color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f);
MetavoxelEditMessage message = { QVariant::fromValue(VoxelColorBoxEdit(Box(minimum, maximum),
_editor->getGridSpacing(), color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
void HeightfieldMaterialBrushTool::textureLoaded() {
_color->setColor(_texture->getAverageColor());
}
VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) :
@ -1347,6 +1352,22 @@ VoxelMaterialBoxTool::VoxelMaterialBoxTool(MetavoxelEditor* editor) :
widget->setLayout(form);
layout()->addWidget(widget);
QHBoxLayout* gridLayout = new QHBoxLayout();
gridLayout->addStretch(1);
gridLayout->addWidget(_snapToGrid = new QCheckBox("Snap to Grid"));
gridLayout->addStretch(1);
form->addRow(gridLayout);
_snapToGrid->setChecked(true);
QHBoxLayout* colorLayout = new QHBoxLayout();
form->addRow(colorLayout);
colorLayout->addWidget(new QLabel("Color:"));
colorLayout->addWidget(_color = new QColorEditor(this), 1);
connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialBoxTool::clearTexture);
QPushButton* eraseButton = new QPushButton("Erase");
colorLayout->addWidget(eraseButton);
connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialBoxTool::clearColor);
form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialBoxTool::updateTexture);
}
@ -1355,139 +1376,152 @@ bool VoxelMaterialBoxTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("VoxelColorAttribute");
}
bool VoxelMaterialBoxTool::shouldSnapToGrid() {
return _snapToGrid->isChecked();
}
QColor VoxelMaterialBoxTool::getColor() {
return _texture ? _texture->getAverageColor() : QColor();
return _color->getColor();
}
void VoxelMaterialBoxTool::applyValue(const glm::vec3& minimum, const glm::vec3& maximum) {
SharedObjectPointer material = _materialEditor->getObject();
_materialEditor->detachObject();
QColor color;
if (_texture) {
color = _texture->getAverageColor();
color.setAlphaF(1.0f);
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
material = SharedObjectPointer();
}
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialBoxEdit(Box(minimum, maximum),
_editor->getGridSpacing(), material, color)) };
QColor color = _color->getColor();
color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f);
Cuboid* cuboid = new Cuboid();
cuboid->setTranslation((maximum + minimum) * 0.5f);
glm::vec3 vector = (maximum - minimum) * 0.5f;
cuboid->setScale(vector.x);
cuboid->setAspectY(vector.y / vector.x);
cuboid->setAspectZ(vector.z / vector.x);
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(SharedObjectPointer(cuboid), material, color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
void VoxelMaterialBoxTool::clearColor() {
_color->setColor(QColor(0, 0, 0, 0));
clearTexture();
}
void VoxelMaterialBoxTool::clearTexture() {
_materialEditor->setObject(new MaterialObject());
}
void VoxelMaterialBoxTool::updateTexture() {
if (_texture) {
_texture->disconnect(this);
}
MaterialObject* material = static_cast<MaterialObject*>(_materialEditor->getObject().data());
if (!material->getDiffuse().isValid()) {
_texture.clear();
return;
}
_texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE);
if (_texture) {
if (_texture->isLoaded()) {
textureLoaded();
} else {
connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialBoxTool::textureLoaded);
}
}
}
SphereTool::SphereTool(MetavoxelEditor* editor, const QString& name) :
MetavoxelTool(editor, name, false, true) {
void VoxelMaterialBoxTool::textureLoaded() {
_color->setColor(_texture->getAverageColor());
}
VoxelMaterialSpannerTool::VoxelMaterialSpannerTool(MetavoxelEditor* editor) :
PlaceSpannerTool(editor, "Set Voxel Material (Spanner)", QString(), false) {
QWidget* widget = new QWidget();
widget->setLayout(_form = new QFormLayout());
QFormLayout* form = new QFormLayout();
widget->setLayout(form);
layout()->addWidget(widget);
_form->addRow("Radius:", _radius = new QDoubleSpinBox());
_radius->setSingleStep(0.01);
_radius->setMaximum(FLT_MAX);
_radius->setValue(1.0);
form->addRow(_spannerEditor = new SharedObjectEditor(&Spanner::staticMetaObject, false, this));
_spannerEditor->setObject(new Sphere());
QHBoxLayout* colorLayout = new QHBoxLayout();
form->addRow(colorLayout);
colorLayout->addWidget(new QLabel("Color:"));
colorLayout->addWidget(_color = new QColorEditor(this), 1);
connect(_color, &QColorEditor::colorChanged, this, &VoxelMaterialSpannerTool::clearTexture);
QPushButton* eraseButton = new QPushButton("Erase");
colorLayout->addWidget(eraseButton);
connect(eraseButton, &QPushButton::clicked, this, &VoxelMaterialSpannerTool::clearColor);
form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSpannerTool::updateTexture);
QPushButton* place = new QPushButton("Set");
layout()->addWidget(place);
connect(place, &QPushButton::clicked, this, &VoxelMaterialSpannerTool::place);
}
void SphereTool::render() {
if (Application::getInstance()->isMouseHidden()) {
return;
}
glm::quat rotation = _editor->getGridRotation();
glm::quat inverseRotation = glm::inverse(rotation);
glm::vec3 rayOrigin = inverseRotation * Application::getInstance()->getMouseRayOrigin();
glm::vec3 rayDirection = inverseRotation * Application::getInstance()->getMouseRayDirection();
float position = _editor->getGridPosition();
if (glm::abs(rayDirection.z) < EPSILON) {
return;
}
float distance = (position - rayOrigin.z) / rayDirection.z;
_position = Application::getInstance()->getMouseRayOrigin() +
Application::getInstance()->getMouseRayDirection() * distance;
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
const float CURSOR_ALPHA = 0.5f;
QColor color = getColor();
glColor4f(color.redF(), color.greenF(), color.blueF(), CURSOR_ALPHA);
glEnable(GL_CULL_FACE);
Application::getInstance()->getGeometryCache()->renderSphere(_radius->value(), 10, 10);
glDisable(GL_CULL_FACE);
glPopMatrix();
}
bool SphereTool::eventFilter(QObject* watched, QEvent* event) {
if (event->type() == QEvent::Wheel) {
float angle = static_cast<QWheelEvent*>(event)->angleDelta().y();
const float ANGLE_SCALE = 1.0f / 1000.0f;
_radius->setValue(_radius->value() * glm::pow(2.0f, angle * ANGLE_SCALE));
return true;
} else if (event->type() == QEvent::MouseButtonPress) {
applyValue(_position, _radius->value());
return true;
}
return false;
}
VoxelColorSphereTool::VoxelColorSphereTool(MetavoxelEditor* editor) :
SphereTool(editor, "Set Voxel Color (Sphere)") {
_form->addRow("Color:", _color = new QColorEditor(this));
}
bool VoxelColorSphereTool::appliesTo(const AttributePointer& attribute) const {
bool VoxelMaterialSpannerTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("VoxelColorAttribute");
}
QColor VoxelColorSphereTool::getColor() {
SharedObjectPointer VoxelMaterialSpannerTool::getSpanner(bool detach) {
if (detach) {
_spannerEditor->detachObject();
}
return _spannerEditor->getObject();
}
QColor VoxelMaterialSpannerTool::getColor() {
return _color->getColor();
}
void VoxelColorSphereTool::applyValue(const glm::vec3& position, float radius) {
// ensure that color is either 100% transparent or 100% opaque
void VoxelMaterialSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) {
_spannerEditor->detachObject();
SharedObjectPointer material = _materialEditor->getObject();
if (static_cast<MaterialObject*>(material.data())->getDiffuse().isValid()) {
_materialEditor->detachObject();
} else {
material = SharedObjectPointer();
}
QColor color = _color->getColor();
color.setAlphaF(color.alphaF() > 0.5f ? 1.0f : 0.0f);
MetavoxelEditMessage message = { QVariant::fromValue(VoxelColorSphereEdit(position, radius,
_editor->getGridSpacing(), color)) };
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSpannerEdit(spanner, material, color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
VoxelMaterialSphereTool::VoxelMaterialSphereTool(MetavoxelEditor* editor) :
SphereTool(editor, "Set Voxel Material (Sphere)") {
_form->addRow(_materialEditor = new SharedObjectEditor(&MaterialObject::staticMetaObject, false));
connect(_materialEditor, &SharedObjectEditor::objectChanged, this, &VoxelMaterialSphereTool::updateTexture);
void VoxelMaterialSpannerTool::clearColor() {
_color->setColor(QColor(0, 0, 0, 0));
clearTexture();
}
bool VoxelMaterialSphereTool::appliesTo(const AttributePointer& attribute) const {
return attribute->inherits("VoxelColorAttribute");
void VoxelMaterialSpannerTool::clearTexture() {
_materialEditor->setObject(new MaterialObject());
}
QColor VoxelMaterialSphereTool::getColor() {
return _texture ? _texture->getAverageColor() : QColor();
}
void VoxelMaterialSphereTool::applyValue(const glm::vec3& position, float radius) {
SharedObjectPointer material = _materialEditor->getObject();
_materialEditor->detachObject();
QColor color;
void VoxelMaterialSpannerTool::updateTexture() {
if (_texture) {
color = _texture->getAverageColor();
color.setAlphaF(1.0f);
_texture->disconnect(this);
}
MaterialObject* material = static_cast<MaterialObject*>(_materialEditor->getObject().data());
if (!material->getDiffuse().isValid()) {
_texture.clear();
return;
}
_texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE);
if (_texture) {
if (_texture->isLoaded()) {
textureLoaded();
} else {
connect(_texture.data(), &Resource::loaded, this, &VoxelMaterialSpannerTool::textureLoaded);
}
}
MetavoxelEditMessage message = { QVariant::fromValue(VoxelMaterialSphereEdit(position, radius,
_editor->getGridSpacing(), material, color)) };
Application::getInstance()->getMetavoxels()->applyEdit(message, true);
}
void VoxelMaterialSphereTool::updateTexture() {
MaterialObject* material = static_cast<MaterialObject*>(_materialEditor->getObject().data());
_texture = Application::getInstance()->getTextureCache()->getTexture(material->getDiffuse(), SPLAT_TEXTURE);
void VoxelMaterialSpannerTool::textureLoaded() {
_color->setColor(_texture->getAverageColor());
}

View file

@ -125,6 +125,8 @@ public:
protected:
virtual bool shouldSnapToGrid();
virtual QColor getColor() = 0;
virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum) = 0;
@ -177,7 +179,8 @@ class PlaceSpannerTool : public MetavoxelTool {
public:
PlaceSpannerTool(MetavoxelEditor* editor, const QString& name, const QString& placeText);
PlaceSpannerTool(MetavoxelEditor* editor, const QString& name,
const QString& placeText = QString(), bool usesValue = true);
virtual void simulate(float deltaTime);
@ -189,9 +192,11 @@ public:
protected:
virtual QColor getColor();
virtual SharedObjectPointer getSpanner(bool detach = false);
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) = 0;
private slots:
protected slots:
void place();
};
@ -375,23 +380,6 @@ private:
QDoubleSpinBox* _height;
};
/// Allows coloring parts of the heightfield.
class HeightfieldColorBrushTool : public HeightfieldBrushTool {
Q_OBJECT
public:
HeightfieldColorBrushTool(MetavoxelEditor* editor);
protected:
virtual QVariant createEdit(bool alternate);
private:
QColorEditor* _color;
};
/// Allows texturing parts of the heightfield.
class HeightfieldMaterialBrushTool : public HeightfieldBrushTool {
Q_OBJECT
@ -406,33 +394,15 @@ protected:
private slots:
void clearTexture();
void updateTexture();
void textureLoaded();
private:
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
};
/// Allows setting voxel colors by dragging out a box.
class VoxelColorBoxTool : public BoxTool {
Q_OBJECT
public:
VoxelColorBoxTool(MetavoxelEditor* editor);
virtual bool appliesTo(const AttributePointer& attribute) const;
protected:
virtual QColor getColor();
virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
private:
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
};
/// Allows setting voxel materials by dragging out a box.
@ -447,87 +417,54 @@ public:
protected:
virtual bool shouldSnapToGrid();
virtual QColor getColor();
virtual void applyValue(const glm::vec3& minimum, const glm::vec3& maximum);
private slots:
void clearColor();
void clearTexture();
void updateTexture();
void textureLoaded();
private:
QCheckBox* _snapToGrid;
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
};
/// Base class for tools based on a sphere brush.
class SphereTool : public MetavoxelTool {
/// Allows setting voxel materials by placing a spanner.
class VoxelMaterialSpannerTool : public PlaceSpannerTool {
Q_OBJECT
public:
SphereTool(MetavoxelEditor* editor, const QString& name);
virtual void render();
virtual bool eventFilter(QObject* watched, QEvent* event);
protected:
virtual QColor getColor() = 0;
virtual void applyValue(const glm::vec3& position, float radius) = 0;
QFormLayout* _form;
QDoubleSpinBox* _radius;
glm::vec3 _position;
};
/// Allows setting voxel colors by moving a sphere around.
class VoxelColorSphereTool : public SphereTool {
Q_OBJECT
public:
VoxelColorSphereTool(MetavoxelEditor* editor);
VoxelMaterialSpannerTool(MetavoxelEditor* editor);
virtual bool appliesTo(const AttributePointer& attribute) const;
protected:
virtual SharedObjectPointer getSpanner(bool detach = false);
virtual QColor getColor();
virtual void applyValue(const glm::vec3& position, float radius);
private:
QColorEditor* _color;
};
/// Allows setting voxel materials by moving a sphere around.
class VoxelMaterialSphereTool : public SphereTool {
Q_OBJECT
public:
VoxelMaterialSphereTool(MetavoxelEditor* editor);
virtual bool appliesTo(const AttributePointer& attribute) const;
protected:
virtual QColor getColor();
virtual void applyValue(const glm::vec3& position, float radius);
virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner);
private slots:
void clearColor();
void clearTexture();
void updateTexture();
void textureLoaded();
private:
SharedObjectEditor* _spannerEditor;
QColorEditor* _color;
SharedObjectEditor* _materialEditor;
QSharedPointer<NetworkTexture> _texture;
};

View file

@ -18,10 +18,16 @@
const glm::vec3 DEFAULT_POSITION = glm::vec3(0.0f, 0.0f, 0.0f);
const float DEFAULT_LINE_WIDTH = 1.0f;
const bool DEFAULT_IS_SOLID = false;
const bool DEFAULT_IS_DASHED_LINE = false;
Base3DOverlay::Base3DOverlay() :
_position(DEFAULT_POSITION),
_lineWidth(DEFAULT_LINE_WIDTH)
_lineWidth(DEFAULT_LINE_WIDTH),
_rotation(),
_isSolid(DEFAULT_IS_SOLID),
_isDashedLine(DEFAULT_IS_DASHED_LINE),
_ignoreRayIntersection(false)
{
}
@ -60,4 +66,94 @@ void Base3DOverlay::setProperties(const QScriptValue& properties) {
if (properties.property("lineWidth").isValid()) {
setLineWidth(properties.property("lineWidth").toVariant().toFloat());
}
QScriptValue rotation = properties.property("rotation");
if (rotation.isValid()) {
glm::quat newRotation;
// size, scale, dimensions is special, it might just be a single scalar, or it might be a vector, check that here
QScriptValue x = rotation.property("x");
QScriptValue y = rotation.property("y");
QScriptValue z = rotation.property("z");
QScriptValue w = rotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
setRotation(newRotation);
}
}
if (properties.property("isSolid").isValid()) {
setIsSolid(properties.property("isSolid").toVariant().toBool());
}
if (properties.property("isFilled").isValid()) {
setIsSolid(properties.property("isSolid").toVariant().toBool());
}
if (properties.property("isWire").isValid()) {
setIsSolid(!properties.property("isWire").toVariant().toBool());
}
if (properties.property("solid").isValid()) {
setIsSolid(properties.property("solid").toVariant().toBool());
}
if (properties.property("filled").isValid()) {
setIsSolid(properties.property("filled").toVariant().toBool());
}
if (properties.property("wire").isValid()) {
setIsSolid(!properties.property("wire").toVariant().toBool());
}
if (properties.property("isDashedLine").isValid()) {
setIsDashedLine(properties.property("isDashedLine").toVariant().toBool());
}
if (properties.property("dashed").isValid()) {
setIsDashedLine(properties.property("dashed").toVariant().toBool());
}
if (properties.property("ignoreRayIntersection").isValid()) {
setIgnoreRayIntersection(properties.property("ignoreRayIntersection").toVariant().toBool());
}
}
bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) const {
return false;
}
void Base3DOverlay::drawDashedLine(const glm::vec3& start, const glm::vec3& end) {
glBegin(GL_LINES);
// draw each line segment with appropriate gaps
const float DASH_LENGTH = 0.05f;
const float GAP_LENGTH = 0.025f;
const float SEGMENT_LENGTH = DASH_LENGTH + GAP_LENGTH;
float length = glm::distance(start, end);
float segmentCount = length / SEGMENT_LENGTH;
int segmentCountFloor = (int)glm::floor(segmentCount);
glm::vec3 segmentVector = (end - start) / segmentCount;
glm::vec3 dashVector = segmentVector / SEGMENT_LENGTH * DASH_LENGTH;
glm::vec3 gapVector = segmentVector / SEGMENT_LENGTH * GAP_LENGTH;
glm::vec3 point = start;
glVertex3f(point.x, point.y, point.z);
for (int i = 0; i < segmentCountFloor; i++) {
point += dashVector;
glVertex3f(point.x, point.y, point.z);
point += gapVector;
glVertex3f(point.x, point.y, point.z);
}
glVertex3f(end.x, end.y, end.z);
glEnd();
}

View file

@ -12,6 +12,9 @@
#define hifi_Base3DOverlay_h
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <BoxBase.h>
#include "Overlay.h"
@ -24,17 +27,35 @@ public:
// getters
const glm::vec3& getPosition() const { return _position; }
const glm::vec3& getCenter() const { return _position; } // TODO: consider implementing registration points in this class
float getLineWidth() const { return _lineWidth; }
bool getIsSolid() const { return _isSolid; }
bool getIsDashedLine() const { return _isDashedLine; }
bool getIsSolidLine() const { return !_isDashedLine; }
const glm::quat& getRotation() const { return _rotation; }
bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; }
// setters
void setPosition(const glm::vec3& position) { _position = position; }
void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
void setRotation(const glm::quat& value) { _rotation = value; }
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
virtual void setProperties(const QScriptValue& properties);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;
protected:
void drawDashedLine(const glm::vec3& start, const glm::vec3& end);
glm::vec3 _position;
float _lineWidth;
glm::quat _rotation;
bool _isSolid;
bool _isDashedLine;
bool _ignoreRayIntersection;
};

View file

@ -56,16 +56,16 @@ void BillboardOverlay::render() {
glPushMatrix(); {
glTranslatef(_position.x, _position.y, _position.z);
glm::quat rotation;
if (_isFacingAvatar) {
// rotate about vertical to face the camera
glm::quat rotation = Application::getInstance()->getCamera()->getRotation();
rotation = Application::getInstance()->getCamera()->getRotation();
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
} else {
glm::vec3 axis = glm::axis(_rotation);
glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
rotation = getRotation();
}
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glScalef(_scale, _scale, _scale);
if (_billboardTexture) {
@ -73,10 +73,14 @@ void BillboardOverlay::render() {
float x = _fromImage.width() / (2.0f * maxSize);
float y = -_fromImage.height() / (2.0f * maxSize);
glColor3f(1.0f, 1.0f, 1.0f);
const float MAX_COLOR = 255.0f;
xColor color = getColor();
float alpha = getAlpha();
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
glBegin(GL_QUADS); {
glTexCoord2f((float)_fromImage.x() / (float)_size.width(),
(float)_fromImage.y() / (float)_size.height());
glVertex2f(-x, -y);
glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(),
(float)_fromImage.y() / (float)_size.height());
@ -140,20 +144,6 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) {
_scale = scaleValue.toVariant().toFloat();
}
QScriptValue rotationValue = properties.property("rotation");
if (rotationValue.isValid()) {
QScriptValue x = rotationValue.property("x");
QScriptValue y = rotationValue.property("y");
QScriptValue z = rotationValue.property("z");
QScriptValue w = rotationValue.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
_rotation.x = x.toVariant().toFloat();
_rotation.y = y.toVariant().toFloat();
_rotation.z = z.toVariant().toFloat();
_rotation.w = w.toVariant().toFloat();
}
}
QScriptValue isFacingAvatarValue = properties.property("isFacingAvatar");
if (isFacingAvatarValue.isValid()) {
_isFacingAvatar = isFacingAvatarValue.toVariant().toBool();
@ -172,3 +162,20 @@ void BillboardOverlay::replyFinished() {
_billboard = reply->readAll();
_isLoaded = true;
}
bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) const {
if (_billboardTexture) {
float maxSize = glm::max(_fromImage.width(), _fromImage.height());
float x = _fromImage.width() / (2.0f * maxSize);
float y = -_fromImage.height() / (2.0f * maxSize);
float maxDimension = glm::max(x,y);
float scaledDimension = maxDimension * _scale;
glm::vec3 corner = getCenter() - glm::vec3(scaledDimension, scaledDimension, scaledDimension) ;
AACube myCube(corner, scaledDimension * 2.0f);
return myCube.findRayIntersection(origin, direction, distance, face);
}
return false;
}

View file

@ -26,6 +26,8 @@ public:
virtual void render();
virtual void setProperties(const QScriptValue& properties);
void setClipFromSource(const QRect& bounds) { _fromImage = bounds; }
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;
private slots:
void replyFinished();
@ -39,8 +41,7 @@ private:
QScopedPointer<Texture> _billboardTexture;
QRect _fromImage; // where from in the image to sample
glm::quat _rotation;
float _scale;
bool _isFacingAvatar;
};

View file

@ -0,0 +1,184 @@
//
// Circle3DOverlay.cpp
// interface/src/ui/overlays
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "Circle3DOverlay.h"
#include "renderer/GlowEffect.h"
Circle3DOverlay::Circle3DOverlay() :
_startAt(0.0f),
_endAt(360.0f),
_outerRadius(1.0f),
_innerRadius(0.0f)
{
}
Circle3DOverlay::~Circle3DOverlay() {
}
void Circle3DOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
const float FULL_CIRCLE = 360.0f;
const float SLICES = 180.0f; // The amount of segment to create the circle
const float SLICE_ANGLE = FULL_CIRCLE / SLICES;
//const int slices = 15;
float alpha = getAlpha();
xColor color = getColor();
const float MAX_COLOR = 255.0f;
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
glDisable(GL_LIGHTING);
glm::vec3 position = getPosition();
glm::vec3 center = getCenter();
glm::vec2 dimensions = getDimensions();
glm::quat rotation = getRotation();
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
glScalef(dimensions.x, dimensions.y, 1.0f);
// Create the circle in the coordinates origin
float outerRadius = getOuterRadius();
float innerRadius = getInnerRadius(); // only used in solid case
float startAt = getStartAt();
float endAt = getEndAt();
glLineWidth(_lineWidth);
// for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise
// we just draw a line...
if (getIsSolid()) {
glBegin(GL_QUAD_STRIP);
float angle = startAt;
float angleInRadians = glm::radians(angle);
glm::vec2 firstInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius);
glm::vec2 firstOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius);
glVertex2f(firstInnerPoint.x, firstInnerPoint.y);
glVertex2f(firstOuterPoint.x, firstOuterPoint.y);
while (angle < endAt) {
angleInRadians = glm::radians(angle);
glm::vec2 thisInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius);
glm::vec2 thisOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius);
glVertex2f(thisOuterPoint.x, thisOuterPoint.y);
glVertex2f(thisInnerPoint.x, thisInnerPoint.y);
angle += SLICE_ANGLE;
}
// get the last slice portion....
angle = endAt;
angleInRadians = glm::radians(angle);
glm::vec2 lastInnerPoint(cos(angleInRadians) * innerRadius, sin(angleInRadians) * innerRadius);
glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius);
glVertex2f(lastOuterPoint.x, lastOuterPoint.y);
glVertex2f(lastInnerPoint.x, lastInnerPoint.y);
glEnd();
} else {
if (getIsDashedLine()) {
glBegin(GL_LINES);
} else {
glBegin(GL_LINE_STRIP);
}
float angle = startAt;
float angleInRadians = glm::radians(angle);
glm::vec2 firstPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius);
glVertex2f(firstPoint.x, firstPoint.y);
while (angle < endAt) {
angle += SLICE_ANGLE;
angleInRadians = glm::radians(angle);
glm::vec2 thisPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius);
glVertex2f(thisPoint.x, thisPoint.y);
if (getIsDashedLine()) {
angle += SLICE_ANGLE / 2.0f; // short gap
angleInRadians = glm::radians(angle);
glm::vec2 dashStartPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius);
glVertex2f(dashStartPoint.x, dashStartPoint.y);
}
}
// get the last slice portion....
angle = endAt;
angleInRadians = glm::radians(angle);
glm::vec2 lastOuterPoint(cos(angleInRadians) * outerRadius, sin(angleInRadians) * outerRadius);
glVertex2f(lastOuterPoint.x, lastOuterPoint.y);
glEnd();
}
glPopMatrix();
glPopMatrix();
if (glower) {
delete glower;
}
}
void Circle3DOverlay::setProperties(const QScriptValue &properties) {
Planar3DOverlay::setProperties(properties);
QScriptValue startAt = properties.property("startAt");
if (startAt.isValid()) {
setStartAt(startAt.toVariant().toFloat());
}
QScriptValue endAt = properties.property("endAt");
if (endAt.isValid()) {
setEndAt(endAt.toVariant().toFloat());
}
QScriptValue outerRadius = properties.property("outerRadius");
if (outerRadius.isValid()) {
setOuterRadius(outerRadius.toVariant().toFloat());
}
QScriptValue innerRadius = properties.property("innerRadius");
if (innerRadius.isValid()) {
setInnerRadius(innerRadius.toVariant().toFloat());
}
}

View file

@ -0,0 +1,43 @@
//
// Circle3DOverlay.h
// interface/src/ui/overlays
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Circle3DOverlay_h
#define hifi_Circle3DOverlay_h
#include "Planar3DOverlay.h"
class Circle3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
Circle3DOverlay();
~Circle3DOverlay();
virtual void render();
virtual void setProperties(const QScriptValue& properties);
float getStartAt() const { return _startAt; }
float getEndAt() const { return _endAt; }
float getOuterRadius() const { return _outerRadius; }
float getInnerRadius() const { return _innerRadius; }
void setStartAt(float value) { _startAt = value; }
void setEndAt(float value) { _endAt = value; }
void setOuterRadius(float value) { _outerRadius = value; }
void setInnerRadius(float value) { _innerRadius = value; }
protected:
float _startAt;
float _endAt;
float _outerRadius;
float _innerRadius;
};
#endif // hifi_Circle3DOverlay_h

View file

@ -13,8 +13,11 @@
#include <QGLWidget>
#include <SharedUtil.h>
#include <StreamUtils.h>
#include "Application.h"
#include "Cube3DOverlay.h"
#include "renderer/GlowEffect.h"
Cube3DOverlay::Cube3DOverlay() {
}
@ -27,22 +30,75 @@ void Cube3DOverlay::render() {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
glDisable(GL_LIGHTING);
glPushMatrix();
glTranslatef(_position.x + _size * 0.5f,
_position.y + _size * 0.5f,
_position.z + _size * 0.5f);
glLineWidth(_lineWidth);
if (_isSolid) {
glutSolidCube(_size);
} else {
glLineWidth(_lineWidth);
glutWireCube(_size);
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
float alpha = getAlpha();
xColor color = getColor();
const float MAX_COLOR = 255.0f;
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
//glDisable(GL_LIGHTING);
// TODO: handle registration point??
glm::vec3 position = getPosition();
glm::vec3 center = getCenter();
glm::vec3 dimensions = getDimensions();
glm::quat rotation = getRotation();
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
if (_isSolid) {
glScalef(dimensions.x, dimensions.y, dimensions.z);
Application::getInstance()->getDeferredLightingEffect()->renderSolidCube(1.0f);
} else {
glLineWidth(_lineWidth);
if (getIsDashedLine()) {
glm::vec3 halfDimensions = dimensions / 2.0f;
glm::vec3 bottomLeftNear(-halfDimensions.x, -halfDimensions.y, -halfDimensions.z);
glm::vec3 bottomRightNear(halfDimensions.x, -halfDimensions.y, -halfDimensions.z);
glm::vec3 topLeftNear(-halfDimensions.x, halfDimensions.y, -halfDimensions.z);
glm::vec3 topRightNear(halfDimensions.x, halfDimensions.y, -halfDimensions.z);
glm::vec3 bottomLeftFar(-halfDimensions.x, -halfDimensions.y, halfDimensions.z);
glm::vec3 bottomRightFar(halfDimensions.x, -halfDimensions.y, halfDimensions.z);
glm::vec3 topLeftFar(-halfDimensions.x, halfDimensions.y, halfDimensions.z);
glm::vec3 topRightFar(halfDimensions.x, halfDimensions.y, halfDimensions.z);
drawDashedLine(bottomLeftNear, bottomRightNear);
drawDashedLine(bottomRightNear, bottomRightFar);
drawDashedLine(bottomRightFar, bottomLeftFar);
drawDashedLine(bottomLeftFar, bottomLeftNear);
drawDashedLine(topLeftNear, topRightNear);
drawDashedLine(topRightNear, topRightFar);
drawDashedLine(topRightFar, topLeftFar);
drawDashedLine(topLeftFar, topLeftNear);
drawDashedLine(bottomLeftNear, topLeftNear);
drawDashedLine(bottomRightNear, topRightNear);
drawDashedLine(bottomLeftFar, topLeftFar);
drawDashedLine(bottomRightFar, topRightFar);
} else {
glScalef(dimensions.x, dimensions.y, dimensions.z);
Application::getInstance()->getDeferredLightingEffect()->renderWireCube(1.0f);
}
}
glPopMatrix();
glPopMatrix();
if (glower) {
delete glower;
}
}

View file

@ -65,8 +65,10 @@ void ImageOverlay::render() {
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, _textureID);
}
const float MAX_COLOR = 255;
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
const float MAX_COLOR = 255.0f;
xColor color = getColor();
float alpha = getAlpha();
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
float imageWidth = _textureImage.width();
float imageHeight = _textureImage.height();
@ -106,6 +108,7 @@ void ImageOverlay::render() {
}
glVertex2f(_bounds.left(), _bounds.bottom());
glEnd();
if (_renderImage) {
glDisable(GL_TEXTURE_2D);
}

View file

@ -12,6 +12,7 @@
#include "InterfaceConfig.h"
#include "Line3DOverlay.h"
#include "renderer/GlowEffect.h"
Line3DOverlay::Line3DOverlay() {
@ -25,16 +26,33 @@ void Line3DOverlay::render() {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
glDisable(GL_LIGHTING);
glLineWidth(_lineWidth);
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
glBegin(GL_LINES);
float alpha = getAlpha();
xColor color = getColor();
const float MAX_COLOR = 255.0f;
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
if (getIsDashedLine()) {
drawDashedLine(_position, _end);
} else {
glBegin(GL_LINES);
glVertex3f(_position.x, _position.y, _position.z);
glVertex3f(_end.x, _end.y, _end.z);
glEnd();
glEnd();
}
glEnable(GL_LIGHTING);
if (glower) {
delete glower;
}
}
void Line3DOverlay::setProperties(const QScriptValue& properties) {

View file

@ -27,6 +27,13 @@ void LocalModelsOverlay::update(float deltatime) {
void LocalModelsOverlay::render() {
if (_visible) {
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
glPushMatrix(); {
Application* app = Application::getInstance();
glm::vec3 oldTranslation = app->getViewMatrixTranslation();
@ -34,5 +41,10 @@ void LocalModelsOverlay::render() {
_entityTreeRenderer->render();
Application::getInstance()->setViewMatrixTranslation(oldTranslation);
} glPopMatrix();
if (glower) {
delete glower;
}
}
}

View file

@ -52,12 +52,25 @@ void LocalVoxelsOverlay::update(float deltatime) {
}
void LocalVoxelsOverlay::render() {
if (_visible && _size > 0 && _voxelSystem && _voxelSystem->isInitialized()) {
glm::vec3 dimensions = getDimensions();
float size = glm::length(dimensions);
if (_visible && size > 0 && _voxelSystem && _voxelSystem->isInitialized()) {
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
glPushMatrix(); {
glTranslatef(_position.x, _position.y, _position.z);
glScalef(_size, _size, _size);
glScalef(dimensions.x, dimensions.y, dimensions.z);
_voxelSystem->render();
} glPopMatrix();
if (glower) {
delete glower;
}
}
}

View file

@ -43,45 +43,16 @@ void ModelOverlay::render() {
}
if (_model.isActive()) {
if (_model.isRenderable()) {
_model.render(_alpha);
}
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
if (displayModelBounds) {
glm::vec3 unRotatedMinimum = _model.getUnscaledMeshExtents().minimum;
glm::vec3 unRotatedMaximum = _model.getUnscaledMeshExtents().maximum;
glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum;
float width = unRotatedExtents.x;
float height = unRotatedExtents.y;
float depth = unRotatedExtents.z;
Extents rotatedExtents = _model.getUnscaledMeshExtents();
rotatedExtents.rotate(_rotation);
glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;
const glm::vec3& modelScale = _model.getScale();
glPushMatrix(); {
glTranslatef(_position.x, _position.y, _position.z);
// draw the rotated bounding cube
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix(); {
glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z);
glutWireCube(1.0);
} glPopMatrix();
// draw the model relative bounding box
glm::vec3 axis = glm::axis(_rotation);
glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
glColor3f(0.0f, 1.0f, 0.0f);
glutWireCube(1.0);
} glPopMatrix();
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
_model.render(getAlpha());
if (glower) {
delete glower;
}
}
}
}

View file

@ -23,6 +23,16 @@ Overlay::Overlay() :
_parent(NULL),
_isLoaded(true),
_alpha(DEFAULT_ALPHA),
_glowLevel(0.0f),
_pulse(0.0f),
_pulseMax(0.0f),
_pulseMin(0.0f),
_pulsePeriod(1.0f),
_pulseDirection(1.0f),
_lastPulseUpdate(usecTimestampNow()),
_glowLevelPulse(0.0f),
_alphaPulse(0.0f),
_colorPulse(0.0f),
_color(DEFAULT_OVERLAY_COLOR),
_visible(true),
_anchor(NO_ANCHOR)
@ -54,6 +64,34 @@ void Overlay::setProperties(const QScriptValue& properties) {
setAlpha(properties.property("alpha").toVariant().toFloat());
}
if (properties.property("glowLevel").isValid()) {
setGlowLevel(properties.property("glowLevel").toVariant().toFloat());
}
if (properties.property("pulseMax").isValid()) {
setPulseMax(properties.property("pulseMax").toVariant().toFloat());
}
if (properties.property("pulseMin").isValid()) {
setPulseMin(properties.property("pulseMin").toVariant().toFloat());
}
if (properties.property("pulsePeriod").isValid()) {
setPulsePeriod(properties.property("pulsePeriod").toVariant().toFloat());
}
if (properties.property("glowLevelPulse").isValid()) {
setGlowLevelPulse(properties.property("glowLevelPulse").toVariant().toFloat());
}
if (properties.property("alphaPulse").isValid()) {
setAlphaPulse(properties.property("alphaPulse").toVariant().toFloat());
}
if (properties.property("colorPulse").isValid()) {
setColorPulse(properties.property("colorPulse").toVariant().toFloat());
}
if (properties.property("visible").isValid()) {
setVisible(properties.property("visible").toVariant().toBool());
}
@ -65,3 +103,73 @@ void Overlay::setProperties(const QScriptValue& properties) {
}
}
}
xColor Overlay::getColor() {
if (_colorPulse == 0.0f) {
return _color;
}
float pulseLevel = updatePulse();
xColor result = _color;
if (_colorPulse < 0.0f) {
result.red *= (1.0f - pulseLevel);
result.green *= (1.0f - pulseLevel);
result.blue *= (1.0f - pulseLevel);
} else {
result.red *= pulseLevel;
result.green *= pulseLevel;
result.blue *= pulseLevel;
}
return result;
}
float Overlay::getAlpha() {
if (_alphaPulse == 0.0f) {
return _alpha;
}
float pulseLevel = updatePulse();
return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel);
}
float Overlay::getGlowLevel() {
if (_glowLevelPulse == 0.0f) {
return _glowLevel;
}
float pulseLevel = updatePulse();
return (_glowLevelPulse >= 0.0f) ? _glowLevel * pulseLevel : _glowLevel * (1.0f - pulseLevel);
}
// glow level travels from min to max, then max to min in one period.
float Overlay::updatePulse() {
if (_pulsePeriod <= 0.0f) {
return _pulse;
}
quint64 now = usecTimestampNow();
quint64 elapsedUSecs = (now - _lastPulseUpdate);
float elapsedSeconds = (float)elapsedUSecs / (float)USECS_PER_SECOND;
float elapsedPeriods = elapsedSeconds / _pulsePeriod;
// we can safely remove any "full" periods, since those just rotate us back
// to our final glow level
elapsedPeriods = fmod(elapsedPeriods, 1.0f);
_lastPulseUpdate = now;
float glowDistance = (_pulseMax - _pulseMin);
float glowDistancePerPeriod = glowDistance * 2.0f;
float glowDelta = _pulseDirection * glowDistancePerPeriod * elapsedPeriods;
float newGlow = _pulse + glowDelta;
float limit = (_pulseDirection > 0.0f) ? _pulseMax : _pulseMin;
float passedLimit = (_pulseDirection > 0.0f) ? (newGlow >= limit) : (newGlow <= limit);
if (passedLimit) {
float glowDeltaToLimit = newGlow - limit;
float glowDeltaFromLimitBack = glowDelta - glowDeltaToLimit;
glowDelta = -glowDeltaFromLimitBack;
_pulseDirection *= -1.0f;
}
_pulse += glowDelta;
return _pulse;
}

View file

@ -42,22 +42,59 @@ public:
// getters
bool isLoaded() { return _isLoaded; }
bool getVisible() const { return _visible; }
const xColor& getColor() const { return _color; }
float getAlpha() const { return _alpha; }
xColor getColor();
float getAlpha();
float getGlowLevel();
Anchor getAnchor() const { return _anchor; }
float getPulseMax() const { return _pulseMax; }
float getPulseMin() const { return _pulseMin; }
float getPulsePeriod() const { return _pulsePeriod; }
float getPulseDirection() const { return _pulseDirection; }
float getGlowLevelPulse() const { return _glowLevelPulse; }
float getColorPulse() const { return _colorPulse; }
float getAlphaPulse() const { return _alphaPulse; }
// setters
void setVisible(bool visible) { _visible = visible; }
void setColor(const xColor& color) { _color = color; }
void setAlpha(float alpha) { _alpha = alpha; }
void setGlowLevel(float value) { _glowLevel = value; }
void setAnchor(Anchor anchor) { _anchor = anchor; }
void setPulseMax(float value) { _pulseMax = value; }
void setPulseMin(float value) { _pulseMin = value; }
void setPulsePeriod(float value) { _pulsePeriod = value; }
void setPulseDirection(float value) { _pulseDirection = value; }
void setGlowLevelPulse(float value) { _glowLevelPulse = value; }
void setColorPulse(float value) { _colorPulse = value; }
void setAlphaPulse(float value) { _alphaPulse = value; }
virtual void setProperties(const QScriptValue& properties);
protected:
float updatePulse();
QGLWidget* _parent;
bool _isLoaded;
float _alpha;
float _glowLevel;
float _pulse;
float _pulseMax;
float _pulseMin;
float _pulsePeriod;
float _pulseDirection;
quint64 _lastPulseUpdate;
float _glowLevelPulse; // ratio of the pulse to the glow level
float _alphaPulse; // ratio of the pulse to the alpha
float _colorPulse; // ratio of the pulse to the color
xColor _color;
bool _visible; // should the overlay be drawn at all
Anchor _anchor;

View file

@ -8,9 +8,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <limits>
#include <Application.h>
#include "BillboardOverlay.h"
#include "Circle3DOverlay.h"
#include "Cube3DOverlay.h"
#include "ImageOverlay.h"
#include "Line3DOverlay.h"
@ -18,6 +20,7 @@
#include "LocalVoxelsOverlay.h"
#include "ModelOverlay.h"
#include "Overlays.h"
#include "Rectangle3DOverlay.h"
#include "Sphere3DOverlay.h"
#include "TextOverlay.h"
@ -147,6 +150,18 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "circle3d") {
thisOverlay = new Circle3DOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "rectangle3d") {
thisOverlay = new Rectangle3DOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "line3d") {
thisOverlay = new Line3DOverlay();
thisOverlay->init(_parent);
@ -241,6 +256,107 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) {
return 0; // not found
}
RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray) {
float bestDistance = std::numeric_limits<float>::max();
RayToOverlayIntersectionResult result;
QMapIterator<unsigned int, Overlay*> i(_overlays3D);
i.toBack();
while (i.hasPrevious()) {
i.previous();
unsigned int thisID = i.key();
Base3DOverlay* thisOverlay = static_cast<Base3DOverlay*>(i.value());
if (thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) {
float thisDistance;
BoxFace thisFace;
if (thisOverlay->findRayIntersection(ray.origin, ray.direction, thisDistance, thisFace)) {
if (thisDistance < bestDistance) {
bestDistance = thisDistance;
result.intersects = true;
result.distance = thisDistance;
result.face = thisFace;
result.overlayID = thisID;
result.intersection = ray.origin + (ray.direction * thisDistance);
}
}
}
}
return result;
}
RayToOverlayIntersectionResult::RayToOverlayIntersectionResult() :
intersects(false),
overlayID(-1),
distance(0),
face(),
intersection()
{
}
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
obj.setProperty("overlayID", value.overlayID);
obj.setProperty("distance", value.distance);
QString faceName = "";
// handle BoxFace
switch (value.face) {
case MIN_X_FACE:
faceName = "MIN_X_FACE";
break;
case MAX_X_FACE:
faceName = "MAX_X_FACE";
break;
case MIN_Y_FACE:
faceName = "MIN_Y_FACE";
break;
case MAX_Y_FACE:
faceName = "MAX_Y_FACE";
break;
case MIN_Z_FACE:
faceName = "MIN_Z_FACE";
break;
case MAX_Z_FACE:
faceName = "MAX_Z_FACE";
break;
default:
case UNKNOWN_FACE:
faceName = "UNKNOWN_FACE";
break;
}
obj.setProperty("face", faceName);
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
return obj;
}
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) {
value.intersects = object.property("intersects").toVariant().toBool();
value.overlayID = object.property("overlayID").toVariant().toInt();
value.distance = object.property("distance").toVariant().toFloat();
QString faceName = object.property("face").toVariant().toString();
if (faceName == "MIN_X_FACE") {
value.face = MIN_X_FACE;
} else if (faceName == "MAX_X_FACE") {
value.face = MAX_X_FACE;
} else if (faceName == "MIN_Y_FACE") {
value.face = MIN_Y_FACE;
} else if (faceName == "MAX_Y_FACE") {
value.face = MAX_Y_FACE;
} else if (faceName == "MIN_Z_FACE") {
value.face = MIN_Z_FACE;
} else if (faceName == "MAX_Z_FACE") {
value.face = MAX_Z_FACE;
} else {
value.face = UNKNOWN_FACE;
};
QScriptValue intersection = object.property("intersection");
if (intersection.isValid()) {
vec3FromScriptValue(intersection, value.intersection);
}
}
bool Overlays::isLoaded(unsigned int id) {
QReadLocker lock(&_lock);
Overlay* overlay = _overlays2D.value(id);

View file

@ -15,6 +15,21 @@
#include "Overlay.h"
class RayToOverlayIntersectionResult {
public:
RayToOverlayIntersectionResult();
bool intersects;
int overlayID;
float distance;
BoxFace face;
glm::vec3 intersection;
};
Q_DECLARE_METATYPE(RayToOverlayIntersectionResult);
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value);
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value);
class Overlays : public QObject {
Q_OBJECT
public:
@ -36,8 +51,11 @@ public slots:
/// deletes a particle
void deleteOverlay(unsigned int id);
/// returns the top most overlay at the screen point, or 0 if not overlay at that point
/// returns the top most 2D overlay at the screen point, or 0 if not overlay at that point
unsigned int getOverlayAtPoint(const glm::vec2& point);
/// returns details about the closest 3D Overlay hit by the pick ray
RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray);
/// returns whether the overlay's assets are loaded or not
bool isLoaded(unsigned int id);
@ -52,5 +70,6 @@ private:
QReadWriteLock _deleteLock;
};
#endif // hifi_Overlays_h

View file

@ -0,0 +1,76 @@
//
// Planar3DOverlay.cpp
// interface/src/ui/overlays
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include <StreamUtils.h>
#include "Planar3DOverlay.h"
const float DEFAULT_SIZE = 1.0f;
Planar3DOverlay::Planar3DOverlay() :
_dimensions(glm::vec2(DEFAULT_SIZE, DEFAULT_SIZE))
{
}
Planar3DOverlay::~Planar3DOverlay() {
}
void Planar3DOverlay::setProperties(const QScriptValue& properties) {
Base3DOverlay::setProperties(properties);
QScriptValue dimensions = properties.property("dimensions");
// if "dimensions" property was not there, check to see if they included aliases: scale
if (!dimensions.isValid()) {
dimensions = properties.property("scale");
if (!dimensions.isValid()) {
dimensions = properties.property("size");
}
}
if (dimensions.isValid()) {
bool validDimensions = false;
glm::vec2 newDimensions;
QScriptValue x = dimensions.property("x");
QScriptValue y = dimensions.property("y");
if (x.isValid() && y.isValid()) {
newDimensions.x = x.toVariant().toFloat();
newDimensions.y = y.toVariant().toFloat();
validDimensions = true;
} else {
QScriptValue width = dimensions.property("width");
QScriptValue height = dimensions.property("height");
if (width.isValid() && height.isValid()) {
newDimensions.x = width.toVariant().toFloat();
newDimensions.y = height.toVariant().toFloat();
validDimensions = true;
}
}
// size, scale, dimensions is special, it might just be a single scalar, check that here
if (!validDimensions && dimensions.isNumber()) {
float size = dimensions.toVariant().toFloat();
newDimensions.x = size;
newDimensions.y = size;
validDimensions = true;
}
if (validDimensions) {
setDimensions(newDimensions);
}
}
}

View file

@ -0,0 +1,45 @@
//
// Planar3DOverlay.h
// interface/src/ui/overlays
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Planar3DOverlay_h
#define hifi_Planar3DOverlay_h
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <glm/glm.hpp>
#include <QGLWidget>
#include <QScriptValue>
#include "Base3DOverlay.h"
class Planar3DOverlay : public Base3DOverlay {
Q_OBJECT
public:
Planar3DOverlay();
~Planar3DOverlay();
// getters
const glm::vec2& getDimensions() const { return _dimensions; }
// setters
void setSize(float size) { _dimensions = glm::vec2(size, size); }
void setDimensions(const glm::vec2& value) { _dimensions = value; }
virtual void setProperties(const QScriptValue& properties);
protected:
glm::vec2 _dimensions;
};
#endif // hifi_Planar3DOverlay_h

View file

@ -0,0 +1,114 @@
//
// Rectangle3DOverlay.cpp
// interface/src/ui/overlays
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <SharedUtil.h>
#include "Rectangle3DOverlay.h"
#include "renderer/GlowEffect.h"
Rectangle3DOverlay::Rectangle3DOverlay() {
}
Rectangle3DOverlay::~Rectangle3DOverlay() {
}
void Rectangle3DOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
float alpha = getAlpha();
xColor color = getColor();
const float MAX_COLOR = 255.0f;
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
glDisable(GL_LIGHTING);
glm::vec3 position = getPosition();
glm::vec3 center = getCenter();
glm::vec2 dimensions = getDimensions();
glm::vec2 halfDimensions = dimensions * 0.5f;
glm::quat rotation = getRotation();
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
//glScalef(dimensions.x, dimensions.y, 1.0f);
glLineWidth(_lineWidth);
// for our overlay, is solid means we draw a solid "filled" rectangle otherwise we just draw a border line...
if (getIsSolid()) {
glBegin(GL_QUADS);
glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y);
glVertex3f(halfDimensions.x, 0.0f, -halfDimensions.y);
glVertex3f(halfDimensions.x, 0.0f, halfDimensions.y);
glVertex3f(-halfDimensions.x, 0.0f, halfDimensions.y);
glEnd();
} else {
if (getIsDashedLine()) {
glm::vec3 point1(-halfDimensions.x, 0.0f, -halfDimensions.y);
glm::vec3 point2(halfDimensions.x, 0.0f, -halfDimensions.y);
glm::vec3 point3(halfDimensions.x, 0.0f, halfDimensions.y);
glm::vec3 point4(-halfDimensions.x, 0.0f, halfDimensions.y);
drawDashedLine(point1, point2);
drawDashedLine(point2, point3);
drawDashedLine(point3, point4);
drawDashedLine(point4, point1);
} else {
glBegin(GL_LINE_STRIP);
glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y);
glVertex3f(halfDimensions.x, 0.0f, -halfDimensions.y);
glVertex3f(halfDimensions.x, 0.0f, halfDimensions.y);
glVertex3f(-halfDimensions.x, 0.0f, halfDimensions.y);
glVertex3f(-halfDimensions.x, 0.0f, -halfDimensions.y);
glEnd();
}
}
glPopMatrix();
glPopMatrix();
if (glower) {
delete glower;
}
}
void Rectangle3DOverlay::setProperties(const QScriptValue &properties) {
Planar3DOverlay::setProperties(properties);
}

View file

@ -0,0 +1,27 @@
//
// Rectangle3DOverlay.h
// interface/src/ui/overlays
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Rectangle3DOverlay_h
#define hifi_Rectangle3DOverlay_h
#include "Planar3DOverlay.h"
class Rectangle3DOverlay : public Planar3DOverlay {
Q_OBJECT
public:
Rectangle3DOverlay();
~Rectangle3DOverlay();
virtual void render();
virtual void setProperties(const QScriptValue& properties);
};
#endif // hifi_Rectangle3DOverlay_h

View file

@ -16,6 +16,7 @@
#include "Sphere3DOverlay.h"
#include "Application.h"
#include "renderer/GlowEffect.h"
Sphere3DOverlay::Sphere3DOverlay() {
}
@ -28,22 +29,44 @@ void Sphere3DOverlay::render() {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glColor4f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, _alpha);
const int SLICES = 15;
float alpha = getAlpha();
xColor color = getColor();
const float MAX_COLOR = 255.0f;
glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
glDisable(GL_LIGHTING);
glPushMatrix();
glTranslatef(_position.x,
_position.y,
_position.z);
glLineWidth(_lineWidth);
const int slices = 15;
if (_isSolid) {
Application::getInstance()->getGeometryCache()->renderSphere(_size, slices, slices);
} else {
glutWireSphere(_size, slices, slices);
glm::vec3 position = getPosition();
glm::vec3 center = getCenter();
glm::vec3 dimensions = getDimensions();
glm::quat rotation = getRotation();
float glowLevel = getGlowLevel();
Glower* glower = NULL;
if (glowLevel > 0.0f) {
glower = new Glower(glowLevel);
}
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
glScalef(dimensions.x, dimensions.y, dimensions.z);
//Application::getInstance()->getDeferredLightingEffect()->renderSolidCube(1.0f);
if (_isSolid) {
Application::getInstance()->getGeometryCache()->renderSphere(1.0f, SLICES, SLICES);
} else {
glutWireSphere(1.0f, SLICES, SLICES);
}
glPopMatrix();
glPopMatrix();
if (glower) {
delete glower;
}
}

View file

@ -28,13 +28,36 @@ TextOverlay::TextOverlay() :
TextOverlay::~TextOverlay() {
}
xColor TextOverlay::getBackgroundColor() {
if (_colorPulse == 0.0f) {
return _backgroundColor;
}
float pulseLevel = updatePulse();
xColor result = _backgroundColor;
if (_colorPulse < 0.0f) {
result.red *= (1.0f - pulseLevel);
result.green *= (1.0f - pulseLevel);
result.blue *= (1.0f - pulseLevel);
} else {
result.red *= pulseLevel;
result.green *= pulseLevel;
result.blue *= pulseLevel;
}
return result;
}
void TextOverlay::render() {
if (!_visible) {
return; // do nothing if we're not visible
}
const float MAX_COLOR = 255;
glColor4f(_backgroundColor.red / MAX_COLOR, _backgroundColor.green / MAX_COLOR, _backgroundColor.blue / MAX_COLOR, _alpha);
const float MAX_COLOR = 255.0f;
xColor backgroundColor = getBackgroundColor();
float alpha = getAlpha();
glColor4f(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR, alpha);
glBegin(GL_QUADS);
glVertex2f(_bounds.left(), _bounds.top());
@ -64,7 +87,6 @@ void TextOverlay::render() {
const int lineGap = 2;
lineOffset += lineGap;
}
}
void TextOverlay::setProperties(const QScriptValue& properties) {

View file

@ -43,6 +43,7 @@ public:
const QString& getText() const { return _text; }
int getLeftMargin() const { return _leftMargin; }
int getTopMargin() const { return _topMargin; }
xColor getBackgroundColor();
// setters
void setText(const QString& text) { _text = text; }

View file

@ -12,16 +12,16 @@
#include "InterfaceConfig.h"
#include <QGLWidget>
#include <AABox.h>
#include <SharedUtil.h>
#include <StreamUtils.h>
#include "Volume3DOverlay.h"
const float DEFAULT_SIZE = 1.0f;
const bool DEFAULT_IS_SOLID = false;
Volume3DOverlay::Volume3DOverlay() :
_size(DEFAULT_SIZE),
_isSolid(DEFAULT_IS_SOLID)
_dimensions(glm::vec3(DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE))
{
}
@ -31,20 +31,62 @@ Volume3DOverlay::~Volume3DOverlay() {
void Volume3DOverlay::setProperties(const QScriptValue& properties) {
Base3DOverlay::setProperties(properties);
if (properties.property("size").isValid()) {
setSize(properties.property("size").toVariant().toFloat());
QScriptValue dimensions = properties.property("dimensions");
// if "dimensions" property was not there, check to see if they included aliases: scale
if (!dimensions.isValid()) {
dimensions = properties.property("scale");
if (!dimensions.isValid()) {
dimensions = properties.property("size");
}
}
if (properties.property("isSolid").isValid()) {
setIsSolid(properties.property("isSolid").toVariant().toBool());
}
if (properties.property("isWire").isValid()) {
setIsSolid(!properties.property("isWire").toVariant().toBool());
}
if (properties.property("solid").isValid()) {
setIsSolid(properties.property("solid").toVariant().toBool());
}
if (properties.property("wire").isValid()) {
setIsSolid(!properties.property("wire").toVariant().toBool());
if (dimensions.isValid()) {
bool validDimensions = false;
glm::vec3 newDimensions;
QScriptValue x = dimensions.property("x");
QScriptValue y = dimensions.property("y");
QScriptValue z = dimensions.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
newDimensions.x = x.toVariant().toFloat();
newDimensions.y = y.toVariant().toFloat();
newDimensions.z = z.toVariant().toFloat();
validDimensions = true;
} else {
QScriptValue width = dimensions.property("width");
QScriptValue height = dimensions.property("height");
QScriptValue depth = dimensions.property("depth");
if (width.isValid() && height.isValid() && depth.isValid()) {
newDimensions.x = width.toVariant().toFloat();
newDimensions.y = height.toVariant().toFloat();
newDimensions.z = depth.toVariant().toFloat();
validDimensions = true;
}
}
// size, scale, dimensions is special, it might just be a single scalar, check that here
if (!validDimensions && dimensions.isNumber()) {
float size = dimensions.toVariant().toFloat();
newDimensions.x = size;
newDimensions.y = size;
newDimensions.z = size;
validDimensions = true;
}
if (validDimensions) {
setDimensions(newDimensions);
}
}
}
bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face) const {
// TODO: this is not exactly accurate because it doesn't properly handle rotation... but it's close enough for our
// current use cases. We do need to improve it to be more accurate
AABox myBox(getCorner(), _dimensions);
return myBox.findRayIntersection(origin, direction, distance, face);
}

View file

@ -14,6 +14,8 @@
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <glm/glm.hpp>
#include <QGLWidget>
#include <QScriptValue>
@ -27,18 +29,20 @@ public:
~Volume3DOverlay();
// getters
float getSize() const { return _size; }
bool getIsSolid() const { return _isSolid; }
const glm::vec3& getCenter() const { return _position; } // TODO: consider adding registration point!!
glm::vec3 getCorner() const { return _position - (_dimensions * 0.5f); } // TODO: consider adding registration point!!
const glm::vec3& getDimensions() const { return _dimensions; }
// setters
void setSize(float size) { _size = size; }
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
void setSize(float size) { _dimensions = glm::vec3(size, size, size); }
void setDimensions(const glm::vec3& value) { _dimensions = value; }
virtual void setProperties(const QScriptValue& properties);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const;
protected:
float _size;
bool _isSolid;
glm::vec3 _dimensions;
};

View file

@ -1 +0,0 @@
console.log('This would be the main JS file.');

View file

@ -1,17 +0,0 @@
var metas = document.getElementsByTagName('meta');
var i;
if (navigator.userAgent.match(/iPhone/i)) {
for (i=0; i<metas.length; i++) {
if (metas[i].name == "viewport") {
metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
}
}
document.addEventListener("gesturestart", gestureStart, false);
}
function gestureStart() {
for (i=0; i<metas.length; i++) {
if (metas[i].name == "viewport") {
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
}
}
}

View file

@ -1,190 +0,0 @@
JENKINS_URL = 'https://jenkins.below92.com/'
GITHUB_HOOK_URL = 'https://github.com/worklist/hifi/'
GIT_REPO_URL = 'git@github.com:worklist/hifi.git'
HIPCHAT_ROOM = 'High Fidelity'
def hifiJob(String targetName, Boolean deploy) {
job {
name "hifi-${targetName}"
logRotator(7, -1, -1, -1)
scm {
git(GIT_REPO_URL, 'master') { node ->
node << {
includedRegions "${targetName}/.*\nlibraries/.*"
useShallowClone true
}
}
}
configure { project ->
project / 'properties' << {
'com.coravy.hudson.plugins.github.GithubProjectProperty' {
projectUrl GITHUB_HOOK_URL
}
'jenkins.plugins.hipchat.HipChatNotifier_-HipChatJobProperty' {
room HIPCHAT_ROOM
}
'hudson.plugins.buildblocker.BuildBlockerProperty' {
useBuildBlocker true
blockingJobs 'hifi--seed'
}
}
project / 'triggers' << 'com.cloudbees.jenkins.GitHubPushTrigger' {
spec ''
}
}
configure cmakeBuild(targetName, 'make install')
if (deploy) {
publishers {
publishScp("${ARTIFACT_DESTINATION}") {
entry("**/build/${targetName}/${targetName}", "deploy/${targetName}")
}
}
}
configure { project ->
project / 'publishers' << {
if (deploy) {
'hudson.plugins.postbuildtask.PostbuildTask' {
'tasks' {
'hudson.plugins.postbuildtask.TaskProperties' {
logTexts {
'hudson.plugins.postbuildtask.LogProperties' {
logText '.'
operator 'AND'
}
}
EscalateStatus true
RunIfJobSuccessful true
script "curl -d 'action=deploy&role=highfidelity-live&revision=${targetName}' https://${ARTIFACT_DESTINATION}"
}
}
}
}
'jenkins.plugins.hipchat.HipChatNotifier' {
jenkinsUrl JENKINS_URL
authToken "${HIPCHAT_AUTH_TOKEN}"
room HIPCHAT_ROOM
}
}
}
}
}
static Closure cmakeBuild(srcDir, instCommand) {
return { project ->
project / 'builders' / 'hudson.plugins.cmake.CmakeBuilder' {
sourceDir '.'
buildDir 'build'
installDir ''
buildType 'RelWithDebInfo'
generator 'Unix Makefiles'
makeCommand "make ${srcDir}"
installCommand instCommand
preloadScript ''
cmakeArgs ''
projectCmakePath '/usr/local/bin/cmake'
cleanBuild 'false'
cleanInstallDir 'false'
builderImpl ''
}
}
}
def targets = [
'animation-server':true,
'assignment-server':true,
'assignment-client':true,
'domain-server':true,
'eve':true,
'pairing-server':true,
'space-server':true,
'voxel-server':true,
]
/* setup all of the target jobs to use the above template */
for (target in targets) {
queue hifiJob(target.key, target.value)
}
/* setup the OS X interface builds */
interfaceOSXJob = hifiJob('interface', false)
interfaceOSXJob.with {
name 'hifi-interface-osx'
scm {
git(GIT_REPO_URL, 'stable') { node ->
node << {
includedRegions "interface/.*\nlibraries/.*"
useShallowClone true
}
}
}
configure { project ->
project << {
assignedNode 'interface-mini'
canRoam false
}
}
}
queue interfaceOSXJob
/* setup the parametrized build job for builds from jenkins */
parameterizedJob = hifiJob('$TARGET', true)
parameterizedJob.with {
name 'hifi-branch-deploy'
parameters {
stringParam('GITHUB_USER', '', "Specifies the name of the GitHub user that we're building from.")
stringParam('GIT_BRANCH', '', "Specifies the specific branch to build and deploy.")
stringParam('HOSTNAME', 'devel.highfidelity.io', "Specifies the hostname to deploy against.")
stringParam('TARGET', '', "What server to build specifically")
}
scm {
git('git@github.com:/$GITHUB_USER/hifi.git', '$GIT_BRANCH') { node ->
node << {
wipeOutWorkspace true
useShallowClone true
}
}
}
configure { project ->
def curlCommand = 'curl -d action=hifidevgrid -d "hostname=$HOSTNAME" ' +
'-d "github_user=$GITHUB_USER" -d "build_branch=$GIT_BRANCH" ' +
"-d \"revision=\$TARGET\" https://${ARTIFACT_DESTINATION}"
(project / publishers / 'hudson.plugins.postbuildtask.PostbuildTask' /
tasks / 'hudson.plugins.postbuildtask.TaskProperties' / script).setValue(curlCommand)
}
}
doxygenJob = hifiJob('docs', false)
doxygenJob.with {
scm {
git(GIT_REPO_URL, 'master') { node ->
node << {
useShallowClone true
}
}
}
configure { project ->
(project / builders).setValue('')
}
steps {
shell('doxygen')
}
}
queue doxygenJob

View file

@ -25,6 +25,7 @@
const float EntityItem::IMMORTAL = -1.0f; /// special lifetime which means the entity lives for ever. default lifetime
const float EntityItem::DEFAULT_GLOW_LEVEL = 0.0f;
const float EntityItem::DEFAULT_LOCAL_RENDER_ALPHA = 1.0f;
const float EntityItem::DEFAULT_MASS = 1.0f;
const float EntityItem::DEFAULT_LIFETIME = EntityItem::IMMORTAL;
const float EntityItem::DEFAULT_DAMPING = 0.5f;
@ -61,8 +62,8 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_position = glm::vec3(0,0,0);
_rotation = DEFAULT_ROTATION;
_dimensions = DEFAULT_DIMENSIONS;
_glowLevel = DEFAULT_GLOW_LEVEL;
_localRenderAlpha = DEFAULT_LOCAL_RENDER_ALPHA;
_mass = DEFAULT_MASS;
_velocity = DEFAULT_VELOCITY;
_gravity = DEFAULT_GRAVITY;
@ -745,6 +746,7 @@ EntityItemProperties EntityItem::getProperties() const {
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getAngularVelocity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignoreForCollisions, getIgnoreForCollisions);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove);
@ -779,6 +781,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, setAngularVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignoreForCollisions, setIgnoreForCollisions);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, setCollisionsWillMove);

View file

@ -153,6 +153,10 @@ public:
float getGlowLevel() const { return _glowLevel; }
void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; }
static const float DEFAULT_LOCAL_RENDER_ALPHA;
float getLocalRenderAlpha() const { return _localRenderAlpha; }
void setLocalRenderAlpha(float localRenderAlpha) { _localRenderAlpha = localRenderAlpha; }
static const float DEFAULT_MASS;
float getMass() const { return _mass; }
void setMass(float value) { _mass = value; }
@ -263,6 +267,7 @@ protected:
glm::vec3 _dimensions;
glm::quat _rotation;
float _glowLevel;
float _localRenderAlpha;
float _mass;
glm::vec3 _velocity;
glm::vec3 _gravity;

View file

@ -67,6 +67,7 @@ EntityItemProperties::EntityItemProperties() :
_animationFrameIndex(ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX),
_animationFPS(ModelEntityItem::DEFAULT_ANIMATION_FPS),
_glowLevel(0.0f),
_localRenderAlpha(1.0f),
_naturalDimensions(1.0f, 1.0f, 1.0f),
_colorChanged(false),
@ -76,6 +77,7 @@ EntityItemProperties::EntityItemProperties() :
_animationFrameIndexChanged(false),
_animationFPSChanged(false),
_glowLevelChanged(false),
_localRenderAlphaChanged(false),
_defaultSettings(true)
{
@ -156,6 +158,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha);
COPY_PROPERTY_TO_QSCRIPTVALUE(ignoreForCollisions);
COPY_PROPERTY_TO_QSCRIPTVALUE(collisionsWillMove);
@ -202,6 +205,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(localRenderAlpha, setLocalRenderAlpha);
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(ignoreForCollisions, setIgnoreForCollisions);
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(collisionsWillMove, setCollisionsWillMove);
@ -605,6 +609,7 @@ void EntityItemProperties::markAllChanged() {
_animationFrameIndexChanged = true;
_animationFPSChanged = true;
_glowLevelChanged = true;
_localRenderAlphaChanged = true;
}
AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const {

View file

@ -161,6 +161,7 @@ public:
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFPS() const { return _animationFPS; }
float getGlowLevel() const { return _glowLevel; }
float getLocalRenderAlpha() const { return _localRenderAlpha; }
const QString& getScript() const { return _script; }
// model related properties
@ -171,6 +172,7 @@ public:
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; }
void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; }
void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; }
void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; }
void setScript(const QString& value) { _script = value; _scriptChanged = true; }
@ -201,6 +203,7 @@ public:
bool animationFrameIndexChanged() const { return _animationFrameIndexChanged; }
bool animationFPSChanged() const { return _animationFPSChanged; }
bool glowLevelChanged() const { return _glowLevelChanged; }
bool localRenderAlphaChanged() const { return _localRenderAlphaChanged; }
void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; }
void markAllChanged();
@ -283,6 +286,7 @@ private:
float _animationFrameIndex;
float _animationFPS;
float _glowLevel;
float _localRenderAlpha;
QVector<SittingPoint> _sittingPoints;
glm::vec3 _naturalDimensions;
@ -293,6 +297,7 @@ private:
bool _animationFrameIndexChanged;
bool _animationFPSChanged;
bool _glowLevelChanged;
bool _localRenderAlphaChanged;
bool _defaultSettings;
};

View file

@ -15,6 +15,8 @@
#include <QThread>
#include <QtDebug>
#include <glm/gtx/transform.hpp>
#include <GeometryUtil.h>
#include "MetavoxelData.h"
@ -29,6 +31,7 @@ REGISTER_META_OBJECT(MetavoxelRenderer)
REGISTER_META_OBJECT(DefaultMetavoxelRenderer)
REGISTER_META_OBJECT(Spanner)
REGISTER_META_OBJECT(Sphere)
REGISTER_META_OBJECT(Cuboid)
REGISTER_META_OBJECT(StaticModel)
static int metavoxelDataTypeId = registerSimpleMetaType<MetavoxelData>();
@ -2025,6 +2028,14 @@ bool Spanner::findRayIntersection(const glm::vec3& origin, const glm::vec3& dire
return _bounds.findRayIntersection(origin, direction, distance);
}
bool Spanner::contains(const glm::vec3& point) {
return false;
}
bool Spanner::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) {
return false;
}
QByteArray Spanner::getRendererClassName() const {
return "SpannerRendererer";
}
@ -2042,7 +2053,7 @@ void SpannerRenderer::simulate(float deltaTime) {
// nothing by default
}
void SpannerRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
void SpannerRenderer::render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize) {
// nothing by default
}
@ -2072,20 +2083,22 @@ void Transformable::setScale(float scale) {
}
}
Sphere::Sphere() :
_color(Qt::gray) {
connect(this, SIGNAL(translationChanged(const glm::vec3&)), SLOT(updateBounds()));
connect(this, SIGNAL(scaleChanged(float)), SLOT(updateBounds()));
updateBounds();
ColorTransformable::ColorTransformable() :
_color(Qt::white) {
}
void Sphere::setColor(const QColor& color) {
void ColorTransformable::setColor(const QColor& color) {
if (_color != color) {
emit colorChanged(_color = color);
}
}
Sphere::Sphere() {
connect(this, SIGNAL(translationChanged(const glm::vec3&)), SLOT(updateBounds()));
connect(this, SIGNAL(scaleChanged(float)), SLOT(updateBounds()));
updateBounds();
}
const QVector<AttributePointer>& Sphere::getAttributes() const {
static QVector<AttributePointer> attributes = QVector<AttributePointer>() <<
AttributeRegistry::getInstance()->getColorAttribute() << AttributeRegistry::getInstance()->getNormalAttribute();
@ -2175,6 +2188,38 @@ bool Sphere::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
return findRaySphereIntersection(origin, direction, getTranslation(), getScale(), distance);
}
bool Sphere::contains(const glm::vec3& point) {
return glm::distance(point, getTranslation()) <= getScale();
}
bool Sphere::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) {
glm::vec3 relativeStart = start - getTranslation();
glm::vec3 vector = end - start;
float a = glm::dot(vector, vector);
if (a == 0.0f) {
return false;
}
float b = glm::dot(relativeStart, vector);
float radicand = b * b - a * (glm::dot(relativeStart, relativeStart) - getScale() * getScale());
if (radicand < 0.0f) {
return false;
}
float radical = glm::sqrt(radicand);
float first = (-b - radical) / a;
if (first >= 0.0f && first <= 1.0f) {
distance = first;
normal = glm::normalize(relativeStart + vector * distance);
return true;
}
float second = (-b + radical) / a;
if (second >= 0.0f && second <= 1.0f) {
distance = second;
normal = glm::normalize(relativeStart + vector * distance);
return true;
}
return false;
}
QByteArray Sphere::getRendererClassName() const {
return "SphereRenderer";
}
@ -2201,6 +2246,89 @@ AttributeValue Sphere::getNormal(MetavoxelInfo& info, int alpha) const {
return AttributeValue(getAttributes().at(1), encodeInline<QRgb>(color));
}
Cuboid::Cuboid() :
_aspectY(1.0f),
_aspectZ(1.0f) {
connect(this, &Cuboid::translationChanged, this, &Cuboid::updateBoundsAndPlanes);
connect(this, &Cuboid::rotationChanged, this, &Cuboid::updateBoundsAndPlanes);
connect(this, &Cuboid::scaleChanged, this, &Cuboid::updateBoundsAndPlanes);
connect(this, &Cuboid::aspectYChanged, this, &Cuboid::updateBoundsAndPlanes);
connect(this, &Cuboid::aspectZChanged, this, &Cuboid::updateBoundsAndPlanes);
updateBoundsAndPlanes();
}
void Cuboid::setAspectY(float aspectY) {
if (_aspectY != aspectY) {
emit aspectYChanged(_aspectY = aspectY);
}
}
void Cuboid::setAspectZ(float aspectZ) {
if (_aspectZ != aspectZ) {
emit aspectZChanged(_aspectZ = aspectZ);
}
}
bool Cuboid::contains(const glm::vec3& point) {
glm::vec4 point4(point, 1.0f);
for (int i = 0; i < PLANE_COUNT; i++) {
if (glm::dot(_planes[i], point4) > 0.0f) {
return false;
}
}
return true;
}
bool Cuboid::intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal) {
glm::vec4 start4(start, 1.0f);
glm::vec4 vector = glm::vec4(end - start, 0.0f);
for (int i = 0; i < PLANE_COUNT; i++) {
// first check the segment against the plane
float divisor = glm::dot(_planes[i], vector);
if (glm::abs(divisor) < EPSILON) {
continue;
}
float t = -glm::dot(_planes[i], start4) / divisor;
if (t < 0.0f || t > 1.0f) {
continue;
}
// now that we've established that it intersects the plane, check against the other sides
glm::vec4 point = start4 + vector * t;
const int PLANES_PER_AXIS = 2;
int indexOffset = ((i / PLANES_PER_AXIS) + 1) * PLANES_PER_AXIS;
for (int j = 0; j < PLANE_COUNT - PLANES_PER_AXIS; j++) {
if (glm::dot(_planes[(indexOffset + j) % PLANE_COUNT], point) > 0.0f) {
goto outerContinue;
}
}
distance = t;
normal = glm::vec3(_planes[i]);
return true;
outerContinue: ;
}
return false;
}
QByteArray Cuboid::getRendererClassName() const {
return "CuboidRenderer";
}
void Cuboid::updateBoundsAndPlanes() {
glm::vec3 extent(getScale(), getScale() * _aspectY, getScale() * _aspectZ);
glm::mat4 rotationMatrix = glm::mat4_cast(getRotation());
setBounds(glm::translate(getTranslation()) * rotationMatrix * Box(-extent, extent));
glm::vec4 translation4 = glm::vec4(getTranslation(), 1.0f);
_planes[0] = glm::vec4(glm::vec3(rotationMatrix[0]), -glm::dot(rotationMatrix[0], translation4) - getScale());
_planes[1] = glm::vec4(glm::vec3(-rotationMatrix[0]), glm::dot(rotationMatrix[0], translation4) - getScale());
_planes[2] = glm::vec4(glm::vec3(rotationMatrix[1]), -glm::dot(rotationMatrix[1], translation4) - getScale() * _aspectY);
_planes[3] = glm::vec4(glm::vec3(-rotationMatrix[1]), glm::dot(rotationMatrix[1], translation4) - getScale() * _aspectY);
_planes[4] = glm::vec4(glm::vec3(rotationMatrix[2]), -glm::dot(rotationMatrix[2], translation4) - getScale() * _aspectZ);
_planes[5] = glm::vec4(glm::vec3(-rotationMatrix[2]), glm::dot(rotationMatrix[2], translation4) - getScale() * _aspectZ);
}
StaticModel::StaticModel() {
}

View file

@ -639,6 +639,12 @@ public:
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
/// Checks whether the spanner contains the specified point.
virtual bool contains(const glm::vec3& point);
/// Finds the intersection, if any, between the specified line segment and the spanner.
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
signals:
void boundsWillChange();
@ -675,7 +681,7 @@ public:
virtual void init(Spanner* spanner);
virtual void simulate(float deltaTime);
virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize);
virtual void render(const glm::vec4& color, Mode mode, const glm::vec3& clipMinimum, float clipSize);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
@ -717,29 +723,44 @@ private:
float _scale;
};
/// A sphere.
class Sphere : public Transformable {
/// A transformable object with a color.
class ColorTransformable : public Transformable {
Q_OBJECT
Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged DESIGNABLE false)
public:
ColorTransformable();
void setColor(const QColor& color);
const QColor& getColor() const { return _color; }
signals:
void colorChanged(const QColor& color);
protected:
QColor _color;
};
/// A sphere.
class Sphere : public ColorTransformable {
Q_OBJECT
public:
Q_INVOKABLE Sphere();
void setColor(const QColor& color);
const QColor& getColor() const { return _color; }
virtual const QVector<AttributePointer>& getAttributes() const;
virtual const QVector<AttributePointer>& getVoxelizedAttributes() const;
virtual bool getAttributeValues(MetavoxelInfo& info, bool force = false) const;
virtual bool blendAttributeValues(MetavoxelInfo& info, bool force = false) const;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& clipMinimum, float clipSize, float& distance) const;
signals:
void colorChanged(const QColor& color);
virtual bool contains(const glm::vec3& point);
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
protected:
virtual QByteArray getRendererClassName() const;
@ -751,8 +772,47 @@ private slots:
private:
AttributeValue getNormal(MetavoxelInfo& info, int alpha) const;
};
/// A cuboid.
class Cuboid : public ColorTransformable {
Q_OBJECT
Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged)
Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged)
QColor _color;
public:
Q_INVOKABLE Cuboid();
void setAspectY(float aspectY);
float getAspectY() const { return _aspectY; }
void setAspectZ(float aspectZ);
float getAspectZ() const { return _aspectZ; }
virtual bool contains(const glm::vec3& point);
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
signals:
void aspectYChanged(float aspectY);
void aspectZChanged(float aspectZ);
protected:
virtual QByteArray getRendererClassName() const;
private slots:
void updateBoundsAndPlanes();
private:
float _aspectY;
float _aspectZ;
static const int PLANE_COUNT = 6;
glm::vec4 _planes[PLANE_COUNT];
};
/// A static 3D model loaded from the network.

View file

@ -408,12 +408,6 @@ void PaintHeightfieldHeightEdit::apply(MetavoxelData& data, const WeakSharedObje
data.guide(visitor);
}
PaintHeightfieldColorEdit::PaintHeightfieldColorEdit(const glm::vec3& position, float radius, const QColor& color) :
position(position),
radius(radius),
color(color) {
}
class PaintHeightfieldMaterialEditVisitor : public MetavoxelVisitor {
public:
@ -599,11 +593,6 @@ int PaintHeightfieldMaterialEditVisitor::visit(MetavoxelInfo& info) {
return STOP_RECURSION;
}
void PaintHeightfieldColorEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
PaintHeightfieldMaterialEditVisitor visitor(position, radius, SharedObjectPointer(), color);
data.guide(visitor);
}
PaintHeightfieldMaterialEdit::PaintHeightfieldMaterialEdit(const glm::vec3& position, float radius,
const SharedObjectPointer& material, const QColor& averageColor) :
position(position),
@ -617,34 +606,34 @@ void PaintHeightfieldMaterialEdit::apply(MetavoxelData& data, const WeakSharedOb
data.guide(visitor);
}
VoxelColorBoxEdit::VoxelColorBoxEdit(const Box& region, float granularity, const QColor& color) :
region(region),
granularity(granularity),
color(color) {
}
const int VOXEL_BLOCK_SIZE = 16;
const int VOXEL_BLOCK_SAMPLES = VOXEL_BLOCK_SIZE + 1;
const int VOXEL_BLOCK_AREA = VOXEL_BLOCK_SAMPLES * VOXEL_BLOCK_SAMPLES;
const int VOXEL_BLOCK_VOLUME = VOXEL_BLOCK_AREA * VOXEL_BLOCK_SAMPLES;
class VoxelMaterialBoxEditVisitor : public MetavoxelVisitor {
VoxelMaterialSpannerEdit::VoxelMaterialSpannerEdit(const SharedObjectPointer& spanner,
const SharedObjectPointer& material, const QColor& averageColor) :
spanner(spanner),
material(material),
averageColor(averageColor) {
}
class VoxelMaterialSpannerEditVisitor : public MetavoxelVisitor {
public:
VoxelMaterialBoxEditVisitor(const Box& region, float granularity,
const SharedObjectPointer& material, const QColor& color);
VoxelMaterialSpannerEditVisitor(Spanner* spanner, const SharedObjectPointer& material, const QColor& color);
virtual int visit(MetavoxelInfo& info);
private:
Box _region;
Spanner* _spanner;
SharedObjectPointer _material;
QColor _color;
float _blockSize;
};
VoxelMaterialBoxEditVisitor::VoxelMaterialBoxEditVisitor(const Box& region, float granularity,
VoxelMaterialSpannerEditVisitor::VoxelMaterialSpannerEditVisitor(Spanner* spanner,
const SharedObjectPointer& material, const QColor& color) :
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getVoxelColorAttribute() <<
AttributeRegistry::getInstance()->getVoxelHermiteAttribute() <<
@ -652,15 +641,15 @@ VoxelMaterialBoxEditVisitor::VoxelMaterialBoxEditVisitor(const Box& region, floa
AttributeRegistry::getInstance()->getVoxelColorAttribute() <<
AttributeRegistry::getInstance()->getVoxelHermiteAttribute() <<
AttributeRegistry::getInstance()->getVoxelMaterialAttribute()),
_region(region),
_spanner(spanner),
_material(material),
_color(color),
_blockSize(granularity * VOXEL_BLOCK_SIZE) {
_blockSize(spanner->getVoxelizationGranularity() * VOXEL_BLOCK_SIZE) {
}
int VoxelMaterialBoxEditVisitor::visit(MetavoxelInfo& info) {
int VoxelMaterialSpannerEditVisitor::visit(MetavoxelInfo& info) {
Box bounds = info.getBounds();
if (!bounds.intersects(_region)) {
if (!bounds.intersects(_spanner->getBounds())) {
return STOP_RECURSION;
}
if (info.size > _blockSize) {
@ -671,7 +660,7 @@ int VoxelMaterialBoxEditVisitor::visit(MetavoxelInfo& info) {
colorPointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME);
QVector<QRgb> colorContents = oldColorContents;
Box overlap = info.getBounds().getIntersection(_region);
Box overlap = info.getBounds().getIntersection(_spanner->getBounds());
float scale = VOXEL_BLOCK_SIZE / info.size;
overlap.minimum = (overlap.minimum - info.minimum) * scale;
overlap.maximum = (overlap.maximum - info.minimum) * scale;
@ -682,279 +671,18 @@ int VoxelMaterialBoxEditVisitor::visit(MetavoxelInfo& info) {
int sizeY = (int)overlap.maximum.y - minY + 1;
int sizeZ = (int)overlap.maximum.z - minZ + 1;
QRgb rgb = _color.rgba();
for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA) {
for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; destY += VOXEL_BLOCK_SAMPLES) {
for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++) {
*destX = rgb;
}
}
}
VoxelColorDataPointer newColorPointer(new VoxelColorData(colorContents, VOXEL_BLOCK_SAMPLES));
info.outputValues[0] = AttributeValue(info.inputValues.at(0).getAttribute(),
encodeInline<VoxelColorDataPointer>(newColorPointer));
VoxelHermiteDataPointer hermitePointer = info.inputValues.at(1).getInlineValue<VoxelHermiteDataPointer>();
QVector<QRgb> hermiteContents = (hermitePointer && hermitePointer->getSize() == VOXEL_BLOCK_SAMPLES) ?
hermitePointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME * VoxelHermiteData::EDGE_COUNT);
int hermiteArea = VOXEL_BLOCK_AREA * VoxelHermiteData::EDGE_COUNT;
int hermiteSamples = VOXEL_BLOCK_SAMPLES * VoxelHermiteData::EDGE_COUNT;
int hermiteMinX = minX, hermiteMinY = minY, hermiteMinZ = minZ;
int hermiteSizeX = sizeX, hermiteSizeY = sizeY, hermiteSizeZ = sizeZ;
if (minX > 0) {
hermiteMinX--;
hermiteSizeX++;
}
if (minY > 0) {
hermiteMinY--;
hermiteSizeY++;
}
if (minZ > 0) {
hermiteMinZ--;
hermiteSizeZ++;
}
const int NORMAL_MAX = 127;
QRgb* hermiteDestZ = hermiteContents.data() + hermiteMinZ * hermiteArea + hermiteMinY * hermiteSamples +
hermiteMinX * VoxelHermiteData::EDGE_COUNT;
for (int z = hermiteMinZ, hermiteMaxZ = z + hermiteSizeZ - 1; z <= hermiteMaxZ; z++, hermiteDestZ += hermiteArea) {
QRgb* hermiteDestY = hermiteDestZ;
for (int y = hermiteMinY, hermiteMaxY = y + hermiteSizeY - 1; y <= hermiteMaxY; y++, hermiteDestY += hermiteSamples) {
QRgb* hermiteDestX = hermiteDestY;
for (int x = hermiteMinX, hermiteMaxX = x + hermiteSizeX - 1; x <= hermiteMaxX; x++,
hermiteDestX += VoxelHermiteData::EDGE_COUNT) {
// internal edges are set to zero; border edges (when non-terminal) are set to the intersection values
if ((x == hermiteMinX || x == hermiteMaxX) && x != VOXEL_BLOCK_SIZE) {
int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]);
int alpha1 = qAlpha(color[1]);
if (alpha0 != alpha1) {
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) {
if (x == hermiteMinX) {
int alpha = (overlap.minimum.x - x) * EIGHT_BIT_MAXIMUM;
if (alpha <= qAlpha(hermiteDestX[0])) {
hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, alpha);
}
} else {
int alpha = (overlap.maximum.x - x) * EIGHT_BIT_MAXIMUM;
if (alpha >= qAlpha(hermiteDestX[0])) {
hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0, alpha);
}
}
} else {
hermiteDestX[0] = qRgba(alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, 0,
((x == hermiteMinX ? overlap.minimum.x : overlap.maximum.x) - x) * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[0] = 0x0;
}
} else {
hermiteDestX[0] = 0x0;
}
if ((y == hermiteMinY || y == hermiteMaxY) && y != VOXEL_BLOCK_SIZE) {
int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]);
int alpha2 = qAlpha(color[VOXEL_BLOCK_SAMPLES]);
if (alpha0 != alpha2) {
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) {
if (y == hermiteMinY) {
int alpha = (overlap.minimum.y - y) * EIGHT_BIT_MAXIMUM;
if (alpha <= qAlpha(hermiteDestX[1])) {
hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, alpha);
}
} else {
int alpha = (overlap.maximum.y - y) * EIGHT_BIT_MAXIMUM;
if (alpha >= qAlpha(hermiteDestX[1])) {
hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0, alpha);
}
}
} else {
hermiteDestX[1] = qRgba(0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, 0,
((y == hermiteMinY ? overlap.minimum.y : overlap.maximum.y) - y) * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[1] = 0x0;
}
} else {
hermiteDestX[1] = 0x0;
}
if ((z == hermiteMinZ || z == hermiteMaxZ) && z != VOXEL_BLOCK_SIZE) {
int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]);
int alpha4 = qAlpha(color[VOXEL_BLOCK_AREA]);
if (alpha0 != alpha4) {
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) {
if (z == hermiteMinZ) {
int alpha = (overlap.minimum.z - z) * EIGHT_BIT_MAXIMUM;
if (alpha <= qAlpha(hermiteDestX[2])) {
hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, alpha);
}
} else {
int alpha = (overlap.maximum.z - z) * EIGHT_BIT_MAXIMUM;
if (alpha >= qAlpha(hermiteDestX[2])) {
hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX, alpha);
}
}
} else {
hermiteDestX[2] = qRgba(0, 0, alpha0 == 0 ? -NORMAL_MAX : NORMAL_MAX,
((z == hermiteMinZ ? overlap.minimum.z : overlap.maximum.z) - z) * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[2] = 0x0;
}
} else {
hermiteDestX[2] = 0x0;
}
}
}
}
VoxelHermiteDataPointer newHermitePointer(new VoxelHermiteData(hermiteContents, VOXEL_BLOCK_SAMPLES));
info.outputValues[1] = AttributeValue(info.inputValues.at(1).getAttribute(),
encodeInline<VoxelHermiteDataPointer>(newHermitePointer));
VoxelMaterialDataPointer materialPointer = info.inputValues.at(2).getInlineValue<VoxelMaterialDataPointer>();
QByteArray materialContents;
QVector<SharedObjectPointer> materials;
if (materialPointer && materialPointer->getSize() == VOXEL_BLOCK_SAMPLES) {
materialContents = materialPointer->getContents();
materials = materialPointer->getMaterials();
} else {
materialContents = QByteArray(VOXEL_BLOCK_VOLUME, 0);
}
uchar materialIndex = getMaterialIndex(_material, materials, materialContents);
for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA) {
for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY; destY += VOXEL_BLOCK_SAMPLES) {
for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++) {
*destX = materialIndex;
}
}
}
clearUnusedMaterials(materials, materialContents);
VoxelMaterialDataPointer newMaterialPointer(new VoxelMaterialData(materialContents, VOXEL_BLOCK_SAMPLES, materials));
info.outputValues[2] = AttributeValue(_inputs.at(2), encodeInline<VoxelMaterialDataPointer>(newMaterialPointer));
return STOP_RECURSION;
}
void VoxelColorBoxEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
// expand to fit the entire edit
while (!data.getBounds().contains(region)) {
data.expand();
}
VoxelMaterialBoxEditVisitor visitor(region, granularity, SharedObjectPointer(), color);
data.guide(visitor);
}
VoxelMaterialBoxEdit::VoxelMaterialBoxEdit(const Box& region, float granularity,
const SharedObjectPointer& material, const QColor& averageColor) :
region(region),
granularity(granularity),
material(material),
averageColor(averageColor) {
}
void VoxelMaterialBoxEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
// expand to fit the entire edit
while (!data.getBounds().contains(region)) {
data.expand();
}
VoxelMaterialBoxEditVisitor visitor(region, granularity, material, averageColor);
data.guide(visitor);
}
VoxelColorSphereEdit::VoxelColorSphereEdit(const glm::vec3& center, float radius, float granularity, const QColor& color) :
center(center),
radius(radius),
granularity(granularity),
color(color) {
}
class VoxelMaterialSphereEditVisitor : public MetavoxelVisitor {
public:
VoxelMaterialSphereEditVisitor(const glm::vec3& center, float radius, const Box& bounds, float granularity,
const SharedObjectPointer& material, const QColor& color);
virtual int visit(MetavoxelInfo& info);
private:
glm::vec3 _center;
float _radius;
Box _bounds;
SharedObjectPointer _material;
QColor _color;
float _blockSize;
};
VoxelMaterialSphereEditVisitor::VoxelMaterialSphereEditVisitor(const glm::vec3& center, float radius, const Box& bounds,
float granularity, const SharedObjectPointer& material, const QColor& color) :
MetavoxelVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getVoxelColorAttribute() <<
AttributeRegistry::getInstance()->getVoxelHermiteAttribute() <<
AttributeRegistry::getInstance()->getVoxelMaterialAttribute(), QVector<AttributePointer>() <<
AttributeRegistry::getInstance()->getVoxelColorAttribute() <<
AttributeRegistry::getInstance()->getVoxelHermiteAttribute() <<
AttributeRegistry::getInstance()->getVoxelMaterialAttribute()),
_center(center),
_radius(radius),
_bounds(bounds),
_material(material),
_color(color),
_blockSize(granularity * VOXEL_BLOCK_SIZE) {
}
int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
Box bounds = info.getBounds();
if (!bounds.intersects(_bounds)) {
return STOP_RECURSION;
}
if (info.size > _blockSize) {
return DEFAULT_ORDER;
}
VoxelColorDataPointer colorPointer = info.inputValues.at(0).getInlineValue<VoxelColorDataPointer>();
QVector<QRgb> oldColorContents = (colorPointer && colorPointer->getSize() == VOXEL_BLOCK_SAMPLES) ?
colorPointer->getContents() : QVector<QRgb>(VOXEL_BLOCK_VOLUME);
QVector<QRgb> colorContents = oldColorContents;
Box overlap = info.getBounds().getIntersection(_bounds);
float scale = VOXEL_BLOCK_SIZE / info.size;
overlap.minimum = (overlap.minimum - info.minimum) * scale;
overlap.maximum = (overlap.maximum - info.minimum) * scale;
int minX = glm::ceil(overlap.minimum.x);
int minY = glm::ceil(overlap.minimum.y);
int minZ = glm::ceil(overlap.minimum.z);
int sizeX = (int)overlap.maximum.x - minX + 1;
int sizeY = (int)overlap.maximum.y - minY + 1;
int sizeZ = (int)overlap.maximum.z - minZ + 1;
glm::vec3 relativeCenter = (_center - info.minimum) * scale;
float relativeRadius = _radius * scale;
float relativeRadiusSquared = relativeRadius * relativeRadius;
QRgb rgb = _color.rgba();
bool flipped = (qAlpha(rgb) == 0);
glm::vec3 position(0.0f, 0.0f, minZ);
float step = 1.0f / scale;
glm::vec3 position(0.0f, 0.0f, info.minimum.z + minZ * step);
for (QRgb* destZ = colorContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) {
position.y = minY;
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (QRgb* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y++) {
position.x = minX;
for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x++) {
if (glm::distance(relativeCenter, position) <= relativeRadius) {
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (QRgb* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
*destX = rgb;
}
}
@ -995,38 +723,26 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
hermiteDestX += VoxelHermiteData::EDGE_COUNT) {
// at each intersected non-terminal edge, we check for a transition and, if one is detected, we assign the
// crossing and normal values based on intersection with the sphere
glm::vec3 vector(x - relativeCenter.x, y - relativeCenter.y, z - relativeCenter.z);
float distance;
glm::vec3 normal;
if (x != VOXEL_BLOCK_SIZE) {
int offset = z * VOXEL_BLOCK_AREA + y * VOXEL_BLOCK_SAMPLES + x;
const QRgb* color = colorContents.constData() + offset;
int alpha0 = qAlpha(color[0]);
int alpha1 = qAlpha(color[1]);
if (alpha0 != alpha1) {
float radicand = relativeRadiusSquared - vector.y * vector.y - vector.z * vector.z;
float parameter = 0.5f;
if (radicand >= 0.0f) {
float root = glm::sqrt(radicand);
parameter = -vector.x - root;
if (parameter < 0.0f || parameter > 1.0f) {
parameter = glm::clamp(-vector.x + root, 0.0f, 1.0f);
if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step,
info.minimum + glm::vec3(x + 1, y, z) * step, distance, normal)) {
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) {
int alpha = distance * EIGHT_BIT_MAXIMUM;
if (normal.x < 0.0f ? alpha <= qAlpha(hermiteDestX[0]) : alpha >= qAlpha(hermiteDestX[0])) {
hermiteDestX[0] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[0] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM);
}
}
glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f);
float length = glm::length(normal);
if (length > EPSILON) {
normal /= length;
} else {
normal = glm::vec3(0.0f, 1.0f, 0.0f);
}
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[1]) == alpha1) {
int alpha = parameter * EIGHT_BIT_MAXIMUM;
if (normal.x < 0.0f ? alpha <= qAlpha(hermiteDestX[0]) : alpha >= qAlpha(hermiteDestX[0])) {
hermiteDestX[0] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[0] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[0] = 0x0;
}
@ -1039,31 +755,18 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
int alpha0 = qAlpha(color[0]);
int alpha2 = qAlpha(color[VOXEL_BLOCK_SAMPLES]);
if (alpha0 != alpha2) {
float radicand = relativeRadiusSquared - vector.x * vector.x - vector.z * vector.z;
float parameter = 0.5f;
if (radicand >= 0.0f) {
float root = glm::sqrt(radicand);
parameter = -vector.y - root;
if (parameter < 0.0f || parameter > 1.0f) {
parameter = glm::clamp(-vector.y + root, 0.0f, 1.0f);
if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step,
info.minimum + glm::vec3(x, y + 1, z) * step, distance, normal)) {
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) {
int alpha = distance * EIGHT_BIT_MAXIMUM;
if (normal.y < 0.0f ? alpha <= qAlpha(hermiteDestX[1]) : alpha >= qAlpha(hermiteDestX[1])) {
hermiteDestX[1] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[1] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM);
}
}
glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f);
float length = glm::length(normal);
if (length > EPSILON) {
normal /= length;
} else {
normal = glm::vec3(1.0f, 0.0f, 0.0f);
}
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_SAMPLES]) == alpha2) {
int alpha = parameter * EIGHT_BIT_MAXIMUM;
if (normal.y < 0.0f ? alpha <= qAlpha(hermiteDestX[1]) : alpha >= qAlpha(hermiteDestX[1])) {
hermiteDestX[1] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[1] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[1] = 0x0;
}
@ -1076,31 +779,18 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
int alpha0 = qAlpha(color[0]);
int alpha4 = qAlpha(color[VOXEL_BLOCK_AREA]);
if (alpha0 != alpha4) {
float radicand = relativeRadiusSquared - vector.x * vector.x - vector.y * vector.y;
float parameter = 0.5f;
if (radicand >= 0.0f) {
float root = glm::sqrt(radicand);
parameter = -vector.z - root;
if (parameter < 0.0f || parameter > 1.0f) {
parameter = glm::clamp(-vector.z + root, 0.0f, 1.0f);
if (_spanner->intersects(info.minimum + glm::vec3(x, y, z) * step,
info.minimum + glm::vec3(x, y, z + 1) * step, distance, normal)) {
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) {
int alpha = distance * EIGHT_BIT_MAXIMUM;
if (normal.z < 0.0f ? alpha <= qAlpha(hermiteDestX[2]) : alpha >= qAlpha(hermiteDestX[2])) {
hermiteDestX[2] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[2] = packNormal(flipped ? -normal : normal, distance * EIGHT_BIT_MAXIMUM);
}
}
glm::vec3 normal = vector + glm::vec3(parameter, 0.0f, 0.0f);
float length = glm::length(normal);
if (length > EPSILON) {
normal /= length;
} else {
normal = glm::vec3(1.0f, 0.0f, 0.0f);
}
const QRgb* oldColor = oldColorContents.constData() + offset;
if (qAlpha(oldColor[0]) == alpha0 && qAlpha(oldColor[VOXEL_BLOCK_AREA]) == alpha4) {
int alpha = parameter * EIGHT_BIT_MAXIMUM;
if (normal.z < 0.0f ? alpha <= qAlpha(hermiteDestX[2]) : alpha >= qAlpha(hermiteDestX[2])) {
hermiteDestX[2] = packNormal(flipped ? -normal : normal, alpha);
}
} else {
hermiteDestX[2] = packNormal(flipped ? -normal : normal, parameter * EIGHT_BIT_MAXIMUM);
}
} else {
hermiteDestX[2] = 0x0;
}
@ -1127,15 +817,15 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
}
uchar materialIndex = getMaterialIndex(_material, materials, materialContents);
position.z = minZ;
position.z = info.minimum.z + minZ * step;
for (uchar* destZ = (uchar*)materialContents.data() + minZ * VOXEL_BLOCK_AREA + minY * VOXEL_BLOCK_SAMPLES + minX,
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z++) {
position.y = minY;
*endZ = destZ + sizeZ * VOXEL_BLOCK_AREA; destZ != endZ; destZ += VOXEL_BLOCK_AREA, position.z += step) {
position.y = info.minimum.y + minY * step;
for (uchar* destY = destZ, *endY = destY + sizeY * VOXEL_BLOCK_SAMPLES; destY != endY;
destY += VOXEL_BLOCK_SAMPLES, position.y++) {
position.x = minX;
for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x++) {
if (glm::distance(relativeCenter, position) <= relativeRadius) {
destY += VOXEL_BLOCK_SAMPLES, position.y += step) {
position.x = info.minimum.x + minX * step;
for (uchar* destX = destY, *endX = destX + sizeX; destX != endX; destX++, position.x += step) {
if (_spanner->contains(position)) {
*destX = materialIndex;
}
}
@ -1149,33 +839,12 @@ int VoxelMaterialSphereEditVisitor::visit(MetavoxelInfo& info) {
return STOP_RECURSION;
}
void VoxelColorSphereEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
void VoxelMaterialSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
// expand to fit the entire edit
glm::vec3 extents(radius, radius, radius);
Box bounds(center - extents, center + extents);
while (!data.getBounds().contains(bounds)) {
Spanner* spanner = static_cast<Spanner*>(this->spanner.data());
while (!data.getBounds().contains(spanner->getBounds())) {
data.expand();
}
VoxelMaterialSphereEditVisitor visitor(center, radius, bounds, granularity, SharedObjectPointer(), color);
data.guide(visitor);
}
VoxelMaterialSphereEdit::VoxelMaterialSphereEdit(const glm::vec3& center, float radius, float granularity,
const SharedObjectPointer& material, const QColor& averageColor) :
center(center),
radius(radius),
granularity(granularity),
material(material),
averageColor(averageColor) {
}
void VoxelMaterialSphereEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const {
// expand to fit the entire edit
glm::vec3 extents(radius, radius, radius);
Box bounds(center - extents, center + extents);
while (!data.getBounds().contains(bounds)) {
data.expand();
}
VoxelMaterialSphereEditVisitor visitor(center, radius, bounds, granularity, material, averageColor);
VoxelMaterialSpannerEditVisitor visitor(spanner, material, averageColor);
data.guide(visitor);
}

View file

@ -224,23 +224,6 @@ public:
DECLARE_STREAMABLE_METATYPE(PaintHeightfieldHeightEdit)
/// An edit that sets a region of a heightfield color.
class PaintHeightfieldColorEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM glm::vec3 position;
STREAM float radius;
STREAM QColor color;
PaintHeightfieldColorEdit(const glm::vec3& position = glm::vec3(), float radius = 0.0f, const QColor& color = QColor());
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};
DECLARE_STREAMABLE_METATYPE(PaintHeightfieldColorEdit)
/// An edit that sets a region of a heightfield material.
class PaintHeightfieldMaterialEdit : public MetavoxelEdit {
STREAMABLE
@ -260,79 +243,22 @@ public:
DECLARE_STREAMABLE_METATYPE(PaintHeightfieldMaterialEdit)
/// An edit that sets the color of voxels within a box to a value.
class VoxelColorBoxEdit : public MetavoxelEdit {
/// An edit that sets the materials of voxels within a spanner to a value.
class VoxelMaterialSpannerEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM Box region;
STREAM float granularity;
STREAM QColor color;
VoxelColorBoxEdit(const Box& region = Box(), float granularity = 0.0f, const QColor& color = QColor());
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};
DECLARE_STREAMABLE_METATYPE(VoxelColorBoxEdit)
/// An edit that sets the materials of voxels within a box to a value.
class VoxelMaterialBoxEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM Box region;
STREAM float granularity;
STREAM SharedObjectPointer spanner;
STREAM SharedObjectPointer material;
STREAM QColor averageColor;
VoxelMaterialBoxEdit(const Box& region = Box(), float granularity = 0.0f,
VoxelMaterialSpannerEdit(const SharedObjectPointer& spanner = SharedObjectPointer(),
const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor());
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};
DECLARE_STREAMABLE_METATYPE(VoxelMaterialBoxEdit)
/// An edit that sets the color of voxels within a sphere to a value.
class VoxelColorSphereEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM glm::vec3 center;
STREAM float radius;
STREAM float granularity;
STREAM QColor color;
VoxelColorSphereEdit(const glm::vec3& center = glm::vec3(), float radius = 0.0f,
float granularity = 0.0f, const QColor& color = QColor());
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};
DECLARE_STREAMABLE_METATYPE(VoxelColorSphereEdit)
/// An edit that sets the materials of voxels within a sphere to a value.
class VoxelMaterialSphereEdit : public MetavoxelEdit {
STREAMABLE
public:
STREAM glm::vec3 center;
STREAM float radius;
STREAM float granularity;
STREAM SharedObjectPointer material;
STREAM QColor averageColor;
VoxelMaterialSphereEdit(const glm::vec3& center = glm::vec3(), float radius = 0.0f, float granularity = 0.0f,
const SharedObjectPointer& material = SharedObjectPointer(), const QColor& averageColor = QColor());
virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const;
};
DECLARE_STREAMABLE_METATYPE(VoxelMaterialSphereEdit)
DECLARE_STREAMABLE_METATYPE(VoxelMaterialSpannerEdit)
#endif // hifi_MetavoxelMessages_h

View file

@ -490,6 +490,7 @@ QColorEditor::QColorEditor(QWidget* parent) : QWidget(parent) {
setLayout(layout);
layout->addWidget(_button = new QPushButton());
connect(_button, SIGNAL(clicked()), SLOT(selectColor()));
setColor(QColor());
}
void QColorEditor::setColor(const QColor& color) {

View file

@ -59,7 +59,8 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co
AccountManager::AccountManager() :
_authURL(),
_pendingCallbackMap(),
_accountInfo()
_accountInfo(),
_shouldPersistToSettingsFile(true)
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
@ -83,14 +84,18 @@ void AccountManager::logout() {
emit balanceChanged(0);
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
if (_shouldPersistToSettingsFile) {
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
} else {
qDebug() << "Cleared data server account info in account manager.";
}
emit logoutComplete();
// the username has changed to blank
@ -116,28 +121,29 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
if (_authURL != authURL) {
_authURL = authURL;
qDebug() << "URL for node authentication has been changed to" << qPrintable(_authURL.toString());
qDebug() << "Re-setting authentication flow.";
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
// profile info isn't guaranteed to be saved too
if (_accountInfo.hasProfile()) {
emit profileChanged();
} else {
requestProfile();
qDebug() << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
if (_shouldPersistToSettingsFile) {
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
// profile info isn't guaranteed to be saved too
if (_accountInfo.hasProfile()) {
emit profileChanged();
} else {
requestProfile();
}
}
}
}
@ -314,8 +320,9 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "An access token is required for requests to" << qPrintable(_authURL.toString());
}
@ -337,6 +344,19 @@ bool AccountManager::checkAndSignalForAccessToken() {
return hasToken;
}
void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
// clear our current DataServerAccountInfo
_accountInfo = DataServerAccountInfo();
// start the new account info with a new OAuthAccessToken
OAuthAccessToken newOAuthToken;
newOAuthToken.token = accessToken;
qDebug() << "Setting new account manager access token to" << accessToken;
_accountInfo.setAccessToken(newOAuthToken);
}
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
@ -387,12 +407,14 @@ void AccountManager::requestAccessTokenFinished() {
_accountInfo.setAccessTokenFromJSON(rootObject);
emit loginComplete(rootURL);
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
if (_shouldPersistToSettingsFile) {
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
requestProfile();
}
@ -436,13 +458,16 @@ void AccountManager::requestProfileFinished() {
// the username has changed to whatever came back
emit usernameChanged(_accountInfo.getUsername());
// store the whole profile into the local settings
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
if (_shouldPersistToSettingsFile) {
// store the whole profile into the local settings
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
} else {
// TODO: error handling
qDebug() << "Error in response for profile";

View file

@ -59,10 +59,13 @@ public:
const QUrl& getAuthURL() const { return _authURL; }
void setAuthURL(const QUrl& authURL);
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; }
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
bool hasValidAccessToken();
Q_INVOKABLE bool checkAndSignalForAccessToken();
void setAccessTokenForCurrentAuthURL(const QString& accessToken);
void requestAccessToken(const QString& login, const QString& password);
void requestProfile();
@ -107,6 +110,7 @@ private:
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
DataServerAccountInfo _accountInfo;
bool _shouldPersistToSettingsFile;
};
#endif // hifi_AccountManager_h

View file

@ -22,17 +22,46 @@ AddressManager& AddressManager::getInstance() {
return sharedInstance;
}
QString AddressManager::pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation,
const glm::quat& orientation) {
AddressManager::AddressManager() :
_currentDomain(),
_positionGetter(NULL),
_orientationGetter(NULL)
{
QString pathString = "/" + createByteArray(position);
}
const QUrl AddressManager::currentAddress() {
QUrl hifiURL;
if (hasOrientation) {
QString orientationString = createByteArray(orientation);
pathString += "/" + orientationString;
hifiURL.setScheme(HIFI_URL_SCHEME);
hifiURL.setHost(_currentDomain);
hifiURL.setPath(currentPath());
return hifiURL;
}
const QString AddressManager::currentPath(bool withOrientation) const {
if (_positionGetter) {
QString pathString = "/" + createByteArray(_positionGetter());
if (withOrientation) {
if (_orientationGetter) {
QString orientationString = createByteArray(_orientationGetter());
pathString += "/" + orientationString;
} else {
qDebug() << "Cannot add orientation to path without a getter for position."
<< "Call AdressManager::setOrientationGetter to pass a function that will return a glm::quat";
}
}
return pathString;
} else {
qDebug() << "Cannot create address path without a getter for position."
<< "Call AdressManager::setPositionGetter to pass a function that will return a const glm::vec3&";
return QString();
}
return pathString;
}
const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
@ -118,9 +147,26 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
QJsonObject domainObject = dataObject[ADDRESS_API_DOMAIN_KEY].toObject();
const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address";
QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString();
const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address";
emit possibleDomainChangeRequired(domainHostname);
if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) {
QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString();
emit possibleDomainChangeRequiredToHostname(domainHostname);
} else {
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
const QString DOMAIN_ID_KEY = "id";
QString domainIDString = domainObject[DOMAIN_ID_KEY].toString();
QUuid domainID(domainIDString);
emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID);
}
// set our current domain to the name that came back
const QString DOMAIN_NAME_KEY = "name";
_currentDomain = domainObject[DOMAIN_NAME_KEY].toString();
// take the path that came back
const QString LOCATION_KEY = "location";
@ -132,7 +178,7 @@ void AddressManager::handleAPIResponse(const QJsonObject &jsonObject) {
} else if (domainObject.contains(LOCATION_KEY)) {
returnedPath = domainObject[LOCATION_KEY].toObject()[LOCATION_PATH_KEY].toString();
}
bool shouldFaceViewpoint = dataObject.contains(ADDRESS_API_ONLINE_KEY);
if (!returnedPath.isEmpty()) {
@ -182,16 +228,25 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive);
if (hostnameRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(hostnameRegex.cap(0));
QString domainHostname = hostnameRegex.cap(0);
emit possibleDomainChangeRequiredToHostname(domainHostname);
emit lookupResultsFinished();
_currentDomain = domainHostname;
return true;
}
QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING);
if (ipAddressRegex.indexIn(lookupString) != -1) {
emit possibleDomainChangeRequired(ipAddressRegex.cap(0));
QString domainIPString = ipAddressRegex.cap(0);
emit possibleDomainChangeRequiredToHostname(domainIPString);
emit lookupResultsFinished();
_currentDomain = domainIPString;
return true;
}

View file

@ -21,15 +21,24 @@
static const QString HIFI_URL_SCHEME = "hifi";
typedef const glm::vec3& (*PositionGetter)();
typedef glm::quat (*OrientationGetter)();
class AddressManager : public QObject {
Q_OBJECT
public:
static AddressManager& getInstance();
static QString pathForPositionAndOrientation(const glm::vec3& position, bool hasOrientation = false,
const glm::quat& orientation = glm::quat());
const QUrl currentAddress();
const QString currentPath(bool withOrientation = true) const;
const QString& getCurrentDomain() const { return _currentDomain; }
void attemptPlaceNameLookup(const QString& lookupString);
void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; }
void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
public slots:
void handleLookupString(const QString& lookupString);
@ -40,11 +49,14 @@ signals:
void lookupResultsFinished();
void lookupResultIsOffline();
void lookupResultIsNotFound();
void possibleDomainChangeRequired(const QString& newHostname);
void possibleDomainChangeRequiredToHostname(const QString& newHostname);
void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID);
void locationChangeRequired(const glm::vec3& newPosition,
bool hasOrientationChange, const glm::quat& newOrientation,
bool shouldFaceLocation);
private:
AddressManager();
const JSONCallbackParameters& apiCallbackParameters();
bool handleUrl(const QUrl& lookupUrl);
@ -52,6 +64,10 @@ private:
bool handleNetworkAddress(const QString& lookupString);
bool handleRelativeViewpoint(const QString& pathSubsection, bool shouldFace = false);
bool handleUsername(const QString& lookupString);
QString _currentDomain;
PositionGetter _positionGetter;
OrientationGetter _orientationGetter;
};
#endif // hifi_AddressManager_h

View file

@ -27,6 +27,7 @@ public:
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
const OAuthAccessToken& getAccessToken() const { return _accessToken; }
void setAccessToken(const OAuthAccessToken& accessToken) { _accessToken = accessToken; }
void setAccessTokenFromJSON(const QJsonObject& jsonObject);
const QString& getUsername() const { return _username; }

View file

@ -25,6 +25,10 @@ DomainHandler::DomainHandler(QObject* parent) :
_uuid(),
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_iceDomainID(),
_iceClientID(),
_iceServerSockAddr(),
_icePeer(),
_isConnected(false),
_handshakeTimer(NULL),
_settingsObject(),
@ -35,7 +39,16 @@ DomainHandler::DomainHandler(QObject* parent) :
void DomainHandler::clearConnectionInfo() {
_uuid = QUuid();
_icePeer = NetworkPeer();
if (requiresICE()) {
// if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server
_sockAddr.setAddress(QHostAddress::Null);
}
_isConnected = false;
emit disconnectedFromDomain();
if (_handshakeTimer) {
@ -57,6 +70,7 @@ void DomainHandler::softReset() {
void DomainHandler::hardReset() {
softReset();
_iceDomainID = QUuid();
_hostname = QString();
_sockAddr.setAddress(QHostAddress::Null);
}
@ -123,6 +137,31 @@ void DomainHandler::setHostname(const QString& hostname) {
}
}
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
if (id != _uuid) {
// re-set the domain info to connect to new domain
hardReset();
_iceDomainID = id;
_iceServerSockAddr = HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT);
// refresh our ICE client UUID to something new
_iceClientID = QUuid::createUuid();
qDebug() << "ICE required to connect to domain via ice server at" << iceServerHostname;
}
}
void DomainHandler::activateICELocalSocket() {
_sockAddr = _icePeer.getLocalSocket();
_hostname = _sockAddr.getAddress().toString();
}
void DomainHandler::activateICEPublicSocket() {
_sockAddr = _icePeer.getPublicSocket();
_hostname = _sockAddr.getAddress().toString();
}
void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
for (int i = 0; i < hostInfo.addresses().size(); i++) {
if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
@ -152,21 +191,29 @@ void DomainHandler::setIsConnected(bool isConnected) {
}
}
void DomainHandler::requestDomainSettings() const {
if (_settingsObject.isEmpty()) {
// setup the URL required to grab settings JSON
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(_hostname);
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType());
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString();
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL));
connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished);
void DomainHandler::requestDomainSettings() {
NodeType_t owningNodeType = NodeList::getInstance()->getOwnerType();
if (owningNodeType == NodeType::Agent) {
// for now the agent nodes don't need any settings - this allows local assignment-clients
// to connect to a domain that is using automatic networking (since we don't have TCP hole punch yet)
_settingsObject = QJsonObject();
emit settingsReceived(_settingsObject);
} else {
if (_settingsObject.isEmpty()) {
// setup the URL required to grab settings JSON
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(_hostname);
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType());
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString();
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL));
connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished);
}
}
}
@ -216,3 +263,20 @@ void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirement
// initializeDTLSSession();
}
void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) {
QDataStream iceResponseStream(icePacket);
iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket));
NetworkPeer packetPeer;
iceResponseStream >> packetPeer;
if (packetPeer.getUUID() != _iceDomainID) {
qDebug() << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
} else {
qDebug() << "Received network peer object for domain -" << packetPeer;
_icePeer = packetPeer;
emit requestICEConnectionAttempt();
}
}

View file

@ -20,6 +20,7 @@
#include <QtNetwork/QHostInfo>
#include "HifiSockAddr.h"
#include "NetworkPeer.h"
const QString DEFAULT_DOMAIN_HOSTNAME = "sandbox.highfidelity.io";
@ -54,18 +55,30 @@ public:
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
const QUuid& getICEDomainID() const { return _iceDomainID; }
const QUuid& getICEClientID() const { return _iceClientID; }
bool requiresICE() const { return !_iceServerSockAddr.isNull(); }
const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; }
NetworkPeer& getICEPeer() { return _icePeer; }
void activateICELocalSocket();
void activateICEPublicSocket();
bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected);
bool hasSettings() const { return !_settingsObject.isEmpty(); }
void requestDomainSettings() const;
void requestDomainSettings();
const QJsonObject& getSettingsObject() const { return _settingsObject; }
void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket);
void processICEResponsePacket(const QByteArray& icePacket);
void softReset();
public slots:
void setHostname(const QString& hostname);
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
private slots:
void completedHostnameLookup(const QHostInfo& hostInfo);
@ -74,6 +87,7 @@ signals:
void hostnameChanged(const QString& hostname);
void connectedToDomain(const QString& hostname);
void disconnectedFromDomain();
void requestICEConnectionAttempt();
void settingsReceived(const QJsonObject& domainSettingsObject);
void settingsReceiveFail();
@ -85,6 +99,10 @@ private:
QString _hostname;
HifiSockAddr _sockAddr;
QUuid _assignmentUUID;
QUuid _iceDomainID;
QUuid _iceClientID;
HifiSockAddr _iceServerSockAddr;
NetworkPeer _icePeer;
bool _isConnected;
QTimer* _handshakeTimer;
QJsonObject _settingsObject;

View file

@ -459,6 +459,39 @@ unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeS
return n;
}
QByteArray LimitedNodeList::constructPingPacket(PingType_t pingType, bool isVerified, const QUuid& packetHeaderID) {
QByteArray pingPacket = byteArrayWithPopulatedHeader(isVerified ? PacketTypePing : PacketTypeUnverifiedPing,
packetHeaderID);
QDataStream packetStream(&pingPacket, QIODevice::Append);
packetStream << pingType;
packetStream << usecTimestampNow();
return pingPacket;
}
QByteArray LimitedNodeList::constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID) {
QDataStream pingPacketStream(pingPacket);
pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket));
PingType_t typeFromOriginalPing;
pingPacketStream >> typeFromOriginalPing;
quint64 timeFromOriginalPing;
pingPacketStream >> timeFromOriginalPing;
PacketType replyType = (packetTypeForPacket(pingPacket) == PacketTypePing)
? PacketTypePingReply : PacketTypeUnverifiedPingReply;
QByteArray replyPacket = byteArrayWithPopulatedHeader(replyType, packetHeaderID);
QDataStream packetStream(&replyPacket, QIODevice::Append);
packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow();
return replyPacket;
}
SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) {
if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) {
@ -618,3 +651,25 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) {
return false;
}
void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr,
QUuid headerID, const QUuid& connectionRequestID) {
if (headerID.isNull()) {
headerID = _sessionUUID;
}
QByteArray iceRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeIceServerHeartbeat, headerID);
QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append);
iceDataStream << _publicSockAddr << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort());
if (!connectionRequestID.isNull()) {
iceDataStream << connectionRequestID;
qDebug() << "Sending packet to ICE server to request connection info for peer with ID"
<< uuidStringWithoutCurlyBraces(connectionRequestID);
}
writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr);
}

View file

@ -52,6 +52,14 @@ typedef QSharedPointer<Node> SharedNodePointer;
typedef QHash<QUuid, SharedNodePointer> NodeHash;
Q_DECLARE_METATYPE(SharedNodePointer)
typedef quint8 PingType_t;
namespace PingType {
const PingType_t Agnostic = 0;
const PingType_t Local = 1;
const PingType_t Public = 2;
const PingType_t Symmetric = 3;
}
class LimitedNodeList : public QObject {
Q_OBJECT
public:
@ -104,8 +112,15 @@ public:
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
void resetPacketStats();
QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic, bool isVerified = true,
const QUuid& packetHeaderID = QUuid());
QByteArray constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID = QUuid());
virtual void sendSTUNRequest();
virtual bool processSTUNResponse(const QByteArray& packet);
void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr,
QUuid headerID = QUuid(), const QUuid& connectRequestID = QUuid());
public slots:
void reset();
void eraseAllNodes();

View file

@ -0,0 +1,99 @@
//
// NetworkPeer.cpp
// libraries/networking/src
//
// Created by Stephen Birarda on 2014-10-02.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <qdatetime.h>
#include <SharedUtil.h>
#include <UUID.h>
#include "NetworkPeer.h"
NetworkPeer::NetworkPeer() :
_uuid(),
_publicSocket(),
_localSocket(),
_wakeTimestamp(QDateTime::currentMSecsSinceEpoch()),
_lastHeardMicrostamp(usecTimestampNow()),
_connectionAttempts(0)
{
}
NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) :
_uuid(uuid),
_publicSocket(publicSocket),
_localSocket(localSocket),
_wakeTimestamp(QDateTime::currentMSecsSinceEpoch()),
_lastHeardMicrostamp(usecTimestampNow()),
_connectionAttempts(0)
{
}
NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) {
_uuid = otherPeer._uuid;
_publicSocket = otherPeer._publicSocket;
_localSocket = otherPeer._localSocket;
_wakeTimestamp = otherPeer._wakeTimestamp;
_lastHeardMicrostamp = otherPeer._lastHeardMicrostamp;
_connectionAttempts = otherPeer._connectionAttempts;
}
NetworkPeer& NetworkPeer::operator=(const NetworkPeer& otherPeer) {
NetworkPeer temp(otherPeer);
swap(temp);
return *this;
}
void NetworkPeer::swap(NetworkPeer& otherPeer) {
using std::swap;
swap(_uuid, otherPeer._uuid);
swap(_publicSocket, otherPeer._publicSocket);
swap(_localSocket, otherPeer._localSocket);
swap(_wakeTimestamp, otherPeer._wakeTimestamp);
swap(_lastHeardMicrostamp, otherPeer._lastHeardMicrostamp);
swap(_connectionAttempts, otherPeer._connectionAttempts);
}
QByteArray NetworkPeer::toByteArray() const {
QByteArray peerByteArray;
QDataStream peerStream(&peerByteArray, QIODevice::Append);
peerStream << *this;
return peerByteArray;
}
QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer) {
out << peer._uuid;
out << peer._publicSocket;
out << peer._localSocket;
return out;
}
QDataStream& operator>>(QDataStream& in, NetworkPeer& peer) {
in >> peer._uuid;
in >> peer._publicSocket;
in >> peer._localSocket;
return in;
}
QDebug operator<<(QDebug debug, const NetworkPeer &peer) {
debug << uuidStringWithoutCurlyBraces(peer.getUUID())
<< "- public:" << peer.getPublicSocket()
<< "- local:" << peer.getLocalSocket();
return debug;
}

Some files were not shown because too many files have changed in this diff Show more