mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 15:53:31 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into reset-hud-on-driving
This commit is contained in:
commit
5d806cba7f
120 changed files with 3516 additions and 2328 deletions
assignment-client/src/octree
domain-server/src
DomainGatekeeper.cppDomainServer.cppDomainServer.hDomainServerNodeData.hNodeConnectionData.cppNodeConnectionData.h
interface
resources
fonts
qml
controls-uit
dialogs
hifi
styles-uit
src
libraries
animation/src
audio/src
avatars/src
controllers/src/controllers
entities-renderer/src
EntityTreeRenderer.cppEntityTreeRenderer.hRenderableBoxEntityItem.cppRenderableBoxEntityItem.hRenderableEntityItem.cppRenderableEntityItem.hRenderablePolyVoxEntityItem.cppRenderableShapeEntityItem.cppRenderableShapeEntityItem.hRenderableSphereEntityItem.cppRenderableSphereEntityItem.h
entities/src
BoxEntityItem.cppBoxEntityItem.hEntityEditPacketSender.cppEntityEditPacketSender.hEntityItem.cppEntityItem.hEntityItemProperties.cppEntityItemProperties.hEntityPropertyFlags.hEntityScriptingInterface.cppEntityScriptingInterface.hEntityTree.cppEntityTypes.cppEntityTypes.hShapeEntityItem.cppShapeEntityItem.hSimulationOwner.hSphereEntityItem.cppSphereEntityItem.hZoneEntityItem.cppZoneEntityItem.h
fbx/src
gl/src/gl
gpu-gl/src/gpu/gl
gpu/src/gpu
networking/src
AddressManager.cppAddressManager.hDomainHandler.cppDomainHandler.hHifiSockAddr.cppNode.cppNodeList.cppReceivedMessage.cpp
udt
physics/src
procedural/src/procedural
render-utils/src
script-engine/src
shared/src
ui/src
scripts
developer/tests
system/assets/images
|
@ -153,7 +153,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
|
|||
qDebug() << " maxSize=" << maxSize;
|
||||
qDebug("OctreeInboundPacketProcessor::processPacket() %hhu "
|
||||
"payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d",
|
||||
packetType, message->getRawMessage(), message->getSize(), editData,
|
||||
(unsigned char)packetType, message->getRawMessage(), message->getSize(), editData,
|
||||
message->getPosition(), maxSize);
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
|
|||
if (debugProcessPacket) {
|
||||
qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu "
|
||||
"payload=%p payloadLength=%lld editData=%p payloadPosition=%lld",
|
||||
packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition());
|
||||
(unsigned char)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition());
|
||||
}
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
|
@ -208,7 +208,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
|
|||
}
|
||||
trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime);
|
||||
} else {
|
||||
qDebug("unknown packet ignored... packetType=%hhu", packetType);
|
||||
qDebug("unknown packet ignored... packetType=%hhu", (unsigned char)packetType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,9 +55,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
if (message->getSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QDataStream packetStream(message->getMessage());
|
||||
|
||||
|
||||
// read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it
|
||||
NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr());
|
||||
|
||||
|
@ -105,6 +105,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
nodeData->setSendingSockAddr(message->getSenderSockAddr());
|
||||
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
|
||||
nodeData->setPlaceName(nodeConnection.placeName);
|
||||
|
||||
// signal that we just connected a node so the DomainServer can get it a list
|
||||
// and broadcast its presence right away
|
||||
|
@ -150,6 +151,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||
nodeData->setWalletUUID(it->second.getWalletUUID());
|
||||
nodeData->setNodeVersion(it->second.getNodeVersion());
|
||||
nodeData->setWasAssigned(true);
|
||||
|
||||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||
_pendingAssignedNodes.erase(it);
|
||||
|
@ -282,7 +284,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
// set the edit rights for this user
|
||||
newNode->setIsAllowedEditor(isAllowedEditor);
|
||||
newNode->setCanRez(canRez);
|
||||
|
||||
|
||||
// grab the linked data for our new node so we can set the username
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
|
|
|
@ -462,7 +462,7 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
nodeList->startSTUNPublicSocketUpdate();
|
||||
} else {
|
||||
// send our heartbeat to data server so it knows what our network settings are
|
||||
sendHeartbeatToDataServer();
|
||||
sendHeartbeatToMetaverse();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
|
@ -471,7 +471,7 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
sendHeartbeatToDataServer();
|
||||
sendHeartbeatToMetaverse();
|
||||
}
|
||||
|
||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||
|
@ -480,7 +480,7 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
|
||||
|
||||
QTimer* dataHeartbeatTimer = new QTimer(this);
|
||||
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer()));
|
||||
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse()));
|
||||
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
|
||||
}
|
||||
|
||||
|
@ -678,6 +678,9 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
|
|||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet());
|
||||
|
||||
// update the connecting hostname in case it has changed
|
||||
nodeData->setPlaceName(nodeRequestData.placeName);
|
||||
|
||||
sendDomainListToNode(sendingNode, message->getSenderSockAddr());
|
||||
}
|
||||
|
||||
|
@ -1029,11 +1032,11 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
|
|||
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
|
||||
void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) {
|
||||
sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString());
|
||||
sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString());
|
||||
}
|
||||
|
||||
|
||||
void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
|
||||
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
@ -1056,20 +1059,34 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
|
|||
domainObject[RESTRICTED_ACCESS_FLAG] =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
|
||||
|
||||
// add the number of currently connected agent users
|
||||
int numConnectedAuthedUsers = 0;
|
||||
// figure out the breakdown of currently connected interface clients
|
||||
int numConnectedUnassigned = 0;
|
||||
QJsonObject userHostnames;
|
||||
|
||||
nodeList->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){
|
||||
if (node->getLinkedData() && !static_cast<DomainServerNodeData*>(node->getLinkedData())->getUsername().isEmpty()) {
|
||||
++numConnectedAuthedUsers;
|
||||
static const QString DEFAULT_HOSTNAME = "*";
|
||||
|
||||
nodeList->eachNode([&numConnectedUnassigned, &userHostnames](const SharedNodePointer& node) {
|
||||
if (node->getLinkedData()) {
|
||||
auto nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (!nodeData->wasAssigned()) {
|
||||
++numConnectedUnassigned;
|
||||
|
||||
// increment the count for this hostname (or the default if we don't have one)
|
||||
auto hostname = nodeData->getPlaceName().isEmpty() ? DEFAULT_HOSTNAME : nodeData->getPlaceName();
|
||||
userHostnames[hostname] = userHostnames[hostname].toInt() + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
||||
const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
|
||||
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
||||
static const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
|
||||
static const QString HEARTBEAT_USER_HOSTNAMES_KEY = "user_hostnames";
|
||||
|
||||
QJsonObject heartbeatObject;
|
||||
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers;
|
||||
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedUnassigned;
|
||||
heartbeatObject[HEARTBEAT_USER_HOSTNAMES_KEY] = userHostnames;
|
||||
|
||||
domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject;
|
||||
|
||||
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
||||
|
|
|
@ -71,7 +71,7 @@ private slots:
|
|||
void sendPendingTransactionsToServer();
|
||||
|
||||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); }
|
||||
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
|
||||
void sendHeartbeatToIceServer();
|
||||
|
||||
void handleConnectedNode(SharedNodePointer newNode);
|
||||
|
@ -103,7 +103,7 @@ private:
|
|||
|
||||
void setupAutomaticNetworking();
|
||||
void setupICEHeartbeatForFullNetworking();
|
||||
void sendHeartbeatToDataServer(const QString& networkAddress);
|
||||
void sendHeartbeatToMetaverse(const QString& networkAddress);
|
||||
|
||||
void randomizeICEServerAddress(bool shouldTriggerHostLookup);
|
||||
|
||||
|
|
|
@ -56,6 +56,12 @@ public:
|
|||
|
||||
void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue);
|
||||
void removeOverrideForKey(const QString& key, const QString& value);
|
||||
|
||||
const QString& getPlaceName() { return _placeName; }
|
||||
void setPlaceName(const QString& placeName) { _placeName = placeName; }
|
||||
|
||||
bool wasAssigned() const { return _wasAssigned; };
|
||||
void setWasAssigned(bool wasAssigned) { _wasAssigned = wasAssigned; }
|
||||
|
||||
private:
|
||||
QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats);
|
||||
|
@ -75,6 +81,10 @@ private:
|
|||
bool _isAuthenticated = true;
|
||||
NodeSet _nodeInterestSet;
|
||||
QString _nodeVersion;
|
||||
|
||||
QString _placeName;
|
||||
|
||||
bool _wasAssigned { false };
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerNodeData_h
|
||||
|
|
|
@ -23,7 +23,7 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c
|
|||
|
||||
dataStream >> newHeader.nodeType
|
||||
>> newHeader.publicSockAddr >> newHeader.localSockAddr
|
||||
>> newHeader.interestList;
|
||||
>> newHeader.interestList >> newHeader.placeName;
|
||||
|
||||
newHeader.senderSockAddr = senderSockAddr;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
HifiSockAddr localSockAddr;
|
||||
HifiSockAddr senderSockAddr;
|
||||
QList<NodeType_t> interestList;
|
||||
QString placeName;
|
||||
};
|
||||
|
||||
|
||||
|
|
BIN
interface/resources/fonts/FiraSans-Regular.ttf
Normal file
BIN
interface/resources/fonts/FiraSans-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
|
@ -21,7 +21,6 @@ import "../hifi/models"
|
|||
TableView {
|
||||
id: tableView
|
||||
|
||||
// property var tableModel: ListModel { }
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
|
||||
|
@ -46,7 +45,7 @@ TableView {
|
|||
|
||||
RalewayRegular {
|
||||
id: textHeader
|
||||
size: hifi.fontSizes.tableText
|
||||
size: hifi.fontSizes.tableHeading
|
||||
color: hifi.colors.lightGrayText
|
||||
text: styleData.value
|
||||
anchors {
|
||||
|
@ -87,7 +86,7 @@ TableView {
|
|||
bottomMargin: 3 // ""
|
||||
}
|
||||
radius: 3
|
||||
color: hifi.colors.tableScrollHandle
|
||||
color: hifi.colors.tableScrollHandleDark
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +106,7 @@ TableView {
|
|||
margins: 1 // Shrink
|
||||
}
|
||||
radius: 4
|
||||
color: hifi.colors.tableScrollBackground
|
||||
color: hifi.colors.tableScrollBackgroundDark
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ FocusScope {
|
|||
HiFiGlyphs {
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: -8
|
||||
topMargin: -11
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
size: hifi.dimensions.spinnerSize
|
||||
|
|
|
@ -17,8 +17,9 @@ import "../styles-uit"
|
|||
|
||||
Original.Button {
|
||||
property int color: 0
|
||||
property int colorScheme: hifi.colorShemes.light
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
property string glyph: ""
|
||||
property int size: 32
|
||||
|
||||
width: 120
|
||||
height: 28
|
||||
|
@ -65,7 +66,13 @@ Original.Button {
|
|||
: hifi.buttons.disabledTextColor[control.colorScheme]
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors {
|
||||
// Tweak horizontal alignment so that it looks right.
|
||||
left: parent.left
|
||||
leftMargin: -0.5
|
||||
}
|
||||
text: control.glyph
|
||||
size: control.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ SpinBox {
|
|||
incrementControl: HiFiGlyphs {
|
||||
id: incrementButton
|
||||
text: hifi.glyphs.caratUp
|
||||
x: 6
|
||||
x: 10
|
||||
y: 1
|
||||
size: hifi.dimensions.spinnerSize
|
||||
color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
|
||||
|
@ -64,8 +64,8 @@ SpinBox {
|
|||
|
||||
decrementControl: HiFiGlyphs {
|
||||
text: hifi.glyphs.caratDn
|
||||
x: 6
|
||||
y: -3
|
||||
x: 10
|
||||
y: -1
|
||||
size: hifi.dimensions.spinnerSize
|
||||
color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
|
||||
}
|
||||
|
|
|
@ -17,20 +17,68 @@ import "../styles-uit"
|
|||
TableView {
|
||||
id: tableView
|
||||
|
||||
property var tableModel: ListModel { }
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
property bool expandSelectedRow: false
|
||||
|
||||
model: tableModel
|
||||
|
||||
TableViewColumn {
|
||||
role: "name"
|
||||
}
|
||||
|
||||
anchors { left: parent.left; right: parent.right }
|
||||
model: ListModel { }
|
||||
|
||||
headerVisible: false
|
||||
headerDelegate: Item { } // Fix OSX QML bug that displays scrollbar starting too low.
|
||||
headerDelegate: Rectangle {
|
||||
height: hifi.dimensions.tableHeaderHeight
|
||||
color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
|
||||
|
||||
RalewayRegular {
|
||||
id: titleText
|
||||
text: styleData.value
|
||||
size: hifi.fontSizes.tableHeading
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
id: titleSort
|
||||
text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
size: hifi.fontSizes.tableHeadingIcon
|
||||
anchors {
|
||||
left: titleText.right
|
||||
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: titleText.verticalCenter
|
||||
}
|
||||
visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
topMargin: 1
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 2
|
||||
}
|
||||
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
|
||||
visible: styleData.column > 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
|
||||
}
|
||||
}
|
||||
|
||||
// Use rectangle to draw border with rounded corners.
|
||||
frameVisible: false
|
||||
|
@ -50,8 +98,10 @@ TableView {
|
|||
|
||||
style: TableViewStyle {
|
||||
// Needed in order for rows to keep displaying rows after end of table entries.
|
||||
backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven
|
||||
alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
|
||||
backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
|
||||
alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
|
||||
|
||||
padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0
|
||||
|
||||
handle: Item {
|
||||
id: scrollbarHandle
|
||||
|
@ -59,33 +109,38 @@ TableView {
|
|||
Rectangle {
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 3
|
||||
bottomMargin: 3 // ""
|
||||
leftMargin: 2 // Move it right
|
||||
rightMargin: -2 // ""
|
||||
topMargin: 3 // Shrink vertically
|
||||
bottomMargin: 3 // ""
|
||||
}
|
||||
radius: 3
|
||||
color: hifi.colors.tableScrollHandle
|
||||
color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark
|
||||
}
|
||||
}
|
||||
|
||||
scrollBarBackground: Item {
|
||||
implicitWidth: 10
|
||||
implicitWidth: 9
|
||||
Rectangle {
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: -1 // Expand
|
||||
topMargin: headerVisible ? -hifi.dimensions.tableHeaderHeight : -1
|
||||
}
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
}
|
||||
color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 1 // Shrink
|
||||
Rectangle {
|
||||
// Extend header bottom border
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.tableHeaderHeight - 1
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: 1
|
||||
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
|
||||
visible: headerVisible
|
||||
}
|
||||
radius: 4
|
||||
color: hifi.colors.tableScrollBackground
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,85 +154,11 @@ TableView {
|
|||
}
|
||||
|
||||
rowDelegate: Rectangle {
|
||||
height: (styleData.selected ? 1.8 : 1) * hifi.dimensions.tableRowHeight
|
||||
height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight
|
||||
color: styleData.selected
|
||||
? hifi.colors.primaryHighlight
|
||||
: tableView.isLightColorScheme
|
||||
? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd)
|
||||
: (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd)
|
||||
}
|
||||
|
||||
itemDelegate: Item {
|
||||
anchors {
|
||||
left: parent ? parent.left : undefined
|
||||
leftMargin: hifi.dimensions.tablePadding
|
||||
right: parent ? parent.right : undefined
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
}
|
||||
|
||||
FiraSansSemiBold {
|
||||
id: textItem
|
||||
text: styleData.value
|
||||
size: hifi.fontSizes.tableText
|
||||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
topMargin: 3
|
||||
}
|
||||
|
||||
// FIXME: Put reload item in tableModel passed in from RunningScripts.
|
||||
HiFiGlyphs {
|
||||
id: reloadButton
|
||||
text: hifi.glyphs.reloadSmall
|
||||
color: reloadButtonArea.pressed ? hifi.colors.white : parent.color
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: stopButton.left
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: reloadButtonArea
|
||||
anchors { fill: parent; margins: -2 }
|
||||
onClicked: reloadScript(model.url)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Put stop item in tableModel passed in from RunningScripts.
|
||||
HiFiGlyphs {
|
||||
id: stopButton
|
||||
text: hifi.glyphs.closeSmall
|
||||
color: stopButtonArea.pressed ? hifi.colors.white : parent.color
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: stopButtonArea
|
||||
anchors { fill: parent; margins: -2 }
|
||||
onClicked: stopScript(model.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Automatically use aux. information from tableModel
|
||||
FiraSansSemiBold {
|
||||
text: tableModel.get(styleData.row) ? tableModel.get(styleData.row).url : ""
|
||||
elide: Text.ElideMiddle
|
||||
size: hifi.fontSizes.tableText
|
||||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
anchors {
|
||||
top: textItem.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
visible: styleData.selected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Table.qml
|
||||
// Tree.qml
|
||||
//
|
||||
// Created by David Rowe on 17 Feb 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
|
@ -85,27 +85,18 @@ TreeView {
|
|||
bottomMargin: 3 // ""
|
||||
}
|
||||
radius: 3
|
||||
color: hifi.colors.tableScrollHandle
|
||||
color: hifi.colors.tableScrollHandleDark
|
||||
}
|
||||
}
|
||||
|
||||
scrollBarBackground: Item {
|
||||
implicitWidth: 10
|
||||
implicitWidth: 9
|
||||
Rectangle {
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: -1 // Expand
|
||||
}
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 1 // Shrink
|
||||
}
|
||||
radius: 4
|
||||
color: hifi.colors.tableScrollBackground
|
||||
color: hifi.colors.tableBackgroundDark
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
import QtQuick 2.0
|
||||
//
|
||||
// FileDialog.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 14 Jan 2016
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import Qt.labs.folderlistmodel 2.1
|
||||
import Qt.labs.settings 1.0
|
||||
|
@ -6,17 +16,20 @@ import QtQuick.Controls.Styles 1.4
|
|||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
|
||||
import ".."
|
||||
import "../windows"
|
||||
import "../styles"
|
||||
import "../controls" as VrControls
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
import "../windows-uit"
|
||||
|
||||
import "fileDialog"
|
||||
|
||||
//FIXME implement shortcuts for favorite location
|
||||
ModalWindow {
|
||||
id: root
|
||||
resizable: true
|
||||
width: 640
|
||||
height: 480
|
||||
//resizable: true
|
||||
implicitWidth: 640
|
||||
implicitHeight: 480
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Settings {
|
||||
category: "FileDialog"
|
||||
|
@ -30,12 +43,14 @@ ModalWindow {
|
|||
// Set from OffscreenUi::getOpenFile()
|
||||
property alias caption: root.title;
|
||||
// Set from OffscreenUi::getOpenFile()
|
||||
property alias dir: model.folder;
|
||||
property alias dir: fileTableModel.folder;
|
||||
// Set from OffscreenUi::getOpenFile()
|
||||
property alias filter: selectionType.filtersString;
|
||||
// Set from OffscreenUi::getOpenFile()
|
||||
property int options; // <-- FIXME unused
|
||||
|
||||
property string iconText: text !== "" ? hifi.glyphs.scriptUpload : ""
|
||||
property int iconSize: 40
|
||||
|
||||
property bool selectDirectory: false;
|
||||
property bool showHidden: false;
|
||||
|
@ -46,77 +61,120 @@ ModalWindow {
|
|||
property alias model: fileTableView.model
|
||||
property var drives: helper.drives()
|
||||
|
||||
property int titleWidth: 0
|
||||
|
||||
signal selectedFile(var file);
|
||||
signal canceled();
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Helper " + helper + " drives " + drives)
|
||||
drivesSelector.onCurrentTextChanged.connect(function(){
|
||||
root.dir = helper.pathToUrl(drivesSelector.currentText);
|
||||
})
|
||||
|
||||
// HACK: The following lines force the model to initialize properly such that the go-up button
|
||||
// works properly from the initial screen.
|
||||
var initialFolder = folderListModel.folder;
|
||||
fileTableModel.folder = helper.pathToUrl(drives[0]);
|
||||
fileTableModel.folder = initialFolder;
|
||||
|
||||
iconText = root.title !== "" ? hifi.glyphs.scriptUpload : "";
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "white"
|
||||
Item {
|
||||
clip: true
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
anchors.margins: 0
|
||||
|
||||
Row {
|
||||
id: navControls
|
||||
anchors { left: parent.left; top: parent.top; margins: 8 }
|
||||
spacing: 8
|
||||
// FIXME implement back button
|
||||
//VrControls.ButtonAwesome {
|
||||
// id: backButton
|
||||
// text: "\uf0a8"
|
||||
// size: currentDirectory.height
|
||||
// enabled: d.backStack.length != 0
|
||||
// MouseArea { anchors.fill: parent; onClicked: d.navigateBack() }
|
||||
//}
|
||||
VrControls.ButtonAwesome {
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentMargin.y
|
||||
left: parent.left
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
||||
GlyphButton {
|
||||
id: upButton
|
||||
enabled: model.parentFolder && model.parentFolder !== ""
|
||||
text: "\uf0aa"
|
||||
size: 32
|
||||
glyph: hifi.glyphs.levelUp
|
||||
width: height
|
||||
size: 30
|
||||
enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
|
||||
onClicked: d.navigateUp();
|
||||
}
|
||||
VrControls.ButtonAwesome {
|
||||
|
||||
GlyphButton {
|
||||
id: homeButton
|
||||
property var destination: helper.home();
|
||||
glyph: hifi.glyphs.home
|
||||
size: 28
|
||||
width: height
|
||||
enabled: d.homeDestination ? true : false
|
||||
text: "\uf015"
|
||||
size: 32
|
||||
onClicked: d.navigateHome();
|
||||
}
|
||||
|
||||
VrControls.ComboBox {
|
||||
id: drivesSelector
|
||||
width: 48
|
||||
height: homeButton.height
|
||||
model: drives
|
||||
visible: drives.length > 1
|
||||
currentIndex: 0
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: currentDirectory
|
||||
height: homeButton.height
|
||||
style: TextFieldStyle { renderType: Text.QtRendering }
|
||||
anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 }
|
||||
property var lastValidFolder: helper.urlToPath(model.folder)
|
||||
onLastValidFolderChanged: text = lastValidFolder;
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pointSize: 14
|
||||
font.bold: true
|
||||
ComboBox {
|
||||
id: pathSelector
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentMargin.y
|
||||
left: navControls.right
|
||||
leftMargin: hifi.dimensions.contentSpacing.x
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
// FIXME add support auto-completion
|
||||
onAccepted: {
|
||||
if (!helper.validFolder(text)) {
|
||||
text = lastValidFolder;
|
||||
return
|
||||
property var lastValidFolder: helper.urlToPath(fileTableModel.folder)
|
||||
|
||||
function calculatePathChoices(folder) {
|
||||
var folders = folder.split("/"),
|
||||
choices = [],
|
||||
i, length;
|
||||
|
||||
if (folders[folders.length - 1] === "") {
|
||||
folders.pop();
|
||||
}
|
||||
|
||||
choices.push(folders[0]);
|
||||
|
||||
for (i = 1, length = folders.length; i < length; i++) {
|
||||
choices.push(choices[i - 1] + "/" + folders[i]);
|
||||
}
|
||||
|
||||
if (folders[0] === "") {
|
||||
// Special handling for OSX root dir.
|
||||
choices[0] = "/";
|
||||
}
|
||||
|
||||
choices.reverse();
|
||||
|
||||
if (drives && drives.length > 1) {
|
||||
choices.push("This PC");
|
||||
}
|
||||
|
||||
if (choices.length > 0) {
|
||||
pathSelector.model = choices;
|
||||
}
|
||||
}
|
||||
|
||||
onLastValidFolderChanged: {
|
||||
var folder = d.capitalizeDrive(lastValidFolder);
|
||||
calculatePathChoices(folder);
|
||||
}
|
||||
|
||||
onCurrentTextChanged: {
|
||||
var folder = currentText;
|
||||
|
||||
if (/^[a-zA-z]:$/.test(folder)) {
|
||||
folder = "file:///" + folder + "/";
|
||||
} else if (folder === "This PC") {
|
||||
folder = "file:///";
|
||||
} else {
|
||||
folder = helper.pathToUrl(folder);
|
||||
}
|
||||
|
||||
if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
|
||||
fileTableModel.folder = folder;
|
||||
}
|
||||
model.folder = helper.pathToUrl(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,67 +185,323 @@ ModalWindow {
|
|||
property bool currentSelectionIsFolder;
|
||||
property var backStack: []
|
||||
property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); }
|
||||
property var modelConnection: Connections { target: model; onFolderChanged: d.update(); }
|
||||
property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); }
|
||||
property var homeDestination: helper.home();
|
||||
Component.onCompleted: update();
|
||||
|
||||
function capitalizeDrive(path) {
|
||||
// Consistently capitalize drive letter for Windows.
|
||||
if (/[a-zA-Z]:/.test(path)) {
|
||||
return path.charAt(0).toUpperCase() + path.slice(1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function update() {
|
||||
var row = fileTableView.currentRow;
|
||||
if (row === -1 && root.selectDirectory) {
|
||||
currentSelectionUrl = fileTableView.model.folder;
|
||||
currentSelectionIsFolder = true;
|
||||
|
||||
if (row === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentSelectionUrl = fileTableView.model.get(row, "fileURL");
|
||||
currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
|
||||
currentSelectionIsFolder = fileTableView.model.isFolder(row);
|
||||
if (root.selectDirectory || !currentSelectionIsFolder) {
|
||||
currentSelection.text = helper.urlToPath(currentSelectionUrl);
|
||||
currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
|
||||
} else {
|
||||
currentSelection.text = ""
|
||||
currentSelection.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
function navigateUp() {
|
||||
if (model.parentFolder && model.parentFolder !== "") {
|
||||
model.folder = model.parentFolder
|
||||
if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") {
|
||||
fileTableModel.folder = fileTableModel.parentFolder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function navigateHome() {
|
||||
model.folder = homeDestination;
|
||||
fileTableModel.folder = homeDestination;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
FileTableView {
|
||||
FolderListModel {
|
||||
id: folderListModel
|
||||
nameFilters: selectionType.currentFilter
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
Component.onCompleted: {
|
||||
showFiles = !root.selectDirectory
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
fileTableModel.update(); // Update once the data from the folder change is available.
|
||||
}
|
||||
|
||||
function getItem(index, field) {
|
||||
return get(index, field);
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
// Emulates FolderListModel but contains drive data.
|
||||
id: driveListModel
|
||||
|
||||
property int count: 1
|
||||
|
||||
Component.onCompleted: initialize();
|
||||
|
||||
function initialize() {
|
||||
var drive,
|
||||
i;
|
||||
|
||||
count = drives.length;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
drive = drives[i].slice(0, -1); // Remove trailing "/".
|
||||
append({
|
||||
fileName: drive,
|
||||
fileModified: new Date(0),
|
||||
fileSize: 0,
|
||||
filePath: drive + "/",
|
||||
fileIsDir: true,
|
||||
fileNameSort: drive.toLowerCase()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getItem(index, field) {
|
||||
return get(index)[field];
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: fileTableModel
|
||||
|
||||
// FolderListModel has a couple of problems:
|
||||
// 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757
|
||||
// 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901
|
||||
//
|
||||
// To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with
|
||||
// drive information when viewing at the computer level.
|
||||
|
||||
property var folder
|
||||
property int sortOrder: Qt.AscendingOrder
|
||||
property int sortColumn: 0
|
||||
property var model: folderListModel
|
||||
property string parentFolder: calculateParentFolder();
|
||||
|
||||
readonly property string rootFolder: "file:///"
|
||||
|
||||
function calculateParentFolder() {
|
||||
if (model === folderListModel) {
|
||||
if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) {
|
||||
return rootFolder;
|
||||
} else {
|
||||
return folderListModel.parentFolder;
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
if (folder === rootFolder) {
|
||||
model = driveListModel;
|
||||
update();
|
||||
} else {
|
||||
var needsUpdate = model === driveListModel && folder === folderListModel.folder;
|
||||
|
||||
model = folderListModel;
|
||||
folderListModel.folder = folder;
|
||||
|
||||
if (needsUpdate) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isFolder(row) {
|
||||
if (row === -1) {
|
||||
return false;
|
||||
}
|
||||
return get(row).fileIsDir;
|
||||
}
|
||||
|
||||
function update() {
|
||||
var dataFields = ["fileName", "fileModified", "fileSize"],
|
||||
sortFields = ["fileNameSort", "fileModified", "fileSize"],
|
||||
dataField = dataFields[sortColumn],
|
||||
sortField = sortFields[sortColumn],
|
||||
sortValue,
|
||||
fileName,
|
||||
fileIsDir,
|
||||
comparisonFunction,
|
||||
lower,
|
||||
middle,
|
||||
upper,
|
||||
rows = 0,
|
||||
i;
|
||||
|
||||
clear();
|
||||
|
||||
comparisonFunction = sortOrder === Qt.AscendingOrder
|
||||
? function(a, b) { return a < b; }
|
||||
: function(a, b) { return a > b; }
|
||||
|
||||
for (i = 0; i < model.count; i++) {
|
||||
fileName = model.getItem(i, "fileName");
|
||||
fileIsDir = model.getItem(i, "fileIsDir");
|
||||
|
||||
sortValue = model.getItem(i, dataField);
|
||||
if (dataField === "fileName") {
|
||||
// Directories first by prefixing a "*".
|
||||
// Case-insensitive.
|
||||
sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase();
|
||||
}
|
||||
|
||||
lower = 0;
|
||||
upper = rows;
|
||||
while (lower < upper) {
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
var lessThan;
|
||||
if (comparisonFunction(sortValue, get(middle)[sortField])) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
lessThan = false;
|
||||
lower = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
insert(lower, {
|
||||
fileName: fileName,
|
||||
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
|
||||
fileSize: model.getItem(i, "fileSize"),
|
||||
filePath: model.getItem(i, "filePath"),
|
||||
fileIsDir: fileIsDir,
|
||||
fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase()
|
||||
});
|
||||
|
||||
rows++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Table {
|
||||
id: fileTableView
|
||||
anchors { left: parent.left; right: parent.right; top: currentDirectory.bottom; bottom: currentSelection.top; margins: 8 }
|
||||
colorScheme: hifi.colorSchemes.light
|
||||
anchors {
|
||||
top: navControls.bottom
|
||||
topMargin: hifi.dimensions.contentSpacing.y
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: currentSelection.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
|
||||
}
|
||||
headerVisible: !selectDirectory
|
||||
onDoubleClicked: navigateToRow(row);
|
||||
focus: true
|
||||
Keys.onReturnPressed: navigateToCurrentRow();
|
||||
Keys.onEnterPressed: navigateToCurrentRow();
|
||||
model: FolderListModel {
|
||||
id: model
|
||||
nameFilters: selectionType.currentFilter
|
||||
showDirsFirst: true
|
||||
showDotAndDotDot: false
|
||||
showFiles: !root.selectDirectory
|
||||
// For some reason, declaring these bindings directly in the targets doesn't
|
||||
// work for setting the initial state
|
||||
Component.onCompleted: {
|
||||
currentDirectory.lastValidFolder = Qt.binding(function() { return helper.urlToPath(model.folder); });
|
||||
upButton.enabled = Qt.binding(function() { return (model.parentFolder && model.parentFolder != "") ? true : false; });
|
||||
showFiles = !root.selectDirectory
|
||||
|
||||
sortIndicatorColumn: 0
|
||||
sortIndicatorOrder: Qt.AscendingOrder
|
||||
sortIndicatorVisible: true
|
||||
|
||||
model: fileTableModel
|
||||
|
||||
function updateSort() {
|
||||
model.sortOrder = sortIndicatorOrder;
|
||||
model.sortColumn = sortIndicatorColumn;
|
||||
model.update();
|
||||
}
|
||||
|
||||
onSortIndicatorColumnChanged: { updateSort(); }
|
||||
|
||||
onSortIndicatorOrderChanged: { updateSort(); }
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus && currentRow == -1) {
|
||||
fileTableView.selection.select(0)
|
||||
}
|
||||
onFolderChanged: {
|
||||
fileTableView.selection.clear();
|
||||
fileTableView.selection.select(0);
|
||||
fileTableView.currentRow = 0;
|
||||
}
|
||||
|
||||
itemDelegate: Item {
|
||||
clip: true
|
||||
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
|
||||
|
||||
FiraSansSemiBold {
|
||||
text: getText();
|
||||
elide: styleData.elideMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.tablePadding
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
size: hifi.fontSizes.tableText
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
|
||||
? firaSansSemiBold.name : firaSansRegular.name
|
||||
|
||||
function getText() {
|
||||
if (styleData.row === -1) {
|
||||
return styleData.value;
|
||||
}
|
||||
|
||||
switch (styleData.column) {
|
||||
case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value;
|
||||
case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value);
|
||||
default: return styleData.value;
|
||||
}
|
||||
}
|
||||
function formatSize(size) {
|
||||
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
|
||||
var suffixIndex = 0
|
||||
while ((size / 1024.0) > 1.1) {
|
||||
size /= 1024.0;
|
||||
++suffixIndex;
|
||||
}
|
||||
|
||||
size = Math.round(size*1000)/1000;
|
||||
size = size.toLocaleString()
|
||||
|
||||
return size + " " + suffixes[suffixIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
id: fileNameColumn
|
||||
role: "fileName"
|
||||
title: "Name"
|
||||
width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
|
||||
movable: false
|
||||
resizable: true
|
||||
}
|
||||
TableViewColumn {
|
||||
id: fileMofifiedColumn
|
||||
role: "fileModified"
|
||||
title: "Date"
|
||||
width: 0.3 * fileTableView.width
|
||||
movable: false
|
||||
resizable: true
|
||||
visible: !selectDirectory
|
||||
}
|
||||
TableViewColumn {
|
||||
role: "fileSize"
|
||||
title: "Size"
|
||||
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
|
||||
movable: false
|
||||
resizable: true
|
||||
visible: !selectDirectory
|
||||
}
|
||||
|
||||
function navigateToRow(row) {
|
||||
currentRow = row;
|
||||
navigateToCurrentRow();
|
||||
|
@ -196,9 +510,9 @@ ModalWindow {
|
|||
function navigateToCurrentRow() {
|
||||
var row = fileTableView.currentRow
|
||||
var isFolder = model.isFolder(row);
|
||||
var file = model.get(row, "fileURL");
|
||||
var file = model.get(row).filePath;
|
||||
if (isFolder) {
|
||||
fileTableView.model.folder = file
|
||||
fileTableView.model.folder = helper.pathToUrl(file);
|
||||
} else {
|
||||
okAction.trigger();
|
||||
}
|
||||
|
@ -213,7 +527,7 @@ ModalWindow {
|
|||
var newPrefix = prefix + event.text.toLowerCase();
|
||||
var matchedIndex = -1;
|
||||
for (var i = 0; i < model.count; ++i) {
|
||||
var name = model.get(i, "fileName").toLowerCase();
|
||||
var name = model.get(i).fileName.toLowerCase();
|
||||
if (0 === name.indexOf(newPrefix)) {
|
||||
matchedIndex = i;
|
||||
break;
|
||||
|
@ -254,14 +568,19 @@ ModalWindow {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: currentSelection
|
||||
style: TextFieldStyle { renderType: Text.QtRendering }
|
||||
anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top }
|
||||
label: "Path:"
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: selectionType.visible ? selectionType.left: parent.right
|
||||
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
|
||||
bottom: buttonRow.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
readOnly: !root.saveDialog
|
||||
activeFocusOnTab: !readOnly
|
||||
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
||||
|
@ -270,27 +589,34 @@ ModalWindow {
|
|||
|
||||
FileTypeSelection {
|
||||
id: selectionType
|
||||
anchors { bottom: buttonRow.top; bottomMargin: 8; right: parent.right; rightMargin: 8; left: buttonRow.left }
|
||||
visible: !selectDirectory
|
||||
anchors {
|
||||
top: currentSelection.top
|
||||
left: buttonRow.left
|
||||
right: parent.right
|
||||
}
|
||||
visible: !selectDirectory && filtersCount > 1
|
||||
KeyNavigation.left: fileTableView
|
||||
KeyNavigation.right: openButton
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
spacing: 8
|
||||
anchors {
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.y
|
||||
|
||||
Button {
|
||||
id: openButton
|
||||
color: hifi.buttons.blue
|
||||
action: okAction
|
||||
Keys.onReturnPressed: okAction.trigger()
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: selectionType
|
||||
KeyNavigation.right: cancelButton
|
||||
}
|
||||
|
||||
Button {
|
||||
id: cancelButton
|
||||
action: cancelAction
|
||||
|
@ -385,5 +711,3 @@ ModalWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
TableView {
|
||||
id: root
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus && currentRow == -1) {
|
||||
root.selection.select(0)
|
||||
}
|
||||
}
|
||||
|
||||
itemDelegate: Component {
|
||||
Item {
|
||||
clip: true
|
||||
Text {
|
||||
x: 3
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: styleData.textColor
|
||||
elide: styleData.elideMode
|
||||
text: getText();
|
||||
font.italic: root.model.get(styleData.row, "fileIsDir") ? true : false
|
||||
|
||||
function getText() {
|
||||
switch (styleData.column) {
|
||||
//case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss");
|
||||
case 2: return root.model.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value);
|
||||
default: return styleData.value;
|
||||
}
|
||||
}
|
||||
function formatSize(size) {
|
||||
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
|
||||
var suffixIndex = 0
|
||||
while ((size / 1024.0) > 1.1) {
|
||||
size /= 1024.0;
|
||||
++suffixIndex;
|
||||
}
|
||||
|
||||
size = Math.round(size*1000)/1000;
|
||||
size = size.toLocaleString()
|
||||
|
||||
return size + " " + suffixes[suffixIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
role: "fileName"
|
||||
title: "Name"
|
||||
width: 400
|
||||
}
|
||||
TableViewColumn {
|
||||
role: "fileModified"
|
||||
title: "Date Modified"
|
||||
width: 200
|
||||
}
|
||||
TableViewColumn {
|
||||
role: "fileSize"
|
||||
title: "Size"
|
||||
width: 200
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,11 +1,22 @@
|
|||
//
|
||||
// FileTypeSelection.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 29 Jan 2016
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "../../controls" as VrControls
|
||||
import "../../controls-uit"
|
||||
|
||||
VrControls.ComboBox {
|
||||
ComboBox {
|
||||
id: root
|
||||
property string filtersString: "All Files (*.*)";
|
||||
property var currentFilter: [ "*.*" ];
|
||||
property int filtersCount: filtersString.split(';;').length
|
||||
|
||||
// Per http://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName the string can contain
|
||||
// multiple filters separated by semicolons
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
QtObject {
|
||||
readonly property string aboutApp: "About Interface";
|
||||
readonly property string addRemoveFriends: "Add/Remove Friends...";
|
||||
readonly property string addressBar: "Show Address Bar";
|
||||
readonly property string animations: "Animations...";
|
||||
readonly property string animDebugDrawAnimPose: "Debug Draw Animation";
|
||||
readonly property string animDebugDrawDefaultPose: "Debug Draw Default Pose";
|
||||
readonly property string animDebugDrawPosition: "Debug Draw Position";
|
||||
readonly property string antialiasing: "Antialiasing";
|
||||
readonly property string assetMigration: "ATP Asset Migration";
|
||||
readonly property string assetServer: "Asset Server";
|
||||
readonly property string atmosphere: "Atmosphere";
|
||||
readonly property string attachments: "Attachments...";
|
||||
readonly property string audioNetworkStats: "Audio Network Stats";
|
||||
readonly property string audioNoiseReduction: "Audio Noise Reduction";
|
||||
readonly property string audioScope: "Show Scope";
|
||||
readonly property string audioScopeFiftyFrames: "Fifty";
|
||||
readonly property string audioScopeFiveFrames: "Five";
|
||||
readonly property string audioScopeFrames: "Display Frames";
|
||||
readonly property string audioScopePause: "Pause Scope";
|
||||
readonly property string audioScopeTwentyFrames: "Twenty";
|
||||
readonly property string audioStatsShowInjectedStreams: "Audio Stats Show Injected Streams";
|
||||
readonly property string audioTools: "Show Level Meter";
|
||||
readonly property string autoMuteAudio: "Auto Mute Microphone";
|
||||
readonly property string avatarReceiveStats: "Show Receive Stats";
|
||||
readonly property string back: "Back";
|
||||
readonly property string bandwidthDetails: "Bandwidth Details";
|
||||
readonly property string binaryEyelidControl: "Binary Eyelid Control";
|
||||
readonly property string bookmarkLocation: "Bookmark Location";
|
||||
readonly property string bookmarks: "Bookmarks";
|
||||
readonly property string cachesSize: "RAM Caches Size";
|
||||
readonly property string calibrateCamera: "Calibrate Camera";
|
||||
readonly property string cameraEntityMode: "Entity Mode";
|
||||
readonly property string centerPlayerInView: "Center Player In View";
|
||||
readonly property string chat: "Chat...";
|
||||
readonly property string collisions: "Collisions";
|
||||
readonly property string connexion: "Activate 3D Connexion Devices";
|
||||
readonly property string console_: "Console...";
|
||||
readonly property string controlWithSpeech: "Control With Speech";
|
||||
readonly property string copyAddress: "Copy Address to Clipboard";
|
||||
readonly property string copyPath: "Copy Path to Clipboard";
|
||||
readonly property string coupleEyelids: "Couple Eyelids";
|
||||
readonly property string crashInterface: "Crash Interface";
|
||||
readonly property string debugAmbientOcclusion: "Debug Ambient Occlusion";
|
||||
readonly property string decreaseAvatarSize: "Decrease Avatar Size";
|
||||
readonly property string deleteBookmark: "Delete Bookmark...";
|
||||
readonly property string disableActivityLogger: "Disable Activity Logger";
|
||||
readonly property string disableEyelidAdjustment: "Disable Eyelid Adjustment";
|
||||
readonly property string disableLightEntities: "Disable Light Entities";
|
||||
readonly property string disableNackPackets: "Disable Entity NACK Packets";
|
||||
readonly property string diskCacheEditor: "Disk Cache Editor";
|
||||
readonly property string displayCrashOptions: "Display Crash Options";
|
||||
readonly property string displayHandTargets: "Show Hand Targets";
|
||||
readonly property string displayModelBounds: "Display Model Bounds";
|
||||
readonly property string displayModelTriangles: "Display Model Triangles";
|
||||
readonly property string displayModelElementChildProxies: "Display Model Element Children";
|
||||
readonly property string displayModelElementProxy: "Display Model Element Bounds";
|
||||
readonly property string displayDebugTimingDetails: "Display Timing Details";
|
||||
readonly property string dontDoPrecisionPicking: "Don't Do Precision Picking";
|
||||
readonly property string dontRenderEntitiesAsScene: "Don't Render Entities as Scene";
|
||||
readonly property string echoLocalAudio: "Echo Local Audio";
|
||||
readonly property string echoServerAudio: "Echo Server Audio";
|
||||
readonly property string enable3DTVMode: "Enable 3DTV Mode";
|
||||
readonly property string enableCharacterController: "Enable avatar collisions";
|
||||
readonly property string expandMyAvatarSimulateTiming: "Expand /myAvatar/simulation";
|
||||
readonly property string expandMyAvatarTiming: "Expand /myAvatar";
|
||||
readonly property string expandOtherAvatarTiming: "Expand /otherAvatar";
|
||||
readonly property string expandPaintGLTiming: "Expand /paintGL";
|
||||
readonly property string expandUpdateTiming: "Expand /update";
|
||||
readonly property string faceshift: "Faceshift";
|
||||
readonly property string firstPerson: "First Person";
|
||||
readonly property string fivePointCalibration: "5 Point Calibration";
|
||||
readonly property string fixGaze: "Fix Gaze (no saccade)";
|
||||
readonly property string forward: "Forward";
|
||||
readonly property string frameTimer: "Show Timer";
|
||||
readonly property string fullscreenMirror: "Mirror";
|
||||
readonly property string help: "Help...";
|
||||
readonly property string increaseAvatarSize: "Increase Avatar Size";
|
||||
readonly property string independentMode: "Independent Mode";
|
||||
readonly property string inputMenu: "Avatar>Input Devices";
|
||||
readonly property string keyboardMotorControl: "Enable Keyboard Motor Control";
|
||||
readonly property string leapMotionOnHMD: "Leap Motion on HMD";
|
||||
readonly property string loadScript: "Open and Run Script File...";
|
||||
readonly property string loadScriptURL: "Open and Run Script from URL...";
|
||||
readonly property string lodTools: "LOD Tools";
|
||||
readonly property string login: "Login";
|
||||
readonly property string log: "Log";
|
||||
readonly property string logExtraTimings: "Log Extra Timing Details";
|
||||
readonly property string lowVelocityFilter: "Low Velocity Filter";
|
||||
readonly property string meshVisible: "Draw Mesh";
|
||||
readonly property string miniMirror: "Mini Mirror";
|
||||
readonly property string muteAudio: "Mute Microphone";
|
||||
readonly property string muteEnvironment: "Mute Environment";
|
||||
readonly property string muteFaceTracking: "Mute Face Tracking";
|
||||
readonly property string namesAboveHeads: "Names Above Heads";
|
||||
readonly property string noFaceTracking: "None";
|
||||
readonly property string octreeStats: "Entity Statistics";
|
||||
readonly property string onePointCalibration: "1 Point Calibration";
|
||||
readonly property string onlyDisplayTopTen: "Only Display Top Ten";
|
||||
readonly property string outputMenu: "Display";
|
||||
readonly property string packageModel: "Package Model...";
|
||||
readonly property string pair: "Pair";
|
||||
readonly property string physicsShowOwned: "Highlight Simulation Ownership";
|
||||
readonly property string physicsShowHulls: "Draw Collision Hulls";
|
||||
readonly property string pipelineWarnings: "Log Render Pipeline Warnings";
|
||||
readonly property string preferences: "General...";
|
||||
readonly property string quit: "Quit";
|
||||
readonly property string reloadAllScripts: "Reload All Scripts";
|
||||
readonly property string reloadContent: "Reload Content (Clears all caches)";
|
||||
readonly property string renderBoundingCollisionShapes: "Show Bounding Collision Shapes";
|
||||
readonly property string renderFocusIndicator: "Show Eye Focus";
|
||||
readonly property string renderLookAtTargets: "Show Look-at Targets";
|
||||
readonly property string renderLookAtVectors: "Show Look-at Vectors";
|
||||
readonly property string renderResolution: "Scale Resolution";
|
||||
readonly property string renderResolutionOne: "1";
|
||||
readonly property string renderResolutionTwoThird: "2/3";
|
||||
readonly property string renderResolutionHalf: "1/2";
|
||||
readonly property string renderResolutionThird: "1/3";
|
||||
readonly property string renderResolutionQuarter: "1/4";
|
||||
readonly property string renderAmbientLight: "Ambient Light";
|
||||
readonly property string renderAmbientLightGlobal: "Global";
|
||||
readonly property string renderAmbientLight0: "OLD_TOWN_SQUARE";
|
||||
readonly property string renderAmbientLight1: "GRACE_CATHEDRAL";
|
||||
readonly property string renderAmbientLight2: "EUCALYPTUS_GROVE";
|
||||
readonly property string renderAmbientLight3: "ST_PETERS_BASILICA";
|
||||
readonly property string renderAmbientLight4: "UFFIZI_GALLERY";
|
||||
readonly property string renderAmbientLight5: "GALILEOS_TOMB";
|
||||
readonly property string renderAmbientLight6: "VINE_STREET_KITCHEN";
|
||||
readonly property string renderAmbientLight7: "BREEZEWAY";
|
||||
readonly property string renderAmbientLight8: "CAMPUS_SUNSET";
|
||||
readonly property string renderAmbientLight9: "FUNSTON_BEACH_SUNSET";
|
||||
readonly property string resetAvatarSize: "Reset Avatar Size";
|
||||
readonly property string resetSensors: "Reset Sensors";
|
||||
readonly property string runningScripts: "Running Scripts...";
|
||||
readonly property string runTimingTests: "Run Timing Tests";
|
||||
readonly property string scriptEditor: "Script Editor...";
|
||||
readonly property string scriptedMotorControl: "Enable Scripted Motor Control";
|
||||
readonly property string showDSConnectTable: "Show Domain Connection Timing";
|
||||
readonly property string showBordersEntityNodes: "Show Entity Nodes";
|
||||
readonly property string showRealtimeEntityStats: "Show Realtime Entity Stats";
|
||||
readonly property string showWhosLookingAtMe: "Show Who's Looking at Me";
|
||||
readonly property string standingHMDSensorMode: "Standing HMD Sensor Mode";
|
||||
readonly property string simulateEyeTracking: "Simulate";
|
||||
readonly property string sMIEyeTracking: "SMI Eye Tracking";
|
||||
readonly property string stars: "Stars";
|
||||
readonly property string stats: "Stats";
|
||||
readonly property string stopAllScripts: "Stop All Scripts";
|
||||
readonly property string suppressShortTimings: "Suppress Timings Less than 10ms";
|
||||
readonly property string thirdPerson: "Third Person";
|
||||
readonly property string threePointCalibration: "3 Point Calibration";
|
||||
readonly property string throttleFPSIfNotFocus: "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp
|
||||
readonly property string toolWindow: "Tool Window";
|
||||
readonly property string transmitterDrive: "Transmitter Drive";
|
||||
readonly property string turnWithHead: "Turn using Head";
|
||||
readonly property string useAudioForMouth: "Use Audio for Mouth";
|
||||
readonly property string useCamera: "Use Camera";
|
||||
readonly property string velocityFilter: "Velocity Filter";
|
||||
readonly property string visibleToEveryone: "Everyone";
|
||||
readonly property string visibleToFriends: "Friends";
|
||||
readonly property string visibleToNoOne: "No one";
|
||||
readonly property string worldAxes: "World Axes";
|
||||
}
|
||||
|
|
@ -118,11 +118,89 @@ Window {
|
|||
}
|
||||
|
||||
HifiControls.Table {
|
||||
tableModel: runningScriptsModel
|
||||
model: runningScriptsModel
|
||||
id: table
|
||||
height: 185
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
expandSelectedRow: true
|
||||
|
||||
itemDelegate: Item {
|
||||
anchors {
|
||||
left: parent ? parent.left : undefined
|
||||
leftMargin: hifi.dimensions.tablePadding
|
||||
right: parent ? parent.right : undefined
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
}
|
||||
|
||||
FiraSansSemiBold {
|
||||
id: textItem
|
||||
text: styleData.value
|
||||
size: hifi.fontSizes.tableText
|
||||
color: table.colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
topMargin: 3
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
id: reloadButton
|
||||
text: hifi.glyphs.reloadSmall
|
||||
color: reloadButtonArea.pressed ? hifi.colors.white : parent.color
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: stopButton.left
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: reloadButtonArea
|
||||
anchors { fill: parent; margins: -2 }
|
||||
onClicked: reloadScript(model.url)
|
||||
}
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
id: stopButton
|
||||
text: hifi.glyphs.closeSmall
|
||||
color: stopButtonArea.pressed ? hifi.colors.white : parent.color
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: stopButtonArea
|
||||
anchors { fill: parent; margins: -2 }
|
||||
onClicked: stopScript(model.url)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FiraSansSemiBold {
|
||||
text: runningScriptsModel.get(styleData.row) ? runningScriptsModel.get(styleData.row).url : ""
|
||||
elide: Text.ElideMiddle
|
||||
size: hifi.fontSizes.tableText
|
||||
color: table.colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
anchors {
|
||||
top: textItem.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
visible: styleData.selected
|
||||
}
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
role: "name"
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.VerticalSpacer {
|
||||
|
|
23
interface/resources/qml/styles-uit/FiraSansRegular.qml
Normal file
23
interface/resources/qml/styles-uit/FiraSansRegular.qml
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// FiraSansRegular.qml
|
||||
//
|
||||
// Created by David Rowe on 12 May 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
Text {
|
||||
id: root
|
||||
FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
|
||||
property real size: 32
|
||||
font.pixelSize: size
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.family: firaSansRegular.name
|
||||
}
|
|
@ -89,12 +89,16 @@ Item {
|
|||
readonly property color transparent: "#00ffffff"
|
||||
|
||||
// Control specific colors
|
||||
readonly property color tableRowLightOdd: white50
|
||||
readonly property color tableRowLightEven: "#1a575757"
|
||||
readonly property color tableRowDarkOdd: "#80393939"
|
||||
readonly property color tableRowDarkEven: "#a6181818"
|
||||
readonly property color tableScrollHandle: "#707070"
|
||||
readonly property color tableScrollBackground: "#323232"
|
||||
readonly property color tableRowLightOdd: "#eaeaea" // Equivalent to white50 over #e3e3e3 background
|
||||
readonly property color tableRowLightEven: "#c6c6c6" // Equivavlent to "#1a575757" over #e3e3e3 background
|
||||
readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background
|
||||
readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background
|
||||
readonly property color tableBackgroundLight: tableRowLightEven
|
||||
readonly property color tableBackgroundDark: tableRowDarkEven
|
||||
readonly property color tableScrollHandleLight: tableRowLightOdd
|
||||
readonly property color tableScrollHandleDark: "#707070"
|
||||
readonly property color tableScrollBackgroundLight: tableRowLightEven
|
||||
readonly property color tableScrollBackgroundDark: "#323232"
|
||||
readonly property color checkboxLightStart: "#ffffff"
|
||||
readonly property color checkboxLightFinish: "#afafaf"
|
||||
readonly property color checkboxDarkStart: "#7d7d7d"
|
||||
|
@ -137,10 +141,10 @@ Item {
|
|||
readonly property real textPadding: 8
|
||||
readonly property real sliderHandleSize: 18
|
||||
readonly property real sliderGrooveHeight: 8
|
||||
readonly property real spinnerSize: 42
|
||||
readonly property real spinnerSize: 50
|
||||
readonly property real tablePadding: 12
|
||||
readonly property real tableRowHeight: largeScreen ? 26 : 23
|
||||
readonly property real tableHeaderHeight: 40
|
||||
readonly property real tableHeaderHeight: 29
|
||||
readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30)
|
||||
readonly property real modalDialogTitleHeight: 40
|
||||
readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor
|
||||
|
@ -157,6 +161,8 @@ Item {
|
|||
readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12
|
||||
readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24
|
||||
readonly property real tableHeading: dimensions.largeScreen ? 12 : 10
|
||||
readonly property real tableHeadingIcon: dimensions.largeScreen ? 40 : 33
|
||||
readonly property real tableText: dimensions.largeScreen ? 15 : 12
|
||||
readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real iconButton: dimensions.largeScreen ? 13 : 9
|
||||
|
|
|
@ -174,7 +174,6 @@ static const QString FBX_EXTENSION = ".fbx";
|
|||
static const QString OBJ_EXTENSION = ".obj";
|
||||
static const QString AVA_JSON_EXTENSION = ".ava.json";
|
||||
|
||||
static const int MSECS_PER_SEC = 1000;
|
||||
static const int MIRROR_VIEW_TOP_PADDING = 5;
|
||||
static const int MIRROR_VIEW_LEFT_PADDING = 10;
|
||||
static const int MIRROR_VIEW_WIDTH = 265;
|
||||
|
@ -633,7 +632,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
||||
|
||||
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
|
||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC;
|
||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND;
|
||||
|
||||
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
|
||||
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
|
||||
|
@ -762,6 +761,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
// Tell our entity edit sender about our known jurisdictions
|
||||
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
|
||||
_entityEditSender.setMyAvatar(getMyAvatar());
|
||||
|
||||
// For now we're going to set the PPS for outbound packets to be super high, this is
|
||||
// probably not the right long term solution. But for now, we're going to do this to
|
||||
|
@ -1050,6 +1050,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
// Make sure we don't time out during slow operations at startup
|
||||
updateHeartbeat();
|
||||
|
||||
OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender();
|
||||
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
||||
entityPacketSender->setMyAvatar(getMyAvatar());
|
||||
|
||||
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
||||
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
||||
|
||||
|
@ -1823,9 +1827,9 @@ bool Application::event(QEvent* event) {
|
|||
|
||||
// Presentation/painting logic
|
||||
// TODO: Decouple presentation and painting loops
|
||||
static bool isPainting = false;
|
||||
static bool isPaintingThrottled = false;
|
||||
if ((int)event->type() == (int)Present) {
|
||||
if (isPainting) {
|
||||
if (isPaintingThrottled) {
|
||||
// If painting (triggered by presentation) is hogging the main thread,
|
||||
// repost as low priority to avoid hanging the GUI.
|
||||
// This has the effect of allowing presentation to exceed the paint budget by X times and
|
||||
|
@ -1833,14 +1837,17 @@ bool Application::event(QEvent* event) {
|
|||
// (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS).
|
||||
removePostedEvents(this, Present);
|
||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Present)), Qt::LowEventPriority);
|
||||
isPainting = false;
|
||||
isPaintingThrottled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
idle();
|
||||
|
||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
|
||||
isPainting = true;
|
||||
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
|
||||
if (shouldPaint(nsecsElapsed)) {
|
||||
_lastTimeUpdated.start();
|
||||
idle(nsecsElapsed);
|
||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
|
||||
}
|
||||
isPaintingThrottled = true;
|
||||
|
||||
return true;
|
||||
} else if ((int)event->type() == (int)Paint) {
|
||||
|
@ -1850,7 +1857,7 @@ bool Application::event(QEvent* event) {
|
|||
|
||||
paintGL();
|
||||
|
||||
isPainting = false;
|
||||
isPaintingThrottled = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2634,10 +2641,9 @@ bool Application::acceptSnapshot(const QString& urlString) {
|
|||
|
||||
static uint32_t _renderedFrameIndex { INVALID_FRAME };
|
||||
|
||||
void Application::idle() {
|
||||
// idle is called on a queued connection, so make sure we should be here.
|
||||
if (_inPaint || _aboutToQuit) {
|
||||
return;
|
||||
bool Application::shouldPaint(float nsecsElapsed) {
|
||||
if (_aboutToQuit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
|
@ -2656,16 +2662,21 @@ void Application::idle() {
|
|||
}
|
||||
#endif
|
||||
|
||||
float msecondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC / USECS_PER_MSEC;
|
||||
float msecondsSinceLastUpdate = nsecsElapsed / NSECS_PER_USEC / USECS_PER_MSEC;
|
||||
|
||||
// Throttle if requested
|
||||
if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sync up the _renderedFrameIndex
|
||||
_renderedFrameIndex = displayPlugin->presentCount();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::idle(float nsecsElapsed) {
|
||||
|
||||
// Update the deadlock watchdog
|
||||
updateHeartbeat();
|
||||
|
||||
|
@ -2682,7 +2693,7 @@ void Application::idle() {
|
|||
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
float secondsSinceLastUpdate = msecondsSinceLastUpdate / MSECS_PER_SECOND;
|
||||
float secondsSinceLastUpdate = nsecsElapsed / NSECS_PER_MSEC / MSECS_PER_SECOND;
|
||||
|
||||
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
|
||||
if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) {
|
||||
|
@ -2692,9 +2703,6 @@ void Application::idle() {
|
|||
_keyboardDeviceHasFocus = true;
|
||||
}
|
||||
|
||||
// We're going to execute idle processing, so restart the last idle timer
|
||||
_lastTimeUpdated.start();
|
||||
|
||||
checkChangeCursor();
|
||||
|
||||
Stats::getInstance()->updateStats();
|
||||
|
@ -2921,7 +2929,7 @@ void Application::loadSettings() {
|
|||
}
|
||||
|
||||
void Application::saveSettings() const {
|
||||
sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SEC);
|
||||
sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SECOND);
|
||||
DependencyManager::get<AudioClient>()->saveSettings();
|
||||
DependencyManager::get<LODManager>()->saveSettings();
|
||||
|
||||
|
@ -3589,10 +3597,6 @@ void Application::update(float deltaTime) {
|
|||
|
||||
int Application::sendNackPackets() {
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// iterates through all nodes in NodeList
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -4194,6 +4198,7 @@ void Application::clearDomainOctreeDetails() {
|
|||
qCDebug(interfaceapp) << "Clearing domain octree details...";
|
||||
|
||||
resetPhysicsReadyInformation();
|
||||
getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities
|
||||
|
||||
// reset our node to stats and node to jurisdiction maps... since these must be changing...
|
||||
_entityServerJurisdictions.withWriteLock([&] {
|
||||
|
|
|
@ -329,7 +329,8 @@ private:
|
|||
|
||||
void cleanupBeforeQuit();
|
||||
|
||||
void idle();
|
||||
bool shouldPaint(float nsecsElapsed);
|
||||
void idle(float nsecsElapsed);
|
||||
void update(float deltaTime);
|
||||
|
||||
// Various helper functions called during update()
|
||||
|
|
|
@ -29,6 +29,10 @@ enum CameraMode
|
|||
};
|
||||
|
||||
Q_DECLARE_METATYPE(CameraMode);
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
__attribute__((unused))
|
||||
#endif
|
||||
static int cameraModeId = qRegisterMetaType<CameraMode>();
|
||||
|
||||
class Camera : public QObject {
|
||||
|
|
|
@ -530,9 +530,6 @@ Menu::Menu() {
|
|||
// Developer > Network >>>
|
||||
MenuWrapper* networkMenu = developerMenu->addMenu("Network");
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
|
||||
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false,
|
||||
qApp->getEntityEditPacketSender(),
|
||||
SLOT(toggleNackPackets()));
|
||||
addCheckableActionToQMenuAndActionHash(networkMenu,
|
||||
MenuOption::DisableActivityLogger,
|
||||
0,
|
||||
|
|
|
@ -84,7 +84,6 @@ namespace MenuOption {
|
|||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
|
||||
const QString DisableLightEntities = "Disable Light Entities";
|
||||
const QString DisableNackPackets = "Disable Entity NACK Packets";
|
||||
const QString DiskCacheEditor = "Disk Cache Editor";
|
||||
const QString DisplayCrashOptions = "Display Crash Options";
|
||||
const QString DisplayHandTargets = "Show Hand Targets";
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <TextRenderer3D.h>
|
||||
#include <TextureCache.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
#include <DebugDraw.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -102,6 +103,18 @@ Avatar::Avatar(RigPointer rig) :
|
|||
|
||||
Avatar::~Avatar() {
|
||||
assert(isDead()); // mark dead before calling the dtor
|
||||
|
||||
EntityTreeRenderer* treeRenderer = qApp->getEntities();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (_motionState) {
|
||||
delete _motionState;
|
||||
_motionState = nullptr;
|
||||
|
@ -157,6 +170,90 @@ void Avatar::animateScaleChanges(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void Avatar::updateAvatarEntities() {
|
||||
// - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity()
|
||||
// - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited
|
||||
// - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket
|
||||
// - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces
|
||||
// - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData()
|
||||
// - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true
|
||||
// - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are...
|
||||
|
||||
if (!_avatarEntityDataChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getID() == QUuid()) {
|
||||
return; // wait until MyAvatar gets an ID before doing this.
|
||||
}
|
||||
|
||||
EntityTreeRenderer* treeRenderer = qApp->getEntities();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
QScriptEngine scriptEngine;
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
// see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties
|
||||
// and either add or update the entity.
|
||||
QByteArray jsonByteArray = avatarEntities.value(entityID);
|
||||
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray);
|
||||
if (!jsonProperties.isObject()) {
|
||||
qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex());
|
||||
continue;
|
||||
}
|
||||
|
||||
QVariant variantProperties = jsonProperties.toVariant();
|
||||
QVariantMap asMap = variantProperties.toMap();
|
||||
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
|
||||
EntityItemProperties properties;
|
||||
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties);
|
||||
properties.setClientOnly(true);
|
||||
properties.setOwningAvatarID(getID());
|
||||
|
||||
// there's no entity-server to tell us we're the simulation owner, so always set the
|
||||
// simulationOwner to the owningAvatarID and a high priority.
|
||||
properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY);
|
||||
|
||||
if (properties.getParentID() == AVATAR_SELF_ID) {
|
||||
properties.setParentID(getID());
|
||||
}
|
||||
|
||||
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
|
||||
|
||||
if (entity) {
|
||||
if (entityTree->updateEntity(entityID, properties)) {
|
||||
entity->updateLastEditedFromRemote();
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
entity = entityTree->addEntity(entityID, properties);
|
||||
if (!entity) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs();
|
||||
foreach (auto entityID, recentlyDettachedAvatarEntities) {
|
||||
if (!_avatarEntityData.contains(entityID)) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (success) {
|
||||
setAvatarEntityDataChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Avatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
|
||||
|
@ -229,6 +326,7 @@ void Avatar::simulate(float deltaTime) {
|
|||
|
||||
simulateAttachments(deltaTime);
|
||||
updatePalms();
|
||||
updateAvatarEntities();
|
||||
}
|
||||
|
||||
bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const {
|
||||
|
@ -1088,7 +1186,7 @@ void Avatar::setParentID(const QUuid& parentID) {
|
|||
if (success) {
|
||||
setTransform(beforeChangeTransform, success);
|
||||
if (!success) {
|
||||
qDebug() << "Avatar::setParentID failed to reset avatar's location.";
|
||||
qCDebug(interfaceapp) << "Avatar::setParentID failed to reset avatar's location.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1103,7 +1201,7 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) {
|
|||
if (success) {
|
||||
setTransform(beforeChangeTransform, success);
|
||||
if (!success) {
|
||||
qDebug() << "Avatar::setParentJointIndex failed to reset avatar's location.";
|
||||
qCDebug(interfaceapp) << "Avatar::setParentJointIndex failed to reset avatar's location.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ public:
|
|||
typedef std::shared_ptr<render::Item::PayloadInterface> PayloadPointer;
|
||||
|
||||
void init();
|
||||
void updateAvatarEntities();
|
||||
void simulate(float deltaTime);
|
||||
virtual void simulateAttachments(float deltaTime);
|
||||
|
||||
|
|
|
@ -311,6 +311,10 @@ void MyAvatar::update(float deltaTime) {
|
|||
head->setAudioLoudness(audio->getLastInputLoudness());
|
||||
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
|
||||
|
||||
if (_avatarEntityDataLocallyEdited) {
|
||||
sendIdentityPacket();
|
||||
}
|
||||
|
||||
simulate(deltaTime);
|
||||
|
||||
currentEnergy += energyChargeRate;
|
||||
|
@ -426,7 +430,14 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
|
||||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
bool flyingAllowed = true;
|
||||
bool ghostingAllowed = true;
|
||||
entityTree->withWriteLock([&] {
|
||||
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
|
||||
if (zone) {
|
||||
flyingAllowed = zone->getFlyingAllowed();
|
||||
ghostingAllowed = zone->getGhostingAllowed();
|
||||
}
|
||||
auto now = usecTimestampNow();
|
||||
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||
MovingEntitiesOperator moveOperator(entityTree);
|
||||
|
@ -443,7 +454,8 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
EntityItemProperties properties = entity->getProperties();
|
||||
properties.setQueryAACubeDirty();
|
||||
properties.setLastEdited(now);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties);
|
||||
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties);
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
}
|
||||
}
|
||||
|
@ -454,7 +466,13 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
entityTree->recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
});
|
||||
_characterController.setFlyingAllowed(flyingAllowed);
|
||||
if (!_characterController.isEnabled() && !ghostingAllowed) {
|
||||
_characterController.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
updateAvatarEntities();
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
|
@ -702,6 +720,16 @@ void MyAvatar::saveData() {
|
|||
}
|
||||
settings.endArray();
|
||||
|
||||
settings.beginWriteArray("avatarEntityData");
|
||||
int avatarEntityIndex = 0;
|
||||
for (auto entityID : _avatarEntityData.keys()) {
|
||||
settings.setArrayIndex(avatarEntityIndex);
|
||||
settings.setValue("id", entityID);
|
||||
settings.setValue("properties", _avatarEntityData.value(entityID));
|
||||
avatarEntityIndex++;
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
settings.setValue("displayName", _displayName);
|
||||
settings.setValue("collisionSoundURL", _collisionSoundURL);
|
||||
settings.setValue("useSnapTurn", _useSnapTurn);
|
||||
|
@ -813,6 +841,17 @@ void MyAvatar::loadData() {
|
|||
settings.endArray();
|
||||
setAttachmentData(attachmentData);
|
||||
|
||||
int avatarEntityCount = settings.beginReadArray("avatarEntityData");
|
||||
for (int i = 0; i < avatarEntityCount; i++) {
|
||||
settings.setArrayIndex(i);
|
||||
QUuid entityID = settings.value("id").toUuid();
|
||||
// QUuid entityID = QUuid::createUuid(); // generate a new ID
|
||||
QByteArray properties = settings.value("properties").toByteArray();
|
||||
updateAvatarEntity(entityID, properties);
|
||||
}
|
||||
settings.endArray();
|
||||
setAvatarEntityDataChanged(true);
|
||||
|
||||
setDisplayName(settings.value("displayName").toString());
|
||||
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
|
||||
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
|
||||
|
@ -1790,7 +1829,21 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
|
|||
} else {
|
||||
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
||||
}
|
||||
_characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
|
||||
|
||||
bool ghostingAllowed = true;
|
||||
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
|
||||
if (entityTreeRenderer) {
|
||||
std::shared_ptr<ZoneEntityItem> zone = entityTreeRenderer->myAvatarZone();
|
||||
if (zone) {
|
||||
ghostingAllowed = zone->getGhostingAllowed();
|
||||
}
|
||||
}
|
||||
bool checked = menu->isOptionChecked(MenuOption::EnableCharacterController);
|
||||
if (!ghostingAllowed) {
|
||||
checked = true;
|
||||
}
|
||||
|
||||
_characterController.setEnabled(checked);
|
||||
}
|
||||
|
||||
void MyAvatar::clearDriveKeys() {
|
||||
|
|
|
@ -196,7 +196,7 @@ bool AssetMappingModel::isKnownFolder(QString path) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
static int assetMappingModelMetatypeId = qRegisterMetaType<AssetMappingModel*>("AssetMappingModel*");
|
||||
int assetMappingModelMetatypeId = qRegisterMetaType<AssetMappingModel*>("AssetMappingModel*");
|
||||
|
||||
void AssetMappingModel::refresh() {
|
||||
qDebug() << "Refreshing asset mapping model";
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "AnimationCache.h"
|
||||
#include "AnimationLogging.h"
|
||||
|
||||
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
|
||||
int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
|
||||
|
||||
AnimationCache::AnimationCache(QObject* parent) :
|
||||
ResourceCache(parent)
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
#include "AudioLogging.h"
|
||||
#include "SoundCache.h"
|
||||
|
||||
static int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
|
||||
int soundPointerMetaTypeId = qRegisterMetaType<SharedSoundPointer>();
|
||||
|
||||
SoundCache::SoundCache(QObject* parent) :
|
||||
ResourceCache(parent)
|
||||
|
|
|
@ -955,14 +955,16 @@ void AvatarData::clearJointsData() {
|
|||
}
|
||||
|
||||
bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) {
|
||||
// this is used by the avatar-mixer
|
||||
QDataStream packetStream(data);
|
||||
|
||||
QUuid avatarUUID;
|
||||
QUrl unusedModelURL; // legacy faceModel support
|
||||
QUrl skeletonModelURL;
|
||||
QVector<AttachmentData> attachmentData;
|
||||
AvatarEntityMap avatarEntityData;
|
||||
QString displayName;
|
||||
packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName;
|
||||
packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName >> avatarEntityData;
|
||||
|
||||
bool hasIdentityChanged = false;
|
||||
|
||||
|
@ -982,6 +984,11 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) {
|
|||
hasIdentityChanged = true;
|
||||
}
|
||||
|
||||
if (avatarEntityData != _avatarEntityData) {
|
||||
setAvatarEntityData(avatarEntityData);
|
||||
hasIdentityChanged = true;
|
||||
}
|
||||
|
||||
return hasIdentityChanged;
|
||||
}
|
||||
|
||||
|
@ -993,7 +1000,7 @@ QByteArray AvatarData::identityByteArray() {
|
|||
|
||||
QUrl unusedModelURL; // legacy faceModel support
|
||||
|
||||
identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName;
|
||||
identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName << _avatarEntityData;
|
||||
|
||||
return identityData;
|
||||
}
|
||||
|
@ -1167,6 +1174,8 @@ void AvatarData::sendIdentityPacket() {
|
|||
[&](const SharedNodePointer& node) {
|
||||
nodeList->sendPacketList(std::move(packetList), *node);
|
||||
});
|
||||
|
||||
_avatarEntityDataLocallyEdited = false;
|
||||
}
|
||||
|
||||
void AvatarData::updateJointMappings() {
|
||||
|
@ -1339,6 +1348,7 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel");
|
|||
static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel");
|
||||
static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName");
|
||||
static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments");
|
||||
static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
|
||||
static const QString JSON_AVATAR_SCALE = QStringLiteral("scale");
|
||||
|
||||
QJsonValue toJsonValue(const JointData& joint) {
|
||||
|
@ -1377,6 +1387,17 @@ QJsonObject AvatarData::toJson() const {
|
|||
root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson;
|
||||
}
|
||||
|
||||
if (!_avatarEntityData.empty()) {
|
||||
QJsonArray avatarEntityJson;
|
||||
for (auto entityID : _avatarEntityData.keys()) {
|
||||
QVariantMap entityData;
|
||||
entityData.insert("id", entityID);
|
||||
entityData.insert("properties", _avatarEntityData.value(entityID));
|
||||
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
|
||||
}
|
||||
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
|
||||
}
|
||||
|
||||
auto recordingBasis = getRecordingBasis();
|
||||
bool success;
|
||||
Transform avatarTransform = getTransform(success);
|
||||
|
@ -1476,6 +1497,13 @@ void AvatarData::fromJson(const QJsonObject& json) {
|
|||
setAttachmentData(attachments);
|
||||
}
|
||||
|
||||
// if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) {
|
||||
// QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray();
|
||||
// for (auto attachmentJson : attachmentsJson) {
|
||||
// // TODO -- something
|
||||
// }
|
||||
// }
|
||||
|
||||
// Joint rotations are relative to the avatar, so they require no basis correction
|
||||
if (json.contains(JSON_AVATAR_JOINT_ARRAY)) {
|
||||
QVector<JointData> jointArray;
|
||||
|
@ -1628,9 +1656,69 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
|
|||
QVector<AttachmentData> newAttachments;
|
||||
newAttachments.reserve(variant.size());
|
||||
for (const auto& attachmentVar : variant) {
|
||||
AttachmentData attachment;
|
||||
AttachmentData attachment;
|
||||
attachment.fromVariant(attachmentVar);
|
||||
newAttachments.append(attachment);
|
||||
}
|
||||
setAttachmentData(newAttachments);
|
||||
}
|
||||
|
||||
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData));
|
||||
return;
|
||||
}
|
||||
_avatarEntityData.insert(entityID, entityData);
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
}
|
||||
|
||||
void AvatarData::clearAvatarEntity(const QUuid& entityID) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "clearAvatarEntity", Q_ARG(const QUuid&, entityID));
|
||||
return;
|
||||
}
|
||||
_avatarEntityData.remove(entityID);
|
||||
_avatarEntityDataLocallyEdited = true;
|
||||
}
|
||||
|
||||
AvatarEntityMap AvatarData::getAvatarEntityData() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
AvatarEntityMap result;
|
||||
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getAvatarEntityData", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(AvatarEntityMap, result));
|
||||
return result;
|
||||
}
|
||||
return _avatarEntityData;
|
||||
}
|
||||
|
||||
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData));
|
||||
return;
|
||||
}
|
||||
if (_avatarEntityData != avatarEntityData) {
|
||||
// keep track of entities that were attached to this avatar but no longer are
|
||||
AvatarEntityIDs previousAvatarEntityIDs = QSet<QUuid>::fromList(_avatarEntityData.keys());
|
||||
|
||||
_avatarEntityData = avatarEntityData;
|
||||
setAvatarEntityDataChanged(true);
|
||||
|
||||
foreach (auto entityID, previousAvatarEntityIDs) {
|
||||
if (!_avatarEntityData.contains(entityID)) {
|
||||
_avatarEntityDetached.insert(entityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
AvatarEntityIDs result;
|
||||
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getRecentlyDetachedIDs", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(AvatarEntityIDs, result));
|
||||
return result;
|
||||
}
|
||||
AvatarEntityIDs result = _avatarEntityDetached;
|
||||
_avatarEntityDetached.clear();
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ typedef unsigned long long quint64;
|
|||
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
|
||||
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
|
||||
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
|
||||
using AvatarEntityMap = QMap<QUuid, QByteArray>;
|
||||
using AvatarEntityIDs = QSet<QUuid>;
|
||||
|
||||
using AvatarDataSequenceNumber = uint16_t;
|
||||
|
||||
|
@ -134,6 +136,10 @@ class AttachmentData;
|
|||
class Transform;
|
||||
using TransformPointer = std::shared_ptr<Transform>;
|
||||
|
||||
// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows
|
||||
// the value to be reset when the sessionID changes.
|
||||
const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}");
|
||||
|
||||
class AvatarData : public QObject, public SpatiallyNestable {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -272,6 +278,9 @@ public:
|
|||
Q_INVOKABLE QVariantList getAttachmentsVariant() const;
|
||||
Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant);
|
||||
|
||||
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
|
||||
Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID);
|
||||
|
||||
void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; }
|
||||
|
||||
// key state
|
||||
|
@ -323,6 +332,11 @@ public:
|
|||
|
||||
glm::vec3 getClientGlobalPosition() { return _globalPosition; }
|
||||
|
||||
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const;
|
||||
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
|
||||
void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
|
||||
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
|
||||
|
||||
public slots:
|
||||
void sendAvatarDataPacket();
|
||||
void sendIdentityPacket();
|
||||
|
@ -390,6 +404,11 @@ protected:
|
|||
// updates about one avatar to another.
|
||||
glm::vec3 _globalPosition;
|
||||
|
||||
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
||||
AvatarEntityMap _avatarEntityData;
|
||||
bool _avatarEntityDataLocallyEdited { false };
|
||||
bool _avatarEntityDataChanged { false };
|
||||
|
||||
private:
|
||||
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
||||
static QUrl _defaultFullAvatarModelUrl;
|
||||
|
|
|
@ -107,21 +107,23 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer<ReceivedMessage> mess
|
|||
}
|
||||
|
||||
void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
// this is used by clients
|
||||
// setup a data stream to parse the packet
|
||||
QDataStream identityStream(message->getMessage());
|
||||
|
||||
QUuid sessionUUID;
|
||||
|
||||
|
||||
while (!identityStream.atEnd()) {
|
||||
|
||||
QUrl faceMeshURL, skeletonURL;
|
||||
QVector<AttachmentData> attachmentData;
|
||||
AvatarEntityMap avatarEntityData;
|
||||
QString displayName;
|
||||
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName;
|
||||
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName >> avatarEntityData;
|
||||
|
||||
// mesh URL for a UUID, find avatar in our list
|
||||
auto avatar = newOrExistingAvatar(sessionUUID, sendingNode);
|
||||
|
||||
|
||||
if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) {
|
||||
avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire
|
||||
}
|
||||
|
@ -130,6 +132,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
|||
avatar->setAttachmentData(attachmentData);
|
||||
}
|
||||
|
||||
avatar->setAvatarEntityData(avatarEntityData);
|
||||
|
||||
if (avatar->getDisplayName() != displayName) {
|
||||
avatar->setDisplayName(displayName);
|
||||
}
|
||||
|
|
|
@ -336,10 +336,10 @@ QVector<QString> UserInputMapper::getActionNames() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
static int actionMetaTypeId = qRegisterMetaType<Action>();
|
||||
static int inputMetaTypeId = qRegisterMetaType<Input>();
|
||||
static int inputPairMetaTypeId = qRegisterMetaType<Input::NamedPair>();
|
||||
static int poseMetaTypeId = qRegisterMetaType<controller::Pose>("Pose");
|
||||
int actionMetaTypeId = qRegisterMetaType<Action>();
|
||||
int inputMetaTypeId = qRegisterMetaType<Input>();
|
||||
int inputPairMetaTypeId = qRegisterMetaType<Input::NamedPair>();
|
||||
int poseMetaTypeId = qRegisterMetaType<controller::Pose>("Pose");
|
||||
|
||||
QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input);
|
||||
void inputFromScriptValue(const QScriptValue& object, Input& input);
|
||||
|
|
|
@ -29,17 +29,16 @@
|
|||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
#include "RenderableBoxEntityItem.h"
|
||||
#include "RenderableLightEntityItem.h"
|
||||
#include "RenderableModelEntityItem.h"
|
||||
#include "RenderableParticleEffectEntityItem.h"
|
||||
#include "RenderableSphereEntityItem.h"
|
||||
#include "RenderableTextEntityItem.h"
|
||||
#include "RenderableWebEntityItem.h"
|
||||
#include "RenderableZoneEntityItem.h"
|
||||
#include "RenderableLineEntityItem.h"
|
||||
#include "RenderablePolyVoxEntityItem.h"
|
||||
#include "RenderablePolyLineEntityItem.h"
|
||||
#include "RenderableShapeEntityItem.h"
|
||||
#include "EntitiesRendererLogging.h"
|
||||
#include "AddressManager.h"
|
||||
#include <Rig.h>
|
||||
|
@ -56,8 +55,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
|||
_dontDoPrecisionPicking(false)
|
||||
{
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, RenderableWebEntityItem::factory)
|
||||
|
@ -66,7 +63,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
|||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyVox, RenderablePolyVoxEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyLine, RenderablePolyLineEntityItem::factory)
|
||||
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Shape, RenderableShapeEntityItem::factory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableShapeEntityItem::boxFactory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableShapeEntityItem::sphereFactory)
|
||||
|
||||
_currentHoverOverEntityID = UNKNOWN_ENTITY_ID;
|
||||
_currentClickingOnEntityID = UNKNOWN_ENTITY_ID;
|
||||
}
|
||||
|
|
|
@ -88,6 +88,8 @@ public:
|
|||
// For Scene.shouldRenderEntities
|
||||
QList<EntityItemID>& getEntitiesLastInScene() { return _entityIDsLastInScene; }
|
||||
|
||||
std::shared_ptr<ZoneEntityItem> myAvatarZone() { return _bestZone; }
|
||||
|
||||
signals:
|
||||
void mousePressOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
|
||||
void mousePressOffEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event);
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
//
|
||||
// RenderableBoxEntityItem.cpp
|
||||
// libraries/entities-renderer/src/
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 8/6/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RenderableBoxEntityItem.h"
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <gpu/Batch.h>
|
||||
|
||||
#include <GeometryCache.h>
|
||||
#include <ObjectMotionState.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include <render-utils/simple_vert.h>
|
||||
#include <render-utils/simple_frag.h>
|
||||
|
||||
EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity{ new RenderableBoxEntityItem(entityID) };
|
||||
entity->setProperties(properties);
|
||||
return entity;
|
||||
}
|
||||
|
||||
void RenderableBoxEntityItem::setUserData(const QString& value) {
|
||||
if (value != getUserData()) {
|
||||
BoxEntityItem::setUserData(value);
|
||||
if (_procedural) {
|
||||
_procedural->parse(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableBoxEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Box);
|
||||
Q_ASSERT(args->_batch);
|
||||
|
||||
if (!_procedural) {
|
||||
_procedural.reset(new Procedural(this->getUserData()));
|
||||
_procedural->_vertexSource = simple_vert;
|
||||
_procedural->_fragmentSource = simple_frag;
|
||||
_procedural->_state->setCullMode(gpu::State::CULL_NONE);
|
||||
_procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
_procedural->_state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
}
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha());
|
||||
|
||||
bool success;
|
||||
auto transToCenter = getTransformToCenter(success);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
batch.setModelTransform(transToCenter); // we want to include the scale as well
|
||||
if (_procedural->ready()) {
|
||||
_procedural->prepare(batch, getPosition(), getDimensions());
|
||||
auto color = _procedural->getColor(cubeColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
DependencyManager::get<GeometryCache>()->renderCube(batch);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->renderSolidCubeInstance(batch, cubeColor);
|
||||
}
|
||||
static const auto triCount = DependencyManager::get<GeometryCache>()->getCubeTriangleCount();
|
||||
args->_details._trianglesRendered += (int)triCount;
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// RenderableBoxEntityItem.h
|
||||
// libraries/entities-renderer/src/
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 8/6/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_RenderableBoxEntityItem_h
|
||||
#define hifi_RenderableBoxEntityItem_h
|
||||
|
||||
#include <BoxEntityItem.h>
|
||||
#include <procedural/Procedural.h>
|
||||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
class RenderableBoxEntityItem : public BoxEntityItem {
|
||||
public:
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
RenderableBoxEntityItem(const EntityItemID& entityItemID) : BoxEntityItem(entityItemID) { }
|
||||
|
||||
virtual void render(RenderArgs* args) override;
|
||||
virtual void setUserData(const QString& value) override;
|
||||
|
||||
SIMPLE_RENDERABLE()
|
||||
private:
|
||||
QSharedPointer<Procedural> _procedural;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_RenderableBoxEntityItem_h
|
|
@ -47,6 +47,9 @@ namespace render {
|
|||
}
|
||||
|
||||
void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid& myNodeID = nodeList->getSessionUUID();
|
||||
|
||||
statusGetters.push_back([entity] () -> render::Item::Status::Value {
|
||||
quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote();
|
||||
const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND);
|
||||
|
@ -81,9 +84,7 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status:
|
|||
(unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET);
|
||||
});
|
||||
|
||||
statusGetters.push_back([entity] () -> render::Item::Status::Value {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid& myNodeID = nodeList->getSessionUUID();
|
||||
statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value {
|
||||
bool weOwnSimulation = entity->getSimulationOwner().matchesValidID(myNodeID);
|
||||
bool otherOwnSimulation = !weOwnSimulation && !entity->getSimulationOwner().isNull();
|
||||
|
||||
|
@ -106,4 +107,18 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status:
|
|||
return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN,
|
||||
(unsigned char)RenderItemStatusIcon::HAS_ACTIONS);
|
||||
});
|
||||
|
||||
statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value {
|
||||
if (entity->getClientOnly()) {
|
||||
if (entity->getOwningAvatarID() == myNodeID) {
|
||||
return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN,
|
||||
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
|
||||
} else {
|
||||
return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED,
|
||||
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
|
||||
}
|
||||
}
|
||||
return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN,
|
||||
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ enum class RenderItemStatusIcon {
|
|||
SIMULATION_OWNER = 3,
|
||||
HAS_ACTIONS = 4,
|
||||
OTHER_SIMULATION_OWNER = 5,
|
||||
CLIENT_ONLY = 6,
|
||||
NONE = 255
|
||||
};
|
||||
|
||||
|
|
|
@ -981,7 +981,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
|
|||
PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation);
|
||||
EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr;
|
||||
if (packetSender) {
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, entity->getID(), properties);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
113
libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
Normal file
113
libraries/entities-renderer/src/RenderableShapeEntityItem.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/05/09
|
||||
// Copyright 2013 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 "RenderableShapeEntityItem.h"
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <gpu/Batch.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include <render-utils/simple_vert.h>
|
||||
#include <render-utils/simple_frag.h>
|
||||
|
||||
// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
|
||||
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
|
||||
static const float SPHERE_ENTITY_SCALE = 0.5f;
|
||||
|
||||
static GeometryCache::Shape MAPPING[entity::NUM_SHAPES] = {
|
||||
GeometryCache::Triangle,
|
||||
GeometryCache::Quad,
|
||||
GeometryCache::Circle,
|
||||
GeometryCache::Cube,
|
||||
GeometryCache::Sphere,
|
||||
GeometryCache::Tetrahedron,
|
||||
GeometryCache::Octahedron,
|
||||
GeometryCache::Dodecahedron,
|
||||
GeometryCache::Icosahedron,
|
||||
GeometryCache::Torus,
|
||||
GeometryCache::Cone,
|
||||
GeometryCache::Cylinder,
|
||||
};
|
||||
|
||||
|
||||
RenderableShapeEntityItem::Pointer RenderableShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
Pointer entity = std::make_shared<RenderableShapeEntityItem>(entityID);
|
||||
entity->setProperties(properties);
|
||||
return entity;
|
||||
}
|
||||
|
||||
EntityItemPointer RenderableShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
return baseFactory(entityID, properties);
|
||||
}
|
||||
|
||||
EntityItemPointer RenderableShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
auto result = baseFactory(entityID, properties);
|
||||
result->setShape(entity::Cube);
|
||||
return result;
|
||||
}
|
||||
|
||||
EntityItemPointer RenderableShapeEntityItem::sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
auto result = baseFactory(entityID, properties);
|
||||
result->setShape(entity::Sphere);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RenderableShapeEntityItem::setUserData(const QString& value) {
|
||||
if (value != getUserData()) {
|
||||
ShapeEntityItem::setUserData(value);
|
||||
if (_procedural) {
|
||||
_procedural->parse(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableShapeEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
|
||||
//Q_ASSERT(getType() == EntityTypes::Shape);
|
||||
Q_ASSERT(args->_batch);
|
||||
|
||||
if (!_procedural) {
|
||||
_procedural.reset(new Procedural(getUserData()));
|
||||
_procedural->_vertexSource = simple_vert;
|
||||
_procedural->_fragmentSource = simple_frag;
|
||||
_procedural->_state->setCullMode(gpu::State::CULL_NONE);
|
||||
_procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
_procedural->_state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
}
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
glm::vec4 color(toGlm(getXColor()), getLocalRenderAlpha());
|
||||
bool success;
|
||||
Transform modelTransform = getTransformToCenter(success);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
if (_shape == entity::Sphere) {
|
||||
modelTransform.postScale(SPHERE_ENTITY_SCALE);
|
||||
}
|
||||
batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation
|
||||
if (_procedural->ready()) {
|
||||
_procedural->prepare(batch, getPosition(), getDimensions());
|
||||
auto outColor = _procedural->getColor(color);
|
||||
batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a);
|
||||
DependencyManager::get<GeometryCache>()->renderShape(batch, MAPPING[_shape]);
|
||||
} else {
|
||||
// FIXME, support instanced multi-shape rendering using multidraw indirect
|
||||
DependencyManager::get<GeometryCache>()->renderSolidShapeInstance(batch, MAPPING[_shape], color);
|
||||
}
|
||||
|
||||
|
||||
static const auto triCount = DependencyManager::get<GeometryCache>()->getShapeTriangleCount(MAPPING[_shape]);
|
||||
args->_details._trianglesRendered += (int)triCount;
|
||||
}
|
36
libraries/entities-renderer/src/RenderableShapeEntityItem.h
Normal file
36
libraries/entities-renderer/src/RenderableShapeEntityItem.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/05/09
|
||||
// Copyright 2013 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_RenderableShapeEntityItem_h
|
||||
#define hifi_RenderableShapeEntityItem_h
|
||||
|
||||
#include <ShapeEntityItem.h>
|
||||
#include <procedural/Procedural.h>
|
||||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
class RenderableShapeEntityItem : public ShapeEntityItem {
|
||||
using Pointer = std::shared_ptr<RenderableShapeEntityItem>;
|
||||
static Pointer baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
public:
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
static EntityItemPointer boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
static EntityItemPointer sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
RenderableShapeEntityItem(const EntityItemID& entityItemID) : ShapeEntityItem(entityItemID) {}
|
||||
|
||||
void render(RenderArgs* args) override;
|
||||
void setUserData(const QString& value) override;
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
|
||||
private:
|
||||
QSharedPointer<Procedural> _procedural;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_RenderableShapeEntityItem_h
|
|
@ -1,80 +0,0 @@
|
|||
//
|
||||
// RenderableSphereEntityItem.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 8/6/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RenderableSphereEntityItem.h"
|
||||
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include <gpu/Batch.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include <render-utils/simple_vert.h>
|
||||
#include <render-utils/simple_frag.h>
|
||||
|
||||
// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
|
||||
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
|
||||
static const float SPHERE_ENTITY_SCALE = 0.5f;
|
||||
|
||||
|
||||
EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity{ new RenderableSphereEntityItem(entityID) };
|
||||
entity->setProperties(properties);
|
||||
return entity;
|
||||
}
|
||||
|
||||
void RenderableSphereEntityItem::setUserData(const QString& value) {
|
||||
if (value != getUserData()) {
|
||||
SphereEntityItem::setUserData(value);
|
||||
if (_procedural) {
|
||||
_procedural->parse(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableSphereEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableSphereEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Sphere);
|
||||
Q_ASSERT(args->_batch);
|
||||
|
||||
if (!_procedural) {
|
||||
_procedural.reset(new Procedural(getUserData()));
|
||||
_procedural->_vertexSource = simple_vert;
|
||||
_procedural->_fragmentSource = simple_frag;
|
||||
_procedural->_state->setCullMode(gpu::State::CULL_NONE);
|
||||
_procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
_procedural->_state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
}
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
glm::vec4 sphereColor(toGlm(getXColor()), getLocalRenderAlpha());
|
||||
bool success;
|
||||
Transform modelTransform = getTransformToCenter(success);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
modelTransform.postScale(SPHERE_ENTITY_SCALE);
|
||||
batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation
|
||||
if (_procedural->ready()) {
|
||||
_procedural->prepare(batch, getPosition(), getDimensions());
|
||||
auto color = _procedural->getColor(sphereColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->renderSolidSphereInstance(batch, sphereColor);
|
||||
}
|
||||
static const auto triCount = DependencyManager::get<GeometryCache>()->getSphereTriangleCount();
|
||||
args->_details._trianglesRendered += (int)triCount;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
//
|
||||
// RenderableSphereEntityItem.h
|
||||
// interface/src/entities
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 8/6/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_RenderableSphereEntityItem_h
|
||||
#define hifi_RenderableSphereEntityItem_h
|
||||
|
||||
#include <SphereEntityItem.h>
|
||||
#include <procedural/Procedural.h>
|
||||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
class RenderableSphereEntityItem : public SphereEntityItem {
|
||||
public:
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
RenderableSphereEntityItem(const EntityItemID& entityItemID) : SphereEntityItem(entityItemID) { }
|
||||
|
||||
virtual void render(RenderArgs* args) override;
|
||||
virtual void setUserData(const QString& value) override;
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
|
||||
private:
|
||||
QSharedPointer<Procedural> _procedural;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_RenderableSphereEntityItem_h
|
|
@ -1,103 +0,0 @@
|
|||
//
|
||||
// BoxEntityItem.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/4/13.
|
||||
// Copyright 2013 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 <QDebug>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
|
||||
#include "BoxEntityItem.h"
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityItemProperties.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntityTreeElement.h"
|
||||
|
||||
EntityItemPointer BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity { new BoxEntityItem(entityID) };
|
||||
entity->setProperties(properties);
|
||||
return entity;
|
||||
}
|
||||
|
||||
BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Box;
|
||||
}
|
||||
|
||||
EntityItemProperties BoxEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
|
||||
properties._color = getXColor();
|
||||
properties._colorChanged = false;
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool BoxEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||
bool somethingChanged = false;
|
||||
somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - getLastEdited();
|
||||
qCDebug(entities) << "BoxEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
}
|
||||
setLastEdited(properties._lastEdited);
|
||||
}
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
int BoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) {
|
||||
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
EntityPropertyFlags BoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_COLOR;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
bool successPropertyFits = true;
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
||||
}
|
||||
|
||||
void BoxEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << " BOX EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
|
||||
qCDebug(entities) << " position:" << debugTreeVector(getPosition());
|
||||
qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions());
|
||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
//
|
||||
// BoxEntityItem.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/4/13.
|
||||
// Copyright 2013 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_BoxEntityItem_h
|
||||
#define hifi_BoxEntityItem_h
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
class BoxEntityItem : public EntityItem {
|
||||
public:
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
BoxEntityItem(const EntityItemID& entityItemID);
|
||||
|
||||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
// methods for getting/setting all properties of an entity
|
||||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const;
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const;
|
||||
|
||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged);
|
||||
|
||||
const rgbColor& getColor() const { return _color; }
|
||||
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
|
||||
|
||||
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
|
||||
void setColor(const xColor& value) {
|
||||
_color[RED_INDEX] = value.red;
|
||||
_color[GREEN_INDEX] = value.green;
|
||||
_color[BLUE_INDEX] = value.blue;
|
||||
}
|
||||
|
||||
virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; }
|
||||
virtual bool shouldBePhysical() const { return !isDead(); }
|
||||
|
||||
virtual void debugDump() const;
|
||||
|
||||
protected:
|
||||
rgbColor _color;
|
||||
};
|
||||
|
||||
#endif // hifi_BoxEntityItem_h
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include <assert.h>
|
||||
#include <QJsonDocument>
|
||||
#include <PerfStat.h>
|
||||
#include <OctalCode.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
@ -24,9 +25,7 @@ EntityEditPacketSender::EntityEditPacketSender() {
|
|||
}
|
||||
|
||||
void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
if (_shouldProcessNack) {
|
||||
processNackPacket(*message, sendingNode);
|
||||
}
|
||||
processNackPacket(*message, sendingNode);
|
||||
}
|
||||
|
||||
void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) {
|
||||
|
@ -35,18 +34,76 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte
|
|||
}
|
||||
}
|
||||
|
||||
void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemID modelID,
|
||||
const EntityItemProperties& properties) {
|
||||
void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
|
||||
EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID,
|
||||
const EntityItemProperties& properties) {
|
||||
if (!_shouldSend) {
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
if (properties.getOwningAvatarID() != _myAvatar->getID()) {
|
||||
return; // don't send updates for someone else's avatarEntity
|
||||
}
|
||||
|
||||
assert(properties.getClientOnly());
|
||||
|
||||
// this is an avatar-based entity. update our avatar-data rather than sending to the entity-server
|
||||
assert(_myAvatar);
|
||||
|
||||
if (!entityTree) {
|
||||
qDebug() << "EntityEditPacketSender::queueEditEntityMessage null entityTree.";
|
||||
return;
|
||||
}
|
||||
EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID);
|
||||
if (!entity) {
|
||||
qDebug() << "EntityEditPacketSender::queueEditEntityMessage can't find entity.";
|
||||
return;
|
||||
}
|
||||
|
||||
// the properties that get serialized into the avatar identity packet should be the entire set
|
||||
// rather than just the ones being edited.
|
||||
entity->setProperties(properties);
|
||||
EntityItemProperties entityProperties = entity->getProperties();
|
||||
|
||||
QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties);
|
||||
QVariant variantProperties = scriptProperties.toVariant();
|
||||
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties);
|
||||
|
||||
// the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar
|
||||
QJsonObject jsonObject = jsonProperties.object();
|
||||
if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) {
|
||||
jsonObject["parentID"] = AVATAR_SELF_ID.toString();
|
||||
}
|
||||
jsonProperties = QJsonDocument(jsonObject);
|
||||
|
||||
QByteArray binaryProperties = jsonProperties.toBinaryData();
|
||||
_myAvatar->updateAvatarEntity(entityItemID, binaryProperties);
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
||||
EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID,
|
||||
const EntityItemProperties& properties) {
|
||||
if (!_shouldSend) {
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
if (properties.getClientOnly()) {
|
||||
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
|
||||
|
||||
if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, bufferOut)) {
|
||||
if (EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut)) {
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(entities) << "calling queueOctreeEditMessage()...";
|
||||
qCDebug(entities) << " id:" << modelID;
|
||||
qCDebug(entities) << " id:" << entityItemID;
|
||||
qCDebug(entities) << " properties:" << properties;
|
||||
#endif
|
||||
queueOctreeEditMessage(type, bufferOut);
|
||||
|
@ -58,6 +115,10 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI
|
|||
return; // bail early
|
||||
}
|
||||
|
||||
// in case this was a clientOnly entity:
|
||||
assert(_myAvatar);
|
||||
_myAvatar->clearAvatarEntity(entityItemID);
|
||||
|
||||
QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityErase), 0);
|
||||
|
||||
if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, bufferOut)) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <OctreeEditPacketSender.h>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "AvatarData.h"
|
||||
|
||||
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
|
||||
class EntityEditPacketSender : public OctreeEditPacketSender {
|
||||
|
@ -22,11 +23,21 @@ class EntityEditPacketSender : public OctreeEditPacketSender {
|
|||
public:
|
||||
EntityEditPacketSender();
|
||||
|
||||
void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; }
|
||||
AvatarData* getMyAvatar() { return _myAvatar; }
|
||||
void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); }
|
||||
|
||||
void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID, const EntityItemProperties& properties);
|
||||
|
||||
|
||||
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
|
||||
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
|
||||
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
|
||||
/// NOTE: EntityItemProperties assumes that all distances are in meter units
|
||||
void queueEditEntityMessage(PacketType type, EntityItemID modelID, const EntityItemProperties& properties);
|
||||
void queueEditEntityMessage(PacketType type, EntityTreePointer entityTree,
|
||||
EntityItemID entityItemID, const EntityItemProperties& properties);
|
||||
|
||||
|
||||
void queueEraseEntityMessage(const EntityItemID& entityItemID);
|
||||
|
||||
|
@ -36,9 +47,9 @@ public:
|
|||
|
||||
public slots:
|
||||
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void toggleNackPackets() { _shouldProcessNack = !_shouldProcessNack; }
|
||||
|
||||
private:
|
||||
bool _shouldProcessNack = true;
|
||||
AvatarData* _myAvatar { nullptr };
|
||||
QScriptEngine _scriptEngine;
|
||||
};
|
||||
#endif // hifi_EntityEditPacketSender_h
|
||||
|
|
|
@ -137,6 +137,9 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_PARENT_JOINT_INDEX;
|
||||
requestedProperties += PROP_QUERY_AA_CUBE;
|
||||
|
||||
requestedProperties += PROP_CLIENT_ONLY;
|
||||
requestedProperties += PROP_OWNING_AVATAR_ID;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -1093,6 +1096,8 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
|
|||
properties._id = getID();
|
||||
properties._idSet = true;
|
||||
properties._created = _created;
|
||||
properties.setClientOnly(_clientOnly);
|
||||
properties.setOwningAvatarID(_owningAvatarID);
|
||||
|
||||
properties._type = getType();
|
||||
|
||||
|
@ -1133,6 +1138,9 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localPosition, getLocalPosition);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRotation, getLocalOrientation);
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(clientOnly, getClientOnly);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID);
|
||||
|
||||
properties._defaultSettings = false;
|
||||
|
||||
return properties;
|
||||
|
@ -1222,6 +1230,9 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube);
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(clientOnly, setClientOnly);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID);
|
||||
|
||||
AACube saveQueryAACube = _queryAACube;
|
||||
checkAndAdjustQueryAACube();
|
||||
if (saveQueryAACube != _queryAACube) {
|
||||
|
|
|
@ -372,6 +372,7 @@ public:
|
|||
glm::vec3 entityToWorld(const glm::vec3& point) const;
|
||||
|
||||
quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; }
|
||||
void updateLastEditedFromRemote() { _lastEditedFromRemote = usecTimestampNow(); }
|
||||
|
||||
void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
|
||||
|
||||
|
@ -422,12 +423,19 @@ public:
|
|||
/// entity to definitively state if the preload signal should be sent.
|
||||
///
|
||||
/// We only want to preload if:
|
||||
/// there is some script, and either the script value or the scriptTimestamp
|
||||
/// there is some script, and either the script value or the scriptTimestamp
|
||||
/// value have changed since our last preload
|
||||
bool shouldPreloadScript() const { return !_script.isEmpty() &&
|
||||
bool shouldPreloadScript() const { return !_script.isEmpty() &&
|
||||
((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp)); }
|
||||
void scriptHasPreloaded() { _loadedScript = _script; _loadedScriptTimestamp = _scriptTimestamp; }
|
||||
|
||||
bool getClientOnly() const { return _clientOnly; }
|
||||
void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
|
||||
// if this entity is client-only, which avatar is it associated with?
|
||||
QUuid getOwningAvatarID() const { return _owningAvatarID; }
|
||||
void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; }
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
void setSimulated(bool simulated) { _simulated = simulated; }
|
||||
|
@ -539,6 +547,9 @@ protected:
|
|||
mutable QHash<QUuid, quint64> _previouslyDeletedActions;
|
||||
|
||||
QUuid _sourceUUID; /// the server node UUID we came from
|
||||
|
||||
bool _clientOnly { false };
|
||||
QUuid _owningAvatarID;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityItem_h
|
||||
|
|
|
@ -308,6 +308,12 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_LOCAL_POSITION, localPosition);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation);
|
||||
|
||||
CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed);
|
||||
CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
|
||||
|
||||
CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly);
|
||||
CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID);
|
||||
|
||||
changedProperties += _animation.getChangedProperties();
|
||||
changedProperties += _keyLight.getChangedProperties();
|
||||
changedProperties += _skybox.getChangedProperties();
|
||||
|
@ -465,6 +471,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
|
||||
_stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
||||
_skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
|
||||
}
|
||||
|
||||
// Web only
|
||||
|
@ -539,6 +548,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation);
|
||||
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID);
|
||||
|
||||
properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly()));
|
||||
properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID()));
|
||||
|
||||
// FIXME - I don't think these properties are supported any more
|
||||
//COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha);
|
||||
|
||||
|
@ -674,6 +689,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(shape, QString, setShape);
|
||||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed);
|
||||
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID);
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
@ -825,6 +847,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>);
|
||||
ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<glm::vec3>);
|
||||
|
||||
ADD_PROPERTY_TO_MAP(PROP_SHAPE, Shape, shape, QString);
|
||||
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FPS, Animation, animation, FPS, fps);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame);
|
||||
|
@ -845,6 +869,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_HOUR, Stage, stage, Hour, hour);
|
||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_AUTOMATIC_HOURDAY, Stage, stage, AutomaticHourDay, automaticHourDay);
|
||||
|
||||
ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool);
|
||||
|
||||
// FIXME - these are not yet handled
|
||||
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
|
||||
|
||||
|
@ -1085,6 +1112,9 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
|
||||
_staticSkybox.setProperties(properties);
|
||||
_staticSkybox.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState);
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed());
|
||||
APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed());
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::PolyVox) {
|
||||
|
@ -1114,7 +1144,9 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, properties.getStrokeWidths());
|
||||
APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures());
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::Shape) {
|
||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
|
||||
}
|
||||
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID());
|
||||
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
|
||||
|
@ -1369,6 +1401,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode);
|
||||
properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes);
|
||||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed);
|
||||
}
|
||||
|
||||
if (properties.getType() == EntityTypes::PolyVox) {
|
||||
|
@ -1399,6 +1434,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_WIDTHS, QVector<float>, setStrokeWidths);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures);
|
||||
}
|
||||
if (properties.getType() == EntityTypes::Shape) {
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
|
||||
|
@ -1559,6 +1597,12 @@ void EntityItemProperties::markAllChanged() {
|
|||
_jointTranslationsChanged = true;
|
||||
|
||||
_queryAACubeChanged = true;
|
||||
|
||||
_flyingAllowedChanged = true;
|
||||
_ghostingAllowedChanged = true;
|
||||
|
||||
_clientOnlyChanged = true;
|
||||
_owningAvatarIDChanged = true;
|
||||
}
|
||||
|
||||
// The minimum bounding box for the entity.
|
||||
|
@ -1880,6 +1924,24 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
out += "queryAACube";
|
||||
}
|
||||
|
||||
if (clientOnlyChanged()) {
|
||||
out += "clientOnly";
|
||||
}
|
||||
if (owningAvatarIDChanged()) {
|
||||
out += "owningAvatarID";
|
||||
}
|
||||
|
||||
if (flyingAllowedChanged()) {
|
||||
out += "flyingAllowed";
|
||||
}
|
||||
if (ghostingAllowedChanged()) {
|
||||
out += "ghostingAllowed";
|
||||
}
|
||||
|
||||
if (shapeChanged()) {
|
||||
out += "shape";
|
||||
}
|
||||
|
||||
getAnimation().listChangedProperties(out);
|
||||
getKeyLight().listChangedProperties(out);
|
||||
getSkybox().listChangedProperties(out);
|
||||
|
|
|
@ -50,7 +50,7 @@ const quint64 UNKNOWN_CREATED_TIME = 0;
|
|||
/// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an
|
||||
/// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete
|
||||
/// set of entity item properties via JavaScript hashes/QScriptValues
|
||||
/// all units for position, dimensions, etc are in meter units
|
||||
/// all units for SI units (meter, second, radian, etc)
|
||||
class EntityItemProperties {
|
||||
friend class EntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
|
@ -64,6 +64,7 @@ class EntityItemProperties {
|
|||
friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ShapeEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
public:
|
||||
EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags());
|
||||
virtual ~EntityItemProperties() = default;
|
||||
|
@ -195,6 +196,7 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID);
|
||||
DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1);
|
||||
DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube());
|
||||
DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere");
|
||||
|
||||
// these are used when bouncing location data into and out of scripts
|
||||
DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, glmVec3, ENTITY_ITEM_ZERO_VEC3);
|
||||
|
@ -205,6 +207,12 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>, QVector<bool>());
|
||||
DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<glm::vec3>, QVector<glm::vec3>());
|
||||
|
||||
DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED);
|
||||
DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED);
|
||||
|
||||
DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false);
|
||||
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
|
||||
|
||||
static QString getBackgroundModeString(BackgroundMode mode);
|
||||
|
||||
|
||||
|
@ -416,6 +424,12 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslationsSet, jointTranslationsSet, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslations, jointTranslations, "");
|
||||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FlyingAllowed, flyingAllowed, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, "");
|
||||
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");
|
||||
|
||||
properties.getAnimation().debugDump();
|
||||
properties.getSkybox().debugDump();
|
||||
properties.getStage().debugDump();
|
||||
|
|
|
@ -169,6 +169,14 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_FALLOFF_RADIUS, // for Light entity
|
||||
|
||||
PROP_FLYING_ALLOWED, // can avatars in a zone fly?
|
||||
PROP_GHOSTING_ALLOWED, // can avatars in a zone turn off physics?
|
||||
|
||||
PROP_CLIENT_ONLY, // doesn't go over wire
|
||||
PROP_OWNING_AVATAR_ID, // doesn't go over wire
|
||||
|
||||
PROP_SHAPE,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -36,7 +36,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
|
|||
|
||||
void EntityScriptingInterface::queueEntityMessage(PacketType packetType,
|
||||
EntityItemID entityID, const EntityItemProperties& properties) {
|
||||
getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties);
|
||||
getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties);
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::canAdjustLocks() {
|
||||
|
@ -123,10 +123,17 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti
|
|||
}
|
||||
|
||||
|
||||
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) {
|
||||
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) {
|
||||
EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties);
|
||||
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
|
||||
|
||||
if (clientOnly) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
propertiesWithSimID.setClientOnly(clientOnly);
|
||||
propertiesWithSimID.setOwningAvatarID(myNodeID);
|
||||
}
|
||||
|
||||
auto dimensions = propertiesWithSimID.getDimensions();
|
||||
float volume = dimensions.x * dimensions.y * dimensions.z;
|
||||
auto density = propertiesWithSimID.getDensity();
|
||||
|
@ -272,13 +279,21 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
|
||||
bool updatedEntity = false;
|
||||
_entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) {
|
||||
// don't edit other avatar's avatarEntities
|
||||
return;
|
||||
}
|
||||
|
||||
if (scriptSideProperties.parentRelatedPropertyChanged()) {
|
||||
// All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them.
|
||||
// If any of these changed, pull any missing properties from the entity.
|
||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||
if (!entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
//existing entity, retrieve old velocity for check down below
|
||||
oldVelocity = entity->getVelocity().length();
|
||||
|
||||
|
@ -296,6 +311,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
}
|
||||
}
|
||||
properties = convertLocationFromScriptSemantics(properties);
|
||||
properties.setClientOnly(entity->getClientOnly());
|
||||
properties.setOwningAvatarID(entity->getOwningAvatarID());
|
||||
|
||||
float cost = calculateCost(density * volume, oldVelocity, newVelocity);
|
||||
cost *= costMultiplier;
|
||||
|
@ -384,6 +401,14 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
|||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||
if (entity) {
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) {
|
||||
// don't delete other avatar's avatarEntities
|
||||
shouldDelete = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto dimensions = entity->getDimensions();
|
||||
float volume = dimensions.x * dimensions.y * dimensions.z;
|
||||
auto density = entity->getDensity();
|
||||
|
@ -771,6 +796,11 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
|
|||
return false;
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
|
||||
EntityItemProperties properties;
|
||||
|
||||
EntityItemPointer entity;
|
||||
bool doTransmit = false;
|
||||
_entityTree->withWriteLock([&] {
|
||||
|
@ -786,15 +816,20 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
|
|||
return;
|
||||
}
|
||||
|
||||
if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) {
|
||||
return;
|
||||
}
|
||||
|
||||
doTransmit = actor(simulation, entity);
|
||||
if (doTransmit) {
|
||||
properties.setClientOnly(entity->getClientOnly());
|
||||
properties.setOwningAvatarID(entity->getOwningAvatarID());
|
||||
_entityTree->entityChanged(entity);
|
||||
}
|
||||
});
|
||||
|
||||
// transmit the change
|
||||
if (doTransmit) {
|
||||
EntityItemProperties properties;
|
||||
_entityTree->withReadLock([&] {
|
||||
properties = entity->getProperties();
|
||||
});
|
||||
|
|
|
@ -82,7 +82,7 @@ public slots:
|
|||
Q_INVOKABLE bool canRez();
|
||||
|
||||
/// adds a model with the specific properties
|
||||
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties);
|
||||
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false);
|
||||
|
||||
/// temporary method until addEntity can be used from QJSEngine
|
||||
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position);
|
||||
|
|
|
@ -311,7 +311,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
|||
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer result = NULL;
|
||||
|
||||
if (getIsClient()) {
|
||||
bool clientOnly = properties.getClientOnly();
|
||||
|
||||
if (!clientOnly && getIsClient()) {
|
||||
// if our Node isn't allowed to create entities in this domain, don't try.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (nodeList && !nodeList->getThisNodeCanRez()) {
|
||||
|
@ -1382,8 +1384,11 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra
|
|||
|
||||
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
|
||||
|
||||
EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element);
|
||||
EntityTreePointer tree = entityTreeElement->getTree();
|
||||
|
||||
// queue the packet to send to the server
|
||||
args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties);
|
||||
args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, tree, newID, properties);
|
||||
|
||||
// also update the local tree instantly (note: this is not our tree, but an alternate tree)
|
||||
if (args->otherTree) {
|
||||
|
|
|
@ -18,17 +18,16 @@
|
|||
#include "EntityItemProperties.h"
|
||||
#include "EntityTypes.h"
|
||||
|
||||
#include "BoxEntityItem.h"
|
||||
#include "LightEntityItem.h"
|
||||
#include "ModelEntityItem.h"
|
||||
#include "ParticleEffectEntityItem.h"
|
||||
#include "SphereEntityItem.h"
|
||||
#include "TextEntityItem.h"
|
||||
#include "WebEntityItem.h"
|
||||
#include "ZoneEntityItem.h"
|
||||
#include "LineEntityItem.h"
|
||||
#include "PolyVoxEntityItem.h"
|
||||
#include "PolyLineEntityItem.h"
|
||||
#include "ShapeEntityItem.h"
|
||||
|
||||
QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap;
|
||||
QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap;
|
||||
|
@ -39,16 +38,17 @@ const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown";
|
|||
|
||||
// Register Entity the default implementations of entity types here...
|
||||
REGISTER_ENTITY_TYPE(Model)
|
||||
REGISTER_ENTITY_TYPE(Box)
|
||||
REGISTER_ENTITY_TYPE(Web)
|
||||
REGISTER_ENTITY_TYPE(Sphere)
|
||||
REGISTER_ENTITY_TYPE(Light)
|
||||
REGISTER_ENTITY_TYPE(Text)
|
||||
REGISTER_ENTITY_TYPE(ParticleEffect)
|
||||
REGISTER_ENTITY_TYPE(Zone)
|
||||
REGISTER_ENTITY_TYPE(Line)
|
||||
REGISTER_ENTITY_TYPE(PolyVox)
|
||||
REGISTER_ENTITY_TYPE(PolyLine);
|
||||
REGISTER_ENTITY_TYPE(PolyLine)
|
||||
REGISTER_ENTITY_TYPE(Shape)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory)
|
||||
|
||||
const QString& EntityTypes::getEntityTypeName(EntityType entityType) {
|
||||
QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType);
|
||||
|
|
|
@ -48,7 +48,8 @@ public:
|
|||
Line,
|
||||
PolyVox,
|
||||
PolyLine,
|
||||
LAST = PolyLine
|
||||
Shape,
|
||||
LAST = Shape
|
||||
} EntityType;
|
||||
|
||||
static const QString& getEntityTypeName(EntityType entityType);
|
||||
|
@ -69,9 +70,18 @@ private:
|
|||
/// named NameEntityItem and must of a static method called factory that takes an EnityItemID, and EntityItemProperties and return a newly
|
||||
/// constructed (heap allocated) instance of your type. e.g. The following prototype:
|
||||
// static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
#define REGISTER_ENTITY_TYPE(x) static bool x##Registration = \
|
||||
#define REGISTER_ENTITY_TYPE(x) bool x##Registration = \
|
||||
EntityTypes::registerEntityType(EntityTypes::x, #x, x##EntityItem::factory);
|
||||
|
||||
|
||||
struct EntityRegistrationChecker {
|
||||
EntityRegistrationChecker(bool result, const char* debugMessage) {
|
||||
if (!result) {
|
||||
qDebug() << debugMessage;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Macro for registering entity types with an overloaded factory. Like using the REGISTER_ENTITY_TYPE macro: Make sure to add
|
||||
/// an element to the EntityType enum with your name. But unlike REGISTER_ENTITY_TYPE, your class can be named anything
|
||||
/// so long as you provide a static method passed to the macro, that takes an EnityItemID, and EntityItemProperties and
|
||||
|
@ -79,9 +89,9 @@ private:
|
|||
// static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
#define REGISTER_ENTITY_TYPE_WITH_FACTORY(x,y) static bool x##Registration = \
|
||||
EntityTypes::registerEntityType(EntityTypes::x, #x, y); \
|
||||
if (!x##Registration) { \
|
||||
qDebug() << "UNEXPECTED: REGISTER_ENTITY_TYPE_WITH_FACTORY(" #x "," #y ") FAILED.!"; \
|
||||
}
|
||||
EntityRegistrationChecker x##RegistrationChecker( \
|
||||
x##Registration, \
|
||||
"UNEXPECTED: REGISTER_ENTITY_TYPE_WITH_FACTORY(" #x "," #y ") FAILED.!");
|
||||
|
||||
|
||||
#endif // hifi_EntityTypes_h
|
||||
|
|
230
libraries/entities/src/ShapeEntityItem.cpp
Normal file
230
libraries/entities/src/ShapeEntityItem.cpp
Normal file
|
@ -0,0 +1,230 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/05/09
|
||||
// Copyright 2013 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 <glm/gtx/transform.hpp>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <GeometryUtil.h>
|
||||
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityItemProperties.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "ShapeEntityItem.h"
|
||||
|
||||
namespace entity {
|
||||
static const std::vector<QString> shapeStrings { {
|
||||
"Triangle",
|
||||
"Quad",
|
||||
"Circle",
|
||||
"Cube",
|
||||
"Sphere",
|
||||
"Tetrahedron",
|
||||
"Octahedron",
|
||||
"Dodecahedron",
|
||||
"Icosahedron",
|
||||
"Torus",
|
||||
"Cone",
|
||||
"Cylinder"
|
||||
} };
|
||||
|
||||
Shape shapeFromString(const ::QString& shapeString) {
|
||||
for (size_t i = 0; i < shapeStrings.size(); ++i) {
|
||||
if (shapeString.toLower() == shapeStrings[i].toLower()) {
|
||||
return static_cast<Shape>(i);
|
||||
}
|
||||
}
|
||||
return Shape::Sphere;
|
||||
}
|
||||
|
||||
::QString stringFromShape(Shape shape) {
|
||||
return shapeStrings[shape];
|
||||
}
|
||||
}
|
||||
|
||||
ShapeEntityItem::Pointer ShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
Pointer entity { new ShapeEntityItem(entityID) };
|
||||
entity->setProperties(properties);
|
||||
return entity;
|
||||
}
|
||||
|
||||
EntityItemPointer ShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
return baseFactory(entityID, properties);
|
||||
}
|
||||
|
||||
EntityItemPointer ShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
auto result = baseFactory(entityID, properties);
|
||||
result->setShape(entity::Shape::Cube);
|
||||
return result;
|
||||
}
|
||||
|
||||
EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
auto result = baseFactory(entityID, properties);
|
||||
result->setShape(entity::Shape::Sphere);
|
||||
return result;
|
||||
}
|
||||
|
||||
// our non-pure virtual subclass for now...
|
||||
ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Shape;
|
||||
_volumeMultiplier *= PI / 6.0f;
|
||||
}
|
||||
|
||||
EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
properties.setColor(getXColor());
|
||||
properties.setShape(entity::stringFromShape(getShape()));
|
||||
return properties;
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setShape(const entity::Shape& shape) {
|
||||
_shape = shape;
|
||||
switch (_shape) {
|
||||
case entity::Shape::Cube:
|
||||
_type = EntityTypes::Box;
|
||||
break;
|
||||
case entity::Shape::Sphere:
|
||||
_type = EntityTypes::Sphere;
|
||||
break;
|
||||
default:
|
||||
_type = EntityTypes::Shape;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - getLastEdited();
|
||||
qCDebug(entities) << "ShapeEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
}
|
||||
setLastEdited(properties.getLastEdited());
|
||||
}
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) {
|
||||
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape);
|
||||
READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor);
|
||||
READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_SHAPE;
|
||||
requestedProperties += PROP_COLOR;
|
||||
requestedProperties += PROP_ALPHA;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
bool successPropertyFits = true;
|
||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape()));
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, getAlpha());
|
||||
}
|
||||
|
||||
// This value specifes how the shape should be treated by physics calculations.
|
||||
// For now, all polys will act as spheres
|
||||
ShapeType ShapeEntityItem::getShapeType() const {
|
||||
return SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setColor(const rgbColor& value) {
|
||||
memcpy(_color, value, sizeof(rgbColor));
|
||||
}
|
||||
|
||||
xColor ShapeEntityItem::getXColor() const {
|
||||
return xColor { _color[0], _color[1], _color[2] };
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setColor(const xColor& value) {
|
||||
setColor(rgbColor { value.red, value.green, value.blue });
|
||||
}
|
||||
|
||||
QColor ShapeEntityItem::getQColor() const {
|
||||
auto& color = getColor();
|
||||
return QColor(color[0], color[1], color[2], (int)(getAlpha() * 255));
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setColor(const QColor& value) {
|
||||
setColor(rgbColor { (uint8_t)value.red(), (uint8_t)value.green(), (uint8_t)value.blue() });
|
||||
setAlpha(value.alpha());
|
||||
}
|
||||
|
||||
bool ShapeEntityItem::supportsDetailedRayIntersection() const {
|
||||
return _shape == entity::Sphere;
|
||||
}
|
||||
|
||||
bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
// determine the ray in the frame of the entity transformed from a unit sphere
|
||||
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
|
||||
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)));
|
||||
|
||||
float localDistance;
|
||||
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
|
||||
if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) {
|
||||
// determine where on the unit sphere the hit point occured
|
||||
glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance);
|
||||
// then translate back to work coordinates
|
||||
glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f));
|
||||
distance = glm::distance(origin, hitAt);
|
||||
bool success;
|
||||
surfaceNormal = glm::normalize(hitAt - getCenterPosition(success));
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShapeEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qCDebug(entities) << " shape:" << stringFromShape(_shape);
|
||||
qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
|
||||
qCDebug(entities) << " position:" << debugTreeVector(getPosition());
|
||||
qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions());
|
||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
}
|
||||
|
103
libraries/entities/src/ShapeEntityItem.h
Normal file
103
libraries/entities/src/ShapeEntityItem.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016/05/09
|
||||
// Copyright 2013 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_ShapeEntityItem_h
|
||||
#define hifi_ShapeEntityItem_h
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
namespace entity {
|
||||
enum Shape {
|
||||
Triangle,
|
||||
Quad,
|
||||
Circle,
|
||||
Cube,
|
||||
Sphere,
|
||||
Tetrahedron,
|
||||
Octahedron,
|
||||
Dodecahedron,
|
||||
Icosahedron,
|
||||
Torus,
|
||||
Cone,
|
||||
Cylinder,
|
||||
NUM_SHAPES,
|
||||
};
|
||||
|
||||
Shape shapeFromString(const ::QString& shapeString);
|
||||
::QString stringFromShape(Shape shape);
|
||||
}
|
||||
|
||||
|
||||
class ShapeEntityItem : public EntityItem {
|
||||
using Pointer = std::shared_ptr<ShapeEntityItem>;
|
||||
static Pointer baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
public:
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
static EntityItemPointer sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
static EntityItemPointer boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
ShapeEntityItem(const EntityItemID& entityItemID);
|
||||
|
||||
void pureVirtualFunctionPlaceHolder() override { };
|
||||
// Triggers warnings on OSX
|
||||
//ALLOW_INSTANTIATION
|
||||
|
||||
// methods for getting/setting all properties of an entity
|
||||
EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const override;
|
||||
|
||||
int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) override;
|
||||
|
||||
entity::Shape getShape() const { return _shape; }
|
||||
void setShape(const entity::Shape& shape);
|
||||
void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); }
|
||||
|
||||
float getAlpha() const { return _alpha; };
|
||||
void setAlpha(float alpha) { _alpha = alpha; }
|
||||
|
||||
const rgbColor& getColor() const { return _color; }
|
||||
void setColor(const rgbColor& value);
|
||||
|
||||
xColor getXColor() const;
|
||||
void setColor(const xColor& value);
|
||||
|
||||
QColor getQColor() const;
|
||||
void setColor(const QColor& value);
|
||||
|
||||
ShapeType getShapeType() const override;
|
||||
bool shouldBePhysical() const override { return !isDead(); }
|
||||
|
||||
bool supportsDetailedRayIntersection() const override;
|
||||
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
|
||||
void debugDump() const override;
|
||||
|
||||
protected:
|
||||
|
||||
float _alpha { 1 };
|
||||
rgbColor _color;
|
||||
entity::Shape _shape { entity::Shape::Sphere };
|
||||
};
|
||||
|
||||
#endif // hifi_ShapeEntityItem_h
|
|
@ -27,6 +27,7 @@ const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
|
|||
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
|
||||
const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80;
|
||||
const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
|
||||
const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
|
||||
|
||||
// PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar
|
||||
// which really just means: things that collide with it will be bid at a priority level one lower
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
//
|
||||
// SphereEntityItem.cpp
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/4/13.
|
||||
// Copyright 2013 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 <glm/gtx/transform.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
#include <GeometryUtil.h>
|
||||
|
||||
#include "EntitiesLogging.h"
|
||||
#include "EntityItemProperties.h"
|
||||
#include "EntityTree.h"
|
||||
#include "EntityTreeElement.h"
|
||||
#include "SphereEntityItem.h"
|
||||
|
||||
EntityItemPointer SphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity { new SphereEntityItem(entityID) };
|
||||
entity->setProperties(properties);
|
||||
return entity;
|
||||
}
|
||||
|
||||
// our non-pure virtual subclass for now...
|
||||
SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Sphere;
|
||||
_volumeMultiplier *= PI / 6.0f;
|
||||
}
|
||||
|
||||
EntityItemProperties SphereEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
properties.setColor(getXColor());
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool SphereEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - getLastEdited();
|
||||
qCDebug(entities) << "SphereEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
}
|
||||
setLastEdited(properties.getLastEdited());
|
||||
}
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
int SphereEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) {
|
||||
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||
EntityPropertyFlags SphereEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_COLOR;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
bool successPropertyFits = true;
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor());
|
||||
}
|
||||
|
||||
bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const {
|
||||
// determine the ray in the frame of the entity transformed from a unit sphere
|
||||
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
|
||||
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
|
||||
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f)));
|
||||
|
||||
float localDistance;
|
||||
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
|
||||
if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) {
|
||||
// determine where on the unit sphere the hit point occured
|
||||
glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance);
|
||||
// then translate back to work coordinates
|
||||
glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f));
|
||||
distance = glm::distance(origin, hitAt);
|
||||
bool success;
|
||||
surfaceNormal = glm::normalize(hitAt - getCenterPosition(success));
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void SphereEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << "SHPERE EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
|
||||
qCDebug(entities) << " position:" << debugTreeVector(getPosition());
|
||||
qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions());
|
||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
//
|
||||
// SphereEntityItem.h
|
||||
// libraries/entities/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/4/13.
|
||||
// Copyright 2013 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_SphereEntityItem_h
|
||||
#define hifi_SphereEntityItem_h
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
class SphereEntityItem : public EntityItem {
|
||||
public:
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
SphereEntityItem(const EntityItemID& entityItemID);
|
||||
|
||||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
// methods for getting/setting all properties of an entity
|
||||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const;
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const;
|
||||
|
||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged);
|
||||
|
||||
const rgbColor& getColor() const { return _color; }
|
||||
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
|
||||
|
||||
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
|
||||
void setColor(const xColor& value) {
|
||||
_color[RED_INDEX] = value.red;
|
||||
_color[GREEN_INDEX] = value.green;
|
||||
_color[BLUE_INDEX] = value.blue;
|
||||
}
|
||||
|
||||
virtual ShapeType getShapeType() const { return SHAPE_TYPE_SPHERE; }
|
||||
virtual bool shouldBePhysical() const { return !isDead(); }
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const;
|
||||
|
||||
virtual void debugDump() const;
|
||||
|
||||
protected:
|
||||
|
||||
rgbColor _color;
|
||||
};
|
||||
|
||||
#endif // hifi_SphereEntityItem_h
|
|
@ -23,8 +23,12 @@
|
|||
bool ZoneEntityItem::_zonesArePickable = false;
|
||||
bool ZoneEntityItem::_drawZoneBoundaries = false;
|
||||
|
||||
|
||||
const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX;
|
||||
const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = "";
|
||||
const bool ZoneEntityItem::DEFAULT_FLYING_ALLOWED = true;
|
||||
const bool ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED = true;
|
||||
|
||||
|
||||
EntityItemPointer ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity { new ZoneEntityItem(entityID) };
|
||||
|
@ -55,6 +59,9 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr
|
|||
|
||||
_skyboxProperties.getProperties(properties);
|
||||
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -70,6 +77,9 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode);
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed);
|
||||
|
||||
bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties);
|
||||
|
||||
somethingChanged = somethingChanged || somethingChangedInKeyLight || somethingChangedInStage || somethingChangedInSkybox;
|
||||
|
@ -116,6 +126,9 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
bytesRead += bytesFromSkybox;
|
||||
dataAt += bytesFromSkybox;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed);
|
||||
READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
@ -125,13 +138,16 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p
|
|||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
|
||||
requestedProperties += _keyLightProperties.getEntityProperties(params);
|
||||
|
||||
|
||||
requestedProperties += PROP_SHAPE_TYPE;
|
||||
requestedProperties += PROP_COMPOUND_SHAPE_URL;
|
||||
requestedProperties += PROP_BACKGROUND_MODE;
|
||||
requestedProperties += _stageProperties.getEntityProperties(params);
|
||||
requestedProperties += _skyboxProperties.getEntityProperties(params);
|
||||
|
||||
|
||||
requestedProperties += PROP_FLYING_ALLOWED;
|
||||
requestedProperties += PROP_GHOSTING_ALLOWED;
|
||||
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -155,10 +171,12 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
|
|||
APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)getBackgroundMode()); // could this be a uint16??
|
||||
|
||||
|
||||
_skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties,
|
||||
propertyFlags, propertiesDidntFit, propertyCount, appendState);
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed());
|
||||
APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed());
|
||||
}
|
||||
|
||||
void ZoneEntityItem::debugDump() const {
|
||||
|
|
|
@ -70,6 +70,11 @@ public:
|
|||
const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; }
|
||||
const StagePropertyGroup& getStageProperties() const { return _stageProperties; }
|
||||
|
||||
bool getFlyingAllowed() const { return _flyingAllowed; }
|
||||
void setFlyingAllowed(bool value) { _flyingAllowed = value; }
|
||||
bool getGhostingAllowed() const { return _ghostingAllowed; }
|
||||
void setGhostingAllowed(bool value) { _ghostingAllowed = value; }
|
||||
|
||||
virtual bool supportsDetailedRayIntersection() const { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
|
@ -80,18 +85,23 @@ public:
|
|||
|
||||
static const ShapeType DEFAULT_SHAPE_TYPE;
|
||||
static const QString DEFAULT_COMPOUND_SHAPE_URL;
|
||||
|
||||
static const bool DEFAULT_FLYING_ALLOWED;
|
||||
static const bool DEFAULT_GHOSTING_ALLOWED;
|
||||
|
||||
protected:
|
||||
KeyLightPropertyGroup _keyLightProperties;
|
||||
|
||||
|
||||
ShapeType _shapeType = DEFAULT_SHAPE_TYPE;
|
||||
QString _compoundShapeURL;
|
||||
|
||||
|
||||
BackgroundMode _backgroundMode = BACKGROUND_MODE_INHERIT;
|
||||
|
||||
StagePropertyGroup _stageProperties;
|
||||
SkyboxPropertyGroup _skyboxProperties;
|
||||
|
||||
bool _flyingAllowed { DEFAULT_FLYING_ALLOWED };
|
||||
bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED };
|
||||
|
||||
static bool _drawZoneBoundaries;
|
||||
static bool _zonesArePickable;
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
static int FBXGeometryPointerMetaTypeId = qRegisterMetaType<FBXGeometry::Pointer>();
|
||||
int FBXGeometryPointerMetaTypeId = qRegisterMetaType<FBXGeometry::Pointer>();
|
||||
|
||||
QStringList FBXGeometry::getJointNames() const {
|
||||
QStringList names;
|
||||
|
@ -130,9 +130,9 @@ QString FBXGeometry::getModelNameOfMesh(int meshIndex) const {
|
|||
return QString();
|
||||
}
|
||||
|
||||
static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
|
||||
static int fbxAnimationFrameMetaTypeId = qRegisterMetaType<FBXAnimationFrame>();
|
||||
static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<FBXAnimationFrame> >();
|
||||
int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
|
||||
int fbxAnimationFrameMetaTypeId = qRegisterMetaType<FBXAnimationFrame>();
|
||||
int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<FBXAnimationFrame> >();
|
||||
|
||||
|
||||
glm::vec3 parseVec3(const QString& string) {
|
||||
|
|
|
@ -716,9 +716,9 @@ QQmlContext* OffscreenQmlSurface::getRootContext() {
|
|||
}
|
||||
|
||||
Q_DECLARE_METATYPE(std::function<void()>);
|
||||
static auto VoidLambdaType = qRegisterMetaType<std::function<void()>>();
|
||||
auto VoidLambdaType = qRegisterMetaType<std::function<void()>>();
|
||||
Q_DECLARE_METATYPE(std::function<QVariant()>);
|
||||
static auto VariantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
|
||||
auto VariantLambdaType = qRegisterMetaType<std::function<QVariant()>>();
|
||||
|
||||
|
||||
void OffscreenQmlSurface::executeOnUiThread(std::function<void()> function, bool blocking ) {
|
||||
|
|
|
@ -33,7 +33,7 @@ using namespace gpu::gl;
|
|||
|
||||
|
||||
static const QString DEBUG_FLAG("HIFI_ENABLE_OPENGL_45");
|
||||
static bool enableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
|
||||
bool enableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
|
||||
|
||||
Backend* GLBackend::createBackend() {
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
using namespace gpu;
|
||||
|
||||
static int TexturePointerMetaTypeId = qRegisterMetaType<TexturePointer>();
|
||||
int TexturePointerMetaTypeId = qRegisterMetaType<TexturePointer>();
|
||||
|
||||
std::atomic<uint32_t> Texture::_textureCPUCount{ 0 };
|
||||
std::atomic<Texture::Size> Texture::_textureCPUMemoryUsage{ 0 };
|
||||
|
|
|
@ -144,12 +144,21 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
// 4. domain network address (IP or dns resolvable hostname)
|
||||
|
||||
// use our regex'ed helpers to figure out what we're supposed to do with this
|
||||
if (!handleUsername(lookupUrl.authority())) {
|
||||
if (handleUsername(lookupUrl.authority())) {
|
||||
// handled a username for lookup
|
||||
|
||||
// in case we're failing to connect to where we thought this user was
|
||||
// store their username as previous lookup so we can refresh their location via API
|
||||
_previousLookup = lookupUrl;
|
||||
} else {
|
||||
// we're assuming this is either a network address or global place name
|
||||
// check if it is a network address first
|
||||
bool hostChanged;
|
||||
if (handleNetworkAddress(lookupUrl.host()
|
||||
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) {
|
||||
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) {
|
||||
|
||||
// a network address lookup clears the previous lookup since we don't expect to re-attempt it
|
||||
_previousLookup.clear();
|
||||
|
||||
// If the host changed then we have already saved to history
|
||||
if (hostChanged) {
|
||||
|
@ -165,10 +174,16 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
// we may have a path that defines a relative viewpoint - if so we should jump to that now
|
||||
handlePath(path, trigger);
|
||||
} else if (handleDomainID(lookupUrl.host())){
|
||||
// store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info
|
||||
_previousLookup = lookupUrl;
|
||||
|
||||
// no place name - this is probably a domain ID
|
||||
// try to look up the domain ID on the metaverse API
|
||||
attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger);
|
||||
} else {
|
||||
// store this place name as the previous lookup in case we fail to connect and want to refresh API info
|
||||
_previousLookup = lookupUrl;
|
||||
|
||||
// wasn't an address - lookup the place name
|
||||
// we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
|
||||
attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger);
|
||||
|
@ -180,9 +195,13 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
} else if (lookupUrl.toString().startsWith('/')) {
|
||||
qCDebug(networking) << "Going to relative path" << lookupUrl.path();
|
||||
|
||||
// a path lookup clears the previous lookup since we don't expect to re-attempt it
|
||||
_previousLookup.clear();
|
||||
|
||||
// if this is a relative path then handle it as a relative viewpoint
|
||||
handlePath(lookupUrl.path(), trigger, true);
|
||||
emit lookupResultsFinished();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -276,7 +295,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
|||
|
||||
qCDebug(networking) << "Possible domain change required to connect to" << domainHostname
|
||||
<< "on" << domainPort;
|
||||
emit possibleDomainChangeRequired(domainHostname, domainPort);
|
||||
emit possibleDomainChangeRequired(domainHostname, domainPort, domainID);
|
||||
} else {
|
||||
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
|
||||
|
||||
|
@ -295,21 +314,30 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
|||
// set our current root place name to the name that came back
|
||||
const QString PLACE_NAME_KEY = "name";
|
||||
QString placeName = rootMap[PLACE_NAME_KEY].toString();
|
||||
|
||||
if (!placeName.isEmpty()) {
|
||||
if (setHost(placeName, trigger)) {
|
||||
trigger = LookupTrigger::Internal;
|
||||
}
|
||||
|
||||
_placeName = placeName;
|
||||
} else {
|
||||
if (setHost(domainIDString, trigger)) {
|
||||
trigger = LookupTrigger::Internal;
|
||||
}
|
||||
|
||||
// this isn't a place, so clear the place name
|
||||
_placeName.clear();
|
||||
}
|
||||
|
||||
// check if we had a path to override the path returned
|
||||
QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString();
|
||||
|
||||
if (!overridePath.isEmpty()) {
|
||||
handlePath(overridePath, trigger);
|
||||
// make sure we don't re-handle an overriden path if this was a refresh of info from API
|
||||
if (trigger != LookupTrigger::AttemptedRefresh) {
|
||||
handlePath(overridePath, trigger);
|
||||
}
|
||||
} else {
|
||||
// take the path that came back
|
||||
const QString PLACE_PATH_KEY = "path";
|
||||
|
@ -580,13 +608,15 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16
|
|||
bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) {
|
||||
bool hostChanged = setHost(hostname, trigger, port);
|
||||
|
||||
// clear any current place information
|
||||
_rootPlaceID = QUuid();
|
||||
_placeName.clear();
|
||||
|
||||
qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port;
|
||||
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress);
|
||||
|
||||
emit possibleDomainChangeRequired(hostname, port);
|
||||
emit possibleDomainChangeRequired(hostname, port, QUuid());
|
||||
|
||||
return hostChanged;
|
||||
}
|
||||
|
@ -606,6 +636,13 @@ void AddressManager::goToUser(const QString& username) {
|
|||
QByteArray(), nullptr, requestParams);
|
||||
}
|
||||
|
||||
void AddressManager::refreshPreviousLookup() {
|
||||
// if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history)
|
||||
if (!_previousLookup.isEmpty()) {
|
||||
handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh);
|
||||
}
|
||||
}
|
||||
|
||||
void AddressManager::copyAddress() {
|
||||
QApplication::clipboard()->setText(currentAddress().toString());
|
||||
}
|
||||
|
@ -617,7 +654,10 @@ void AddressManager::copyPath() {
|
|||
void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
|
||||
|
||||
// if we're cold starting and this is called for the first address (from settings) we don't do anything
|
||||
if (trigger != LookupTrigger::StartupFromSettings && trigger != LookupTrigger::DomainPathResponse) {
|
||||
if (trigger != LookupTrigger::StartupFromSettings
|
||||
&& trigger != LookupTrigger::DomainPathResponse
|
||||
&& trigger != LookupTrigger::AttemptedRefresh) {
|
||||
|
||||
if (trigger == LookupTrigger::Back) {
|
||||
// we're about to push to the forward stack
|
||||
// if it's currently empty emit our signal to say that going forward is now possible
|
||||
|
|
|
@ -48,7 +48,8 @@ public:
|
|||
Forward,
|
||||
StartupFromSettings,
|
||||
DomainPathResponse,
|
||||
Internal
|
||||
Internal,
|
||||
AttemptedRefresh
|
||||
};
|
||||
|
||||
bool isConnected();
|
||||
|
@ -58,6 +59,7 @@ public:
|
|||
const QString currentPath(bool withOrientation = true) const;
|
||||
|
||||
const QUuid& getRootPlaceID() const { return _rootPlaceID; }
|
||||
const QString& getPlaceName() const { return _placeName; }
|
||||
|
||||
const QString& getHost() const { return _host; }
|
||||
|
||||
|
@ -88,6 +90,8 @@ public slots:
|
|||
|
||||
void goToUser(const QString& username);
|
||||
|
||||
void refreshPreviousLookup();
|
||||
|
||||
void storeCurrentAddress();
|
||||
|
||||
void copyAddress();
|
||||
|
@ -98,7 +102,7 @@ signals:
|
|||
void lookupResultIsOffline();
|
||||
void lookupResultIsNotFound();
|
||||
|
||||
void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort);
|
||||
void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID);
|
||||
void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID);
|
||||
|
||||
void locationChangeRequired(const glm::vec3& newPosition,
|
||||
|
@ -141,6 +145,7 @@ private:
|
|||
|
||||
QString _host;
|
||||
quint16 _port;
|
||||
QString _placeName;
|
||||
QUuid _rootPlaceID;
|
||||
PositionGetter _positionGetter;
|
||||
OrientationGetter _orientationGetter;
|
||||
|
@ -150,6 +155,8 @@ private:
|
|||
quint64 _lastBackPush = 0;
|
||||
|
||||
QString _newHostLookupPath;
|
||||
|
||||
QUrl _previousLookup;
|
||||
};
|
||||
|
||||
#endif // hifi_AddressManager_h
|
||||
|
|
|
@ -28,16 +28,8 @@
|
|||
|
||||
DomainHandler::DomainHandler(QObject* parent) :
|
||||
QObject(parent),
|
||||
_uuid(),
|
||||
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
|
||||
_assignmentUUID(),
|
||||
_connectionToken(),
|
||||
_iceDomainID(),
|
||||
_iceClientID(),
|
||||
_iceServerSockAddr(),
|
||||
_icePeer(this),
|
||||
_isConnected(false),
|
||||
_settingsObject(),
|
||||
_settingsTimer(this)
|
||||
{
|
||||
_sockAddr.setObjectName("DomainServer");
|
||||
|
@ -105,7 +97,7 @@ void DomainHandler::hardReset() {
|
|||
softReset();
|
||||
|
||||
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
|
||||
_iceDomainID = QUuid();
|
||||
_pendingDomainID = QUuid();
|
||||
_iceServerSockAddr = HifiSockAddr();
|
||||
_hostname = QString();
|
||||
_sockAddr.clear();
|
||||
|
@ -140,7 +132,7 @@ void DomainHandler::setUUID(const QUuid& uuid) {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
|
||||
void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) {
|
||||
|
||||
if (hostname != _hostname || _sockAddr.getPort() != port) {
|
||||
// re-set the domain info so that auth information is reloaded
|
||||
|
@ -169,6 +161,8 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
|
|||
// grab the port by reading the string after the colon
|
||||
_sockAddr.setPort(port);
|
||||
}
|
||||
|
||||
_pendingDomainID = domainID;
|
||||
}
|
||||
|
||||
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
|
||||
|
@ -179,7 +173,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
|
|||
// refresh our ICE client UUID to something new
|
||||
_iceClientID = QUuid::createUuid();
|
||||
|
||||
_iceDomainID = id;
|
||||
_pendingDomainID = id;
|
||||
|
||||
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
|
||||
replaceableSockAddr->~HifiSockAddr();
|
||||
|
@ -341,7 +335,7 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
|
|||
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation);
|
||||
|
||||
if (_icePeer.getUUID() != _iceDomainID) {
|
||||
if (_icePeer.getUUID() != _pendingDomainID) {
|
||||
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
|
||||
_icePeer.reset();
|
||||
} else {
|
||||
|
|
|
@ -58,8 +58,8 @@ public:
|
|||
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
|
||||
const QUuid& getICEDomainID() const { return _iceDomainID; }
|
||||
|
||||
const QUuid& getPendingDomainID() const { return _pendingDomainID; }
|
||||
|
||||
const QUuid& getICEClientID() const { return _iceClientID; }
|
||||
|
||||
|
@ -85,7 +85,7 @@ public:
|
|||
|
||||
void softReset();
|
||||
public slots:
|
||||
void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT);
|
||||
void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid());
|
||||
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
|
||||
|
||||
void processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList);
|
||||
|
@ -126,11 +126,11 @@ private:
|
|||
HifiSockAddr _sockAddr;
|
||||
QUuid _assignmentUUID;
|
||||
QUuid _connectionToken;
|
||||
QUuid _iceDomainID;
|
||||
QUuid _pendingDomainID; // ID of domain being connected to, via ICE or direct connection
|
||||
QUuid _iceClientID;
|
||||
HifiSockAddr _iceServerSockAddr;
|
||||
NetworkPeer _icePeer;
|
||||
bool _isConnected;
|
||||
bool _isConnected { false };
|
||||
QJsonObject _settingsObject;
|
||||
QString _pendingPath;
|
||||
QTimer _settingsTimer;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include "HifiSockAddr.h"
|
||||
#include "NetworkLogging.h"
|
||||
|
||||
static int hifiSockAddrMetaTypeId = qRegisterMetaType<HifiSockAddr>();
|
||||
int hifiSockAddrMetaTypeId = qRegisterMetaType<HifiSockAddr>();
|
||||
|
||||
HifiSockAddr::HifiSockAddr() :
|
||||
_address(),
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
|
||||
const QString UNKNOWN_NodeType_t_NAME = "Unknown";
|
||||
|
||||
static int NodePtrMetaTypeId = qRegisterMetaType<Node*>("Node*");
|
||||
static int sharedPtrNodeMetaTypeId = qRegisterMetaType<QSharedPointer<Node>>("QSharedPointer<Node>");
|
||||
static int sharedNodePtrMetaTypeId = qRegisterMetaType<SharedNodePointer>("SharedNodePointer");
|
||||
int NodePtrMetaTypeId = qRegisterMetaType<Node*>("Node*");
|
||||
int sharedPtrNodeMetaTypeId = qRegisterMetaType<QSharedPointer<Node>>("QSharedPointer<Node>");
|
||||
int sharedNodePtrMetaTypeId = qRegisterMetaType<SharedNodePointer>("SharedNodePointer");
|
||||
|
||||
namespace NodeType {
|
||||
QHash<NodeType_t, QString> TypeNameHash;
|
||||
|
|
|
@ -50,7 +50,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
|||
|
||||
// handle domain change signals from AddressManager
|
||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,
|
||||
&_domainHandler, &DomainHandler::setHostnameAndPort);
|
||||
&_domainHandler, &DomainHandler::setSocketAndID);
|
||||
|
||||
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,
|
||||
&_domainHandler, &DomainHandler::setIceServerHostnameAndID);
|
||||
|
@ -314,8 +314,10 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
packetStream << connectUUID;
|
||||
}
|
||||
|
||||
// pack our data to send to the domain-server
|
||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
||||
// pack our data to send to the domain-server including
|
||||
// the hostname information (so the domain-server can see which place name we came in on)
|
||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList()
|
||||
<< DependencyManager::get<AddressManager>()->getPlaceName();
|
||||
|
||||
if (!_domainHandler.isConnected()) {
|
||||
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
|
||||
|
@ -344,6 +346,14 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
// increment the count of un-replied check-ins
|
||||
_numNoReplyDomainCheckIns++;
|
||||
}
|
||||
|
||||
if (!_publicSockAddr.isNull() && !_domainHandler.isConnected() && !_domainHandler.getPendingDomainID().isNull()) {
|
||||
// if we aren't connected to the domain-server, and we have an ID
|
||||
// (that we presume belongs to a domain in the HF Metaverse)
|
||||
// we request connection information for the domain every so often to make sure what we have is up to date
|
||||
|
||||
DependencyManager::get<AddressManager>()->refreshPreviousLookup();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::handleDSPathQuery(const QString& newPath) {
|
||||
|
@ -451,7 +461,7 @@ void NodeList::handleICEConnectionToDomainServer() {
|
|||
|
||||
LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(),
|
||||
_domainHandler.getICEClientID(),
|
||||
_domainHandler.getICEDomainID());
|
||||
_domainHandler.getPendingDomainID());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,7 +474,7 @@ void NodeList::pingPunchForDomainServer() {
|
|||
|
||||
if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) {
|
||||
qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID"
|
||||
<< uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID());
|
||||
<< uuidStringWithoutCurlyBraces(_domainHandler.getPendingDomainID());
|
||||
} else {
|
||||
if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) {
|
||||
// if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
#include "QSharedPointer"
|
||||
|
||||
static int receivedMessageMetaTypeId = qRegisterMetaType<ReceivedMessage*>("ReceivedMessage*");
|
||||
static int sharedPtrReceivedMessageMetaTypeId = qRegisterMetaType<QSharedPointer<ReceivedMessage>>("QSharedPointer<ReceivedMessage>");
|
||||
int receivedMessageMetaTypeId = qRegisterMetaType<ReceivedMessage*>("ReceivedMessage*");
|
||||
int sharedPtrReceivedMessageMetaTypeId = qRegisterMetaType<QSharedPointer<ReceivedMessage>>("QSharedPointer<ReceivedMessage>");
|
||||
|
||||
static const int HEAD_DATA_SIZE = 512;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
using namespace udt;
|
||||
|
||||
static int packetMetaTypeId = qRegisterMetaType<Packet*>("Packet*");
|
||||
int packetMetaTypeId = qRegisterMetaType<Packet*>("Packet*");
|
||||
|
||||
using Key = uint64_t;
|
||||
static const std::array<Key, 4> KEYS {{
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
|
||||
Q_DECLARE_METATYPE(PacketType);
|
||||
static int packetTypeMetaTypeId = qRegisterMetaType<PacketType>();
|
||||
int packetTypeMetaTypeId = qRegisterMetaType<PacketType>();
|
||||
|
||||
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
|
||||
<< PacketType::NodeJsonStats << PacketType::EntityQuery
|
||||
|
@ -47,10 +47,10 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
return VERSION_LIGHT_HAS_FALLOFF_RADIUS;
|
||||
return VERSION_ENTITIES_NO_FLY_ZONES;
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarEntities);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::AssetGetInfo:
|
||||
|
@ -58,6 +58,9 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::AssetUpload:
|
||||
// Removal of extension from Asset requests
|
||||
return 18;
|
||||
case PacketType::DomainConnectRequest:
|
||||
// addition of referring hostname information
|
||||
return 18;
|
||||
default:
|
||||
return 17;
|
||||
}
|
||||
|
|
|
@ -171,10 +171,12 @@ const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54;
|
|||
const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55;
|
||||
const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56;
|
||||
const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
|
||||
const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58;
|
||||
|
||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||
TranslationSupport = 17,
|
||||
SoftAttachmentSupport
|
||||
SoftAttachmentSupport,
|
||||
AvatarEntities
|
||||
};
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
using namespace udt;
|
||||
|
||||
static int packetListMetaTypeId = qRegisterMetaType<PacketList*>("PacketList*");
|
||||
int packetListMetaTypeId = qRegisterMetaType<PacketList*>("PacketList*");
|
||||
|
||||
std::unique_ptr<PacketList> PacketList::create(PacketType packetType, QByteArray extendedHeader,
|
||||
bool isReliable, bool isOrdered) {
|
||||
|
|
|
@ -264,6 +264,10 @@ void CharacterController::setState(State desiredState, const char* reason) {
|
|||
#else
|
||||
void CharacterController::setState(State desiredState) {
|
||||
#endif
|
||||
if (!_flyingAllowed && desiredState == State::Hover) {
|
||||
desiredState = State::InAir;
|
||||
}
|
||||
|
||||
if (desiredState != _state) {
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason;
|
||||
|
@ -644,3 +648,13 @@ bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPositio
|
|||
avatarRigidBodyRotation = bulletToGLM(worldTrans.getRotation());
|
||||
return true;
|
||||
}
|
||||
|
||||
void CharacterController::setFlyingAllowed(bool value) {
|
||||
if (_flyingAllowed != value) {
|
||||
_flyingAllowed = value;
|
||||
|
||||
if (!_flyingAllowed && _state == State::Hover) {
|
||||
SET_STATE(State::InAir, "flying not allowed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,6 +110,9 @@ public:
|
|||
|
||||
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
||||
|
||||
void setFlyingAllowed(bool value);
|
||||
|
||||
|
||||
protected:
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
void setState(State state, const char* reason);
|
||||
|
@ -172,6 +175,8 @@ protected:
|
|||
btRigidBody* _rigidBody { nullptr };
|
||||
uint32_t _pendingFlags { 0 };
|
||||
uint32_t _previousFlags { 0 };
|
||||
|
||||
bool _flyingAllowed { true };
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterControllerInterface_h
|
||||
|
|
|
@ -404,6 +404,11 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
|
|||
assert(_body);
|
||||
assert(entityTreeIsLocked());
|
||||
|
||||
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
|
||||
// don't send updates for someone else's avatarEntities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_entity->actionDataNeedsTransmit()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -547,8 +552,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
|
||||
#endif
|
||||
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties);
|
||||
_entity->setLastBroadcast(usecTimestampNow());
|
||||
EntityTreeElementPointer element = _entity->getElement();
|
||||
EntityTreePointer tree = element ? element->getTree() : nullptr;
|
||||
|
||||
properties.setClientOnly(_entity->getClientOnly());
|
||||
properties.setOwningAvatarID(_entity->getOwningAvatarID());
|
||||
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties);
|
||||
_entity->setLastBroadcast(now);
|
||||
|
||||
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
|
||||
// if they've changed.
|
||||
|
@ -559,8 +570,13 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
EntityItemProperties newQueryCubeProperties;
|
||||
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
|
||||
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties);
|
||||
entityDescendant->setLastBroadcast(usecTimestampNow());
|
||||
|
||||
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
|
||||
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
|
||||
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree,
|
||||
descendant->getID(), newQueryCubeProperties);
|
||||
entityDescendant->setLastBroadcast(now);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -100,10 +100,6 @@ bool Procedural::parseUrl(const QUrl& shaderUrl) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (_shaderUrl == shaderUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_shaderUrl = shaderUrl;
|
||||
_shaderDirty = true;
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@ static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT;
|
|||
|
||||
static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals
|
||||
static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3);
|
||||
static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT16;
|
||||
static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint16);
|
||||
static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32;
|
||||
static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint32);
|
||||
|
||||
void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices) {
|
||||
vertexBuffer->append(vertices);
|
||||
|
@ -112,101 +112,7 @@ void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count
|
|||
}
|
||||
}
|
||||
|
||||
const VertexVector& icosahedronVertices() {
|
||||
static const float phi = (1.0f + sqrtf(5.0f)) / 2.0f;
|
||||
static const float a = 0.5f;
|
||||
static const float b = 1.0f / (2.0f * phi);
|
||||
|
||||
static const VertexVector vertices{ //
|
||||
vec3(0, b, -a), vec3(-b, a, 0), vec3(b, a, 0), //
|
||||
vec3(0, b, a), vec3(b, a, 0), vec3(-b, a, 0), //
|
||||
vec3(0, b, a), vec3(-a, 0, b), vec3(0, -b, a), //
|
||||
vec3(0, b, a), vec3(0, -b, a), vec3(a, 0, b), //
|
||||
vec3(0, b, -a), vec3(a, 0, -b), vec3(0, -b, -a),//
|
||||
vec3(0, b, -a), vec3(0, -b, -a), vec3(-a, 0, -b), //
|
||||
vec3(0, -b, a), vec3(-b, -a, 0), vec3(b, -a, 0), //
|
||||
vec3(0, -b, -a), vec3(b, -a, 0), vec3(-b, -a, 0), //
|
||||
vec3(-b, a, 0), vec3(-a, 0, -b), vec3(-a, 0, b), //
|
||||
vec3(-b, -a, 0), vec3(-a, 0, b), vec3(-a, 0, -b), //
|
||||
vec3(b, a, 0), vec3(a, 0, b), vec3(a, 0, -b), //
|
||||
vec3(b, -a, 0), vec3(a, 0, -b), vec3(a, 0, b), //
|
||||
vec3(0, b, a), vec3(-b, a, 0), vec3(-a, 0, b), //
|
||||
vec3(0, b, a), vec3(a, 0, b), vec3(b, a, 0), //
|
||||
vec3(0, b, -a), vec3(-a, 0, -b), vec3(-b, a, 0), //
|
||||
vec3(0, b, -a), vec3(b, a, 0), vec3(a, 0, -b), //
|
||||
vec3(0, -b, -a), vec3(-b, -a, 0), vec3(-a, 0, -b), //
|
||||
vec3(0, -b, -a), vec3(a, 0, -b), vec3(b, -a, 0), //
|
||||
vec3(0, -b, a), vec3(-a, 0, b), vec3(-b, -a, 0), //
|
||||
vec3(0, -b, a), vec3(b, -a, 0), vec3(a, 0, b)
|
||||
}; //
|
||||
return vertices;
|
||||
}
|
||||
|
||||
const VertexVector& tetrahedronVertices() {
|
||||
static const float a = 1.0f / sqrtf(2.0f);
|
||||
static const auto A = vec3(0, 1, a);
|
||||
static const auto B = vec3(0, -1, a);
|
||||
static const auto C = vec3(1, 0, -a);
|
||||
static const auto D = vec3(-1, 0, -a);
|
||||
static const VertexVector vertices{
|
||||
A, B, C,
|
||||
D, B, A,
|
||||
C, D, A,
|
||||
C, B, D,
|
||||
};
|
||||
return vertices;
|
||||
}
|
||||
|
||||
static const size_t TESSELTATION_MULTIPLIER = 4;
|
||||
static const size_t ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT = 3;
|
||||
static const size_t VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER = 2;
|
||||
|
||||
|
||||
VertexVector tesselate(const VertexVector& startingTriangles, int count) {
|
||||
VertexVector triangles = startingTriangles;
|
||||
if (0 != (triangles.size() % 3)) {
|
||||
throw std::runtime_error("Bad number of vertices for tesselation");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < triangles.size(); ++i) {
|
||||
triangles[i] = glm::normalize(triangles[i]);
|
||||
}
|
||||
|
||||
VertexVector newTriangles;
|
||||
while (count) {
|
||||
newTriangles.clear();
|
||||
// Tesselation takes one triangle and makes it into 4 triangles
|
||||
// See https://en.wikipedia.org/wiki/Space-filling_tree#/media/File:Space_Filling_Tree_Tri_iter_1_2_3.png
|
||||
newTriangles.reserve(triangles.size() * TESSELTATION_MULTIPLIER);
|
||||
for (size_t i = 0; i < triangles.size(); i += VERTICES_PER_TRIANGLE) {
|
||||
const vec3& a = triangles[i];
|
||||
const vec3& b = triangles[i + 1];
|
||||
const vec3& c = triangles[i + 2];
|
||||
vec3 ab = glm::normalize(a + b);
|
||||
vec3 bc = glm::normalize(b + c);
|
||||
vec3 ca = glm::normalize(c + a);
|
||||
|
||||
newTriangles.push_back(a);
|
||||
newTriangles.push_back(ab);
|
||||
newTriangles.push_back(ca);
|
||||
|
||||
newTriangles.push_back(b);
|
||||
newTriangles.push_back(bc);
|
||||
newTriangles.push_back(ab);
|
||||
|
||||
newTriangles.push_back(c);
|
||||
newTriangles.push_back(ca);
|
||||
newTriangles.push_back(bc);
|
||||
|
||||
newTriangles.push_back(ab);
|
||||
newTriangles.push_back(bc);
|
||||
newTriangles.push_back(ca);
|
||||
}
|
||||
triangles.swap(newTriangles);
|
||||
--count;
|
||||
}
|
||||
return triangles;
|
||||
}
|
||||
|
||||
size_t GeometryCache::getShapeTriangleCount(Shape shape) {
|
||||
return _shapes[shape]._indexCount / VERTICES_PER_TRIANGLE;
|
||||
|
@ -220,6 +126,324 @@ size_t GeometryCache::getCubeTriangleCount() {
|
|||
return getShapeTriangleCount(Cube);
|
||||
}
|
||||
|
||||
using Index = uint32_t;
|
||||
using IndexPair = uint64_t;
|
||||
using IndexPairs = std::unordered_set<IndexPair>;
|
||||
|
||||
template <size_t N>
|
||||
using Face = std::array<Index, N>;
|
||||
|
||||
template <size_t N>
|
||||
using FaceVector = std::vector<Face<N>>;
|
||||
|
||||
template <size_t N>
|
||||
struct Solid {
|
||||
VertexVector vertices;
|
||||
FaceVector<N> faces;
|
||||
|
||||
Solid<N>& fitDimension(float newMaxDimension) {
|
||||
float maxDimension = 0;
|
||||
for (const auto& vertex : vertices) {
|
||||
maxDimension = std::max(maxDimension, std::max(std::max(vertex.x, vertex.y), vertex.z));
|
||||
}
|
||||
float multiplier = newMaxDimension / maxDimension;
|
||||
for (auto& vertex : vertices) {
|
||||
vertex *= multiplier;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
vec3 getFaceNormal(size_t faceIndex) const {
|
||||
vec3 result;
|
||||
const auto& face = faces[faceIndex];
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
result += vertices[face[i]];
|
||||
}
|
||||
result /= N;
|
||||
return glm::normalize(result);
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
static size_t triangulatedFaceTriangleCount() {
|
||||
return N - 2;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
static size_t triangulatedFaceIndexCount() {
|
||||
return triangulatedFaceTriangleCount<N>() * VERTICES_PER_TRIANGLE;
|
||||
}
|
||||
|
||||
static IndexPair indexToken(Index a, Index b) {
|
||||
if (a > b) {
|
||||
std::swap(a, b);
|
||||
}
|
||||
return (((IndexPair)a) << 32) | ((IndexPair)b);
|
||||
}
|
||||
|
||||
static Solid<3> tesselate(Solid<3> solid, int count) {
|
||||
float length = glm::length(solid.vertices[0]);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Solid<3> result { solid.vertices, {} };
|
||||
result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 3);
|
||||
for (size_t f = 0; f < solid.faces.size(); ++f) {
|
||||
Index baseVertex = (Index)result.vertices.size();
|
||||
const Face<3>& oldFace = solid.faces[f];
|
||||
const vec3& a = solid.vertices[oldFace[0]];
|
||||
const vec3& b = solid.vertices[oldFace[1]];
|
||||
const vec3& c = solid.vertices[oldFace[2]];
|
||||
vec3 ab = glm::normalize(a + b) * length;
|
||||
vec3 bc = glm::normalize(b + c) * length;
|
||||
vec3 ca = glm::normalize(c + a) * length;
|
||||
result.vertices.push_back(ab);
|
||||
result.vertices.push_back(bc);
|
||||
result.vertices.push_back(ca);
|
||||
result.faces.push_back(Face<3>{ { oldFace[0], baseVertex, baseVertex + 2 } });
|
||||
result.faces.push_back(Face<3>{ { baseVertex, oldFace[1], baseVertex + 1 } });
|
||||
result.faces.push_back(Face<3>{ { baseVertex + 1, oldFace[2], baseVertex + 2 } });
|
||||
result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } });
|
||||
}
|
||||
solid = result;
|
||||
}
|
||||
return solid;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void setupFlatShape(GeometryCache::ShapeData& shapeData, const Solid<N>& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) {
|
||||
Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
|
||||
VertexVector vertices;
|
||||
IndexVector solidIndices, wireIndices;
|
||||
IndexPairs wireSeenIndices;
|
||||
|
||||
size_t faceCount = shape.faces.size();
|
||||
size_t faceIndexCount = triangulatedFaceIndexCount<N>();
|
||||
|
||||
vertices.reserve(N * faceCount * 2);
|
||||
solidIndices.reserve(faceIndexCount * faceCount);
|
||||
|
||||
for (size_t f = 0; f < faceCount; ++f) {
|
||||
const Face<N>& face = shape.faces[f];
|
||||
// Compute the face normal
|
||||
vec3 faceNormal = shape.getFaceNormal(f);
|
||||
|
||||
// Create the vertices for the face
|
||||
for (Index i = 0; i < N; ++i) {
|
||||
Index originalIndex = face[i];
|
||||
vertices.push_back(shape.vertices[originalIndex]);
|
||||
vertices.push_back(faceNormal);
|
||||
}
|
||||
|
||||
// Create the wire indices for unseen edges
|
||||
for (Index i = 0; i < N; ++i) {
|
||||
Index a = i;
|
||||
Index b = (i + 1) % N;
|
||||
auto token = indexToken(face[a], face[b]);
|
||||
if (0 == wireSeenIndices.count(token)) {
|
||||
wireSeenIndices.insert(token);
|
||||
wireIndices.push_back(a + baseVertex);
|
||||
wireIndices.push_back(b + baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the solid face indices
|
||||
for (Index i = 0; i < N - 2; ++i) {
|
||||
solidIndices.push_back(0 + baseVertex);
|
||||
solidIndices.push_back(i + 1 + baseVertex);
|
||||
solidIndices.push_back(i + 2 + baseVertex);
|
||||
}
|
||||
baseVertex += (Index)N;
|
||||
}
|
||||
|
||||
shapeData.setupVertices(vertexBuffer, vertices);
|
||||
shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void setupSmoothShape(GeometryCache::ShapeData& shapeData, const Solid<N>& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) {
|
||||
Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE);
|
||||
|
||||
VertexVector vertices;
|
||||
vertices.reserve(shape.vertices.size() * 2);
|
||||
for (const auto& vertex : shape.vertices) {
|
||||
vertices.push_back(vertex);
|
||||
vertices.push_back(vertex);
|
||||
}
|
||||
|
||||
IndexVector solidIndices, wireIndices;
|
||||
IndexPairs wireSeenIndices;
|
||||
|
||||
size_t faceCount = shape.faces.size();
|
||||
size_t faceIndexCount = triangulatedFaceIndexCount<N>();
|
||||
|
||||
solidIndices.reserve(faceIndexCount * faceCount);
|
||||
|
||||
for (size_t f = 0; f < faceCount; ++f) {
|
||||
const Face<N>& face = shape.faces[f];
|
||||
// Create the wire indices for unseen edges
|
||||
for (Index i = 0; i < N; ++i) {
|
||||
Index a = face[i];
|
||||
Index b = face[(i + 1) % N];
|
||||
auto token = indexToken(a, b);
|
||||
if (0 == wireSeenIndices.count(token)) {
|
||||
wireSeenIndices.insert(token);
|
||||
wireIndices.push_back(a + baseVertex);
|
||||
wireIndices.push_back(b + baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the solid face indices
|
||||
for (Index i = 0; i < N - 2; ++i) {
|
||||
solidIndices.push_back(face[i] + baseVertex);
|
||||
solidIndices.push_back(face[i + 1] + baseVertex);
|
||||
solidIndices.push_back(face[i + 2] + baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
shapeData.setupVertices(vertexBuffer, vertices);
|
||||
shapeData.setupIndices(indexBuffer, solidIndices, wireIndices);
|
||||
}
|
||||
|
||||
// The golden ratio
|
||||
static const float PHI = 1.61803398874f;
|
||||
|
||||
static const Solid<3>& tetrahedron() {
|
||||
static const auto A = vec3(1, 1, 1);
|
||||
static const auto B = vec3(1, -1, -1);
|
||||
static const auto C = vec3(-1, 1, -1);
|
||||
static const auto D = vec3(-1, -1, 1);
|
||||
static const Solid<3> TETRAHEDRON = Solid<3>{
|
||||
{ A, B, C, D },
|
||||
FaceVector<3>{
|
||||
Face<3> { { 0, 1, 2 } },
|
||||
Face<3> { { 3, 1, 0 } },
|
||||
Face<3> { { 2, 3, 0 } },
|
||||
Face<3> { { 2, 1, 3 } },
|
||||
}
|
||||
}.fitDimension(0.5f);
|
||||
return TETRAHEDRON;
|
||||
}
|
||||
|
||||
static const Solid<4>& cube() {
|
||||
static const auto A = vec3(1, 1, 1);
|
||||
static const auto B = vec3(-1, 1, 1);
|
||||
static const auto C = vec3(-1, 1, -1);
|
||||
static const auto D = vec3(1, 1, -1);
|
||||
static const Solid<4> CUBE = Solid<4>{
|
||||
{ A, B, C, D, -A, -B, -C, -D },
|
||||
FaceVector<4>{
|
||||
Face<4> { { 3, 2, 1, 0 } },
|
||||
Face<4> { { 0, 1, 7, 6 } },
|
||||
Face<4> { { 1, 2, 4, 7 } },
|
||||
Face<4> { { 2, 3, 5, 4 } },
|
||||
Face<4> { { 3, 0, 6, 5 } },
|
||||
Face<4> { { 4, 5, 6, 7 } },
|
||||
}
|
||||
}.fitDimension(0.5f);
|
||||
return CUBE;
|
||||
}
|
||||
|
||||
static const Solid<3>& octahedron() {
|
||||
static const auto A = vec3(0, 1, 0);
|
||||
static const auto B = vec3(0, -1, 0);
|
||||
static const auto C = vec3(0, 0, 1);
|
||||
static const auto D = vec3(0, 0, -1);
|
||||
static const auto E = vec3(1, 0, 0);
|
||||
static const auto F = vec3(-1, 0, 0);
|
||||
static const Solid<3> OCTAHEDRON = Solid<3>{
|
||||
{ A, B, C, D, E, F},
|
||||
FaceVector<3> {
|
||||
Face<3> { { 0, 2, 4, } },
|
||||
Face<3> { { 0, 4, 3, } },
|
||||
Face<3> { { 0, 3, 5, } },
|
||||
Face<3> { { 0, 5, 2, } },
|
||||
Face<3> { { 1, 4, 2, } },
|
||||
Face<3> { { 1, 3, 4, } },
|
||||
Face<3> { { 1, 5, 3, } },
|
||||
Face<3> { { 1, 2, 5, } },
|
||||
}
|
||||
}.fitDimension(0.5f);
|
||||
return OCTAHEDRON;
|
||||
}
|
||||
|
||||
static const Solid<5>& dodecahedron() {
|
||||
static const float P = PHI;
|
||||
static const float IP = 1.0f / PHI;
|
||||
static const vec3 A = vec3(IP, P, 0);
|
||||
static const vec3 B = vec3(-IP, P, 0);
|
||||
static const vec3 C = vec3(-1, 1, 1);
|
||||
static const vec3 D = vec3(0, IP, P);
|
||||
static const vec3 E = vec3(1, 1, 1);
|
||||
static const vec3 F = vec3(1, 1, -1);
|
||||
static const vec3 G = vec3(-1, 1, -1);
|
||||
static const vec3 H = vec3(-P, 0, IP);
|
||||
static const vec3 I = vec3(0, -IP, P);
|
||||
static const vec3 J = vec3(P, 0, IP);
|
||||
|
||||
static const Solid<5> DODECAHEDRON = Solid<5>{
|
||||
{
|
||||
A, B, C, D, E, F, G, H, I, J,
|
||||
-A, -B, -C, -D, -E, -F, -G, -H, -I, -J,
|
||||
},
|
||||
FaceVector<5> {
|
||||
Face<5> { { 0, 1, 2, 3, 4 } },
|
||||
Face<5> { { 0, 5, 18, 6, 1 } },
|
||||
Face<5> { { 1, 6, 19, 7, 2 } },
|
||||
Face<5> { { 2, 7, 15, 8, 3 } },
|
||||
Face<5> { { 3, 8, 16, 9, 4 } },
|
||||
Face<5> { { 4, 9, 17, 5, 0 } },
|
||||
Face<5> { { 14, 13, 12, 11, 10 } },
|
||||
Face<5> { { 11, 16, 8, 15, 10 } },
|
||||
Face<5> { { 12, 17, 9, 16, 11 } },
|
||||
Face<5> { { 13, 18, 5, 17, 12 } },
|
||||
Face<5> { { 14, 19, 6, 18, 13 } },
|
||||
Face<5> { { 10, 15, 7, 19, 14 } },
|
||||
}
|
||||
}.fitDimension(0.5f);
|
||||
return DODECAHEDRON;
|
||||
}
|
||||
|
||||
static const Solid<3>& icosahedron() {
|
||||
static const float N = 1.0f / PHI;
|
||||
static const float P = 1.0f;
|
||||
static const auto A = vec3(N, P, 0);
|
||||
static const auto B = vec3(-N, P, 0);
|
||||
static const auto C = vec3(0, N, P);
|
||||
static const auto D = vec3(P, 0, N);
|
||||
static const auto E = vec3(P, 0, -N);
|
||||
static const auto F = vec3(0, N, -P);
|
||||
|
||||
static const Solid<3> ICOSAHEDRON = Solid<3> {
|
||||
{
|
||||
A, B, C, D, E, F,
|
||||
-A, -B, -C, -D, -E, -F,
|
||||
},
|
||||
FaceVector<3> {
|
||||
Face<3> { { 1, 2, 0 } },
|
||||
Face<3> { { 2, 3, 0 } },
|
||||
Face<3> { { 3, 4, 0 } },
|
||||
Face<3> { { 4, 5, 0 } },
|
||||
Face<3> { { 5, 1, 0 } },
|
||||
|
||||
Face<3> { { 1, 10, 2 } },
|
||||
Face<3> { { 11, 2, 10 } },
|
||||
Face<3> { { 2, 11, 3 } },
|
||||
Face<3> { { 7, 3, 11 } },
|
||||
Face<3> { { 3, 7, 4 } },
|
||||
Face<3> { { 8, 4, 7 } },
|
||||
Face<3> { { 4, 8, 5 } },
|
||||
Face<3> { { 9, 5, 8 } },
|
||||
Face<3> { { 5, 9, 1 } },
|
||||
Face<3> { { 10, 1, 9 } },
|
||||
|
||||
Face<3> { { 8, 7, 6 } },
|
||||
Face<3> { { 9, 8, 6 } },
|
||||
Face<3> { { 10, 9, 6 } },
|
||||
Face<3> { { 11, 10, 6 } },
|
||||
Face<3> { { 7, 11, 6 } },
|
||||
}
|
||||
}.fitDimension(0.5f);
|
||||
return ICOSAHEDRON;
|
||||
}
|
||||
|
||||
// FIXME solids need per-face vertices, but smooth shaded
|
||||
// components do not. Find a way to support using draw elements
|
||||
|
@ -229,229 +453,28 @@ size_t GeometryCache::getCubeTriangleCount() {
|
|||
void GeometryCache::buildShapes() {
|
||||
auto vertexBuffer = std::make_shared<gpu::Buffer>();
|
||||
auto indexBuffer = std::make_shared<gpu::Buffer>();
|
||||
size_t startingIndex = 0;
|
||||
|
||||
// Cube
|
||||
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
|
||||
{
|
||||
ShapeData& shapeData = _shapes[Cube];
|
||||
VertexVector vertices;
|
||||
// front
|
||||
vertices.push_back(vec3(1, 1, 1));
|
||||
vertices.push_back(vec3(0, 0, 1));
|
||||
vertices.push_back(vec3(-1, 1, 1));
|
||||
vertices.push_back(vec3(0, 0, 1));
|
||||
vertices.push_back(vec3(-1, -1, 1));
|
||||
vertices.push_back(vec3(0, 0, 1));
|
||||
vertices.push_back(vec3(1, -1, 1));
|
||||
vertices.push_back(vec3(0, 0, 1));
|
||||
|
||||
// right
|
||||
vertices.push_back(vec3(1, 1, 1));
|
||||
vertices.push_back(vec3(1, 0, 0));
|
||||
vertices.push_back(vec3(1, -1, 1));
|
||||
vertices.push_back(vec3(1, 0, 0));
|
||||
vertices.push_back(vec3(1, -1, -1));
|
||||
vertices.push_back(vec3(1, 0, 0));
|
||||
vertices.push_back(vec3(1, 1, -1));
|
||||
vertices.push_back(vec3(1, 0, 0));
|
||||
|
||||
// top
|
||||
vertices.push_back(vec3(1, 1, 1));
|
||||
vertices.push_back(vec3(0, 1, 0));
|
||||
vertices.push_back(vec3(1, 1, -1));
|
||||
vertices.push_back(vec3(0, 1, 0));
|
||||
vertices.push_back(vec3(-1, 1, -1));
|
||||
vertices.push_back(vec3(0, 1, 0));
|
||||
vertices.push_back(vec3(-1, 1, 1));
|
||||
vertices.push_back(vec3(0, 1, 0));
|
||||
|
||||
// left
|
||||
vertices.push_back(vec3(-1, 1, 1));
|
||||
vertices.push_back(vec3(-1, 0, 0));
|
||||
vertices.push_back(vec3(-1, 1, -1));
|
||||
vertices.push_back(vec3(-1, 0, 0));
|
||||
vertices.push_back(vec3(-1, -1, -1));
|
||||
vertices.push_back(vec3(-1, 0, 0));
|
||||
vertices.push_back(vec3(-1, -1, 1));
|
||||
vertices.push_back(vec3(-1, 0, 0));
|
||||
|
||||
// bottom
|
||||
vertices.push_back(vec3(-1, -1, -1));
|
||||
vertices.push_back(vec3(0, -1, 0));
|
||||
vertices.push_back(vec3(1, -1, -1));
|
||||
vertices.push_back(vec3(0, -1, 0));
|
||||
vertices.push_back(vec3(1, -1, 1));
|
||||
vertices.push_back(vec3(0, -1, 0));
|
||||
vertices.push_back(vec3(-1, -1, 1));
|
||||
vertices.push_back(vec3(0, -1, 0));
|
||||
|
||||
// back
|
||||
vertices.push_back(vec3(1, -1, -1));
|
||||
vertices.push_back(vec3(0, 0, -1));
|
||||
vertices.push_back(vec3(-1, -1, -1));
|
||||
vertices.push_back(vec3(0, 0, -1));
|
||||
vertices.push_back(vec3(-1, 1, -1));
|
||||
vertices.push_back(vec3(0, 0, -1));
|
||||
vertices.push_back(vec3(1, 1, -1));
|
||||
vertices.push_back(vec3(0, 0, -1));
|
||||
|
||||
static const size_t VERTEX_FORMAT_SIZE = 2;
|
||||
static const size_t VERTEX_OFFSET = 0;
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i) {
|
||||
auto vertexIndex = i;
|
||||
// Make a unit cube by having the vertices (at index N)
|
||||
// while leaving the normals (at index N + 1) alone
|
||||
if (VERTEX_OFFSET == vertexIndex % VERTEX_FORMAT_SIZE) {
|
||||
vertices[vertexIndex] *= 0.5f;
|
||||
}
|
||||
}
|
||||
shapeData.setupVertices(_shapeVertices, vertices);
|
||||
|
||||
IndexVector indices{
|
||||
0, 1, 2, 2, 3, 0, // front
|
||||
4, 5, 6, 6, 7, 4, // right
|
||||
8, 9, 10, 10, 11, 8, // top
|
||||
12, 13, 14, 14, 15, 12, // left
|
||||
16, 17, 18, 18, 19, 16, // bottom
|
||||
20, 21, 22, 22, 23, 20 // back
|
||||
};
|
||||
for (auto& index : indices) {
|
||||
index += (uint16_t)startingIndex;
|
||||
}
|
||||
|
||||
IndexVector wireIndices{
|
||||
0, 1, 1, 2, 2, 3, 3, 0, // front
|
||||
20, 21, 21, 22, 22, 23, 23, 20, // back
|
||||
0, 23, 1, 22, 2, 21, 3, 20 // sides
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < wireIndices.size(); ++i) {
|
||||
indices[i] += (uint16_t)startingIndex;
|
||||
}
|
||||
|
||||
shapeData.setupIndices(_shapeIndices, indices, wireIndices);
|
||||
}
|
||||
|
||||
setupFlatShape(_shapes[Cube], cube(), _shapeVertices, _shapeIndices);
|
||||
// Tetrahedron
|
||||
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
|
||||
{
|
||||
ShapeData& shapeData = _shapes[Tetrahedron];
|
||||
size_t vertexCount = 4;
|
||||
VertexVector vertices;
|
||||
{
|
||||
VertexVector originalVertices = tetrahedronVertices();
|
||||
vertexCount = originalVertices.size();
|
||||
vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER);
|
||||
for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) {
|
||||
auto triangleStartIndex = i;
|
||||
vec3 faceNormal;
|
||||
for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
|
||||
auto triangleVertexIndex = j;
|
||||
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
|
||||
faceNormal += originalVertices[vertexIndex];
|
||||
}
|
||||
faceNormal = glm::normalize(faceNormal);
|
||||
for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
|
||||
auto triangleVertexIndex = j;
|
||||
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
|
||||
vertices.push_back(glm::normalize(originalVertices[vertexIndex]));
|
||||
vertices.push_back(faceNormal);
|
||||
}
|
||||
}
|
||||
}
|
||||
shapeData.setupVertices(_shapeVertices, vertices);
|
||||
|
||||
IndexVector indices;
|
||||
for (size_t i = 0; i < vertexCount; i += VERTICES_PER_TRIANGLE) {
|
||||
auto triangleStartIndex = i;
|
||||
for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
|
||||
auto triangleVertexIndex = j;
|
||||
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
|
||||
indices.push_back((uint16_t)(vertexIndex + startingIndex));
|
||||
}
|
||||
}
|
||||
|
||||
IndexVector wireIndices{
|
||||
0, 1, 1, 2, 2, 0,
|
||||
0, 3, 1, 3, 2, 3,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < wireIndices.size(); ++i) {
|
||||
wireIndices[i] += (uint16_t)startingIndex;
|
||||
}
|
||||
|
||||
shapeData.setupIndices(_shapeIndices, indices, wireIndices);
|
||||
}
|
||||
|
||||
setupFlatShape(_shapes[Tetrahedron], tetrahedron(), _shapeVertices, _shapeIndices);
|
||||
// Icosahedron
|
||||
setupFlatShape(_shapes[Icosahedron], icosahedron(), _shapeVertices, _shapeIndices);
|
||||
// Octahedron
|
||||
setupFlatShape(_shapes[Octahedron], octahedron(), _shapeVertices, _shapeIndices);
|
||||
// Dodecahedron
|
||||
setupFlatShape(_shapes[Dodecahedron], dodecahedron(), _shapeVertices, _shapeIndices);
|
||||
|
||||
// Sphere
|
||||
// FIXME this uses way more vertices than required. Should find a way to calculate the indices
|
||||
// using shared vertices for better vertex caching
|
||||
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
|
||||
{
|
||||
ShapeData& shapeData = _shapes[Sphere];
|
||||
VertexVector vertices;
|
||||
IndexVector indices;
|
||||
{
|
||||
VertexVector originalVertices = tesselate(icosahedronVertices(), ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT);
|
||||
vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER);
|
||||
for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) {
|
||||
auto triangleStartIndex = i;
|
||||
for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
|
||||
auto triangleVertexIndex = j;
|
||||
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
|
||||
const auto& vertex = originalVertices[i + j];
|
||||
// Spheres use the same values for vertices and normals
|
||||
vertices.push_back(vertex);
|
||||
vertices.push_back(vertex);
|
||||
indices.push_back((uint16_t)(vertexIndex + startingIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shapeData.setupVertices(_shapeVertices, vertices);
|
||||
// FIXME don't use solid indices for wire drawing.
|
||||
shapeData.setupIndices(_shapeIndices, indices, indices);
|
||||
}
|
||||
|
||||
// Icosahedron
|
||||
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
|
||||
{
|
||||
ShapeData& shapeData = _shapes[Icosahedron];
|
||||
|
||||
VertexVector vertices;
|
||||
IndexVector indices;
|
||||
{
|
||||
const VertexVector& originalVertices = icosahedronVertices();
|
||||
vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER);
|
||||
for (size_t i = 0; i < originalVertices.size(); i += 3) {
|
||||
auto triangleStartIndex = i;
|
||||
vec3 faceNormal;
|
||||
for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
|
||||
auto triangleVertexIndex = j;
|
||||
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
|
||||
faceNormal += originalVertices[vertexIndex];
|
||||
}
|
||||
faceNormal = glm::normalize(faceNormal);
|
||||
for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) {
|
||||
auto triangleVertexIndex = j;
|
||||
auto vertexIndex = triangleStartIndex + triangleVertexIndex;
|
||||
vertices.push_back(glm::normalize(originalVertices[vertexIndex]));
|
||||
vertices.push_back(faceNormal);
|
||||
indices.push_back((uint16_t)(vertexIndex + startingIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shapeData.setupVertices(_shapeVertices, vertices);
|
||||
// FIXME don't use solid indices for wire drawing.
|
||||
shapeData.setupIndices(_shapeIndices, indices, indices);
|
||||
}
|
||||
Solid<3> sphere = icosahedron();
|
||||
sphere = tesselate(sphere, ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT);
|
||||
sphere.fitDimension(1.0f);
|
||||
setupSmoothShape(_shapes[Sphere], sphere, _shapeVertices, _shapeIndices);
|
||||
|
||||
// Line
|
||||
startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE;
|
||||
{
|
||||
Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE);
|
||||
ShapeData& shapeData = _shapes[Line];
|
||||
shapeData.setupVertices(_shapeVertices, VertexVector{
|
||||
vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0),
|
||||
|
@ -459,9 +482,8 @@ void GeometryCache::buildShapes() {
|
|||
});
|
||||
IndexVector wireIndices;
|
||||
// Only two indices
|
||||
wireIndices.push_back(0 + (uint16_t)startingIndex);
|
||||
wireIndices.push_back(1 + (uint16_t)startingIndex);
|
||||
|
||||
wireIndices.push_back(0 + baseVertex);
|
||||
wireIndices.push_back(1 + baseVertex);
|
||||
shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices);
|
||||
}
|
||||
|
||||
|
@ -1801,10 +1823,10 @@ uint32_t toCompactColor(const glm::vec4& color) {
|
|||
|
||||
static const size_t INSTANCE_COLOR_BUFFER = 0;
|
||||
|
||||
void renderInstances(const std::string& name, gpu::Batch& batch, const glm::vec4& color, bool isWire,
|
||||
void renderInstances(gpu::Batch& batch, const glm::vec4& color, bool isWire,
|
||||
const render::ShapePipelinePointer& pipeline, GeometryCache::Shape shape) {
|
||||
// Add pipeline to name
|
||||
std::string instanceName = name + std::to_string(std::hash<render::ShapePipelinePointer>()(pipeline));
|
||||
std::string instanceName = (isWire ? "wire_shapes_" : "solid_shapes_") + std::to_string(shape) + "_" + std::to_string(std::hash<render::ShapePipelinePointer>()(pipeline));
|
||||
|
||||
// Add color to named buffer
|
||||
{
|
||||
|
@ -1826,14 +1848,16 @@ void renderInstances(const std::string& name, gpu::Batch& batch, const glm::vec4
|
|||
});
|
||||
}
|
||||
|
||||
void GeometryCache::renderSolidShapeInstance(gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
|
||||
renderInstances(batch, color, false, pipeline, shape);
|
||||
}
|
||||
|
||||
void GeometryCache::renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
renderInstances(INSTANCE_NAME, batch, color, false, pipeline, GeometryCache::Sphere);
|
||||
renderInstances(batch, color, false, pipeline, GeometryCache::Sphere);
|
||||
}
|
||||
|
||||
void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
renderInstances(INSTANCE_NAME, batch, color, true, pipeline, GeometryCache::Sphere);
|
||||
renderInstances(batch, color, true, pipeline, GeometryCache::Sphere);
|
||||
}
|
||||
|
||||
// Enable this in a debug build to cause 'box' entities to iterate through all the
|
||||
|
@ -1841,8 +1865,6 @@ void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4&
|
|||
//#define DEBUG_SHAPES
|
||||
|
||||
void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
|
||||
#ifdef DEBUG_SHAPES
|
||||
static auto startTime = usecTimestampNow();
|
||||
renderInstances(INSTANCE_NAME, batch, color, pipeline, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) {
|
||||
|
@ -1876,11 +1898,11 @@ void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4&
|
|||
}
|
||||
});
|
||||
#else
|
||||
renderInstances(INSTANCE_NAME, batch, color, false, pipeline, GeometryCache::Cube);
|
||||
renderInstances(batch, color, false, pipeline, GeometryCache::Cube);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GeometryCache::renderWireCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) {
|
||||
static const std::string INSTANCE_NAME = __FUNCTION__;
|
||||
renderInstances(INSTANCE_NAME, batch, color, true, pipeline, GeometryCache::Cube);
|
||||
renderInstances(batch, color, true, pipeline, GeometryCache::Cube);
|
||||
}
|
||||
|
|
|
@ -121,8 +121,8 @@ inline uint qHash(const Vec4PairVec4Pair& v, uint seed) {
|
|||
seed);
|
||||
}
|
||||
|
||||
using IndexVector = std::vector<uint32_t>;
|
||||
using VertexVector = std::vector<glm::vec3>;
|
||||
using IndexVector = std::vector<uint16_t>;
|
||||
|
||||
/// Stores cached geometry.
|
||||
class GeometryCache : public Dependency {
|
||||
|
@ -137,7 +137,7 @@ public:
|
|||
Cube,
|
||||
Sphere,
|
||||
Tetrahedron,
|
||||
Octahetron,
|
||||
Octahedron,
|
||||
Dodecahedron,
|
||||
Icosahedron,
|
||||
Torus,
|
||||
|
@ -163,6 +163,13 @@ public:
|
|||
void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer);
|
||||
void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer);
|
||||
|
||||
void renderSolidShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec4& color = glm::vec4(1),
|
||||
const render::ShapePipelinePointer& pipeline = _simplePipeline);
|
||||
void renderSolidShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec3& color,
|
||||
const render::ShapePipelinePointer& pipeline = _simplePipeline) {
|
||||
renderSolidShapeInstance(batch, shape, glm::vec4(color, 1.0f), pipeline);
|
||||
}
|
||||
|
||||
void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color,
|
||||
const render::ShapePipelinePointer& pipeline = _simplePipeline);
|
||||
void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec3& color,
|
||||
|
|
|
@ -29,9 +29,9 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
static int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>();
|
||||
static int weakNetworkGeometryPointerTypeId = qRegisterMetaType<std::weak_ptr<NetworkGeometry> >();
|
||||
static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
|
||||
int nakedModelPointerTypeId = qRegisterMetaType<ModelPointer>();
|
||||
int weakNetworkGeometryPointerTypeId = qRegisterMetaType<std::weak_ptr<NetworkGeometry> >();
|
||||
int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
|
||||
float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
|
||||
#define HTTP_INVALID_COM "http://invalid.com"
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
||||
|
||||
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
||||
static int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
|
||||
int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>();
|
||||
|
||||
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
||||
QString message = "";
|
||||
|
|
|
@ -21,17 +21,17 @@
|
|||
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
static int vec4MetaTypeId = qRegisterMetaType<glm::vec4>();
|
||||
static int vec3MetaTypeId = qRegisterMetaType<glm::vec3>();
|
||||
static int qVectorVec3MetaTypeId = qRegisterMetaType<QVector<glm::vec3>>();
|
||||
static int qVectorQuatMetaTypeId = qRegisterMetaType<QVector<glm::quat>>();
|
||||
static int qVectorBoolMetaTypeId = qRegisterMetaType<QVector<bool>>();
|
||||
static int vec2MetaTypeId = qRegisterMetaType<glm::vec2>();
|
||||
static int quatMetaTypeId = qRegisterMetaType<glm::quat>();
|
||||
static int xColorMetaTypeId = qRegisterMetaType<xColor>();
|
||||
static int pickRayMetaTypeId = qRegisterMetaType<PickRay>();
|
||||
static int collisionMetaTypeId = qRegisterMetaType<Collision>();
|
||||
static int qMapURLStringMetaTypeId = qRegisterMetaType<QMap<QUrl,QString>>();
|
||||
int vec4MetaTypeId = qRegisterMetaType<glm::vec4>();
|
||||
int vec3MetaTypeId = qRegisterMetaType<glm::vec3>();
|
||||
int qVectorVec3MetaTypeId = qRegisterMetaType<QVector<glm::vec3>>();
|
||||
int qVectorQuatMetaTypeId = qRegisterMetaType<QVector<glm::quat>>();
|
||||
int qVectorBoolMetaTypeId = qRegisterMetaType<QVector<bool>>();
|
||||
int vec2MetaTypeId = qRegisterMetaType<glm::vec2>();
|
||||
int quatMetaTypeId = qRegisterMetaType<glm::quat>();
|
||||
int xColorMetaTypeId = qRegisterMetaType<xColor>();
|
||||
int pickRayMetaTypeId = qRegisterMetaType<PickRay>();
|
||||
int collisionMetaTypeId = qRegisterMetaType<Collision>();
|
||||
int qMapURLStringMetaTypeId = qRegisterMetaType<QMap<QUrl,QString>>();
|
||||
|
||||
void registerMetaTypes(QScriptEngine* engine) {
|
||||
qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue);
|
||||
|
|
|
@ -57,7 +57,7 @@ public:
|
|||
|
||||
void addSample(T sample) {
|
||||
if (numSamples > 0) {
|
||||
average = (sample * WEIGHTING) + (average * ONE_MINUS_WEIGHTING);
|
||||
average = ((float)sample * WEIGHTING) + ((float)average * ONE_MINUS_WEIGHTING);
|
||||
} else {
|
||||
average = sample;
|
||||
}
|
||||
|
|
|
@ -590,6 +590,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
|
|||
// let the parent class do it's work
|
||||
bool result = OffscreenQmlSurface::eventFilter(originalDestination, event);
|
||||
|
||||
|
||||
// Check if this is a key press/release event that might need special attention
|
||||
auto type = event->type();
|
||||
if (type != QEvent::KeyPress && type != QEvent::KeyRelease) {
|
||||
|
@ -597,7 +598,8 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
|
|||
}
|
||||
|
||||
QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);
|
||||
bool& pressed = _pressedKeys[keyEvent->key()];
|
||||
auto key = keyEvent->key();
|
||||
bool& pressed = _pressedKeys[key];
|
||||
|
||||
// Keep track of which key press events the QML has accepted
|
||||
if (result && QEvent::KeyPress == type) {
|
||||
|
@ -607,7 +609,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
|
|||
// QML input elements absorb key press, but apparently not key release.
|
||||
// therefore we want to ensure that key release events for key presses that were
|
||||
// accepted by the QML layer are suppressed
|
||||
if (!result && type == QEvent::KeyRelease && pressed) {
|
||||
if (type == QEvent::KeyRelease && pressed) {
|
||||
pressed = false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -15,161 +15,11 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var NUM_ENTITIES = 20000; // number of entities to spawn
|
||||
var ENTITY_SPAWN_LIMIT = 1000;
|
||||
var ENTITY_SPAWN_INTERVAL = 0.1;
|
||||
Script.include("./entitySpawnTool.js");
|
||||
|
||||
var UPDATE_INTERVAL = 0.05; // Re-randomize the entity's position every x seconds / ms
|
||||
var ENTITY_LIFETIME = 30; // Entity timeout (when/if we crash, we need the entities to delete themselves)
|
||||
var KEEPALIVE_INTERVAL = 5; // Refreshes the timeout every X seconds
|
||||
|
||||
var RADIUS = 5.0; // Spawn within this radius (square)
|
||||
var Y_OFFSET = 1.5; // Spawn at an offset below the avatar
|
||||
var TEST_ENTITY_NAME = "EntitySpawnTest";
|
||||
|
||||
(function () {
|
||||
this.makeEntity = function (properties) {
|
||||
var entity = Entities.addEntity(properties);
|
||||
// print("spawning entity: " + JSON.stringify(properties));
|
||||
|
||||
return {
|
||||
update: function (properties) {
|
||||
Entities.editEntity(entity, properties);
|
||||
},
|
||||
destroy: function () {
|
||||
Entities.deleteEntity(entity)
|
||||
},
|
||||
getAge: function () {
|
||||
return Entities.getEntityProperties(entity).age;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.randomPositionXZ = function (center, radius) {
|
||||
return {
|
||||
x: center.x + (Math.random() * radius * 2.0) - radius,
|
||||
y: center.y,
|
||||
z: center.z + (Math.random() * radius * 2.0) - radius
|
||||
};
|
||||
}
|
||||
this.randomColor = function () {
|
||||
var shade = Math.floor(Math.random() * 255);
|
||||
var hue = Math.floor(Math.random() * (255 - shade));
|
||||
|
||||
return {
|
||||
red: shade + hue,
|
||||
green: shade,
|
||||
blue: shade
|
||||
};
|
||||
}
|
||||
this.randomDimensions = function () {
|
||||
return {
|
||||
x: 0.1 + Math.random() * 0.5,
|
||||
y: 0.1 + Math.random() * 0.1,
|
||||
z: 0.1 + Math.random() * 0.5
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
(function () {
|
||||
var entities = [];
|
||||
var entitiesToCreate = 0;
|
||||
var entitiesSpawned = 0;
|
||||
|
||||
|
||||
function clear () {
|
||||
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||
var that = this;
|
||||
ids.forEach(function(id) {
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == TEST_ENTITY_NAME) {
|
||||
Entities.deleteEntity(id);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
function createEntities () {
|
||||
print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")");
|
||||
entitiesToCreate = NUM_ENTITIES;
|
||||
Script.update.connect(spawnEntities);
|
||||
}
|
||||
|
||||
var spawnTimer = 0.0;
|
||||
function spawnEntities (dt) {
|
||||
if (entitiesToCreate <= 0) {
|
||||
Script.update.disconnect(spawnEntities);
|
||||
print("Finished spawning entities");
|
||||
}
|
||||
else if ((spawnTimer -= dt) < 0.0){
|
||||
spawnTimer = ENTITY_SPAWN_INTERVAL;
|
||||
|
||||
var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT);
|
||||
print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")");
|
||||
|
||||
entitiesToCreate -= n;
|
||||
|
||||
var center = MyAvatar.position;
|
||||
center.y -= Y_OFFSET;
|
||||
|
||||
for (; n > 0; --n) {
|
||||
entities.push(makeEntity({
|
||||
type: "Box",
|
||||
name: TEST_ENTITY_NAME,
|
||||
position: randomPositionXZ(center, RADIUS),
|
||||
color: randomColor(),
|
||||
dimensions: randomDimensions(),
|
||||
lifetime: ENTITY_LIFETIME
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function despawnEntities () {
|
||||
print("despawning entities");
|
||||
entities.forEach(function (entity) {
|
||||
entity.destroy();
|
||||
});
|
||||
entities = [];
|
||||
}
|
||||
|
||||
var keepAliveTimer = 0.0;
|
||||
var updateTimer = 0.0;
|
||||
|
||||
// Runs the following entity updates:
|
||||
// a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and
|
||||
// b) re-randomizes its position every UPDATE_INTERVAL seconds.
|
||||
// This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again).
|
||||
function updateEntities (dt) {
|
||||
var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false;
|
||||
var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false;
|
||||
|
||||
if (updateLifetime || updateProperties) {
|
||||
var center = MyAvatar.position;
|
||||
center.y -= Y_OFFSET;
|
||||
|
||||
entities.forEach((updateLifetime && updateProperties && function (entity) {
|
||||
entity.update({
|
||||
lifetime: entity.getAge() + ENTITY_LIFETIME,
|
||||
position: randomPositionXZ(center, RADIUS)
|
||||
});
|
||||
}) || (updateLifetime && function (entity) {
|
||||
entity.update({
|
||||
lifetime: entity.getAge() + ENTITY_LIFETIME
|
||||
});
|
||||
}) || (updateProperties && function (entity) {
|
||||
entity.update({
|
||||
position: randomPositionXZ(center, RADIUS)
|
||||
});
|
||||
}) || null, this);
|
||||
}
|
||||
}
|
||||
|
||||
function init () {
|
||||
Script.update.disconnect(init);
|
||||
clear();
|
||||
createEntities();
|
||||
Script.update.connect(updateEntities);
|
||||
Script.scriptEnding.connect(despawnEntities);
|
||||
}
|
||||
Script.update.connect(init);
|
||||
})();
|
||||
ENTITY_SPAWNER({
|
||||
count: 20000,
|
||||
spawnLimit: 1000,
|
||||
spawnInterval: 0.1,
|
||||
updateInterval: 0.05
|
||||
});
|
||||
|
|
184
scripts/developer/tests/entitySpawnTool.js
Normal file
184
scripts/developer/tests/entitySpawnTool.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
// entityEditStressTest.js
|
||||
//
|
||||
// Created by Seiji Emery on 8/31/15
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Stress tests the client + server-side entity trees by spawning huge numbers of entities in
|
||||
// close proximity to your avatar and updating them continuously (ie. applying position edits),
|
||||
// with the intent of discovering crashes and other bugs related to the entity, scripting,
|
||||
// rendering, networking, and/or physics subsystems.
|
||||
//
|
||||
// This script was originally created to find + diagnose an a clientside crash caused by improper
|
||||
// locking of the entity tree, but can be reused for other purposes.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
ENTITY_SPAWNER = function (properties) {
|
||||
properties = properties || {};
|
||||
var RADIUS = properties.radius || 5.0; // Spawn within this radius (square)
|
||||
var Y_OFFSET = properties.yOffset || 1.5; // Spawn at an offset below the avatar
|
||||
var TEST_ENTITY_NAME = properties.entityName || "EntitySpawnTest";
|
||||
|
||||
var NUM_ENTITIES = properties.count || 1000; // number of entities to spawn
|
||||
var ENTITY_SPAWN_LIMIT = properties.spawnLimit || 100;
|
||||
var ENTITY_SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 1.0;
|
||||
|
||||
var UPDATE_INTERVAL = properties.updateInterval || properties.interval || 0.1; // Re-randomize the entity's position every x seconds / ms
|
||||
var ENTITY_LIFETIME = properties.lifetime || 30; // Entity timeout (when/if we crash, we need the entities to delete themselves)
|
||||
var KEEPALIVE_INTERVAL = properties.keepAlive || 5; // Refreshes the timeout every X seconds
|
||||
var UPDATES = properties.updates || false
|
||||
var SHAPES = properties.shapes || ["Icosahedron", "Tetrahedron", "Cube", "Sphere" ];
|
||||
|
||||
function makeEntity(properties) {
|
||||
var entity = Entities.addEntity(properties);
|
||||
// print("spawning entity: " + JSON.stringify(properties));
|
||||
|
||||
return {
|
||||
update: function (properties) {
|
||||
Entities.editEntity(entity, properties);
|
||||
},
|
||||
destroy: function () {
|
||||
Entities.deleteEntity(entity)
|
||||
},
|
||||
getAge: function () {
|
||||
return Entities.getEntityProperties(entity).age;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function randomPositionXZ(center, radius) {
|
||||
return {
|
||||
x: center.x + (Math.random() * radius * 2.0) - radius,
|
||||
y: center.y,
|
||||
z: center.z + (Math.random() * radius * 2.0) - radius
|
||||
};
|
||||
}
|
||||
|
||||
function randomPosition(center, radius) {
|
||||
return {
|
||||
x: center.x + (Math.random() * radius * 2.0) - radius,
|
||||
y: center.y + (Math.random() * radius * 2.0) - radius,
|
||||
z: center.z + (Math.random() * radius * 2.0) - radius
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function randomColor() {
|
||||
return {
|
||||
red: Math.floor(Math.random() * 255),
|
||||
green: Math.floor(Math.random() * 255),
|
||||
blue: Math.floor(Math.random() * 255),
|
||||
};
|
||||
}
|
||||
|
||||
function randomDimensions() {
|
||||
return {
|
||||
x: 0.1 + Math.random() * 0.5,
|
||||
y: 0.1 + Math.random() * 0.1,
|
||||
z: 0.1 + Math.random() * 0.5
|
||||
};
|
||||
}
|
||||
|
||||
var entities = [];
|
||||
var entitiesToCreate = 0;
|
||||
var entitiesSpawned = 0;
|
||||
var spawnTimer = 0.0;
|
||||
var keepAliveTimer = 0.0;
|
||||
var updateTimer = 0.0;
|
||||
|
||||
function clear () {
|
||||
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||
var that = this;
|
||||
ids.forEach(function(id) {
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == TEST_ENTITY_NAME) {
|
||||
Entities.deleteEntity(id);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
function createEntities () {
|
||||
print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")");
|
||||
entitiesToCreate = NUM_ENTITIES;
|
||||
Script.update.connect(spawnEntities);
|
||||
}
|
||||
|
||||
function spawnEntities (dt) {
|
||||
if (entitiesToCreate <= 0) {
|
||||
Script.update.disconnect(spawnEntities);
|
||||
print("Finished spawning entities");
|
||||
}
|
||||
else if ((spawnTimer -= dt) < 0.0){
|
||||
spawnTimer = ENTITY_SPAWN_INTERVAL;
|
||||
|
||||
var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT);
|
||||
print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")");
|
||||
|
||||
entitiesToCreate -= n;
|
||||
|
||||
var center = MyAvatar.position;
|
||||
center.y -= Y_OFFSET;
|
||||
|
||||
for (; n > 0; --n) {
|
||||
entities.push(makeEntity({
|
||||
type: "Shape",
|
||||
shape: SHAPES[n % SHAPES.length],
|
||||
name: TEST_ENTITY_NAME,
|
||||
position: randomPosition(center, RADIUS),
|
||||
color: randomColor(),
|
||||
dimensions: randomDimensions(),
|
||||
lifetime: ENTITY_LIFETIME
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function despawnEntities () {
|
||||
print("despawning entities");
|
||||
entities.forEach(function (entity) {
|
||||
entity.destroy();
|
||||
});
|
||||
entities = [];
|
||||
}
|
||||
|
||||
// Runs the following entity updates:
|
||||
// a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and
|
||||
// b) re-randomizes its position every UPDATE_INTERVAL seconds.
|
||||
// This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again).
|
||||
function updateEntities (dt) {
|
||||
var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false;
|
||||
var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false;
|
||||
|
||||
if (updateLifetime || updateProperties) {
|
||||
var center = MyAvatar.position;
|
||||
center.y -= Y_OFFSET;
|
||||
|
||||
entities.forEach((updateLifetime && updateProperties && function (entity) {
|
||||
entity.update({
|
||||
lifetime: entity.getAge() + ENTITY_LIFETIME,
|
||||
position: randomPosition(center, RADIUS)
|
||||
});
|
||||
}) || (updateLifetime && function (entity) {
|
||||
entity.update({
|
||||
lifetime: entity.getAge() + ENTITY_LIFETIME
|
||||
});
|
||||
}) || (updateProperties && function (entity) {
|
||||
entity.update({
|
||||
position: randomPosition(center, RADIUS)
|
||||
});
|
||||
}) || null, this);
|
||||
}
|
||||
}
|
||||
|
||||
function init () {
|
||||
Script.update.disconnect(init);
|
||||
clear();
|
||||
createEntities();
|
||||
Script.update.connect(updateEntities);
|
||||
Script.scriptEnding.connect(despawnEntities);
|
||||
}
|
||||
|
||||
Script.update.connect(init);
|
||||
};
|
24
scripts/developer/tests/primitivesTest.js
Normal file
24
scripts/developer/tests/primitivesTest.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
// entityEditStressTest.js
|
||||
//
|
||||
// Created by Seiji Emery on 8/31/15
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Stress tests the client + server-side entity trees by spawning huge numbers of entities in
|
||||
// close proximity to your avatar and updating them continuously (ie. applying position edits),
|
||||
// with the intent of discovering crashes and other bugs related to the entity, scripting,
|
||||
// rendering, networking, and/or physics subsystems.
|
||||
//
|
||||
// This script was originally created to find + diagnose an a clientside crash caused by improper
|
||||
// locking of the entity tree, but can be reused for other purposes.
|
||||
//
|
||||
// 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("./entitySpawnTool.js");
|
||||
|
||||
|
||||
ENTITY_SPAWNER({
|
||||
updateInterval: 2.0
|
||||
})
|
||||
|
90
scripts/system/assets/images/lock.svg
Normal file
90
scripts/system/assets/images/lock.svg
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64mm"
|
||||
height="64mm"
|
||||
viewBox="0 0 226.77165 226.77165"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="lock.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient5587"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5589" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5587"
|
||||
id="linearGradient5591"
|
||||
x1="63.214287"
|
||||
y1="970.93364"
|
||||
x2="165.35715"
|
||||
y2="970.93364"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="-342.3833"
|
||||
inkscape:cy="122.87157"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2782"
|
||||
inkscape:window-height="1764"
|
||||
inkscape:window-x="98"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-825.59055)">
|
||||
<rect
|
||||
style="fill:url(#linearGradient5591);fill-opacity:1;stroke:#000000;stroke-linejoin:round;stroke-opacity:1;stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect4138"
|
||||
width="97.14286"
|
||||
height="82.85714"
|
||||
x="65.714287"
|
||||
y="929.50507" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#a7abdf;stroke-width:9.00350285;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 78.319686,927.59356 c -2.221784,0.87707 -0.135759,-31.35492 36.848474,-30.3048 34.29837,0.97388 35.8249,30.70886 35.8249,30.70886"
|
||||
id="path4148"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
</g>
|
||||
</svg>
|
After (image error) Size: 2.9 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue