mirror of
https://github.com/lubosz/overte.git
synced 2025-04-26 17:55:29 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into 21691-improveNitpickWebPage
This commit is contained in:
commit
3f37024c55
125 changed files with 5670 additions and 2552 deletions
.gitignore
.vs
interface
resources/qml
LoginDialog
controlsUit
hifi
src
libraries
animation/src
avatars-renderer/src/avatars-renderer
baking
CMakeLists.txt
src
entities-renderer/src
entities/src
fbx/src
FBX.hFBXSerializer.cppFBXSerializer.hFBXSerializer_Material.cppFBXSerializer_Mesh.cppFBXSerializer_Node.cppFSTReader.hGLTFSerializer.cppGLTFSerializer.hOBJSerializer.cppOBJSerializer.h
graphics-scripting/src/graphics-scripting
graphics/src/graphics
hfm/src/hfm
image/src/image
model-baker
CMakeLists.txt
src/model-baker
Baker.cppBaker.hBakerTypes.hBuildDracoMeshTask.cppBuildDracoMeshTask.hBuildGraphicsMeshTask.cppCalculateBlendshapeTangentsTask.cppCalculateMeshTangentsTask.cppModelMath.hParseFlowDataTask.cppParseFlowDataTask.hParseMaterialMappingTask.cppParseMaterialMappingTask.hPrepareJointsTask.cppPrepareJointsTask.h
model-networking/src/model-networking
networking/src
render-utils/src
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -26,6 +26,9 @@ android/**/src/main/assets
|
|||
android/**/gradle*
|
||||
*.class
|
||||
|
||||
# Visual Studio
|
||||
/.vs
|
||||
|
||||
# VSCode
|
||||
# List taken from Github Global Ignores master@435c4d92
|
||||
# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore
|
||||
|
|
BIN
.vs/slnx.sqlite
BIN
.vs/slnx.sqlite
Binary file not shown.
|
@ -510,7 +510,7 @@ Item {
|
|||
console.log("Create Failed: " + error);
|
||||
if (completeProfileBody.withSteam || completeProfileBody.withOculus) {
|
||||
if (completeProfileBody.loginDialogPoppedUp) {
|
||||
action = completeProfileBody.withSteam ? "Steam" : "Oculus";
|
||||
var action = completeProfileBody.withSteam ? "Steam" : "Oculus";
|
||||
var data = {
|
||||
"action": "user failed to create a profile with " + action + " from the complete profile screen"
|
||||
}
|
||||
|
|
|
@ -143,6 +143,16 @@ Original.Button {
|
|||
horizontalAlignment: Text.AlignHCenter
|
||||
text: control.text
|
||||
Component.onCompleted: {
|
||||
setTextPosition();
|
||||
}
|
||||
onTextChanged: {
|
||||
setTextPosition();
|
||||
}
|
||||
function setTextPosition() {
|
||||
// force TextMetrics to re-evaluate the text field and glyph sizes
|
||||
// as for some reason it's not automatically being done.
|
||||
buttonGlyphTextMetrics.text = buttonGlyph.text;
|
||||
buttonTextMetrics.text = text;
|
||||
if (control.buttonGlyph !== "") {
|
||||
buttonText.x = buttonContentItem.width/2 - buttonTextMetrics.width/2 + (buttonGlyphTextMetrics.width + control.buttonGlyphRightMargin)/2;
|
||||
} else {
|
||||
|
|
|
@ -40,6 +40,7 @@ Item {
|
|||
property bool isConcurrency: action === 'concurrency';
|
||||
property bool isAnnouncement: action === 'announcement';
|
||||
property bool isStacked: !isConcurrency && drillDownToPlace;
|
||||
property bool has3DHTML: PlatformInfo.has3DHTML();
|
||||
|
||||
|
||||
property int textPadding: 10;
|
||||
|
@ -298,7 +299,7 @@ Item {
|
|||
|
||||
StateImage {
|
||||
id: actionIcon;
|
||||
visible: !isAnnouncement;
|
||||
visible: !isAnnouncement && has3DHTML;
|
||||
imageURL: "../../images/info-icon-2-state.svg";
|
||||
size: 30;
|
||||
buttonState: messageArea.containsMouse ? 1 : 0;
|
||||
|
@ -315,7 +316,7 @@ Item {
|
|||
}
|
||||
MouseArea {
|
||||
id: messageArea;
|
||||
visible: !isAnnouncement;
|
||||
visible: !isAnnouncement && has3DHTML;
|
||||
width: parent.width;
|
||||
height: messageHeight;
|
||||
anchors.top: lobby.bottom;
|
||||
|
|
|
@ -54,7 +54,7 @@ Column {
|
|||
'require_online=true',
|
||||
'protocol=' + encodeURIComponent(Window.protocolSignature())
|
||||
];
|
||||
endpoint: '/api/v1/user_stories?' + options.join('&');
|
||||
endpoint: '/api/v1/user_stories?' + options.join('&') + (PlatformInfo.isStandalone() ? '&standalone_optimized=true' : '')
|
||||
itemsPerPage: 4;
|
||||
processPage: function (data) {
|
||||
return data.user_stories.map(makeModelData);
|
||||
|
|
|
@ -46,6 +46,8 @@ Item {
|
|||
property string placeName: ""
|
||||
property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : "transparent"))
|
||||
property alias avImage: avatarImage
|
||||
property bool has3DHTML: PlatformInfo.has3DHTML();
|
||||
|
||||
Item {
|
||||
id: avatarImage
|
||||
visible: profileUrl !== "" && userName !== "";
|
||||
|
@ -94,10 +96,12 @@ Item {
|
|||
enabled: (selected && activeTab == "nearbyTab") || isMyCard;
|
||||
hoverEnabled: enabled
|
||||
onClicked: {
|
||||
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
|
||||
userInfoViewer.visible = true;
|
||||
if (has3DHTML) {
|
||||
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
|
||||
userInfoViewer.visible = true;
|
||||
}
|
||||
}
|
||||
onEntered: infoHoverImage.visible = true;
|
||||
onEntered: infoHoverImage.visible = has3DHTML;
|
||||
onExited: infoHoverImage.visible = false;
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +356,7 @@ Item {
|
|||
}
|
||||
StateImage {
|
||||
id: nameCardConnectionInfoImage
|
||||
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
|
||||
visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && has3DHTML
|
||||
imageURL: "../../images/info-icon-2-state.svg" // PLACEHOLDER!!!
|
||||
size: 32;
|
||||
buttonState: 0;
|
||||
|
@ -364,8 +368,10 @@ Item {
|
|||
enabled: selected
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
|
||||
userInfoViewer.visible = true;
|
||||
if (has3DHTML) {
|
||||
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
|
||||
userInfoViewer.visible = true;
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
nameCardConnectionInfoImage.buttonState = 1;
|
||||
|
@ -376,8 +382,7 @@ Item {
|
|||
}
|
||||
FiraSansRegular {
|
||||
id: nameCardConnectionInfoText
|
||||
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
|
||||
width: parent.width
|
||||
visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && PlatformInfo.has3DHTML()
|
||||
height: displayNameTextPixelSize
|
||||
size: displayNameTextPixelSize - 4
|
||||
anchors.left: nameCardConnectionInfoImage.right
|
||||
|
@ -391,9 +396,10 @@ Item {
|
|||
id: nameCardRemoveConnectionImage
|
||||
visible: selected && !isMyCard && pal.activeTab == "connectionsTab"
|
||||
text: hifi.glyphs.close
|
||||
size: 28;
|
||||
size: 24;
|
||||
x: 120
|
||||
anchors.verticalCenter: nameCardConnectionInfoImage.verticalCenter
|
||||
anchors.left: has3DHTML ? nameCardConnectionInfoText.right + 10 : avatarImage.right
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill:nameCardRemoveConnectionImage
|
||||
|
|
|
@ -71,6 +71,7 @@ Item {
|
|||
|
||||
onBalanceResult : {
|
||||
balanceText.text = result.data.balance;
|
||||
sendButton.enabled = true;
|
||||
}
|
||||
|
||||
onTransferAssetToNodeResult: {
|
||||
|
@ -1371,6 +1372,7 @@ Item {
|
|||
height: 40;
|
||||
width: 100;
|
||||
text: "SUBMIT";
|
||||
enabled: false;
|
||||
onClicked: {
|
||||
if (root.assetCertID === "" && parseInt(amountTextField.text) > parseInt(balanceText.text)) {
|
||||
amountTextField.focus = true;
|
||||
|
|
|
@ -87,22 +87,11 @@ Rectangle {
|
|||
console.log("Failed to get Marketplace Categories", result.data.message);
|
||||
} else {
|
||||
categoriesModel.clear();
|
||||
categoriesModel.append({
|
||||
id: -1,
|
||||
name: "Everything"
|
||||
});
|
||||
categoriesModel.append({
|
||||
id: -1,
|
||||
name: "Stand-alone Optimized"
|
||||
});
|
||||
categoriesModel.append({
|
||||
id: -1,
|
||||
name: "Stand-alone Compatible"
|
||||
});
|
||||
result.data.items.forEach(function(category) {
|
||||
result.data.categories.forEach(function(category) {
|
||||
categoriesModel.append({
|
||||
id: category.id,
|
||||
name: category.name
|
||||
name: category.name,
|
||||
count: category.count
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -359,9 +348,11 @@ Rectangle {
|
|||
}
|
||||
|
||||
onAccepted: {
|
||||
root.searchString = searchField.text;
|
||||
getMarketplaceItems();
|
||||
searchField.forceActiveFocus();
|
||||
if (root.searchString !== searchField.text) {
|
||||
root.searchString = searchField.text;
|
||||
getMarketplaceItems();
|
||||
searchField.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
|
@ -382,6 +373,7 @@ Rectangle {
|
|||
id: categoriesDropdown
|
||||
|
||||
anchors.fill: parent;
|
||||
anchors.topMargin: 2
|
||||
|
||||
visible: false
|
||||
z: 10
|
||||
|
@ -396,12 +388,12 @@ Rectangle {
|
|||
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left;
|
||||
bottom: parent.bottom;
|
||||
top: parent.top;
|
||||
topMargin: 100;
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
top: parent.top
|
||||
topMargin: 100
|
||||
}
|
||||
width: parent.width/3
|
||||
width: parent.width*2/3
|
||||
|
||||
color: hifi.colors.white
|
||||
|
||||
|
@ -420,6 +412,7 @@ Rectangle {
|
|||
|
||||
model: categoriesModel
|
||||
delegate: ItemDelegate {
|
||||
id: categoriesItemDelegate
|
||||
height: 34
|
||||
width: parent.width
|
||||
|
||||
|
@ -431,34 +424,71 @@ Rectangle {
|
|||
|
||||
color: hifi.colors.white
|
||||
visible: true
|
||||
border.color: hifi.colors.blueHighlight
|
||||
border.width: 0
|
||||
|
||||
RalewayRegular {
|
||||
RalewaySemiBold {
|
||||
id: categoriesItemText
|
||||
|
||||
anchors.leftMargin: 15
|
||||
anchors.fill:parent
|
||||
anchors.top:parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: categoryItemCount.right
|
||||
|
||||
elide: Text.ElideRight
|
||||
text: model.name
|
||||
color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray
|
||||
color: categoriesItemDelegate.ListView.isCurrentItem ? hifi.colors.blueHighlight : hifi.colors.baseGray
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: 14
|
||||
}
|
||||
Rectangle {
|
||||
id: categoryItemCount
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
topMargin: 7
|
||||
bottomMargin: 7
|
||||
leftMargin: 10
|
||||
rightMargin: 10
|
||||
left: parent.left
|
||||
}
|
||||
width: childrenRect.width
|
||||
color: hifi.colors.faintGray
|
||||
radius: height/2
|
||||
|
||||
RalewaySemiBold {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
width: 50
|
||||
|
||||
text: model.count
|
||||
color: hifi.colors.lightGrayText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: 10
|
||||
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: false
|
||||
|
||||
onEntered: {
|
||||
categoriesItem.color = ListView.isCurrentItem ? hifi.colors.white : hifi.colors.lightBlueHighlight;
|
||||
onPositionChanged: {
|
||||
// Must use onPositionChanged and not onEntered
|
||||
// due to a QML bug where a mouseenter event was
|
||||
// being fired on open of the categories list even
|
||||
// though the mouse was outside the borders
|
||||
categoriesItem.border.width = 2;
|
||||
}
|
||||
onExited: {
|
||||
categoriesItem.border.width = 0;
|
||||
}
|
||||
|
||||
onExited: {
|
||||
categoriesItem.color = ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.white;
|
||||
onCanceled: {
|
||||
categoriesItem.border.width = 0;
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
|
@ -476,9 +506,9 @@ Rectangle {
|
|||
parent: categoriesListView.parent
|
||||
|
||||
anchors {
|
||||
top: categoriesListView.top;
|
||||
bottom: categoriesListView.bottom;
|
||||
left: categoriesListView.right;
|
||||
top: categoriesListView.top
|
||||
bottom: categoriesListView.bottom
|
||||
left: categoriesListView.right
|
||||
}
|
||||
|
||||
contentItem.opacity: 1
|
||||
|
@ -559,6 +589,8 @@ Rectangle {
|
|||
standaloneOptimized: model.standalone_optimized
|
||||
|
||||
onShowItem: {
|
||||
// reset the edition back to -1 to clear the 'update item' status
|
||||
marketplaceItem.edition = -1;
|
||||
MarketplaceScriptingInterface.getMarketplaceItem(item_id);
|
||||
}
|
||||
|
||||
|
|
|
@ -298,7 +298,7 @@ Rectangle {
|
|||
property bool isFreeSpecial: isStocking || isUpdate
|
||||
enabled: isFreeSpecial || (availability === 'available')
|
||||
buttonGlyph: (enabled && !isUpdate && (price > 0)) ? hifi.glyphs.hfc : ""
|
||||
text: isUpdate ? "UPDATE FOR FREE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability))
|
||||
text: isUpdate ? "UPDATE" : (isStocking ? "FREE STOCK" : (enabled ? (price || "FREE") : availability))
|
||||
color: hifi.buttons.blue
|
||||
|
||||
buttonGlyphSize: 24
|
||||
|
|
|
@ -49,7 +49,7 @@ Item {
|
|||
property string wornEntityID;
|
||||
property string updatedItemId;
|
||||
property string upgradeTitle;
|
||||
property bool updateAvailable: root.updateItemId && root.updateItemId !== "";
|
||||
property bool updateAvailable: root.updateItemId !== "";
|
||||
property bool valid;
|
||||
property bool standaloneOptimized;
|
||||
property bool standaloneIncompatible;
|
||||
|
|
|
@ -523,9 +523,9 @@ Rectangle {
|
|||
item.cardBackVisible = false;
|
||||
item.isInstalled = root.installedApps.indexOf(item.id) > -1;
|
||||
item.wornEntityID = '';
|
||||
item.upgrade_id = item.upgrade_id ? item.upgrade_id : "";
|
||||
});
|
||||
sendToScript({ method: 'purchases_updateWearables' });
|
||||
|
||||
return data.assets;
|
||||
}
|
||||
}
|
||||
|
@ -545,7 +545,7 @@ Rectangle {
|
|||
delegate: PurchasedItem {
|
||||
itemName: title;
|
||||
itemId: id;
|
||||
updateItemId: model.upgrade_id ? model.upgrade_id : "";
|
||||
updateItemId: model.upgrade_id
|
||||
itemPreviewImageUrl: preview;
|
||||
itemHref: download_url;
|
||||
certificateId: certificate_id;
|
||||
|
|
|
@ -32,6 +32,7 @@ Rectangle {
|
|||
property string initialActiveViewAfterStatus5: "walletInventory";
|
||||
property bool keyboardRaised: false;
|
||||
property bool isPassword: false;
|
||||
property bool has3DHTML: PlatformInfo.has3DHTML();
|
||||
|
||||
anchors.fill: (typeof parent === undefined) ? undefined : parent;
|
||||
|
||||
|
@ -335,8 +336,10 @@ Rectangle {
|
|||
Connections {
|
||||
onSendSignalToWallet: {
|
||||
if (msg.method === 'transactionHistory_usernameLinkClicked') {
|
||||
userInfoViewer.url = msg.usernameLink;
|
||||
userInfoViewer.visible = true;
|
||||
if (has3DHTML) {
|
||||
userInfoViewer.url = msg.usernameLink;
|
||||
userInfoViewer.visible = true;
|
||||
}
|
||||
} else {
|
||||
sendToScript(msg);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ Item {
|
|||
HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
|
||||
property bool has3DHTML: PlatformInfo.has3DHTML();
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
|
@ -333,7 +335,9 @@ Item {
|
|||
|
||||
onLinkActivated: {
|
||||
if (link.indexOf("users/") !== -1) {
|
||||
sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link});
|
||||
if (has3DHTML) {
|
||||
sendSignalToWallet({method: 'transactionHistory_usernameLinkClicked', usernameLink: link});
|
||||
}
|
||||
} else {
|
||||
sendSignalToWallet({method: 'transactionHistory_linkClicked', itemId: model.marketplace_item});
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ StackView {
|
|||
property int cardWidth: 212;
|
||||
property int cardHeight: 152;
|
||||
property var tablet: null;
|
||||
property bool has3DHTML: PlatformInfo.has3DHTML();
|
||||
|
||||
RootHttpRequest { id: http; }
|
||||
signal sendToScript(var message);
|
||||
|
@ -75,8 +76,10 @@ StackView {
|
|||
}
|
||||
function goCard(targetString, standaloneOptimized) {
|
||||
if (0 !== targetString.indexOf('hifi://')) {
|
||||
var card = tabletWebView.createObject();
|
||||
card.url = addressBarDialog.metaverseServerUrl + targetString;
|
||||
if(has3DHTML) {
|
||||
var card = tabletWebView.createObject();
|
||||
card.url = addressBarDialog.metaverseServerUrl + targetString;
|
||||
}
|
||||
card.parentStackItem = root;
|
||||
root.push(card);
|
||||
return;
|
||||
|
|
|
@ -3043,6 +3043,9 @@ void Application::initializeUi() {
|
|||
QUrl{ "hifi/commerce/wallet/Wallet.qml" },
|
||||
QUrl{ "hifi/commerce/wallet/WalletHome.qml" },
|
||||
QUrl{ "hifi/tablet/TabletAddressDialog.qml" },
|
||||
QUrl{ "hifi/Card.qml" },
|
||||
QUrl{ "hifi/Pal.qml" },
|
||||
QUrl{ "hifi/NameCard.qml" },
|
||||
}, platformInfoCallback);
|
||||
|
||||
QmlContextCallback ttsCallback = [](QQmlContext* context) {
|
||||
|
@ -5772,6 +5775,7 @@ void Application::reloadResourceCaches() {
|
|||
|
||||
queryOctree(NodeType::EntityServer, PacketType::EntityQuery);
|
||||
|
||||
getMyAvatar()->prepareAvatarEntityDataForReload();
|
||||
// Clear the entities and their renderables
|
||||
getEntities()->clear();
|
||||
|
||||
|
@ -6947,9 +6951,6 @@ void Application::updateWindowTitle() const {
|
|||
}
|
||||
|
||||
void Application::clearDomainOctreeDetails(bool clearAll) {
|
||||
// before we delete all entities get MyAvatar's AvatarEntityData ready
|
||||
getMyAvatar()->prepareAvatarEntityDataForReload();
|
||||
|
||||
// if we're about to quit, we really don't need to do the rest of these things...
|
||||
if (_aboutToQuit) {
|
||||
return;
|
||||
|
|
|
@ -87,7 +87,7 @@ void MarketplaceItemUploader::doGetCategories() {
|
|||
if (error == QNetworkReply::NoError) {
|
||||
auto doc = QJsonDocument::fromJson(reply->readAll());
|
||||
auto extractCategoryID = [&doc]() -> std::pair<bool, int> {
|
||||
auto items = doc.object()["data"].toObject()["items"];
|
||||
auto items = doc.object()["data"].toObject()["categories"];
|
||||
if (!items.isArray()) {
|
||||
qWarning() << "Categories parse error: data.items is not an array";
|
||||
return { false, 0 };
|
||||
|
|
|
@ -3450,6 +3450,34 @@ float MyAvatar::getGravity() {
|
|||
return _characterController.getGravity();
|
||||
}
|
||||
|
||||
void MyAvatar::setSessionUUID(const QUuid& sessionUUID) {
|
||||
QUuid oldID = getSessionUUID();
|
||||
Avatar::setSessionUUID(sessionUUID);
|
||||
QUuid id = getSessionUUID();
|
||||
if (id != oldID) {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
QList<QUuid> avatarEntityIDs;
|
||||
_avatarEntitiesLock.withReadLock([&] {
|
||||
avatarEntityIDs = _packedAvatarEntityData.keys();
|
||||
});
|
||||
entityTree->withWriteLock([&] {
|
||||
for (const auto& entityID : avatarEntityIDs) {
|
||||
auto entity = entityTree->findEntityByID(entityID);
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
entity->setOwningAvatarID(id);
|
||||
if (entity->getParentID() == oldID) {
|
||||
entity->setParentID(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::increaseSize() {
|
||||
float minScale = getDomainMinScale();
|
||||
float maxScale = getDomainMaxScale();
|
||||
|
|
|
@ -1213,6 +1213,12 @@ public:
|
|||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setSessionUUID
|
||||
* @param {Uuid} sessionUUID
|
||||
*/
|
||||
virtual void setSessionUUID(const QUuid& sessionUUID) override;
|
||||
|
||||
/**jsdoc
|
||||
* Increase the avatar's scale by five percent, up to a minimum scale of <code>1000</code>.
|
||||
* @function MyAvatar.increaseSize
|
||||
|
|
|
@ -124,41 +124,45 @@ void AnimClip::copyFromNetworkAnim() {
|
|||
_anim.resize(animFrameCount);
|
||||
|
||||
// find the size scale factor for translation in the animation.
|
||||
const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarSkeleton->nameToJointIndex("Hips"));
|
||||
const int animHipsParentIndex = animSkeleton.getParentIndex(animSkeleton.nameToJointIndex("Hips"));
|
||||
const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarSkeleton->nameToJointIndex("Hips"));
|
||||
const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animSkeleton.nameToJointIndex("Hips"));
|
||||
|
||||
// the get the units and the heights for the animation and the avatar
|
||||
const float avatarUnitScale = extractScale(avatarSkeleton->getGeometryOffset()).y;
|
||||
const float animationUnitScale = extractScale(animModel.offset).y;
|
||||
const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y;
|
||||
const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y;
|
||||
|
||||
// get the parent scales for the avatar and the animation
|
||||
float avatarHipsParentScale = 1.0f;
|
||||
if (avatarHipsParentIndex >= 0) {
|
||||
const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex);
|
||||
avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y;
|
||||
}
|
||||
float animHipsParentScale = 1.0f;
|
||||
if (animHipsParentIndex >= 0) {
|
||||
const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex);
|
||||
animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y;
|
||||
}
|
||||
|
||||
const float EPSILON = 0.0001f;
|
||||
float boneLengthScale = 1.0f;
|
||||
// compute the ratios for the units, the heights in meters, and the parent scales
|
||||
if ((fabsf(animHeightInMeters) > EPSILON) && (animationUnitScale > EPSILON) && (animHipsParentScale > EPSILON)) {
|
||||
const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters;
|
||||
const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale);
|
||||
const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale);
|
||||
const int avatarHipsIndex = avatarSkeleton->nameToJointIndex("Hips");
|
||||
const int animHipsIndex = animSkeleton.nameToJointIndex("Hips");
|
||||
if (avatarHipsIndex != -1 && animHipsIndex != -1) {
|
||||
const int avatarHipsParentIndex = avatarSkeleton->getParentIndex(avatarHipsIndex);
|
||||
const int animHipsParentIndex = animSkeleton.getParentIndex(animHipsIndex);
|
||||
|
||||
boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio;
|
||||
const AnimPose& avatarHipsAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsIndex);
|
||||
const AnimPose& animHipsAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsIndex);
|
||||
|
||||
// the get the units and the heights for the animation and the avatar
|
||||
const float avatarUnitScale = extractScale(avatarSkeleton->getGeometryOffset()).y;
|
||||
const float animationUnitScale = extractScale(animModel.offset).y;
|
||||
const float avatarHeightInMeters = avatarUnitScale * avatarHipsAbsoluteDefaultPose.trans().y;
|
||||
const float animHeightInMeters = animationUnitScale * animHipsAbsoluteDefaultPose.trans().y;
|
||||
|
||||
// get the parent scales for the avatar and the animation
|
||||
float avatarHipsParentScale = 1.0f;
|
||||
if (avatarHipsParentIndex != -1) {
|
||||
const AnimPose& avatarHipsParentAbsoluteDefaultPose = avatarSkeleton->getAbsoluteDefaultPose(avatarHipsParentIndex);
|
||||
avatarHipsParentScale = avatarHipsParentAbsoluteDefaultPose.scale().y;
|
||||
}
|
||||
float animHipsParentScale = 1.0f;
|
||||
if (animHipsParentIndex != -1) {
|
||||
const AnimPose& animationHipsParentAbsoluteDefaultPose = animSkeleton.getAbsoluteDefaultPose(animHipsParentIndex);
|
||||
animHipsParentScale = animationHipsParentAbsoluteDefaultPose.scale().y;
|
||||
}
|
||||
|
||||
const float EPSILON = 0.0001f;
|
||||
// compute the ratios for the units, the heights in meters, and the parent scales
|
||||
if ((fabsf(animHeightInMeters) > EPSILON) && (animationUnitScale > EPSILON) && (animHipsParentScale > EPSILON)) {
|
||||
const float avatarToAnimationHeightRatio = avatarHeightInMeters / animHeightInMeters;
|
||||
const float unitsRatio = 1.0f / (avatarUnitScale / animationUnitScale);
|
||||
const float parentScaleRatio = 1.0f / (avatarHipsParentScale / animHipsParentScale);
|
||||
|
||||
boneLengthScale = avatarToAnimationHeightRatio * unitsRatio * parentScaleRatio;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int frame = 0; frame < animFrameCount; frame++) {
|
||||
const HFMAnimationFrame& animFrame = animModel.animationFrames[frame];
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
const int NUM_BODY_CONE_SIDES = 9;
|
||||
const float CHAT_MESSAGE_SCALE = 0.0015f;
|
||||
const float CHAT_MESSAGE_HEIGHT = 0.1f;
|
||||
const float DISPLAYNAME_FADE_TIME = 0.5f;
|
||||
|
@ -1661,60 +1660,6 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
int Avatar::_jointConesID = GeometryCache::UNKNOWN_ID;
|
||||
|
||||
// render a makeshift cone section that serves as a body part connecting joint spheres
|
||||
void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
|
||||
float radius1, float radius2, const glm::vec4& color) {
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
if (_jointConesID == GeometryCache::UNKNOWN_ID) {
|
||||
_jointConesID = geometryCache->allocateID();
|
||||
}
|
||||
|
||||
glm::vec3 axis = position2 - position1;
|
||||
float length = glm::length(axis);
|
||||
|
||||
if (length > 0.0f) {
|
||||
|
||||
axis /= length;
|
||||
|
||||
glm::vec3 perpSin = glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin));
|
||||
perpSin = glm::cross(perpCos, axis);
|
||||
|
||||
float angleb = 0.0f;
|
||||
QVector<glm::vec3> points;
|
||||
|
||||
for (int i = 0; i < NUM_BODY_CONE_SIDES; i ++) {
|
||||
|
||||
// the rectangles that comprise the sides of the cone section are
|
||||
// referenced by "a" and "b" in one dimension, and "1", and "2" in the other dimension.
|
||||
int anglea = angleb;
|
||||
angleb = ((float)(i+1) / (float)NUM_BODY_CONE_SIDES) * TWO_PI;
|
||||
|
||||
float sa = sinf(anglea);
|
||||
float sb = sinf(angleb);
|
||||
float ca = cosf(anglea);
|
||||
float cb = cosf(angleb);
|
||||
|
||||
glm::vec3 p1a = position1 + perpSin * sa * radius1 + perpCos * ca * radius1;
|
||||
glm::vec3 p1b = position1 + perpSin * sb * radius1 + perpCos * cb * radius1;
|
||||
glm::vec3 p2a = position2 + perpSin * sa * radius2 + perpCos * ca * radius2;
|
||||
glm::vec3 p2b = position2 + perpSin * sb * radius2 + perpCos * cb * radius2;
|
||||
|
||||
points << p1a << p1b << p2a << p1b << p2a << p2b;
|
||||
}
|
||||
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
|
||||
// TODO: this is really inefficient constantly recreating these vertices buffers. It would be
|
||||
// better if the avatars cached these buffers for each of the joints they are rendering
|
||||
geometryCache->updateVertices(_jointConesID, points, color);
|
||||
geometryCache->renderVertices(batch, gpu::TRIANGLES, _jointConesID);
|
||||
}
|
||||
}
|
||||
|
||||
float Avatar::getSkeletonHeight() const {
|
||||
Extents extents = _skeletonModel->getBindExtents();
|
||||
return extents.maximum.y - extents.minimum.y;
|
||||
|
|
|
@ -296,9 +296,6 @@ public:
|
|||
|
||||
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
|
||||
|
||||
static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
|
||||
float radius1, float radius2, const glm::vec4& color);
|
||||
|
||||
/**jsdoc
|
||||
* Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
|
||||
* with an offset of <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly.
|
||||
|
@ -665,8 +662,6 @@ protected:
|
|||
AvatarTransit _transit;
|
||||
std::mutex _transitLock;
|
||||
|
||||
static int _jointConesID;
|
||||
|
||||
int _voiceSphereID;
|
||||
|
||||
float _displayNameTargetAlpha { 1.0f };
|
||||
|
|
|
@ -338,24 +338,20 @@ void SkeletonModel::computeBoundingShape() {
|
|||
void SkeletonModel::renderBoundingCollisionShapes(RenderArgs* args, gpu::Batch& batch, float scale, float alpha) {
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
// draw a blue sphere at the capsule top point
|
||||
glm::vec3 topPoint = _translation + getRotation() * (scale * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * Vectors::UNIT_Y));
|
||||
|
||||
glm::vec3 topPoint = _translation + _rotation * (scale * (_boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * Vectors::UNIT_Y));
|
||||
batch.setModelTransform(Transform().setTranslation(topPoint).postScale(scale * _boundingCapsuleRadius));
|
||||
geometryCache->renderSolidSphereInstance(args, batch, glm::vec4(0.6f, 0.6f, 0.8f, alpha));
|
||||
|
||||
// draw a yellow sphere at the capsule bottom point
|
||||
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, scale * _boundingCapsuleHeight, 0.0f);
|
||||
glm::vec3 axis = topPoint - bottomPoint;
|
||||
|
||||
glm::vec3 bottomPoint = topPoint - _rotation * glm::vec3(0.0f, scale * _boundingCapsuleHeight, 0.0f);
|
||||
batch.setModelTransform(Transform().setTranslation(bottomPoint).postScale(scale * _boundingCapsuleRadius));
|
||||
geometryCache->renderSolidSphereInstance(args, batch, glm::vec4(0.8f, 0.8f, 0.6f, alpha));
|
||||
|
||||
// draw a green cylinder between the two points
|
||||
glm::vec3 origin(0.0f);
|
||||
batch.setModelTransform(Transform().setTranslation(bottomPoint));
|
||||
geometryCache->bindSimpleProgram(batch);
|
||||
Avatar::renderJointConnectingCone(batch, origin, axis, scale * _boundingCapsuleRadius, scale * _boundingCapsuleRadius,
|
||||
glm::vec4(0.6f, 0.8f, 0.6f, alpha));
|
||||
float capsuleDiameter = 2.0f * _boundingCapsuleRadius;
|
||||
glm::vec3 cylinderDimensions = glm::vec3(capsuleDiameter, _boundingCapsuleHeight, capsuleDiameter);
|
||||
batch.setModelTransform(Transform().setScale(scale * cylinderDimensions).setRotation(_rotation).setTranslation(0.5f * (topPoint + bottomPoint)));
|
||||
geometryCache->renderSolidShapeInstance(args, batch, GeometryCache::Shape::Cylinder, glm::vec4(0.6f, 0.8f, 0.6f, alpha));
|
||||
}
|
||||
|
||||
bool SkeletonModel::hasSkeleton() {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
set(TARGET_NAME baking)
|
||||
setup_hifi_library(Concurrent)
|
||||
|
||||
link_hifi_libraries(shared graphics networking ktx image fbx)
|
||||
link_hifi_libraries(shared shaders graphics networking material-networking graphics-scripting ktx image fbx model-baker task)
|
||||
include_hifi_library_headers(gpu)
|
||||
include_hifi_library_headers(hfm)
|
||||
|
||||
target_draco()
|
||||
|
|
|
@ -52,7 +52,7 @@ protected:
|
|||
void handleErrors(const QStringList& errors);
|
||||
|
||||
// List of baked output files. For instance, for an FBX this would
|
||||
// include the .fbx and all of its texture files.
|
||||
// include the .fbx, a .fst pointing to the fbx, and all of the fbx texture files.
|
||||
std::vector<QString> _outputFiles;
|
||||
|
||||
QStringList _errorList;
|
||||
|
|
|
@ -33,29 +33,19 @@
|
|||
#include "ModelBakingLoggingCategory.h"
|
||||
#include "TextureBaker.h"
|
||||
|
||||
#ifdef HIFI_DUMP_FBX
|
||||
#include "FBXToJSON.h"
|
||||
#endif
|
||||
|
||||
void FBXBaker::bake() {
|
||||
qDebug() << "FBXBaker" << _modelURL << "bake starting";
|
||||
|
||||
// setup the output folder for the results of this bake
|
||||
setupOutputFolder();
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
if (hasBeenBaked) {
|
||||
// Look for the original model file one directory higher. Perhaps this is an oven output directory.
|
||||
QUrl originalRelativePath = QUrl("../original/" + inputModelURL.fileName().replace(BAKED_FBX_EXTENSION, FBX_EXTENSION));
|
||||
QUrl newInputModelURL = inputModelURL.adjusted(QUrl::RemoveFilename).resolved(originalRelativePath);
|
||||
_modelURL = newInputModelURL;
|
||||
}
|
||||
|
||||
connect(this, &FBXBaker::sourceCopyReadyToLoad, this, &FBXBaker::bakeSourceCopy);
|
||||
|
||||
// make a local copy of the FBX file
|
||||
loadSourceFBX();
|
||||
}
|
||||
|
||||
void FBXBaker::bakeSourceCopy() {
|
||||
// load the scene from the FBX file
|
||||
importScene();
|
||||
void FBXBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) {
|
||||
_hfmModel = hfmModel;
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
|
@ -68,222 +58,100 @@ void FBXBaker::bakeSourceCopy() {
|
|||
return;
|
||||
}
|
||||
|
||||
rewriteAndBakeSceneModels();
|
||||
rewriteAndBakeSceneModels(hfmModel->meshes, dracoMeshes, dracoMaterialLists);
|
||||
}
|
||||
|
||||
if (shouldStop()) {
|
||||
void FBXBaker::replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList) {
|
||||
// Compress mesh information and store in dracoMeshNode
|
||||
FBXNode dracoMeshNode;
|
||||
bool success = buildDracoMeshNode(dracoMeshNode, dracoMeshBytes, dracoMaterialList);
|
||||
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if we're already done with textures (in case we had none to re-write)
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
void FBXBaker::setupOutputFolder() {
|
||||
// make sure there isn't already an output directory using the same name
|
||||
if (QDir(_bakedOutputDir).exists()) {
|
||||
qWarning() << "Output path" << _bakedOutputDir << "already exists. Continuing.";
|
||||
} else {
|
||||
qCDebug(model_baking) << "Creating FBX output folder" << _bakedOutputDir;
|
||||
meshNode.children.push_back(dracoMeshNode);
|
||||
|
||||
// attempt to make the output folder
|
||||
if (!QDir().mkpath(_bakedOutputDir)) {
|
||||
handleError("Failed to create FBX output folder " + _bakedOutputDir);
|
||||
return;
|
||||
}
|
||||
// attempt to make the output folder
|
||||
if (!QDir().mkpath(_originalOutputDir)) {
|
||||
handleError("Failed to create FBX output folder " + _originalOutputDir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
static const std::vector<QString> nodeNamesToDelete {
|
||||
// Node data that is packed into the draco mesh
|
||||
"Vertices",
|
||||
"PolygonVertexIndex",
|
||||
"LayerElementNormal",
|
||||
"LayerElementColor",
|
||||
"LayerElementUV",
|
||||
"LayerElementMaterial",
|
||||
"LayerElementTexture",
|
||||
|
||||
void FBXBaker::loadSourceFBX() {
|
||||
// check if the FBX is local or first needs to be downloaded
|
||||
if (_modelURL.isLocalFile()) {
|
||||
// load up the local file
|
||||
QFile localFBX { _modelURL.toLocalFile() };
|
||||
|
||||
qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath;
|
||||
|
||||
if (!localFBX.exists()) {
|
||||
//QMessageBox::warning(this, "Could not find " + _fbxURL.toString(), "");
|
||||
handleError("Could not find " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// make a copy in the output folder
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName();
|
||||
localFBX.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
localFBX.copy(_originalModelFilePath);
|
||||
|
||||
// emit our signal to start the import of the FBX source copy
|
||||
emit sourceCopyReadyToLoad();
|
||||
} else {
|
||||
// remote file, kick off a download
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
// setup the request to follow re-directs and always hit the network
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
|
||||
networkRequest.setUrl(_modelURL);
|
||||
|
||||
qCDebug(model_baking) << "Downloading" << _modelURL;
|
||||
auto networkReply = networkAccessManager.get(networkRequest);
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::handleFBXNetworkReply() {
|
||||
auto requestReply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
qCDebug(model_baking) << "Downloaded" << _modelURL;
|
||||
|
||||
// grab the contents of the reply and make a copy in the output folder
|
||||
QFile copyOfOriginal(_originalModelFilePath);
|
||||
|
||||
qDebug(model_baking) << "Writing copy of original FBX to" << _originalModelFilePath << copyOfOriginal.fileName();
|
||||
|
||||
if (!copyOfOriginal.open(QIODevice::WriteOnly)) {
|
||||
// add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")");
|
||||
return;
|
||||
}
|
||||
if (copyOfOriginal.write(requestReply->readAll()) == -1) {
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)");
|
||||
return;
|
||||
}
|
||||
|
||||
// close that file now that we are done writing to it
|
||||
copyOfOriginal.close();
|
||||
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
// emit our signal to start the import of the FBX source copy
|
||||
emit sourceCopyReadyToLoad();
|
||||
} else {
|
||||
// add an error to our list stating that the FBX could not be downloaded
|
||||
handleError("Failed to download " + _modelURL.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::importScene() {
|
||||
qDebug() << "file path: " << _originalModelFilePath.toLocal8Bit().data() << QDir(_originalModelFilePath).exists();
|
||||
|
||||
QFile fbxFile(_originalModelFilePath);
|
||||
if (!fbxFile.open(QIODevice::ReadOnly)) {
|
||||
handleError("Error opening " + _originalModelFilePath + " for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
FBXSerializer fbxSerializer;
|
||||
|
||||
qCDebug(model_baking) << "Parsing" << _modelURL;
|
||||
_rootNode = fbxSerializer._rootNode = fbxSerializer.parseFBX(&fbxFile);
|
||||
|
||||
#ifdef HIFI_DUMP_FBX
|
||||
{
|
||||
FBXToJSON fbxToJSON;
|
||||
fbxToJSON << _rootNode;
|
||||
QFileInfo modelFile(_originalModelFilePath);
|
||||
QString outFilename(_bakedOutputDir + "/" + modelFile.completeBaseName() + "_FBX.json");
|
||||
QFile jsonFile(outFilename);
|
||||
if (jsonFile.open(QIODevice::WriteOnly)) {
|
||||
jsonFile.write(fbxToJSON.str().c_str(), fbxToJSON.str().length());
|
||||
jsonFile.close();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
_hfmModel = fbxSerializer.extractHFMModel({}, _modelURL.toString());
|
||||
_textureContentMap = fbxSerializer._textureContent;
|
||||
}
|
||||
|
||||
void FBXBaker::rewriteAndBakeSceneModels() {
|
||||
unsigned int meshIndex = 0;
|
||||
bool hasDeformers { false };
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
if (rootChild.name == "Objects") {
|
||||
for (FBXNode& objectChild : rootChild.children) {
|
||||
if (objectChild.name == "Deformer") {
|
||||
hasDeformers = true;
|
||||
break;
|
||||
}
|
||||
// Node data that we don't support
|
||||
"Edges",
|
||||
"LayerElementTangent",
|
||||
"LayerElementBinormal",
|
||||
"LayerElementSmoothing"
|
||||
};
|
||||
auto& children = meshNode.children;
|
||||
auto it = children.begin();
|
||||
while (it != children.end()) {
|
||||
auto begin = nodeNamesToDelete.begin();
|
||||
auto end = nodeNamesToDelete.end();
|
||||
if (find(begin, end, it->name) != end) {
|
||||
it = children.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (hasDeformers) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) {
|
||||
std::vector<int> meshIndexToRuntimeOrder;
|
||||
auto meshCount = (int)meshes.size();
|
||||
meshIndexToRuntimeOrder.resize(meshCount);
|
||||
for (int i = 0; i < meshCount; i++) {
|
||||
meshIndexToRuntimeOrder[meshes[i].meshIndex] = i;
|
||||
}
|
||||
|
||||
// The meshIndex represents the order in which the meshes are loaded from the FBX file
|
||||
// We replicate this order by iterating over the meshes in the same way that FBXSerializer does
|
||||
int meshIndex = 0;
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
if (rootChild.name == "Objects") {
|
||||
for (FBXNode& objectChild : rootChild.children) {
|
||||
if (objectChild.name == "Geometry") {
|
||||
|
||||
// TODO Pull this out of _hfmModel instead so we don't have to reprocess it
|
||||
auto extractedMesh = FBXSerializer::extractMesh(objectChild, meshIndex, false);
|
||||
|
||||
// Callback to get MaterialID
|
||||
GetMaterialIDCallback materialIDcallback = [&extractedMesh](int partIndex) {
|
||||
return extractedMesh.partMaterialTextures[partIndex].first;
|
||||
};
|
||||
|
||||
// Compress mesh information and store in dracoMeshNode
|
||||
FBXNode dracoMeshNode;
|
||||
bool success = compressMesh(extractedMesh.mesh, hasDeformers, dracoMeshNode, materialIDcallback);
|
||||
|
||||
// if bake fails - return, if there were errors and continue, if there were warnings.
|
||||
if (!success) {
|
||||
if (hasErrors()) {
|
||||
return;
|
||||
} else if (hasWarnings()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
objectChild.children.push_back(dracoMeshNode);
|
||||
|
||||
static const std::vector<QString> nodeNamesToDelete {
|
||||
// Node data that is packed into the draco mesh
|
||||
"Vertices",
|
||||
"PolygonVertexIndex",
|
||||
"LayerElementNormal",
|
||||
"LayerElementColor",
|
||||
"LayerElementUV",
|
||||
"LayerElementMaterial",
|
||||
"LayerElementTexture",
|
||||
|
||||
// Node data that we don't support
|
||||
"Edges",
|
||||
"LayerElementTangent",
|
||||
"LayerElementBinormal",
|
||||
"LayerElementSmoothing"
|
||||
};
|
||||
auto& children = objectChild.children;
|
||||
auto it = children.begin();
|
||||
while (it != children.end()) {
|
||||
auto begin = nodeNamesToDelete.begin();
|
||||
auto end = nodeNamesToDelete.end();
|
||||
if (find(begin, end, it->name) != end) {
|
||||
it = children.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
for (FBXNode& object : rootChild.children) {
|
||||
if (object.name == "Geometry") {
|
||||
if (object.properties.at(2) == "Mesh") {
|
||||
int meshNum = meshIndexToRuntimeOrder[meshIndex];
|
||||
replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
meshIndex++;
|
||||
}
|
||||
} else if (object.name == "Model") {
|
||||
for (FBXNode& modelChild : object.children) {
|
||||
if (modelChild.name == "Properties60" || modelChild.name == "Properties70") {
|
||||
// This is a properties node
|
||||
// Remove the geometric transform because that has been applied directly to the vertices in FBXSerializer
|
||||
static const QVariant GEOMETRIC_TRANSLATION = hifi::ByteArray("GeometricTranslation");
|
||||
static const QVariant GEOMETRIC_ROTATION = hifi::ByteArray("GeometricRotation");
|
||||
static const QVariant GEOMETRIC_SCALING = hifi::ByteArray("GeometricScaling");
|
||||
for (int i = 0; i < modelChild.children.size(); i++) {
|
||||
const auto& prop = modelChild.children[i];
|
||||
const auto& propertyName = prop.properties.at(0);
|
||||
if (propertyName == GEOMETRIC_TRANSLATION ||
|
||||
propertyName == GEOMETRIC_ROTATION ||
|
||||
propertyName == GEOMETRIC_SCALING) {
|
||||
modelChild.children.removeAt(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
} else if (modelChild.name == "Vertices") {
|
||||
// This model is also a mesh
|
||||
int meshNum = meshIndexToRuntimeOrder[meshIndex];
|
||||
replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
meshIndex++;
|
||||
}
|
||||
}
|
||||
} // Geometry Object
|
||||
}
|
||||
|
||||
} // foreach root child
|
||||
if (hasErrors()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,31 +31,18 @@ using TextureBakerThreadGetter = std::function<QThread*()>;
|
|||
class FBXBaker : public ModelBaker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using ModelBaker::ModelBaker;
|
||||
FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
|
||||
signals:
|
||||
void sourceCopyReadyToLoad();
|
||||
|
||||
private slots:
|
||||
void bakeSourceCopy();
|
||||
void handleFBXNetworkReply();
|
||||
protected:
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) override;
|
||||
|
||||
private:
|
||||
void setupOutputFolder();
|
||||
|
||||
void loadSourceFBX();
|
||||
|
||||
void importScene();
|
||||
void embedTextureMetaData();
|
||||
void rewriteAndBakeSceneModels();
|
||||
void rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists);
|
||||
void rewriteAndBakeSceneTextures();
|
||||
void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList);
|
||||
|
||||
HFMModel* _hfmModel;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
QHash<QUrl, QString> _remappedTexturePaths;
|
||||
hfm::Model::Pointer _hfmModel;
|
||||
|
||||
bool _pendingErrorEmission { false };
|
||||
};
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
|
||||
#include "JSBaker.h"
|
||||
|
||||
#include <PathUtils.h>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "Baker.h"
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <PathUtils.h>
|
||||
|
||||
const int ASCII_CHARACTERS_UPPER_LIMIT = 126;
|
||||
|
||||
|
@ -21,25 +23,79 @@ JSBaker::JSBaker(const QUrl& jsURL, const QString& bakedOutputDir) :
|
|||
_jsURL(jsURL),
|
||||
_bakedOutputDir(bakedOutputDir)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void JSBaker::bake() {
|
||||
qCDebug(js_baking) << "JS Baker " << _jsURL << "bake starting";
|
||||
|
||||
// Import file to start baking
|
||||
QFile jsFile(_jsURL.toLocalFile());
|
||||
if (!jsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
handleError("Error opening " + _jsURL.fileName() + " for reading");
|
||||
return;
|
||||
}
|
||||
// once our script is loaded, kick off a the processing
|
||||
connect(this, &JSBaker::originalScriptLoaded, this, &JSBaker::processScript);
|
||||
|
||||
if (_originalScript.isEmpty()) {
|
||||
// first load the script (either locally or remotely)
|
||||
loadScript();
|
||||
} else {
|
||||
// we already have a script passed to us, use that
|
||||
processScript();
|
||||
}
|
||||
}
|
||||
|
||||
void JSBaker::loadScript() {
|
||||
// check if the script is local or first needs to be downloaded
|
||||
if (_jsURL.isLocalFile()) {
|
||||
// load up the local file
|
||||
QFile localScript(_jsURL.toLocalFile());
|
||||
if (!localScript.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
handleError("Error opening " + _jsURL.fileName() + " for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
_originalScript = localScript.readAll();
|
||||
|
||||
emit originalScriptLoaded();
|
||||
} else {
|
||||
// remote file, kick off a download
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
// setup the request to follow re-directs and always hit the network
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
|
||||
networkRequest.setUrl(_jsURL);
|
||||
|
||||
qCDebug(js_baking) << "Downloading" << _jsURL;
|
||||
|
||||
// kickoff the download, wait for slot to tell us it is done
|
||||
auto networkReply = networkAccessManager.get(networkRequest);
|
||||
connect(networkReply, &QNetworkReply::finished, this, &JSBaker::handleScriptNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
void JSBaker::handleScriptNetworkReply() {
|
||||
auto requestReply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
qCDebug(js_baking) << "Downloaded script" << _jsURL;
|
||||
|
||||
// store the original script so it can be passed along for the bake
|
||||
_originalScript = requestReply->readAll();
|
||||
|
||||
emit originalScriptLoaded();
|
||||
} else {
|
||||
// add an error to our list stating that this script could not be downloaded
|
||||
handleError("Error downloading " + _jsURL.toString() + " - " + requestReply->errorString());
|
||||
}
|
||||
}
|
||||
|
||||
void JSBaker::processScript() {
|
||||
// Read file into an array
|
||||
QByteArray inputJS = jsFile.readAll();
|
||||
QByteArray outputJS;
|
||||
|
||||
// Call baking on inputJS and store result in outputJS
|
||||
bool success = bakeJS(inputJS, outputJS);
|
||||
bool success = bakeJS(_originalScript, outputJS);
|
||||
if (!success) {
|
||||
qCDebug(js_baking) << "Bake Failed";
|
||||
handleError("Unterminated multi-line comment");
|
||||
|
|
|
@ -25,11 +25,24 @@ public:
|
|||
JSBaker(const QUrl& jsURL, const QString& bakedOutputDir);
|
||||
static bool bakeJS(const QByteArray& inputFile, QByteArray& outputFile);
|
||||
|
||||
QString getJSPath() const { return _jsURL.toDisplayString(); }
|
||||
QString getBakedJSFilePath() const { return _bakedJSFilePath; }
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
|
||||
signals:
|
||||
void originalScriptLoaded();
|
||||
|
||||
private slots:
|
||||
void processScript();
|
||||
|
||||
private:
|
||||
void loadScript();
|
||||
void handleScriptNetworkReply();
|
||||
|
||||
QUrl _jsURL;
|
||||
QByteArray _originalScript;
|
||||
QString _bakedOutputDir;
|
||||
QString _bakedJSFilePath;
|
||||
|
||||
|
|
247
libraries/baking/src/MaterialBaker.cpp
Normal file
247
libraries/baking/src/MaterialBaker.cpp
Normal file
|
@ -0,0 +1,247 @@
|
|||
//
|
||||
// MaterialBaker.cpp
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Sam Gondelman on 2/26/2019
|
||||
// Copyright 2019 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 "MaterialBaker.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "QJsonObject"
|
||||
#include "QJsonDocument"
|
||||
|
||||
#include "MaterialBakingLoggingCategory.h"
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include <PathUtils.h>
|
||||
|
||||
#include <graphics-scripting/GraphicsScriptingInterface.h>
|
||||
|
||||
std::function<QThread*()> MaterialBaker::_getNextOvenWorkerThreadOperator;
|
||||
|
||||
static int materialNum = 0;
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<graphics::Material::MapChannel> {
|
||||
size_t operator()(const graphics::Material::MapChannel& a) const {
|
||||
return std::hash<size_t>()((size_t)a);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath) :
|
||||
_materialData(materialData),
|
||||
_isURL(isURL),
|
||||
_bakedOutputDir(bakedOutputDir),
|
||||
_textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)),
|
||||
_destinationPath(destinationPath)
|
||||
{
|
||||
}
|
||||
|
||||
void MaterialBaker::bake() {
|
||||
qDebug(material_baking) << "Material Baker" << _materialData << "bake starting";
|
||||
|
||||
// once our script is loaded, kick off a the processing
|
||||
connect(this, &MaterialBaker::originalMaterialLoaded, this, &MaterialBaker::processMaterial);
|
||||
|
||||
if (!_materialResource) {
|
||||
// first load the material (either locally or remotely)
|
||||
loadMaterial();
|
||||
} else {
|
||||
// we already have a material passed to us, use that
|
||||
if (_materialResource->isLoaded()) {
|
||||
processMaterial();
|
||||
} else {
|
||||
connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::loadMaterial() {
|
||||
if (!_isURL) {
|
||||
qCDebug(material_baking) << "Loading local material" << _materialData;
|
||||
|
||||
_materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource());
|
||||
// TODO: add baseURL to allow these to reference relative files next to them
|
||||
_materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(_materialData.toUtf8()), QUrl());
|
||||
} else {
|
||||
qCDebug(material_baking) << "Downloading material" << _materialData;
|
||||
_materialResource = MaterialCache::instance().getMaterial(_materialData);
|
||||
}
|
||||
|
||||
if (_materialResource) {
|
||||
if (_materialResource->isLoaded()) {
|
||||
emit originalMaterialLoaded();
|
||||
} else {
|
||||
connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded);
|
||||
}
|
||||
} else {
|
||||
handleError("Error loading " + _materialData);
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::processMaterial() {
|
||||
if (!_materialResource || _materialResource->parsedMaterials.networkMaterials.size() == 0) {
|
||||
handleError("Error processing " + _materialData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (QDir(_textureOutputDir).exists()) {
|
||||
qWarning() << "Output path" << _textureOutputDir << "already exists. Continuing.";
|
||||
} else {
|
||||
qCDebug(material_baking) << "Creating materialTextures output folder" << _textureOutputDir;
|
||||
if (!QDir().mkpath(_textureOutputDir)) {
|
||||
handleError("Failed to create materialTextures output folder " + _textureOutputDir);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) {
|
||||
if (networkMaterial.second) {
|
||||
auto textureMaps = networkMaterial.second->getTextureMaps();
|
||||
for (auto textureMap : textureMaps) {
|
||||
if (textureMap.second && textureMap.second->getTextureSource()) {
|
||||
graphics::Material::MapChannel mapChannel = textureMap.first;
|
||||
auto texture = textureMap.second->getTextureSource();
|
||||
|
||||
QUrl url = texture->getUrl();
|
||||
QString cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString();
|
||||
auto idx = cleanURL.lastIndexOf('.');
|
||||
auto extension = idx >= 0 ? url.toDisplayString().mid(idx + 1).toLower() : "";
|
||||
|
||||
if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) {
|
||||
QUrl textureURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
// FIXME: this isn't properly handling bumpMaps or glossMaps
|
||||
static std::unordered_map<graphics::Material::MapChannel, image::TextureUsage::Type> MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP;
|
||||
if (MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.empty()) {
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::EMISSIVE_MAP] = image::TextureUsage::EMISSIVE_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ALBEDO_MAP] = image::TextureUsage::ALBEDO_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::METALLIC_MAP] = image::TextureUsage::METALLIC_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ROUGHNESS_MAP] = image::TextureUsage::ROUGHNESS_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::NORMAL_MAP] = image::TextureUsage::NORMAL_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::OCCLUSION_MAP] = image::TextureUsage::OCCLUSION_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::LIGHTMAP_MAP] = image::TextureUsage::LIGHTMAP_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::SCATTERING_MAP] = image::TextureUsage::SCATTERING_TEXTURE;
|
||||
}
|
||||
|
||||
auto it = MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.find(mapChannel);
|
||||
if (it == MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.end()) {
|
||||
handleError("Unknown map channel");
|
||||
return;
|
||||
}
|
||||
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey(textureURL, it->second);
|
||||
if (!_textureBakers.contains(textureKey)) {
|
||||
auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), it->second);
|
||||
|
||||
QSharedPointer<TextureBaker> textureBaker {
|
||||
new TextureBaker(textureURL, it->second, _textureOutputDir, "", baseTextureFileName),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
textureBaker->setMapChannel(mapChannel);
|
||||
connect(textureBaker.data(), &TextureBaker::finished, this, &MaterialBaker::handleFinishedTextureBaker);
|
||||
_textureBakers.insert(textureKey, textureBaker);
|
||||
textureBaker->moveToThread(_getNextOvenWorkerThreadOperator ? _getNextOvenWorkerThreadOperator() : thread());
|
||||
QMetaObject::invokeMethod(textureBaker.data(), "bake");
|
||||
}
|
||||
_materialsNeedingRewrite.insert(textureKey, networkMaterial.second);
|
||||
} else {
|
||||
qCDebug(material_baking) << "Texture extension not supported: " << extension;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_textureBakers.empty()) {
|
||||
outputMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::handleFinishedTextureBaker() {
|
||||
auto baker = qobject_cast<TextureBaker*>(sender());
|
||||
|
||||
if (baker) {
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey = { baker->getTextureURL(), baker->getTextureType() };
|
||||
if (!baker->hasErrors()) {
|
||||
// this TextureBaker is done and everything went according to plan
|
||||
qCDebug(material_baking) << "Re-writing texture references to" << baker->getTextureURL();
|
||||
|
||||
auto newURL = QUrl(_textureOutputDir).resolved(baker->getMetaTextureFileName());
|
||||
auto relativeURL = QDir(_bakedOutputDir).relativeFilePath(newURL.toString());
|
||||
|
||||
// Replace the old texture URLs
|
||||
for (auto networkMaterial : _materialsNeedingRewrite.values(textureKey)) {
|
||||
networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(_destinationPath.resolved(relativeURL));
|
||||
}
|
||||
} else {
|
||||
// this texture failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
||||
// the texture to our warnings
|
||||
_warningList << baker->getWarnings();
|
||||
}
|
||||
|
||||
_materialsNeedingRewrite.remove(textureKey);
|
||||
_textureBakers.remove(textureKey);
|
||||
|
||||
if (_textureBakers.empty()) {
|
||||
outputMaterial();
|
||||
}
|
||||
} else {
|
||||
handleWarning("Unidentified baker finished and signaled to material baker to handle texture. Material: " + _materialData);
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::outputMaterial() {
|
||||
if (_materialResource) {
|
||||
QJsonObject json;
|
||||
if (_materialResource->parsedMaterials.networkMaterials.size() == 1) {
|
||||
auto networkMaterial = _materialResource->parsedMaterials.networkMaterials.begin();
|
||||
auto scriptableMaterial = scriptable::ScriptableMaterial(networkMaterial->second);
|
||||
QVariant materialVariant = scriptable::scriptableMaterialToScriptValue(&_scriptEngine, scriptableMaterial).toVariant();
|
||||
json.insert("materials", QJsonDocument::fromVariant(materialVariant).object());
|
||||
} else {
|
||||
QJsonArray materialArray;
|
||||
for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) {
|
||||
auto scriptableMaterial = scriptable::ScriptableMaterial(networkMaterial.second);
|
||||
QVariant materialVariant = scriptable::scriptableMaterialToScriptValue(&_scriptEngine, scriptableMaterial).toVariant();
|
||||
materialArray.append(QJsonDocument::fromVariant(materialVariant).object());
|
||||
}
|
||||
json.insert("materials", materialArray);
|
||||
}
|
||||
|
||||
QByteArray outputMaterial = QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
if (_isURL) {
|
||||
auto fileName = QUrl(_materialData).fileName();
|
||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||
auto bakedFilename = baseName + BAKED_MATERIAL_EXTENSION;
|
||||
|
||||
_bakedMaterialData = _bakedOutputDir + "/" + bakedFilename;
|
||||
|
||||
QFile bakedFile;
|
||||
bakedFile.setFileName(_bakedMaterialData);
|
||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Error opening " + _bakedMaterialData + " for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
bakedFile.write(outputMaterial);
|
||||
|
||||
// Export successful
|
||||
_outputFiles.push_back(_bakedMaterialData);
|
||||
qCDebug(material_baking) << "Exported" << _materialData << "to" << _bakedMaterialData;
|
||||
} else {
|
||||
_bakedMaterialData = QString(outputMaterial);
|
||||
qCDebug(material_baking) << "Converted" << _materialData << "to" << _bakedMaterialData;
|
||||
}
|
||||
}
|
||||
|
||||
// emit signal to indicate the material baking is finished
|
||||
emit finished();
|
||||
}
|
67
libraries/baking/src/MaterialBaker.h
Normal file
67
libraries/baking/src/MaterialBaker.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// MaterialBaker.h
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Sam Gondelman on 2/26/2019
|
||||
// Copyright 2019 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_MaterialBaker_h
|
||||
#define hifi_MaterialBaker_h
|
||||
|
||||
#include "Baker.h"
|
||||
|
||||
#include "TextureBaker.h"
|
||||
#include "baking/TextureFileNamer.h"
|
||||
|
||||
#include <material-networking/MaterialCache.h>
|
||||
|
||||
static const QString BAKED_MATERIAL_EXTENSION = ".baked.json";
|
||||
|
||||
class MaterialBaker : public Baker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath);
|
||||
|
||||
QString getMaterialData() const { return _materialData; }
|
||||
bool isURL() const { return _isURL; }
|
||||
QString getBakedMaterialData() const { return _bakedMaterialData; }
|
||||
|
||||
static void setNextOvenWorkerThreadOperator(std::function<QThread*()> getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; }
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
|
||||
signals:
|
||||
void originalMaterialLoaded();
|
||||
|
||||
private slots:
|
||||
void processMaterial();
|
||||
void outputMaterial();
|
||||
void handleFinishedTextureBaker();
|
||||
|
||||
private:
|
||||
void loadMaterial();
|
||||
|
||||
QString _materialData;
|
||||
bool _isURL;
|
||||
|
||||
NetworkMaterialResourcePointer _materialResource;
|
||||
|
||||
QHash<QPair<QUrl, image::TextureUsage::Type>, QSharedPointer<TextureBaker>> _textureBakers;
|
||||
QMultiHash<QPair<QUrl, image::TextureUsage::Type>, std::shared_ptr<NetworkMaterial>> _materialsNeedingRewrite;
|
||||
|
||||
QString _bakedOutputDir;
|
||||
QString _textureOutputDir;
|
||||
QString _bakedMaterialData;
|
||||
QUrl _destinationPath;
|
||||
|
||||
QScriptEngine _scriptEngine;
|
||||
static std::function<QThread*()> _getNextOvenWorkerThreadOperator;
|
||||
TextureFileNamer _textureFileNamer;
|
||||
};
|
||||
|
||||
#endif // !hifi_MaterialBaker_h
|
14
libraries/baking/src/MaterialBakingLoggingCategory.cpp
Normal file
14
libraries/baking/src/MaterialBakingLoggingCategory.cpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// MaterialBakingLoggingCategory.cpp
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Sam Gondelman on 2/26/2019
|
||||
// Copyright 2019 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 "MaterialBakingLoggingCategory.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(material_baking, "hifi.material-baking");
|
19
libraries/baking/src/MaterialBakingLoggingCategory.h
Normal file
19
libraries/baking/src/MaterialBakingLoggingCategory.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// MaterialBakingLoggingCategory.h
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Sam Gondelman on 2/26/2019
|
||||
// Copyright 2019 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_MaterialBakingLoggingCategory_h
|
||||
#define hifi_MaterialBakingLoggingCategory_h
|
||||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(material_baking)
|
||||
|
||||
#endif // hifi_MaterialBakingLoggingCategory_h
|
|
@ -12,8 +12,17 @@
|
|||
#include "ModelBaker.h"
|
||||
|
||||
#include <PathUtils.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <hfm/ModelFormatRegistry.h>
|
||||
#include <FBXSerializer.h>
|
||||
|
||||
#include <model-baker/Baker.h>
|
||||
#include <model-baker/PrepareJointsTask.h>
|
||||
|
||||
#include <FBXWriter.h>
|
||||
#include <FSTReader.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( push )
|
||||
|
@ -31,37 +40,275 @@
|
|||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
#include "baking/BakerLibrary.h"
|
||||
|
||||
ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory) :
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
_modelURL(inputModelURL),
|
||||
_bakedOutputDir(bakedOutputDirectory),
|
||||
_originalOutputDir(originalOutputDirectory),
|
||||
_textureThreadGetter(inputTextureThreadGetter)
|
||||
_textureThreadGetter(inputTextureThreadGetter),
|
||||
_hasBeenBaked(hasBeenBaked)
|
||||
{
|
||||
auto tempDir = PathUtils::generateTemporaryDir();
|
||||
auto bakedFilename = _modelURL.fileName();
|
||||
if (!hasBeenBaked) {
|
||||
bakedFilename = bakedFilename.left(bakedFilename.lastIndexOf('.'));
|
||||
bakedFilename += BAKED_FBX_EXTENSION;
|
||||
}
|
||||
_bakedModelURL = _bakedOutputDir + "/" + bakedFilename;
|
||||
}
|
||||
|
||||
if (tempDir.isEmpty()) {
|
||||
handleError("Failed to create a temporary directory.");
|
||||
void ModelBaker::setOutputURLSuffix(const QUrl& outputURLSuffix) {
|
||||
_outputURLSuffix = outputURLSuffix;
|
||||
}
|
||||
|
||||
void ModelBaker::setMappingURL(const QUrl& mappingURL) {
|
||||
_mappingURL = mappingURL;
|
||||
}
|
||||
|
||||
void ModelBaker::setMapping(const hifi::VariantHash& mapping) {
|
||||
_mapping = mapping;
|
||||
}
|
||||
|
||||
QUrl ModelBaker::getFullOutputMappingURL() const {
|
||||
QUrl appendedURL = _outputMappingURL;
|
||||
appendedURL.setFragment(_outputURLSuffix.fragment());
|
||||
appendedURL.setQuery(_outputURLSuffix.query());
|
||||
appendedURL.setUserInfo(_outputURLSuffix.userInfo());
|
||||
return appendedURL;
|
||||
}
|
||||
|
||||
void ModelBaker::bake() {
|
||||
qDebug() << "ModelBaker" << _modelURL << "bake starting";
|
||||
|
||||
// Setup the output folders for the results of this bake
|
||||
initializeOutputDirs();
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_modelTempDir = tempDir;
|
||||
|
||||
_originalModelFilePath = _modelTempDir.filePath(_modelURL.fileName());
|
||||
qDebug() << "Made temporary dir " << _modelTempDir;
|
||||
qDebug() << "Origin file path: " << _originalModelFilePath;
|
||||
connect(this, &ModelBaker::modelLoaded, this, &ModelBaker::bakeSourceCopy);
|
||||
|
||||
// make a local copy of the model
|
||||
saveSourceModel();
|
||||
}
|
||||
|
||||
ModelBaker::~ModelBaker() {
|
||||
if (_modelTempDir.exists()) {
|
||||
if (!_modelTempDir.remove(_originalModelFilePath)) {
|
||||
qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalModelFilePath;
|
||||
void ModelBaker::initializeOutputDirs() {
|
||||
// Attempt to make the output folders
|
||||
// Warn if there is an output directory using the same name, unless we know a parent FST baker created them already
|
||||
|
||||
if (QDir(_bakedOutputDir).exists()) {
|
||||
if (_mappingURL.isEmpty()) {
|
||||
qWarning() << "Output path" << _bakedOutputDir << "already exists. Continuing.";
|
||||
}
|
||||
if (!_modelTempDir.rmdir(".")) {
|
||||
qCWarning(model_baking) << "Failed to remove temporary directory:" << _modelTempDir;
|
||||
} else {
|
||||
qCDebug(model_baking) << "Creating baked output folder" << _bakedOutputDir;
|
||||
if (!QDir().mkpath(_bakedOutputDir)) {
|
||||
handleError("Failed to create baked output folder " + _bakedOutputDir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QDir originalOutputDir { _originalOutputDir };
|
||||
if (originalOutputDir.exists()) {
|
||||
if (_mappingURL.isEmpty()) {
|
||||
qWarning() << "Output path" << _originalOutputDir << "already exists. Continuing.";
|
||||
}
|
||||
} else {
|
||||
qCDebug(model_baking) << "Creating original output folder" << _originalOutputDir;
|
||||
if (!QDir().mkpath(_originalOutputDir)) {
|
||||
handleError("Failed to create original output folder " + _originalOutputDir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (originalOutputDir.isReadable()) {
|
||||
// The output directory is available. Use that to write/read the original model file
|
||||
_originalOutputModelPath = originalOutputDir.filePath(_modelURL.fileName());
|
||||
} else {
|
||||
handleError("Unable to write to original output folder " + _originalOutputDir);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::saveSourceModel() {
|
||||
// check if the FBX is local or first needs to be downloaded
|
||||
if (_modelURL.isLocalFile()) {
|
||||
// load up the local file
|
||||
QFile localModelURL { _modelURL.toLocalFile() };
|
||||
|
||||
qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalOutputModelPath;
|
||||
|
||||
if (!localModelURL.exists()) {
|
||||
//QMessageBox::warning(this, "Could not find " + _modelURL.toString(), "");
|
||||
handleError("Could not find " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
localModelURL.copy(_originalOutputModelPath);
|
||||
|
||||
// emit our signal to start the import of the model source copy
|
||||
emit modelLoaded();
|
||||
} else {
|
||||
// remote file, kick off a download
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
// setup the request to follow re-directs and always hit the network
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
|
||||
networkRequest.setUrl(_modelURL);
|
||||
|
||||
qCDebug(model_baking) << "Downloading" << _modelURL;
|
||||
auto networkReply = networkAccessManager.get(networkRequest);
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, this, &ModelBaker::handleModelNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::handleModelNetworkReply() {
|
||||
auto requestReply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
qCDebug(model_baking) << "Downloaded" << _modelURL;
|
||||
|
||||
// grab the contents of the reply and make a copy in the output folder
|
||||
QFile copyOfOriginal(_originalOutputModelPath);
|
||||
|
||||
qDebug(model_baking) << "Writing copy of original model file to" << _originalOutputModelPath << copyOfOriginal.fileName();
|
||||
|
||||
if (!copyOfOriginal.open(QIODevice::WriteOnly)) {
|
||||
// add an error to the error list for this model stating that a duplicate of the original model could not be made
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalOutputModelPath + ")");
|
||||
return;
|
||||
}
|
||||
if (copyOfOriginal.write(requestReply->readAll()) == -1) {
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)");
|
||||
return;
|
||||
}
|
||||
|
||||
// close that file now that we are done writing to it
|
||||
copyOfOriginal.close();
|
||||
|
||||
// emit our signal to start the import of the model source copy
|
||||
emit modelLoaded();
|
||||
} else {
|
||||
// add an error to our list stating that the model could not be downloaded
|
||||
handleError("Failed to download " + _modelURL.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::bakeSourceCopy() {
|
||||
QFile modelFile(_originalOutputModelPath);
|
||||
if (!modelFile.open(QIODevice::ReadOnly)) {
|
||||
handleError("Error opening " + _originalOutputModelPath + " for reading");
|
||||
return;
|
||||
}
|
||||
hifi::ByteArray modelData = modelFile.readAll();
|
||||
|
||||
hfm::Model::Pointer bakedModel;
|
||||
std::vector<hifi::ByteArray> dracoMeshes;
|
||||
std::vector<std::vector<hifi::ByteArray>> dracoMaterialLists; // Material order for per-mesh material lookup used by dracoMeshes
|
||||
|
||||
{
|
||||
auto serializer = DependencyManager::get<ModelFormatRegistry>()->getSerializerForMediaType(modelData, _modelURL, "");
|
||||
if (!serializer) {
|
||||
handleError("Could not recognize file type of model file " + _originalOutputModelPath);
|
||||
return;
|
||||
}
|
||||
hifi::VariantHash serializerMapping = _mapping;
|
||||
serializerMapping["combineParts"] = true; // set true so that OBJSerializer reads material info from material library
|
||||
serializerMapping["deduplicateIndices"] = true; // Draco compression also deduplicates, but we might as well shave it off to save on some earlier processing (currently FBXSerializer only)
|
||||
hfm::Model::Pointer loadedModel = serializer->read(modelData, serializerMapping, _modelURL);
|
||||
|
||||
// Temporarily support copying the pre-parsed node from FBXSerializer, for better performance in FBXBaker
|
||||
// TODO: Pure HFM baking
|
||||
std::shared_ptr<FBXSerializer> fbxSerializer = std::dynamic_pointer_cast<FBXSerializer>(serializer);
|
||||
if (fbxSerializer) {
|
||||
qCDebug(model_baking) << "Parsing" << _modelURL;
|
||||
_rootNode = fbxSerializer->_rootNode;
|
||||
}
|
||||
|
||||
baker::Baker baker(loadedModel, serializerMapping, _mappingURL);
|
||||
auto config = baker.getConfiguration();
|
||||
// Enable compressed draco mesh generation
|
||||
config->getJobConfig("BuildDracoMesh")->setEnabled(true);
|
||||
// Do not permit potentially lossy modification of joint data meant for runtime
|
||||
((PrepareJointsConfig*)config->getJobConfig("PrepareJoints"))->passthrough = true;
|
||||
// The resources parsed from this job will not be used for now
|
||||
// TODO: Proper full baking of all materials for a model
|
||||
config->getJobConfig("ParseMaterialMapping")->setEnabled(false);
|
||||
|
||||
// Begin hfm baking
|
||||
baker.run();
|
||||
|
||||
bakedModel = baker.getHFMModel();
|
||||
dracoMeshes = baker.getDracoMeshes();
|
||||
dracoMaterialLists = baker.getDracoMaterialLists();
|
||||
}
|
||||
|
||||
// Populate _textureContentMap with path to content mappings, for quick lookup by URL
|
||||
for (auto materialIt = bakedModel->materials.cbegin(); materialIt != bakedModel->materials.cend(); materialIt++) {
|
||||
static const auto addTexture = [](QHash<hifi::ByteArray, hifi::ByteArray>& textureContentMap, const hfm::Texture& texture) {
|
||||
if (!textureContentMap.contains(texture.filename)) {
|
||||
// Content may be empty, unless the data is inlined
|
||||
textureContentMap[texture.filename] = texture.content;
|
||||
}
|
||||
};
|
||||
const hfm::Material& material = *materialIt;
|
||||
addTexture(_textureContentMap, material.normalTexture);
|
||||
addTexture(_textureContentMap, material.albedoTexture);
|
||||
addTexture(_textureContentMap, material.opacityTexture);
|
||||
addTexture(_textureContentMap, material.glossTexture);
|
||||
addTexture(_textureContentMap, material.roughnessTexture);
|
||||
addTexture(_textureContentMap, material.specularTexture);
|
||||
addTexture(_textureContentMap, material.metallicTexture);
|
||||
addTexture(_textureContentMap, material.emissiveTexture);
|
||||
addTexture(_textureContentMap, material.occlusionTexture);
|
||||
addTexture(_textureContentMap, material.scatteringTexture);
|
||||
addTexture(_textureContentMap, material.lightmapTexture);
|
||||
}
|
||||
|
||||
// Do format-specific baking
|
||||
bakeProcessedSource(bakedModel, dracoMeshes, dracoMaterialLists);
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Output FST file, copying over input mappings if available
|
||||
QString outputFSTFilename = !_mappingURL.isEmpty() ? _mappingURL.fileName() : _modelURL.fileName();
|
||||
auto extensionStart = outputFSTFilename.indexOf(".");
|
||||
if (extensionStart != -1) {
|
||||
outputFSTFilename.resize(extensionStart);
|
||||
}
|
||||
outputFSTFilename += ".baked.fst";
|
||||
QString outputFSTURL = _bakedOutputDir + "/" + outputFSTFilename;
|
||||
|
||||
auto outputMapping = _mapping;
|
||||
outputMapping[FST_VERSION_FIELD] = FST_VERSION;
|
||||
outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName();
|
||||
// All textures will be found in the same directory as the model
|
||||
outputMapping[TEXDIR_FIELD] = ".";
|
||||
hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping);
|
||||
|
||||
QFile fstOutputFile { outputFSTURL };
|
||||
if (!fstOutputFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Failed to open file '" + outputFSTURL + "' for writing");
|
||||
return;
|
||||
}
|
||||
if (fstOutputFile.write(fstOut) == -1) {
|
||||
handleError("Failed to write to file '" + outputFSTURL + "'");
|
||||
return;
|
||||
}
|
||||
_outputFiles.push_back(outputFSTURL);
|
||||
_outputMappingURL = outputFSTURL;
|
||||
|
||||
// check if we're already done with textures (in case we had none to re-write)
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
void ModelBaker::abort() {
|
||||
|
@ -74,176 +321,36 @@ void ModelBaker::abort() {
|
|||
}
|
||||
}
|
||||
|
||||
bool ModelBaker::compressMesh(HFMMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) {
|
||||
if (mesh.wasCompressed) {
|
||||
handleError("Cannot re-bake a file that contains compressed mesh");
|
||||
bool ModelBaker::buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList) {
|
||||
if (dracoMeshBytes.isEmpty()) {
|
||||
handleError("Failed to finalize the baking of a draco Geometry node");
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());
|
||||
|
||||
int64_t numTriangles{ 0 };
|
||||
for (auto& part : mesh.parts) {
|
||||
if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) {
|
||||
handleWarning("Found a mesh part with invalid index data, skipping");
|
||||
continue;
|
||||
}
|
||||
numTriangles += part.quadTrianglesIndices.size() / 3;
|
||||
numTriangles += part.triangleIndices.size() / 3;
|
||||
}
|
||||
|
||||
if (numTriangles == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
draco::TriangleSoupMeshBuilder meshBuilder;
|
||||
|
||||
meshBuilder.Start(numTriangles);
|
||||
|
||||
bool hasNormals{ mesh.normals.size() > 0 };
|
||||
bool hasColors{ mesh.colors.size() > 0 };
|
||||
bool hasTexCoords{ mesh.texCoords.size() > 0 };
|
||||
bool hasTexCoords1{ mesh.texCoords1.size() > 0 };
|
||||
bool hasPerFaceMaterials = (materialIDCallback) ? (mesh.parts.size() > 1 || materialIDCallback(0) != 0 ) : true;
|
||||
bool needsOriginalIndices{ hasDeformers };
|
||||
|
||||
int normalsAttributeID { -1 };
|
||||
int colorsAttributeID { -1 };
|
||||
int texCoordsAttributeID { -1 };
|
||||
int texCoords1AttributeID { -1 };
|
||||
int faceMaterialAttributeID { -1 };
|
||||
int originalIndexAttributeID { -1 };
|
||||
|
||||
const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION,
|
||||
3, draco::DT_FLOAT32);
|
||||
if (needsOriginalIndices) {
|
||||
originalIndexAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX,
|
||||
1, draco::DT_INT32);
|
||||
}
|
||||
|
||||
if (hasNormals) {
|
||||
normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasColors) {
|
||||
colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
texCoords1AttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasPerFaceMaterials) {
|
||||
faceMaterialAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID,
|
||||
1, draco::DT_UINT16);
|
||||
}
|
||||
|
||||
auto partIndex = 0;
|
||||
draco::FaceIndex face;
|
||||
uint16_t materialID;
|
||||
|
||||
for (auto& part : mesh.parts) {
|
||||
materialID = (materialIDCallback) ? materialIDCallback(partIndex) : partIndex;
|
||||
|
||||
auto addFace = [&](QVector<int>& indices, int index, draco::FaceIndex face) {
|
||||
int32_t idx0 = indices[index];
|
||||
int32_t idx1 = indices[index + 1];
|
||||
int32_t idx2 = indices[index + 2];
|
||||
|
||||
if (hasPerFaceMaterials) {
|
||||
meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID);
|
||||
}
|
||||
|
||||
meshBuilder.SetAttributeValuesForFace(positionAttributeID, face,
|
||||
&mesh.vertices[idx0], &mesh.vertices[idx1],
|
||||
&mesh.vertices[idx2]);
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face,
|
||||
&mesh.originalIndices[idx0],
|
||||
&mesh.originalIndices[idx1],
|
||||
&mesh.originalIndices[idx2]);
|
||||
}
|
||||
if (hasNormals) {
|
||||
meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face,
|
||||
&mesh.normals[idx0], &mesh.normals[idx1],
|
||||
&mesh.normals[idx2]);
|
||||
}
|
||||
if (hasColors) {
|
||||
meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face,
|
||||
&mesh.colors[idx0], &mesh.colors[idx1],
|
||||
&mesh.colors[idx2]);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face,
|
||||
&mesh.texCoords[idx0], &mesh.texCoords[idx1],
|
||||
&mesh.texCoords[idx2]);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face,
|
||||
&mesh.texCoords1[idx0], &mesh.texCoords1[idx1],
|
||||
&mesh.texCoords1[idx2]);
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) {
|
||||
addFace(part.quadTrianglesIndices, i, face++);
|
||||
}
|
||||
|
||||
for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) {
|
||||
addFace(part.triangleIndices, i, face++);
|
||||
}
|
||||
|
||||
partIndex++;
|
||||
}
|
||||
|
||||
auto dracoMesh = meshBuilder.Finalize();
|
||||
|
||||
if (!dracoMesh) {
|
||||
handleWarning("Failed to finalize the baking of a draco Geometry node");
|
||||
return false;
|
||||
}
|
||||
|
||||
// we need to modify unique attribute IDs for custom attributes
|
||||
// so the attributes are easily retrievable on the other side
|
||||
if (hasPerFaceMaterials) {
|
||||
dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID);
|
||||
}
|
||||
|
||||
if (hasTexCoords1) {
|
||||
dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1);
|
||||
}
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX);
|
||||
}
|
||||
|
||||
draco::Encoder encoder;
|
||||
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10);
|
||||
encoder.SetSpeedOptions(0, 5);
|
||||
|
||||
draco::EncoderBuffer buffer;
|
||||
encoder.EncodeMeshToBuffer(*dracoMesh, &buffer);
|
||||
|
||||
FBXNode dracoNode;
|
||||
dracoNode.name = "DracoMesh";
|
||||
auto value = QVariant::fromValue(QByteArray(buffer.data(), (int)buffer.size()));
|
||||
dracoNode.properties.append(value);
|
||||
dracoNode.properties.append(QVariant::fromValue(dracoMeshBytes));
|
||||
// Additional draco mesh node information
|
||||
{
|
||||
FBXNode fbxVersionNode;
|
||||
fbxVersionNode.name = "FBXDracoMeshVersion";
|
||||
fbxVersionNode.properties.append(FBX_DRACO_MESH_VERSION);
|
||||
dracoNode.children.append(fbxVersionNode);
|
||||
|
||||
FBXNode dracoVersionNode;
|
||||
dracoVersionNode.name = "DracoMeshVersion";
|
||||
dracoVersionNode.properties.append(DRACO_MESH_VERSION);
|
||||
dracoNode.children.append(dracoVersionNode);
|
||||
|
||||
FBXNode materialListNode;
|
||||
materialListNode.name = "MaterialList";
|
||||
for (const hifi::ByteArray& materialID : dracoMaterialList) {
|
||||
materialListNode.properties.append(materialID);
|
||||
}
|
||||
dracoNode.children.append(materialListNode);
|
||||
}
|
||||
|
||||
dracoMeshNode = dracoNode;
|
||||
// Mesh compression successful return true
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -274,45 +381,42 @@ QString ModelBaker::compressTexture(QString modelTextureFileName, image::Texture
|
|||
if (!modelTextureFileInfo.filePath().isEmpty()) {
|
||||
textureContent = _textureContentMap.value(modelTextureFileName.toLocal8Bit());
|
||||
}
|
||||
auto urlToTexture = getTextureURL(modelTextureFileInfo, modelTextureFileName, !textureContent.isNull());
|
||||
auto urlToTexture = getTextureURL(modelTextureFileInfo, !textureContent.isNull());
|
||||
|
||||
QString baseTextureFileName;
|
||||
if (_remappedTexturePaths.contains(urlToTexture)) {
|
||||
baseTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||
} else {
|
||||
TextureKey textureKey { urlToTexture, textureType };
|
||||
auto bakingTextureIt = _bakingTextures.find(textureKey);
|
||||
if (bakingTextureIt == _bakingTextures.cend()) {
|
||||
// construct the new baked texture file name and file path
|
||||
// ensuring that the baked texture will have a unique name
|
||||
// even if there was another texture with the same name at a different path
|
||||
baseTextureFileName = createBaseTextureFileName(modelTextureFileInfo);
|
||||
_remappedTexturePaths[urlToTexture] = baseTextureFileName;
|
||||
}
|
||||
QString baseTextureFileName = _textureFileNamer.createBaseTextureFileName(modelTextureFileInfo, textureType);
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||
<< "to" << baseTextureFileName;
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX
|
||||
};
|
||||
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX
|
||||
};
|
||||
textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
if (!_bakingTextures.contains(urlToTexture)) {
|
||||
_outputFiles.push_back(bakedTextureFilePath);
|
||||
|
||||
// bake this texture asynchronously
|
||||
bakeTexture(urlToTexture, textureType, _bakedOutputDir, baseTextureFileName, textureContent);
|
||||
bakeTexture(textureKey, _bakedOutputDir, baseTextureFileName, textureContent);
|
||||
} else {
|
||||
// Fetch existing texture meta name
|
||||
textureChild = (*bakingTextureIt)->getBaseFilename() + BAKED_META_TEXTURE_SUFFIX;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||
<< "to" << textureChild;
|
||||
|
||||
return textureChild;
|
||||
}
|
||||
|
||||
void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) {
|
||||
|
||||
void ModelBaker::bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) {
|
||||
// start a bake for this texture and add it to our list to keep track of
|
||||
QSharedPointer<TextureBaker> bakingTexture{
|
||||
new TextureBaker(textureURL, textureType, outputDir, "../", bakedFilename, textureContent),
|
||||
new TextureBaker(textureKey.first, textureKey.second, outputDir, "../", bakedFilename, textureContent),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
|
||||
|
@ -321,7 +425,7 @@ void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type t
|
|||
connect(bakingTexture.data(), &TextureBaker::aborted, this, &ModelBaker::handleAbortedTexture);
|
||||
|
||||
// keep a shared pointer to the baking texture
|
||||
_bakingTextures.insert(textureURL, bakingTexture);
|
||||
_bakingTextures.insert(textureKey, bakingTexture);
|
||||
|
||||
// start baking the texture on one of our available worker threads
|
||||
bakingTexture->moveToThread(_textureThreadGetter());
|
||||
|
@ -373,7 +477,7 @@ void ModelBaker::handleBakedTexture() {
|
|||
|
||||
|
||||
// now that this texture has been baked and handled, we can remove that TextureBaker from our hash
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
checkIfTexturesFinished();
|
||||
} else {
|
||||
|
@ -384,7 +488,7 @@ void ModelBaker::handleBakedTexture() {
|
|||
_pendingErrorEmission = true;
|
||||
|
||||
// now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
|
@ -397,7 +501,7 @@ void ModelBaker::handleBakedTexture() {
|
|||
// we have errors to attend to, so we don't do extra processing for this texture
|
||||
// but we do need to remove that TextureBaker from our list
|
||||
// and then check if we're done with all textures
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
@ -411,7 +515,7 @@ void ModelBaker::handleAbortedTexture() {
|
|||
qDebug() << "Texture aborted: " << bakedTexture->getTextureURL();
|
||||
|
||||
if (bakedTexture) {
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
}
|
||||
|
||||
// since a texture we were baking aborted, our status is also aborted
|
||||
|
@ -425,14 +529,11 @@ void ModelBaker::handleAbortedTexture() {
|
|||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded) {
|
||||
QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded) {
|
||||
QUrl urlToTexture;
|
||||
|
||||
// use QFileInfo to easily split up the existing texture filename into its components
|
||||
auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/"));
|
||||
|
||||
if (isEmbedded) {
|
||||
urlToTexture = _modelURL.toString() + "/" + apparentRelativePath.filePath();
|
||||
urlToTexture = _modelURL.toString() + "/" + textureFileInfo.filePath();
|
||||
} else {
|
||||
if (textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// set the texture URL to the local texture that we have confirmed exists
|
||||
|
@ -442,14 +543,14 @@ QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativ
|
|||
|
||||
// this is a relative file path which will require different handling
|
||||
// depending on the location of the original model
|
||||
if (_modelURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) {
|
||||
if (_modelURL.isLocalFile() && textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// the absolute path we ran into for the texture in the model exists on this machine
|
||||
// so use that file
|
||||
urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath());
|
||||
urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath());
|
||||
} else {
|
||||
// we didn't find the texture on this machine at the absolute path
|
||||
// so assume that it is right beside the model to match the behaviour of interface
|
||||
urlToTexture = _modelURL.resolved(apparentRelativePath.fileName());
|
||||
urlToTexture = _modelURL.resolved(textureFileInfo.fileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -494,25 +595,6 @@ void ModelBaker::checkIfTexturesFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
QString ModelBaker::createBaseTextureFileName(const QFileInfo& textureFileInfo) {
|
||||
// first make sure we have a unique base name for this texture
|
||||
// in case another texture referenced by this model has the same base name
|
||||
auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()];
|
||||
|
||||
QString baseTextureFileName{ textureFileInfo.completeBaseName() };
|
||||
|
||||
if (nameMatches > 0) {
|
||||
// there are already nameMatches texture with this name
|
||||
// append - and that number to our baked texture file name so that it is unique
|
||||
baseTextureFileName += "-" + QString::number(nameMatches);
|
||||
}
|
||||
|
||||
// increment the number of name matches
|
||||
++nameMatches;
|
||||
|
||||
return baseTextureFileName;
|
||||
}
|
||||
|
||||
void ModelBaker::setWasAborted(bool wasAborted) {
|
||||
if (wasAborted != _wasAborted.load()) {
|
||||
Baker::setWasAborted(wasAborted);
|
||||
|
@ -588,31 +670,25 @@ void ModelBaker::embedTextureMetaData() {
|
|||
}
|
||||
|
||||
void ModelBaker::exportScene() {
|
||||
// save the relative path to this FBX inside our passed output folder
|
||||
auto fileName = _modelURL.fileName();
|
||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||
auto bakedFilename = baseName + BAKED_FBX_EXTENSION;
|
||||
|
||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||
|
||||
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
||||
|
||||
QFile bakedFile(_bakedModelFilePath);
|
||||
QString bakedModelURL = _bakedModelURL.toString();
|
||||
QFile bakedFile(bakedModelURL);
|
||||
|
||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
||||
handleError("Error opening " + bakedModelURL + " for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
bakedFile.write(fbxData);
|
||||
|
||||
_outputFiles.push_back(_bakedModelFilePath);
|
||||
_outputFiles.push_back(bakedModelURL);
|
||||
|
||||
#ifdef HIFI_DUMP_FBX
|
||||
{
|
||||
FBXToJSON fbxToJSON;
|
||||
fbxToJSON << _rootNode;
|
||||
QFileInfo modelFile(_bakedModelFilePath);
|
||||
QFileInfo modelFile(_bakedModelURL.toString());
|
||||
QString outFilename(modelFile.dir().absolutePath() + "/" + modelFile.completeBaseName() + "_FBX.json");
|
||||
QFile jsonFile(outFilename);
|
||||
if (jsonFile.open(QIODevice::WriteOnly)) {
|
||||
|
@ -622,5 +698,5 @@ void ModelBaker::exportScene() {
|
|||
}
|
||||
#endif
|
||||
|
||||
qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath;
|
||||
qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << bakedModelURL;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
#include "baking/TextureFileNamer.h"
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
|
@ -30,57 +31,84 @@
|
|||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
using GetMaterialIDCallback = std::function <int(int)>;
|
||||
|
||||
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
||||
static const QString FST_EXTENSION { ".fst" };
|
||||
static const QString BAKED_FST_EXTENSION { ".baked.fst" };
|
||||
static const QString FBX_EXTENSION { ".fbx" };
|
||||
static const QString BAKED_FBX_EXTENSION { ".baked.fbx" };
|
||||
static const QString OBJ_EXTENSION { ".obj" };
|
||||
static const QString GLTF_EXTENSION { ".gltf" };
|
||||
|
||||
class ModelBaker : public Baker {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "");
|
||||
virtual ~ModelBaker();
|
||||
using TextureKey = QPair<QUrl, image::TextureUsage::Type>;
|
||||
|
||||
bool compressMesh(HFMMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr);
|
||||
ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
void setOutputURLSuffix(const QUrl& urlSuffix);
|
||||
void setMappingURL(const QUrl& mappingURL);
|
||||
void setMapping(const hifi::VariantHash& mapping);
|
||||
|
||||
void initializeOutputDirs();
|
||||
|
||||
bool buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList);
|
||||
QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE);
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
QUrl getModelURL() const { return _modelURL; }
|
||||
QString getBakedModelFilePath() const { return _bakedModelFilePath; }
|
||||
virtual QUrl getFullOutputMappingURL() const;
|
||||
QUrl getBakedModelURL() const { return _bakedModelURL; }
|
||||
|
||||
signals:
|
||||
void modelLoaded();
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
virtual void abort() override;
|
||||
|
||||
protected:
|
||||
void saveSourceModel();
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) = 0;
|
||||
void checkIfTexturesFinished();
|
||||
void texturesFinished();
|
||||
void embedTextureMetaData();
|
||||
void exportScene();
|
||||
|
||||
|
||||
FBXNode _rootNode;
|
||||
QHash<QByteArray, QByteArray> _textureContentMap;
|
||||
QUrl _modelURL;
|
||||
QUrl _outputURLSuffix;
|
||||
QUrl _mappingURL;
|
||||
hifi::VariantHash _mapping;
|
||||
QString _bakedOutputDir;
|
||||
QString _originalOutputDir;
|
||||
QString _bakedModelFilePath;
|
||||
QDir _modelTempDir;
|
||||
QString _originalModelFilePath;
|
||||
TextureBakerThreadGetter _textureThreadGetter;
|
||||
QString _originalOutputModelPath;
|
||||
QString _outputMappingURL;
|
||||
QUrl _bakedModelURL;
|
||||
|
||||
protected slots:
|
||||
void handleModelNetworkReply();
|
||||
virtual void bakeSourceCopy();
|
||||
|
||||
private slots:
|
||||
void handleBakedTexture();
|
||||
void handleAbortedTexture();
|
||||
|
||||
private:
|
||||
QString createBaseTextureFileName(const QFileInfo & textureFileInfo);
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false);
|
||||
void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir,
|
||||
const QString & bakedFilename, const QByteArray & textureContent);
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded = false);
|
||||
void bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent);
|
||||
QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL);
|
||||
|
||||
TextureBakerThreadGetter _textureThreadGetter;
|
||||
QMultiHash<QUrl, QSharedPointer<TextureBaker>> _bakingTextures;
|
||||
|
||||
QMultiHash<TextureKey, QSharedPointer<TextureBaker>> _bakingTextures;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
QHash<QUrl, QString> _remappedTexturePaths;
|
||||
bool _pendingErrorEmission{ false };
|
||||
bool _pendingErrorEmission { false };
|
||||
|
||||
bool _hasBeenBaked { false };
|
||||
|
||||
TextureFileNamer _textureFileNamer;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelBaker_h
|
||||
|
|
|
@ -35,157 +35,51 @@ const QByteArray CONNECTIONS_NODE_PROPERTY = "OO";
|
|||
const QByteArray CONNECTIONS_NODE_PROPERTY_1 = "OP";
|
||||
const QByteArray MESH = "Mesh";
|
||||
|
||||
void OBJBaker::bake() {
|
||||
qDebug() << "OBJBaker" << _modelURL << "bake starting";
|
||||
|
||||
// trigger bakeOBJ once OBJ is loaded
|
||||
connect(this, &OBJBaker::OBJLoaded, this, &OBJBaker::bakeOBJ);
|
||||
|
||||
// make a local copy of the OBJ
|
||||
loadOBJ();
|
||||
}
|
||||
|
||||
void OBJBaker::loadOBJ() {
|
||||
if (!QDir().mkpath(_bakedOutputDir)) {
|
||||
handleError("Failed to create baked OBJ output folder " + _bakedOutputDir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QDir().mkpath(_originalOutputDir)) {
|
||||
handleError("Failed to create original OBJ output folder " + _originalOutputDir);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the OBJ is local or it needs to be downloaded
|
||||
if (_modelURL.isLocalFile()) {
|
||||
// loading the local OBJ
|
||||
QFile localOBJ { _modelURL.toLocalFile() };
|
||||
|
||||
qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath;
|
||||
|
||||
if (!localOBJ.exists()) {
|
||||
handleError("Could not find " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// make a copy in the output folder
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName();
|
||||
localOBJ.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
localOBJ.copy(_originalModelFilePath);
|
||||
|
||||
// local OBJ is loaded emit signal to trigger its baking
|
||||
emit OBJLoaded();
|
||||
} else {
|
||||
// OBJ is remote, start download
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
// setup the request to follow re-directs and always hit the network
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
networkRequest.setUrl(_modelURL);
|
||||
|
||||
qCDebug(model_baking) << "Downloading" << _modelURL;
|
||||
auto networkReply = networkAccessManager.get(networkRequest);
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, this, &OBJBaker::handleOBJNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
void OBJBaker::handleOBJNetworkReply() {
|
||||
auto requestReply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
qCDebug(model_baking) << "Downloaded" << _modelURL;
|
||||
|
||||
// grab the contents of the reply and make a copy in the output folder
|
||||
QFile copyOfOriginal(_originalModelFilePath);
|
||||
|
||||
qDebug(model_baking) << "Writing copy of original obj to" << _originalModelFilePath << copyOfOriginal.fileName();
|
||||
|
||||
if (!copyOfOriginal.open(QIODevice::WriteOnly)) {
|
||||
// add an error to the error list for this obj stating that a duplicate of the original obj could not be made
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")");
|
||||
return;
|
||||
}
|
||||
if (copyOfOriginal.write(requestReply->readAll()) == -1) {
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)");
|
||||
return;
|
||||
}
|
||||
|
||||
// close that file now that we are done writing to it
|
||||
copyOfOriginal.close();
|
||||
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
// remote OBJ is loaded emit signal to trigger its baking
|
||||
emit OBJLoaded();
|
||||
} else {
|
||||
// add an error to our list stating that the OBJ could not be downloaded
|
||||
handleError("Failed to download " + _modelURL.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void OBJBaker::bakeOBJ() {
|
||||
// Read the OBJ file
|
||||
QFile objFile(_originalModelFilePath);
|
||||
if (!objFile.open(QIODevice::ReadOnly)) {
|
||||
handleError("Error opening " + _originalModelFilePath + " for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray objData = objFile.readAll();
|
||||
|
||||
OBJSerializer serializer;
|
||||
QVariantHash mapping;
|
||||
mapping["combineParts"] = true; // set true so that OBJSerializer reads material info from material library
|
||||
auto geometry = serializer.read(objData, mapping, _modelURL);
|
||||
|
||||
void OBJBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) {
|
||||
// Write OBJ Data as FBX tree nodes
|
||||
createFBXNodeTree(_rootNode, *geometry);
|
||||
|
||||
checkIfTexturesFinished();
|
||||
createFBXNodeTree(_rootNode, hfmModel, dracoMeshes[0]);
|
||||
}
|
||||
|
||||
void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) {
|
||||
void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh) {
|
||||
// Make all generated nodes children of rootNode
|
||||
rootNode.children = { FBXNode(), FBXNode(), FBXNode() };
|
||||
FBXNode& globalSettingsNode = rootNode.children[0];
|
||||
FBXNode& objectNode = rootNode.children[1];
|
||||
FBXNode& connectionsNode = rootNode.children[2];
|
||||
|
||||
// Generating FBX Header Node
|
||||
FBXNode headerNode;
|
||||
headerNode.name = FBX_HEADER_EXTENSION;
|
||||
|
||||
// Generating global settings node
|
||||
// Required for Unit Scale Factor
|
||||
FBXNode globalSettingsNode;
|
||||
globalSettingsNode.name = GLOBAL_SETTINGS_NODE_NAME;
|
||||
|
||||
// Setting the tree hierarchy: GlobalSettings -> Properties70 -> P -> Properties
|
||||
FBXNode properties70Node;
|
||||
properties70Node.name = PROPERTIES70_NODE_NAME;
|
||||
|
||||
FBXNode pNode;
|
||||
{
|
||||
pNode.name = P_NODE_NAME;
|
||||
pNode.properties.append({
|
||||
"UnitScaleFactor", "double", "Number", "",
|
||||
UNIT_SCALE_FACTOR
|
||||
});
|
||||
globalSettingsNode.children.push_back(FBXNode());
|
||||
FBXNode& properties70Node = globalSettingsNode.children.back();
|
||||
properties70Node.name = PROPERTIES70_NODE_NAME;
|
||||
|
||||
FBXNode pNode;
|
||||
{
|
||||
pNode.name = P_NODE_NAME;
|
||||
pNode.properties.append({
|
||||
"UnitScaleFactor", "double", "Number", "",
|
||||
UNIT_SCALE_FACTOR
|
||||
});
|
||||
}
|
||||
properties70Node.children = { pNode };
|
||||
|
||||
}
|
||||
|
||||
properties70Node.children = { pNode };
|
||||
globalSettingsNode.children = { properties70Node };
|
||||
|
||||
// Generating Object node
|
||||
FBXNode objectNode;
|
||||
objectNode.name = OBJECTS_NODE_NAME;
|
||||
objectNode.children = { FBXNode(), FBXNode() };
|
||||
FBXNode& geometryNode = objectNode.children[0];
|
||||
FBXNode& modelNode = objectNode.children[1];
|
||||
|
||||
// Generating Object node's child - Geometry node
|
||||
FBXNode geometryNode;
|
||||
// Generating Object node's child - Geometry node
|
||||
geometryNode.name = GEOMETRY_NODE_NAME;
|
||||
NodeID geometryID;
|
||||
{
|
||||
|
@ -196,15 +90,8 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) {
|
|||
MESH
|
||||
};
|
||||
}
|
||||
|
||||
// Compress the mesh information and store in dracoNode
|
||||
bool hasDeformers = false; // No concept of deformers for an OBJ
|
||||
FBXNode dracoNode;
|
||||
compressMesh(hfmModel.meshes[0], hasDeformers, dracoNode);
|
||||
geometryNode.children.append(dracoNode);
|
||||
|
||||
|
||||
// Generating Object node's child - Model node
|
||||
FBXNode modelNode;
|
||||
modelNode.name = MODEL_NODE_NAME;
|
||||
NodeID modelID;
|
||||
{
|
||||
|
@ -212,16 +99,14 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) {
|
|||
modelNode.properties = { modelID, MODEL_NODE_NAME, MESH };
|
||||
}
|
||||
|
||||
objectNode.children = { geometryNode, modelNode };
|
||||
|
||||
// Generating Objects node's child - Material node
|
||||
auto& meshParts = hfmModel.meshes[0].parts;
|
||||
auto& meshParts = hfmModel->meshes[0].parts;
|
||||
for (auto& meshPart : meshParts) {
|
||||
FBXNode materialNode;
|
||||
materialNode.name = MATERIAL_NODE_NAME;
|
||||
if (hfmModel.materials.size() == 1) {
|
||||
if (hfmModel->materials.size() == 1) {
|
||||
// case when no material information is provided, OBJSerializer considers it as a single default material
|
||||
for (auto& materialID : hfmModel.materials.keys()) {
|
||||
for (auto& materialID : hfmModel->materials.keys()) {
|
||||
setMaterialNodeProperties(materialNode, materialID, hfmModel);
|
||||
}
|
||||
} else {
|
||||
|
@ -231,12 +116,28 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) {
|
|||
objectNode.children.append(materialNode);
|
||||
}
|
||||
|
||||
// Store the draco node containing the compressed mesh information, along with the per-meshPart material IDs the draco node references
|
||||
// Because we redefine the material IDs when initializing the material nodes above, we pass that in for the material list
|
||||
// The nth mesh part gets the nth material
|
||||
if (!dracoMesh.isEmpty()) {
|
||||
std::vector<hifi::ByteArray> newMaterialList;
|
||||
newMaterialList.reserve(_materialIDs.size());
|
||||
for (auto materialID : _materialIDs) {
|
||||
newMaterialList.push_back(hifi::ByteArray(std::to_string((int)materialID).c_str()));
|
||||
}
|
||||
FBXNode dracoNode;
|
||||
buildDracoMeshNode(dracoNode, dracoMesh, newMaterialList);
|
||||
geometryNode.children.append(dracoNode);
|
||||
} else {
|
||||
handleWarning("Baked mesh for OBJ model '" + _modelURL.toString() + "' is empty");
|
||||
}
|
||||
|
||||
// Generating Texture Node
|
||||
// iterate through mesh parts and process the associated textures
|
||||
auto size = meshParts.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
QString material = meshParts[i].materialID;
|
||||
HFMMaterial currentMaterial = hfmModel.materials[material];
|
||||
HFMMaterial currentMaterial = hfmModel->materials[material];
|
||||
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
||||
auto textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(textureID, i);
|
||||
|
@ -281,14 +182,15 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) {
|
|||
}
|
||||
|
||||
// Generating Connections node
|
||||
FBXNode connectionsNode;
|
||||
connectionsNode.name = CONNECTIONS_NODE_NAME;
|
||||
|
||||
// connect Geometry to Model
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, geometryID, modelID };
|
||||
connectionsNode.children = { cNode };
|
||||
// connect Geometry to Model
|
||||
{
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, geometryID, modelID };
|
||||
connectionsNode.children.push_back(cNode);
|
||||
}
|
||||
|
||||
// connect all materials to model
|
||||
for (auto& materialID : _materialIDs) {
|
||||
|
@ -320,18 +222,15 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel) {
|
|||
};
|
||||
connectionsNode.children.append(cDiffuseNode);
|
||||
}
|
||||
|
||||
// Make all generated nodes children of rootNode
|
||||
rootNode.children = { globalSettingsNode, objectNode, connectionsNode };
|
||||
}
|
||||
|
||||
// Set properties for material nodes
|
||||
void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, HFMModel& hfmModel) {
|
||||
void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel) {
|
||||
auto materialID = nextNodeID();
|
||||
_materialIDs.push_back(materialID);
|
||||
materialNode.properties = { materialID, material, MESH };
|
||||
|
||||
HFMMaterial currentMaterial = hfmModel.materials[material];
|
||||
HFMMaterial currentMaterial = hfmModel->materials[material];
|
||||
|
||||
// Setting the hierarchy: Material -> Properties70 -> P -> Properties
|
||||
FBXNode properties70Node;
|
||||
|
|
|
@ -27,20 +27,12 @@ class OBJBaker : public ModelBaker {
|
|||
public:
|
||||
using ModelBaker::ModelBaker;
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
|
||||
signals:
|
||||
void OBJLoaded();
|
||||
|
||||
private slots:
|
||||
void bakeOBJ();
|
||||
void handleOBJNetworkReply();
|
||||
protected:
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) override;
|
||||
|
||||
private:
|
||||
void loadOBJ();
|
||||
void createFBXNodeTree(FBXNode& rootNode, HFMModel& hfmModel);
|
||||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, HFMModel& hfmModel);
|
||||
void createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& hfmModel, const hifi::ByteArray& dracoMesh);
|
||||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel);
|
||||
NodeID nextNodeID() { return _nodeID++; }
|
||||
|
||||
|
||||
|
|
|
@ -47,6 +47,14 @@ TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type tex
|
|||
auto originalFilename = textureURL.fileName();
|
||||
_baseFilename = originalFilename.left(originalFilename.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
auto textureFilename = _textureURL.fileName();
|
||||
QString originalExtension;
|
||||
int extensionStart = textureFilename.indexOf(".");
|
||||
if (extensionStart != -1) {
|
||||
originalExtension = textureFilename.mid(extensionStart);
|
||||
}
|
||||
_originalCopyFilePath = _outputDirectory.absoluteFilePath(_baseFilename + originalExtension);
|
||||
}
|
||||
|
||||
void TextureBaker::bake() {
|
||||
|
@ -128,7 +136,9 @@ void TextureBaker::processTexture() {
|
|||
|
||||
TextureMeta meta;
|
||||
|
||||
auto originalCopyFilePath = _outputDirectory.absoluteFilePath(_textureURL.fileName());
|
||||
QString originalCopyFilePath = _originalCopyFilePath.toString();
|
||||
|
||||
// Copy the original file into the baked output directory if it doesn't exist yet
|
||||
{
|
||||
QFile file { originalCopyFilePath };
|
||||
if (!file.open(QIODevice::WriteOnly) || file.write(_originalTexture) == -1) {
|
||||
|
@ -138,9 +148,10 @@ void TextureBaker::processTexture() {
|
|||
// IMPORTANT: _originalTexture is empty past this point
|
||||
_originalTexture.clear();
|
||||
_outputFiles.push_back(originalCopyFilePath);
|
||||
meta.original = _metaTexturePathPrefix + _textureURL.fileName();
|
||||
meta.original = _metaTexturePathPrefix + _originalCopyFilePath.fileName();
|
||||
}
|
||||
|
||||
// Load the copy of the original file from the baked output directory. New images will be created using the original as the source data.
|
||||
auto buffer = std::static_pointer_cast<QIODevice>(std::make_shared<QFile>(originalCopyFilePath));
|
||||
if (!buffer->open(QIODevice::ReadOnly)) {
|
||||
handleError("Could not open original file at " + originalCopyFilePath);
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include "Baker.h"
|
||||
|
||||
#include <material-networking/MaterialCache.h>
|
||||
|
||||
extern const QString BAKED_TEXTURE_KTX_EXT;
|
||||
extern const QString BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
|
@ -37,12 +39,18 @@ public:
|
|||
|
||||
QUrl getTextureURL() const { return _textureURL; }
|
||||
|
||||
QString getBaseFilename() const { return _baseFilename; }
|
||||
|
||||
QString getMetaTextureFileName() const { return _metaTextureFileName; }
|
||||
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
static void setCompressionEnabled(bool enabled) { _compressionEnabled = enabled; }
|
||||
|
||||
void setMapChannel(graphics::Material::MapChannel mapChannel) { _mapChannel = mapChannel; }
|
||||
graphics::Material::MapChannel getMapChannel() const { return _mapChannel; }
|
||||
image::TextureUsage::Type getTextureType() const { return _textureType; }
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
virtual void abort() override;
|
||||
|
@ -60,11 +68,14 @@ private:
|
|||
QUrl _textureURL;
|
||||
QByteArray _originalTexture;
|
||||
image::TextureUsage::Type _textureType;
|
||||
graphics::Material::MapChannel _mapChannel;
|
||||
bool _mapChannelSet { false };
|
||||
|
||||
QString _baseFilename;
|
||||
QDir _outputDirectory;
|
||||
QString _metaTextureFileName;
|
||||
QString _metaTexturePathPrefix;
|
||||
QUrl _originalCopyFilePath;
|
||||
|
||||
std::atomic<bool> _abortProcessing { false };
|
||||
|
||||
|
|
83
libraries/baking/src/baking/BakerLibrary.cpp
Normal file
83
libraries/baking/src/baking/BakerLibrary.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// BakerLibrary.cpp
|
||||
// libraries/baking/src/baking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/02/14.
|
||||
// Copyright 2019 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 "BakerLibrary.h"
|
||||
|
||||
#include "FSTBaker.h"
|
||||
#include "../FBXBaker.h"
|
||||
#include "../OBJBaker.h"
|
||||
|
||||
// Check if the file pointed to by this URL is a bakeable model, by comparing extensions
|
||||
QUrl getBakeableModelURL(const QUrl& url) {
|
||||
static const std::vector<QString> extensionsToBake = {
|
||||
FST_EXTENSION,
|
||||
BAKED_FST_EXTENSION,
|
||||
FBX_EXTENSION,
|
||||
BAKED_FBX_EXTENSION,
|
||||
OBJ_EXTENSION,
|
||||
GLTF_EXTENSION
|
||||
};
|
||||
|
||||
QUrl cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
QString cleanURLString = cleanURL.fileName();
|
||||
for (auto& extension : extensionsToBake) {
|
||||
if (cleanURLString.endsWith(extension, Qt::CaseInsensitive)) {
|
||||
return cleanURL;
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << "Unknown model type: " << url.fileName();
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
bool isModelBaked(const QUrl& bakeableModelURL) {
|
||||
auto modelString = bakeableModelURL.toString();
|
||||
auto beforeModelExtension = modelString;
|
||||
beforeModelExtension.resize(modelString.lastIndexOf('.'));
|
||||
return beforeModelExtension.endsWith(".baked");
|
||||
}
|
||||
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath) {
|
||||
auto filename = bakeableModelURL.fileName();
|
||||
|
||||
// Output in a sub-folder with the name of the model, potentially suffixed by a number to make it unique
|
||||
auto baseName = filename.left(filename.lastIndexOf('.')).left(filename.lastIndexOf(".baked"));
|
||||
auto subDirName = "/" + baseName;
|
||||
int i = 1;
|
||||
while (QDir(contentOutputPath + subDirName).exists()) {
|
||||
subDirName = "/" + baseName + "-" + QString::number(i++);
|
||||
}
|
||||
|
||||
QString bakedOutputDirectory = contentOutputPath + subDirName + "/baked";
|
||||
QString originalOutputDirectory = contentOutputPath + subDirName + "/original";
|
||||
|
||||
return getModelBakerWithOutputDirectories(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
}
|
||||
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) {
|
||||
auto filename = bakeableModelURL.fileName();
|
||||
|
||||
std::unique_ptr<ModelBaker> baker;
|
||||
|
||||
if (filename.endsWith(FST_EXTENSION, Qt::CaseInsensitive)) {
|
||||
baker = std::make_unique<FSTBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive));
|
||||
} else if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive)) {
|
||||
baker = std::make_unique<FBXBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive));
|
||||
} else if (filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) {
|
||||
baker = std::make_unique<OBJBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
//} else if (filename.endsWith(GLTF_EXTENSION, Qt::CaseInsensitive)) {
|
||||
//baker = std::make_unique<GLTFBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
} else {
|
||||
qDebug() << "Could not create ModelBaker for url" << bakeableModelURL;
|
||||
}
|
||||
|
||||
return baker;
|
||||
}
|
31
libraries/baking/src/baking/BakerLibrary.h
Normal file
31
libraries/baking/src/baking/BakerLibrary.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// ModelBaker.h
|
||||
// libraries/baking/src/baking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/02/14.
|
||||
// Copyright 2019 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_BakerLibrary_h
|
||||
#define hifi_BakerLibrary_h
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#include "../ModelBaker.h"
|
||||
|
||||
// Returns either the given model URL if valid, or an empty URL
|
||||
QUrl getBakeableModelURL(const QUrl& url);
|
||||
|
||||
bool isModelBaked(const QUrl& bakeableModelURL);
|
||||
|
||||
// Assuming the URL is valid, gets the appropriate baker for the given URL, and creates the base directory where the baker's output will later be stored
|
||||
// Returns an empty pointer if a baker could not be created
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath);
|
||||
|
||||
// Similar to getModelBaker, but gives control over where the output folders will be
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory);
|
||||
|
||||
#endif // hifi_BakerLibrary_h
|
128
libraries/baking/src/baking/FSTBaker.cpp
Normal file
128
libraries/baking/src/baking/FSTBaker.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
//
|
||||
// FSTBaker.cpp
|
||||
// libraries/baking/src/baking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/03/06.
|
||||
// Copyright 2019 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 "FSTBaker.h"
|
||||
|
||||
#include <PathUtils.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include "BakerLibrary.h"
|
||||
|
||||
#include <FSTReader.h>
|
||||
|
||||
FSTBaker::FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputMappingURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
if (hasBeenBaked) {
|
||||
// Look for the original model file one directory higher. Perhaps this is an oven output directory.
|
||||
QUrl originalRelativePath = QUrl("../original/" + inputMappingURL.fileName().replace(BAKED_FST_EXTENSION, FST_EXTENSION));
|
||||
QUrl newInputMappingURL = inputMappingURL.adjusted(QUrl::RemoveFilename).resolved(originalRelativePath);
|
||||
_modelURL = newInputMappingURL;
|
||||
}
|
||||
_mappingURL = _modelURL;
|
||||
|
||||
{
|
||||
// Unused, but defined for consistency
|
||||
auto bakedFilename = _modelURL.fileName();
|
||||
bakedFilename.replace(FST_EXTENSION, BAKED_FST_EXTENSION);
|
||||
_bakedModelURL = _bakedOutputDir + "/" + bakedFilename;
|
||||
}
|
||||
}
|
||||
|
||||
QUrl FSTBaker::getFullOutputMappingURL() const {
|
||||
if (_modelBaker) {
|
||||
return _modelBaker->getFullOutputMappingURL();
|
||||
}
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
void FSTBaker::bakeSourceCopy() {
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile fstFile(_originalOutputModelPath);
|
||||
if (!fstFile.open(QIODevice::ReadOnly)) {
|
||||
handleError("Error opening " + _originalOutputModelPath + " for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
hifi::ByteArray fstData = fstFile.readAll();
|
||||
_mapping = FSTReader::readMapping(fstData);
|
||||
|
||||
auto filenameField = _mapping[FILENAME_FIELD].toString();
|
||||
if (filenameField.isEmpty()) {
|
||||
handleError("The '" + FILENAME_FIELD + "' property in the FST file '" + _originalOutputModelPath + "' could not be found");
|
||||
return;
|
||||
}
|
||||
auto modelURL = _mappingURL.adjusted(QUrl::RemoveFilename).resolved(filenameField);
|
||||
auto bakeableModelURL = getBakeableModelURL(modelURL);
|
||||
if (bakeableModelURL.isEmpty()) {
|
||||
handleError("The '" + FILENAME_FIELD + "' property in the FST file '" + _originalOutputModelPath + "' could not be resolved to a valid bakeable model url");
|
||||
return;
|
||||
}
|
||||
|
||||
auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _textureThreadGetter, _bakedOutputDir, _originalOutputDir);
|
||||
_modelBaker = std::unique_ptr<ModelBaker>(dynamic_cast<ModelBaker*>(baker.release()));
|
||||
if (!_modelBaker) {
|
||||
handleError("The model url '" + bakeableModelURL.toString() + "' from the FST file '" + _originalOutputModelPath + "' (property: '" + FILENAME_FIELD + "') could not be used to initialize a valid model baker");
|
||||
return;
|
||||
}
|
||||
if (dynamic_cast<FSTBaker*>(_modelBaker.get())) {
|
||||
// Could be interesting, but for now let's just prevent infinite FST loops in the most straightforward way possible
|
||||
handleError("The FST file '" + _originalOutputModelPath + "' (property: '" + FILENAME_FIELD + "') references another FST file. FST chaining is not supported.");
|
||||
return;
|
||||
}
|
||||
_modelBaker->setMappingURL(_mappingURL);
|
||||
_modelBaker->setMapping(_mapping);
|
||||
// Hold on to the old url userinfo/query/fragment data so ModelBaker::getFullOutputMappingURL retains that data from the original model URL
|
||||
_modelBaker->setOutputURLSuffix(modelURL);
|
||||
|
||||
connect(_modelBaker.get(), &ModelBaker::aborted, this, &FSTBaker::handleModelBakerAborted);
|
||||
connect(_modelBaker.get(), &ModelBaker::finished, this, &FSTBaker::handleModelBakerFinished);
|
||||
|
||||
// FSTBaker can't do much while waiting for the ModelBaker to finish, so start the bake on this thread.
|
||||
_modelBaker->bake();
|
||||
}
|
||||
|
||||
void FSTBaker::handleModelBakerEnded() {
|
||||
for (auto& warning : _modelBaker->getWarnings()) {
|
||||
_warningList.push_back(warning);
|
||||
}
|
||||
for (auto& error : _modelBaker->getErrors()) {
|
||||
_errorList.push_back(error);
|
||||
}
|
||||
|
||||
// Get the output files, including but not limited to the FST file and the baked model file
|
||||
for (auto& outputFile : _modelBaker->getOutputFiles()) {
|
||||
_outputFiles.push_back(outputFile);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FSTBaker::handleModelBakerAborted() {
|
||||
handleModelBakerEnded();
|
||||
if (!wasAborted()) {
|
||||
setWasAborted(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FSTBaker::handleModelBakerFinished() {
|
||||
handleModelBakerEnded();
|
||||
setIsFinished(true);
|
||||
}
|
||||
|
||||
void FSTBaker::abort() {
|
||||
ModelBaker::abort();
|
||||
if (_modelBaker) {
|
||||
_modelBaker->abort();
|
||||
}
|
||||
}
|
45
libraries/baking/src/baking/FSTBaker.h
Normal file
45
libraries/baking/src/baking/FSTBaker.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// FSTBaker.h
|
||||
// libraries/baking/src/baking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/03/06.
|
||||
// Copyright 2019 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_FSTBaker_h
|
||||
#define hifi_FSTBaker_h
|
||||
|
||||
#include "../ModelBaker.h"
|
||||
|
||||
class FSTBaker : public ModelBaker {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
virtual QUrl getFullOutputMappingURL() const override;
|
||||
|
||||
signals:
|
||||
void fstLoaded();
|
||||
|
||||
public slots:
|
||||
virtual void abort() override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<ModelBaker> _modelBaker;
|
||||
|
||||
protected slots:
|
||||
virtual void bakeSourceCopy() override;
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) override {};
|
||||
void handleModelBakerAborted();
|
||||
void handleModelBakerFinished();
|
||||
|
||||
private:
|
||||
void handleModelBakerEnded();
|
||||
};
|
||||
|
||||
#endif // hifi_FSTBaker_h
|
34
libraries/baking/src/baking/TextureFileNamer.cpp
Normal file
34
libraries/baking/src/baking/TextureFileNamer.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// TextureFileNamer.cpp
|
||||
// libraries/baking/src/baking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/03/14.
|
||||
// Copyright 2019 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 "TextureFileNamer.h"
|
||||
|
||||
QString TextureFileNamer::createBaseTextureFileName(const QFileInfo& textureFileInfo, const image::TextureUsage::Type textureType) {
|
||||
// If two textures have the same URL but are used differently, we need to process them separately
|
||||
QString addMapChannel = QString::fromStdString("_" + std::to_string(textureType));
|
||||
|
||||
QString baseTextureFileName{ textureFileInfo.baseName() + addMapChannel };
|
||||
|
||||
// first make sure we have a unique base name for this texture
|
||||
// in case another texture referenced by this model has the same base name
|
||||
auto& nameMatches = _textureNameMatchCount[baseTextureFileName];
|
||||
|
||||
if (nameMatches > 0) {
|
||||
// there are already nameMatches texture with this name
|
||||
// append - and that number to our baked texture file name so that it is unique
|
||||
baseTextureFileName += "-" + QString::number(nameMatches);
|
||||
}
|
||||
|
||||
// increment the number of name matches
|
||||
++nameMatches;
|
||||
|
||||
return baseTextureFileName;
|
||||
}
|
30
libraries/baking/src/baking/TextureFileNamer.h
Normal file
30
libraries/baking/src/baking/TextureFileNamer.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// TextureFileNamer.h
|
||||
// libraries/baking/src/baking
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/03/14.
|
||||
// Copyright 2019 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_TextureFileNamer_h
|
||||
#define hifi_TextureFileNamer_h
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QHash>
|
||||
|
||||
#include <image/Image.h>
|
||||
|
||||
class TextureFileNamer {
|
||||
public:
|
||||
TextureFileNamer() {}
|
||||
|
||||
QString createBaseTextureFileName(const QFileInfo& textureFileInfo, const image::TextureUsage::Type textureType);
|
||||
|
||||
protected:
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
};
|
||||
|
||||
#endif // hifi_TextureFileNamer_h
|
|
@ -46,12 +46,7 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity)
|
|||
|
||||
void PolyLineEntityRenderer::buildPipeline() {
|
||||
// FIXME: opaque pipeline
|
||||
gpu::ShaderPointer program;
|
||||
if (DISABLE_DEFERRED) {
|
||||
program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke_forward);
|
||||
} else {
|
||||
program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke);
|
||||
}
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(DISABLE_DEFERRED ? shader::entities_renderer::program::paintStroke_forward : shader::entities_renderer::program::paintStroke);
|
||||
|
||||
{
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
#include "RenderPipelines.h"
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
//#define SHAPE_ENTITY_USE_FADE_EFFECT
|
||||
#ifdef SHAPE_ENTITY_USE_FADE_EFFECT
|
||||
#include <FadeEffect.h>
|
||||
|
@ -277,16 +275,10 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
|||
} else if (!useMaterialPipeline(materials)) {
|
||||
// FIXME, support instanced multi-shape rendering using multidraw indirect
|
||||
outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
render::ShapePipelinePointer pipeline;
|
||||
if (_renderLayer == RenderLayer::WORLD && !DISABLE_DEFERRED) {
|
||||
pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
|
||||
} else {
|
||||
pipeline = outColor.a < 1.0f ? geometryCache->getForwardTransparentShapePipeline() : geometryCache->getForwardOpaqueShapePipeline();
|
||||
}
|
||||
if (render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES) {
|
||||
geometryCache->renderWireShapeInstance(args, batch, geometryShape, outColor, pipeline);
|
||||
geometryCache->renderWireShapeInstance(args, batch, geometryShape, outColor, args->_shapePipeline);
|
||||
} else {
|
||||
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
|
||||
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, args->_shapePipeline);
|
||||
}
|
||||
} else {
|
||||
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
#include "DeferredLightingEffect.h"
|
||||
|
||||
using namespace render;
|
||||
using namespace render::entities;
|
||||
|
@ -162,7 +162,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
glm::vec4 backgroundColor;
|
||||
Transform modelTransform;
|
||||
glm::vec3 dimensions;
|
||||
bool forwardRendered;
|
||||
bool layered;
|
||||
withReadLock([&] {
|
||||
modelTransform = _renderTransform;
|
||||
dimensions = _dimensions;
|
||||
|
@ -172,7 +172,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created);
|
||||
backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha);
|
||||
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
|
||||
forwardRendered = _renderLayer != RenderLayer::WORLD || DISABLE_DEFERRED;
|
||||
layered = _renderLayer != RenderLayer::WORLD;
|
||||
});
|
||||
|
||||
// Render background
|
||||
|
@ -184,6 +184,11 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
||||
// FIXME: we need to find a better way of rendering text so we don't have to do this
|
||||
if (layered) {
|
||||
DependencyManager::get<DeferredLightingEffect>()->setupKeyLightBatch(args, batch);
|
||||
}
|
||||
|
||||
auto transformToTopLeft = modelTransform;
|
||||
transformToTopLeft.setRotation(EntityItem::getBillboardRotation(transformToTopLeft.getTranslation(), transformToTopLeft.getRotation(), _billboardMode, args->getViewFrustum().getPosition()));
|
||||
transformToTopLeft.postTranslate(dimensions * glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left
|
||||
|
@ -192,7 +197,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
if (backgroundColor.a > 0.0f) {
|
||||
batch.setModelTransform(transformToTopLeft);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, forwardRendered);
|
||||
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, layered);
|
||||
geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID);
|
||||
}
|
||||
|
||||
|
@ -203,7 +208,11 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
batch.setModelTransform(transformToTopLeft);
|
||||
|
||||
glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin));
|
||||
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, forwardRendered);
|
||||
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, layered);
|
||||
}
|
||||
|
||||
if (layered) {
|
||||
DependencyManager::get<DeferredLightingEffect>()->unsetKeyLightBatch(batch);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1646,11 +1646,9 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
|
||||
EntityItemProperties properties;
|
||||
|
||||
EntityItemPointer entity;
|
||||
bool doTransmit = false;
|
||||
_entityTree->withWriteLock([this, &entity, entityID, myNodeID, &doTransmit, actor, &properties] {
|
||||
_entityTree->withWriteLock([this, &entity, entityID, myNodeID, &doTransmit, actor] {
|
||||
EntitySimulationPointer simulation = _entityTree->getSimulation();
|
||||
entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||
if (!entity) {
|
||||
|
@ -1669,16 +1667,12 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
|
|||
|
||||
doTransmit = actor(simulation, entity);
|
||||
_entityTree->entityChanged(entity);
|
||||
if (doTransmit) {
|
||||
properties.setEntityHostType(entity->getEntityHostType());
|
||||
properties.setOwningAvatarID(entity->getOwningAvatarID());
|
||||
}
|
||||
});
|
||||
|
||||
// transmit the change
|
||||
if (doTransmit) {
|
||||
_entityTree->withReadLock([&] {
|
||||
properties = entity->getProperties();
|
||||
EntityItemProperties properties = _entityTree->resultWithReadLock<EntityItemProperties>([&] {
|
||||
return entity->getProperties();
|
||||
});
|
||||
|
||||
properties.setActionDataDirty();
|
||||
|
|
|
@ -13,27 +13,26 @@
|
|||
#define hifi_FBX_h_
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QVarLengthArray>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <shared/HifiTypes.h>
|
||||
|
||||
// See comment in FBXSerializer::parseFBX().
|
||||
static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23;
|
||||
static const QByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary ");
|
||||
static const QByteArray FBX_BINARY_PROLOG2("\0\x1a\0", 3);
|
||||
static const hifi::ByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary ");
|
||||
static const hifi::ByteArray FBX_BINARY_PROLOG2("\0\x1a\0", 3);
|
||||
static const quint32 FBX_VERSION_2015 = 7400;
|
||||
static const quint32 FBX_VERSION_2016 = 7500;
|
||||
|
||||
static const int DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES = 1000;
|
||||
static const int DRACO_ATTRIBUTE_MATERIAL_ID = DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES;
|
||||
static const int DRACO_ATTRIBUTE_TEX_COORD_1 = DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES + 1;
|
||||
static const int DRACO_ATTRIBUTE_ORIGINAL_INDEX = DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES + 2;
|
||||
|
||||
static const int32_t FBX_PROPERTY_UNCOMPRESSED_FLAG = 0;
|
||||
static const int32_t FBX_PROPERTY_COMPRESSED_FLAG = 1;
|
||||
|
||||
// The version of the FBX node containing the draco mesh. See also: DRACO_MESH_VERSION in HFM.h
|
||||
static const int FBX_DRACO_MESH_VERSION = 2;
|
||||
|
||||
class FBXNode;
|
||||
using FBXNodeList = QList<FBXNode>;
|
||||
|
||||
|
@ -41,7 +40,7 @@ using FBXNodeList = QList<FBXNode>;
|
|||
/// A node within an FBX document.
|
||||
class FBXNode {
|
||||
public:
|
||||
QByteArray name;
|
||||
hifi::ByteArray name;
|
||||
QVariantList properties;
|
||||
FBXNodeList children;
|
||||
};
|
||||
|
|
|
@ -178,7 +178,7 @@ public:
|
|||
|
||||
void printNode(const FBXNode& node, int indentLevel) {
|
||||
int indentLength = 2;
|
||||
QByteArray spaces(indentLevel * indentLength, ' ');
|
||||
hifi::ByteArray spaces(indentLevel * indentLength, ' ');
|
||||
QDebug nodeDebug = qDebug(modelformat);
|
||||
|
||||
nodeDebug.nospace() << spaces.data() << node.name.data() << ": ";
|
||||
|
@ -308,7 +308,7 @@ public:
|
|||
};
|
||||
|
||||
bool checkMaterialsHaveTextures(const QHash<QString, HFMMaterial>& materials,
|
||||
const QHash<QString, QByteArray>& textureFilenames, const QMultiMap<QString, QString>& _connectionChildMap) {
|
||||
const QHash<QString, hifi::ByteArray>& textureFilenames, const QMultiMap<QString, QString>& _connectionChildMap) {
|
||||
foreach (const QString& materialID, materials.keys()) {
|
||||
foreach (const QString& childID, _connectionChildMap.values(materialID)) {
|
||||
if (textureFilenames.contains(childID)) {
|
||||
|
@ -375,7 +375,7 @@ HFMLight extractLight(const FBXNode& object) {
|
|||
return light;
|
||||
}
|
||||
|
||||
QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
|
||||
hifi::ByteArray fileOnUrl(const hifi::ByteArray& filepath, const QString& url) {
|
||||
// in order to match the behaviour when loading models from remote URLs
|
||||
// we assume that all external textures are right beside the loaded model
|
||||
// ignoring any relative paths or absolute paths inside of models
|
||||
|
@ -383,8 +383,10 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
|
|||
return filepath.mid(filepath.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QString& url) {
|
||||
HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QString& url) {
|
||||
const FBXNode& node = _rootNode;
|
||||
bool deduplicateIndices = mapping["deduplicateIndices"].toBool();
|
||||
|
||||
QMap<QString, ExtractedMesh> meshes;
|
||||
QHash<QString, QString> modelIDsToNames;
|
||||
QHash<QString, int> meshIDsToMeshIndices;
|
||||
|
@ -406,11 +408,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
|
||||
std::map<QString, HFMLight> lights;
|
||||
|
||||
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
|
||||
hifi::VariantHash blendshapeMappings = mapping.value("bs").toHash();
|
||||
|
||||
QMultiHash<QByteArray, WeightedIndex> blendshapeIndices;
|
||||
QMultiHash<hifi::ByteArray, WeightedIndex> blendshapeIndices;
|
||||
for (int i = 0;; i++) {
|
||||
QByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i];
|
||||
hifi::ByteArray blendshapeName = FACESHIFT_BLENDSHAPES[i];
|
||||
if (blendshapeName.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
@ -455,7 +457,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
}
|
||||
} else if (subobject.name == "Properties70") {
|
||||
foreach (const FBXNode& subsubobject, subobject.children) {
|
||||
static const QVariant APPLICATION_NAME = QVariant(QByteArray("Original|ApplicationName"));
|
||||
static const QVariant APPLICATION_NAME = QVariant(hifi::ByteArray("Original|ApplicationName"));
|
||||
if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 &&
|
||||
subsubobject.properties.at(0) == APPLICATION_NAME) {
|
||||
hfmModel.applicationName = subsubobject.properties.at(4).toString();
|
||||
|
@ -472,9 +474,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
int index = 4;
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == propertyName) {
|
||||
static const QVariant UNIT_SCALE_FACTOR = QByteArray("UnitScaleFactor");
|
||||
static const QVariant AMBIENT_COLOR = QByteArray("AmbientColor");
|
||||
static const QVariant UP_AXIS = QByteArray("UpAxis");
|
||||
static const QVariant UNIT_SCALE_FACTOR = hifi::ByteArray("UnitScaleFactor");
|
||||
static const QVariant AMBIENT_COLOR = hifi::ByteArray("AmbientColor");
|
||||
static const QVariant UP_AXIS = hifi::ByteArray("UpAxis");
|
||||
const auto& subpropName = subobject.properties.at(0);
|
||||
if (subpropName == UNIT_SCALE_FACTOR) {
|
||||
unitScaleFactor = subobject.properties.at(index).toFloat();
|
||||
|
@ -499,7 +501,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
foreach (const FBXNode& object, child.children) {
|
||||
if (object.name == "Geometry") {
|
||||
if (object.properties.at(2) == "Mesh") {
|
||||
meshes.insert(getID(object.properties), extractMesh(object, meshIndex));
|
||||
meshes.insert(getID(object.properties), extractMesh(object, meshIndex, deduplicateIndices));
|
||||
} else { // object.properties.at(2) == "Shape"
|
||||
ExtractedBlendshape extracted = { getID(object.properties), extractBlendshape(object) };
|
||||
blendshapes.append(extracted);
|
||||
|
@ -540,7 +542,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
QVector<ExtractedBlendshape> blendshapes;
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
bool properties = false;
|
||||
QByteArray propertyName;
|
||||
hifi::ByteArray propertyName;
|
||||
int index;
|
||||
if (subobject.name == "Properties60") {
|
||||
properties = true;
|
||||
|
@ -553,27 +555,27 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
index = 4;
|
||||
}
|
||||
if (properties) {
|
||||
static const QVariant ROTATION_ORDER = QByteArray("RotationOrder");
|
||||
static const QVariant GEOMETRIC_TRANSLATION = QByteArray("GeometricTranslation");
|
||||
static const QVariant GEOMETRIC_ROTATION = QByteArray("GeometricRotation");
|
||||
static const QVariant GEOMETRIC_SCALING = QByteArray("GeometricScaling");
|
||||
static const QVariant LCL_TRANSLATION = QByteArray("Lcl Translation");
|
||||
static const QVariant LCL_ROTATION = QByteArray("Lcl Rotation");
|
||||
static const QVariant LCL_SCALING = QByteArray("Lcl Scaling");
|
||||
static const QVariant ROTATION_MAX = QByteArray("RotationMax");
|
||||
static const QVariant ROTATION_MAX_X = QByteArray("RotationMaxX");
|
||||
static const QVariant ROTATION_MAX_Y = QByteArray("RotationMaxY");
|
||||
static const QVariant ROTATION_MAX_Z = QByteArray("RotationMaxZ");
|
||||
static const QVariant ROTATION_MIN = QByteArray("RotationMin");
|
||||
static const QVariant ROTATION_MIN_X = QByteArray("RotationMinX");
|
||||
static const QVariant ROTATION_MIN_Y = QByteArray("RotationMinY");
|
||||
static const QVariant ROTATION_MIN_Z = QByteArray("RotationMinZ");
|
||||
static const QVariant ROTATION_OFFSET = QByteArray("RotationOffset");
|
||||
static const QVariant ROTATION_PIVOT = QByteArray("RotationPivot");
|
||||
static const QVariant SCALING_OFFSET = QByteArray("ScalingOffset");
|
||||
static const QVariant SCALING_PIVOT = QByteArray("ScalingPivot");
|
||||
static const QVariant PRE_ROTATION = QByteArray("PreRotation");
|
||||
static const QVariant POST_ROTATION = QByteArray("PostRotation");
|
||||
static const QVariant ROTATION_ORDER = hifi::ByteArray("RotationOrder");
|
||||
static const QVariant GEOMETRIC_TRANSLATION = hifi::ByteArray("GeometricTranslation");
|
||||
static const QVariant GEOMETRIC_ROTATION = hifi::ByteArray("GeometricRotation");
|
||||
static const QVariant GEOMETRIC_SCALING = hifi::ByteArray("GeometricScaling");
|
||||
static const QVariant LCL_TRANSLATION = hifi::ByteArray("Lcl Translation");
|
||||
static const QVariant LCL_ROTATION = hifi::ByteArray("Lcl Rotation");
|
||||
static const QVariant LCL_SCALING = hifi::ByteArray("Lcl Scaling");
|
||||
static const QVariant ROTATION_MAX = hifi::ByteArray("RotationMax");
|
||||
static const QVariant ROTATION_MAX_X = hifi::ByteArray("RotationMaxX");
|
||||
static const QVariant ROTATION_MAX_Y = hifi::ByteArray("RotationMaxY");
|
||||
static const QVariant ROTATION_MAX_Z = hifi::ByteArray("RotationMaxZ");
|
||||
static const QVariant ROTATION_MIN = hifi::ByteArray("RotationMin");
|
||||
static const QVariant ROTATION_MIN_X = hifi::ByteArray("RotationMinX");
|
||||
static const QVariant ROTATION_MIN_Y = hifi::ByteArray("RotationMinY");
|
||||
static const QVariant ROTATION_MIN_Z = hifi::ByteArray("RotationMinZ");
|
||||
static const QVariant ROTATION_OFFSET = hifi::ByteArray("RotationOffset");
|
||||
static const QVariant ROTATION_PIVOT = hifi::ByteArray("RotationPivot");
|
||||
static const QVariant SCALING_OFFSET = hifi::ByteArray("ScalingOffset");
|
||||
static const QVariant SCALING_PIVOT = hifi::ByteArray("ScalingPivot");
|
||||
static const QVariant PRE_ROTATION = hifi::ByteArray("PreRotation");
|
||||
static const QVariant POST_ROTATION = hifi::ByteArray("PostRotation");
|
||||
foreach(const FBXNode& property, subobject.children) {
|
||||
const auto& childProperty = property.properties.at(0);
|
||||
if (property.name == propertyName) {
|
||||
|
@ -643,10 +645,10 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (subobject.name == "Vertices") {
|
||||
} else if (subobject.name == "Vertices" || subobject.name == "DracoMesh") {
|
||||
// it's a mesh as well as a model
|
||||
mesh = &meshes[getID(object.properties)];
|
||||
*mesh = extractMesh(object, meshIndex);
|
||||
*mesh = extractMesh(object, meshIndex, deduplicateIndices);
|
||||
|
||||
} else if (subobject.name == "Shape") {
|
||||
ExtractedBlendshape blendshape = { subobject.properties.at(0).toString(),
|
||||
|
@ -713,8 +715,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
const int MODEL_UV_SCALING_MIN_SIZE = 2;
|
||||
const int CROPPING_MIN_SIZE = 4;
|
||||
if (subobject.name == "RelativeFilename" && subobject.properties.length() >= RELATIVE_FILENAME_MIN_SIZE) {
|
||||
QByteArray filename = subobject.properties.at(0).toByteArray();
|
||||
QByteArray filepath = filename.replace('\\', '/');
|
||||
hifi::ByteArray filename = subobject.properties.at(0).toByteArray();
|
||||
hifi::ByteArray filepath = filename.replace('\\', '/');
|
||||
filename = fileOnUrl(filepath, url);
|
||||
_textureFilepaths.insert(getID(object.properties), filepath);
|
||||
_textureFilenames.insert(getID(object.properties), filename);
|
||||
|
@ -743,17 +745,17 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
subobject.properties.at(2).value<int>(),
|
||||
subobject.properties.at(3).value<int>()));
|
||||
} else if (subobject.name == "Properties70") {
|
||||
QByteArray propertyName;
|
||||
hifi::ByteArray propertyName;
|
||||
int index;
|
||||
propertyName = "P";
|
||||
index = 4;
|
||||
foreach (const FBXNode& property, subobject.children) {
|
||||
static const QVariant UV_SET = QByteArray("UVSet");
|
||||
static const QVariant CURRENT_TEXTURE_BLEND_MODE = QByteArray("CurrentTextureBlendMode");
|
||||
static const QVariant USE_MATERIAL = QByteArray("UseMaterial");
|
||||
static const QVariant TRANSLATION = QByteArray("Translation");
|
||||
static const QVariant ROTATION = QByteArray("Rotation");
|
||||
static const QVariant SCALING = QByteArray("Scaling");
|
||||
static const QVariant UV_SET = hifi::ByteArray("UVSet");
|
||||
static const QVariant CURRENT_TEXTURE_BLEND_MODE = hifi::ByteArray("CurrentTextureBlendMode");
|
||||
static const QVariant USE_MATERIAL = hifi::ByteArray("UseMaterial");
|
||||
static const QVariant TRANSLATION = hifi::ByteArray("Translation");
|
||||
static const QVariant ROTATION = hifi::ByteArray("Rotation");
|
||||
static const QVariant SCALING = hifi::ByteArray("Scaling");
|
||||
if (property.name == propertyName) {
|
||||
QString v = property.properties.at(0).toString();
|
||||
if (property.properties.at(0) == UV_SET) {
|
||||
|
@ -807,8 +809,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
_textureParams.insert(getID(object.properties), tex);
|
||||
}
|
||||
} else if (object.name == "Video") {
|
||||
QByteArray filepath;
|
||||
QByteArray content;
|
||||
hifi::ByteArray filepath;
|
||||
hifi::ByteArray content;
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "RelativeFilename") {
|
||||
filepath = subobject.properties.at(0).toByteArray();
|
||||
|
@ -828,7 +830,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
foreach (const FBXNode& subobject, object.children) {
|
||||
bool properties = false;
|
||||
|
||||
QByteArray propertyName;
|
||||
hifi::ByteArray propertyName;
|
||||
int index;
|
||||
if (subobject.name == "Properties60") {
|
||||
properties = true;
|
||||
|
@ -845,31 +847,31 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
|
||||
if (properties) {
|
||||
std::vector<std::string> unknowns;
|
||||
static const QVariant DIFFUSE_COLOR = QByteArray("DiffuseColor");
|
||||
static const QVariant DIFFUSE_FACTOR = QByteArray("DiffuseFactor");
|
||||
static const QVariant DIFFUSE = QByteArray("Diffuse");
|
||||
static const QVariant SPECULAR_COLOR = QByteArray("SpecularColor");
|
||||
static const QVariant SPECULAR_FACTOR = QByteArray("SpecularFactor");
|
||||
static const QVariant SPECULAR = QByteArray("Specular");
|
||||
static const QVariant EMISSIVE_COLOR = QByteArray("EmissiveColor");
|
||||
static const QVariant EMISSIVE_FACTOR = QByteArray("EmissiveFactor");
|
||||
static const QVariant EMISSIVE = QByteArray("Emissive");
|
||||
static const QVariant AMBIENT_FACTOR = QByteArray("AmbientFactor");
|
||||
static const QVariant SHININESS = QByteArray("Shininess");
|
||||
static const QVariant OPACITY = QByteArray("Opacity");
|
||||
static const QVariant MAYA_USE_NORMAL_MAP = QByteArray("Maya|use_normal_map");
|
||||
static const QVariant MAYA_BASE_COLOR = QByteArray("Maya|base_color");
|
||||
static const QVariant MAYA_USE_COLOR_MAP = QByteArray("Maya|use_color_map");
|
||||
static const QVariant MAYA_ROUGHNESS = QByteArray("Maya|roughness");
|
||||
static const QVariant MAYA_USE_ROUGHNESS_MAP = QByteArray("Maya|use_roughness_map");
|
||||
static const QVariant MAYA_METALLIC = QByteArray("Maya|metallic");
|
||||
static const QVariant MAYA_USE_METALLIC_MAP = QByteArray("Maya|use_metallic_map");
|
||||
static const QVariant MAYA_EMISSIVE = QByteArray("Maya|emissive");
|
||||
static const QVariant MAYA_EMISSIVE_INTENSITY = QByteArray("Maya|emissive_intensity");
|
||||
static const QVariant MAYA_USE_EMISSIVE_MAP = QByteArray("Maya|use_emissive_map");
|
||||
static const QVariant MAYA_USE_AO_MAP = QByteArray("Maya|use_ao_map");
|
||||
static const QVariant MAYA_UV_SCALE = QByteArray("Maya|uv_scale");
|
||||
static const QVariant MAYA_UV_OFFSET = QByteArray("Maya|uv_offset");
|
||||
static const QVariant DIFFUSE_COLOR = hifi::ByteArray("DiffuseColor");
|
||||
static const QVariant DIFFUSE_FACTOR = hifi::ByteArray("DiffuseFactor");
|
||||
static const QVariant DIFFUSE = hifi::ByteArray("Diffuse");
|
||||
static const QVariant SPECULAR_COLOR = hifi::ByteArray("SpecularColor");
|
||||
static const QVariant SPECULAR_FACTOR = hifi::ByteArray("SpecularFactor");
|
||||
static const QVariant SPECULAR = hifi::ByteArray("Specular");
|
||||
static const QVariant EMISSIVE_COLOR = hifi::ByteArray("EmissiveColor");
|
||||
static const QVariant EMISSIVE_FACTOR = hifi::ByteArray("EmissiveFactor");
|
||||
static const QVariant EMISSIVE = hifi::ByteArray("Emissive");
|
||||
static const QVariant AMBIENT_FACTOR = hifi::ByteArray("AmbientFactor");
|
||||
static const QVariant SHININESS = hifi::ByteArray("Shininess");
|
||||
static const QVariant OPACITY = hifi::ByteArray("Opacity");
|
||||
static const QVariant MAYA_USE_NORMAL_MAP = hifi::ByteArray("Maya|use_normal_map");
|
||||
static const QVariant MAYA_BASE_COLOR = hifi::ByteArray("Maya|base_color");
|
||||
static const QVariant MAYA_USE_COLOR_MAP = hifi::ByteArray("Maya|use_color_map");
|
||||
static const QVariant MAYA_ROUGHNESS = hifi::ByteArray("Maya|roughness");
|
||||
static const QVariant MAYA_USE_ROUGHNESS_MAP = hifi::ByteArray("Maya|use_roughness_map");
|
||||
static const QVariant MAYA_METALLIC = hifi::ByteArray("Maya|metallic");
|
||||
static const QVariant MAYA_USE_METALLIC_MAP = hifi::ByteArray("Maya|use_metallic_map");
|
||||
static const QVariant MAYA_EMISSIVE = hifi::ByteArray("Maya|emissive");
|
||||
static const QVariant MAYA_EMISSIVE_INTENSITY = hifi::ByteArray("Maya|emissive_intensity");
|
||||
static const QVariant MAYA_USE_EMISSIVE_MAP = hifi::ByteArray("Maya|use_emissive_map");
|
||||
static const QVariant MAYA_USE_AO_MAP = hifi::ByteArray("Maya|use_ao_map");
|
||||
static const QVariant MAYA_UV_SCALE = hifi::ByteArray("Maya|uv_scale");
|
||||
static const QVariant MAYA_UV_OFFSET = hifi::ByteArray("Maya|uv_offset");
|
||||
static const int MAYA_UV_OFFSET_PROPERTY_LENGTH = 6;
|
||||
static const int MAYA_UV_SCALE_PROPERTY_LENGTH = 6;
|
||||
|
||||
|
@ -1050,7 +1052,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
}
|
||||
|
||||
} else if (object.properties.last() == "BlendShapeChannel") {
|
||||
QByteArray name = object.properties.at(1).toByteArray();
|
||||
hifi::ByteArray name = object.properties.at(1).toByteArray();
|
||||
|
||||
name = name.left(name.indexOf('\0'));
|
||||
if (!blendshapeIndices.contains(name)) {
|
||||
|
@ -1087,8 +1089,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
#endif
|
||||
}
|
||||
} else if (child.name == "Connections") {
|
||||
static const QVariant OO = QByteArray("OO");
|
||||
static const QVariant OP = QByteArray("OP");
|
||||
static const QVariant OO = hifi::ByteArray("OO");
|
||||
static const QVariant OP = hifi::ByteArray("OP");
|
||||
foreach (const FBXNode& connection, child.children) {
|
||||
if (connection.name == "C" || connection.name == "Connect") {
|
||||
if (connection.properties.at(0) == OO) {
|
||||
|
@ -1107,7 +1109,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
}
|
||||
} else if (connection.properties.at(0) == OP) {
|
||||
int counter = 0;
|
||||
QByteArray type = connection.properties.at(3).toByteArray().toLower();
|
||||
hifi::ByteArray type = connection.properties.at(3).toByteArray().toLower();
|
||||
if (type.contains("DiffuseFactor")) {
|
||||
diffuseFactorTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
} else if ((type.contains("diffuse") && !type.contains("tex_global_diffuse"))) {
|
||||
|
@ -1404,9 +1406,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
|
||||
// look for textures, material properties
|
||||
// allocate the Part material library
|
||||
// NOTE: extracted.partMaterialTextures is empty for FBX_DRACO_MESH_VERSION >= 2. In that case, the mesh part's materialID string is already defined.
|
||||
int materialIndex = 0;
|
||||
int textureIndex = 0;
|
||||
bool generateTangents = false;
|
||||
QList<QString> children = _connectionChildMap.values(modelID);
|
||||
for (int i = children.size() - 1; i >= 0; i--) {
|
||||
|
||||
|
@ -1419,12 +1421,10 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
if (extracted.partMaterialTextures.at(j).first == materialIndex) {
|
||||
HFMMeshPart& part = extracted.mesh.parts[j];
|
||||
part.materialID = material.materialID;
|
||||
generateTangents |= material.needTangentSpace();
|
||||
}
|
||||
}
|
||||
|
||||
materialIndex++;
|
||||
|
||||
} else if (_textureFilenames.contains(childID)) {
|
||||
// NOTE (Sabrina 2019/01/11): getTextures now takes in the materialID as a second parameter, because FBX material nodes can sometimes have uv transform information (ex: "Maya|uv_scale")
|
||||
// I'm leaving the second parameter blank right now as this code may never be used.
|
||||
|
@ -1694,11 +1694,13 @@ std::unique_ptr<hfm::Serializer::Factory> FBXSerializer::getFactory() const {
|
|||
return std::make_unique<hfm::Serializer::SimpleFactory<FBXSerializer>>();
|
||||
}
|
||||
|
||||
HFMModel::Pointer FBXSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&data));
|
||||
HFMModel::Pointer FBXSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) {
|
||||
QBuffer buffer(const_cast<hifi::ByteArray*>(&data));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
||||
_rootNode = parseFBX(&buffer);
|
||||
|
||||
// FBXSerializer's mapping parameter supports the bool "deduplicateIndices," which is passed into FBXSerializer::extractMesh as "deduplicate"
|
||||
|
||||
return HFMModel::Pointer(extractHFMModel(mapping, url.toString()));
|
||||
}
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
#include <QtGlobal>
|
||||
#include <QMetaType>
|
||||
#include <QSet>
|
||||
#include <QUrl>
|
||||
#include <QVarLengthArray>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
@ -25,6 +22,7 @@
|
|||
|
||||
#include <Extents.h>
|
||||
#include <Transform.h>
|
||||
#include <shared/HifiTypes.h>
|
||||
|
||||
#include "FBX.h"
|
||||
#include <hfm/HFMSerializer.h>
|
||||
|
@ -114,25 +112,25 @@ public:
|
|||
HFMModel* _hfmModel;
|
||||
/// Reads HFMModel from the supplied model and mapping data.
|
||||
/// \exception QString if an error occurs in parsing
|
||||
HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
|
||||
HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) override;
|
||||
|
||||
FBXNode _rootNode;
|
||||
static FBXNode parseFBX(QIODevice* device);
|
||||
|
||||
HFMModel* extractHFMModel(const QVariantHash& mapping, const QString& url);
|
||||
HFMModel* extractHFMModel(const hifi::VariantHash& mapping, const QString& url);
|
||||
|
||||
static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate = true);
|
||||
static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate);
|
||||
QHash<QString, ExtractedMesh> meshes;
|
||||
|
||||
HFMTexture getTexture(const QString& textureID, const QString& materialID);
|
||||
|
||||
QHash<QString, QString> _textureNames;
|
||||
// Hashes the original RelativeFilename of textures
|
||||
QHash<QString, QByteArray> _textureFilepaths;
|
||||
QHash<QString, hifi::ByteArray> _textureFilepaths;
|
||||
// Hashes the place to look for textures, in case they are not inlined
|
||||
QHash<QString, QByteArray> _textureFilenames;
|
||||
QHash<QString, hifi::ByteArray> _textureFilenames;
|
||||
// Hashes texture content by filepath, in case they are inlined
|
||||
QHash<QByteArray, QByteArray> _textureContent;
|
||||
QHash<hifi::ByteArray, hifi::ByteArray> _textureContent;
|
||||
QHash<QString, TextureParam> _textureParams;
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDataStream>
|
||||
#include <QIODevice>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
|
@ -29,7 +28,7 @@
|
|||
|
||||
HFMTexture FBXSerializer::getTexture(const QString& textureID, const QString& materialID) {
|
||||
HFMTexture texture;
|
||||
const QByteArray& filepath = _textureFilepaths.value(textureID);
|
||||
const hifi::ByteArray& filepath = _textureFilepaths.value(textureID);
|
||||
texture.content = _textureContent.value(filepath);
|
||||
|
||||
if (texture.content.isEmpty()) { // the content is not inlined
|
||||
|
|
|
@ -13,12 +13,20 @@
|
|||
#pragma warning( push )
|
||||
#pragma warning( disable : 4267 )
|
||||
#endif
|
||||
// gcc and clang
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
#endif
|
||||
|
||||
#include <draco/compression/decode.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <QBuffer>
|
||||
|
@ -190,8 +198,8 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me
|
|||
|
||||
bool isMaterialPerPolygon = false;
|
||||
|
||||
static const QVariant BY_VERTICE = QByteArray("ByVertice");
|
||||
static const QVariant INDEX_TO_DIRECT = QByteArray("IndexToDirect");
|
||||
static const QVariant BY_VERTICE = hifi::ByteArray("ByVertice");
|
||||
static const QVariant INDEX_TO_DIRECT = hifi::ByteArray("IndexToDirect");
|
||||
|
||||
bool isDracoMesh = false;
|
||||
|
||||
|
@ -321,7 +329,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me
|
|||
}
|
||||
}
|
||||
} else if (child.name == "LayerElementMaterial") {
|
||||
static const QVariant BY_POLYGON = QByteArray("ByPolygon");
|
||||
static const QVariant BY_POLYGON = hifi::ByteArray("ByPolygon");
|
||||
foreach (const FBXNode& subdata, child.children) {
|
||||
if (subdata.name == "Materials") {
|
||||
materials = getIntVector(subdata);
|
||||
|
@ -345,10 +353,26 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me
|
|||
isDracoMesh = true;
|
||||
data.extracted.mesh.wasCompressed = true;
|
||||
|
||||
// Check for additional metadata
|
||||
unsigned int dracoMeshNodeVersion = 1;
|
||||
std::vector<QString> dracoMaterialList;
|
||||
for (const auto& dracoChild : child.children) {
|
||||
if (dracoChild.name == "FBXDracoMeshVersion") {
|
||||
if (!dracoChild.children.isEmpty()) {
|
||||
dracoMeshNodeVersion = dracoChild.properties[0].toUInt();
|
||||
}
|
||||
} else if (dracoChild.name == "MaterialList") {
|
||||
dracoMaterialList.reserve(dracoChild.properties.size());
|
||||
for (const auto& materialID : dracoChild.properties) {
|
||||
dracoMaterialList.push_back(materialID.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load the draco mesh from the FBX and create a draco::Mesh
|
||||
draco::Decoder decoder;
|
||||
draco::DecoderBuffer decodedBuffer;
|
||||
QByteArray dracoArray = child.properties.at(0).value<QByteArray>();
|
||||
hifi::ByteArray dracoArray = child.properties.at(0).value<hifi::ByteArray>();
|
||||
decodedBuffer.Init(dracoArray.data(), dracoArray.size());
|
||||
|
||||
std::unique_ptr<draco::Mesh> dracoMesh(new draco::Mesh());
|
||||
|
@ -462,8 +486,20 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me
|
|||
// grab or setup the HFMMeshPart for the part this face belongs to
|
||||
int& partIndexPlusOne = materialTextureParts[materialTexture];
|
||||
if (partIndexPlusOne == 0) {
|
||||
data.extracted.partMaterialTextures.append(materialTexture);
|
||||
data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1);
|
||||
HFMMeshPart& part = data.extracted.mesh.parts.back();
|
||||
|
||||
// Figure out what material this part is
|
||||
if (dracoMeshNodeVersion >= 2) {
|
||||
// Define the materialID now
|
||||
if (dracoMaterialList.size() - 1 <= materialID) {
|
||||
part.materialID = dracoMaterialList[materialID];
|
||||
}
|
||||
} else {
|
||||
// Define the materialID later, based on the order of first appearance of the materials in the _connectionChildMap
|
||||
data.extracted.partMaterialTextures.append(materialTexture);
|
||||
}
|
||||
|
||||
partIndexPlusOne = data.extracted.mesh.parts.size();
|
||||
}
|
||||
|
||||
|
|
|
@ -48,10 +48,10 @@ QVariant readBinaryArray(QDataStream& in, int& position) {
|
|||
QVector<T> values;
|
||||
if ((int)QSysInfo::ByteOrder == (int)in.byteOrder()) {
|
||||
values.resize(arrayLength);
|
||||
QByteArray arrayData;
|
||||
hifi::ByteArray arrayData;
|
||||
if (encoding == FBX_PROPERTY_COMPRESSED_FLAG) {
|
||||
// preface encoded data with uncompressed length
|
||||
QByteArray compressed(sizeof(quint32) + compressedLength, 0);
|
||||
hifi::ByteArray compressed(sizeof(quint32) + compressedLength, 0);
|
||||
*((quint32*)compressed.data()) = qToBigEndian<quint32>(arrayLength * sizeof(T));
|
||||
in.readRawData(compressed.data() + sizeof(quint32), compressedLength);
|
||||
position += compressedLength;
|
||||
|
@ -73,11 +73,11 @@ QVariant readBinaryArray(QDataStream& in, int& position) {
|
|||
values.reserve(arrayLength);
|
||||
if (encoding == FBX_PROPERTY_COMPRESSED_FLAG) {
|
||||
// preface encoded data with uncompressed length
|
||||
QByteArray compressed(sizeof(quint32) + compressedLength, 0);
|
||||
hifi::ByteArray compressed(sizeof(quint32) + compressedLength, 0);
|
||||
*((quint32*)compressed.data()) = qToBigEndian<quint32>(arrayLength * sizeof(T));
|
||||
in.readRawData(compressed.data() + sizeof(quint32), compressedLength);
|
||||
position += compressedLength;
|
||||
QByteArray uncompressed = qUncompress(compressed);
|
||||
hifi::ByteArray uncompressed = qUncompress(compressed);
|
||||
if (uncompressed.isEmpty()) { // answers empty byte array if corrupt
|
||||
throw QString("corrupt fbx file");
|
||||
}
|
||||
|
@ -234,7 +234,7 @@ public:
|
|||
};
|
||||
|
||||
int nextToken();
|
||||
const QByteArray& getDatum() const { return _datum; }
|
||||
const hifi::ByteArray& getDatum() const { return _datum; }
|
||||
|
||||
void pushBackToken(int token) { _pushedBackToken = token; }
|
||||
void ungetChar(char ch) { _device->ungetChar(ch); }
|
||||
|
@ -242,7 +242,7 @@ public:
|
|||
private:
|
||||
|
||||
QIODevice* _device;
|
||||
QByteArray _datum;
|
||||
hifi::ByteArray _datum;
|
||||
int _pushedBackToken;
|
||||
};
|
||||
|
||||
|
@ -325,7 +325,7 @@ FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
|
|||
expectingDatum = true;
|
||||
|
||||
} else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) {
|
||||
QByteArray datum = tokenizer.getDatum();
|
||||
hifi::ByteArray datum = tokenizer.getDatum();
|
||||
if ((token = tokenizer.nextToken()) == ':') {
|
||||
tokenizer.ungetChar(':');
|
||||
tokenizer.pushBackToken(Tokenizer::DATUM_TOKEN);
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <QBuffer>
|
||||
#include <QVariantHash>
|
||||
|
||||
static const unsigned int FST_VERSION = 1;
|
||||
static const QString FST_VERSION_FIELD = "version";
|
||||
static const QString NAME_FIELD = "name";
|
||||
static const QString TYPE_FIELD = "type";
|
||||
static const QString FILENAME_FIELD = "filename";
|
||||
|
|
|
@ -125,18 +125,18 @@ bool GLTFSerializer::getObjectArrayVal(const QJsonObject& object, const QString&
|
|||
return _defined;
|
||||
}
|
||||
|
||||
QByteArray GLTFSerializer::setGLBChunks(const QByteArray& data) {
|
||||
hifi::ByteArray GLTFSerializer::setGLBChunks(const hifi::ByteArray& data) {
|
||||
int byte = 4;
|
||||
int jsonStart = data.indexOf("JSON", Qt::CaseSensitive);
|
||||
int binStart = data.indexOf("BIN", Qt::CaseSensitive);
|
||||
int jsonLength, binLength;
|
||||
QByteArray jsonLengthChunk, binLengthChunk;
|
||||
hifi::ByteArray jsonLengthChunk, binLengthChunk;
|
||||
|
||||
jsonLengthChunk = data.mid(jsonStart - byte, byte);
|
||||
QDataStream tempJsonLen(jsonLengthChunk);
|
||||
tempJsonLen.setByteOrder(QDataStream::LittleEndian);
|
||||
tempJsonLen >> jsonLength;
|
||||
QByteArray jsonChunk = data.mid(jsonStart + byte, jsonLength);
|
||||
hifi::ByteArray jsonChunk = data.mid(jsonStart + byte, jsonLength);
|
||||
|
||||
if (binStart != -1) {
|
||||
binLengthChunk = data.mid(binStart - byte, byte);
|
||||
|
@ -567,10 +567,10 @@ bool GLTFSerializer::addTexture(const QJsonObject& object) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GLTFSerializer::parseGLTF(const QByteArray& data) {
|
||||
bool GLTFSerializer::parseGLTF(const hifi::ByteArray& data) {
|
||||
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
|
||||
|
||||
QByteArray jsonChunk = data;
|
||||
hifi::ByteArray jsonChunk = data;
|
||||
|
||||
if (_url.toString().endsWith("glb") && data.indexOf("glTF") == 0 && data.contains("JSON")) {
|
||||
jsonChunk = setGLBChunks(data);
|
||||
|
@ -734,7 +734,7 @@ glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) {
|
|||
return tmat;
|
||||
}
|
||||
|
||||
bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) {
|
||||
bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::URL& url) {
|
||||
|
||||
//Build dependencies
|
||||
QVector<QVector<int>> nodeDependencies(_file.nodes.size());
|
||||
|
@ -994,15 +994,15 @@ std::unique_ptr<hfm::Serializer::Factory> GLTFSerializer::getFactory() const {
|
|||
return std::make_unique<hfm::Serializer::SimpleFactory<GLTFSerializer>>();
|
||||
}
|
||||
|
||||
HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
|
||||
HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) {
|
||||
|
||||
_url = url;
|
||||
|
||||
// Normalize url for local files
|
||||
QUrl normalizeUrl = DependencyManager::get<ResourceManager>()->normalizeURL(_url);
|
||||
hifi::URL normalizeUrl = DependencyManager::get<ResourceManager>()->normalizeURL(_url);
|
||||
if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) {
|
||||
QString localFileName = PathUtils::expandToLocalDataAbsolutePath(normalizeUrl).toLocalFile();
|
||||
_url = QUrl(QFileInfo(localFileName).absoluteFilePath());
|
||||
_url = hifi::URL(QFileInfo(localFileName).absoluteFilePath());
|
||||
}
|
||||
|
||||
if (parseGLTF(data)) {
|
||||
|
@ -1020,15 +1020,15 @@ HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHas
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) {
|
||||
bool GLTFSerializer::readBinary(const QString& url, hifi::ByteArray& outdata) {
|
||||
bool success;
|
||||
|
||||
if (url.contains("data:application/octet-stream;base64,")) {
|
||||
outdata = requestEmbeddedData(url);
|
||||
success = !outdata.isEmpty();
|
||||
} else {
|
||||
QUrl binaryUrl = _url.resolved(url);
|
||||
std::tie<bool, QByteArray>(success, outdata) = requestData(binaryUrl);
|
||||
hifi::URL binaryUrl = _url.resolved(url);
|
||||
std::tie<bool, hifi::ByteArray>(success, outdata) = requestData(binaryUrl);
|
||||
}
|
||||
|
||||
return success;
|
||||
|
@ -1038,16 +1038,16 @@ bool GLTFSerializer::doesResourceExist(const QString& url) {
|
|||
if (_url.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
QUrl candidateUrl = _url.resolved(url);
|
||||
hifi::URL candidateUrl = _url.resolved(url);
|
||||
return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl);
|
||||
}
|
||||
|
||||
std::tuple<bool, QByteArray> GLTFSerializer::requestData(QUrl& url) {
|
||||
std::tuple<bool, hifi::ByteArray> GLTFSerializer::requestData(hifi::URL& url) {
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, url, true, -1, "GLTFSerializer::requestData");
|
||||
|
||||
if (!request) {
|
||||
return std::make_tuple(false, QByteArray());
|
||||
return std::make_tuple(false, hifi::ByteArray());
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
|
@ -1058,17 +1058,17 @@ std::tuple<bool, QByteArray> GLTFSerializer::requestData(QUrl& url) {
|
|||
if (request->getResult() == ResourceRequest::Success) {
|
||||
return std::make_tuple(true, request->getData());
|
||||
} else {
|
||||
return std::make_tuple(false, QByteArray());
|
||||
return std::make_tuple(false, hifi::ByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray GLTFSerializer::requestEmbeddedData(const QString& url) {
|
||||
hifi::ByteArray GLTFSerializer::requestEmbeddedData(const QString& url) {
|
||||
QString binaryUrl = url.split(",")[1];
|
||||
return binaryUrl.isEmpty() ? QByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8());
|
||||
return binaryUrl.isEmpty() ? hifi::ByteArray() : QByteArray::fromBase64(binaryUrl.toUtf8());
|
||||
}
|
||||
|
||||
|
||||
QNetworkReply* GLTFSerializer::request(QUrl& url, bool isTest) {
|
||||
QNetworkReply* GLTFSerializer::request(hifi::URL& url, bool isTest) {
|
||||
if (!qApp) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1099,8 +1099,8 @@ HFMTexture GLTFSerializer::getHFMTexture(const GLTFTexture& texture) {
|
|||
if (texture.defined["source"]) {
|
||||
QString url = _file.images[texture.source].uri;
|
||||
|
||||
QString fname = QUrl(url).fileName();
|
||||
QUrl textureUrl = _url.resolved(url);
|
||||
QString fname = hifi::URL(url).fileName();
|
||||
hifi::URL textureUrl = _url.resolved(url);
|
||||
qCDebug(modelformat) << "fname: " << fname;
|
||||
fbxtex.name = fname;
|
||||
fbxtex.filename = textureUrl.toEncoded();
|
||||
|
@ -1188,7 +1188,7 @@ void GLTFSerializer::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& mat
|
|||
}
|
||||
|
||||
template<typename T, typename L>
|
||||
bool GLTFSerializer::readArray(const QByteArray& bin, int byteOffset, int count,
|
||||
bool GLTFSerializer::readArray(const hifi::ByteArray& bin, int byteOffset, int count,
|
||||
QVector<L>& outarray, int accessorType) {
|
||||
|
||||
QDataStream blobstream(bin);
|
||||
|
@ -1245,7 +1245,7 @@ bool GLTFSerializer::readArray(const QByteArray& bin, int byteOffset, int count,
|
|||
return true;
|
||||
}
|
||||
template<typename T>
|
||||
bool GLTFSerializer::addArrayOfType(const QByteArray& bin, int byteOffset, int count,
|
||||
bool GLTFSerializer::addArrayOfType(const hifi::ByteArray& bin, int byteOffset, int count,
|
||||
QVector<T>& outarray, int accessorType, int componentType) {
|
||||
|
||||
switch (componentType) {
|
||||
|
|
|
@ -214,7 +214,7 @@ struct GLTFBufferView {
|
|||
struct GLTFBuffer {
|
||||
int byteLength; //required
|
||||
QString uri;
|
||||
QByteArray blob;
|
||||
hifi::ByteArray blob;
|
||||
QMap<QString, bool> defined;
|
||||
void dump() {
|
||||
if (defined["byteLength"]) {
|
||||
|
@ -705,16 +705,16 @@ public:
|
|||
MediaType getMediaType() const override;
|
||||
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
|
||||
|
||||
HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
|
||||
HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) override;
|
||||
private:
|
||||
GLTFFile _file;
|
||||
QUrl _url;
|
||||
QByteArray _glbBinary;
|
||||
hifi::URL _url;
|
||||
hifi::ByteArray _glbBinary;
|
||||
|
||||
glm::mat4 getModelTransform(const GLTFNode& node);
|
||||
|
||||
bool buildGeometry(HFMModel& hfmModel, const QUrl& url);
|
||||
bool parseGLTF(const QByteArray& data);
|
||||
bool buildGeometry(HFMModel& hfmModel, const hifi::URL& url);
|
||||
bool parseGLTF(const hifi::ByteArray& data);
|
||||
|
||||
bool getStringVal(const QJsonObject& object, const QString& fieldname,
|
||||
QString& value, QMap<QString, bool>& defined);
|
||||
|
@ -733,7 +733,7 @@ private:
|
|||
bool getObjectArrayVal(const QJsonObject& object, const QString& fieldname,
|
||||
QJsonArray& objects, QMap<QString, bool>& defined);
|
||||
|
||||
QByteArray setGLBChunks(const QByteArray& data);
|
||||
hifi::ByteArray setGLBChunks(const hifi::ByteArray& data);
|
||||
|
||||
int getMaterialAlphaMode(const QString& type);
|
||||
int getAccessorType(const QString& type);
|
||||
|
@ -760,24 +760,24 @@ private:
|
|||
bool addSkin(const QJsonObject& object);
|
||||
bool addTexture(const QJsonObject& object);
|
||||
|
||||
bool readBinary(const QString& url, QByteArray& outdata);
|
||||
bool readBinary(const QString& url, hifi::ByteArray& outdata);
|
||||
|
||||
template<typename T, typename L>
|
||||
bool readArray(const QByteArray& bin, int byteOffset, int count,
|
||||
bool readArray(const hifi::ByteArray& bin, int byteOffset, int count,
|
||||
QVector<L>& outarray, int accessorType);
|
||||
|
||||
template<typename T>
|
||||
bool addArrayOfType(const QByteArray& bin, int byteOffset, int count,
|
||||
bool addArrayOfType(const hifi::ByteArray& bin, int byteOffset, int count,
|
||||
QVector<T>& outarray, int accessorType, int componentType);
|
||||
|
||||
void retriangulate(const QVector<int>& in_indices, const QVector<glm::vec3>& in_vertices,
|
||||
const QVector<glm::vec3>& in_normals, QVector<int>& out_indices,
|
||||
QVector<glm::vec3>& out_vertices, QVector<glm::vec3>& out_normals);
|
||||
|
||||
std::tuple<bool, QByteArray> requestData(QUrl& url);
|
||||
QByteArray requestEmbeddedData(const QString& url);
|
||||
std::tuple<bool, hifi::ByteArray> requestData(hifi::URL& url);
|
||||
hifi::ByteArray requestEmbeddedData(const QString& url);
|
||||
|
||||
QNetworkReply* request(QUrl& url, bool isTest);
|
||||
QNetworkReply* request(hifi::URL& url, bool isTest);
|
||||
bool doesResourceExist(const QString& url);
|
||||
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ T& checked_at(QVector<T>& vector, int i) {
|
|||
OBJTokenizer::OBJTokenizer(QIODevice* device) : _device(device), _pushedBackToken(-1) {
|
||||
}
|
||||
|
||||
const QByteArray OBJTokenizer::getLineAsDatum() {
|
||||
const hifi::ByteArray OBJTokenizer::getLineAsDatum() {
|
||||
return _device->readLine().trimmed();
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ bool OBJTokenizer::isNextTokenFloat() {
|
|||
if (nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
return false;
|
||||
}
|
||||
QByteArray token = getDatum();
|
||||
hifi::ByteArray token = getDatum();
|
||||
pushBackToken(OBJTokenizer::DATUM_TOKEN);
|
||||
bool ok;
|
||||
token.toFloat(&ok);
|
||||
|
@ -182,7 +182,7 @@ void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID) {
|
|||
// OBJFace
|
||||
// NOTE (trent, 7/20/17): The vertexColors vector being passed-in isn't necessary here, but I'm just
|
||||
// pairing it with the vertices vector for consistency.
|
||||
bool OBJFace::add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& vertexColors) {
|
||||
bool OBJFace::add(const hifi::ByteArray& vertexIndex, const hifi::ByteArray& textureIndex, const hifi::ByteArray& normalIndex, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& vertexColors) {
|
||||
bool ok;
|
||||
int index = vertexIndex.toInt(&ok);
|
||||
if (!ok) {
|
||||
|
@ -238,11 +238,11 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f
|
|||
}
|
||||
}
|
||||
|
||||
bool OBJSerializer::isValidTexture(const QByteArray &filename) {
|
||||
bool OBJSerializer::isValidTexture(const hifi::ByteArray &filename) {
|
||||
if (_url.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
QUrl candidateUrl = _url.resolved(QUrl(filename));
|
||||
hifi::URL candidateUrl = _url.resolved(hifi::URL(filename));
|
||||
|
||||
return DependencyManager::get<ResourceManager>()->resourceExists(candidateUrl);
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ void OBJSerializer::parseMaterialLibrary(QIODevice* device) {
|
|||
#endif
|
||||
return;
|
||||
}
|
||||
QByteArray token = tokenizer.getDatum();
|
||||
hifi::ByteArray token = tokenizer.getDatum();
|
||||
if (token == "newmtl") {
|
||||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
return;
|
||||
|
@ -328,8 +328,8 @@ void OBJSerializer::parseMaterialLibrary(QIODevice* device) {
|
|||
} else if (token == "Ks") {
|
||||
currentMaterial.specularColor = tokenizer.getVec3();
|
||||
} else if ((token == "map_Kd") || (token == "map_Ke") || (token == "map_Ks") || (token == "map_bump") || (token == "bump") || (token == "map_d")) {
|
||||
const QByteArray textureLine = tokenizer.getLineAsDatum();
|
||||
QByteArray filename;
|
||||
const hifi::ByteArray textureLine = tokenizer.getLineAsDatum();
|
||||
hifi::ByteArray filename;
|
||||
OBJMaterialTextureOptions textureOptions;
|
||||
parseTextureLine(textureLine, filename, textureOptions);
|
||||
if (filename.endsWith(".tga")) {
|
||||
|
@ -354,7 +354,7 @@ void OBJSerializer::parseMaterialLibrary(QIODevice* device) {
|
|||
}
|
||||
}
|
||||
|
||||
void OBJSerializer::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) {
|
||||
void OBJSerializer::parseTextureLine(const hifi::ByteArray& textureLine, hifi::ByteArray& filename, OBJMaterialTextureOptions& textureOptions) {
|
||||
// Texture options reference http://paulbourke.net/dataformats/mtl/
|
||||
// and https://wikivisually.com/wiki/Material_Template_Library
|
||||
|
||||
|
@ -442,12 +442,12 @@ void OBJSerializer::parseTextureLine(const QByteArray& textureLine, QByteArray&
|
|||
}
|
||||
}
|
||||
|
||||
std::tuple<bool, QByteArray> requestData(QUrl& url) {
|
||||
std::tuple<bool, hifi::ByteArray> requestData(hifi::URL& url) {
|
||||
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(
|
||||
nullptr, url, true, -1, "(OBJSerializer) requestData");
|
||||
|
||||
if (!request) {
|
||||
return std::make_tuple(false, QByteArray());
|
||||
return std::make_tuple(false, hifi::ByteArray());
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
|
@ -458,12 +458,12 @@ std::tuple<bool, QByteArray> requestData(QUrl& url) {
|
|||
if (request->getResult() == ResourceRequest::Success) {
|
||||
return std::make_tuple(true, request->getData());
|
||||
} else {
|
||||
return std::make_tuple(false, QByteArray());
|
||||
return std::make_tuple(false, hifi::ByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QNetworkReply* request(QUrl& url, bool isTest) {
|
||||
QNetworkReply* request(hifi::URL& url, bool isTest) {
|
||||
if (!qApp) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -488,7 +488,7 @@ QNetworkReply* request(QUrl& url, bool isTest) {
|
|||
}
|
||||
|
||||
|
||||
bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, HFMModel& hfmModel,
|
||||
bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHash& mapping, HFMModel& hfmModel,
|
||||
float& scaleGuess, bool combineParts) {
|
||||
FaceGroup faces;
|
||||
HFMMesh& mesh = hfmModel.meshes[0];
|
||||
|
@ -522,7 +522,7 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& m
|
|||
result = false;
|
||||
break;
|
||||
}
|
||||
QByteArray token = tokenizer.getDatum();
|
||||
hifi::ByteArray token = tokenizer.getDatum();
|
||||
//qCDebug(modelformat) << token;
|
||||
// we don't support separate objects in the same file, so treat "o" the same as "g".
|
||||
if (token == "g" || token == "o") {
|
||||
|
@ -535,7 +535,7 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& m
|
|||
if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) {
|
||||
break;
|
||||
}
|
||||
QByteArray groupName = tokenizer.getDatum();
|
||||
hifi::ByteArray groupName = tokenizer.getDatum();
|
||||
currentGroup = groupName;
|
||||
if (!combineParts) {
|
||||
currentMaterialName = QString("part-") + QString::number(_partCounter++);
|
||||
|
@ -544,7 +544,7 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& m
|
|||
if (tokenizer.nextToken(true) != OBJTokenizer::DATUM_TOKEN) {
|
||||
break;
|
||||
}
|
||||
QByteArray libraryName = tokenizer.getDatum();
|
||||
hifi::ByteArray libraryName = tokenizer.getDatum();
|
||||
librariesSeen[libraryName] = true;
|
||||
// We'll read it later only if we actually need it.
|
||||
} else if (token == "usemtl") {
|
||||
|
@ -598,14 +598,14 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& m
|
|||
// vertex-index
|
||||
// vertex-index/texture-index
|
||||
// vertex-index/texture-index/surface-normal-index
|
||||
QByteArray token = tokenizer.getDatum();
|
||||
hifi::ByteArray token = tokenizer.getDatum();
|
||||
auto firstChar = token[0];
|
||||
// Tokenizer treats line endings as whitespace. Non-digit and non-negative sign indicates done;
|
||||
if (!isdigit(firstChar) && firstChar != '-') {
|
||||
tokenizer.pushBackToken(OBJTokenizer::DATUM_TOKEN);
|
||||
break;
|
||||
}
|
||||
QList<QByteArray> parts = token.split('/');
|
||||
QList<hifi::ByteArray> parts = token.split('/');
|
||||
assert(parts.count() >= 1);
|
||||
assert(parts.count() <= 3);
|
||||
// If indices are negative relative indices then adjust them to absolute indices based on current vector sizes
|
||||
|
@ -626,7 +626,7 @@ bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& m
|
|||
}
|
||||
}
|
||||
}
|
||||
const QByteArray noData {};
|
||||
const hifi::ByteArray noData {};
|
||||
face.add(parts[0], (parts.count() > 1) ? parts[1] : noData, (parts.count() > 2) ? parts[2] : noData,
|
||||
vertices, vertexColors);
|
||||
face.groupName = currentGroup;
|
||||
|
@ -661,9 +661,9 @@ std::unique_ptr<hfm::Serializer::Factory> OBJSerializer::getFactory() const {
|
|||
return std::make_unique<hfm::Serializer::SimpleFactory<OBJSerializer>>();
|
||||
}
|
||||
|
||||
HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
|
||||
HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) {
|
||||
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
|
||||
QBuffer buffer { const_cast<QByteArray*>(&data) };
|
||||
QBuffer buffer { const_cast<hifi::ByteArray*>(&data) };
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
||||
auto hfmModelPtr = std::make_shared<HFMModel>();
|
||||
|
@ -849,11 +849,11 @@ HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash
|
|||
int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail
|
||||
QString basename = filename.remove(extIndex + 1, sizeof("obj"));
|
||||
preDefinedMaterial.diffuseColor = glm::vec3(1.0f);
|
||||
QVector<QByteArray> extensions = { "jpg", "jpeg", "png", "tga" };
|
||||
QByteArray base = basename.toUtf8(), textName = "";
|
||||
QVector<hifi::ByteArray> extensions = { "jpg", "jpeg", "png", "tga" };
|
||||
hifi::ByteArray base = basename.toUtf8(), textName = "";
|
||||
qCDebug(modelformat) << "OBJSerializer looking for default texture";
|
||||
for (int i = 0; i < extensions.count(); i++) {
|
||||
QByteArray candidateString = base + extensions[i];
|
||||
hifi::ByteArray candidateString = base + extensions[i];
|
||||
if (isValidTexture(candidateString)) {
|
||||
textName = candidateString;
|
||||
break;
|
||||
|
@ -871,11 +871,11 @@ HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash
|
|||
if (needsMaterialLibrary) {
|
||||
foreach (QString libraryName, librariesSeen.keys()) {
|
||||
// Throw away any path part of libraryName, and merge against original url.
|
||||
QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName());
|
||||
hifi::URL libraryUrl = _url.resolved(hifi::URL(libraryName).fileName());
|
||||
qCDebug(modelformat) << "OBJSerializer material library" << libraryName;
|
||||
bool success;
|
||||
QByteArray data;
|
||||
std::tie<bool, QByteArray>(success, data) = requestData(libraryUrl);
|
||||
hifi::ByteArray data;
|
||||
std::tie<bool, hifi::ByteArray>(success, data) = requestData(libraryUrl);
|
||||
if (success) {
|
||||
QBuffer buffer { &data };
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
|
|
@ -25,9 +25,9 @@ public:
|
|||
COMMENT_TOKEN = 0x101
|
||||
};
|
||||
int nextToken(bool allowSpaceChar = false);
|
||||
const QByteArray& getDatum() const { return _datum; }
|
||||
const hifi::ByteArray& getDatum() const { return _datum; }
|
||||
bool isNextTokenFloat();
|
||||
const QByteArray getLineAsDatum(); // some "filenames" have spaces in them
|
||||
const hifi::ByteArray getLineAsDatum(); // some "filenames" have spaces in them
|
||||
void skipLine() { _device->readLine(); }
|
||||
void pushBackToken(int token) { _pushedBackToken = token; }
|
||||
void ungetChar(char ch) { _device->ungetChar(ch); }
|
||||
|
@ -39,7 +39,7 @@ public:
|
|||
|
||||
private:
|
||||
QIODevice* _device;
|
||||
QByteArray _datum;
|
||||
hifi::ByteArray _datum;
|
||||
int _pushedBackToken;
|
||||
QString _comment;
|
||||
};
|
||||
|
@ -52,7 +52,7 @@ public:
|
|||
QString groupName; // We don't make use of hierarchical structure, but it can be preserved for debugging and future use.
|
||||
QString materialName;
|
||||
// Add one more set of vertex data. Answers true if successful
|
||||
bool add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex,
|
||||
bool add(const hifi::ByteArray& vertexIndex, const hifi::ByteArray& textureIndex, const hifi::ByteArray& normalIndex,
|
||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& vertexColors);
|
||||
// Return a set of one or more OBJFaces from this one, in which each is just a triangle.
|
||||
// Even though HFMMeshPart can handle quads, it would be messy to try to keep track of mixed-size faces, so we treat everything as triangles.
|
||||
|
@ -75,11 +75,11 @@ public:
|
|||
glm::vec3 diffuseColor;
|
||||
glm::vec3 specularColor;
|
||||
glm::vec3 emissiveColor;
|
||||
QByteArray diffuseTextureFilename;
|
||||
QByteArray specularTextureFilename;
|
||||
QByteArray emissiveTextureFilename;
|
||||
QByteArray bumpTextureFilename;
|
||||
QByteArray opacityTextureFilename;
|
||||
hifi::ByteArray diffuseTextureFilename;
|
||||
hifi::ByteArray specularTextureFilename;
|
||||
hifi::ByteArray emissiveTextureFilename;
|
||||
hifi::ByteArray bumpTextureFilename;
|
||||
hifi::ByteArray opacityTextureFilename;
|
||||
|
||||
OBJMaterialTextureOptions bumpTextureOptions;
|
||||
int illuminationModel;
|
||||
|
@ -103,17 +103,17 @@ public:
|
|||
QString currentMaterialName;
|
||||
QHash<QString, OBJMaterial> materials;
|
||||
|
||||
HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
|
||||
HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) override;
|
||||
|
||||
private:
|
||||
QUrl _url;
|
||||
hifi::URL _url;
|
||||
|
||||
QHash<QByteArray, bool> librariesSeen;
|
||||
bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, HFMModel& hfmModel,
|
||||
QHash<hifi::ByteArray, bool> librariesSeen;
|
||||
bool parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHash& mapping, HFMModel& hfmModel,
|
||||
float& scaleGuess, bool combineParts);
|
||||
void parseMaterialLibrary(QIODevice* device);
|
||||
void parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions);
|
||||
bool isValidTexture(const QByteArray &filename); // true if the file exists. TODO?: check content-type header and that it is a supported format.
|
||||
void parseTextureLine(const hifi::ByteArray& textureLine, hifi::ByteArray& filename, OBJMaterialTextureOptions& textureOptions);
|
||||
bool isValidTexture(const hifi::ByteArray &filename); // true if the file exists. TODO?: check content-type header and that it is a supported format.
|
||||
|
||||
int _partCounter { 0 };
|
||||
};
|
||||
|
|
|
@ -96,6 +96,8 @@ namespace scriptable {
|
|||
|
||||
bool defaultFallthrough;
|
||||
std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script
|
||||
|
||||
graphics::MaterialKey key { 0 };
|
||||
};
|
||||
|
||||
/**jsdoc
|
||||
|
|
|
@ -363,24 +363,87 @@ namespace scriptable {
|
|||
obj.setProperty("name", material.name);
|
||||
obj.setProperty("model", material.model);
|
||||
|
||||
const QScriptValue FALLTHROUGH("fallthrough");
|
||||
obj.setProperty("opacity", material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT) ? FALLTHROUGH : material.opacity);
|
||||
obj.setProperty("roughness", material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT) ? FALLTHROUGH : material.roughness);
|
||||
obj.setProperty("metallic", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT) ? FALLTHROUGH : material.metallic);
|
||||
obj.setProperty("scattering", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT) ? FALLTHROUGH : material.scattering);
|
||||
obj.setProperty("unlit", material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT) ? FALLTHROUGH : material.unlit);
|
||||
obj.setProperty("emissive", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.emissive));
|
||||
obj.setProperty("albedo", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.albedo));
|
||||
bool hasPropertyFallthroughs = !material.propertyFallthroughs.empty();
|
||||
|
||||
obj.setProperty("emissiveMap", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT) ? FALLTHROUGH : material.emissiveMap);
|
||||
obj.setProperty("albedoMap", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT) ? FALLTHROUGH : material.albedoMap);
|
||||
obj.setProperty("opacityMap", material.opacityMap);
|
||||
obj.setProperty("occlusionMap", material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT) ? FALLTHROUGH : material.occlusionMap);
|
||||
obj.setProperty("lightmapMap", material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT) ? FALLTHROUGH : material.lightmapMap);
|
||||
obj.setProperty("scatteringMap", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT) ? FALLTHROUGH : material.scatteringMap);
|
||||
const QScriptValue FALLTHROUGH("fallthrough");
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT)) {
|
||||
obj.setProperty("opacity", FALLTHROUGH);
|
||||
} else if (material.key.isTranslucentFactor()) {
|
||||
obj.setProperty("opacity", material.opacity);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT)) {
|
||||
obj.setProperty("roughness", FALLTHROUGH);
|
||||
} else if (material.key.isGlossy()) {
|
||||
obj.setProperty("roughness", material.roughness);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT)) {
|
||||
obj.setProperty("metallic", FALLTHROUGH);
|
||||
} else if (material.key.isMetallic()) {
|
||||
obj.setProperty("metallic", material.metallic);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT)) {
|
||||
obj.setProperty("scattering", FALLTHROUGH);
|
||||
} else if (material.key.isScattering()) {
|
||||
obj.setProperty("scattering", material.scattering);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT)) {
|
||||
obj.setProperty("unlit", FALLTHROUGH);
|
||||
} else if (material.key.isUnlit()) {
|
||||
obj.setProperty("unlit", material.unlit);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT)) {
|
||||
obj.setProperty("emissive", FALLTHROUGH);
|
||||
} else if (material.key.isEmissive()) {
|
||||
obj.setProperty("emissive", vec3ColorToScriptValue(engine, material.emissive));
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT)) {
|
||||
obj.setProperty("albedo", FALLTHROUGH);
|
||||
} else if (material.key.isAlbedo()) {
|
||||
obj.setProperty("albedo", vec3ColorToScriptValue(engine, material.albedo));
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT)) {
|
||||
obj.setProperty("emissiveMap", FALLTHROUGH);
|
||||
} else if (!material.emissiveMap.isEmpty()) {
|
||||
obj.setProperty("emissiveMap", material.emissiveMap);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT)) {
|
||||
obj.setProperty("albedoMap", FALLTHROUGH);
|
||||
} else if (!material.albedoMap.isEmpty()) {
|
||||
obj.setProperty("albedoMap", material.albedoMap);
|
||||
}
|
||||
|
||||
if (!material.opacityMap.isEmpty()) {
|
||||
obj.setProperty("opacityMap", material.opacityMap);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT)) {
|
||||
obj.setProperty("occlusionMap", FALLTHROUGH);
|
||||
} else if (!material.occlusionMap.isEmpty()) {
|
||||
obj.setProperty("occlusionMap", material.occlusionMap);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT)) {
|
||||
obj.setProperty("lightmapMap", FALLTHROUGH);
|
||||
} else if (!material.lightmapMap.isEmpty()) {
|
||||
obj.setProperty("lightmapMap", material.lightmapMap);
|
||||
}
|
||||
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT)) {
|
||||
obj.setProperty("scatteringMap", FALLTHROUGH);
|
||||
} else if (!material.scatteringMap.isEmpty()) {
|
||||
obj.setProperty("scatteringMap", material.scatteringMap);
|
||||
}
|
||||
|
||||
// Only set one of each of these
|
||||
if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) {
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) {
|
||||
obj.setProperty("metallicMap", FALLTHROUGH);
|
||||
} else if (!material.metallicMap.isEmpty()) {
|
||||
obj.setProperty("metallicMap", material.metallicMap);
|
||||
|
@ -388,7 +451,7 @@ namespace scriptable {
|
|||
obj.setProperty("specularMap", material.specularMap);
|
||||
}
|
||||
|
||||
if (material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT)) {
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT)) {
|
||||
obj.setProperty("roughnessMap", FALLTHROUGH);
|
||||
} else if (!material.roughnessMap.isEmpty()) {
|
||||
obj.setProperty("roughnessMap", material.roughnessMap);
|
||||
|
@ -396,7 +459,7 @@ namespace scriptable {
|
|||
obj.setProperty("glossMap", material.glossMap);
|
||||
}
|
||||
|
||||
if (material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT)) {
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT)) {
|
||||
obj.setProperty("normalMap", FALLTHROUGH);
|
||||
} else if (!material.normalMap.isEmpty()) {
|
||||
obj.setProperty("normalMap", material.normalMap);
|
||||
|
@ -405,16 +468,16 @@ namespace scriptable {
|
|||
}
|
||||
|
||||
// These need to be implemented, but set the fallthrough for now
|
||||
if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) {
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) {
|
||||
obj.setProperty("texCoordTransform0", FALLTHROUGH);
|
||||
}
|
||||
if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) {
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) {
|
||||
obj.setProperty("texCoordTransform1", FALLTHROUGH);
|
||||
}
|
||||
if (material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) {
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) {
|
||||
obj.setProperty("lightmapParams", FALLTHROUGH);
|
||||
}
|
||||
if (material.propertyFallthroughs.at(graphics::Material::MATERIAL_PARAMS)) {
|
||||
if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::Material::MATERIAL_PARAMS)) {
|
||||
obj.setProperty("materialParams", FALLTHROUGH);
|
||||
}
|
||||
|
||||
|
|
|
@ -103,6 +103,10 @@ private:
|
|||
|
||||
};
|
||||
|
||||
namespace scriptable {
|
||||
QScriptValue scriptableMaterialToScriptValue(QScriptEngine* engine, const scriptable::ScriptableMaterial &material);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(glm::uint32)
|
||||
Q_DECLARE_METATYPE(QVector<glm::uint32>)
|
||||
Q_DECLARE_METATYPE(NestableType)
|
||||
|
|
|
@ -45,75 +45,80 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const
|
|||
defaultFallthrough = material.defaultFallthrough;
|
||||
propertyFallthroughs = material.propertyFallthroughs;
|
||||
|
||||
key = material.key;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) :
|
||||
name(material->getName().c_str()),
|
||||
model(material->getModel().c_str()),
|
||||
opacity(material->getOpacity()),
|
||||
roughness(material->getRoughness()),
|
||||
metallic(material->getMetallic()),
|
||||
scattering(material->getScattering()),
|
||||
unlit(material->isUnlit()),
|
||||
emissive(material->getEmissive()),
|
||||
albedo(material->getAlbedo()),
|
||||
defaultFallthrough(material->getDefaultFallthrough()),
|
||||
propertyFallthroughs(material->getPropertyFallthroughs())
|
||||
{
|
||||
auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
emissiveMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPointer& material) {
|
||||
if (material) {
|
||||
name = material->getName().c_str();
|
||||
model = material->getModel().c_str();
|
||||
opacity = material->getOpacity();
|
||||
roughness = material->getRoughness();
|
||||
metallic = material->getMetallic();
|
||||
scattering = material->getScattering();
|
||||
unlit = material->isUnlit();
|
||||
emissive = material->getEmissive();
|
||||
albedo = material->getAlbedo();
|
||||
defaultFallthrough = material->getDefaultFallthrough();
|
||||
propertyFallthroughs = material->getPropertyFallthroughs();
|
||||
key = material->getKey();
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
albedoMap = map->getTextureSource()->getUrl().toString();
|
||||
if (map->useAlphaChannel()) {
|
||||
opacityMap = albedoMap;
|
||||
auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
emissiveMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
}
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) {
|
||||
metallicMap = map->getTextureSource()->getUrl().toString();
|
||||
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) {
|
||||
specularMap = map->getTextureSource()->getUrl().toString();
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::ALBEDO_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
albedoMap = map->getTextureSource()->getUrl().toString();
|
||||
if (map->useAlphaChannel()) {
|
||||
opacityMap = albedoMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) {
|
||||
roughnessMap = map->getTextureSource()->getUrl().toString();
|
||||
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) {
|
||||
glossMap = map->getTextureSource()->getUrl().toString();
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::METALLIC_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
if (map->getTextureSource()->getType() == image::TextureUsage::Type::METALLIC_TEXTURE) {
|
||||
metallicMap = map->getTextureSource()->getUrl().toString();
|
||||
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::SPECULAR_TEXTURE) {
|
||||
specularMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) {
|
||||
normalMap = map->getTextureSource()->getUrl().toString();
|
||||
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) {
|
||||
bumpMap = map->getTextureSource()->getUrl().toString();
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::ROUGHNESS_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
if (map->getTextureSource()->getType() == image::TextureUsage::Type::ROUGHNESS_TEXTURE) {
|
||||
roughnessMap = map->getTextureSource()->getUrl().toString();
|
||||
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::GLOSS_TEXTURE) {
|
||||
glossMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
occlusionMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::NORMAL_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
if (map->getTextureSource()->getType() == image::TextureUsage::Type::NORMAL_TEXTURE) {
|
||||
normalMap = map->getTextureSource()->getUrl().toString();
|
||||
} else if (map->getTextureSource()->getType() == image::TextureUsage::Type::BUMP_TEXTURE) {
|
||||
bumpMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
}
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
lightmapMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::OCCLUSION_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
occlusionMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
scatteringMap = map->getTextureSource()->getUrl().toString();
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::LIGHTMAP_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
lightmapMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
|
||||
map = material->getTextureMap(graphics::Material::MapChannel::SCATTERING_MAP);
|
||||
if (map && map->getTextureSource()) {
|
||||
scatteringMap = map->getTextureSource()->getUrl().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -235,7 +235,7 @@ vec3 fetchLightmapMap(vec2 uv) {
|
|||
<@endfunc@>
|
||||
<@func discardInvisible(opacity)@>
|
||||
{
|
||||
if (<$opacity$> < 1.e-6) {
|
||||
if (<$opacity$> <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,14 @@ using ColorType = glm::vec3;
|
|||
|
||||
const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048;
|
||||
|
||||
// The version of the Draco mesh binary data itself. See also: FBX_DRACO_MESH_VERSION in FBX.h
|
||||
static const int DRACO_MESH_VERSION = 2;
|
||||
|
||||
static const int DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES = 1000;
|
||||
static const int DRACO_ATTRIBUTE_MATERIAL_ID = DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES;
|
||||
static const int DRACO_ATTRIBUTE_TEX_COORD_1 = DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES + 1;
|
||||
static const int DRACO_ATTRIBUTE_ORIGINAL_INDEX = DRACO_BEGIN_CUSTOM_HIFI_ATTRIBUTES + 2;
|
||||
|
||||
// High Fidelity Model namespace
|
||||
namespace hfm {
|
||||
|
||||
|
|
|
@ -205,7 +205,11 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) {
|
|||
// Help the QImage loader by extracting the image file format from the url filename ext.
|
||||
// Some tga are not created properly without it.
|
||||
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
|
||||
content.open(QIODevice::ReadOnly);
|
||||
if (!content.isReadable()) {
|
||||
content.open(QIODevice::ReadOnly);
|
||||
} else {
|
||||
content.reset();
|
||||
}
|
||||
|
||||
if (filenameExtension == "tga") {
|
||||
QImage image = image::readTGA(content);
|
||||
|
|
|
@ -4,4 +4,6 @@ setup_hifi_library()
|
|||
link_hifi_libraries(shared shaders task gpu graphics hfm material-networking)
|
||||
include_hifi_library_headers(networking)
|
||||
include_hifi_library_headers(image)
|
||||
include_hifi_library_headers(ktx)
|
||||
include_hifi_library_headers(ktx)
|
||||
|
||||
target_draco()
|
||||
|
|
|
@ -11,15 +11,15 @@
|
|||
|
||||
#include "Baker.h"
|
||||
|
||||
#include <shared/HifiTypes.h>
|
||||
|
||||
#include "BakerTypes.h"
|
||||
#include "ModelMath.h"
|
||||
#include "BuildGraphicsMeshTask.h"
|
||||
#include "CalculateMeshNormalsTask.h"
|
||||
#include "CalculateMeshTangentsTask.h"
|
||||
#include "CalculateBlendshapeNormalsTask.h"
|
||||
#include "CalculateBlendshapeTangentsTask.h"
|
||||
#include "PrepareJointsTask.h"
|
||||
#include "BuildDracoMeshTask.h"
|
||||
#include "ParseFlowDataTask.h"
|
||||
|
||||
namespace baker {
|
||||
|
@ -60,12 +60,12 @@ namespace baker {
|
|||
blendshapesPerMeshOut = blendshapesPerMeshIn;
|
||||
|
||||
for (int i = 0; i < (int)blendshapesPerMeshOut.size(); i++) {
|
||||
const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i];
|
||||
const auto& tangentsPerBlendshape = tangentsPerBlendshapePerMesh[i];
|
||||
const auto& normalsPerBlendshape = safeGet(normalsPerBlendshapePerMesh, i);
|
||||
const auto& tangentsPerBlendshape = safeGet(tangentsPerBlendshapePerMesh, i);
|
||||
auto& blendshapesOut = blendshapesPerMeshOut[i];
|
||||
for (int j = 0; j < (int)blendshapesOut.size(); j++) {
|
||||
const auto& normals = normalsPerBlendshape[j];
|
||||
const auto& tangents = tangentsPerBlendshape[j];
|
||||
const auto& normals = safeGet(normalsPerBlendshape, j);
|
||||
const auto& tangents = safeGet(tangentsPerBlendshape, j);
|
||||
auto& blendshape = blendshapesOut[j];
|
||||
blendshape.normals = QVector<glm::vec3>::fromStdVector(normals);
|
||||
blendshape.tangents = QVector<glm::vec3>::fromStdVector(tangents);
|
||||
|
@ -91,10 +91,10 @@ namespace baker {
|
|||
auto meshesOut = meshesIn;
|
||||
for (int i = 0; i < numMeshes; i++) {
|
||||
auto& meshOut = meshesOut[i];
|
||||
meshOut._mesh = graphicsMeshesIn[i];
|
||||
meshOut.normals = QVector<glm::vec3>::fromStdVector(normalsPerMeshIn[i]);
|
||||
meshOut.tangents = QVector<glm::vec3>::fromStdVector(tangentsPerMeshIn[i]);
|
||||
meshOut.blendshapes = QVector<hfm::Blendshape>::fromStdVector(blendshapesPerMeshIn[i]);
|
||||
meshOut._mesh = safeGet(graphicsMeshesIn, i);
|
||||
meshOut.normals = QVector<glm::vec3>::fromStdVector(safeGet(normalsPerMeshIn, i));
|
||||
meshOut.tangents = QVector<glm::vec3>::fromStdVector(safeGet(tangentsPerMeshIn, i));
|
||||
meshOut.blendshapes = QVector<hfm::Blendshape>::fromStdVector(safeGet(blendshapesPerMeshIn, i));
|
||||
}
|
||||
output = meshesOut;
|
||||
}
|
||||
|
@ -119,12 +119,13 @@ namespace baker {
|
|||
|
||||
class BakerEngineBuilder {
|
||||
public:
|
||||
using Input = VaryingSet2<hfm::Model::Pointer, GeometryMappingPair>;
|
||||
using Output = VaryingSet2<hfm::Model::Pointer, MaterialMapping>;
|
||||
using Input = VaryingSet3<hfm::Model::Pointer, hifi::VariantHash, hifi::URL>;
|
||||
using Output = VaryingSet4<hfm::Model::Pointer, MaterialMapping, std::vector<hifi::ByteArray>, std::vector<std::vector<hifi::ByteArray>>>;
|
||||
using JobModel = Task::ModelIO<BakerEngineBuilder, Input, Output>;
|
||||
void build(JobModel& model, const Varying& input, Varying& output) {
|
||||
const auto& hfmModelIn = input.getN<Input>(0);
|
||||
const auto& mapping = input.getN<Input>(1);
|
||||
const auto& materialMappingBaseURL = input.getN<Input>(2);
|
||||
|
||||
// Split up the inputs from hfm::Model
|
||||
const auto modelPartsIn = model.addJob<GetModelPartsTask>("GetModelParts", hfmModelIn);
|
||||
|
@ -157,7 +158,18 @@ namespace baker {
|
|||
const auto jointIndices = jointInfoOut.getN<PrepareJointsTask::Output>(2);
|
||||
|
||||
// Parse material mapping
|
||||
const auto materialMapping = model.addJob<ParseMaterialMappingTask>("ParseMaterialMapping", mapping);
|
||||
const auto parseMaterialMappingInputs = ParseMaterialMappingTask::Input(mapping, materialMappingBaseURL).asVarying();
|
||||
const auto materialMapping = model.addJob<ParseMaterialMappingTask>("ParseMaterialMapping", parseMaterialMappingInputs);
|
||||
|
||||
// Build Draco meshes
|
||||
// NOTE: This task is disabled by default and must be enabled through configuration
|
||||
// TODO: Tangent support (Needs changes to FBXSerializer_Mesh as well)
|
||||
// NOTE: Due to an unresolved linker error, BuildDracoMeshTask is not functional on Android
|
||||
// TODO: Figure out why BuildDracoMeshTask.cpp won't link with draco on Android
|
||||
const auto buildDracoMeshInputs = BuildDracoMeshTask::Input(meshesIn, normalsPerMesh, tangentsPerMesh).asVarying();
|
||||
const auto buildDracoMeshOutputs = model.addJob<BuildDracoMeshTask>("BuildDracoMesh", buildDracoMeshInputs);
|
||||
const auto dracoMeshes = buildDracoMeshOutputs.getN<BuildDracoMeshTask::Output>(0);
|
||||
const auto materialList = buildDracoMeshOutputs.getN<BuildDracoMeshTask::Output>(1);
|
||||
|
||||
// Parse flow data
|
||||
const auto flowData = model.addJob<ParseFlowDataTask>("ParseFlowData", mapping);
|
||||
|
@ -170,20 +182,38 @@ namespace baker {
|
|||
const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut, jointsOut, jointRotationOffsets, jointIndices, flowData).asVarying();
|
||||
const auto hfmModelOut = model.addJob<BuildModelTask>("BuildModel", buildModelInputs);
|
||||
|
||||
output = Output(hfmModelOut, materialMapping);
|
||||
output = Output(hfmModelOut, materialMapping, dracoMeshes, materialList);
|
||||
}
|
||||
};
|
||||
|
||||
Baker::Baker(const hfm::Model::Pointer& hfmModel, const GeometryMappingPair& mapping) :
|
||||
Baker::Baker(const hfm::Model::Pointer& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& materialMappingBaseURL) :
|
||||
_engine(std::make_shared<Engine>(BakerEngineBuilder::JobModel::create("Baker"), std::make_shared<BakeContext>())) {
|
||||
_engine->feedInput<BakerEngineBuilder::Input>(0, hfmModel);
|
||||
_engine->feedInput<BakerEngineBuilder::Input>(1, mapping);
|
||||
_engine->feedInput<BakerEngineBuilder::Input>(2, materialMappingBaseURL);
|
||||
}
|
||||
|
||||
std::shared_ptr<TaskConfig> Baker::getConfiguration() {
|
||||
return _engine->getConfiguration();
|
||||
}
|
||||
|
||||
void Baker::run() {
|
||||
_engine->run();
|
||||
hfmModel = _engine->getOutput().get<BakerEngineBuilder::Output>().get0();
|
||||
materialMapping = _engine->getOutput().get<BakerEngineBuilder::Output>().get1();
|
||||
}
|
||||
|
||||
hfm::Model::Pointer Baker::getHFMModel() const {
|
||||
return _engine->getOutput().get<BakerEngineBuilder::Output>().get0();
|
||||
}
|
||||
|
||||
MaterialMapping Baker::getMaterialMapping() const {
|
||||
return _engine->getOutput().get<BakerEngineBuilder::Output>().get1();
|
||||
}
|
||||
|
||||
const std::vector<hifi::ByteArray>& Baker::getDracoMeshes() const {
|
||||
return _engine->getOutput().get<BakerEngineBuilder::Output>().get2();
|
||||
}
|
||||
|
||||
std::vector<std::vector<hifi::ByteArray>> Baker::getDracoMaterialLists() const {
|
||||
return _engine->getOutput().get<BakerEngineBuilder::Output>().get3();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,8 +12,7 @@
|
|||
#ifndef hifi_baker_Baker_h
|
||||
#define hifi_baker_Baker_h
|
||||
|
||||
#include <QMap>
|
||||
|
||||
#include <shared/HifiTypes.h>
|
||||
#include <hfm/HFM.h>
|
||||
|
||||
#include "Engine.h"
|
||||
|
@ -24,18 +23,22 @@
|
|||
namespace baker {
|
||||
class Baker {
|
||||
public:
|
||||
Baker(const hfm::Model::Pointer& hfmModel, const GeometryMappingPair& mapping);
|
||||
Baker(const hfm::Model::Pointer& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& materialMappingBaseURL);
|
||||
|
||||
std::shared_ptr<TaskConfig> getConfiguration();
|
||||
|
||||
void run();
|
||||
|
||||
// Outputs, available after run() is called
|
||||
hfm::Model::Pointer hfmModel;
|
||||
MaterialMapping materialMapping;
|
||||
hfm::Model::Pointer getHFMModel() const;
|
||||
MaterialMapping getMaterialMapping() const;
|
||||
const std::vector<hifi::ByteArray>& getDracoMeshes() const;
|
||||
// This is a ByteArray and not a std::string because the character sequence can contain the null character (particularly for FBX materials)
|
||||
std::vector<std::vector<hifi::ByteArray>> getDracoMaterialLists() const;
|
||||
|
||||
protected:
|
||||
EnginePointer _engine;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif //hifi_baker_Baker_h
|
||||
|
|
|
@ -36,7 +36,6 @@ namespace baker {
|
|||
using TangentsPerBlendshape = std::vector<std::vector<glm::vec3>>;
|
||||
|
||||
using MeshIndicesToModelNames = QHash<int, QString>;
|
||||
using GeometryMappingPair = std::pair<QUrl, QVariantHash>;
|
||||
};
|
||||
|
||||
#endif // hifi_BakerTypes_h
|
||||
|
|
251
libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp
Normal file
251
libraries/model-baker/src/model-baker/BuildDracoMeshTask.cpp
Normal file
|
@ -0,0 +1,251 @@
|
|||
//
|
||||
// BuildDracoMeshTask.cpp
|
||||
// model-baker/src/model-baker
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/02/20.
|
||||
// Copyright 2019 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 "BuildDracoMeshTask.h"
|
||||
|
||||
// Fix build warnings due to draco headers not casting size_t
|
||||
#ifdef _WIN32
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4267 )
|
||||
#endif
|
||||
// gcc and clang
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <draco/compression/encode.h>
|
||||
#include <draco/mesh/triangle_soup_mesh_builder.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include "ModelBakerLogging.h"
|
||||
#include "ModelMath.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
std::vector<hifi::ByteArray> createMaterialList(const hfm::Mesh& mesh) {
|
||||
std::vector<hifi::ByteArray> materialList;
|
||||
for (const auto& meshPart : mesh.parts) {
|
||||
auto materialID = QVariant(meshPart.materialID).toByteArray();
|
||||
const auto materialIt = std::find(materialList.cbegin(), materialList.cend(), materialID);
|
||||
if (materialIt == materialList.cend()) {
|
||||
materialList.push_back(materialID);
|
||||
}
|
||||
}
|
||||
return materialList;
|
||||
}
|
||||
|
||||
std::unique_ptr<draco::Mesh> createDracoMesh(const hfm::Mesh& mesh, const std::vector<glm::vec3>& normals, const std::vector<glm::vec3>& tangents, const std::vector<hifi::ByteArray>& materialList) {
|
||||
Q_ASSERT(normals.size() == 0 || normals.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());
|
||||
|
||||
int64_t numTriangles{ 0 };
|
||||
for (auto& part : mesh.parts) {
|
||||
int extraQuadTriangleIndices = part.quadTrianglesIndices.size() % 3;
|
||||
int extraTriangleIndices = part.triangleIndices.size() % 3;
|
||||
if (extraQuadTriangleIndices != 0 || extraTriangleIndices != 0) {
|
||||
qCWarning(model_baker) << "Found a mesh part with indices not divisible by three. Some indices will be discarded during Draco mesh creation.";
|
||||
}
|
||||
numTriangles += (part.quadTrianglesIndices.size() - extraQuadTriangleIndices) / 3;
|
||||
numTriangles += (part.triangleIndices.size() - extraTriangleIndices) / 3;
|
||||
}
|
||||
|
||||
if (numTriangles == 0) {
|
||||
return std::unique_ptr<draco::Mesh>();
|
||||
}
|
||||
|
||||
draco::TriangleSoupMeshBuilder meshBuilder;
|
||||
|
||||
meshBuilder.Start(numTriangles);
|
||||
|
||||
bool hasNormals{ normals.size() > 0 };
|
||||
bool hasColors{ mesh.colors.size() > 0 };
|
||||
bool hasTexCoords{ mesh.texCoords.size() > 0 };
|
||||
bool hasTexCoords1{ mesh.texCoords1.size() > 0 };
|
||||
bool hasPerFaceMaterials{ mesh.parts.size() > 1 };
|
||||
bool needsOriginalIndices{ (!mesh.clusterIndices.empty() || !mesh.blendshapes.empty()) && mesh.originalIndices.size() > 0 };
|
||||
|
||||
int normalsAttributeID { -1 };
|
||||
int colorsAttributeID { -1 };
|
||||
int texCoordsAttributeID { -1 };
|
||||
int texCoords1AttributeID { -1 };
|
||||
int faceMaterialAttributeID { -1 };
|
||||
int originalIndexAttributeID { -1 };
|
||||
|
||||
const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION,
|
||||
3, draco::DT_FLOAT32);
|
||||
if (needsOriginalIndices) {
|
||||
originalIndexAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX,
|
||||
1, draco::DT_INT32);
|
||||
}
|
||||
|
||||
if (hasNormals) {
|
||||
normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasColors) {
|
||||
colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
texCoords1AttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasPerFaceMaterials) {
|
||||
faceMaterialAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID,
|
||||
1, draco::DT_UINT16);
|
||||
}
|
||||
|
||||
auto partIndex = 0;
|
||||
draco::FaceIndex face;
|
||||
uint16_t materialID;
|
||||
|
||||
for (auto& part : mesh.parts) {
|
||||
auto materialIt = std::find(materialList.cbegin(), materialList.cend(), QVariant(part.materialID).toByteArray());
|
||||
materialID = (uint16_t)(materialIt - materialList.cbegin());
|
||||
|
||||
auto addFace = [&](const QVector<int>& indices, int index, draco::FaceIndex face) {
|
||||
int32_t idx0 = indices[index];
|
||||
int32_t idx1 = indices[index + 1];
|
||||
int32_t idx2 = indices[index + 2];
|
||||
|
||||
if (hasPerFaceMaterials) {
|
||||
meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID);
|
||||
}
|
||||
|
||||
meshBuilder.SetAttributeValuesForFace(positionAttributeID, face,
|
||||
&mesh.vertices[idx0], &mesh.vertices[idx1],
|
||||
&mesh.vertices[idx2]);
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face,
|
||||
&mesh.originalIndices[idx0],
|
||||
&mesh.originalIndices[idx1],
|
||||
&mesh.originalIndices[idx2]);
|
||||
}
|
||||
if (hasNormals) {
|
||||
meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face,
|
||||
&normals[idx0], &normals[idx1],
|
||||
&normals[idx2]);
|
||||
}
|
||||
if (hasColors) {
|
||||
meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face,
|
||||
&mesh.colors[idx0], &mesh.colors[idx1],
|
||||
&mesh.colors[idx2]);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face,
|
||||
&mesh.texCoords[idx0], &mesh.texCoords[idx1],
|
||||
&mesh.texCoords[idx2]);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face,
|
||||
&mesh.texCoords1[idx0], &mesh.texCoords1[idx1],
|
||||
&mesh.texCoords1[idx2]);
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) {
|
||||
addFace(part.quadTrianglesIndices, i, face++);
|
||||
}
|
||||
|
||||
for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) {
|
||||
addFace(part.triangleIndices, i, face++);
|
||||
}
|
||||
|
||||
partIndex++;
|
||||
}
|
||||
|
||||
auto dracoMesh = meshBuilder.Finalize();
|
||||
|
||||
if (!dracoMesh) {
|
||||
qCWarning(model_baker) << "Failed to finalize the baking of a draco Geometry node";
|
||||
return std::unique_ptr<draco::Mesh>();
|
||||
}
|
||||
|
||||
// we need to modify unique attribute IDs for custom attributes
|
||||
// so the attributes are easily retrievable on the other side
|
||||
if (hasPerFaceMaterials) {
|
||||
dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID);
|
||||
}
|
||||
|
||||
if (hasTexCoords1) {
|
||||
dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1);
|
||||
}
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX);
|
||||
}
|
||||
|
||||
return dracoMesh;
|
||||
}
|
||||
#endif // not Q_OS_ANDROID
|
||||
|
||||
void BuildDracoMeshTask::configure(const Config& config) {
|
||||
_encodeSpeed = config.encodeSpeed;
|
||||
_decodeSpeed = config.decodeSpeed;
|
||||
}
|
||||
|
||||
void BuildDracoMeshTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) {
|
||||
#ifdef Q_OS_ANDROID
|
||||
qCWarning(model_baker) << "BuildDracoMesh is disabled on Android. Output meshes will be empty.";
|
||||
#else
|
||||
const auto& meshes = input.get0();
|
||||
const auto& normalsPerMesh = input.get1();
|
||||
const auto& tangentsPerMesh = input.get2();
|
||||
auto& dracoBytesPerMesh = output.edit0();
|
||||
auto& materialLists = output.edit1();
|
||||
|
||||
dracoBytesPerMesh.reserve(meshes.size());
|
||||
materialLists.reserve(meshes.size());
|
||||
for (size_t i = 0; i < meshes.size(); i++) {
|
||||
const auto& mesh = meshes[i];
|
||||
const auto& normals = baker::safeGet(normalsPerMesh, i);
|
||||
const auto& tangents = baker::safeGet(tangentsPerMesh, i);
|
||||
dracoBytesPerMesh.emplace_back();
|
||||
auto& dracoBytes = dracoBytesPerMesh.back();
|
||||
materialLists.push_back(createMaterialList(mesh));
|
||||
const auto& materialList = materialLists.back();
|
||||
|
||||
auto dracoMesh = createDracoMesh(mesh, normals, tangents, materialList);
|
||||
|
||||
if (dracoMesh) {
|
||||
draco::Encoder encoder;
|
||||
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10);
|
||||
encoder.SetSpeedOptions(_encodeSpeed, _decodeSpeed);
|
||||
|
||||
draco::EncoderBuffer buffer;
|
||||
encoder.EncodeMeshToBuffer(*dracoMesh, &buffer);
|
||||
|
||||
dracoBytes = hifi::ByteArray(buffer.data(), (int)buffer.size());
|
||||
}
|
||||
}
|
||||
#endif // not Q_OS_ANDROID
|
||||
}
|
48
libraries/model-baker/src/model-baker/BuildDracoMeshTask.h
Normal file
48
libraries/model-baker/src/model-baker/BuildDracoMeshTask.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// BuildDracoMeshTask.h
|
||||
// model-baker/src/model-baker
|
||||
//
|
||||
// Created by Sabrina Shanman on 2019/02/20.
|
||||
// Copyright 2019 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_BuildDracoMeshTask_h
|
||||
#define hifi_BuildDracoMeshTask_h
|
||||
|
||||
#include <hfm/HFM.h>
|
||||
#include <shared/HifiTypes.h>
|
||||
|
||||
#include "Engine.h"
|
||||
#include "BakerTypes.h"
|
||||
|
||||
// BuildDracoMeshTask is disabled by default
|
||||
class BuildDracoMeshConfig : public baker::JobConfig {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int encodeSpeed MEMBER encodeSpeed)
|
||||
Q_PROPERTY(int decodeSpeed MEMBER decodeSpeed)
|
||||
public:
|
||||
BuildDracoMeshConfig() : baker::JobConfig(false) {}
|
||||
|
||||
int encodeSpeed { 0 };
|
||||
int decodeSpeed { 5 };
|
||||
};
|
||||
|
||||
class BuildDracoMeshTask {
|
||||
public:
|
||||
using Config = BuildDracoMeshConfig;
|
||||
using Input = baker::VaryingSet3<std::vector<hfm::Mesh>, baker::NormalsPerMesh, baker::TangentsPerMesh>;
|
||||
using Output = baker::VaryingSet2<std::vector<hifi::ByteArray>, std::vector<std::vector<hifi::ByteArray>>>;
|
||||
using JobModel = baker::Job::ModelIO<BuildDracoMeshTask, Input, Output, Config>;
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
|
||||
|
||||
protected:
|
||||
int _encodeSpeed { 0 };
|
||||
int _decodeSpeed { 5 };
|
||||
};
|
||||
|
||||
#endif // hifi_BuildDracoMeshTask_h
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <LogHandler.h>
|
||||
#include "ModelBakerLogging.h"
|
||||
#include "ModelMath.h"
|
||||
|
||||
using vec2h = glm::tvec2<glm::detail::hdata>;
|
||||
|
||||
|
@ -385,7 +386,7 @@ void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const
|
|||
auto& graphicsMesh = graphicsMeshes[i];
|
||||
|
||||
// Try to create the graphics::Mesh
|
||||
buildGraphicsMesh(meshes[i], graphicsMesh, normalsPerMesh[i], tangentsPerMesh[i]);
|
||||
buildGraphicsMesh(meshes[i], graphicsMesh, baker::safeGet(normalsPerMesh, i), baker::safeGet(tangentsPerMesh, i));
|
||||
|
||||
// Choose a name for the mesh
|
||||
if (graphicsMesh) {
|
||||
|
|
|
@ -24,7 +24,7 @@ void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& conte
|
|||
|
||||
tangentsPerBlendshapePerMeshOut.reserve(normalsPerBlendshapePerMesh.size());
|
||||
for (size_t i = 0; i < blendshapesPerMesh.size(); i++) {
|
||||
const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i];
|
||||
const auto& normalsPerBlendshape = baker::safeGet(normalsPerBlendshapePerMesh, i);
|
||||
const auto& blendshapes = blendshapesPerMesh[i];
|
||||
const auto& mesh = meshes[i];
|
||||
tangentsPerBlendshapePerMeshOut.emplace_back();
|
||||
|
@ -43,7 +43,7 @@ void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& conte
|
|||
for (size_t j = 0; j < blendshapes.size(); j++) {
|
||||
const auto& blendshape = blendshapes[j];
|
||||
const auto& tangentsIn = blendshape.tangents;
|
||||
const auto& normals = normalsPerBlendshape[j];
|
||||
const auto& normals = baker::safeGet(normalsPerBlendshape, j);
|
||||
tangentsPerBlendshapeOut.emplace_back();
|
||||
auto& tangentsOut = tangentsPerBlendshapeOut[tangentsPerBlendshapeOut.size()-1];
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co
|
|||
for (int i = 0; i < (int)meshes.size(); i++) {
|
||||
const auto& mesh = meshes[i];
|
||||
const auto& tangentsIn = mesh.tangents;
|
||||
const auto& normals = normalsPerMesh[i];
|
||||
const auto& normals = baker::safeGet(normalsPerMesh, i);
|
||||
tangentsPerMeshOut.emplace_back();
|
||||
auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1];
|
||||
|
||||
|
|
|
@ -14,6 +14,17 @@
|
|||
#include "BakerTypes.h"
|
||||
|
||||
namespace baker {
|
||||
template<typename T>
|
||||
const T& safeGet(const std::vector<T>& data, size_t i) {
|
||||
static T t;
|
||||
|
||||
if (data.size() > i) {
|
||||
return data[i];
|
||||
} else {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a reference to the normal at the specified index, or nullptr if it cannot be accessed
|
||||
using NormalAccessor = std::function<glm::vec3*(int index)>;
|
||||
|
||||
|
|
|
@ -8,12 +8,11 @@
|
|||
|
||||
#include "ParseFlowDataTask.h"
|
||||
|
||||
void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Input& mappingPair, Output& output) {
|
||||
void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Input& mapping, Output& output) {
|
||||
FlowData flowData;
|
||||
static const QString FLOW_PHYSICS_FIELD = "flowPhysicsData";
|
||||
static const QString FLOW_COLLISIONS_FIELD = "flowCollisionsData";
|
||||
auto mapping = mappingPair.second;
|
||||
for (auto mappingIter = mapping.begin(); mappingIter != mapping.end(); mappingIter++) {
|
||||
for (auto mappingIter = mapping.cbegin(); mappingIter != mapping.cend(); mappingIter++) {
|
||||
if (mappingIter.key() == FLOW_PHYSICS_FIELD || mappingIter.key() == FLOW_COLLISIONS_FIELD) {
|
||||
QByteArray data = mappingIter.value().toByteArray();
|
||||
QJsonObject dataObject = QJsonDocument::fromJson(data).object();
|
||||
|
|
|
@ -12,11 +12,13 @@
|
|||
#include <hfm/HFM.h>
|
||||
#include "Engine.h"
|
||||
|
||||
#include <shared/HifiTypes.h>
|
||||
|
||||
#include "BakerTypes.h"
|
||||
|
||||
class ParseFlowDataTask {
|
||||
public:
|
||||
using Input = baker::GeometryMappingPair;
|
||||
using Input = hifi::VariantHash;
|
||||
using Output = FlowData;
|
||||
using JobModel = baker::Job::ModelIO<ParseFlowDataTask, Input, Output>;
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
#include "ModelBakerLogging.h"
|
||||
|
||||
void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) {
|
||||
const auto& url = input.first;
|
||||
const auto& mapping = input.second;
|
||||
const auto& mapping = input.get0();
|
||||
const auto& url = input.get1();
|
||||
MaterialMapping materialMapping;
|
||||
|
||||
auto mappingIter = mapping.find("materialMap");
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include <hfm/HFM.h>
|
||||
|
||||
#include <shared/HifiTypes.h>
|
||||
|
||||
#include "Engine.h"
|
||||
#include "BakerTypes.h"
|
||||
|
||||
|
@ -20,7 +22,7 @@
|
|||
|
||||
class ParseMaterialMappingTask {
|
||||
public:
|
||||
using Input = baker::GeometryMappingPair;
|
||||
using Input = baker::VaryingSet2<hifi::VariantHash, hifi::URL>;
|
||||
using Output = MaterialMapping;
|
||||
using JobModel = baker::Job::ModelIO<ParseMaterialMappingTask, Input, Output>;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "ModelBakerLogging.h"
|
||||
|
||||
QMap<QString, QString> getJointNameMapping(const QVariantHash& mapping) {
|
||||
QMap<QString, QString> getJointNameMapping(const hifi::VariantHash& mapping) {
|
||||
static const QString JOINT_NAME_MAPPING_FIELD = "jointMap";
|
||||
QMap<QString, QString> hfmToHifiJointNameMap;
|
||||
if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) {
|
||||
|
@ -26,7 +26,7 @@ QMap<QString, QString> getJointNameMapping(const QVariantHash& mapping) {
|
|||
return hfmToHifiJointNameMap;
|
||||
}
|
||||
|
||||
QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
|
||||
QMap<QString, glm::quat> getJointRotationOffsets(const hifi::VariantHash& mapping) {
|
||||
QMap<QString, glm::quat> jointRotationOffsets;
|
||||
static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset";
|
||||
static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2";
|
||||
|
@ -56,69 +56,76 @@ QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
|
|||
return jointRotationOffsets;
|
||||
}
|
||||
|
||||
void PrepareJointsTask::configure(const Config& config) {
|
||||
_passthrough = config.passthrough;
|
||||
}
|
||||
|
||||
void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) {
|
||||
const auto& jointsIn = input.get0();
|
||||
const auto& mapping = input.get1();
|
||||
auto& jointsOut = output.edit0();
|
||||
auto& jointRotationOffsets = output.edit1();
|
||||
auto& jointIndices = output.edit2();
|
||||
|
||||
bool newJointRot = false;
|
||||
static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2";
|
||||
QVariantHash fstHashMap = mapping.second;
|
||||
if (fstHashMap.contains(JOINT_ROTATION_OFFSET2_FIELD)) {
|
||||
newJointRot = true;
|
||||
if (_passthrough) {
|
||||
jointsOut = jointsIn;
|
||||
} else {
|
||||
newJointRot = false;
|
||||
}
|
||||
const auto& mapping = input.get1();
|
||||
auto& jointRotationOffsets = output.edit1();
|
||||
auto& jointIndices = output.edit2();
|
||||
|
||||
// Get joint renames
|
||||
auto jointNameMapping = getJointNameMapping(mapping.second);
|
||||
// Apply joint metadata from FST file mappings
|
||||
for (const auto& jointIn : jointsIn) {
|
||||
jointsOut.push_back(jointIn);
|
||||
auto& jointOut = jointsOut.back();
|
||||
bool newJointRot = false;
|
||||
static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2";
|
||||
QVariantHash fstHashMap = mapping;
|
||||
if (fstHashMap.contains(JOINT_ROTATION_OFFSET2_FIELD)) {
|
||||
newJointRot = true;
|
||||
} else {
|
||||
newJointRot = false;
|
||||
}
|
||||
|
||||
if (!newJointRot) {
|
||||
auto jointNameMapKey = jointNameMapping.key(jointIn.name);
|
||||
if (jointNameMapping.contains(jointNameMapKey)) {
|
||||
jointOut.name = jointNameMapKey;
|
||||
// Get joint renames
|
||||
auto jointNameMapping = getJointNameMapping(mapping);
|
||||
// Apply joint metadata from FST file mappings
|
||||
for (const auto& jointIn : jointsIn) {
|
||||
jointsOut.push_back(jointIn);
|
||||
auto& jointOut = jointsOut.back();
|
||||
|
||||
if (!newJointRot) {
|
||||
auto jointNameMapKey = jointNameMapping.key(jointIn.name);
|
||||
if (jointNameMapping.contains(jointNameMapKey)) {
|
||||
jointOut.name = jointNameMapKey;
|
||||
}
|
||||
}
|
||||
jointIndices.insert(jointOut.name, (int)jointsOut.size());
|
||||
}
|
||||
|
||||
// Get joint rotation offsets from FST file mappings
|
||||
auto offsets = getJointRotationOffsets(mapping);
|
||||
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||
QString jointName = itr.key();
|
||||
int jointIndex = jointIndices.value(jointName) - 1;
|
||||
if (jointIndex >= 0) {
|
||||
glm::quat rotationOffset = itr.value();
|
||||
jointRotationOffsets.insert(jointIndex, rotationOffset);
|
||||
qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset;
|
||||
}
|
||||
}
|
||||
jointIndices.insert(jointOut.name, (int)jointsOut.size());
|
||||
}
|
||||
|
||||
// Get joint rotation offsets from FST file mappings
|
||||
auto offsets = getJointRotationOffsets(mapping.second);
|
||||
for (auto itr = offsets.begin(); itr != offsets.end(); itr++) {
|
||||
QString jointName = itr.key();
|
||||
int jointIndex = jointIndices.value(jointName) - 1;
|
||||
if (jointIndex >= 0) {
|
||||
glm::quat rotationOffset = itr.value();
|
||||
jointRotationOffsets.insert(jointIndex, rotationOffset);
|
||||
qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset;
|
||||
}
|
||||
}
|
||||
|
||||
if (newJointRot) {
|
||||
for (auto& jointOut : jointsOut) {
|
||||
|
||||
auto jointNameMapKey = jointNameMapping.key(jointOut.name);
|
||||
int mappedIndex = jointIndices.value(jointOut.name);
|
||||
if (jointNameMapping.contains(jointNameMapKey)) {
|
||||
// delete and replace with hifi name
|
||||
jointIndices.remove(jointOut.name);
|
||||
jointOut.name = jointNameMapKey;
|
||||
jointIndices.insert(jointOut.name, mappedIndex);
|
||||
} else {
|
||||
|
||||
// nothing mapped to this fbx joint name
|
||||
if (jointNameMapping.contains(jointOut.name)) {
|
||||
// but the name is in the list of hifi names is mapped to a different joint
|
||||
int extraIndex = jointIndices.value(jointOut.name);
|
||||
if (newJointRot) {
|
||||
for (auto& jointOut : jointsOut) {
|
||||
auto jointNameMapKey = jointNameMapping.key(jointOut.name);
|
||||
int mappedIndex = jointIndices.value(jointOut.name);
|
||||
if (jointNameMapping.contains(jointNameMapKey)) {
|
||||
// delete and replace with hifi name
|
||||
jointIndices.remove(jointOut.name);
|
||||
jointOut.name = "";
|
||||
jointIndices.insert(jointOut.name, extraIndex);
|
||||
jointOut.name = jointNameMapKey;
|
||||
jointIndices.insert(jointOut.name, mappedIndex);
|
||||
} else {
|
||||
// nothing mapped to this fbx joint name
|
||||
if (jointNameMapping.contains(jointOut.name)) {
|
||||
// but the name is in the list of hifi names is mapped to a different joint
|
||||
int extraIndex = jointIndices.value(jointOut.name);
|
||||
jointIndices.remove(jointOut.name);
|
||||
jointOut.name = "";
|
||||
jointIndices.insert(jointOut.name, extraIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,20 +12,32 @@
|
|||
#ifndef hifi_PrepareJointsTask_h
|
||||
#define hifi_PrepareJointsTask_h
|
||||
|
||||
#include <QHash>
|
||||
|
||||
#include <shared/HifiTypes.h>
|
||||
#include <hfm/HFM.h>
|
||||
|
||||
#include "Engine.h"
|
||||
#include "BakerTypes.h"
|
||||
|
||||
// The property "passthrough", when enabled, will let the input joints flow to the output unmodified, unlike the disabled property, which discards the data
|
||||
class PrepareJointsConfig : public baker::JobConfig {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool passthrough MEMBER passthrough)
|
||||
public:
|
||||
bool passthrough { false };
|
||||
};
|
||||
|
||||
class PrepareJointsTask {
|
||||
public:
|
||||
using Input = baker::VaryingSet2<std::vector<hfm::Joint>, baker::GeometryMappingPair /*mapping*/>;
|
||||
using Config = PrepareJointsConfig;
|
||||
using Input = baker::VaryingSet2<std::vector<hfm::Joint>, hifi::VariantHash /*mapping*/>;
|
||||
using Output = baker::VaryingSet3<std::vector<hfm::Joint>, QMap<int, glm::quat> /*jointRotationOffsets*/, QHash<QString, int> /*jointIndices*/>;
|
||||
using JobModel = baker::Job::ModelIO<PrepareJointsTask, Input, Output>;
|
||||
using JobModel = baker::Job::ModelIO<PrepareJointsTask, Input, Output, Config>;
|
||||
|
||||
void configure(const Config& config);
|
||||
void run(const baker::BakeContextPointer& context, const Input& input, Output& output);
|
||||
|
||||
protected:
|
||||
bool _passthrough { false };
|
||||
};
|
||||
|
||||
#endif // hifi_PrepareJointsTask_h
|
|
@ -120,19 +120,21 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
if (filename.isNull()) {
|
||||
finishedLoading(false);
|
||||
} else {
|
||||
QUrl url = _url.resolved(filename);
|
||||
const QString baseURL = _mapping.value("baseURL").toString();
|
||||
const QUrl base = _effectiveBaseURL.resolved(baseURL);
|
||||
QUrl url = base.resolved(filename);
|
||||
|
||||
QString texdir = _mapping.value(TEXDIR_FIELD).toString();
|
||||
if (!texdir.isNull()) {
|
||||
if (!texdir.endsWith('/')) {
|
||||
texdir += '/';
|
||||
}
|
||||
_textureBaseUrl = resolveTextureBaseUrl(url, _url.resolved(texdir));
|
||||
_textureBaseUrl = resolveTextureBaseUrl(url, base.resolved(texdir));
|
||||
} else {
|
||||
_textureBaseUrl = url.resolved(QUrl("."));
|
||||
}
|
||||
|
||||
auto scripts = FSTReader::getScripts(_url, _mapping);
|
||||
auto scripts = FSTReader::getScripts(base, _mapping);
|
||||
if (scripts.size() > 0) {
|
||||
_mapping.remove(SCRIPT_FIELD);
|
||||
for (auto &scriptPath : scripts) {
|
||||
|
@ -145,7 +147,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
if (animGraphVariant.isValid()) {
|
||||
QUrl fstUrl(animGraphVariant.toString());
|
||||
if (fstUrl.isValid()) {
|
||||
_animGraphOverrideUrl = _url.resolved(fstUrl);
|
||||
_animGraphOverrideUrl = base.resolved(fstUrl);
|
||||
} else {
|
||||
_animGraphOverrideUrl = QUrl();
|
||||
}
|
||||
|
@ -154,7 +156,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
}
|
||||
|
||||
auto modelCache = DependencyManager::get<ModelCache>();
|
||||
GeometryExtra extra { GeometryMappingPair(_url, _mapping), _textureBaseUrl, false };
|
||||
GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseUrl, false };
|
||||
|
||||
// Get the raw GeometryResource
|
||||
_geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash<GeometryExtra>()(extra)).staticCast<GeometryResource>();
|
||||
|
@ -249,6 +251,7 @@ void GeometryReader::run() {
|
|||
HFMModel::Pointer hfmModel;
|
||||
QVariantHash serializerMapping = _mapping.second;
|
||||
serializerMapping["combineParts"] = _combineParts;
|
||||
serializerMapping["deduplicateIndices"] = true;
|
||||
|
||||
if (_url.path().toLower().endsWith(".gz")) {
|
||||
QByteArray uncompressedData;
|
||||
|
@ -339,12 +342,12 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
|
|||
|
||||
void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel, const GeometryMappingPair& mapping) {
|
||||
// Do processing on the model
|
||||
baker::Baker modelBaker(hfmModel, mapping);
|
||||
baker::Baker modelBaker(hfmModel, mapping.second, mapping.first);
|
||||
modelBaker.run();
|
||||
|
||||
// Assume ownership of the processed HFMModel
|
||||
_hfmModel = modelBaker.hfmModel;
|
||||
_materialMapping = modelBaker.materialMapping;
|
||||
_hfmModel = modelBaker.getHFMModel();
|
||||
_materialMapping = modelBaker.getMaterialMapping();
|
||||
|
||||
// Copy materials
|
||||
QHash<QString, size_t> materialIDAtlas;
|
||||
|
|
|
@ -834,6 +834,7 @@ bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger)
|
|||
}
|
||||
|
||||
_domainURL = domainURL;
|
||||
_shareablePlaceName.clear();
|
||||
|
||||
// clear any current place information
|
||||
_rootPlaceID = QUuid();
|
||||
|
|
|
@ -29,7 +29,7 @@ float evalOpaqueFinalAlpha(float alpha, float mapAlpha) {
|
|||
<@include LightingModel.slh@>
|
||||
|
||||
void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion, float scattering) {
|
||||
if (alpha != 1.0) {
|
||||
if (alpha < 1.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness
|
|||
}
|
||||
|
||||
void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 lightmap) {
|
||||
if (alpha != 1.0) {
|
||||
if (alpha < 1.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float r
|
|||
}
|
||||
|
||||
void packDeferredFragmentUnlit(vec3 normal, float alpha, vec3 color) {
|
||||
if (alpha != 1.0) {
|
||||
if (alpha < 1.0) {
|
||||
discard;
|
||||
}
|
||||
_fragColor0 = vec4(color, packUnlit());
|
||||
|
|
|
@ -722,8 +722,6 @@ gpu::ShaderPointer GeometryCache::_unlitFadeShader;
|
|||
|
||||
render::ShapePipelinePointer GeometryCache::_simpleOpaquePipeline;
|
||||
render::ShapePipelinePointer GeometryCache::_simpleTransparentPipeline;
|
||||
render::ShapePipelinePointer GeometryCache::_forwardSimpleOpaquePipeline;
|
||||
render::ShapePipelinePointer GeometryCache::_forwardSimpleTransparentPipeline;
|
||||
render::ShapePipelinePointer GeometryCache::_simpleOpaqueFadePipeline;
|
||||
render::ShapePipelinePointer GeometryCache::_simpleTransparentFadePipeline;
|
||||
render::ShapePipelinePointer GeometryCache::_simpleWirePipeline;
|
||||
|
@ -803,8 +801,6 @@ void GeometryCache::initializeShapePipelines() {
|
|||
if (!_simpleOpaquePipeline) {
|
||||
_simpleOpaquePipeline = getShapePipeline(false, false, true, false);
|
||||
_simpleTransparentPipeline = getShapePipeline(false, true, true, false);
|
||||
_forwardSimpleOpaquePipeline = getShapePipeline(false, false, true, false, false, true);
|
||||
_forwardSimpleTransparentPipeline = getShapePipeline(false, true, true, false, false, true);
|
||||
_simpleOpaqueFadePipeline = getFadingShapePipeline(false, false, false, false, false);
|
||||
_simpleTransparentFadePipeline = getFadingShapePipeline(false, true, false, false, false);
|
||||
_simpleWirePipeline = getShapePipeline(false, false, true, true);
|
||||
|
@ -836,14 +832,6 @@ render::ShapePipelinePointer GeometryCache::getFadingShapePipeline(bool textured
|
|||
);
|
||||
}
|
||||
|
||||
render::ShapePipelinePointer GeometryCache::getOpaqueShapePipeline(bool isFading) {
|
||||
return isFading ? _simpleOpaqueFadePipeline : _simpleOpaquePipeline;
|
||||
}
|
||||
|
||||
render::ShapePipelinePointer GeometryCache::getTransparentShapePipeline(bool isFading) {
|
||||
return isFading ? _simpleTransparentFadePipeline : _simpleTransparentPipeline;
|
||||
}
|
||||
|
||||
void GeometryCache::renderShape(gpu::Batch& batch, Shape shape) {
|
||||
batch.setInputFormat(getSolidStreamFormat());
|
||||
_shapes[shape].draw(batch);
|
||||
|
@ -1029,7 +1017,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec2>& points, con
|
|||
int* colorData = new int[details.vertices];
|
||||
int* colorDataAt = colorData;
|
||||
|
||||
const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f);
|
||||
const glm::vec3 NORMAL(0.0f, 1.0f, 0.0f);
|
||||
auto pointCount = points.size();
|
||||
auto colorCount = colors.size();
|
||||
int compactColor = 0;
|
||||
|
@ -1107,7 +1095,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec3>& points, con
|
|||
int* colorData = new int[details.vertices];
|
||||
int* colorDataAt = colorData;
|
||||
|
||||
const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f);
|
||||
const glm::vec3 NORMAL(0.0f, 1.0f, 0.0f);
|
||||
auto pointCount = points.size();
|
||||
auto colorCount = colors.size();
|
||||
for (auto i = 0; i < pointCount; i++) {
|
||||
|
@ -1195,7 +1183,7 @@ void GeometryCache::updateVertices(int id, const QVector<glm::vec3>& points, con
|
|||
int* colorData = new int[details.vertices];
|
||||
int* colorDataAt = colorData;
|
||||
|
||||
const glm::vec3 NORMAL(0.0f, 0.0f, 1.0f);
|
||||
const glm::vec3 NORMAL(0.0f, 1.0f, 0.0f);
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
glm::vec3 point = points[i];
|
||||
glm::vec2 texCoord = texCoords[i];
|
||||
|
@ -2018,77 +2006,6 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm
|
|||
batch.draw(gpu::LINES, 2, 0);
|
||||
}
|
||||
|
||||
|
||||
void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec4& color, float glowIntensity, float glowWidth, int id) {
|
||||
|
||||
// Disable glow lines on OSX
|
||||
#ifndef Q_OS_WIN
|
||||
glowIntensity = 0.0f;
|
||||
#endif
|
||||
|
||||
if (glowIntensity <= 0.0f) {
|
||||
if (color.a >= 1.0f) {
|
||||
bindSimpleProgram(batch, false, false, false, true, true);
|
||||
} else {
|
||||
bindSimpleProgram(batch, false, true, false, true, true);
|
||||
}
|
||||
renderLine(batch, p1, p2, color, id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compile the shaders
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
auto program = gpu::Shader::createProgram(shader::render_utils::program::glowLine);
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
state->setDepthTest(true, false, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(true,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
|
||||
PrepareStencil::testMask(*state);
|
||||
_glowLinePipeline = gpu::Pipeline::create(program, state);
|
||||
});
|
||||
|
||||
batch.setPipeline(_glowLinePipeline);
|
||||
|
||||
Vec3Pair key(p1, p2);
|
||||
bool registered = (id != UNKNOWN_ID);
|
||||
BatchItemDetails& details = _registeredLine3DVBOs[id];
|
||||
|
||||
// if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed
|
||||
if (registered && details.isCreated) {
|
||||
Vec3Pair& lastKey = _lastRegisteredLine3D[id];
|
||||
if (lastKey != key) {
|
||||
details.clear();
|
||||
_lastRegisteredLine3D[id] = key;
|
||||
}
|
||||
}
|
||||
|
||||
const int NUM_VERTICES = 4;
|
||||
if (!details.isCreated) {
|
||||
details.isCreated = true;
|
||||
details.uniformBuffer = std::make_shared<gpu::Buffer>();
|
||||
|
||||
struct LineData {
|
||||
vec4 p1;
|
||||
vec4 p2;
|
||||
vec4 color;
|
||||
float width;
|
||||
};
|
||||
|
||||
LineData lineData { vec4(p1, 1.0f), vec4(p2, 1.0f), color, glowWidth };
|
||||
details.uniformBuffer->resize(sizeof(LineData));
|
||||
details.uniformBuffer->setSubData(0, lineData);
|
||||
}
|
||||
|
||||
// The shader requires no vertices, only uniforms.
|
||||
batch.setUniformBuffer(0, details.uniformBuffer);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, NUM_VERTICES, 0);
|
||||
}
|
||||
|
||||
void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
|
@ -2282,8 +2199,7 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp
|
|||
_unlitShader = _forwardUnlitShader;
|
||||
} else {
|
||||
_simpleShader = gpu::Shader::createProgram(simple_textured);
|
||||
// Use the forward pipeline for both here, otherwise transparents will be unlit
|
||||
_transparentShader = gpu::Shader::createProgram(forward_simple_textured_transparent);
|
||||
_transparentShader = gpu::Shader::createProgram(simple_transparent_textured);
|
||||
_unlitShader = gpu::Shader::createProgram(simple_textured_unlit);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -181,17 +181,6 @@ public:
|
|||
|
||||
static void initializeShapePipelines();
|
||||
|
||||
render::ShapePipelinePointer getOpaqueShapePipeline() { assert(_simpleOpaquePipeline != nullptr); return _simpleOpaquePipeline; }
|
||||
render::ShapePipelinePointer getTransparentShapePipeline() { assert(_simpleTransparentPipeline != nullptr); return _simpleTransparentPipeline; }
|
||||
render::ShapePipelinePointer getForwardOpaqueShapePipeline() { assert(_forwardSimpleOpaquePipeline != nullptr); return _forwardSimpleOpaquePipeline; }
|
||||
render::ShapePipelinePointer getForwardTransparentShapePipeline() { assert(_forwardSimpleTransparentPipeline != nullptr); return _forwardSimpleTransparentPipeline; }
|
||||
render::ShapePipelinePointer getOpaqueFadeShapePipeline() { assert(_simpleOpaqueFadePipeline != nullptr); return _simpleOpaqueFadePipeline; }
|
||||
render::ShapePipelinePointer getTransparentFadeShapePipeline() { assert(_simpleTransparentFadePipeline != nullptr); return _simpleTransparentFadePipeline; }
|
||||
render::ShapePipelinePointer getOpaqueShapePipeline(bool isFading);
|
||||
render::ShapePipelinePointer getTransparentShapePipeline(bool isFading);
|
||||
render::ShapePipelinePointer getWireShapePipeline() { assert(_simpleWirePipeline != nullptr); return GeometryCache::_simpleWirePipeline; }
|
||||
|
||||
|
||||
// Static (instanced) geometry
|
||||
void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer);
|
||||
void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer);
|
||||
|
@ -317,12 +306,6 @@ public:
|
|||
void renderLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec4& color1, const glm::vec4& color2, int id);
|
||||
|
||||
void renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec4& color, float glowIntensity, float glowWidth, int id);
|
||||
|
||||
void renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2, const glm::vec4& color, int id)
|
||||
{ renderGlowLine(batch, p1, p2, color, 1.0f, 0.05f, id); }
|
||||
|
||||
void renderDashedLine(gpu::Batch& batch, const glm::vec3& start, const glm::vec3& end, const glm::vec4& color, int id)
|
||||
{ renderDashedLine(batch, start, end, color, 0.05f, 0.025f, id); }
|
||||
|
||||
|
@ -478,12 +461,9 @@ private:
|
|||
static gpu::ShaderPointer _unlitFadeShader;
|
||||
static render::ShapePipelinePointer _simpleOpaquePipeline;
|
||||
static render::ShapePipelinePointer _simpleTransparentPipeline;
|
||||
static render::ShapePipelinePointer _forwardSimpleOpaquePipeline;
|
||||
static render::ShapePipelinePointer _forwardSimpleTransparentPipeline;
|
||||
static render::ShapePipelinePointer _simpleOpaqueFadePipeline;
|
||||
static render::ShapePipelinePointer _simpleTransparentFadePipeline;
|
||||
static render::ShapePipelinePointer _simpleWirePipeline;
|
||||
gpu::PipelinePointer _glowLinePipeline;
|
||||
|
||||
static QHash<SimpleProgramKey, gpu::PipelinePointer> _simplePrograms;
|
||||
|
||||
|
|
|
@ -67,11 +67,11 @@ float TextRenderer3D::getFontSize() const {
|
|||
}
|
||||
|
||||
void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
|
||||
const glm::vec2& bounds, bool forwardRendered) {
|
||||
const glm::vec2& bounds, bool layered) {
|
||||
// The font does all the OpenGL work
|
||||
if (_font) {
|
||||
_color = color;
|
||||
_font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, forwardRendered);
|
||||
_font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, layered);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
float getFontSize() const; // Pixel size
|
||||
|
||||
void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(1.0f),
|
||||
const glm::vec2& bounds = glm::vec2(-1.0f), bool forwardRendered = false);
|
||||
const glm::vec2& bounds = glm::vec2(-1.0f), bool layered = false);
|
||||
|
||||
private:
|
||||
TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false,
|
||||
|
|
57
libraries/render-utils/src/forward_sdf_text3D.slf
Normal file
57
libraries/render-utils/src/forward_sdf_text3D.slf
Normal file
|
@ -0,0 +1,57 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text3D_transparent.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2015-02-04
|
||||
// Based on fragment shader code from
|
||||
// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
<@include DefaultMaterials.slh@>
|
||||
|
||||
<@include ForwardGlobalLight.slh@>
|
||||
<$declareEvalSkyboxGlobalColor()$>
|
||||
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardCameraTransform()$>
|
||||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
<@include sdf_text3D.slh@>
|
||||
<$declareEvalSDFSuperSampled()$>
|
||||
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||
#define _texCoord0 _texCoord01.xy
|
||||
#define _texCoord1 _texCoord01.zw
|
||||
|
||||
layout(location=0) out vec4 _fragColor0;
|
||||
|
||||
void main() {
|
||||
float a = evalSDFSuperSampled(_texCoord0);
|
||||
|
||||
float alpha = a * _color.a;
|
||||
if (alpha <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
vec3 fragPosition = _positionES.xyz;
|
||||
|
||||
_fragColor0 = vec4(evalSkyboxGlobalColor(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
DEFAULT_OCCLUSION,
|
||||
fragPosition,
|
||||
normalize(_normalWS),
|
||||
_color.rgb,
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_METALLIC,
|
||||
DEFAULT_ROUGHNESS),
|
||||
1.0);
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
<@include gpu/Color.slh@>
|
||||
<@include DefaultMaterials.slh@>
|
||||
|
||||
<@include ForwardGlobalLight.slh@>
|
||||
|
@ -21,10 +22,8 @@
|
|||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
// the albedo texture
|
||||
LAYOUT(binding=0) uniform sampler2D originalTexture;
|
||||
|
||||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||
|
@ -36,7 +35,11 @@ layout(location=0) out vec4 _fragColor0;
|
|||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
float colorAlpha = _color.a * texel.a;
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
vec3 albedo = _color.xyz * texel.xyz;
|
||||
float metallic = DEFAULT_METALLIC;
|
||||
|
||||
vec3 fresnel = getFresnelF0(metallic, albedo);
|
||||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
vec3 fragPosition = _positionES.xyz;
|
||||
|
@ -47,9 +50,9 @@ void main(void) {
|
|||
DEFAULT_OCCLUSION,
|
||||
fragPosition,
|
||||
normalize(_normalWS),
|
||||
_color.rgb * texel.rgb,
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_METALLIC,
|
||||
albedo,
|
||||
fresnel,
|
||||
metallic,
|
||||
DEFAULT_ROUGHNESS),
|
||||
1.0);
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
<@include gpu/Color.slh@>
|
||||
<@include DefaultMaterials.slh@>
|
||||
|
||||
<@include ForwardGlobalLight.slh@>
|
||||
|
@ -21,22 +22,25 @@
|
|||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
// the albedo texture
|
||||
LAYOUT(binding=0) uniform sampler2D originalTexture;
|
||||
|
||||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||
#define _texCoord0 _texCoord01.xy
|
||||
#define _texCoord1 _texCoord01.zw
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
|
||||
|
||||
layout(location=0) out vec4 _fragColor0;
|
||||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
float colorAlpha = _color.a * texel.a;
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
vec3 albedo = _color.xyz * texel.xyz;
|
||||
float alpha = _color.a * texel.a;
|
||||
float metallic = DEFAULT_METALLIC;
|
||||
|
||||
vec3 fresnel = getFresnelF0(metallic, albedo);
|
||||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
vec3 fragPosition = _positionES.xyz;
|
||||
|
@ -47,10 +51,10 @@ void main(void) {
|
|||
DEFAULT_OCCLUSION,
|
||||
fragPosition,
|
||||
normalize(_normalWS),
|
||||
_color.rgb * texel.rgb,
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_METALLIC,
|
||||
albedo,
|
||||
fresnel,
|
||||
metallic,
|
||||
DEFAULT_EMISSIVE,
|
||||
DEFAULT_ROUGHNESS, colorAlpha),
|
||||
colorAlpha);
|
||||
DEFAULT_ROUGHNESS, alpha),
|
||||
alpha);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
VERTEX sdf_text3D
|
|
@ -13,54 +13,22 @@
|
|||
<@include DeferredBufferWrite.slh@>
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
LAYOUT(binding=0) uniform sampler2D Font;
|
||||
<@include sdf_text3D.slh@>
|
||||
<$declareEvalSDFSuperSampled()$>
|
||||
|
||||
struct TextParams {
|
||||
vec4 color;
|
||||
vec4 outline;
|
||||
};
|
||||
|
||||
LAYOUT(binding=0) uniform textParamsBuffer {
|
||||
TextParams params;
|
||||
};
|
||||
|
||||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||
#define _texCoord0 _texCoord01.xy
|
||||
#define _texCoord1 _texCoord01.zw
|
||||
|
||||
#define TAA_TEXTURE_LOD_BIAS -3.0
|
||||
|
||||
const float interiorCutoff = 0.8;
|
||||
const float outlineExpansion = 0.2;
|
||||
const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS);
|
||||
|
||||
float evalSDF(vec2 texCoord) {
|
||||
// retrieve signed distance
|
||||
float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g;
|
||||
sdf = mix(sdf, mix(sdf + outlineExpansion, 1.0 - sdf, float(sdf > interiorCutoff)), float(params.outline.x > 0.0));
|
||||
|
||||
// Rely on TAA for anti-aliasing
|
||||
return step(0.5, sdf);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 dxTexCoord = dFdx(_texCoord0) * 0.5 * taaBias;
|
||||
vec2 dyTexCoord = dFdy(_texCoord0) * 0.5 * taaBias;
|
||||
|
||||
// Perform 4x supersampling for anisotropic filtering
|
||||
float a;
|
||||
a = evalSDF(_texCoord0);
|
||||
a += evalSDF(_texCoord0 + dxTexCoord);
|
||||
a += evalSDF(_texCoord0 + dyTexCoord);
|
||||
a += evalSDF(_texCoord0 + dxTexCoord + dyTexCoord);
|
||||
a *= 0.25;
|
||||
float a = evalSDFSuperSampled(_texCoord0);
|
||||
|
||||
packDeferredFragment(
|
||||
normalize(_normalWS),
|
||||
a * params.color.a,
|
||||
params.color.rgb,
|
||||
a,
|
||||
_color.rgb,
|
||||
DEFAULT_ROUGHNESS,
|
||||
DEFAULT_METALLIC,
|
||||
DEFAULT_EMISSIVE,
|
||||
|
|
63
libraries/render-utils/src/sdf_text3D.slh
Normal file
63
libraries/render-utils/src/sdf_text3D.slh
Normal file
|
@ -0,0 +1,63 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
<!
|
||||
// <$_SCRIBE_FILENAME$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
//
|
||||
// Created by Sam Gondelman on 3/15/19
|
||||
// Copyright 2019 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 SDF_TEXT3D_SLH@>
|
||||
<@def SDF_TEXT3D_SLH@>
|
||||
|
||||
LAYOUT(binding=0) uniform sampler2D Font;
|
||||
|
||||
struct TextParams {
|
||||
vec4 color;
|
||||
vec4 outline;
|
||||
};
|
||||
|
||||
LAYOUT(binding=0) uniform textParamsBuffer {
|
||||
TextParams params;
|
||||
};
|
||||
|
||||
<@func declareEvalSDFSuperSampled()@>
|
||||
|
||||
#define TAA_TEXTURE_LOD_BIAS -3.0
|
||||
|
||||
const float interiorCutoff = 0.8;
|
||||
const float outlineExpansion = 0.2;
|
||||
const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS);
|
||||
|
||||
float evalSDF(vec2 texCoord) {
|
||||
// retrieve signed distance
|
||||
float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g;
|
||||
sdf = mix(sdf, mix(sdf + outlineExpansion, 1.0 - sdf, float(sdf > interiorCutoff)), float(params.outline.x > 0.0));
|
||||
|
||||
// Rely on TAA for anti-aliasing
|
||||
return step(0.5, sdf);
|
||||
}
|
||||
|
||||
float evalSDFSuperSampled(vec2 texCoord) {
|
||||
vec2 dxTexCoord = dFdx(texCoord) * 0.5 * taaBias;
|
||||
vec2 dyTexCoord = dFdy(texCoord) * 0.5 * taaBias;
|
||||
|
||||
// Perform 4x supersampling for anisotropic filtering
|
||||
float a;
|
||||
a = evalSDF(texCoord);
|
||||
a += evalSDF(texCoord + dxTexCoord);
|
||||
a += evalSDF(texCoord + dyTexCoord);
|
||||
a += evalSDF(texCoord + dxTexCoord + dyTexCoord);
|
||||
a *= 0.25;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
<@endfunc@>
|
||||
|
||||
<@endif@>
|
||||
|
|
@ -11,18 +11,23 @@
|
|||
//
|
||||
|
||||
<@include gpu/Inputs.slh@>
|
||||
<@include gpu/Transform.slh@>
|
||||
<@include gpu/Color.slh@>
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardTransform()$>
|
||||
|
||||
<@include sdf_text3D.slh@>
|
||||
|
||||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES;
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01;
|
||||
|
||||
void main() {
|
||||
_texCoord01.xy = inTexCoord0.xy;
|
||||
_color = color_sRGBAToLinear(params.color);
|
||||
|
||||
// standard transform
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
|
|
@ -20,53 +20,22 @@
|
|||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
LAYOUT(binding=0) uniform sampler2D Font;
|
||||
|
||||
struct TextParams {
|
||||
vec4 color;
|
||||
vec4 outline;
|
||||
};
|
||||
|
||||
LAYOUT(binding=0) uniform textParamsBuffer {
|
||||
TextParams params;
|
||||
};
|
||||
<@include sdf_text3D.slh@>
|
||||
<$declareEvalSDFSuperSampled()$>
|
||||
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||
#define _texCoord0 _texCoord01.xy
|
||||
#define _texCoord1 _texCoord01.zw
|
||||
|
||||
layout(location=0) out vec4 _fragColor0;
|
||||
|
||||
#define TAA_TEXTURE_LOD_BIAS -3.0
|
||||
|
||||
const float interiorCutoff = 0.8;
|
||||
const float outlineExpansion = 0.2;
|
||||
const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS);
|
||||
|
||||
float evalSDF(vec2 texCoord) {
|
||||
// retrieve signed distance
|
||||
float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g;
|
||||
sdf = mix(sdf, mix(sdf + outlineExpansion, 1.0 - sdf, float(sdf > interiorCutoff)), float(params.outline.x > 0.0));
|
||||
|
||||
// Rely on TAA for anti-aliasing
|
||||
return step(0.5, sdf);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 dxTexCoord = dFdx(_texCoord0) * 0.5 * taaBias;
|
||||
vec2 dyTexCoord = dFdy(_texCoord0) * 0.5 * taaBias;
|
||||
float a = evalSDFSuperSampled(_texCoord0);
|
||||
|
||||
// Perform 4x supersampling for anisotropic filtering
|
||||
float a;
|
||||
a = evalSDF(_texCoord0);
|
||||
a += evalSDF(_texCoord0 + dxTexCoord);
|
||||
a += evalSDF(_texCoord0 + dyTexCoord);
|
||||
a += evalSDF(_texCoord0 + dxTexCoord + dyTexCoord);
|
||||
a *= 0.25;
|
||||
|
||||
float alpha = a * params.color.a;
|
||||
float alpha = a * _color.a;
|
||||
if (alpha <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
|
@ -80,7 +49,7 @@ void main() {
|
|||
DEFAULT_OCCLUSION,
|
||||
fragPosition,
|
||||
normalize(_normalWS),
|
||||
params.color.rgb,
|
||||
_color.rgb,
|
||||
DEFAULT_FRESNEL,
|
||||
DEFAULT_METALLIC,
|
||||
DEFAULT_EMISSIVE,
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_MS) out vec3 _normalMS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color;
|
||||
|
|
|
@ -11,31 +11,50 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
<@include DefaultMaterials.slh@>
|
||||
|
||||
<@include gpu/Color.slh@>
|
||||
<@include DeferredBufferWrite.slh@>
|
||||
|
||||
<@include render-utils/ShaderConstants.h@>
|
||||
|
||||
// the albedo texture
|
||||
<@include ForwardGlobalLight.slh@>
|
||||
<$declareEvalGlobalLightingAlphaBlended()$>
|
||||
|
||||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardCameraTransform()$>
|
||||
|
||||
LAYOUT(binding=0) uniform sampler2D originalTexture;
|
||||
|
||||
// the interpolated normal
|
||||
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
|
||||
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
|
||||
layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
|
||||
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
||||
#define _texCoord0 _texCoord01.xy
|
||||
#define _texCoord1 _texCoord01.zw
|
||||
|
||||
layout(location=0) out vec4 _fragColor0;
|
||||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a *= abs(_color.a);
|
||||
vec3 albedo = _color.xyz * texel.xyz;
|
||||
float alpha = _color.a * texel.a;
|
||||
float metallic = DEFAULT_METALLIC;
|
||||
|
||||
packDeferredFragmentTranslucent(
|
||||
vec3 fresnel = getFresnelF0(metallic, albedo);
|
||||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
vec3 fragPosition = _positionES.xyz;
|
||||
|
||||
_fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze(
|
||||
cam._viewInverse,
|
||||
1.0,
|
||||
DEFAULT_OCCLUSION,
|
||||
fragPosition,
|
||||
normalize(_normalWS),
|
||||
texel.a,
|
||||
texel.rgb,
|
||||
DEFAULT_ROUGHNESS);
|
||||
albedo,
|
||||
fresnel,
|
||||
metallic,
|
||||
DEFAULT_EMISSIVE,
|
||||
DEFAULT_ROUGHNESS, alpha),
|
||||
alpha);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue