Merge remote-tracking branch 'upstream/master' into fix_radar_zoom_in

This commit is contained in:
Gabriel Calero 2018-04-05 11:33:07 -03:00
commit c26ae3592c
85 changed files with 1924 additions and 576 deletions

View file

@ -470,7 +470,6 @@ void EntityServer::startDynamicDomainVerification() {
// Delete the entity if it doesn't pass static certificate verification
tree->deleteEntity(i.value(), true);
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
@ -490,9 +489,13 @@ void EntityServer::startDynamicDomainVerification() {
if (networkReply->error() == QNetworkReply::NoError) {
if (jsonObject["domain_id"].toString() != thisDomainID) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
tree->deleteEntity(i.value(), true);
if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
tree->deleteEntity(i.value(), true);
} else {
qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << i.value();
}
} else {
qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value();
}

View file

@ -105,8 +105,6 @@ EntityScriptServer::~EntityScriptServer() {
static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server";
void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID));
@ -119,8 +117,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<Rec
}
void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) {
MessageID messageID;
message->readPrimitive(&messageID);
@ -190,15 +186,14 @@ void EntityScriptServer::updateEntityPPS() {
}
void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
// These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them
// about each other.
bool canRezAny = senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified();
bool enable = false;
message->readPrimitive(&enable);
auto senderUUID = senderNode->getUUID();
auto it = _logListeners.find(senderUUID);
if (enable && senderNode->getCanRez()) {
if (enable && canRezAny) {
if (it == std::end(_logListeners)) {
_logListeners.insert(senderUUID);
qCInfo(entity_script_server) << "Node" << senderUUID << "subscribed to log stream";

View file

@ -1042,41 +1042,7 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) {
auto nodeAData = static_cast<DomainServerNodeData*>(nodeA->getLinkedData());
auto nodeBData = static_cast<DomainServerNodeData*>(nodeB->getLinkedData());
// if we have no linked data for node A then B can't possibly be in the interest set
if (!nodeAData) {
return false;
}
// first check if the general interest set A contains the type for B
if (nodeAData->getNodeInterestSet().contains(nodeB->getType())) {
// given that there is a match in the general interest set, do any special checks
// (1/19/17) Agents only need to connect to Entity Script Servers to perform administrative tasks
// related to entity server scripts. Only agents with rez permissions should be doing that, so
// if the agent does not have those permissions, we do not want them and the server to incur the
// overhead of connecting to one another. Additionally we exclude agents that do not care about the
// Entity Script Server and won't attempt to connect to it.
bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent
&& nodeB->getType() == NodeType::EntityScriptServer
&& !nodeA->getCanRez() && !nodeA->getCanRezTmp()
&& !nodeA->getCanRezCertified() && !nodeA->getCanRezTmpCertified();
if (isAgentWithoutRights) {
return false;
}
bool isScriptServerForIneffectiveAgent =
(nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent)
&& ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer))
|| (!nodeB->getCanRez() && !nodeB->getCanRezTmp() && !nodeB->getCanRezCertified() && !nodeB->getCanRezTmpCertified()));
return !isScriptServerForIneffectiveAgent;
} else {
return false;
}
return nodeAData && nodeAData->getNodeInterestSet().contains(nodeB->getType());
}
unsigned int DomainServer::countConnectedUsers() {
@ -3476,4 +3442,4 @@ void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMes
if (node->getCanReplaceContent()) {
handleOctreeFileReplacement(message->readAll());
}
}
}

View file

@ -114,7 +114,7 @@
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" },
{ "from": "Keyboard.Control", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },

View file

@ -163,10 +163,18 @@ TextField {
text: textField.label
colorScheme: textField.colorScheme
anchors.left: parent.left
anchors.right: parent.right
Binding on anchors.right {
when: parent.right
value: parent.right
}
Binding on wrapMode {
when: parent.right
value: Text.WordWrap
}
anchors.bottom: parent.top
anchors.bottomMargin: 3
wrapMode: Text.WordWrap
visible: label != ""
}
}

View file

@ -39,6 +39,7 @@ Windows.ScrollingWindow {
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property var selectedItemCount: treeView.selection.selectedIndexes.length;
property int updatesCount: 0; // this is used for notifying model-dependent bindings about model updates
Settings {
category: "Overlay.AssetServer"
@ -51,6 +52,9 @@ Windows.ScrollingWindow {
ApplicationInterface.uploadRequest.connect(uploadClicked);
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError);
assetMappingsModel.autoRefreshEnabled = true;
assetMappingsModel.updated.connect(function() {
++updatesCount;
});
reload();
}
@ -852,12 +856,17 @@ Windows.ScrollingWindow {
checked = Qt.binding(isChecked);
}
function getStatus() {
// kind of hack for ensuring getStatus() will be re-evaluated on updatesCount changes
return updatesCount, assetProxyModel.data(treeView.selection.currentIndex, 0x105);
}
function isEnabled() {
if (!treeView.selection.hasSelection) {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
var status = getStatus();
if (status === "--") {
return false;
}
@ -882,9 +891,9 @@ Windows.ScrollingWindow {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
return isEnabled() && status !== "Not Baked";
}
var status = getStatus();
return isEnabled() && status !== "Not Baked";
}
}
Item {

View file

@ -96,6 +96,7 @@ Rectangle {
root.activeView = "checkoutFailure";
UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message);
} else {
root.certificateId = result.data.certificate_id;
root.itemHref = result.data.download_url;
if (result.data.categories.indexOf("Wearables") > -1) {
root.itemType = "wearable";
@ -188,7 +189,7 @@ Rectangle {
onItemHrefChanged: {
if (root.itemHref.indexOf(".fst") > -1) {
root.itemType = "avatar";
} else if (root.itemHref.indexOf('.json.gz') > -1) {
} else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) {
root.itemType = "contentSet";
} else if (root.itemHref.indexOf('.app.json') > -1) {
root.itemType = "app";
@ -772,7 +773,7 @@ Rectangle {
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" +
lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "', '" + root.certificateId + "');" +
"root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" +
"UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');";
lightboxPopup.visible = true;

View file

@ -564,7 +564,7 @@ Item {
onClicked: {
Tablet.playSound(TabletEnums.ButtonClick);
if (root.itemType === "contentSet") {
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref});
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId});
} else if (root.itemType === "avatar") {
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
} else if (root.itemType === "app") {

View file

@ -486,7 +486,7 @@ Rectangle {
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "', '" + msg.certID + "'); root.visible = false;";
lightboxPopup.visible = true;
} else if (msg.method === "showChangeAvatarLightbox") {
lightboxPopup.titleText = "Change Avatar";
@ -792,7 +792,7 @@ Rectangle {
currentItemType = "avatar";
} else if (currentCategories.indexOf("Wearables") > -1) {
currentItemType = "wearable";
} else if (currentRootFileUrl.endsWith('.json.gz')) {
} else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) {
currentItemType = "contentSet";
} else if (currentRootFileUrl.endsWith('.app.json')) {
currentItemType = "app";

View file

@ -24,6 +24,18 @@ Item {
HifiConstants { id: hifi; }
id: root;
// This will cause a bug -- if you bring up passphrase selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
// Username Text
RalewayRegular {

View file

@ -68,10 +68,6 @@ Item {
propagateComposedEvents: false;
hoverEnabled: true;
}
Component.onDestruction: {
sendSignalToParent({method: 'maybeEnableHmdPreview'});
}
// This will cause a bug -- if you bring up passphrase selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,

View file

@ -61,9 +61,6 @@ Item {
if (root.shouldImmediatelyFocus) {
focusFirstTextField();
}
sendMessageToLightbox({method: 'disableHmdPreview'});
} else {
sendMessageToLightbox({method: 'maybeEnableHmdPreview'});
}
}

View file

@ -44,6 +44,17 @@ Item {
}
}
// This will cause a bug -- if you bring up security image selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
// Security Image
Item {

View file

@ -25,18 +25,6 @@ Item {
id: root;
property alias currentIndex: securityImageGrid.currentIndex;
// This will cause a bug -- if you bring up security image selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
SecurityImageModel {
id: gridModel;

View file

@ -237,7 +237,7 @@ Rectangle {
} else {
sendToScript(msg);
}
} else if (msg.method === 'maybeEnableHmdPreview') {
} else {
sendToScript(msg);
}
}

View file

@ -76,6 +76,12 @@ Item {
var currentStepNumber = root.activeView.substring(5);
UserActivityLogger.commerceWalletSetupProgress(timestamp, root.setupAttemptID,
Math.round((timestamp - root.startingTimestamp)/1000), currentStepNumber, root.setupStepNames[currentStepNumber - 1]);
if (root.activeView === "step_2" || root.activeView === "step_3") {
sendSignalToWallet({method: 'disableHmdPreview'});
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}
//
@ -441,7 +447,7 @@ Item {
}
Item {
id: choosePassphraseContainer;
visible: root.hasShownSecurityImageTip && root.activeView === "step_3";
visible: root.activeView === "step_3";
// Anchors
anchors.top: titleBarContainer.bottom;
anchors.topMargin: 30;
@ -451,10 +457,7 @@ Item {
onVisibleChanged: {
if (visible) {
sendSignalToWallet({method: 'disableHmdPreview'});
Commerce.getWalletAuthenticatedStatus();
} else {
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
}
}

View file

@ -40,6 +40,7 @@ Rectangle {
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property var selectedItemCount: treeView.selection.selectedIndexes.length;
property int updatesCount: 0; // this is used for notifying model-dependent bindings about model updates
Settings {
category: "Overlay.AssetServer"
@ -51,6 +52,9 @@ Rectangle {
ApplicationInterface.uploadRequest.connect(uploadClicked);
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError);
assetMappingsModel.autoRefreshEnabled = true;
assetMappingsModel.updated.connect(function() {
++updatesCount;
});
reload();
}
@ -850,12 +854,17 @@ Rectangle {
checked = Qt.binding(isChecked);
}
function getStatus() {
// kind of hack for ensuring getStatus() will be re-evaluated on updatesCount changes
return updatesCount, assetProxyModel.data(treeView.selection.currentIndex, 0x105);
}
function isEnabled() {
if (!treeView.selection.hasSelection) {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
var status = getStatus();
if (status === "--") {
return false;
}
@ -880,7 +889,7 @@ Rectangle {
return false;
}
var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105);
var status = getStatus();
return isEnabled() && status !== "Not Baked";
}
}

View file

@ -6244,8 +6244,9 @@ bool Application::canAcceptURL(const QString& urlString) const {
bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
QUrl url(urlString);
if (isDomainURL(url)) {
// this is a URL for a domain, either hifi:// or serverless - have the AddressManager handle it
if (url.scheme() == URL_SCHEME_HIFI) {
// this is a hifi URL - have the AddressManager handle it
QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString",
Qt::AutoConnection, Q_ARG(const QString&, urlString));
return true;

View file

@ -86,10 +86,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE;
}
qCDebug(interfaceapp) << "adjusting LOD DOWN"
<< "fps =" << currentFPS
<< "targetFPS =" << getLODDecreaseFPS()
<< "octreeSizeScale =" << _octreeSizeScale;
emit LODDecreased();
// Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime
// to provide an FPS just above the decrease threshold. It will drift close to its
@ -111,10 +107,6 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) {
_octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE;
}
qCDebug(interfaceapp) << "adjusting LOD UP"
<< "fps =" << currentFPS
<< "targetFPS =" << getLODDecreaseFPS()
<< "octreeSizeScale =" << _octreeSizeScale;
emit LODIncreased();
// Assuming the LOD adjustment will work: we optimistically reset _avgRenderTime
// to provide an FPS just below the increase threshold. It will drift close to its

View file

@ -67,8 +67,8 @@ using namespace std;
const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f;
const float YAW_SPEED_DEFAULT = 100.0f; // degrees/sec
const float PITCH_SPEED_DEFAULT = 75.0f; // degrees/sec
const float YAW_SPEED_DEFAULT = 75.0f; // degrees/sec
const float PITCH_SPEED_DEFAULT = 50.0f; // degrees/sec
const float MAX_BOOST_SPEED = 0.5f * DEFAULT_AVATAR_MAX_WALKING_SPEED; // action motor gets additive boost below this speed
const float MIN_AVATAR_SPEED = 0.05f;
@ -2227,7 +2227,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
}
float boomChange = getDriveKey(ZOOM);
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength += 4.0f * _boomLength * boomChange + boomChange * boomChange;
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
}
@ -2760,6 +2760,18 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const {
}
}
void MyAvatar::triggerVerticalRecenter() {
_follow.setForceActivateVertical(true);
}
void MyAvatar::triggerHorizontalRecenter() {
_follow.setForceActivateHorizontal(true);
}
void MyAvatar::triggerRotationRecenter() {
_follow.setForceActivateRotation(true);
}
// old school meat hook style
glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
glm::vec3 headPosition;
@ -2957,7 +2969,9 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
}
bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@ -2974,6 +2988,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
const float MAX_FORWARD_LEAN = 0.15f;
const float MAX_BACKWARD_LEAN = 0.1f;
if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
return true;
} else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) {
@ -2981,6 +2996,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
}
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
}
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
@ -2988,6 +3004,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
const float CYLINDER_BOTTOM = -1.5f;
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
}
@ -3005,6 +3022,19 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(Vertical);
}
} else {
if (!isActive(Rotation) && getForceActivateRotation()) {
activate(Rotation);
setForceActivateRotation(false);
}
if (!isActive(Horizontal) && getForceActivateHorizontal()) {
activate(Horizontal);
setForceActivateHorizontal(false);
}
if (!isActive(Vertical) && getForceActivateVertical()) {
activate(Vertical);
setForceActivateVertical(false);
}
}
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix;
@ -3054,6 +3084,30 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co
}
}
bool MyAvatar::FollowHelper::getForceActivateRotation() const {
return _forceActivateRotation;
}
void MyAvatar::FollowHelper::setForceActivateRotation(bool val) {
_forceActivateRotation = val;
}
bool MyAvatar::FollowHelper::getForceActivateVertical() const {
return _forceActivateVertical;
}
void MyAvatar::FollowHelper::setForceActivateVertical(bool val) {
_forceActivateVertical = val;
}
bool MyAvatar::FollowHelper::getForceActivateHorizontal() const {
return _forceActivateHorizontal;
}
void MyAvatar::FollowHelper::setForceActivateHorizontal(bool val) {
_forceActivateHorizontal = val;
}
float MyAvatar::getAccelerationEnergy() {
glm::vec3 velocity = getWorldVelocity();
int changeInVelocity = abs(velocity.length() - priorVelocity.length());

View file

@ -404,6 +404,32 @@ public:
Q_INVOKABLE void enableDriveKey(DriveKeys key);
Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const;
/**jsdoc
*The triggerVerticalRecenter function activates one time the recentering
*behaviour in the vertical direction. This call is only takes effect when the property
*MyAvatar.hmdLeanRecenterEnabled is set to false.
*@function MyAvatar.triggerVerticalRecenter
*
*/
/**jsdoc
*The triggerHorizontalRecenter function activates one time the recentering behaviour
*in the horizontal direction. This call is only takes effect when the property
*MyAvatar.hmdLeanRecenterEnabled is set to false.
*@function MyAvatar.triggerHorizontalRecenter
*/
/**jsdoc
*The triggerRotationRecenter function activates one time the recentering behaviour
*in the rotation of the root of the avatar. This call is only takes effect when the property
*MyAvatar.hmdLeanRecenterEnabled is set to false.
*@function MyAvatar.triggerRotationRecenter
*/
Q_INVOKABLE void triggerVerticalRecenter();
Q_INVOKABLE void triggerHorizontalRecenter();
Q_INVOKABLE void triggerRotationRecenter();
eyeContactTarget getEyeContactTarget();
const MyHead* getMyHead() const;
@ -803,6 +829,15 @@ private:
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
bool getForceActivateRotation() const;
void setForceActivateRotation(bool val);
bool getForceActivateVertical() const;
void setForceActivateVertical(bool val);
bool getForceActivateHorizontal() const;
void setForceActivateHorizontal(bool val);
std::atomic<bool> _forceActivateRotation{ false };
std::atomic<bool> _forceActivateVertical{ false };
std::atomic<bool> _forceActivateHorizontal{ false };
};
FollowHelper _follow;
@ -839,6 +874,7 @@ private:
bool _hmdLeanRecenterEnabled { true };
bool _sprint { false };
AnimPose _prePhysicsRoomPose;
std::mutex _holdActionsMutex;
std::vector<AvatarActionHold*> _holdActions;

View file

@ -293,7 +293,7 @@ void Ledger::account() {
// The api/failResponse is called just for the side effect of logging.
void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); }
void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); }
void Ledger::updateLocation(const QString& asset_id, const QString location, const bool controlledFailure) {
void Ledger::updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings, const bool controlledFailure) {
auto wallet = DependencyManager::get<Wallet>();
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
uint walletStatus = walletScriptingInterface->getWalletStatus();
@ -308,6 +308,9 @@ void Ledger::updateLocation(const QString& asset_id, const QString location, con
QJsonObject transaction;
transaction["certificate_id"] = asset_id;
transaction["place_name"] = location;
if (alsoUpdateSiblings) {
transaction["also_update_siblings"] = true;
}
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);

View file

@ -31,7 +31,7 @@ public:
void inventory(const QStringList& keys);
void history(const QStringList& keys, const int& pageNumber);
void account();
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false);
void certificateInfo(const QString& certificateId);
void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);

View file

@ -190,7 +190,9 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou
ledger->transferHfcToUsername(key, username, amount, optionalMessage);
}
void QmlCommerce::replaceContentSet(const QString& itemHref) {
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
auto ledger = DependencyManager::get<Ledger>();
ledger->updateLocation(certificateID, DependencyManager::get<AddressManager>()->getPlaceName(), true);
qApp->replaceDomainContent(itemHref);
QJsonObject messageProperties = {
{ "status", "SuccessfulRequestToReplaceContent" },

View file

@ -84,7 +84,7 @@ protected:
Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void replaceContentSet(const QString& itemHref);
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
Q_INVOKABLE QString getInstalledApps();
Q_INVOKABLE bool installApp(const QString& appHref);

View file

@ -81,26 +81,14 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta
});
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
}
}
PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) {
PickResultPointer visualPickResult = pickResult;
auto rayPickResult = std::static_pointer_cast<RayPickResult>(visualPickResult);
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
startProps.insert("visible", true);
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
glm::vec3 endVec;
if (((defaultState || !_lockEnd) && _lockEndObject.id.isNull()) || type == IntersectionType::HUD) {
endVec = pickRay.origin + pickRay.direction * distance;
} else {
if (type != IntersectionType::HUD) {
glm::vec3 endVec;
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
if (!_lockEndObject.id.isNull()) {
glm::vec3 pos;
glm::quat rot;
@ -122,17 +110,54 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
}
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
} else {
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
rayPickResult->objectID = _lockEndObject.id;
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
} else if (type != IntersectionType::NONE && _lockEnd) {
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(objectID)[3];
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(rayPickResult->objectID)[3];
} else if (type == IntersectionType::OVERLAY) {
endVec = vec3FromVariant(qApp->getOverlays().getProperty(objectID, "position").value);
endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value);
} else if (type == IntersectionType::AVATAR) {
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(objectID)->getPosition();
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(rayPickResult->objectID)->getPosition();
}
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
}
}
return visualPickResult;
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
}
}
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
startProps.insert("visible", true);
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
glm::vec3 endVec = pickRay.origin + pickRay.direction * distance;
QVariant end = vec3toVariant(endVec);
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
@ -195,15 +220,15 @@ void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
PickRay pickRay(rayPickResult->pickVariant);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay();
QUuid uid = rayPickResult->objectID;
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false);
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true);
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
@ -386,4 +411,4 @@ glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::v
default:
return glm::vec2(NAN);
}
}
}

View file

