From 93516423bb9aeb87c887252f82967b7bd09b48fa Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 11 Jan 2019 21:32:18 +0100 Subject: [PATCH 01/13] zone visualizer prototype --- scripts/system/edit.js | 14 +- .../system/modules/entityShapeVisualizer.js | 209 ++++++++++++++++++ 2 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 scripts/system/modules/entityShapeVisualizer.js diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 84143f4c25..c6a30fe209 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -82,13 +82,18 @@ var selectionManager = SelectionManager; var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var ZONE_URL = Script.resourcesPath() + "icons/create-icons/23-zone-01.svg"; -var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) { +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); if (properties.type === 'Light') { return { url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; } else { return { url: PARTICLE_SYSTEM_URL, @@ -106,11 +111,15 @@ var gridTool = new GridTool({ }); gridTool.setVisible(false); +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.updateSelection(selectionManager.selections); }); var DEGREES_TO_RADIANS = Math.PI / 180.0; @@ -836,7 +845,7 @@ var toolBar = (function () { dialogWindow.fromQml.connect(fromQml); } }; - }; + } addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); @@ -1492,6 +1501,7 @@ Script.scriptEnding.connect(function () { cleanupModelMenus(); tooltip.cleanup(); selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); Entities.setLightsArePickable(originalLightsArePickable); Overlays.deleteOverlay(importingSVOImageOverlay); diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js new file mode 100644 index 0000000000..5c91b1311c --- /dev/null +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -0,0 +1,209 @@ +"use strict"; + +// entityShapeVisualizer.js +// +// Created by Thijs Wenker on 1/11/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 +// + +var SHAPETYPE_TO_SHAPE = { + "sphere": "Sphere", + "box": "Cube", + "ellipsoid": "Sphere", + "cylinder-y": "Cylinder", +}; + +function getEntityShapePropertiesForType(properties) { + switch (properties.type) { + case "Zone": + if (SHAPETYPE_TO_SHAPE[properties.shapeType]) { + return { + type: "Shape", + shape: SHAPETYPE_TO_SHAPE[properties.shapeType] + } + } else if (properties.shapeType === "compound") { + return { + type: "Model", + modelURL: properties.compoundShapeURL + } + } + break; + } + + // Default properties + return { + type: "Shape", + shape: "Cube" + } +} + +function getStringifiedEntityShapePropertiesForType(properties) { + return JSON.stringify(getEntityShapePropertiesForType(properties)); +} + +var REQUESTED_ENTITY_SHAPE_PROPERTIES = [ + 'type', 'shapeType', 'compoundShapeURL', 'localDimensions' +]; + +function EntityShape(entityID) { + this.entityID = entityID; + var properties = Entities.getEntityProperties(entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); + + this.previousPropertiesForTypeStringified = getStringifiedEntityShapePropertiesForType(properties); + + this.initialize(properties, this.previousPropertiesForTypeStringified); +} + +EntityShape.prototype = { + initialize: function(properties, propertiesForTypeStringified) { + // Create new instance of JS object: + var overlayProperties = JSON.parse(propertiesForTypeStringified); + + overlayProperties.localPosition = Vec3.ZERO; + overlayProperties.localRotation = Quat.IDENTITY; + overlayProperties.localDimensions = properties.localDimensions; + overlayProperties.canCastShadows = false; + overlayProperties.parentID = this.entityID; + overlayProperties.collisionless = true; + this.entity = Entities.addEntity(overlayProperties, "local"); + + console.warn("created " + this.entity); + console.warn("SHAPETYPE = " + properties.shapeType); + console.warn("SHAPE = " + Entities.getEntityProperties(this.entity, "shape").shape); + + + this.materialEntity = Entities.addEntity({ + type: "Material", + localPosition: Vec3.ZERO, + localRotation: Quat.IDENTITY, + localDimensions: properties.localDimensions, + parentID: this.entity, + priority: 1, + materialURL: "materialData", + materialData: JSON.stringify({ + materialVersion: 1, + materials: { + albedo: [0.0, 0.0, 7.0], + unlit: true, + opacity: 0.4 + } + }), + }, "local"); + + }, + + update: function() { + var properties = Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); + var propertiesForTypeStringified = getStringifiedEntityShapePropertiesForType(properties); + if (propertiesForTypeStringified !== this.previousPropertiesForTypeStringified) { + this.previousPropertiesForTypeStringified = propertiesForTypeStringified; + console.warn("Clearing old properties"); + this.clear(); + this.initialize(properties, propertiesForTypeStringified); + } else { + Entities.editEntity(this.entity, { + localDimensions: properties.localDimensions, + }); + } + + + + //this.previousProperties = Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); + + + console.warn(JSON.stringify(this.previousProperties)); + }, + clear: function() { + Entities.deleteEntity(this.materialEntity); + Entities.deleteEntity(this.entity); + } +}; + +function EntityShapeVisualizer(visualizedTypes) { + this.acceptedEntities = []; + this.ignoredEntities = []; + this.entityShapes = {}; + + this.visualizedTypes = visualizedTypes; +} + +EntityShapeVisualizer.prototype = { + addEntity: function(entityID, properties) { + if (this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID] = new EntityShape(entityID); + + }, + updateEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].update(); + }, + removeEntity: function(entityID) { + if (!this.entityShapes[entityID]) { + return; + } + this.entityShapes[entityID].clear(); + delete this.entityShapes[entityID]; + }, + cleanup: function() { + Object.keys(this.entityShapes).forEach(function(entityID) { + this.entityShapes[entityID].clear(); + }, this); + this.entityShapes = {}; + }, + updateSelection: function(selection) { + var qualifiedSelection = selection.filter(function(entityID) { + if (this.acceptedEntities.indexOf(entityID) !== -1) { + return true; + } + if (this.ignoredEntities.indexOf(entityID) !== -1) { + return false; + } + if (this.visualizedTypes.indexOf(Entities.getEntityProperties(entityID, "type").type) !== -1) { + this.acceptedEntities.push(entityID); + return true; + } + this.ignoredEntities.push(entityID); + return false; + }, this); + + + var newEntries = []; + var updateEntries = []; + + var currentEntries = Object.keys(this.entityShapes); + qualifiedSelection.forEach(function(entityID) { + if (currentEntries.indexOf(entityID) !== -1) { + updateEntries.push(entityID); + } else { + newEntries.push(entityID); + } + }); + + var deleteEntries = currentEntries.filter(function(entityID) { + return updateEntries.indexOf(entityID) === -1; + }); + + deleteEntries.forEach(function(entityID) { + console.warn("removing " + entityID); + this.removeEntity(entityID); + }, this); + + updateEntries.forEach(function(entityID) { + this.updateEntity(entityID); + }, this); + + newEntries.forEach(function(entityID) { + this.addEntity(entityID); + }, this); + } +}; + +module.exports = EntityShapeVisualizer; From b2f16570967c72d243ebc8680eb5c220ba628f5a Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 23 Jan 2019 19:28:34 +0100 Subject: [PATCH 02/13] grid pattern --- .../materials/boxgridpatterncreatezonew.png | Bin 0 -> 8623 bytes .../system/modules/entityShapeVisualizer.js | 49 ++++++++---------- 2 files changed, 22 insertions(+), 27 deletions(-) create mode 100644 scripts/system/assets/images/materials/boxgridpatterncreatezonew.png diff --git a/scripts/system/assets/images/materials/boxgridpatterncreatezonew.png b/scripts/system/assets/images/materials/boxgridpatterncreatezonew.png new file mode 100644 index 0000000000000000000000000000000000000000..2ecc7f85704c735794e8881b8cd3116422096874 GIT binary patch literal 8623 zcmeHNc~nzZ8ow!$2oi!v*|)GyaZ)H^*#u-0!r}r1K?{RIP?m{E*b;%HB2Q{+~J7G7$P>F5=+Dq zlOrPlNbaA$7!)^WtCYbzsvLA|V!sX3h_Gx|(OQceH8(s?e^uA|Ksn|(@&4k=8^I&j zS9rBfy1Kf?CX-yj1!qO4*;Znn&q#WaVp)C2CUy<2X0V6)%Z?^91*@RpfqK=ey6-Au zs~i(fkfO`-rZ?u*e}f;$aBv^~;?N6jCgvXhNupk~;xu*9n=-j)FvxnK@XVx9mI-Zn zO5fx*O5o851kh9PfkO$z-PH(B%MpiZ(~0Z+x@PA}QL$z>T={C@9i|GxfdqNzv5 z^zk=F?)$3V^^(~UFPF0BBgYtK*Uki7uI-4k+U^xL?-Kt=w!{Al?ndXX9s`G;;!H7p zYpdN>_$gYwIR1I-hK$_d!1k5_XWQudKArwoEQU^H*`k|S3Zo-_1LbhrfvNuUF_wLQ zE&(JG^#XU!^3KWbz`!l7?GwpnRBfxWQJn28*N30Ko&U&k;UD*YDQ~nNm~{?oD=sPb zZ2EdOHQxpy_=*+BzubAiY5r;4-+PyCX{x$xS`cxUDC_^YXYvZc(m1M};$6CP#LlWGzeWDgjL)x@ftt}WjNCS@ z?-pG-1<}N=m;e`|M! z$@dJ0Otr96Hs`~0y&N(&`gz)%i@7sy)b=9VVEk|7FEUWLlvfWq>k4;!Zs`u#lZxKb z<4?w|+gbPsmnP@+^%G{^6YrnTO!J%>UB0b9#t%_U6M9oI_V2pLX6Y~bZ`Y$8AD%Ec zv$bV+xlgxO_q^&jsf1`gbz z?*weDlD7=DCf}TTmhso8J=T^yi!wdR=>!V8d#ojBHf<~v#a`HJM~h0WAnn7Q;&m4r z9O<-Qu0eZX=BsV6!SjAmN!jrqF0j*!g6+!`JRIkbT6^^8K81#6j!?b>gVjICOR_K`|&k&Hog!)CSPy4wqbLKgOpP0$U) zvK>5FnN#K5a|nDR_EJo~#$2^q){sXdmVeTxDD%W#*9NRht~Xj>z}Tv6*xZ%v$mV8Y z@vO}1$NEVqyah70(aaE0d+n~Pl0eFt+&WSFO&s7Co3bER$_l{u%Ks!j4W z^bh82w?NLG3XkFz297lo=;2XZ-F@MEvJL%QLLzcd#|-l|=8%jf4amYmJz^ABhp}Xi zZB{EvWESgn28~_QY*q^oXBO-Es*I2f!?JhbS(!`UxD})^p_V_W^r=JksRoT*71}qb zpR@*Vfh^7lkK&H12##!He#uSIh_rBgpY4s#Uvn%^aQ3U_E+utFDKuK+TyOMxHn)|g zOgMb|qim9JW4pfC7p@3s{3#02lm$@Gt|YDYfgwkbn=Utet=2GMjAc0RYGY zUhb|y_y}C8XaE4YSdSnw0P8kDF9KY=qy_*$#p8Y4P@@P{@EJzY;Jq9G8pC*3C;#OB zsezy5T)$M1eZYUqS6cwP9j(}2gVFC0F1a(3YGC^DvZT`ii1#cV0c#S$2C7kD+kuz=#ts@* zZSBHmkSdH2qffyl(eo+@H2r7_`GP(o>8cP&(=&w75z>7kq2yBtJgXp#L)e%Pz6MwR z8xmB`%F6IMNYz%6w?B6LDoQ?ufEN4aB`B0K2eiSwcvSFsN1<{$>Qf;l-GR~(ppOQe z+YCi#Y?TaMJksAU9yNmo^+sgA^pIM`fzlC(9lRL_)u@q@z~lcgipmM0(Pm^JRXQRU zOAjgO=8N%5yd6v`>2Qf-B#aW6R3T+BsUY%+F-c4m4;YwKFsUREfWK6$OrbET;N{Kmb0dSY-;8SkJ(uf=~Y> zwuB$=d_++KlL{u41Oor}N9uLGHQL_+P;ai(u!zB7)+_1VpX{zkfjA+&=fFZoYa(%% z75+K2RDDzND+oROKM(DL=mH^SoABqtOC+V=C5jqA|GE$tuaFSDoEAj+)u9Eb2Jg1R K^_Fw!v3~+z)}_J# literal 0 HcmV?d00001 diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js index 5c91b1311c..d1136c513c 100644 --- a/scripts/system/modules/entityShapeVisualizer.js +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -11,7 +11,6 @@ // var SHAPETYPE_TO_SHAPE = { - "sphere": "Sphere", "box": "Cube", "ellipsoid": "Sphere", "cylinder-y": "Cylinder", @@ -23,13 +22,23 @@ function getEntityShapePropertiesForType(properties) { if (SHAPETYPE_TO_SHAPE[properties.shapeType]) { return { type: "Shape", - shape: SHAPETYPE_TO_SHAPE[properties.shapeType] - } + shape: SHAPETYPE_TO_SHAPE[properties.shapeType], + localDimensions: properties.localDimensions + }; } else if (properties.shapeType === "compound") { return { type: "Model", - modelURL: properties.compoundShapeURL - } + modelURL: properties.compoundShapeURL, + localDimensions: properties.localDimensions + }; + } else if (properties.shapeType === "sphere") { + var sphereDiameter = Math.max(properties.localDimensions.x, properties.localDimensions.y, + properties.localDimensions.z); + return { + type: "Sphere", + modelURL: properties.compoundShapeURL, + localDimensions: {x: sphereDiameter, y: sphereDiameter, z: sphereDiameter} + }; } break; } @@ -37,8 +46,9 @@ function getEntityShapePropertiesForType(properties) { // Default properties return { type: "Shape", - shape: "Cube" - } + shape: "Cube", + localDimensions: properties.localDimensions + }; } function getStringifiedEntityShapePropertiesForType(properties) { @@ -65,17 +75,11 @@ EntityShape.prototype = { overlayProperties.localPosition = Vec3.ZERO; overlayProperties.localRotation = Quat.IDENTITY; - overlayProperties.localDimensions = properties.localDimensions; overlayProperties.canCastShadows = false; overlayProperties.parentID = this.entityID; overlayProperties.collisionless = true; this.entity = Entities.addEntity(overlayProperties, "local"); - - console.warn("created " + this.entity); - console.warn("SHAPETYPE = " + properties.shapeType); - console.warn("SHAPE = " + Entities.getEntityProperties(this.entity, "shape").shape); - - + var PROJECTED_MATERIALS = false; this.materialEntity = Entities.addEntity({ type: "Material", localPosition: Vec3.ZERO, @@ -83,39 +87,31 @@ EntityShape.prototype = { localDimensions: properties.localDimensions, parentID: this.entity, priority: 1, + materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", materialURL: "materialData", materialData: JSON.stringify({ materialVersion: 1, materials: { albedo: [0.0, 0.0, 7.0], unlit: true, - opacity: 0.4 + opacity: 0.4, + albedoMap: Script.resolvePath("../assets/images/materials/boxgridpatterncreatezonew.png") } }), }, "local"); - }, - update: function() { var properties = Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); var propertiesForTypeStringified = getStringifiedEntityShapePropertiesForType(properties); if (propertiesForTypeStringified !== this.previousPropertiesForTypeStringified) { this.previousPropertiesForTypeStringified = propertiesForTypeStringified; - console.warn("Clearing old properties"); this.clear(); this.initialize(properties, propertiesForTypeStringified); } else { Entities.editEntity(this.entity, { - localDimensions: properties.localDimensions, + localDimensions: JSON.parse(propertiesForTypeStringified).localDimensions, }); } - - - - //this.previousProperties = Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); - - - console.warn(JSON.stringify(this.previousProperties)); }, clear: function() { Entities.deleteEntity(this.materialEntity); @@ -192,7 +188,6 @@ EntityShapeVisualizer.prototype = { }); deleteEntries.forEach(function(entityID) { - console.warn("removing " + entityID); this.removeEntity(entityID); }, this); From 219c159a250500d8da35dfe8259f8ddf4fe29f32 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 24 Jan 2019 12:44:05 -0800 Subject: [PATCH 03/13] Switch to nvtt with no openmp dependency --- cmake/ports/nvtt/CONTROL | 2 +- cmake/ports/nvtt/portfile.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/ports/nvtt/CONTROL b/cmake/ports/nvtt/CONTROL index 59ba36d830..94d6193116 100644 --- a/cmake/ports/nvtt/CONTROL +++ b/cmake/ports/nvtt/CONTROL @@ -1,3 +1,3 @@ Source: nvtt -Version: 8c7e6b40ee5095f227b75880fabd89c99d6f34c0 +Version: 330c4d56274a0f602a5c70596e2eb670a4ed56c2 Description: Texture processing tools with support for Direct3D 10 and 11 formats. \ No newline at end of file diff --git a/cmake/ports/nvtt/portfile.cmake b/cmake/ports/nvtt/portfile.cmake index 13b1253322..775cb28e00 100644 --- a/cmake/ports/nvtt/portfile.cmake +++ b/cmake/ports/nvtt/portfile.cmake @@ -10,7 +10,7 @@ include(vcpkg_common_functions) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO highfidelity/nvidia-texture-tools - REF 8c7e6b40ee5095f227b75880fabd89c99d6f34c0 + REF 330c4d56274a0f602a5c70596e2eb670a4ed56c2 SHA512 f107d19dbbd6651ef2126b1422a5db8db291bf70311ac4fb1dbacb5ceaa8752fee38becbd32964f57596f0b84e1223bb2c3ff9d9c4fdc65c3e77a47836657cef HEAD_REF master ) From 4b805105c7bbceaac18acb9f3b0c1eeb54853695 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 24 Jan 2019 13:00:19 -0800 Subject: [PATCH 04/13] Fix hash --- cmake/ports/nvtt/portfile.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ports/nvtt/portfile.cmake b/cmake/ports/nvtt/portfile.cmake index 775cb28e00..4cbe05b692 100644 --- a/cmake/ports/nvtt/portfile.cmake +++ b/cmake/ports/nvtt/portfile.cmake @@ -11,7 +11,7 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO highfidelity/nvidia-texture-tools REF 330c4d56274a0f602a5c70596e2eb670a4ed56c2 - SHA512 f107d19dbbd6651ef2126b1422a5db8db291bf70311ac4fb1dbacb5ceaa8752fee38becbd32964f57596f0b84e1223bb2c3ff9d9c4fdc65c3e77a47836657cef + SHA512 4c0bc2f369120d696cc27710b6d33086b27eef55f537ec66b9a5c8b1839bc2426c0413670b0f65be52c5d353468f0126dfe024be1f0690611d4d7e33ac530127 HEAD_REF master ) From 9bb7e4dcede24a780a17486f9e722e956080f25a Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 7 Jan 2019 16:49:52 -0800 Subject: [PATCH 05/13] Create normal/tangent calculation tasks for meshes/blendshapes --- .../model-baker/src/model-baker/Baker.cpp | 72 +++++++++-- .../model-baker/src/model-baker/BakerTypes.h | 15 +++ .../src/model-baker/BuildGraphicsMeshTask.cpp | 62 ++++----- .../src/model-baker/BuildGraphicsMeshTask.h | 4 +- .../CalculateBlendshapeNormalsTask.cpp | 70 ++++++++++ .../CalculateBlendshapeNormalsTask.h | 28 ++++ .../CalculateBlendshapeTangentsTask.cpp | 95 ++++++++++++++ .../CalculateBlendshapeTangentsTask.h | 28 ++++ .../model-baker/CalculateMeshNormalsTask.cpp | 40 ++++++ .../model-baker/CalculateMeshNormalsTask.h | 30 +++++ .../model-baker/CalculateMeshTangentsTask.cpp | 66 ++++++++++ .../model-baker/CalculateMeshTangentsTask.h | 32 +++++ .../model-baker/src/model-baker/ModelMath.cpp | 121 ++++++++++++++++++ .../model-baker/src/model-baker/ModelMath.h | 34 +++++ 14 files changed, 645 insertions(+), 52 deletions(-) create mode 100644 libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp create mode 100644 libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h create mode 100644 libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp create mode 100644 libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h create mode 100644 libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp create mode 100644 libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h create mode 100644 libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp create mode 100644 libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h create mode 100644 libraries/model-baker/src/model-baker/ModelMath.cpp create mode 100644 libraries/model-baker/src/model-baker/ModelMath.h diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index bf2b21993c..c15b2c9926 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -15,26 +15,64 @@ #include "BakerTypes.h" #include "BuildGraphicsMeshTask.h" +#include "CalculateMeshNormalsTask.h" +#include "CalculateMeshTangentsTask.h" +#include "CalculateBlendshapeNormalsTask.h" +#include "CalculateBlendshapeTangentsTask.h" namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet3, hifi::URL, MeshIndicesToModelNames>; + using Output = VaryingSet4, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { - auto& hfmModelIn = input; + const auto& hfmModelIn = input; output.edit0() = hfmModelIn->meshes.toStdVector(); output.edit1() = hfmModelIn->originalURL; output.edit2() = hfmModelIn->meshIndicesToModelNames; + auto& blendshapesPerMesh = output.edit3(); + blendshapesPerMesh.reserve(hfmModelIn->meshes.size()); + for (int i = 0; i < hfmModelIn->meshes.size(); i++) { + blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); + } + } + }; + + class BuildBlendshapesTask { + public: + using Input = VaryingSet3, std::vector>; + using Output = BlendshapesPerMesh; + using JobModel = Job::ModelIO; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMeshIn = input.get0(); + const auto& normalsPerBlendshapePerMesh = input.get1(); + const auto& tangentsPerBlendshapePerMesh = input.get2(); + auto& blendshapesPerMeshOut = output; + + blendshapesPerMeshOut = blendshapesPerMeshIn; + + for (int i = 0; i < blendshapesPerMeshOut.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& tangentsPerBlendshape = tangentsPerBlendshapePerMesh[i]; + auto& blendshapesOut = blendshapesPerMeshOut[i]; + for (int j = 0; j < blendshapesOut.size(); j++) { + const auto& normals = normalsPerBlendshape[j]; + const auto& tangents = tangentsPerBlendshape[j]; + auto& blendshape = blendshapesOut[j]; + blendshape.normals = QVector::fromStdVector(normals); + blendshape.tangents = QVector::fromStdVector(tangents); + } + } } }; class BuildMeshesTask { public: - using Input = VaryingSet4, std::vector, TangentsPerMesh, BlendshapesPerMesh>; + using Input = VaryingSet5, std::vector, NormalsPerMesh, TangentsPerMesh, BlendshapesPerMesh>; using Output = std::vector; using JobModel = Job::ModelIO; @@ -42,13 +80,15 @@ namespace baker { auto& meshesIn = input.get0(); int numMeshes = (int)meshesIn.size(); auto& graphicsMeshesIn = input.get1(); - auto& tangentsPerMeshIn = input.get2(); - auto& blendshapesPerMeshIn = input.get3(); + auto& normalsPerMeshIn = input.get2(); + auto& tangentsPerMeshIn = input.get3(); + auto& blendshapesPerMeshIn = input.get4(); auto meshesOut = meshesIn; for (int i = 0; i < numMeshes; i++) { auto& meshOut = meshesOut[i]; meshOut._mesh = graphicsMeshesIn[i]; + meshOut.normals = QVector::fromStdVector(normalsPerMeshIn[i]); meshOut.tangents = QVector::fromStdVector(tangentsPerMeshIn[i]); meshOut.blendshapes = QVector::fromStdVector(blendshapesPerMeshIn[i]); } @@ -80,17 +120,25 @@ namespace baker { const auto meshesIn = modelPartsIn.getN(0); const auto url = modelPartsIn.getN(1); const auto meshIndicesToModelNames = modelPartsIn.getN(2); + const auto blendshapesPerMeshIn = modelPartsIn.getN(3); + + // Calculate normals and tangents for meshes and blendshapes if they do not exist + const auto normalsPerMesh = model.addJob("CalculateMeshNormals", meshesIn); + const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, hfmModelIn).asVarying(); + const auto tangentsPerMesh = model.addJob("CalculateMeshTangents", calculateMeshTangentsInputs); + const auto calculateBlendshapeNormalsInputs = CalculateBlendshapeNormalsTask::Input(blendshapesPerMeshIn, meshesIn).asVarying(); + const auto normalsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeNormals", calculateBlendshapeNormalsInputs); + const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, hfmModelIn).asVarying(); + const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); // Build the graphics::MeshPointer for each hfm::Mesh - const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames).asVarying(); - const auto buildGraphicsMeshOutputs = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); - const auto graphicsMeshes = buildGraphicsMeshOutputs.getN(0); - // TODO: Move tangent/blendshape validation/calculation to an earlier step - const auto tangentsPerMesh = buildGraphicsMeshOutputs.getN(1); - const auto blendshapesPerMesh = buildGraphicsMeshOutputs.getN(2); + const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames, normalsPerMesh, tangentsPerMesh).asVarying(); + const auto graphicsMeshes = model.addJob("BuildGraphicsMesh", buildGraphicsMeshInputs); // Combine the outputs into a new hfm::Model - const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, tangentsPerMesh, blendshapesPerMesh).asVarying(); + const auto buildBlendshapesInputs = BuildBlendshapesTask::Input(blendshapesPerMeshIn, normalsPerBlendshapePerMesh, tangentsPerBlendshapePerMesh).asVarying(); + const auto blendshapesPerMeshOut = model.addJob("BuildBlendshapes", buildBlendshapesInputs); + const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, normalsPerMesh, tangentsPerMesh, blendshapesPerMeshOut).asVarying(); const auto meshesOut = model.addJob("BuildMeshes", buildMeshesInputs); const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut).asVarying(); hfmModelOut = model.addJob("BuildModel", buildModelInputs); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h index 8615c1b17c..5d14ee5420 100644 --- a/libraries/model-baker/src/model-baker/BakerTypes.h +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -15,10 +15,25 @@ #include namespace baker { + using MeshIndices = std::vector; + using IndicesPerMesh = std::vector>; + using VerticesPerMesh = std::vector>; + using MeshNormals = std::vector; + using NormalsPerMesh = std::vector>; using MeshTangents = std::vector; using TangentsPerMesh = std::vector>; + using Blendshapes = std::vector; using BlendshapesPerMesh = std::vector>; + using BlendshapeVertices = std::vector; + using BlendshapeNormals = std::vector; + using BlendshapeIndices = std::vector; + using VerticesPerBlendshape = std::vector>; + using NormalsPerBlendshape = std::vector>; + using IndicesPerBlendshape = std::vector>; + using BlendshapeTangents = std::vector; + using TangentsPerBlendshape = std::vector>; + using MeshIndicesToModelNames = QHash; }; diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp index 6d351a99e9..370add2c2e 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -26,9 +26,18 @@ glm::vec3 normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, baker::MeshTangents& meshTangents, baker::Blendshapes& blendshapes) { +void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, const baker::MeshNormals& meshNormals, const baker::MeshTangents& meshTangentsIn) { auto graphicsMesh = std::make_shared(); + // Fill tangents with a dummy value to force tangents to be present if there are normals + baker::MeshTangents meshTangents; + if (!meshTangentsIn.empty()) { + meshTangents = meshTangentsIn; + } else { + meshTangents.reserve(meshNormals.size()); + std::fill_n(std::back_inserter(meshTangents), meshNormals.size(), Vectors::UNIT_X); + } + unsigned int totalSourceIndices = 0; foreach(const HFMMeshPart& part, hfmMesh.parts) { totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); @@ -48,23 +57,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics int numVerts = hfmMesh.vertices.size(); - if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - meshTangents.reserve(hfmMesh.normals.size()); - std::fill_n(std::back_inserter(meshTangents), hfmMesh.normals.size(), Vectors::UNIT_X); - } else { - meshTangents = hfmMesh.tangents.toStdVector(); - } - // Same thing with blend shapes - blendshapes = hfmMesh.blendshapes.toStdVector(); - for (auto& blendShape : blendshapes) { - if (!blendShape.normals.empty() && blendShape.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - blendShape.tangents.reserve(blendShape.normals.size()); - std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X); - } - } - // evaluate all attribute elements and data sizes // Position is a vec3 @@ -73,12 +65,12 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) const auto normalElement = HFM_NORMAL_ELEMENT; - const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); + const int normalsSize = (int)meshNormals.size() * normalElement.getSize(); const int tangentsSize = (int)meshTangents.size() * normalElement.getSize(); // If there are normals then there should be tangents assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { - HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in file"); + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in mesh"); } const auto normalsAndTangentsSize = normalsSize + tangentsSize; @@ -124,11 +116,11 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics if (normalsSize > 0) { std::vector normalsAndTangents; - normalsAndTangents.reserve(hfmMesh.normals.size() + (int)meshTangents.size()); - auto normalIt = hfmMesh.normals.constBegin(); + normalsAndTangents.reserve(meshNormals.size() + (int)meshTangents.size()); + auto normalIt = meshNormals.cbegin(); auto tangentIt = meshTangents.cbegin(); for (; - normalIt != hfmMesh.normals.constEnd(); + normalIt != meshNormals.cend(); ++normalIt, ++tangentIt) { #if HFM_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); @@ -212,11 +204,6 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics auto vertexFormat = std::make_shared(); auto vertexBufferStream = std::make_shared(); - // Decision time: - // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes - // else everything is interleaved in one buffer - - // Default case is no blend shapes gpu::BufferPointer attribBuffer; int totalAttribBufferSize = totalVertsSize; gpu::uint8 posChannel = 0; @@ -244,7 +231,7 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } } - // Pack normal and Tangent with the rest of atributes if no blend shapes + // Pack normal and Tangent with the rest of atributes if (colorsSize) { vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); bufOffset += colorElement.getSize(); @@ -384,22 +371,21 @@ void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphics } void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { - auto& meshes = input.get0(); - auto& url = input.get1(); - auto& meshIndicesToModelNames = input.get2(); + const auto& meshes = input.get0(); + const auto& url = input.get1(); + const auto& meshIndicesToModelNames = input.get2(); + const auto& normalsPerMesh = input.get3(); + const auto& tangentsPerMesh = input.get4(); + + auto& graphicsMeshes = output; - auto& graphicsMeshes = output.edit0(); - auto& tangentsPerMesh = output.edit1(); - auto& blendshapesPerMesh = output.edit2(); int n = (int)meshes.size(); for (int i = 0; i < n; i++) { graphicsMeshes.emplace_back(); auto& graphicsMesh = graphicsMeshes[i]; - tangentsPerMesh.emplace_back(); - blendshapesPerMesh.emplace_back(); // Try to create the graphics::Mesh - buildGraphicsMesh(meshes[i], graphicsMesh, tangentsPerMesh[i], blendshapesPerMesh[i]); + buildGraphicsMesh(meshes[i], graphicsMesh, normalsPerMesh[i], tangentsPerMesh[i]); // Choose a name for the mesh if (graphicsMesh) { diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h index 3c8985ef9a..bb4136c086 100644 --- a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h @@ -20,8 +20,8 @@ class BuildGraphicsMeshTask { public: - using Input = baker::VaryingSet3, hifi::URL, baker::MeshIndicesToModelNames>; - using Output = baker::VaryingSet3, std::vector, std::vector>; + using Input = baker::VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::NormalsPerMesh, baker::TangentsPerMesh>; + using Output = std::vector; using JobModel = baker::Job::ModelIO; void run(const baker::BakeContextPointer& context, const Input& input, Output& output); diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp new file mode 100644 index 0000000000..b908fa9ced --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.cpp @@ -0,0 +1,70 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// 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 "CalculateBlendshapeNormalsTask.h" + +#include "ModelMath.h" + +void CalculateBlendshapeNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& blendshapesPerMesh = input.get0(); + const auto& meshes = input.get1(); + auto& normalsPerBlendshapePerMeshOut = output; + + normalsPerBlendshapePerMeshOut.reserve(blendshapesPerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& mesh = meshes[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + normalsPerBlendshapePerMeshOut.emplace_back(); + auto& normalsPerBlendshapeOut = normalsPerBlendshapePerMeshOut[normalsPerBlendshapePerMeshOut.size()-1]; + + normalsPerBlendshapeOut.reserve(blendshapes.size()); + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& normalsIn = blendshape.normals; + // Check if normals are already defined. Otherwise, calculate them from existing blendshape vertices. + if (!normalsIn.empty()) { + normalsPerBlendshapeOut.push_back(normalsIn.toStdVector()); + } else { + // Create lookup to get index in blendshape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + normalsPerBlendshapeOut.emplace_back(); + auto& normals = normalsPerBlendshapeOut[normalsPerBlendshapeOut.size()-1]; + normals.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&reverseIndices, &blendshape, &normals](int normalIndex) /* NormalAccessor */ { + const auto lookupIndex = reverseIndices[normalIndex]; + if (lookupIndex < blendshape.vertices.size()) { + return &normals[lookupIndex]; + } else { + // Index isn't in the blendshape. Request that the normal not be calculated. + return (glm::vec3*)nullptr; + } + }, + [&mesh, &reverseIndices, &blendshape](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + const auto lookupIndex = reverseIndices[vertexIndex]; + if (lookupIndex < blendshape.vertices.size()) { + outVertex = blendshape.vertices[lookupIndex]; + } else { + // Index isn't in the blendshape, so return vertex from mesh + outVertex = mesh.vertices[lookupIndex]; + } + }); + } + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h new file mode 100644 index 0000000000..0eb7d3c4e7 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeNormalsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// 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_CalculateBlendshapeNormalsTask_h +#define hifi_CalculateBlendshapeNormalsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape normals if not already present in the blendshape +class CalculateBlendshapeNormalsTask { +public: + using Input = baker::VaryingSet2>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp new file mode 100644 index 0000000000..66e1dadc41 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp @@ -0,0 +1,95 @@ +// +// CalculateBlendshapeTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/08. +// 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 "CalculateBlendshapeTangentsTask.h" + +#include + +#include "ModelMath.h" + +void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerBlendshapePerMesh = input.get0(); + const auto& blendshapesPerMesh = input.get1(); + const auto& meshes = input.get2(); + const hfm::Model& model = *input.get3(); + auto& tangentsPerBlendshapePerMeshOut = output; + + tangentsPerBlendshapePerMeshOut.reserve(normalsPerBlendshapePerMesh.size()); + for (size_t i = 0; i < blendshapesPerMesh.size(); i++) { + const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; + const auto& blendshapes = blendshapesPerMesh[i]; + const auto& mesh = meshes[i]; + tangentsPerBlendshapePerMeshOut.emplace_back(); + auto& tangentsPerBlendshapeOut = tangentsPerBlendshapePerMeshOut[tangentsPerBlendshapePerMeshOut.size()-1]; + + // Check if we actually need to calculate the tangents, or just append empty arrays + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = model.materials.find(meshPart.materialID); + if (materialIt != model.materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + + for (size_t j = 0; j < blendshapes.size(); j++) { + const auto& blendshape = blendshapes[j]; + const auto& tangentsIn = blendshape.tangents; + const auto& normals = normalsPerBlendshape[j]; + tangentsPerBlendshapeOut.emplace_back(); + auto& tangentsOut = tangentsPerBlendshapeOut[tangentsPerBlendshapeOut.size()-1]; + + // Check if we already have tangents + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we can and should calculate tangents (we need normals to calculate the tangents) + if (normals.empty() || !needTangents) { + continue; + } + tangentsOut.resize(normals.size()); + + // Create lookup to get index in blend shape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + for (int indexInBlendShape = 0; indexInBlendShape < blendshape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendshape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + baker::calculateTangents(mesh, + [&mesh, &blendshape, &normals, &tangentsOut, &reverseIndices](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + const auto index1 = reverseIndices[firstIndex]; + const auto index2 = reverseIndices[secondIndex]; + + if (index1 < blendshape.vertices.size()) { + outVertices[0] = blendshape.vertices[index1]; + outTexCoords[0] = mesh.texCoords[index1]; + outTexCoords[1] = mesh.texCoords[index2]; + if (index2 < blendshape.vertices.size()) { + outVertices[1] = blendshape.vertices[index2]; + } else { + // Index isn't in the blend shape so return vertex from mesh + outVertices[1] = mesh.vertices[secondIndex]; + } + outNormal = normals[index1]; + return &tangentsOut[index1]; + } else { + // Index isn't in blend shape so return nullptr + return (glm::vec3*)nullptr; + } + }); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h new file mode 100644 index 0000000000..9ff79d17a6 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h @@ -0,0 +1,28 @@ +// +// CalculateBlendshapeTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// 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_CalculateBlendshapeTangentsTask_h +#define hifi_CalculateBlendshapeTangentsTask_h + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate blendshape tangents if not already present in the blendshape +class CalculateBlendshapeTangentsTask { +public: + using Input = baker::VaryingSet4, baker::BlendshapesPerMesh, std::vector, hfm::Model::Pointer>; + using Output = std::vector; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateBlendshapeTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp new file mode 100644 index 0000000000..02986e160e --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp @@ -0,0 +1,40 @@ +// +// CalculateMeshNormalsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// 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 "CalculateMeshNormalsTask.h" + +#include "ModelMath.h" + +void CalculateMeshNormalsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& meshes = input; + auto& normalsPerMeshOut = output; + + normalsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < meshes.size(); i++) { + const auto& mesh = meshes[i]; + normalsPerMeshOut.emplace_back(); + auto& normalsOut = normalsPerMeshOut[normalsPerMeshOut.size()-1]; + // Only calculate normals if this mesh doesn't already have them + if (!mesh.normals.empty()) { + normalsOut = mesh.normals.toStdVector(); + } else { + normalsOut.resize(mesh.vertices.size()); + baker::calculateNormals(mesh, + [&normalsOut](int normalIndex) /* NormalAccessor */ { + return &normalsOut[normalIndex]; + }, + [&mesh](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ { + outVertex = mesh.vertices[vertexIndex]; + } + ); + } + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h new file mode 100644 index 0000000000..b32780c68d --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.h @@ -0,0 +1,30 @@ +// +// CalculateMeshNormalsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// 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_CalculateMeshNormalsTask_h +#define hifi_CalculateMeshNormalsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh normals if not already present in the mesh +class CalculateMeshNormalsTask { +public: + using Input = std::vector; + using Output = baker::NormalsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshNormalsTask_h diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp new file mode 100644 index 0000000000..96cf4ddc5c --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -0,0 +1,66 @@ +// +// CalculateMeshTangentsTask.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/22. +// 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 "CalculateMeshTangentsTask.h" + +#include "ModelMath.h" + +void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + const auto& normalsPerMesh = input.get0(); + const std::vector& meshes = input.get1(); + const hfm::Model& model = *input.get2(); + auto& tangentsPerMeshOut = output; + + tangentsPerMeshOut.reserve(meshes.size()); + for (int i = 0; i < meshes.size(); i++) { + const auto& mesh = meshes[i]; + const auto& tangentsIn = mesh.tangents; + const auto& normals = normalsPerMesh[i]; + const auto& vertices = mesh.vertices; + tangentsPerMeshOut.emplace_back(); + auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; + + // Check if we already have tangents and therefore do not need to do any calculation + if (!tangentsIn.empty()) { + tangentsOut = tangentsIn.toStdVector(); + continue; + } + + // Check if we have normals, and if not then tangents can't be calculated + if (normals.empty()) { + continue; + } + + // Check if we actually need to calculate the tangents + bool needTangents = false; + for (const auto& meshPart : mesh.parts) { + auto materialIt = model.materials.find(meshPart.materialID); + if (materialIt != model.materials.end() && (*materialIt).needTangentSpace()) { + needTangents = true; + break; + } + } + if (needTangents) { + continue; + } + + tangentsOut.resize(normals.size()); + baker::calculateTangents(mesh, + [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { + outVertices[0] = mesh.vertices[firstIndex]; + outVertices[1] = mesh.vertices[secondIndex]; + outNormal = normals[firstIndex]; + outTexCoords[0] = mesh.texCoords[firstIndex]; + outTexCoords[1] = mesh.texCoords[secondIndex]; + return &(tangentsOut[firstIndex]); + }); + } +} diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h new file mode 100644 index 0000000000..1d0d572b28 --- /dev/null +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h @@ -0,0 +1,32 @@ +// +// CalculateMeshTangentsTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// 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_CalculateMeshTangentsTask_h +#define hifi_CalculateMeshTangentsTask_h + +#include + +#include "Engine.h" +#include "BakerTypes.h" + +// Calculate mesh tangents if not already present in the mesh +class CalculateMeshTangentsTask { +public: + using NormalsPerMesh = std::vector>; + + using Input = baker::VaryingSet3, hfm::Model::Pointer>; + using Output = baker::TangentsPerMesh; + using JobModel = baker::Job::ModelIO; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_CalculateMeshTangentsTask_h diff --git a/libraries/model-baker/src/model-baker/ModelMath.cpp b/libraries/model-baker/src/model-baker/ModelMath.cpp new file mode 100644 index 0000000000..50e7e89cf4 --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.cpp @@ -0,0 +1,121 @@ +// +// ModelMath.cpp +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/18. +// 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 "ModelMath.h" + +#include +#include "ModelBakerLogging.h" + +namespace baker { + template + const T& checkedAt(const QVector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + const T& checkedAt(const std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + template + T& checkedAt(std::vector& vector, int i) { + if (i < 0 || i >= vector.size()) { + throw std::out_of_range("baker::checked_at (ModelMath.cpp): index " + std::to_string(i) + " is out of range"); + } + return vector[i]; + } + + void setTangent(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex) { + glm::vec3 vertex[2]; + glm::vec2 texCoords[2]; + glm::vec3 normal; + glm::vec3* tangent = vertexAccessor(firstIndex, secondIndex, vertex, texCoords, normal); + if (tangent) { + glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); + if (glm::length(bitangent) < EPSILON) { + return; + } + glm::vec2 texCoordDelta = texCoords[1] - texCoords[0]; + glm::vec3 normalizedNormal = glm::normalize(normal); + *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * + glm::normalize(bitangent), normalizedNormal); + } + } + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexSetter) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + glm::vec3* n0 = normalAccessor(part.quadIndices[i]); + glm::vec3* n1 = normalAccessor(part.quadIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.quadIndices[i + 2]); + glm::vec3* n3 = normalAccessor(part.quadIndices[i + 3]); + if (!n0 || !n1 || !n2 || !n3) { + // Quad is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; // Assume all vertices in this quad are in the same plane, so only the first three are needed to calculate the normal + vertexSetter(part.quadIndices[i], vertices[0]); + vertexSetter(part.quadIndices[i + 1], vertices[1]); + vertexSetter(part.quadIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = *n3 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + glm::vec3* n0 = normalAccessor(part.triangleIndices[i]); + glm::vec3* n1 = normalAccessor(part.triangleIndices[i + 1]); + glm::vec3* n2 = normalAccessor(part.triangleIndices[i + 2]); + if (!n0 || !n1 || !n2) { + // Tri is not in the mesh (can occur with blendshape meshes, which are a subset of the hfm Mesh vertices) + continue; + } + glm::vec3 vertices[3]; + vertexSetter(part.triangleIndices[i], vertices[0]); + vertexSetter(part.triangleIndices[i + 1], vertices[1]); + vertexSetter(part.triangleIndices[i + 2], vertices[2]); + *n0 = *n1 = *n2 = glm::cross(vertices[1] - vertices[0], vertices[2] - vertices[0]); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateNormals: part.triangleIndices.size() is not divisible by three"); + } + } + } + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor) { + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + for (const HFMMeshPart& part : mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + setTangent(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1)); + setTangent(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); + setTangent(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); + setTangent(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i)); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + setTangent(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); + setTangent(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); + } + if ((part.triangleIndices.size() % 3) != 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "Error in baker::calculateTangents: part.triangleIndices.size() is not divisible by three"); + } + } + } +} + diff --git a/libraries/model-baker/src/model-baker/ModelMath.h b/libraries/model-baker/src/model-baker/ModelMath.h new file mode 100644 index 0000000000..2a909e6eed --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelMath.h @@ -0,0 +1,34 @@ +// +// ModelMath.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2019/01/07. +// 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 + +#include "BakerTypes.h" + +namespace baker { + // Returns a reference to the normal at the specified index, or nullptr if it cannot be accessed + using NormalAccessor = std::function; + + // Assigns a vertex to outVertex given the lookup index + using VertexSetter = std::function; + + void calculateNormals(const hfm::Mesh& mesh, NormalAccessor normalAccessor, VertexSetter vertexAccessor); + + // firstIndex, secondIndex: the vertex indices to be used for calculation + // outVertices: should be assigned a 2 element array containing the vertices at firstIndex and secondIndex + // outTexCoords: same as outVertices but for texture coordinates + // outNormal: reference to the normal of this triangle + // + // Return value: pointer to the tangent you want to be calculated + using IndexAccessor = std::function; + + void calculateTangents(const hfm::Mesh& mesh, IndexAccessor accessor); +}; From fe60b8e69bd8752e4c40252d8bd4d1083566352d Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 24 Jan 2019 11:49:44 -0800 Subject: [PATCH 06/13] Remove now redundant tangent generation code --- libraries/fbx/src/FBXSerializer.cpp | 3 - libraries/hfm/src/hfm/HFM.cpp | 102 ------------------ libraries/hfm/src/hfm/HFM.h | 3 - .../model-baker/src/model-baker/Baker.cpp | 1 + 4 files changed, 1 insertion(+), 108 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 68019c2f4b..bd43de429b 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1452,9 +1452,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } - extracted.mesh.createMeshTangents(generateTangents); - extracted.mesh.createBlendShapeTangents(generateTangents); - // find the clusters with which the mesh is associated QVector clusterIDs; foreach (const QString& childID, _connectionChildMap.values(it.key())) { diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index b9e630456d..908fb1e97b 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -67,108 +67,6 @@ bool HFMMaterial::needTangentSpace() const { return !normalTexture.isNull(); } -static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape); - -void HFMMesh::createBlendShapeTangents(bool generateTangents) { - for (auto& blendShape : blendshapes) { - _createBlendShapeTangents(*this, generateTangents, blendShape); - } -} - -using IndexAccessor = std::function; - -static void setTangents(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, - const QVector& vertices, const QVector& normals, QVector& tangents) { - glm::vec3 vertex[2]; - glm::vec3 normal; - glm::vec3* tangent = vertexAccessor(mesh, firstIndex, secondIndex, vertex, normal); - if (tangent) { - glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); - if (glm::length(bitangent) < EPSILON) { - return; - } - glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); - glm::vec3 normalizedNormal = glm::normalize(normal); - *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * - glm::normalize(bitangent), normalizedNormal); - } -} - -static void createTangents(const HFMMesh& mesh, bool generateFromTexCoords, - const QVector& vertices, const QVector& normals, QVector& tangents, - IndexAccessor accessor) { - // if we have a normal map (and texture coordinates), we must compute tangents - if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { - tangents.resize(vertices.size()); - - foreach(const HFMMeshPart& part, mesh.parts) { - for (int i = 0; i < part.quadIndices.size(); i += 4) { - setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3), vertices, normals, tangents); - setTangents(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i), vertices, normals, tangents); - } - // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 - // This is most likely evidence of a further problem in extractMesh() - for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { - setTangents(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1), vertices, normals, tangents); - setTangents(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2), vertices, normals, tangents); - setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents); - } - if ((part.triangleIndices.size() % 3) != 0) { - qCDebug(modelformat) << "Error in extractHFMModel part.triangleIndices.size() is not divisible by three "; - } - } - } -} - -void HFMMesh::createMeshTangents(bool generateFromTexCoords) { - HFMMesh& mesh = *this; - // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't - // const in the lambda function. - auto& tangents = mesh.tangents; - createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents, - [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { - outVertices[0] = mesh.vertices[firstIndex]; - outVertices[1] = mesh.vertices[secondIndex]; - outNormal = mesh.normals[firstIndex]; - return &(tangents[firstIndex]); - }); -} - -static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape) { - // Create lookup to get index in blend shape from vertex index in mesh - std::vector reverseIndices; - reverseIndices.resize(mesh.vertices.size()); - std::iota(reverseIndices.begin(), reverseIndices.end(), 0); - - for (int indexInBlendShape = 0; indexInBlendShape < blendShape.indices.size(); ++indexInBlendShape) { - auto indexInMesh = blendShape.indices[indexInBlendShape]; - reverseIndices[indexInMesh] = indexInBlendShape; - } - - createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents, - [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { - const auto index1 = reverseIndices[firstIndex]; - const auto index2 = reverseIndices[secondIndex]; - - if (index1 < blendShape.vertices.size()) { - outVertices[0] = blendShape.vertices[index1]; - if (index2 < blendShape.vertices.size()) { - outVertices[1] = blendShape.vertices[index2]; - } else { - // Index isn't in the blend shape so return vertex from mesh - outVertices[1] = mesh.vertices[secondIndex]; - } - outNormal = blendShape.normals[index1]; - return &blendShape.tangents[index1]; - } else { - // Index isn't in blend shape so return nullptr - return (glm::vec3*)nullptr; - } - }); -} - QStringList HFMModel::getJointNames() const { QStringList names; foreach (const HFMJoint& joint, joints) { diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index 78f608d72e..1bd87332a1 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -239,9 +239,6 @@ public: graphics::MeshPointer _mesh; bool wasCompressed { false }; - - void createMeshTangents(bool generateFromTexCoords); - void createBlendShapeTangents(bool generateTangents); }; /**jsdoc diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index c15b2c9926..765003fef7 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -123,6 +123,7 @@ namespace baker { const auto blendshapesPerMeshIn = modelPartsIn.getN(3); // Calculate normals and tangents for meshes and blendshapes if they do not exist + // Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer. const auto normalsPerMesh = model.addJob("CalculateMeshNormals", meshesIn); const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, hfmModelIn).asVarying(); const auto tangentsPerMesh = model.addJob("CalculateMeshTangents", calculateMeshTangentsInputs); From 64ef8b691983a0fe5a240a65534b70cc32f3df27 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 24 Jan 2019 13:02:53 -0800 Subject: [PATCH 07/13] Do not pass in whole hfm::Model by reference to tangent calculation prep steps --- libraries/model-baker/src/model-baker/Baker.cpp | 8 +++++--- .../src/model-baker/CalculateBlendshapeTangentsTask.cpp | 6 +++--- .../src/model-baker/CalculateBlendshapeTangentsTask.h | 2 +- .../src/model-baker/CalculateMeshTangentsTask.cpp | 6 +++--- .../src/model-baker/CalculateMeshTangentsTask.h | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 765003fef7..f9680ab89f 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -25,7 +25,7 @@ namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet4, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh>; + using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { @@ -38,6 +38,7 @@ namespace baker { for (int i = 0; i < hfmModelIn->meshes.size(); i++) { blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); } + output.edit4() = hfmModelIn->materials; } }; @@ -121,15 +122,16 @@ namespace baker { const auto url = modelPartsIn.getN(1); const auto meshIndicesToModelNames = modelPartsIn.getN(2); const auto blendshapesPerMeshIn = modelPartsIn.getN(3); + const auto materials = modelPartsIn.getN(4); // Calculate normals and tangents for meshes and blendshapes if they do not exist // Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer. const auto normalsPerMesh = model.addJob("CalculateMeshNormals", meshesIn); - const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, hfmModelIn).asVarying(); + const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, materials).asVarying(); const auto tangentsPerMesh = model.addJob("CalculateMeshTangents", calculateMeshTangentsInputs); const auto calculateBlendshapeNormalsInputs = CalculateBlendshapeNormalsTask::Input(blendshapesPerMeshIn, meshesIn).asVarying(); const auto normalsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeNormals", calculateBlendshapeNormalsInputs); - const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, hfmModelIn).asVarying(); + const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, materials).asVarying(); const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); // Build the graphics::MeshPointer for each hfm::Mesh diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp index 66e1dadc41..04e05f0378 100644 --- a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp @@ -19,7 +19,7 @@ void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& conte const auto& normalsPerBlendshapePerMesh = input.get0(); const auto& blendshapesPerMesh = input.get1(); const auto& meshes = input.get2(); - const hfm::Model& model = *input.get3(); + const auto& materials = input.get3(); auto& tangentsPerBlendshapePerMeshOut = output; tangentsPerBlendshapePerMeshOut.reserve(normalsPerBlendshapePerMesh.size()); @@ -33,8 +33,8 @@ void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& conte // Check if we actually need to calculate the tangents, or just append empty arrays bool needTangents = false; for (const auto& meshPart : mesh.parts) { - auto materialIt = model.materials.find(meshPart.materialID); - if (materialIt != model.materials.end() && (*materialIt).needTangentSpace()) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { needTangents = true; break; } diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h index 9ff79d17a6..c24b41d2d9 100644 --- a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h @@ -18,7 +18,7 @@ // Calculate blendshape tangents if not already present in the blendshape class CalculateBlendshapeTangentsTask { public: - using Input = baker::VaryingSet4, baker::BlendshapesPerMesh, std::vector, hfm::Model::Pointer>; + using Input = baker::VaryingSet4, baker::BlendshapesPerMesh, std::vector, QHash>; using Output = std::vector; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index 96cf4ddc5c..51251a0005 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -16,7 +16,7 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { const auto& normalsPerMesh = input.get0(); const std::vector& meshes = input.get1(); - const hfm::Model& model = *input.get2(); + const auto& materials = input.get2(); auto& tangentsPerMeshOut = output; tangentsPerMeshOut.reserve(meshes.size()); @@ -42,8 +42,8 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co // Check if we actually need to calculate the tangents bool needTangents = false; for (const auto& meshPart : mesh.parts) { - auto materialIt = model.materials.find(meshPart.materialID); - if (materialIt != model.materials.end() && (*materialIt).needTangentSpace()) { + auto materialIt = materials.find(meshPart.materialID); + if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { needTangents = true; break; } diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h index 1d0d572b28..b8fdb7d5f4 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h @@ -22,7 +22,7 @@ class CalculateMeshTangentsTask { public: using NormalsPerMesh = std::vector>; - using Input = baker::VaryingSet3, hfm::Model::Pointer>; + using Input = baker::VaryingSet3, QHash>; using Output = baker::TangentsPerMesh; using JobModel = baker::Job::ModelIO; From 5993aab1743e8e5765f315d91e8e03b8e3695138 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Thu, 24 Jan 2019 14:43:43 -0800 Subject: [PATCH 08/13] Fix linker error and build warnings --- libraries/model-baker/CMakeLists.txt | 4 +--- libraries/model-baker/src/model-baker/Baker.cpp | 4 ++-- .../model-baker/src/model-baker/CalculateMeshNormalsTask.cpp | 2 +- .../model-baker/src/model-baker/CalculateMeshTangentsTask.cpp | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/libraries/model-baker/CMakeLists.txt b/libraries/model-baker/CMakeLists.txt index 50b113976f..6fa7c1815a 100644 --- a/libraries/model-baker/CMakeLists.txt +++ b/libraries/model-baker/CMakeLists.txt @@ -1,6 +1,4 @@ set(TARGET_NAME model-baker) setup_hifi_library() -link_hifi_libraries(shared task gpu graphics) - -include_hifi_library_headers(hfm) +link_hifi_libraries(shared task gpu graphics hfm) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index f9680ab89f..8d1a82518d 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -56,11 +56,11 @@ namespace baker { blendshapesPerMeshOut = blendshapesPerMeshIn; - for (int i = 0; i < blendshapesPerMeshOut.size(); i++) { + for (int i = 0; i < (int)blendshapesPerMeshOut.size(); i++) { const auto& normalsPerBlendshape = normalsPerBlendshapePerMesh[i]; const auto& tangentsPerBlendshape = tangentsPerBlendshapePerMesh[i]; auto& blendshapesOut = blendshapesPerMeshOut[i]; - for (int j = 0; j < blendshapesOut.size(); j++) { + for (int j = 0; j < (int)blendshapesOut.size(); j++) { const auto& normals = normalsPerBlendshape[j]; const auto& tangents = tangentsPerBlendshape[j]; auto& blendshape = blendshapesOut[j]; diff --git a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp index 02986e160e..a6884e104d 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshNormalsTask.cpp @@ -18,7 +18,7 @@ void CalculateMeshNormalsTask::run(const baker::BakeContextPointer& context, con auto& normalsPerMeshOut = output; normalsPerMeshOut.reserve(meshes.size()); - for (int i = 0; i < meshes.size(); i++) { + for (int i = 0; i < (int)meshes.size(); i++) { const auto& mesh = meshes[i]; normalsPerMeshOut.emplace_back(); auto& normalsOut = normalsPerMeshOut[normalsPerMeshOut.size()-1]; diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index 51251a0005..e94e15507e 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -20,11 +20,10 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co auto& tangentsPerMeshOut = output; tangentsPerMeshOut.reserve(meshes.size()); - for (int i = 0; i < meshes.size(); i++) { + 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& vertices = mesh.vertices; tangentsPerMeshOut.emplace_back(); auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; From 992ce9f875d1169d216ee2830883abf3de5b649d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 25 Jan 2019 04:41:36 +0100 Subject: [PATCH 09/13] ignore scripted key events from the JSON Editors --- scripts/system/html/js/entityProperties.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 15a5a32a34..f66077a4d3 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3797,6 +3797,11 @@ function loaded() { if (FILTERED_NODE_NAMES.includes(keyUpEvent.target.nodeName)) { return; } + + if (elUserDataEditor.contains(keyUpEvent.target) || elMaterialDataEditor.contains(keyUpEvent.target)) { + return; + } + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; From e2dc86261ebdb6e9d43a30cb7e7e092f6eacb3f7 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 25 Jan 2019 05:16:04 +0100 Subject: [PATCH 10/13] add missing Material Entity tooltips --- scripts/system/assets/data/createAppTooltips.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index bf3ff3f324..2de310e955 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -370,6 +370,9 @@ "priority": { "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." }, + "materialMappingMode": { + "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entities their UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." + }, "materialMappingPos": { "tooltip": "The offset position of the bottom left of the material within the parent's UV space." }, @@ -379,6 +382,9 @@ "materialMappingRot": { "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." }, + "materialRepeat": { + "tooltip": "If enabled, the material will repeat." + }, "followCamera": { "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." }, From ef1018c49bebcd87d1a402cd3fd94486a3b628a7 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 28 Jan 2019 16:10:29 +0100 Subject: [PATCH 11/13] - EntityShapeVisualizer.updateSelection -> EntityShapeVisualizer.setEntities (for a more generic use) - GridPattern -> Material - Replaced stringified compare with deep compare - Copied+modified zone icon (consistent color, removed sharp edges) --- scripts/system/assets/images/icon-zone.svg | 73 +++++++++++ .../assets/images/materials/GridPattern.json | 13 ++ ...patterncreatezonew.png => GridPattern.png} | Bin scripts/system/edit.js | 4 +- .../system/modules/entityShapeVisualizer.js | 115 +++++++++++++----- 5 files changed, 171 insertions(+), 34 deletions(-) create mode 100644 scripts/system/assets/images/icon-zone.svg create mode 100644 scripts/system/assets/images/materials/GridPattern.json rename scripts/system/assets/images/materials/{boxgridpatterncreatezonew.png => GridPattern.png} (100%) diff --git a/scripts/system/assets/images/icon-zone.svg b/scripts/system/assets/images/icon-zone.svg new file mode 100644 index 0000000000..41aeac4951 --- /dev/null +++ b/scripts/system/assets/images/icon-zone.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/system/assets/images/materials/GridPattern.json b/scripts/system/assets/images/materials/GridPattern.json new file mode 100644 index 0000000000..468b709ea4 --- /dev/null +++ b/scripts/system/assets/images/materials/GridPattern.json @@ -0,0 +1,13 @@ +{ + "materialVersion": 1, + "materials": { + "albedo": [ + 0.0, + 0.0, + 7.0 + ], + "unlit": true, + "opacity": 0.4, + "albedoMap": "GridPattern.png" + } +} diff --git a/scripts/system/assets/images/materials/boxgridpatterncreatezonew.png b/scripts/system/assets/images/materials/GridPattern.png similarity index 100% rename from scripts/system/assets/images/materials/boxgridpatterncreatezonew.png rename to scripts/system/assets/images/materials/GridPattern.png diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c6a30fe209..2e175d72a4 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -82,7 +82,7 @@ var selectionManager = SelectionManager; var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); -var ZONE_URL = Script.resourcesPath() + "icons/create-icons/23-zone-01.svg"; +var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); @@ -119,7 +119,7 @@ var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); - entityShapeVisualizer.updateSelection(selectionManager.selections); + entityShapeVisualizer.setEntities(selectionManager.selections); }); var DEGREES_TO_RADIANS = Math.PI / 180.0; diff --git a/scripts/system/modules/entityShapeVisualizer.js b/scripts/system/modules/entityShapeVisualizer.js index d1136c513c..fe950c2e2b 100644 --- a/scripts/system/modules/entityShapeVisualizer.js +++ b/scripts/system/modules/entityShapeVisualizer.js @@ -16,6 +16,10 @@ var SHAPETYPE_TO_SHAPE = { "cylinder-y": "Cylinder", }; +var REQUESTED_ENTITY_SHAPE_PROPERTIES = [ + 'type', 'shapeType', 'compoundShapeURL', 'localDimensions' +]; + function getEntityShapePropertiesForType(properties) { switch (properties.type) { case "Zone": @@ -51,27 +55,80 @@ function getEntityShapePropertiesForType(properties) { }; } -function getStringifiedEntityShapePropertiesForType(properties) { - return JSON.stringify(getEntityShapePropertiesForType(properties)); +function deepEqual(a, b) { + if (a === b) { + return true; + } + + if (typeof(a) !== "object" || typeof(b) !== "object") { + return false; + } + + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + + for (var property in a) { + if (!a.hasOwnProperty(property)) { + continue; + } + if (!b.hasOwnProperty(property)) { + return false; + } + if (!deepEqual(a[property], b[property])) { + return false; + } + } + return true; } -var REQUESTED_ENTITY_SHAPE_PROPERTIES = [ - 'type', 'shapeType', 'compoundShapeURL', 'localDimensions' -]; +/** + * Returns an array of property names which are different in comparison. + * @param propertiesA + * @param propertiesB + * @returns {Array} - array of different property names + */ +function compareEntityProperties(propertiesA, propertiesB) { + var differentProperties = [], + property; + + for (property in propertiesA) { + if (!propertiesA.hasOwnProperty(property)) { + continue; + } + if (!propertiesB.hasOwnProperty(property) || !deepEqual(propertiesA[property], propertiesB[property])) { + differentProperties.push(property); + } + } + for (property in propertiesB) { + if (!propertiesB.hasOwnProperty(property)) { + continue; + } + if (!propertiesA.hasOwnProperty(property)) { + differentProperties.push(property); + } + } + + return differentProperties; +} + +function deepCopy(v) { + return JSON.parse(JSON.stringify(v)); +} function EntityShape(entityID) { this.entityID = entityID; - var properties = Entities.getEntityProperties(entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); + var propertiesForType = getEntityShapePropertiesForType(Entities.getEntityProperties(entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES)); - this.previousPropertiesForTypeStringified = getStringifiedEntityShapePropertiesForType(properties); + this.previousPropertiesForType = propertiesForType; - this.initialize(properties, this.previousPropertiesForTypeStringified); + this.initialize(propertiesForType); } EntityShape.prototype = { - initialize: function(properties, propertiesForTypeStringified) { + initialize: function(properties) { // Create new instance of JS object: - var overlayProperties = JSON.parse(propertiesForTypeStringified); + var overlayProperties = deepCopy(properties); overlayProperties.localPosition = Vec3.ZERO; overlayProperties.localRotation = Quat.IDENTITY; @@ -88,29 +145,23 @@ EntityShape.prototype = { parentID: this.entity, priority: 1, materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", - materialURL: "materialData", - materialData: JSON.stringify({ - materialVersion: 1, - materials: { - albedo: [0.0, 0.0, 7.0], - unlit: true, - opacity: 0.4, - albedoMap: Script.resolvePath("../assets/images/materials/boxgridpatterncreatezonew.png") - } - }), + materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), }, "local"); }, update: function() { - var properties = Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES); - var propertiesForTypeStringified = getStringifiedEntityShapePropertiesForType(properties); - if (propertiesForTypeStringified !== this.previousPropertiesForTypeStringified) { - this.previousPropertiesForTypeStringified = propertiesForTypeStringified; - this.clear(); - this.initialize(properties, propertiesForTypeStringified); - } else { + var propertiesForType = getEntityShapePropertiesForType(Entities.getEntityProperties(this.entityID, REQUESTED_ENTITY_SHAPE_PROPERTIES)); + + var difference = compareEntityProperties(propertiesForType, this.previousPropertiesForType); + + if (deepEqual(difference, ['localDimensions'])) { + this.previousPropertiesForType = propertiesForType; Entities.editEntity(this.entity, { - localDimensions: JSON.parse(propertiesForTypeStringified).localDimensions, + localDimensions: propertiesForType.localDimensions, }); + } else if (difference.length > 0) { + this.previousPropertiesForType = propertiesForType; + this.clear(); + this.initialize(propertiesForType); } }, clear: function() { @@ -128,7 +179,7 @@ function EntityShapeVisualizer(visualizedTypes) { } EntityShapeVisualizer.prototype = { - addEntity: function(entityID, properties) { + addEntity: function(entityID) { if (this.entityShapes[entityID]) { return; } @@ -154,8 +205,8 @@ EntityShapeVisualizer.prototype = { }, this); this.entityShapes = {}; }, - updateSelection: function(selection) { - var qualifiedSelection = selection.filter(function(entityID) { + setEntities: function(entities) { + var qualifiedEntities = entities.filter(function(entityID) { if (this.acceptedEntities.indexOf(entityID) !== -1) { return true; } @@ -175,7 +226,7 @@ EntityShapeVisualizer.prototype = { var updateEntries = []; var currentEntries = Object.keys(this.entityShapes); - qualifiedSelection.forEach(function(entityID) { + qualifiedEntities.forEach(function(entityID) { if (currentEntries.indexOf(entityID) !== -1) { updateEntries.push(entityID); } else { From bfb93e67b7a396e9e4ac554d66c7025ed0be6c51 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 28 Jan 2019 16:46:35 +0100 Subject: [PATCH 12/13] materialRepeat will clamp when disabled. --- scripts/system/assets/data/createAppTooltips.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 2de310e955..35ef36ed58 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -383,7 +383,7 @@ "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." }, "materialRepeat": { - "tooltip": "If enabled, the material will repeat." + "tooltip": "If enabled, the material will repeat, otherwise it will clamp." }, "followCamera": { "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." From b75c4ae3b694110c0ba27d6149ced9fb6868abd6 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 28 Jan 2019 20:26:04 +0100 Subject: [PATCH 13/13] fix spelling --- scripts/system/assets/data/createAppTooltips.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 35ef36ed58..4c78da7306 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -371,7 +371,7 @@ "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." }, "materialMappingMode": { - "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entities their UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." + "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entity's UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." }, "materialMappingPos": { "tooltip": "The offset position of the bottom left of the material within the parent's UV space."