Merge branch 'master' of https://github.com/highfidelity/hifi into temp0
Conflicts: interface/src/ui/MetavoxelEditor.cpp interface/src/ui/overlays/Sphere3DOverlay.cpp
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
224
examples/libraries/ExportMenu.js
Normal 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);
|
||||
};
|
200
examples/libraries/ModelImporter.js
Normal 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);
|
||||
};
|
82
examples/libraries/ToolTip.js
Normal 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();
|
62
examples/libraries/dataViewHelpers.js
Normal 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);
|
||||
};
|
||||
}
|
||||
|
255
examples/libraries/entityPropertyDialogBox.js
Normal 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;
|
||||
|
||||
}());
|
||||
|
1674
examples/libraries/entitySelectionTool.js
Normal file
119
examples/libraries/httpMultiPart.js
Normal 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;
|
||||
}());
|
701
examples/libraries/modelUploader.js
Normal 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;
|
||||
}());
|
143
examples/libraries/progressDialog.js
Normal 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;
|
||||
}());
|
66
examples/libraries/stringHelpers.js
Normal 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
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
9
ice-server/CMakeLists.txt
Normal 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()
|
167
ice-server/src/IceServer.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
39
ice-server/src/IceServer.h
Normal 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
|
@ -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();
|
||||
}
|
BIN
images/bg_hr.png
Before Width: | Height: | Size: 943 B |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 33 KiB |
BIN
images/hr.png
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.6 KiB |
52
index.html
|
@ -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 — Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="javascripts/scale.fix.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
184
interface/src/ui/overlays/Circle3DOverlay.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
43
interface/src/ui/overlays/Circle3DOverlay.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
76
interface/src/ui/overlays/Planar3DOverlay.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
45
interface/src/ui/overlays/Planar3DOverlay.h
Normal 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
|
114
interface/src/ui/overlays/Rectangle3DOverlay.cpp
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
27
interface/src/ui/overlays/Rectangle3DOverlay.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
console.log('This would be the main JS 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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
99
libraries/networking/src/NetworkPeer.cpp
Normal 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;
|
||||
}
|