@ -82,6 +82,7 @@ public:
protected:
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
@ -102,7 +103,7 @@ private:
LockEndObject _lockEndObject;
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState);
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay);
void disableRenderState(const RenderState& renderState);
struct TriggerState {

View file

@ -80,7 +80,7 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping,
auto result = offscreenUi->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path",
dropEvent ? dropHelpText : helpText, mapping);
if (!result.isValid()) {
if (!result.isValid() || result.toString() == "") {
completedCallback.call({ -1 });
return;
}

View file

@ -110,7 +110,6 @@ bool SelectionScriptingInterface::enableListHighlight(const QString& listName, c
}
if (!(*highlightStyle).isBoundToList()) {
setupHandler(listName);
(*highlightStyle).setBoundToList(true);
}
@ -172,6 +171,18 @@ render::HighlightStyle SelectionScriptingInterface::getHighlightStyle(const QStr
}
}
bool SelectionScriptingInterface::enableListToScene(const QString& listName) {
setupHandler(listName);
return true;
}
bool SelectionScriptingInterface::disableListToScene(const QString& listName) {
removeHandler(listName);
return true;
}
template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
{
QWriteLocker lock(&_selectionListsLock);
@ -303,6 +314,15 @@ void SelectionScriptingInterface::setupHandler(const QString& selectionName) {
(*handler)->initialize(selectionName);
}
void SelectionScriptingInterface::removeHandler(const QString& selectionName) {
QWriteLocker lock(&_selectionHandlersLock);
auto handler = _handlerMap.find(selectionName);
if (handler != _handlerMap.end()) {
delete handler.value();
_handlerMap.erase(handler);
}
}
void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) {
{
QWriteLocker lock(&_selectionHandlersLock);

View file

@ -160,13 +160,14 @@ public:
* If the Selection doesn't exist, it will be created.
* All objects in the list will be displayed with the highlight effect as specified from the highlightStyle.
* The function can be called several times with different values in the style to modify it.
*
*
* @function Selection.enableListHighlight
* @param listName {string} name of the selection
* @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle).
* @returns {bool} true if the selection was successfully enabled for highlight.
*/
Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle);
/**jsdoc
* Disable highlighting for the named selection.
* If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false.
@ -175,7 +176,27 @@ public:
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled for highlight, false otherwise.
*/
Q_INVOKABLE bool disableListHighlight(const QString& listName);
Q_INVOKABLE bool disableListHighlight(const QString& listName);
/**jsdoc
* Enable scene selection for the named selection.
* If the Selection doesn't exist, it will be created.
* All objects in the list will be sent to a scene selection.
*
* @function Selection.enableListToScene
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully enabled on the scene.
*/
Q_INVOKABLE bool enableListToScene(const QString& listName);
/**jsdoc
* Disable scene selection for the named selection.
* If the Selection doesn't exist or wasn't enabled on the scene then nothing happens simply returning false.
*
* @function Selection.disableListToScene
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled on the scene, false otherwise.
*/
Q_INVOKABLE bool disableListToScene(const QString& listName);
/**jsdoc
* Query the highlight style values for the named selection.
* If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object.
@ -223,7 +244,7 @@ private:
template <class T> bool removeFromGameplayObjects(const QString& listName, T idToRemove);
void setupHandler(const QString& selectionName);
void removeHandler(const QString& selectionName);
};

View file

@ -50,7 +50,9 @@ ContextOverlayInterface::ContextOverlayInterface() {
_entityPropertyFlags += PROP_OWNING_AVATAR_ID;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay);
connect(entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity, this, &ContextOverlayInterface::clickDownOnEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity, this, &ContextOverlayInterface::holdingClickOnEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &ContextOverlayInterface::mouseReleaseOnEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, &ContextOverlayInterface::contextOverlays_hoverEnterEntity);
connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &ContextOverlayInterface::contextOverlays_hoverLeaveEntity);
connect(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"), &TabletProxy::tabletShownChanged, this, [&]() {
@ -97,6 +99,31 @@ void ContextOverlayInterface::setEnabled(bool enabled) {
_enabled = enabled;
}
void ContextOverlayInterface::clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(entityItemID)) {
_mouseDownEntity = entityItemID;
_mouseDownEntityTimestamp = usecTimestampNow();
} else {
if (!_currentEntityWithContextOverlay.isNull()) {
disableEntityHighlight(_currentEntityWithContextOverlay);
destroyContextOverlay(_currentEntityWithContextOverlay, event);
}
}
}
static const float CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC = 400.0f;
void ContextOverlayInterface::holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) {
if (!_mouseDownEntity.isNull() && ((usecTimestampNow() - _mouseDownEntityTimestamp) > (CONTEXT_OVERLAY_CLICK_HOLD_TIME_MSEC * USECS_PER_MSEC))) {
_mouseDownEntity = EntityItemID();
}
}
void ContextOverlayInterface::mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton && contextOverlayFilterPassed(entityItemID) && _mouseDownEntity == entityItemID) {
createOrDestroyContextOverlay(entityItemID, event);
}
}
bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton) {
if (contextOverlayFilterPassed(entityItemID)) {

View file

@ -64,6 +64,10 @@ signals:
void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay);
public slots:
void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event);
bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
bool destroyContextOverlay(const EntityItemID& entityItemID);
@ -84,6 +88,8 @@ private:
};
bool _verboseLogging{ true };
bool _enabled { true };
EntityItemID _mouseDownEntity{};
quint64 _mouseDownEntityTimestamp;
EntityItemID _currentEntityWithContextOverlay{};
EntityItemID _lastInspectedEntity{};
QString _entityMarketplaceID;

View file

@ -1246,6 +1246,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo,
const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) {
const bool ENABLE_POLE_VECTORS = false;
const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f;
int hipsIndex = indexOfJoint("Hips");
@ -1268,7 +1269,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand");
int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm");
int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm");
if (!leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
if (ENABLE_POLE_VECTORS && !leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true);
// smooth toward desired pole vector from previous pole vector... to reduce jitter
@ -1315,7 +1316,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab
int handJointIndex = _animSkeleton->nameToJointIndex("RightHand");
int armJointIndex = _animSkeleton->nameToJointIndex("RightArm");
int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm");
if (!rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
if (ENABLE_POLE_VECTORS && !rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) {
glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false);
// smooth toward desired pole vector from previous pole vector... to reduce jitter
@ -1555,18 +1556,21 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
updateFeet(leftFootEnabled, rightFootEnabled,
params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot]);
if (headEnabled) {
// Blend IK chains toward the joint limit centers, this should stablize head and hand ik.
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses);
} else {
// Blend IK chains toward the UnderPoses, so some of the animaton motion is present in the IK solution.
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses);
}
// if the hips or the feet are being controlled.
if (hipsEnabled || rightFootEnabled || leftFootEnabled) {
// for more predictable IK solve from the center of the joint limits, not from the underpose
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses);
// replace the feet animation with the default pose, this is to prevent unexpected toe wiggling.
_animVars.set("defaultPoseOverlayAlpha", 1.0f);
_animVars.set("defaultPoseOverlayBoneSet", (int)AnimOverlay::BothFeetBoneSet);
} else {
// augment the IK with the underPose.
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses);
// feet should follow source animation
_animVars.unset("defaultPoseOverlayAlpha");
_animVars.unset("defaultPoseOverlayBoneSet");

View file

@ -6,10 +6,43 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// NOTE: we don't need to include this header unless/until we add additional symbols.
// By removing this header we prevent these warnings on windows:
//
// warning LNK4221: This object file does not define any previously undefined public symbols,
// so it will not be used by any link operation that consumes this library
//
//#include "JSEndpoint.h"
#include "JSEndpoint.h"
#include "../../Logging.h"
using namespace controller;
QString formatException(const QJSValue& exception) {
QString note { "UncaughtException" };
QString result;
const auto message = exception.toString();
const auto fileName = exception.property("fileName").toString();
const auto lineNumber = exception.property("lineNumber").toString();
const auto stacktrace = exception.property("stack").toString();
const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3";
const QString SCRIPT_BACKTRACE_SEP = "\n ";
result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber);
if (!stacktrace.isEmpty()) {
result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace);
}
return result;
}
float JSEndpoint::peek() const {
QJSValue result = _callable.call();
if (result.isError()) {
qCDebug(controllers).noquote() << formatException(result);
return 0.0f;
} else {
return (float)result.toNumber();
}
}
void JSEndpoint::apply(float newValue, const Pointer& source) {
QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue) }));
if (result.isError()) {
qCDebug(controllers).noquote() << formatException(result);
}
}

View file

@ -24,16 +24,11 @@ public:
: Endpoint(Input::INVALID_INPUT), _callable(callable) {
}
virtual float peek() const override {
return (float)const_cast<JSEndpoint*>(this)->_callable.call().toNumber();
}
virtual void apply(float newValue, const Pointer& source) override {
_callable.call(QJSValueList({ QJSValue(newValue) }));
}
virtual float peek() const override;
virtual void apply(float newValue, const Pointer& source) override;
private:
QJSValue _callable;
mutable QJSValue _callable;
};
}

View file

@ -7,6 +7,7 @@
//
#include "ScriptEndpoint.h"
#include "../../Logging.h"
#include <QtCore/QThread>
@ -14,6 +15,25 @@
using namespace controller;
QString formatException(const QScriptValue& exception) {
QString note { "UncaughtException" };
QString result;
const auto message = exception.toString();
const auto fileName = exception.property("fileName").toString();
const auto lineNumber = exception.property("lineNumber").toString();
const auto stacktrace = exception.property("stack").toString();
const QString SCRIPT_EXCEPTION_FORMAT = "[%0] %1 in %2:%3";
const QString SCRIPT_BACKTRACE_SEP = "\n ";
result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber);
if (!stacktrace.isEmpty()) {
result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace);
}
return result;
}
float ScriptEndpoint::peek() const {
const_cast<ScriptEndpoint*>(this)->updateValue();
return _lastValueRead;
@ -26,10 +46,11 @@ void ScriptEndpoint::updateValue() {
}
QScriptValue result = _callable.call();
// If the callable ever returns a non-number, we assume it's a pose
// and start reporting ourselves as a pose.
if (result.isNumber()) {
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
_lastValueRead = 0.0f;
} else if (result.isNumber()) {
_lastValueRead = (float)_callable.call().toNumber();
} else {
Pose::fromScriptValue(result, _lastPoseRead);
@ -52,8 +73,12 @@ void ScriptEndpoint::internalApply(float value, int sourceID) {
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValue result = _callable.call(QScriptValue(),
QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) }));
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
}
}
Pose ScriptEndpoint::peekPose() const {
@ -67,6 +92,10 @@ void ScriptEndpoint::updatePose() {
return;
}
QScriptValue result = _callable.call();
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
}
Pose::fromScriptValue(result, _lastPoseRead);
}
@ -85,6 +114,10 @@ void ScriptEndpoint::internalApply(const Pose& newPose, int sourceID) {
Q_ARG(int, sourceID));
return;
}
_callable.call(QScriptValue(),
QScriptValue result = _callable.call(QScriptValue(),
QScriptValueList({ Pose::toScriptValue(_callable.engine(), newPose), QScriptValue(sourceID) }));
if (result.isError()) {
// print JavaScript exception
qCDebug(controllers).noquote() << formatException(result);
}
}

View file

@ -131,6 +131,8 @@ ItemKey ShapeEntityRenderer::getKey() {
withReadLock([&] {
if (isTransparent()) {
builder.withTransparent();
} else if (_canCastShadow) {
builder.withShadowCaster();
}
});

View file

@ -254,17 +254,6 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent);
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
auto dimensions = propertiesWithSimID.getDimensions();
float volume = dimensions.x * dimensions.y * dimensions.z;
auto density = propertiesWithSimID.getDensity();
auto newVelocity = propertiesWithSimID.getVelocity().length();
float cost = calculateCost(density * volume, 0, newVelocity);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
return QUuid();
}
EntityItemID id = EntityItemID(QUuid::createUuid());
// If we have a local entity tree set, then also update it.
@ -295,9 +284,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
// queue the packet
if (success) {
emit debitEnergySource(cost);
queueEntityMessage(PacketType::EntityAdd, id, propertiesWithSimID);
return id;
} else {
return QUuid();
@ -378,27 +365,9 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
EntityItemProperties properties = scriptSideProperties;
auto dimensions = properties.getDimensions();
float volume = dimensions.x * dimensions.y * dimensions.z;
auto density = properties.getDensity();
auto newVelocity = properties.getVelocity().length();
float oldVelocity = { 0.0f };
EntityItemID entityID(id);
if (!_entityTree) {
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
//if there is no local entity entity tree, no existing velocity, use 0.
float cost = calculateCost(density * volume, oldVelocity, newVelocity);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
return QUuid();
} else {
//debit the avatar energy and continue
emit debitEnergySource(cost);
}
return id;
}
// If we have a local entity tree set, then also update it.
@ -420,9 +389,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
// 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.
//existing entity, retrieve old velocity for check down below
oldVelocity = entity->getWorldVelocity().length();
if (!scriptSideProperties.parentIDChanged()) {
properties.setParentID(entity->getParentID());
}
@ -442,23 +408,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
properties.setClientOnly(entity->getClientOnly());
properties.setOwningAvatarID(entity->getOwningAvatarID());
properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent());
float cost = calculateCost(density * volume, oldVelocity, newVelocity);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
updatedEntity = false;
} else {
//debit the avatar energy and continue
updatedEntity = _entityTree->updateEntity(entityID, properties);
if (updatedEntity) {
emit debitEnergySource(cost);
}
}
updatedEntity = _entityTree->updateEntity(entityID, properties);
});
// FIXME: We need to figure out a better way to handle this. Allowing these edits to go through potentially
// breaks avatar energy and entities that are parented.
// breaks entities that are parented.
//
// To handle cases where a script needs to edit an entity with a _known_ entity id but doesn't exist
// in the local entity tree, we need to allow those edits to go through to the server.
@ -577,21 +531,6 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
return;
}
auto dimensions = entity->getScaledDimensions();
float volume = dimensions.x * dimensions.y * dimensions.z;
auto density = entity->getDensity();
auto velocity = entity->getWorldVelocity().length();
float cost = calculateCost(density * volume, velocity, 0);
cost *= costMultiplier;
if (cost > _currentAvatarEnergy) {
shouldDelete = false;
return;
} else {
//debit the avatar energy and continue
emit debitEnergySource(cost);
}
if (entity->getLocked()) {
shouldDelete = false;
} else {
@ -1812,23 +1751,6 @@ void EntityScriptingInterface::emitScriptEvent(const EntityItemID& entityID, con
}
}
float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) {
return std::abs(mass * (newVelocity - oldVelocity));
}
void EntityScriptingInterface::setCurrentAvatarEnergy(float energy) {
// qCDebug(entities) << "NEW AVATAR ENERGY IN ENTITY SCRIPTING INTERFACE: " << energy;
_currentAvatarEnergy = energy;
}
float EntityScriptingInterface::getCostMultiplier() {
return costMultiplier;
}
void EntityScriptingInterface::setCostMultiplier(float value) {
costMultiplier = value;
}
// TODO move this someplace that makes more sense...
bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
const glm::vec3& start, const glm::vec3& end, float radius) {

View file

@ -94,8 +94,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
* Interface has displayed and so knows about.
*
* @namespace Entities
* @property {number} currentAvatarEnergy - <strong>Deprecated</strong>
* @property {number} costMultiplier - <strong>Deprecated</strong>
* @property {Uuid} keyboardFocusEntity - Get or set the {@link Entities.EntityType|Web} entity that has keyboard focus.
* If no entity has keyboard focus, get returns <code>null</code>; set to <code>null</code> or {@link Uuid|Uuid.NULL} to
* clear keyboard focus.
@ -104,8 +102,6 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency {
Q_OBJECT
Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy)
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity)
friend EntityPropertyMetadataRequest;
@ -126,7 +122,6 @@ public:
void setEntityTree(EntityTreePointer modelTree);
EntityTreePointer getEntityTree() { return _entityTree; }
void setEntitiesScriptEngine(QSharedPointer<EntitiesScriptEngineProvider> engine);
float calculateCost(float mass, float oldVelocity, float newVelocity);
void resetActivityTracking();
ActivityTracking getActivityTracking() const { return _activityTracking; }
@ -1834,14 +1829,6 @@ signals:
*/
void clearingEntities();
/**jsdoc
* @function Entities.debitEnergySource
* @param {number} value - The amount to debit.
* @returns {Signal}
* @deprecated This function is deprecated and will soon be removed.
*/
void debitEnergySource(float value);
/**jsdoc
* Triggered in when a script in a {@link Entities.EntityType|Web} entity's Web page script sends an event over the
* script's <code>EventBridge</code>.
@ -1882,14 +1869,8 @@ private:
QSharedPointer<EntitiesScriptEngineProvider> _entitiesScriptEngine;
bool _bidOnSimulationOwnership { false };
float _currentAvatarEnergy = { FLT_MAX };
float getCurrentAvatarEnergy() { return _currentAvatarEnergy; }
void setCurrentAvatarEnergy(float energy);
ActivityTracking _activityTracking;
float costMultiplier = { 0.01f };
float getCostMultiplier();
void setCostMultiplier(float value);
};
#endif // hifi_EntityScriptingInterface_h

View file

@ -425,8 +425,8 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
if (!childEntity) {
continue;
}
EntityTreeElementPointer containingElement = childEntity->getElement();
if (!containingElement) {
EntityTreeElementPointer childContainingElement = childEntity->getElement();
if (!childContainingElement) {
continue;
}
@ -440,7 +440,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
addToNeedsParentFixupList(childEntity);
}
UpdateEntityOperator theChildOperator(getThisPointer(), containingElement, childEntity, queryCube);
UpdateEntityOperator theChildOperator(getThisPointer(), childContainingElement, childEntity, queryCube);
recurseTreeWithOperator(&theChildOperator);
foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) {
if (childChild && childChild->getNestableType() == NestableType::Entity) {

View file

@ -288,7 +288,7 @@ OctreeElementPointer UpdateEntityOperator::possiblyCreateChildAt(const OctreeEle
int indexOfChildContainingNewEntity = element->getMyChildContaining(_newEntityBox);
if (childIndex == indexOfChildContainingNewEntity) {
return element->addChildAtIndex(childIndex);;
return element->addChildAtIndex(childIndex);
}
}
}

View file

@ -55,6 +55,7 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
(&::gpu::gl::GLBackend::do_setUniformBuffer),
(&::gpu::gl::GLBackend::do_setResourceBuffer),
(&::gpu::gl::GLBackend::do_setResourceTexture),
(&::gpu::gl::GLBackend::do_setResourceTextureTable),
(&::gpu::gl::GLBackend::do_setResourceFramebufferSwapChainTexture),
(&::gpu::gl::GLBackend::do_setFramebuffer),

View file

@ -40,7 +40,6 @@
#endif
// Let these be configured by the one define picked above
#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE
#define GPU_STEREO_DRAWCALL_DOUBLED
@ -102,6 +101,12 @@ public:
static const int MAX_NUM_RESOURCE_TEXTURES = 16;
size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; }
// Texture Tables offers 2 dedicated slot (taken from the ubo slots)
static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2;
static const int RESOURCE_TABLE_TEXTURE_SLOT_OFFSET = TRANSFORM_CAMERA_SLOT + 1;
size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; }
// Draw Stage
virtual void do_draw(const Batch& batch, size_t paramOffset) = 0;
virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0;
@ -130,6 +135,7 @@ public:
// Resource Stage
virtual void do_setResourceBuffer(const Batch& batch, size_t paramOffset) final;
virtual void do_setResourceTexture(const Batch& batch, size_t paramOffset) final;
virtual void do_setResourceTextureTable(const Batch& batch, size_t paramOffset);
virtual void do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) final;
// Pipeline Stage
@ -230,6 +236,10 @@ protected:
void recycle() const override;
// FIXME instead of a single flag, create a features struct similar to
// https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDeviceFeatures.html
virtual bool supportsBindless() const { return false; }
static const size_t INVALID_OFFSET = (size_t)-1;
bool _inRenderTransferPass { false };
int32_t _uboAlignment { 0 };
@ -389,6 +399,10 @@ protected:
virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0;
virtual void releaseResourceBuffer(uint32_t slot) = 0;
// Helper function that provides common code used by do_setResourceTexture and
// do_setResourceTextureTable (in non-bindless mode)
void bindResourceTexture(uint32_t slot, const TexturePointer& texture);
// update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s
void releaseResourceTexture(uint32_t slot);

View file

@ -1,4 +1,4 @@
//
//
// GLBackendPipeline.cpp
// libraries/gpu/src/gpu
//
@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "GLBackend.h"
#include <gpu/TextureTable.h>
#include "GLShared.h"
#include "GLPipeline.h"
#include "GLShader.h"
@ -247,8 +249,11 @@ void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) {
return;
}
TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint);
const auto& resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint);
bindResourceTexture(slot, resourceTexture);
}
void GLBackend::bindResourceTexture(uint32_t slot, const TexturePointer& resourceTexture) {
if (!resourceTexture) {
releaseResourceTexture(slot);
return;
@ -306,6 +311,19 @@ void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& reso
}
}
void GLBackend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) {
const auto& textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint);
if (!textureTablePointer) {
return;
}
const auto& textureTable = *textureTablePointer;
const auto& textures = textureTable.getTextures();
for (GLuint slot = 0; slot < textures.size(); ++slot) {
bindResourceTexture(slot, textures[slot]);
}
}
int GLBackend::ResourceStageState::findEmptyTextureSlot() const {
// start from the end of the slots, try to find an empty one that can be used
for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) {
@ -315,4 +333,3 @@ int GLBackend::ResourceStageState::findEmptyTextureSlot() const {
}
return -1;
}

View file

@ -78,6 +78,11 @@ R"SHADER(
#endif
};
// TextureTable specific defines
static const std::string textureTableVersion {
"#extension GL_ARB_bindless_texture : require\n#define GPU_TEXTURE_TABLE_BINDLESS\n"
};
// Versions specific of the shader
static const std::array<std::string, GLShader::NumVersions> VERSION_DEFINES { {
"",
@ -96,9 +101,9 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co
auto& shaderObject = shaderObjects[version];
std::string shaderDefines = getBackendShaderHeader() + "\n"
+ (supportsBindless() ? textureTableVersion : "\n")
+ DOMAIN_DEFINES[shader.getType()] + "\n"
+ VERSION_DEFINES[version];
if (handler) {
bool retest = true;
std::string currentSrc = shaderSource;
@ -120,7 +125,7 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co
compilationLogs[version].compiled = ::gl::compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, compilationLogs[version].message);
}
if (!compilationLogs[version].compiled) {
if (!compilationLogs[version].compiled) {
qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << compilationLogs[version].message.c_str();
shader.setCompilationLogs(compilationLogs);
return nullptr;

View file

@ -167,7 +167,6 @@ void GL41Texture::syncSampler() const {
glTexParameteri(_target, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]);
glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor());
glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset());
glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
@ -206,9 +205,6 @@ void GL41FixedAllocationTexture::allocateStorage() const {
void GL41FixedAllocationTexture::syncSampler() const {
Parent::syncSampler();
const Sampler& sampler = _gpuObject.getSampler();
auto baseMip = std::max<uint16_t>(sampler.getMipOffset(), sampler.getMinMip());
glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, baseMip);
glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.0f : sampler.getMaxMip()));
}
@ -610,4 +606,3 @@ GL41ResourceTexture::GL41ResourceTexture(const std::weak_ptr<GLBackend>& backend
GL41ResourceTexture::~GL41ResourceTexture() {
}

View file

@ -16,9 +16,11 @@
#include <gpu/gl/GLTexture.h>
#include <thread>
#include <gpu/TextureTable.h>
#define INCREMENTAL_TRANSFER 0
#define GPU_SSBO_TRANSFORM_OBJECT 1
#define GPU_BINDLESS_TEXTURES 0
namespace gpu { namespace gl45 {
@ -31,6 +33,9 @@ class GL45Backend : public GLBackend {
friend class Context;
public:
#if GPU_BINDLESS_TEXTURES
virtual bool supportsBindless() const override { return true; }
#endif
#ifdef GPU_SSBO_TRANSFORM_OBJECT
static const GLint TRANSFORM_OBJECT_SLOT { 14 }; // SSBO binding slot
@ -58,8 +63,62 @@ public:
void generateMips() const override;
Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
void syncSampler() const override;
#if GPU_BINDLESS_TEXTURES
bool isBindless() const {
return _bindless.operator bool();
}
struct Bindless {
uint64_t handle{ 0 };
uint32_t minMip{ 0 };
uint32_t sampler{ 0 };
bool operator==(const Bindless& other) const {
return handle == other.handle && minMip == other.minMip && sampler == other.sampler;
}
bool operator!=(const Bindless& other) const {
return !(*this == other);
}
operator bool() const {
return handle != 0;
}
};
virtual const Bindless& getBindless() const;
void releaseBindless() const;
void recreateBindless() const;
private:
mutable Bindless _bindless;
#endif
static Sampler getInvalidSampler();
// This stores the texture handle (64 bits) in xy, the min mip available in z, and the sampler ID in w
mutable Sampler _cachedSampler{ getInvalidSampler() };
};
#if GPU_BINDLESS_TEXTURES
class GL45TextureTable : public GLObject<TextureTable> {
static GLuint allocate();
using Parent = GLObject<TextureTable>;
public:
using BindlessArray = std::array<GL45Texture::Bindless, TextureTable::COUNT>;
GL45TextureTable(const std::weak_ptr<GLBackend>& backend, const TextureTable& texture);
~GL45TextureTable();
void update(const BindlessArray& newHandles);
// FIXME instead of making a buffer for each table, there should be a global buffer of all materials
// and we should store an offset into that buffer
BindlessArray _handles;
};
#endif
//
// Textures that have fixed allocation sizes and cannot be managed at runtime
//
@ -74,6 +133,7 @@ public:
protected:
Size size() const override { return _size; }
void allocateStorage() const;
void syncSampler() const override;
const Size _size { 0 };
@ -104,7 +164,6 @@ public:
friend class GL45Backend;
using PromoteLambda = std::function<void()>;
protected:
GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture);
~GL45VariableAllocationTexture();
@ -114,6 +173,9 @@ public:
Size copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const override;
void copyTextureMipsInGPUMem(GLuint srcId, GLuint destId, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips) override;
#if GPU_BINDLESS_TEXTURES
virtual const Bindless& getBindless() const override;
#endif
};
class GL45ResourceTexture : public GL45VariableAllocationTexture {
@ -182,6 +244,7 @@ protected:
GLuint getQueryID(const QueryPointer& query) override;
GLQuery* syncGPUObject(const Query& query) override;
// Draw Stage
void do_draw(const Batch& batch, size_t paramOffset) override;
void do_drawIndexed(const Batch& batch, size_t paramOffset) override;
@ -213,6 +276,12 @@ protected:
// Texture Management Stage
void initTextureManagementStage() override;
#if GPU_BINDLESS_TEXTURES
GL45TextureTable* syncGPUObject(const TextureTablePointer& textureTable);
// Resource stage
void do_setResourceTextureTable(const Batch& batch, size_t paramOffset) override;
#endif
};
} }

View file

@ -165,6 +165,11 @@ void GL45Backend::makeProgramBindings(ShaderObject& shaderObject) {
shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT;
}
loc = glGetUniformBlockIndex(glprogram, "gpu_resourceTextureTable0");
if (loc >= 0) {
glUniformBlockBinding(glprogram, loc, RESOURCE_TABLE_TEXTURE_SLOT_OFFSET);
}
(void)CHECK_GL_ERROR();
}

View file

@ -20,14 +20,17 @@
#include <QtCore/QDebug>
#include <NumericalConstants.h>
#include <gl/Context.h>
#include <gpu/TextureTable.h>
#include <gpu/gl/GLTexelFormat.h>
using namespace gpu;
using namespace gpu::gl;
using namespace gpu::gl45;
#define SPARSE_PAGE_SIZE_OVERHEAD_ESTIMATE 1.3f
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define FORCE_STRICT_TEXTURE 0
#define ENABLE_SPARSE_TEXTURE 0
GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
if (!texturePointer) {
@ -51,14 +54,18 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
object = new GL45AttachmentTexture(shared_from_this(), texture);
break;
#if FORCE_STRICT_TEXTURE
case TextureUsageType::RESOURCE:
#endif
case TextureUsageType::STRICT_RESOURCE:
qCDebug(gpugllogging) << "Strict texture " << texture.source().c_str();
object = new GL45StrictResourceTexture(shared_from_this(), texture);
break;
#if !FORCE_STRICT_TEXTURE
case TextureUsageType::RESOURCE: {
if (GL45VariableAllocationTexture::_frameTexturesCreated < MAX_RESOURCE_TEXTURES_PER_FRAME) {
#if 0
#if ENABLE_SPARSE_TEXTURE
if (isTextureManagementSparseEnabled() && GL45Texture::isSparseEligible(texture)) {
object = new GL45SparseResourceTexture(shared_from_this(), texture);
} else {
@ -76,7 +83,7 @@ GLTexture* GL45Backend::syncGPUObject(const TexturePointer& texturePointer) {
}
break;
}
#endif
default:
Q_UNREACHABLE();
}
@ -114,6 +121,61 @@ void GL45Backend::initTextureManagementStage() {
using GL45Texture = GL45Backend::GL45Texture;
class GLSamplerCache {
public:
GLuint getGLSampler(const Sampler& sampler) {
if (0 == _samplerCache.count(sampler)) {
GLuint result = 0;
glGenSamplers(1, &result);
const auto& fm = GLTexture::FILTER_MODES[sampler.getFilter()];
glSamplerParameteri(result, GL_TEXTURE_MIN_FILTER, fm.minFilter);
glSamplerParameteri(result, GL_TEXTURE_MAG_FILTER, fm.magFilter);
if (sampler.doComparison()) {
glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB);
glSamplerParameteri(result, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]);
} else {
glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_NONE);
}
glSamplerParameteri(result, GL_TEXTURE_WRAP_S, GLTexture::WRAP_MODES[sampler.getWrapModeU()]);
glSamplerParameteri(result, GL_TEXTURE_WRAP_T, GLTexture::WRAP_MODES[sampler.getWrapModeV()]);
glSamplerParameteri(result, GL_TEXTURE_WRAP_R, GLTexture::WRAP_MODES[sampler.getWrapModeW()]);
glSamplerParameterf(result, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy());
glSamplerParameterfv(result, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor());
glSamplerParameterf(result, GL_TEXTURE_MIN_LOD, sampler.getMinMip());
glSamplerParameterf(result, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
_samplerCache[sampler] = result;
return result;
}
return _samplerCache[sampler];
}
void releaseGLSampler(GLuint sampler) {
// NO OP
}
private:
std::unordered_map<Sampler, GLuint> _samplerCache;
};
static GLSamplerCache SAMPLER_CACHE;
Sampler GL45Texture::getInvalidSampler() {
static Sampler INVALID_SAMPLER;
static std::once_flag once;
std::call_once(once, [] {
Sampler::Desc invalidDesc;
invalidDesc._borderColor = vec4(-1.0f);
INVALID_SAMPLER = Sampler(invalidDesc);
});
return INVALID_SAMPLER;
}
GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture)
: GLTexture(backend, texture, allocate(texture)) {
}
@ -121,10 +183,10 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
GLuint GL45Texture::allocate(const Texture& texture) {
GLuint result;
glCreateTextures(getGLTextureType(texture), 1, &result);
#ifdef DEBUG
auto source = texture.source();
glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data());
#endif
if (::gl::Context::enableDebugLogger()) {
auto source = texture.source();
glObjectLabel(GL_TEXTURE, result, (GLsizei)source.length(), source.data());
}
return result;
}
@ -189,8 +251,50 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
return amountCopied;
}
#if GPU_BINDLESS_TEXTURES
void GL45Texture::releaseBindless() const {
// Release the old handler
SAMPLER_CACHE.releaseGLSampler(_bindless.sampler);
glMakeTextureHandleNonResidentARB(_bindless.handle);
_bindless = Bindless();
}
void GL45Texture::recreateBindless() const {
if (isBindless()) {
releaseBindless();
} else {
// Once a texture is about to become bindless, it's base mip level MUST be set to 0
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0);
}
_bindless.sampler = SAMPLER_CACHE.getGLSampler(_cachedSampler);
_bindless.handle = glGetTextureSamplerHandleARB(_id, _bindless.sampler);
glMakeTextureHandleResidentARB(_bindless.handle);
}
const GL45Texture::Bindless& GL45Texture::getBindless() const {
if (!_bindless) {
recreateBindless();
}
return _bindless;
}
#endif
void GL45Texture::syncSampler() const {
const Sampler& sampler = _gpuObject.getSampler();
if (_cachedSampler == sampler) {
return;
}
_cachedSampler = sampler;
#if GPU_BINDLESS_TEXTURES
if (isBindless()) {
recreateBindless();
return;
}
#endif
const auto& fm = FILTER_MODES[sampler.getFilter()];
glTextureParameteri(_id, GL_TEXTURE_MIN_FILTER, fm.minFilter);
@ -214,6 +318,8 @@ void GL45Texture::syncSampler() const {
glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
}
// Fixed allocation textures, used for strict resources & framebuffer attachments
using GL45FixedAllocationTexture = GL45Backend::GL45FixedAllocationTexture;
GL45FixedAllocationTexture::GL45FixedAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture), _size(texture.evalTotalSize()) {
@ -238,8 +344,6 @@ void GL45FixedAllocationTexture::allocateStorage() const {
void GL45FixedAllocationTexture::syncSampler() const {
Parent::syncSampler();
const Sampler& sampler = _gpuObject.getSampler();
auto baseMip = std::max<uint16_t>(sampler.getMipOffset(), sampler.getMinMip());
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, baseMip);
glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip());
glTextureParameterf(_id, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip()));
}
@ -275,6 +379,9 @@ GL45StrictResourceTexture::GL45StrictResourceTexture(const std::weak_ptr<GLBacke
if (texture.isAutogenerateMips()) {
generateMips();
}
// Re-sync the sampler to force access to the new mip level
syncSampler();
}
GL45StrictResourceTexture::~GL45StrictResourceTexture() {
@ -282,3 +389,87 @@ GL45StrictResourceTexture::~GL45StrictResourceTexture() {
Backend::textureResidentGPUMemSize.update(size(), 0);
}
// Encapsulate bindless textures
#if GPU_BINDLESS_TEXTURES
using GL45TextureTable = GL45Backend::GL45TextureTable;
GLuint GL45TextureTable::allocate() {
GLuint result;
glCreateBuffers(1, &result);
return result;
}
GL45TextureTable::GL45TextureTable(const std::weak_ptr<GLBackend>& backend, const TextureTable& textureTable)
: Parent(backend, textureTable, allocate()){
Backend::setGPUObject(textureTable, this);
// FIXME include these in overall buffer storage reporting
glNamedBufferStorage(_id, sizeof(uvec4) * TextureTable::COUNT, nullptr, GL_DYNAMIC_STORAGE_BIT);
}
void GL45TextureTable::update(const BindlessArray& handles) {
if (_handles != handles) {
_handles = handles;
// FIXME include these in overall buffer storage reporting
// FIXME use a single shared buffer for bindless data
glNamedBufferSubData(_id, 0, sizeof(GL45Texture::Bindless) * TextureTable::COUNT, &_handles[0]);
}
}
GL45TextureTable::~GL45TextureTable() {
if (_id) {
auto backend = _backend.lock();
if (backend) {
// FIXME include these in overall buffer storage reporting
backend->releaseBuffer(_id, 0);
}
}
}
GL45TextureTable* GL45Backend::syncGPUObject(const TextureTablePointer& textureTablePointer) {
const auto& textureTable = *textureTablePointer;
// Find the target handles
auto defaultTextures = gpu::TextureTable::getDefault()->getTextures();
auto textures = textureTable.getTextures();
GL45TextureTable::BindlessArray handles{};
for (size_t i = 0; i < textures.size(); ++i) {
auto texture = textures[i];
if (!texture) {
texture = defaultTextures[i];
}
if (!texture) {
continue;
}
// FIXME what if we have a non-transferrable texture here?
auto gltexture = (GL45Texture*)syncGPUObject(texture);
if (!gltexture) {
continue;
}
handles[i] = gltexture->getBindless();
}
// If the object hasn't been created, or the object definition is out of date, drop and re-create
GL45TextureTable* object = Backend::getGPUObject<GL45TextureTable>(textureTable);
if (!object) {
object = new GL45TextureTable(shared_from_this(), textureTable);
}
object->update(handles);
return object;
}
void GL45Backend::do_setResourceTextureTable(const Batch& batch, size_t paramOffset) {
auto textureTablePointer = batch._textureTables.get(batch._params[paramOffset]._uint);
if (!textureTablePointer) {
return;
}
auto slot = batch._params[paramOffset + 1]._uint;
GL45TextureTable* glTextureTable = syncGPUObject(textureTablePointer);
if (glTextureTable) {
glBindBufferBase(GL_UNIFORM_BUFFER, slot + GLBackend::RESOURCE_TABLE_TEXTURE_SLOT_OFFSET, glTextureTable->_id);
}
}
#endif

View file

@ -27,6 +27,7 @@ using namespace gpu;
using namespace gpu::gl;
using namespace gpu::gl45;
using GL45Texture = GL45Backend::GL45Texture;
using GL45VariableAllocationTexture = GL45Backend::GL45VariableAllocationTexture;
GL45VariableAllocationTexture::GL45VariableAllocationTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture) : GL45Texture(backend, texture) {
@ -40,6 +41,17 @@ GL45VariableAllocationTexture::~GL45VariableAllocationTexture() {
Backend::textureResourcePopulatedGPUMemSize.update(_populatedSize, 0);
}
#if GPU_BINDLESS_TEXTURES
const GL45Texture::Bindless& GL45VariableAllocationTexture::getBindless() const {
// The parent call may re-initialize the _bindless member, so we need to call it first
const auto& result = Parent::getBindless();
// Make sure the referenced structure has the correct minimum available mip value
_bindless.minMip = _populatedMip - _allocatedMip;
// Now return the result
return result;
}
#endif
Size GL45VariableAllocationTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, GLenum internalFormat, GLenum format, GLenum type, Size sourceSize, const void* sourcePointer) const {
Size amountCopied = 0;
amountCopied = Parent::copyMipFaceLinesFromTexture(mip, face, size, yOffset, internalFormat, format, type, sourceSize, sourcePointer);
@ -127,7 +139,13 @@ Size GL45ResourceTexture::copyMipsFromTexture() {
void GL45ResourceTexture::syncSampler() const {
Parent::syncSampler();
#if GPU_BINDLESS_TEXTURES
if (!isBindless()) {
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
}
#else
glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip);
#endif
}
void GL45ResourceTexture::promote() {
@ -137,6 +155,13 @@ void GL45ResourceTexture::promote() {
uint16_t targetAllocatedMip = _allocatedMip - std::min<uint16_t>(_allocatedMip, 2);
targetAllocatedMip = std::max<uint16_t>(_minAllocatedMip, targetAllocatedMip);
#if GPU_BINDLESS_TEXTURES
bool bindless = isBindless();
if (bindless) {
releaseBindless();
}
#endif
GLuint oldId = _id;
auto oldSize = _size;
uint16_t oldAllocatedMip = _allocatedMip;
@ -150,10 +175,17 @@ void GL45ResourceTexture::promote() {
// copy pre-existing mips
copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip);
#if GPU_BINDLESS_TEXTURES
if (bindless) {
getBindless();
}
#endif
// destroy the old texture
glDeleteTextures(1, &oldId);
// Update sampler
_cachedSampler = getInvalidSampler();
syncSampler();
// update the memory usage
@ -170,6 +202,13 @@ void GL45ResourceTexture::demote() {
auto oldSize = _size;
auto oldPopulatedMip = _populatedMip;
#if GPU_BINDLESS_TEXTURES
bool bindless = isBindless();
if (bindless) {
releaseBindless();
}
#endif
// allocate new texture
const_cast<GLuint&>(_id) = allocate(_gpuObject);
uint16_t oldAllocatedMip = _allocatedMip;
@ -179,10 +218,17 @@ void GL45ResourceTexture::demote() {
// copy pre-existing mips
copyTextureMipsInGPUMem(oldId, _id, oldAllocatedMip, _allocatedMip, _populatedMip);
#if GPU_BINDLESS_TEXTURES
if (bindless) {
getBindless();
}
#endif
// destroy the old texture
glDeleteTextures(1, &oldId);
// Update sampler
_cachedSampler = getInvalidSampler();
syncSampler();
// update the memory usage

View file

@ -75,6 +75,7 @@ Batch::Batch(const Batch& batch_) {
_buffers._items.swap(batch._buffers._items);
_textures._items.swap(batch._textures._items);
_textureTables._items.swap(batch._textureTables._items);
_streamFormats._items.swap(batch._streamFormats._items);
_transforms._items.swap(batch._transforms._items);
_pipelines._items.swap(batch._pipelines._items);
@ -113,6 +114,7 @@ void Batch::clear() {
_data.clear();
_buffers.clear();
_textures.clear();
_textureTables.clear();
_streamFormats.clear();
_transforms.clear();
_pipelines.clear();
@ -337,6 +339,12 @@ void Batch::setResourceTexture(uint32 slot, const TextureView& view) {
setResourceTexture(slot, view._texture);
}
void Batch::setResourceTextureTable(const TextureTablePointer& textureTable, uint32 slot) {
ADD_COMMAND(setResourceTextureTable);
_params.emplace_back(_textureTables.cache(textureTable));
_params.emplace_back(slot);
}
void Batch::setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex, unsigned int renderBufferSlot) {
ADD_COMMAND(setResourceFramebufferSwapChainTexture);

View file

@ -187,6 +187,7 @@ public:
void setResourceTexture(uint32 slot, const TexturePointer& texture);
void setResourceTexture(uint32 slot, const TextureView& view); // not a command, just a shortcut from a TextureView
void setResourceTextureTable(const TextureTablePointer& table, uint32 slot = 0);
void setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swpaChainIndex, unsigned int renderBufferSlot = 0U); // not a command, just a shortcut from a TextureView
// Ouput Stage
@ -302,6 +303,7 @@ public:
COMMAND_setUniformBuffer,
COMMAND_setResourceBuffer,
COMMAND_setResourceTexture,
COMMAND_setResourceTextureTable,
COMMAND_setResourceFramebufferSwapChainTexture,
COMMAND_setFramebuffer,
@ -409,9 +411,10 @@ public:
return offset;
}
Data get(uint32 offset) const {
const Data& get(uint32 offset) const {
if (offset >= _items.size()) {
return Data();
static const Data EMPTY;
return EMPTY;
}
return (_items.data() + offset)->_data;
}
@ -424,6 +427,7 @@ public:
typedef Cache<BufferPointer>::Vector BufferCaches;
typedef Cache<TexturePointer>::Vector TextureCaches;
typedef Cache<TextureTablePointer>::Vector TextureTableCaches;
typedef Cache<Stream::FormatPointer>::Vector StreamFormatCaches;
typedef Cache<Transform>::Vector TransformCaches;
typedef Cache<PipelinePointer>::Vector PipelineCaches;
@ -479,6 +483,7 @@ public:
BufferCaches _buffers;
TextureCaches _textures;
TextureTableCaches _textureTables;
StreamFormatCaches _streamFormats;
TransformCaches _transforms;
PipelineCaches _pipelines;

View file

@ -91,6 +91,8 @@ namespace gpu {
using Textures = std::vector<TexturePointer>;
class TextureView;
using TextureViews = std::vector<TextureView>;
class TextureTable;
using TextureTablePointer = std::shared_ptr<TextureTable>;
struct StereoState {
bool isStereo() const {

View file

@ -17,8 +17,10 @@
#include <QMetaType>
#include <QUrl>
#include <functional>
#include <shared/Storage.h>
#include <shared/FileCache.h>
#include <RegisteredMetaTypes.h>
#include "Forward.h"
#include "Resource.h"
#include "Metric.h"
@ -126,12 +128,23 @@ public:
uint8 _wrapModeV = WRAP_REPEAT;
uint8 _wrapModeW = WRAP_REPEAT;
uint8 _mipOffset = 0;
uint8 _minMip = 0;
uint8 _maxMip = MAX_MIP_LEVEL;
Desc() {}
Desc(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _filter(filter), _wrapModeU(wrap), _wrapModeV(wrap), _wrapModeW(wrap) {}
bool operator==(const Desc& other) const {
return _borderColor == other._borderColor &&
_maxAnisotropy == other._maxAnisotropy &&
_filter == other._filter &&
_comparisonFunc == other._comparisonFunc &&
_wrapModeU == other._wrapModeU &&
_wrapModeV == other._wrapModeV &&
_wrapModeW == other._wrapModeW &&
_minMip == other._minMip &&
_maxMip == other._maxMip;
}
};
Sampler() {}
@ -151,11 +164,17 @@ public:
ComparisonFunction getComparisonFunction() const { return ComparisonFunction(_desc._comparisonFunc); }
bool doComparison() const { return getComparisonFunction() != ALWAYS; }
uint8 getMipOffset() const { return _desc._mipOffset; }
uint8 getMinMip() const { return _desc._minMip; }
uint8 getMaxMip() const { return _desc._maxMip; }
const Desc& getDesc() const { return _desc; }
bool operator==(const Sampler& other) const {
return _desc == other._desc;
}
bool operator!=(const Sampler& other) const {
return !(*this == other);
}
protected:
Desc _desc;
};
@ -666,6 +685,17 @@ typedef std::shared_ptr< TextureSource > TextureSourcePointer;
};
namespace std {
template<> struct hash<gpu::Sampler> {
size_t operator()(const gpu::Sampler& sampler) const noexcept {
size_t result = 0;
const auto& desc = sampler.getDesc();
hash_combine(result, desc._comparisonFunc, desc._filter, desc._maxAnisotropy, desc._maxMip, desc._minMip, desc._wrapModeU, desc._wrapModeV, desc._wrapModeW);
return result;
}
};
}
Q_DECLARE_METATYPE(gpu::TexturePointer)
#endif

View file

@ -0,0 +1,54 @@
//
// Created by Bradley Austin Davis on 2017/01/25
// Copyright 2013-2017 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 "TextureTable.h"
#include "Texture.h"
#include <shared/GlobalAppProperties.h>
using namespace gpu;
const size_t TextureTable::COUNT{ TEXTURE_TABLE_COUNT };
TextureTable::TextureTable() { }
TextureTable::TextureTable(const std::initializer_list<TexturePointer>& textures) {
auto max = std::min<size_t>(COUNT, textures.size());
auto itr = textures.begin();
size_t index = 0;
while (itr != textures.end() && index < max) {
setTexture(index, *itr);
++index;
}
}
TextureTable::TextureTable(const Array& textures) : _textures(textures) {
}
void TextureTable::setTexture(size_t index, const TexturePointer& texturePointer) {
if (index >= COUNT || _textures[index] == texturePointer) {
return;
}
{
Lock lock(_mutex);
++_stamp;
_textures[index] = texturePointer;
}
}
void TextureTable::setTexture(size_t index, const TextureView& textureView) {
setTexture(index, textureView._texture);
}
TextureTable::Array TextureTable::getTextures() const {
Array result;
{
Lock lock(_mutex);
result = _textures;
}
return result;
}

View file

@ -0,0 +1,44 @@
//
// Created by Bradley Austin Davis on 2017/01/25
// Copyright 2013-2017 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_gpu_TextureTable_h
#define hifi_gpu_TextureTable_h
#include "Forward.h"
#include <array>
#define TEXTURE_TABLE_COUNT 8
namespace gpu {
class TextureTable {
public:
static const size_t COUNT;
using Array = std::array<TexturePointer, TEXTURE_TABLE_COUNT>;
TextureTable();
TextureTable(const std::initializer_list<TexturePointer>& textures);
TextureTable(const Array& textures);
// Only for gpu::Context
const GPUObjectPointer gpuObject{};
void setTexture(size_t index, const TexturePointer& texturePointer);
void setTexture(size_t index, const TextureView& texturePointer);
Array getTextures() const;
Stamp getStamp() const { return _stamp; }
private:
mutable Mutex _mutex;
Array _textures;
Stamp _stamp{ 1 };
};
}
#endif

View file

@ -0,0 +1,38 @@
<!
// gpu/TextureTable.slh
//
// Created by Sam Gateau on 1/25/17.
// 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
!>
<@if not GPU_TEXTURE_TABLE_SLH@>
<@def GPU_TEXTURE_TABLE_SLH@>
#ifdef GPU_TEXTURE_TABLE_BINDLESS
#define GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES 8
struct GPUTextureTable {
uvec4 _textures[GPU_TEXTURE_TABLE_MAX_NUM_TEXTURES];
};
#define TextureTable(index, name) layout (std140) uniform gpu_resourceTextureTable##index { GPUTextureTable name; }
#define tableTex(name, slot) sampler2D(name._textures[slot].xy)
#define tableTexMinLod(name, slot) float(name._textures[slot].z)
#define tableTexValue(name, slot, uv) \
tableTexValueLod(tableTex(matTex, albedoMap), tableTexMinLod(matTex, albedoMap), uv)
vec4 tableTexValueLod(sampler2D sampler, float minLod, vec2 uv) {
float queryLod = textureQueryLod(sampler, uv).x;
queryLod = max(minLod, queryLod);
return textureLod(sampler, uv, queryLod);
}
#else
#endif
<@endif@>

View file

@ -504,6 +504,8 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) {
gpuktxKeyValue._usage = Texture::Usage::Builder().withColor().withAlpha().build();
}
auto samplerDesc = gpuktxKeyValue._samplerDesc;
samplerDesc._maxMip = gpu::Sampler::MAX_MIP_LEVEL;
auto texture = create(gpuktxKeyValue._usageType,
type,
texelFormat,
@ -513,7 +515,7 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) {
1, // num Samples
header.getNumberOfSlices(),
header.getNumberOfLevels(),
gpuktxKeyValue._samplerDesc);
samplerDesc);
texture->setUsage(gpuktxKeyValue._usage);
// Assing the mips availables

View file

@ -20,6 +20,7 @@
#include <ColorUtils.h>
#include <gpu/Resource.h>
#include <gpu/TextureTable.h>
class Transform;
@ -361,6 +362,8 @@ public:
const std::string& getModel() const { return _model; }
void setModel(const std::string& model) { _model = model; }
const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; }
protected:
std::string _name { "" };
@ -368,6 +371,7 @@ private:
mutable MaterialKey _key;
mutable UniformBufferView _schemaBuffer;
mutable UniformBufferView _texMapArrayBuffer;
mutable gpu::TextureTablePointer _textureTable{ std::make_shared<gpu::TextureTable>() };
TextureMaps _textureMaps;

View file

@ -68,8 +68,9 @@ void Pointer::update(unsigned int pointerID) {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
withReadLock([&] {
auto pickResult = getPrevPickResult();
updateVisuals(pickResult);
generatePointerEvents(pointerID, pickResult);
auto visualPickResult = getVisualPickResult(pickResult);
updateVisuals(visualPickResult);
generatePointerEvents(pointerID, visualPickResult);
});
}

View file

@ -89,6 +89,7 @@ protected:
virtual bool shouldHover(const PickResultPointer& pickResult) { return true; }
virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; }
virtual PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) { return pickResult; };
static const float POINTER_MOVE_DELAY;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED;

View file

@ -380,6 +380,8 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const
batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, nullptr);
batch.setResourceTexture(AntialiasingPass_NextMapSlot, nullptr);
});
args->popViewFrustum();
}
@ -520,7 +522,7 @@ void JitterSample::run(const render::RenderContextPointer& renderContext) {
viewFrustum.setProjection(projMat);
viewFrustum.calculate();
args->setViewFrustum(viewFrustum);
args->pushViewFrustum(viewFrustum);
} else {
mat4 projMats[2];
args->_context->getStereoProjections(projMats);
@ -538,4 +540,4 @@ void JitterSample::run(const render::RenderContextPointer& renderContext) {
}
#endif
#endif

View file

@ -39,43 +39,52 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F
auto scene = renderContext->_scene;
if (_isEditEnabled) {
float minIsectDistance = std::numeric_limits<float>::max();
auto& itemBounds = inputs.get0();
auto editedItem = findNearestItem(renderContext, itemBounds, minIsectDistance);
render::Transaction transaction;
bool hasTransaction{ false };
static const std::string selectionName("TransitionEdit");
auto scene = renderContext->_scene;
if (!scene->isSelectionEmpty(selectionName)) {
auto selection = scene->getSelection(selectionName);
auto editedItem = selection.getItems().front();
render::Transaction transaction;
bool hasTransaction{ false };
if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've changed edited item
hasTransaction = true;
if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've changed edited item
hasTransaction = true;
transaction.removeTransitionFromItem(_editedItem);
}
_editedItem = editedItem;
if (render::Item::isValidID(_editedItem)) {
static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = {
render::Transition::ELEMENT_ENTER_DOMAIN,
render::Transition::BUBBLE_ISECT_OWNER,
render::Transition::BUBBLE_ISECT_TRESPASSER,
render::Transition::USER_ENTER_DOMAIN,
render::Transition::AVATAR_CHANGE
};
auto transitionType = categoryToTransition[inputs.get1()];
transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) {
if (transition == nullptr || transition->isFinished || transition->eventType != transitionType) {
// Relaunch transition
render::Transaction transaction;
transaction.addTransitionToItem(id, transitionType);
scene->enqueueTransaction(transaction);
}
});
hasTransaction = true;
}
if (hasTransaction) {
scene->enqueueTransaction(transaction);
}
} else if (render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've disabled fade edition
render::Transaction transaction;
transaction.removeTransitionFromItem(_editedItem);
}
_editedItem = editedItem;
if (render::Item::isValidID(_editedItem)) {
static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = {
render::Transition::ELEMENT_ENTER_DOMAIN,
render::Transition::BUBBLE_ISECT_OWNER,
render::Transition::BUBBLE_ISECT_TRESPASSER,
render::Transition::USER_ENTER_DOMAIN,
render::Transition::AVATAR_CHANGE
};
auto transitionType = categoryToTransition[inputs.get1()];
transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) {
if (transition == nullptr || transition->isFinished || transition->eventType!=transitionType) {
// Relaunch transition
render::Transaction transaction;
transaction.addTransitionToItem(id, transitionType);
scene->enqueueTransaction(transaction);
}
});
hasTransaction = true;
}
if (hasTransaction) {
scene->enqueueTransaction(transaction);
_editedItem = render::Item::INVALID_ITEM_ID;
}
}
else if (render::Item::isValidID(_editedItem)) {
@ -87,28 +96,6 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F
}
}
render::ItemID FadeEditJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const {
const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition();
const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection();
BoxFace face;
glm::vec3 normal;
float isectDistance;
render::ItemID nearestItem = render::Item::INVALID_ITEM_ID;
const float minDistance = 1.f;
const float maxDistance = 50.f;
for (const auto& itemBound : inputs) {
if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) {
auto& item = renderContext->_scene->getItem(itemBound.id);
if (item.getKey().isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance<maxDistance) {
nearestItem = itemBound.id;
minIsectDistance = isectDistance;
}
}
}
return nearestItem;
}
FadeConfig::FadeConfig()
{
events[FADE_ELEMENT_ENTER_LEAVE_DOMAIN].noiseSize = glm::vec3{ 0.75f, 0.75f, 0.75f };
@ -353,11 +340,9 @@ QString FadeConfig::eventNames[FADE_CATEGORY_COUNT] = {
"avatar_change",
};
void FadeConfig::save() const {
// Save will only work if the HIFI_USE_SOURCE_TREE_RESOURCES environment variable is set
void FadeConfig::save(const QString& configFilePath) const {
assert(editedCategory < FADE_CATEGORY_COUNT);
QJsonObject lProperties;
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
QFile file(configFilePath);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
qWarning() << "Fade event configuration file " << configFilePath << " cannot be opened";
@ -382,8 +367,7 @@ void FadeConfig::save() const {
}
}
void FadeConfig::load() {
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
void FadeConfig::load(const QString& configFilePath) {
QFile file(configFilePath);
if (!file.exists()) {
qWarning() << "Fade event configuration file " << configFilePath << " does not exist";
@ -594,7 +578,7 @@ void FadeJob::run(const render::RenderContextPointer& renderContext, FadeJob::Ou
if (update(*jobConfig, scene, transaction, state, deltaTime)) {
hasTransaction = true;
}
if (isFirstItem && jobConfig->manualFade && (state.threshold != jobConfig->threshold)) {
if (isFirstItem && (state.threshold != jobConfig->threshold)) {
jobConfig->setProperty("threshold", state.threshold);
isFirstItem = false;
}

View file

@ -160,8 +160,8 @@ public:
float manualThreshold{ 0.f };
bool manualFade{ false };
Q_INVOKABLE void save() const;
Q_INVOKABLE void load();
Q_INVOKABLE void save(const QString& filePath) const;
Q_INVOKABLE void load(const QString& filePath);
static QString eventNames[FADE_CATEGORY_COUNT];
@ -190,7 +190,6 @@ private:
bool _isEditEnabled{ false };
render::ItemID _editedItem{ render::Item::INVALID_ITEM_ID };
render::ItemID findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const;
};
class FadeJob {

View file

@ -48,6 +48,74 @@ TexMapArray getTexMapArray() {
<@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion, withScattering)@>
<@include gpu/TextureTable.slh@>
#ifdef GPU_TEXTURE_TABLE_BINDLESS
TextureTable(0, matTex);
<!
ALBEDO = 0,
NORMAL, 1
METALLIC, 2
EMISSIVE_LIGHTMAP, 3
ROUGHNESS, 4
OCCLUSION, 5
SCATTERING, 6
!>
<@if withAlbedo@>
#define albedoMap 0
vec4 fetchAlbedoMap(vec2 uv) {
return tableTexValue(matTex, albedoMap, uv);
}
<@endif@>
<@if withRoughness@>
#define roughnessMap 4
float fetchRoughnessMap(vec2 uv) {
return tableTexValue(matTex, roughnessMap, uv).r;
}
<@endif@>
<@if withNormal@>
#define normalMap 1
vec3 fetchNormalMap(vec2 uv) {
return tableTexValue(matTex, normalMap, uv).xyz;
}
<@endif@>
<@if withMetallic@>
#define metallicMap 2
float fetchMetallicMap(vec2 uv) {
return tableTexValue(matTex, metallicMap, uv).r;
}
<@endif@>
<@if withEmissive@>
#define emissiveMap 3
vec3 fetchEmissiveMap(vec2 uv) {
return tableTexValue(matTex, emissiveMap, uv).rgb;
}
<@endif@>
<@if withOcclusion@>
#define occlusionMap 5
float fetchOcclusionMap(vec2 uv) {
return tableTexValue(matTex, occlusionMap, uv).r;
}
<@endif@>
<@if withScattering@>
#define scatteringMap 6
float fetchScatteringMap(vec2 uv) {
float scattering = texture(tableTex(matTex, scatteringMap), uv).r; // boolean scattering for now
return max(((scattering - 0.1) / 0.9), 0.0);
return tableTexValue(matTex, scatteringMap, uv).r; // boolean scattering for now
}
<@endif@>
#else
<@if withAlbedo@>
uniform sampler2D albedoMap;
vec4 fetchAlbedoMap(vec2 uv) {
@ -102,6 +170,8 @@ float fetchScatteringMap(vec2 uv) {
}
<@endif@>
#endif
<@endfunc@>

View file

@ -152,7 +152,7 @@ void MeshPartPayload::bindMesh(gpu::Batch& batch) {
batch.setInputStream(0, _drawMesh->getVertexStream());
}
void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const {
void MeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const {
batch.setModelTransform(_drawTransform);
}

View file

@ -612,7 +612,8 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state) {
#include "RenderPipelines.h"
#include <model-networking/TextureCache.h>
void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures) {
// FIXME find a better way to setup the default textures
void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) {
if (!material) {
return;
}
@ -630,84 +631,65 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc
numUnlit++;
}
if (!enableTextures) {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
return;
}
const auto& drawMaterialTextures = material->getTextureTable();
// Albedo
if (materialKey.isAlbedoMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView());
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture());
}
}
// Roughness map
if (materialKey.isRoughnessMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::ROUGHNESS, textureCache->getWhiteTexture());
}
}
// Normal map
if (materialKey.isNormalMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::NORMAL, textureCache->getBlueTexture());
}
}
// Metallic map
if (materialKey.isMetallicMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::METALLIC, textureCache->getBlackTexture());
}
}
// Occlusion map
if (materialKey.isOcclusionMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::OCCLUSION, textureCache->getWhiteTexture());
}
}
// Scattering map
if (materialKey.isScatteringMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, itr->second->getTextureView());
// texcoord are assumed to be the same has albedo
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::SCATTERING, textureCache->getWhiteTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::SCATTERING, textureCache->getWhiteTexture());
}
}
@ -715,18 +697,19 @@ void RenderPipelines::bindMaterial(graphics::MaterialPointer material, gpu::Batc
if (materialKey.isLightmapMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture());
}
} else if (materialKey.isEmissiveMap()) {
auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP);
if (itr != textureMaps.end() && itr->second->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) {
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, itr->second->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
drawMaterialTextures->setTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
}
}
batch.setResourceTextureTable(material->getTextureTable());
}

View file

@ -15,7 +15,7 @@
class RenderPipelines {
public:
static void bindMaterial(graphics::MaterialPointer material, gpu::Batch& batch, bool enableTextures);
static void bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures);
};

View file

@ -46,7 +46,7 @@ void printOctalCode(const unsigned char* octalCode) {
}
char sectionValue(const unsigned char* startByte, char startIndexInByte) {
char rightShift = 8 - startIndexInByte - 3;
int8_t rightShift = 8 - startIndexInByte - 3;
if (rightShift < 0) {
return ((startByte[0] << -rightShift) & 7) + (startByte[1] >> (8 + rightShift));
@ -73,7 +73,7 @@ int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsi
return sectionValue(descendantOctalCode + 1 + (branchStartBit / 8), branchStartBit % 8);
}
unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber) {
unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber) {
// find the length (in number of three bit code sequences)
// in the parent
@ -111,7 +111,7 @@ unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNu
// calculate the amount of left shift required
// this will be -1 or -2 if there's wrap
char leftShift = 8 - (startBit % 8) - 3;
int8_t leftShift = 8 - (startBit % 8) - 3;
if (leftShift < 0) {
// we have a wrap-around to accomodate

View file

@ -30,7 +30,7 @@ using OctalCodePtrList = std::vector<OctalCodePtr>;
void printOctalCode(const unsigned char* octalCode);
size_t bytesRequiredForCodeLength(unsigned char threeBitCodes);
int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsigned char* descendantOctalCode);
unsigned char* childOctalCode(const unsigned char* parentOctalCode, char childNumber);
unsigned char* childOctalCode(const unsigned char* parentOctalCode, int childNumber);
const int OVERFLOWED_OCTCODE_BUFFER = -1;
const int UNKNOWN_OCTCODE_LENGTH = -2;

View file

@ -1,4 +1,4 @@
var qml = Script.resourcesPath() + '/qml/AudioScope.qml';
var qml = Script.resourcesPath() + '/qml/AudioScopeUI.qml';
var window = new OverlayWindow({
title: 'Audio Scope',
source: qml,
@ -14,4 +14,4 @@ window.closed.connect(function () {
AudioScope.setServerEcho(false);
AudioScope.selectAudioScopeFiveFrames();
Script.stop();
});
});

View file

@ -1,5 +1,3 @@
"use strict";
//
// debugTransition.js
// developer/utilities/render
@ -12,12 +10,17 @@
//
(function() {
"use strict";
var TABLET_BUTTON_NAME = "Transition";
var QMLAPP_URL = Script.resolvePath("./transition.qml");
var ICON_URL = Script.resolvePath("../../../system/assets/images/transition-i.svg");
var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/transition-a.svg");
Script.include([
Script.resolvePath("../../../system/libraries/stringHelpers.js"),
]);
var onScreen = false;
function onClicked() {
@ -37,6 +40,71 @@
var hasEventBridge = false;
function enableSphereVisualization() {
if (gradientSphere==undefined) {
gradientSphere = Overlays.addOverlay("sphere", {
position: MyAvatar.position,
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0),
dimensions: { x: 1.0, y: 1.0, z: 1.0 },
color: { red: 100, green: 150, blue: 255},
alpha: 0.2,
solid: false
});
}
if (noiseSphere==undefined) {
noiseSphere = Overlays.addOverlay("sphere", {
position: MyAvatar.position,
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0),
dimensions: { x: 1.0, y: 1.0, z: 1.0 },
color: { red: 255, green: 150, blue: 100},
alpha: 0.2,
solid: false
});
}
}
function disableSphereVisualization() {
Overlays.deleteOverlay(noiseSphere);
Overlays.deleteOverlay(gradientSphere);
noiseSphere = undefined
gradientSphere = undefined
}
// Create a Laser pointer used to pick and add objects to selections
var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 };
var COLOR1 = {red: 255, green: 0, blue: 0};
var COLOR2 = {red: 0, green: 255, blue: 0};
var end1 = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR1,
ignoreRayIntersection: true
}
var end2 = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR2,
ignoreRayIntersection: true
}
var laser
function enablePointer() {
laser = Pointers.createPointer(PickType.Ray, {
joint: "Mouse",
filter: Picks.PICK_ENTITIES,
renderStates: [{name: "one", end: end1}],
defaultRenderStates: [{name: "one", end: end2, distance: 2.0}],
enabled: true
});
Pointers.setRenderState(laser, "one");
Pointers.enablePointer(laser)
}
function disablePointer() {
Pointers.disablePointer(laser)
Pointers.removePointer(laser);
}
function wireEventBridge(on) {
if (!tablet) {
print("Warning in wireEventBridge(): 'tablet' undefined!");
@ -46,11 +114,15 @@
if (!hasEventBridge) {
tablet.fromQml.connect(fromQml);
hasEventBridge = true;
enablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = true
}
} else {
if (hasEventBridge) {
tablet.fromQml.disconnect(fromQml);
hasEventBridge = false;
disablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = false
}
}
}
@ -66,12 +138,87 @@
wireEventBridge(onScreen);
}
var isEditEnabled = false
var noiseSphere
var gradientSphere
var selectedEntity
var editedCategory
var FADE_MIN_SCALE = 0.001
var FADE_MAX_SCALE = 10000.0
function parameterToValuePow(parameter, minValue, maxOverMinValue) {
return minValue * Math.pow(maxOverMinValue, parameter);
//return parameter
}
function update(dt) {
var gradientProperties = Entities.getEntityProperties(selectedEntity, ["position", "dimensions"]);
if (gradientProperties!=undefined) {
var pos = gradientProperties.position
if (pos!=undefined) {
var config = Render.getConfig("RenderMainView.Fade")
//print("Center at "+pos.x+" "+pos.y+" "+pos.z)
var dim = {x:config.baseSizeX, y:config.baseSizeY, z:config.baseSizeZ}
dim.x = parameterToValuePow(dim.x, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.y = parameterToValuePow(dim.y, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.z = parameterToValuePow(dim.z, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
if (editedCategory==4 || editedCategory==5) {
dim.y = gradientProperties.dimensions.y
pos.y = pos.y - gradientProperties.dimensions.y/2
}
Overlays.editOverlay(gradientSphere, { position: pos, dimensions: dim, alpha: config.baseLevel })
dim.x = parameterToValuePow(config.noiseSizeX, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.y = parameterToValuePow(config.noiseSizeY, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.z = parameterToValuePow(config.noiseSizeZ, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
Overlays.editOverlay(noiseSphere, { position: pos, dimensions: dim, alpha: config.noiseLevel })
}
}
}
Script.update.connect(update);
function loadConfiguration(fileUrl) {
var config = Render.getConfig("RenderMainView.Fade")
config.load(fileUrl)
}
function saveConfiguration(fileUrl) {
var config = Render.getConfig("RenderMainView.Fade")
config.save(fileUrl)
}
function fromQml(message) {
tokens = message.split('*')
//print("Received '"+message+"' from transition.qml")
command = tokens[0].toLowerCase()
if (command=="category") {
editedCategory = parseInt(tokens[1])
} else if (command=="save") {
var filePath = tokens[1]
print("Raw token = "+filePath)
if (filePath.startsWith("file:///")) {
filePath = filePath.substr(8)
print("Saving configuration to "+filePath)
saveConfiguration(filePath)
} else {
print("Configurations can only be saved to local files")
}
} else if (command=="load") {
var filePath = tokens[1]
if (filePath.startsWith("file:///")) {
filePath = filePath.substr(8)
print("Loading configuration from "+filePath)
loadConfiguration(filePath)
} else {
print("Configurations can only be loaded from local files")
}
}
}
button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged);
Script.scriptEnding.connect(function () {
if (onScreen) {
tablet.gotoHomeScreen();
@ -81,4 +228,28 @@
tablet.removeButton(button);
});
var currentSelectionName = ""
var SelectionList = "TransitionEdit"
Selection.enableListToScene(SelectionList)
Selection.clearSelectedItemsList(SelectionList)
Entities.clickDownOnEntity.connect(function (id, event) {
if (selectedEntity) {
Selection.removeFromSelectedItemsList(SelectionList, "entity", selectedEntity)
}
selectedEntity = id
Selection.addToSelectedItemsList(SelectionList, "entity", selectedEntity)
update()
})
function cleanup() {
disablePointer();
Selection.removeListFromMap(SelectionList)
Selection.disableListToScene(SelectionList);
Overlays.deleteOverlay(noiseSphere);
Overlays.deleteOverlay(gradientSphere);
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -11,6 +11,7 @@
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
@ -22,11 +23,31 @@ Rectangle {
id: root
anchors.margins: hifi.dimensions.contentMargin.x
signal sendToScript(var message);
color: hifi.colors.baseGray;
property var config: Render.getConfig("RenderMainView.Fade");
property var configEdit: Render.getConfig("RenderMainView.FadeEdit");
FileDialog {
id: fileDialog
title: "Please choose a file"
folder: shortcuts.documents
nameFilters: [ "JSON files (*.json)", "All files (*)" ]
onAccepted: {
root.sendToScript(title.split(" ")[0]+"*"+fileUrl.toString())
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 500ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 500
postpone.start()
}
onRejected: {
}
Component.onCompleted: visible = false
}
ColumnLayout {
spacing: 3
anchors.left: parent.left
@ -37,23 +58,14 @@ Rectangle {
}
RowLayout {
spacing: 20
spacing: 8
Layout.fillWidth: true
id: root_col
HifiControls.CheckBox {
anchors.verticalCenter: parent.verticalCenter
boxSize: 20
text: "Edit"
checked: root.configEdit["editFade"]
onCheckedChanged: {
root.configEdit["editFade"] = checked;
Render.getConfig("RenderMainView.DrawFadedOpaqueBounds").enabled = checked;
}
}
HifiControls.ComboBox {
anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true
anchors.left : parent.left
width: 300
id: categoryBox
model: ["Elements enter/leave domain", "Bubble isect. - Owner POV", "Bubble isect. - Trespasser POV", "Another user leaves/arrives", "Changing an avatar"]
Timer {
@ -61,17 +73,48 @@ Rectangle {
interval: 100; running: false; repeat: false
onTriggered: {
paramWidgetLoader.sourceComponent = paramWidgets
var isTimeBased = categoryBox.currentIndex==0 || categoryBox.currentIndex==3
paramWidgetLoader.item.isTimeBased = isTimeBased
}
}
onCurrentIndexChanged: {
var descriptions = [
"Time based threshold, gradients centered on object",
"Fixed threshold, gradients centered on owner avatar",
"Position based threshold (increases when trespasser moves closer to avatar), gradients centered on trespasser avatar",
"Time based threshold, gradients centered on bottom of object",
"UNSUPPORTED"
]
description.text = descriptions[currentIndex]
root.config["editedCategory"] = currentIndex;
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 100ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 100
postpone.start()
root.sendToScript("category*"+currentIndex)
}
}
HifiControls.Button {
action: saveAction
Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
}
HifiControls.Button {
action: loadAction
Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
}
}
HifiControls.Label {
id: description
text: "..."
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
RowLayout {
@ -104,19 +147,18 @@ Rectangle {
id: saveAction
text: "Save"
onTriggered: {
root.config.save()
fileDialog.title = "Save configuration..."
fileDialog.selectExisting = false
fileDialog.open()
}
}
Action {
id: loadAction
text: "Load"
onTriggered: {
root.config.load()
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 500ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 500
postpone.start()
fileDialog.title = "Load configuration..."
fileDialog.selectExisting = true
fileDialog.open()
}
}
@ -128,13 +170,8 @@ Rectangle {
ColumnLayout {
spacing: 3
width: root_col.width
property bool isTimeBased
HifiControls.CheckBox {
text: "Invert"
boxSize: 20
checked: root.config["isInverted"]
onCheckedChanged: { root.config["isInverted"] = checked }
}
RowLayout {
Layout.fillWidth: true
@ -196,16 +233,32 @@ Rectangle {
}
}
ConfigSlider {
RowLayout {
spacing: 20
height: 36
label: "Edge Width"
integral: false
config: root.config
property: "edgeWidth"
max: 1.0
min: 0.0
HifiControls.CheckBox {
text: "Invert gradient"
anchors.verticalCenter: parent.verticalCenter
boxSize: 20
checked: root.config["isInverted"]
onCheckedChanged: { root.config["isInverted"] = checked }
}
ConfigSlider {
anchors.left: undefined
anchors.verticalCenter: parent.verticalCenter
height: 36
width: 300
label: "Edge Width"
integral: false
config: root.config
property: "edgeWidth"
max: 1.0
min: 0.0
}
}
RowLayout {
Layout.fillWidth: true
@ -278,6 +331,8 @@ Rectangle {
Layout.fillWidth: true
ConfigSlider {
enabled: isTimeBased
opacity: isTimeBased ? 1.0 : 0.0
anchors.left: undefined
anchors.right: undefined
Layout.fillWidth: true
@ -290,6 +345,8 @@ Rectangle {
min: 0.1
}
HifiControls.ComboBox {
enabled: isTimeBased
opacity: isTimeBased ? 1.0 : 0.0
Layout.fillWidth: true
model: ["Linear", "Ease In", "Ease Out", "Ease In / Out"]
currentIndex: root.config["timing"]
@ -364,20 +421,6 @@ Rectangle {
id: paramWidgetLoader
sourceComponent: paramWidgets
}
Row {
anchors.left: parent.left
anchors.right: parent.right
Button {
action: saveAction
}
Button {
action: loadAction
}
}
}
}

View file

@ -89,14 +89,8 @@
bubbleOverlayTimestamp = nowTimestamp;
Script.update.connect(update);
updateConnected = true;
// Flash button
if (!bubbleFlashTimer) {
bubbleFlashTimer = Script.setInterval(function () {
writeButtonProperties(bubbleButtonFlashState);
bubbleButtonFlashState = !bubbleButtonFlashState;
}, 500);
}
writeButtonProperties(bubbleButtonFlashState);
bubbleButtonFlashState = !bubbleButtonFlashState;
}
// Called from the C++ scripting interface to show the bubble overlay

View file

@ -14,7 +14,7 @@
PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents
Picks, makeLaserLockInfo Xform, makeLaserParams, AddressManager, getEntityParents, Selection
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -295,6 +295,7 @@ Script.include("/~/system/libraries/Xform.js");
this.actionID = null;
this.grabbedThingID = null;
this.targetObject = null;
this.potentialEntityWithContextOverlay = false;
};
this.updateRecommendedArea = function() {
@ -467,15 +468,17 @@ Script.include("/~/system/libraries/Xform.js");
Script.clearTimeout(this.contextOverlayTimer);
}
this.contextOverlayTimer = false;
if (entityID !== this.entityWithContextOverlay) {
if (entityID === this.entityWithContextOverlay) {
this.destroyContextOverlay();
} else {
Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID);
}
var targetEntity = this.targetObject.getTargetEntity();
entityID = targetEntity.id;
targetProps = targetEntity.props;
if (entityIsGrabbable(targetProps)) {
if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) {
if (!entityIsDistanceGrabbable(targetProps)) {
this.targetObject.makeDynamic();
}

View file

@ -1618,8 +1618,18 @@ SelectionDisplay = (function() {
grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly),
cornerPosition);
for (var i = 0; i < SelectionManager.selections.length; i++) {
var properties = SelectionManager.savedProperties[SelectionManager.selections[i]];
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toMove = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var properties = SelectionManager.savedProperties[toMove[i]];
if (!properties) {
continue;
}
@ -1628,7 +1638,7 @@ SelectionDisplay = (function() {
y: 0,
z: vector.z
});
Entities.editEntity(SelectionManager.selections[i], {
Entities.editEntity(toMove[i], {
position: newPosition
});
@ -1653,11 +1663,11 @@ SelectionDisplay = (function() {
mode: mode,
onBegin: function(event, pickRay, pickResult) {
if (direction === TRANSLATE_DIRECTION.X) {
pickNormal = { x:0, y:0, z:1 };
pickNormal = { x:0, y:1, z:1 };
} else if (direction === TRANSLATE_DIRECTION.Y) {
pickNormal = { x:1, y:0, z:0 };
pickNormal = { x:1, y:0, z:1 };
} else if (direction === TRANSLATE_DIRECTION.Z) {
pickNormal = { x:0, y:1, z:0 };
pickNormal = { x:1, y:1, z:0 };
}
var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation;
@ -1701,7 +1711,6 @@ SelectionDisplay = (function() {
onMove: function(event) {
pickRay = generalComputePickRay(event.x, event.y);
// translate mode left/right based on view toward entity
var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal);
var vector = Vec3.subtract(newIntersection, lastPick);
@ -1719,7 +1728,7 @@ SelectionDisplay = (function() {
var dotVector = Vec3.dot(vector, projectionVector);
vector = Vec3.multiply(dotVector, projectionVector);
vector = grid.snapToGrid(vector);
var wantDebug = false;
if (wantDebug) {
print("translateUpDown... ");
@ -1727,9 +1736,19 @@ SelectionDisplay = (function() {
Vec3.print(" newIntersection:", newIntersection);
Vec3.print(" vector:", vector);
}
for (var i = 0; i < SelectionManager.selections.length; i++) {
var id = SelectionManager.selections[i];
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toMove = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toMove.length; i++) {
var id = toMove[i];
var properties = SelectionManager.savedProperties[id];
var newPosition = Vec3.sum(properties.position, vector);
Entities.editEntity(id, { position: newPosition });
@ -2017,10 +2036,10 @@ SelectionDisplay = (function() {
vector = grid.snapToSpacing(vector);
var changeInDimensions = Vec3.multiply(NEGATE_VECTOR, vec3Mult(localSigns, vector));
if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
if (directionEnum === STRETCH_DIRECTION.ALL) {
var toCameraDistance = getDistanceToCamera(position);
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple);
}
var newDimensions;
@ -2166,8 +2185,19 @@ SelectionDisplay = (function() {
// the selections center point. Otherwise, the rotation will be around the entities
// registration point which does not need repositioning.
var reposition = (SelectionManager.selections.length > 1);
for (var i = 0; i < SelectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
// editing a parent will cause all the children to automatically follow along, so don't
// edit any entity who has an ancestor in SelectionManager.selections
var toRotate = SelectionManager.selections.filter(function (selection) {
if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) {
return false; // a parent is also being moved, so don't issue an edit for this entity
} else {
return true;
}
});
for (var i = 0; i < toRotate.length; i++) {
var entityID = toRotate[i];
var initialProperties = SelectionManager.savedProperties[entityID];
var newProperties = {

View file

@ -1,6 +1,6 @@
"use strict";
/*jslint vars:true, plusplus:true, forin:true*/
/*global Script, Settings, Window, Controller, Overlays, SoundArray, LODManager, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/
/*global Script, Settings, Window, Controller, Overlays, SoundArray, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/
//
// notifications.js
// Version 0.801
@ -84,21 +84,18 @@
var NOTIFICATION_MENU_ITEM_POST = " Notifications";
var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds";
var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_";
var lodTextID = false;
var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications"
var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications";
var NotificationType = {
UNKNOWN: 0,
SNAPSHOT: 1,
LOD_WARNING: 2,
CONNECTION_REFUSED: 3,
EDIT_ERROR: 4,
TABLET: 5,
CONNECTION: 6,
WALLET: 7,
CONNECTION_REFUSED: 2,
EDIT_ERROR: 3,
TABLET: 4,
CONNECTION: 5,
WALLET: 6,
properties: [
{ text: "Snapshot" },
{ text: "Level of Detail" },
{ text: "Connection Refused" },
{ text: "Edit error" },
{ text: "Tablet" },
@ -153,10 +150,6 @@
// This handles the final dismissal of a notification after fading
function dismiss(firstNoteOut, firstButOut, firstOut) {
if (firstNoteOut === lodTextID) {
lodTextID = false;
}
Overlays.deleteOverlay(firstNoteOut);
Overlays.deleteOverlay(firstButOut);
notifications.splice(firstOut, 1);
@ -418,9 +411,6 @@
function deleteNotification(index) {
var notificationTextID = notifications[index];
if (notificationTextID === lodTextID) {
lodTextID = false;
}
Overlays.deleteOverlay(notificationTextID);
Overlays.deleteOverlay(buttons[index]);
notifications.splice(index, 1);
@ -674,20 +664,6 @@
}
}
LODManager.LODDecreased.connect(function () {
var warningText = "\n" +
"Due to the complexity of the content, the \n" +
"level of detail has been decreased. " +
"You can now see: \n" +
LODManager.getLODFeedbackText();
if (lodTextID === false) {
lodTextID = createNotification(warningText, NotificationType.LOD_WARNING);
} else {
Overlays.editOverlay(lodTextID, { text: warningText });
}
});
Controller.keyPressEvent.connect(keyPressEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);

View file

@ -24,15 +24,11 @@ extern AutoTester* autoTester;
#include <math.h>
Test::Test() {
QString regex(EXPECTED_IMAGE_PREFIX + QString("\\\\d").repeated(NUM_DIGITS) + ".png");
expectedImageFilenameFormat = QRegularExpression(regex);
mismatchWindow.setModal(true);
}
bool Test::createTestResultsFolderPath(QString directory) {
QDateTime now = QDateTime::currentDateTime();
QDateTime now = QDateTime::currentDateTime();
testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT);
QDir testResultsFolder(testResultsFolderPath);
@ -76,7 +72,7 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
QImage expectedImage(expectedImagesFullFilenames[i]);
if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) {
messageBox.critical(0, "Internal error #1", "Images are not the same size");
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
exit(-1);
}
@ -84,7 +80,7 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
try {
similarityIndex = imageComparer.compareImages(resultImage, expectedImage);
} catch (...) {
messageBox.critical(0, "Internal error #2", "Image not in expected format");
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Image not in expected format");
exit(-1);
}
@ -131,20 +127,20 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) {
if (!QDir().exists(testResultsFolderPath)) {
messageBox.critical(0, "Internal error #3", "Folder " + testResultsFolderPath + " not found");
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + testResultsFolderPath + " not found");
exit(-1);
}
QString failureFolderPath { testResultsFolderPath + "/" + "Failure_" + QString::number(index) };
if (!QDir().mkdir(failureFolderPath)) {
messageBox.critical(0, "Internal error #4", "Failed to create folder " + failureFolderPath);
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath);
exit(-1);
}
++index;
QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME);
if (!descriptionFile.open(QIODevice::ReadWrite)) {
messageBox.critical(0, "Internal error #5", "Failed to create file " + TEST_RESULTS_FILENAME);
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + TEST_RESULTS_FILENAME);
exit(-1);
}
@ -164,14 +160,14 @@ void Test::appendTestResultsToFile(QString testResultsFolderPath, TestFailure te
sourceFile = testFailure._pathname + testFailure._expectedImageFilename;
destinationFile = failureFolderPath + "/" + "Expected Image.jpg";
if (!QFile::copy(sourceFile, destinationFile)) {
messageBox.critical(0, "Internal error #6", "Failed to copy " + sourceFile + " to " + destinationFile);
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile);
exit(-1);
}
sourceFile = testFailure._pathname + testFailure._actualImageFilename;
destinationFile = failureFolderPath + "/" + "Actual Image.jpg";
if (!QFile::copy(sourceFile, destinationFile)) {
messageBox.critical(0, "Internal error #7", "Failed to copy " + sourceFile + " to " + destinationFile);
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile);
exit(-1);
}
@ -209,11 +205,6 @@ void Test::startTestsEvaluation() {
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory);
QStringList expectedImagesURLs;
const QString URLPrefix("https://raw.githubusercontent.com");
const QString githubUser("NissimHadar");
const QString testsRepo("hifi_tests");
const QString branch("addRecursionToAutotester");
resultImagesFullFilenames.clear();
expectedImagesFilenames.clear();
expectedImagesFullFilenames.clear();
@ -226,16 +217,16 @@ void Test::startTestsEvaluation() {
QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename);
// Images are stored on GitHub as ExpectedImage_ddddd.png
// Extract the digits at the end of the filename (exluding the file extension)
// Extract the digits at the end of the filename (excluding the file extension)
QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS);
QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png";
QString imageURLString(URLPrefix + "/" + githubUser + "/" + testsRepo + "/" + branch + "/" +
expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename);
QString imageURLString("https://github.com/" + githubUser + "/hifi_tests/blob/" + gitHubBranch + "/" +
expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename + "?raw=true");
expectedImagesURLs << imageURLString;
// The image retrieved from Github needs a unique name
// The image retrieved from GitHub needs a unique name
QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI.");
expectedImagesFilenames << expectedImageFilename;
@ -273,25 +264,31 @@ bool Test::isAValidDirectory(QString pathname) {
return true;
}
void Test::importTest(QTextStream& textStream, const QString& testPathname) {
// `testPathname` includes the full path to the test. We need the portion below (and including) `tests`
QStringList filenameParts = testPathname.split('/');
QString Test::extractPathFromTestsDown(QString fullPath) {
// `fullPath` includes the full path to the test. We need the portion below (and including) `tests`
QStringList pathParts = fullPath.split('/');
int i{ 0 };
while (i < filenameParts.length() && filenameParts[i] != "tests") {
while (i < pathParts.length() && pathParts[i] != "tests") {
++i;
}
if (i == filenameParts.length()) {
messageBox.critical(0, "Internal error #10", "Bad testPathname");
if (i == pathParts.length()) {
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad testPathname");
exit(-1);
}
QString filename;
for (int j = i; j < filenameParts.length(); ++j) {
filename += "/" + filenameParts[j];
QString partialPath;
for (int j = i; j < pathParts.length(); ++j) {
partialPath += "/" + pathParts[j];
}
textStream << "Script.include(\"" << "https://raw.githubusercontent.com/" << user << "/hifi_tests/" << branch << filename + "\");" << endl;
return partialPath;
}
void Test::importTest(QTextStream& textStream, const QString& testPathname) {
QString partialPath = extractPathFromTestsDown(testPathname);
textStream << "Script.include(\"" << "https://github.com/" << githubUser
<< "/hifi_tests/blob/" << gitHubBranch << partialPath + "?raw=true\");" << endl;
}
// Creates a single script in a user-selected folder.
@ -353,7 +350,7 @@ void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode
QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename);
if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) {
messageBox.critical(0,
"Internal Error #8",
"Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\""
);
@ -363,7 +360,9 @@ void Test::createRecursiveScript(QString topLevelDirectory, bool interactiveMode
QTextStream textStream(&allTestsFilename);
textStream << "// This is an automatically generated file, created by auto-tester" << endl << endl;
textStream << "var autoTester = Script.require(\"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/autoTester.js\");" << endl;
textStream << "var autoTester = Script.require(\"https://github.com/" + githubUser + "/hifi_tests/blob/"
+ gitHubBranch + "/tests/utils/autoTester.js?raw=true\");" << endl;
textStream << "autoTester.enableRecursive();" << endl << endl;
QVector<QString> testPathnames;
@ -454,6 +453,203 @@ void Test::createTest() {
messageBox.information(0, "Success", "Test images have been created");
}
ExtractedText Test::getTestScriptLines(QString testFileName) {
ExtractedText relevantTextFromTest;
QFile inputFile(testFileName);
inputFile.open(QIODevice::ReadOnly);
if (!inputFile.isOpen()) {
messageBox.critical(0,
"Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Failed to open \"" + testFileName
);
}
QTextStream stream(&inputFile);
QString line = stream.readLine();
// Name of test is the string in the following line:
// autoTester.perform("Apply Material Entities to Avatars", Script.resolvePath("."), function(testType) {...
const QString ws("\\h*"); //white-space character
const QString functionPerformName(ws + "autoTester" + ws + "\\." + ws + "perform");
const QString quotedString("\\\".+\\\"");
const QString ownPath("Script" + ws + "\\." + ws + "resolvePath" + ws + "\\(" + ws + "\\\"\\.\\\"" + ws + "\\)");
const QString functionParameter("function" + ws + "\\(testType" + ws + "\\)");
QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*");
QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle);
// Assert platform checks that test is running on the correct OS
const QString functionAssertPlatform(ws + "autoTester" + ws + "\\." + ws + "assertPlatform");
const QString regexAssertPlatform(ws + functionAssertPlatform + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertPlatform = QRegularExpression(regexAssertPlatform);
// Assert display checks that test is running on the correct display
const QString functionAssertDisplay(ws + "autoTester" + ws + "\\." + ws + "assertDisplay");
const QString regexAssertDisplay(ws + functionAssertDisplay + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertDisplay = QRegularExpression(regexAssertDisplay);
// Assert CPU checks that test is running on the correct type of CPU
const QString functionAssertCPU(ws + "autoTester" + ws + "\\." + ws + "assertCPU");
const QString regexAssertCPU(ws + functionAssertCPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertCPU = QRegularExpression(regexAssertCPU);
// Assert GPU checks that test is running on the correct type of GPU
const QString functionAssertGPU(ws + "autoTester" + ws + "\\." + ws + "assertGPU");
const QString regexAssertGPU(ws + functionAssertGPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertGPU = QRegularExpression(regexAssertGPU);
// Each step is either of the following forms:
// autoTester.addStepSnapshot("Take snapshot"...
// autoTester.addStep("Clean up after test"...
const QString functionAddStepSnapshotName(ws + "autoTester" + ws + "\\." + ws + "addStepSnapshot");
const QString regexStepSnapshot(ws + functionAddStepSnapshotName + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot);
const QString functionAddStepName(ws + "autoTester" + ws + "\\." + ws + "addStep");
const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ws + "\\)" + ".*");
const QRegularExpression lineStep = QRegularExpression(regexStep);
while (!line.isNull()) {
line = stream.readLine();
if (lineContainingTitle.match(line).hasMatch()) {
QStringList tokens = line.split('"');
relevantTextFromTest.title = tokens[1];
} else if (lineAssertPlatform.match(line).hasMatch()) {
QStringList platforms = line.split('"');
relevantTextFromTest.platform = platforms[1];
} else if (lineAssertDisplay.match(line).hasMatch()) {
QStringList displays = line.split('"');
relevantTextFromTest.display = displays[1];
} else if (lineAssertCPU.match(line).hasMatch()) {
QStringList cpus = line.split('"');
relevantTextFromTest.cpu = cpus[1];
} else if (lineAssertGPU.match(line).hasMatch()) {
QStringList gpus = line.split('"');
relevantTextFromTest.gpu = gpus[1];
} else if (lineStepSnapshot.match(line).hasMatch()) {
QStringList tokens = line.split('"');
QString nameOfStep = tokens[1];
Step *step = new Step();
step->text = nameOfStep;
step->takeSnapshot = true;
relevantTextFromTest.stepList.emplace_back(step);
} else if (lineStep.match(line).hasMatch()) {
QStringList tokens = line.split('"');
QString nameOfStep = tokens[1];
Step *step = new Step();
step->text = nameOfStep;
step->takeSnapshot = false;
relevantTextFromTest.stepList.emplace_back(step);
}
}
inputFile.close();
return relevantTextFromTest;
}
// Create an MD file for a user-selected test.
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
void Test::createMDFile() {
// Folder selection
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
if (testDirectory == "") {
return;
}
// Verify folder contains test.js file
QString testFileName(testDirectory + "/" + TEST_FILENAME);
QFileInfo testFileInfo(testFileName);
if (!testFileInfo.exists()) {
messageBox.critical(0, "Error", "Could not find file: " + TEST_FILENAME);
return;
}
ExtractedText testScriptLines = getTestScriptLines(testFileName);
QString mdFilename(testDirectory + "/" + "test.md");
QFile mdFile(mdFilename);
if (!mdFile.open(QIODevice::ReadWrite)) {
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
exit(-1);
}
QTextStream stream(&mdFile);
//Test title
QString testName = testScriptLines.title;
stream << "# " << testName << "\n";
// Find the relevant part of the path to the test (i.e. from "tests" down
QString partialPath = extractPathFromTestsDown(testDirectory);
stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n";
stream << "## Preconditions" << "\n";
stream << "- In an empty region of a domain with editing rights." << "\n\n";
// Platform
QStringList platforms = testScriptLines.platform.split(" ");;
stream << "## Platforms\n";
stream << "Run the test on each of the following platforms\n";
for (int i = 0; i < platforms.size(); ++i) {
// Note that the platforms parameter may include extra spaces, these appear as empty strings in the list
if (platforms[i] != QString()) {
stream << " - " << platforms[i] << "\n";
}
}
// Display
QStringList displays = testScriptLines.display.split(" ");
stream << "## Displays\n";
stream << "Run the test on each of the following displays\n";
for (int i = 0; i < displays.size(); ++i) {
// Note that the displays parameter may include extra spaces, these appear as empty strings in the list
if (displays[i] != QString()) {
stream << " - " << displays[i] << "\n";
}
}
// CPU
QStringList cpus = testScriptLines.cpu.split(" ");
stream << "## Processors\n";
stream << "Run the test on each of the following processors\n";
for (int i = 0; i < cpus.size(); ++i) {
// Note that the cpus parameter may include extra spaces, these appear as empty strings in the list
if (cpus[i] != QString()) {
stream << " - " << cpus[i] << "\n";
}
}
// GPU
QStringList gpus = testScriptLines.gpu.split(" ");
stream << "## Graphics Cards\n";
stream << "Run the test on graphics cards from each of the following vendors\n";
for (int i = 0; i < gpus.size(); ++i) {
// Note that the gpus parameter may include extra spaces, these appear as empty strings in the list
if (gpus[i] != QString()) {
stream << " - " << gpus[i] << "\n";
}
}
stream << "## Steps\n";
stream << "Press space bar to advance step by step\n\n";
int snapShotIndex { 0 };
for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) {
stream << "### Step " << QString::number(i) << "\n";
stream << "- " << testScriptLines.stepList[i]->text << "\n";
if (testScriptLines.stepList[i]->takeSnapshot) {
stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n";
++snapShotIndex;
}
}
mdFile.close();
}
void Test::copyJPGtoPNG(QString sourceJPGFullFilename, QString destinationPNGFullFilename) {
QFile::remove(destinationPNGFullFilename);
@ -526,7 +722,7 @@ QString Test::getExpectedImagePartialSourceDirectory(QString filename) {
}
if (i < 0) {
messageBox.critical(0, "Internal error #9", "Bad filename");
messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Bad filename");
exit(-1);
}

View file

@ -19,6 +19,24 @@
#include "ImageComparer.h"
#include "ui/MismatchWindow.h"
class Step {
public:
QString text;
bool takeSnapshot;
};
using StepList = std::vector<Step*>;
class ExtractedText {
public:
QString title;
QString platform;
QString display;
QString cpu;
QString gpu;
StepList stepList;
};
class Test {
public:
Test();
@ -31,7 +49,7 @@ public:
void createRecursiveScript(QString topLevelDirectory, bool interactiveMode);
void createTest();
void deleteOldSnapshots();
void createMDFile();
bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar);
@ -47,7 +65,7 @@ public:
void zipAndDeleteTestResultsFolder();
bool isAValidDirectory(QString pathname);
QString extractPathFromTestsDown(QString fullPath);
QString getExpectedImageDestinationDirectory(QString filename);
QString getExpectedImagePartialSourceDirectory(QString filename);
@ -62,8 +80,6 @@ private:
QDir imageDirectory;
QRegularExpression expectedImageFilenameFormat;
MismatchWindow mismatchWindow;
ImageComparer imageComparer;
@ -81,9 +97,11 @@ private:
QStringList resultImagesFullFilenames;
// Used for accessing GitHub
const QString user { "NissimHadar" };
const QString branch { "addRecursionToAutotester" };
const QString githubUser{ "highfidelity" };
const QString gitHubBranch { "master" };
const QString DATETIME_FORMAT { "yyyy-MM-dd_hh-mm-ss" };
ExtractedText getTestScriptLines(QString testFileName);
};
#endif // hifi_test_h

View file

@ -33,7 +33,11 @@ void AutoTester::on_createRecursiveScriptsRecursivelyButton_clicked() {
}
void AutoTester::on_createTestButton_clicked() {
test->createTest();
test->createTest();
}
void AutoTester::on_createMDFileButton_clicked() {
test->createMDFile();
}
void AutoTester::on_closeButton_clicked() {

View file

@ -29,8 +29,9 @@ private slots:
void on_evaluateTestsButton_clicked();
void on_createRecursiveScriptButton_clicked();
void on_createRecursiveScriptsRecursivelyButton_clicked();
void on_createTestButton_clicked();
void on_closeButton_clicked();
void on_createTestButton_clicked();
void on_createMDFileButton_clicked();
void on_closeButton_clicked();
void saveImage(int index);

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>607</width>
<height>395</height>
<height>514</height>
</rect>
</property>
<property name="windowTitle">
@ -18,7 +18,7 @@
<property name="geometry">
<rect>
<x>20</x>
<y>300</y>
<y>420</y>
<width>220</width>
<height>40</height>
</rect>
@ -44,7 +44,7 @@
<property name="geometry">
<rect>
<x>20</x>
<y>135</y>
<y>255</y>
<width>220</width>
<height>40</height>
</rect>
@ -70,7 +70,7 @@
<property name="geometry">
<rect>
<x>23</x>
<y>100</y>
<y>220</y>
<width>131</width>
<height>20</height>
</rect>
@ -86,7 +86,7 @@
<property name="geometry">
<rect>
<x>20</x>
<y>190</y>
<y>310</y>
<width>255</width>
<height>23</height>
</rect>
@ -108,6 +108,19 @@
<string>Create Recursive Scripts Recursively</string>
</property>
</widget>
<widget class="QPushButton" name="createMDFileButton">
<property name="geometry">
<rect>
<x>20</x>
<y>90</y>
<width>220</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create MD file</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">

91
tools/bake-tools/bake.py Normal file
View file

@ -0,0 +1,91 @@
import os, json, sys, shutil, subprocess, shlex, time
EXE = os.environ['HIFI_OVEN']
def listFiles(directory, extension):
items = os.listdir(directory)
fileList = []
for f in items:
if f.endswith('.' + extension):
fileList.append(f)
return fileList
def camelCaseString(string):
string = string.replace('-', ' ')
return ''.join(x for x in string.title() if not x.isspace())
def groupFiles(originalDirectory, newDirectory, files):
for file in files:
newPath = os.sep.join([newDirectory, file])
originalPath = os.sep.join([originalDirectory, file])
shutil.move(originalPath, newPath)
def groupKTXFiles(directory, filePath):
baseFile = os.path.basename(filePath)
filename = os.path.splitext(baseFile)[0]
camelCaseFileName = camelCaseString(filename)
path = os.sep.join([directory, camelCaseFileName])
files = listFiles(directory, 'ktx')
if len(files) > 0:
createDirectory(path)
groupFiles(directory, path, files)
newFilePath = os.sep.join([path, baseFile+'.baked.fbx'])
originalFilePath = os.sep.join([directory, baseFile+'.baked.fbx'])
originalFilePath.strip()
shutil.move(originalFilePath, newFilePath)
def bakeFile(filePath, outputDirectory):
createDirectory(outputDirectory)
cmd = EXE + ' -i ' + filePath + ' -o ' + outputDirectory + ' -t fbx'
args = shlex.split(cmd)
process = subprocess.Popen(cmd, stdout=False, stderr=False)
process.wait()
bakedFile = os.path.splitext(filePath)[0]
groupKTXFiles(outputDirectory, bakedFile)
def bakeFilesInDirectory(directory, outputDirectory):
for root, subFolders, filenames in os.walk(directory):
for filename in filenames:
if filename.endswith('.fbx'):
filePath = os.sep.join([root, filename])
absFilePath = os.path.abspath(filePath)
outputFolder = os.path.join(outputDirectory, os.path.relpath(root))
print "Baking file: " + filename
bakeFile(absFilePath, outputFolder)
else:
filePath = os.sep.join([root, filename])
absFilePath = os.path.abspath(filePath)
outputFolder = os.path.join(outputDirectory, os.path.relpath(root))
newFilePath = os.sep.join([outputFolder, filename])
createDirectory(outputFolder)
print "moving file: " + filename + " to: " + outputFolder
shutil.copy(absFilePath, newFilePath)
def createDirectory(directory):
if not os.path.exists(directory):
os.makedirs(directory)
def checkIfExeExists():
if not os.path.isfile(EXE) and os.access(EXE, os.X_OK):
print 'HIFI_OVEN evironment variable is not set'
sys.exit()
def handleOptions():
option = sys.argv[1]
if option == '--help' or option == '-h':
print 'Usage: bake.py INPUT_DIRECTORY[directory to bake] OUTPUT_DIRECTORY[directory to place backed files]'
print 'Note: Output directory will be created if directory does not exist'
sys.exit()
def main():
argsLength = len(sys.argv)
if argsLength == 3:
checkIfExeExists()
rootDirectory = sys.argv[1]
outputDirectory = os.path.abspath(sys.argv[2])
createDirectory(outputDirectory)
bakeFilesInDirectory(rootDirectory, outputDirectory)
elif argsLength == 2:
handleOptions()
main()

View file

@ -0,0 +1,82 @@
import json, os, sys, gzip
prefix = 'file:///~/'
MAP = {}
def createAssetMapping(assetDirectory):
baseDirectory = os.path.basename(os.path.normpath(assetDirectory))
for root, subfolder, filenames in os.walk(assetDirectory):
for filename in filenames:
if not filename.endswith('.ktx'):
substring = os.path.commonprefix([assetDirectory, root])
newPath = root.replace(substring, '');
filePath = os.sep.join([newPath, filename])
if filePath[0] == '\\':
filePath = filePath[1:]
finalPath = prefix + baseDirectory + '/' + filePath
finalPath = finalPath.replace('\\', '/')
file = os.path.splitext(filename)[0]
file = os.path.splitext(file)[0]
MAP[file] = finalPath
def hasURL(prop):
if "URL" in prop:
return True
return False
def handleURL(url):
newUrl = url
if "atp:" in url:
baseFilename = os.path.basename(url)
filename = os.path.splitext(baseFilename)[0]
newUrl = MAP[filename]
print newUrl
return newUrl
def handleOptions():
option = sys.argv[1]
if option == '--help' or option == '-h':
print 'Usage: convertToRelativePaths.py INPUT[json file you want to update the urls] INPUT[directory that the baked files are located in]'
sys.exit()
def main():
argsLength = len(sys.argv)
if argsLength == 3:
jsonFile = sys.argv[1]
gzipFile = jsonFile + '.gz'
assetDirectory = sys.argv[2]
createAssetMapping(assetDirectory)
f = open(jsonFile)
data = json.load(f)
f.close()
for entity in data['Entities']:
for prop in entity:
value = entity[prop]
if hasURL(prop):
value = handleURL(value)
if prop == "script":
value = handleURL(value)
if prop == "textures":
try:
tmp = json.loads(value)
for index in tmp:
tmp[index] = handleURL(tmp[index])
value = json.dumps(tmp)
except:
value = handleURL(value)
if prop == "serverScripts":
value = handleURL(value)
entity[prop] = value
jsonString = json.dumps(data)
jsonBytes= jsonString.encode('utf-8')
with gzip.GzipFile(gzipFile, 'w') as fout: # 4. gzip
fout.write(jsonBytes)
elif argsLength == 2:
handleOptions()
main()