From 2acb6b6857f46c45cb6ae33fe61eeadf442de40e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 28 Nov 2018 16:00:29 -0800 Subject: [PATCH 01/20] v3 procedurals and shader fixes --- .../src/RenderableShapeEntityItem.cpp | 19 +-- libraries/gpu/src/gpu/Pipeline.h | 2 - .../procedural/src/procedural/Procedural.cpp | 122 +++++++++--------- .../procedural/src/procedural/Procedural.h | 64 +++++---- .../src/procedural/ProceduralCommon.slh | 18 ++- .../src/procedural/ProceduralSkybox.cpp | 1 - libraries/render-utils/src/forward_simple.slf | 4 +- libraries/render-utils/src/simple.slf | 78 ++++++++--- .../render-utils/src/simple_transparent.slf | 81 +++++++++--- 9 files changed, 249 insertions(+), 140 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index a705b61cd3..1b5b8bccaf 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -38,18 +38,10 @@ ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Pare // FIXME: Setup proper uniform slots and use correct pipelines for forward rendering _procedural._opaqueFragmentSource = gpu::Shader::Source::get(shader::render_utils::fragment::simple); _procedural._transparentFragmentSource = gpu::Shader::Source::get(shader::render_utils::fragment::simple_transparent); - _procedural._opaqueState->setCullMode(gpu::State::CULL_NONE); - _procedural._opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); + + // TODO: move into Procedural.cpp PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); - _procedural._opaqueState->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _procedural._transparentState->setCullMode(gpu::State::CULL_BACK); - _procedural._transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMask(*_procedural._transparentState); - _procedural._transparentState->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); } bool ShapeEntityRenderer::needsRenderUpdate() const { @@ -212,7 +204,10 @@ ShapeKey ShapeEntityRenderer::getShapeKey() { return builder.build(); } else { ShapeKey::Builder builder; - if (_procedural.isReady()) { + bool proceduralReady = resultWithReadLock([&] { + return _procedural.isReady(); + }); + if (proceduralReady) { builder.withOwnPipeline(); } if (isTransparent()) { @@ -242,7 +237,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { if (_procedural.isReady()) { outColor = _procedural.getColor(outColor); outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; - _procedural.prepare(batch, _position, _dimensions, _orientation, outColor); + _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f)); proceduralRender = true; } } diff --git a/libraries/gpu/src/gpu/Pipeline.h b/libraries/gpu/src/gpu/Pipeline.h index 28f7fe106e..b46226182a 100755 --- a/libraries/gpu/src/gpu/Pipeline.h +++ b/libraries/gpu/src/gpu/Pipeline.h @@ -38,8 +38,6 @@ protected: StatePointer _state; Pipeline(); - Pipeline(const Pipeline& pipeline); // deep copy of the sysmem shader - Pipeline& operator=(const Pipeline& pipeline); // deep copy of the sysmem texture }; typedef Pipeline::Pointer PipelinePointer; diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 7095732d53..a124d4e8be 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -39,8 +39,10 @@ static const std::string PROCEDURAL_BLOCK = "//PROCEDURAL_BLOCK"; static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION"; bool operator==(const ProceduralData& a, const ProceduralData& b) { - return ((a.version == b.version) && (a.shaderUrl == b.shaderUrl) && (a.uniforms == b.uniforms) && - (a.channels == b.channels)); + return ((a.version == b.version) && + (a.shaderUrl == b.shaderUrl) && + (a.uniforms == b.uniforms) && + (a.channels == b.channels)); } QJsonValue ProceduralData::getProceduralData(const QString& proceduralJson) { @@ -57,9 +59,9 @@ QJsonValue ProceduralData::getProceduralData(const QString& proceduralJson) { return doc.object()[PROCEDURAL_USER_DATA_KEY]; } -ProceduralData ProceduralData::parse(const QString& userDataJson) { +ProceduralData ProceduralData::parse(const QString& proceduralData) { ProceduralData result; - result.parse(getProceduralData(userDataJson).toObject()); + result.parse(getProceduralData(proceduralData).toObject()); return result; } @@ -73,7 +75,7 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { if (versionJson.isDouble()) { version = (uint8_t)(floor(versionJson.toDouble())); // invalid version - if (!(version == 1 || version == 2)) { + if (!(version == 1 || version == 2 || version == 3)) { return; } } else { @@ -102,20 +104,27 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { //} Procedural::Procedural() { - _transparentState->setCullMode(gpu::State::CULL_NONE); + _opaqueState->setCullMode(gpu::State::CULL_BACK); + _opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); + _opaqueState->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + + _transparentState->setCullMode(gpu::State::CULL_BACK); _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); - _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + _transparentState->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); _standardInputsBuffer = std::make_shared(sizeof(StandardInputs), nullptr); } void Procedural::setProceduralData(const ProceduralData& proceduralData) { + std::lock_guard lock(_mutex); if (proceduralData == _data) { return; } - _dirty = true; _enabled = false; if (proceduralData.version != _data.version) { @@ -124,6 +133,10 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { } if (proceduralData.uniforms != _data.uniforms) { + // If the uniform keys changed, we need to recreate the whole shader to handle the reflection + if (proceduralData.uniforms.keys() != _data.uniforms.keys()) { + _shaderDirty = true; + } _data.uniforms = proceduralData.uniforms; _uniformsDirty = true; } @@ -147,16 +160,14 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { if (proceduralData.shaderUrl != _data.shaderUrl) { _data.shaderUrl = proceduralData.shaderUrl; - _shaderDirty = true; const auto& shaderUrl = _data.shaderUrl; + + _shaderDirty = true; _networkShader.reset(); _shaderPath.clear(); + _shaderSource.clear(); - if (shaderUrl.isEmpty()) { - return; - } - - if (!shaderUrl.isValid()) { + if (shaderUrl.isEmpty() || !shaderUrl.isValid()) { return; } @@ -180,6 +191,8 @@ bool Procedural::isReady() const { return false; #endif + std::lock_guard lock(_mutex); + if (!_enabled) { return false; } @@ -209,10 +222,11 @@ bool Procedural::isReady() const { } void Procedural::prepare(gpu::Batch& batch, - const glm::vec3& position, - const glm::vec3& size, - const glm::quat& orientation, - const glm::vec4& color) { + const glm::vec3& position, + const glm::vec3& size, + const glm::quat& orientation, + const ProceduralProgramKey key) { + std::lock_guard lock(_mutex); _entityDimensions = size; _entityPosition = position; _entityOrientation = glm::mat3_cast(orientation); @@ -225,62 +239,56 @@ void Procedural::prepare(gpu::Batch& batch, _shaderDirty = true; _shaderModified = lastModified; } - } else if (_networkShader && _networkShader->isLoaded()) { + } else if (_shaderSource.isEmpty() && _networkShader && _networkShader->isLoaded()) { _shaderSource = _networkShader->_source; + _shaderDirty = true; } - if (!_opaquePipeline || !_transparentPipeline || _shaderDirty) { + if (_shaderDirty) { + _proceduralPipelines.clear(); + } + + auto& pipeline = _proceduralPipelines.find(key); + bool recompiledShader = false; + if (pipeline == _proceduralPipelines.end()) { if (!_vertexShader) { _vertexShader = gpu::Shader::createVertex(_vertexSource); } + gpu::Shader::Source& fragmentSource = (key.isTransparent() && _transparentFragmentSource.valid()) ? _transparentFragmentSource : _opaqueFragmentSource; + // Build the fragment shader - _opaqueFragmentSource.replacements.clear(); - if (_data.version == 1) { - _opaqueFragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V1 1"; - } else if (_data.version == 2) { - _opaqueFragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V2 1"; - } - _opaqueFragmentSource.replacements[PROCEDURAL_BLOCK] = _shaderSource.toStdString(); - _transparentFragmentSource.replacements = _opaqueFragmentSource.replacements; + fragmentSource.replacements.clear(); + fragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V" + std::to_string(_data.version); + fragmentSource.replacements[PROCEDURAL_BLOCK] = _shaderSource.toStdString(); // Set any userdata specified uniforms int customSlot = procedural::slot::uniform::Custom; for (const auto& key : _data.uniforms.keys()) { std::string uniformName = key.toLocal8Bit().data(); - _opaqueFragmentSource.reflection.uniforms[uniformName] = customSlot; - _transparentFragmentSource.reflection.uniforms[uniformName] = customSlot; + fragmentSource.reflection.uniforms[uniformName] = customSlot; ++customSlot; } // Leave this here for debugging - // qCDebug(procedural) << "FragmentShader:\n" << fragmentShaderSource.c_str(); + //qCDebug(proceduralLog) << "FragmentShader:\n" << fragmentSource.getSource(shader::Dialect::glsl450, shader::Variant::Mono).c_str(); + + gpu::ShaderPointer fragmentShader = gpu::Shader::createPixel(fragmentSource); + gpu::ShaderPointer program = gpu::Shader::createProgram(_vertexShader, fragmentShader); + + _proceduralPipelines[key] = gpu::Pipeline::create(program, key.isTransparent() ? _transparentState : _opaqueState); - // TODO: THis is a simple fix, we need a cleaner way to provide the "hosting" program for procedural custom shaders to be defined together with the required bindings. - _opaqueFragmentShader = gpu::Shader::createPixel(_opaqueFragmentSource); - _opaqueShader = gpu::Shader::createProgram(_vertexShader, _opaqueFragmentShader); - _opaquePipeline = gpu::Pipeline::create(_opaqueShader, _opaqueState); - if (_transparentFragmentSource.valid()) { - _transparentFragmentShader = gpu::Shader::createPixel(_transparentFragmentSource); - _transparentShader = gpu::Shader::createProgram(_vertexShader, _transparentFragmentShader); - _transparentPipeline = gpu::Pipeline::create(_transparentShader, _transparentState); - } else { - _transparentFragmentShader = _opaqueFragmentShader; - _transparentShader = _opaqueShader; - _transparentPipeline = _opaquePipeline; - } _start = usecTimestampNow(); _frameCount = 0; + recompiledShader = true; } - bool transparent = color.a < 1.0f; - batch.setPipeline(transparent ? _transparentPipeline : _opaquePipeline); + batch.setPipeline(_proceduralPipelines[key]); - if (_shaderDirty || _uniformsDirty || _prevTransparent != transparent) { - setupUniforms(transparent); + if (_shaderDirty || _uniformsDirty) { + setupUniforms(); } - _prevTransparent = transparent; _shaderDirty = _uniformsDirty = false; for (auto lambda : _uniforms) { @@ -290,8 +298,7 @@ void Procedural::prepare(gpu::Batch& batch, static gpu::Sampler sampler; static std::once_flag once; std::call_once(once, [&] { - gpu::Sampler::Desc desc; - desc._filter = gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR; + sampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR); }); for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { @@ -301,19 +308,17 @@ void Procedural::prepare(gpu::Batch& batch, gpuTexture->setSampler(sampler); gpuTexture->setAutoGenerateMips(true); } - batch.setResourceTexture((gpu::uint32)i, gpuTexture); + batch.setResourceTexture(procedural::slot::texture::Channel0 + i, gpuTexture); } } } -void Procedural::setupUniforms(bool transparent) { +void Procedural::setupUniforms() { _uniforms.clear(); - auto customUniformCount = _data.uniforms.keys().size(); // Set any userdata specified uniforms - for (int i = 0; i < customUniformCount; ++i) { - int slot = procedural::slot::uniform::Custom + i; - QString key = _data.uniforms.keys().at(i); + int slot = procedural::slot::uniform::Custom; + for (const auto& key : _data.uniforms.keys()) { std::string uniformName = key.toLocal8Bit().data(); QJsonValue value = _data.uniforms[key]; if (value.isDouble()) { @@ -360,6 +365,7 @@ void Procedural::setupUniforms(bool transparent) { } } } + slot++; } _uniforms.push_back([=](gpu::Batch& batch) { @@ -398,7 +404,7 @@ void Procedural::setupUniforms(bool transparent) { }); } -glm::vec4 Procedural::getColor(const glm::vec4& entityColor) { +glm::vec4 Procedural::getColor(const glm::vec4& entityColor) const { if (_data.version == 1) { return glm::vec4(1); } diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 781ac25249..3e10678ba7 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -7,8 +7,6 @@ // #pragma once -#ifndef hifi_RenderableProcedrualItem_h -#define hifi_RenderableProcedrualItem_h #include @@ -32,7 +30,6 @@ const size_t MAX_PROCEDURAL_TEXTURE_CHANNELS{ 4 }; struct ProceduralData { static QJsonValue getProceduralData(const QString& proceduralJson); static ProceduralData parse(const QString& userDataJson); - // This should only be called from the render thread, as it shares data with Procedural::prepare void parse(const QJsonObject&); // Rendering object descriptions, from userData @@ -42,10 +39,40 @@ struct ProceduralData { QJsonArray channels; }; +class ProceduralProgramKey { +public: + enum FlagBit { + IS_TRANSPARENT = 0, + NUM_FLAGS + }; + + typedef std::bitset Flags; + + Flags _flags; + + bool isTransparent() const { return _flags[IS_TRANSPARENT]; } + + ProceduralProgramKey(bool transparent = false) { + if (transparent) { + _flags.set(IS_TRANSPARENT); + } + } +}; +namespace std { + template <> + struct hash { + size_t operator()(const ProceduralProgramKey& key) const { + return std::hash>()(key._flags); + } + }; +} +inline bool operator==(const ProceduralProgramKey& a, const ProceduralProgramKey& b) { + return a._flags == b._flags; +} +inline bool operator!=(const ProceduralProgramKey& a, const ProceduralProgramKey& b) { + return a._flags != b._flags; +} -// WARNING with threaded rendering it is the RESPONSIBILITY OF THE CALLER to ensure that -// calls to `setProceduralData` happen on the main thread and that calls to `ready` and `prepare` -// are treated atomically, and that they cannot happen concurrently with calls to `setProceduralData` // FIXME better encapsulation // FIXME better mechanism for extending to things rendered using shaders other than simple.slv struct Procedural { @@ -55,10 +82,9 @@ public: bool isReady() const; bool isEnabled() const { return _enabled; } - void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const glm::vec4& color = glm::vec4(1)); - const gpu::ShaderPointer& getOpaqueShader() const { return _opaqueShader; } + void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const ProceduralProgramKey key = ProceduralProgramKey()); - glm::vec4 getColor(const glm::vec4& entityColor); + glm::vec4 getColor(const glm::vec4& entityColor) const; quint64 getFadeStartTime() const { return _fadeStartTime; } bool isFading() const { return _doesFade && _isFading; } void setIsFading(bool isFading) { _isFading = isFading; } @@ -108,22 +134,19 @@ protected: QString _shaderPath; quint64 _shaderModified { 0 }; NetworkShaderPointer _networkShader; - bool _dirty { false }; bool _shaderDirty { true }; bool _uniformsDirty { true }; // Rendering objects UniformLambdas _uniforms; NetworkTexturePointer _channels[MAX_PROCEDURAL_TEXTURE_CHANNELS]; - gpu::PipelinePointer _opaquePipeline; - gpu::PipelinePointer _transparentPipeline; + + std::unordered_map _proceduralPipelines; + + gpu::ShaderPointer _vertexShader; + StandardInputs _standardInputs; gpu::BufferPointer _standardInputsBuffer; - gpu::ShaderPointer _vertexShader; - gpu::ShaderPointer _opaqueFragmentShader; - gpu::ShaderPointer _transparentFragmentShader; - gpu::ShaderPointer _opaqueShader; - gpu::ShaderPointer _transparentShader; // Entity metadata glm::vec3 _entityDimensions; @@ -131,14 +154,11 @@ protected: glm::mat3 _entityOrientation; private: - // This should only be called from the render thread, as it shares data with Procedural::prepare - void setupUniforms(bool transparent); + void setupUniforms(); mutable quint64 _fadeStartTime { 0 }; mutable bool _hasStartedFade { false }; mutable bool _isFading { false }; bool _doesFade { true }; - bool _prevTransparent { false }; + mutable std::mutex _mutex; }; - -#endif diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index d515a79e22..ab8d74bdfa 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -8,11 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> <@include gpu/Noise.slh@> <@include procedural/ShaderConstants.h@> - -<$declareStandardCameraTransform()$> LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL0) uniform sampler2D iChannel0; LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL1) uniform sampler2D iChannel1; @@ -59,6 +56,19 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer { #define iChannelResolution standardInputs.channelResolution #define iWorldOrientation standardInputs.worldOrientation +struct ProceduralFragmentData { + vec3 position; + vec3 normal; + vec3 diffuse; + vec3 specular; + vec3 emissive; + float alpha; + float roughness; + float metallic; + float occlusion; + float scattering; +}; + // Unimplemented uniforms // Resolution doesn't make sense in the VR context const vec3 iResolution = vec3(1.0); @@ -69,8 +79,6 @@ const float iSampleRate = 1.0; // No support for video input const vec4 iChannelTime = vec4(0.0); -#define PROCEDURAL 1 - //PROCEDURAL_VERSION // hack comment for extra whitespace diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index ea5be23eb8..211f6ca0a2 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -35,7 +35,6 @@ bool ProceduralSkybox::empty() { void ProceduralSkybox::clear() { // Parse and prepare a procedural with no shaders to release textures parse(QString()); - _procedural.isReady(); Skybox::clear(); } diff --git a/libraries/render-utils/src/forward_simple.slf b/libraries/render-utils/src/forward_simple.slf index 9c86f9dff1..677c369033 100644 --- a/libraries/render-utils/src/forward_simple.slf +++ b/libraries/render-utils/src/forward_simple.slf @@ -14,9 +14,9 @@ <@include DefaultMaterials.slh@> <@include ForwardGlobalLight.slh@> -<@include gpu/Transform.slh@> - <$declareEvalSkyboxGlobalColor()$> + +<@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> // the interpolated normal diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index dac01aac89..c0bb021c2d 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -14,6 +14,9 @@ <@include DeferredBufferWrite.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include render-utils/ShaderConstants.h@> // the interpolated normal @@ -45,27 +48,64 @@ float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float s return 1.0; } +float getProceduralFragment(inout ProceduralFragmentData proceduralData) { + return 1.0; +} + //PROCEDURAL_BLOCK_END #line 2030 void main(void) { vec3 normal = normalize(_normalWS.xyz); vec3 diffuse = _color.rgb; + float roughness = DEFAULT_ROUGHNESS; + float metallic = DEFAULT_METALLIC; + vec3 emissive = DEFAULT_EMISSIVE; + float occlusion = DEFAULT_OCCLUSION; + float scattering = DEFAULT_SCATTERING; + + float emissiveAmount = 0.0; + +#if defined(PROCEDURAL_V1) + diffuse = getProceduralColor().rgb; + emissiveAmount = 1.0; + emissive = vec3(1.0); +#elif defined(PROCEDURAL_V2) vec3 specular = DEFAULT_SPECULAR; float shininess = DEFAULT_SHININESS; - float emissiveAmount = 0.0; - -#ifdef PROCEDURAL - -#ifdef PROCEDURAL_V1 - diffuse = getProceduralColor().rgb; - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //diffuse = pow(diffuse, vec3(2.2)); - emissiveAmount = 1.0; -#else emissiveAmount = getProceduralColors(diffuse, specular, shininess); -#endif + roughness = max(0.0, 1.0 - shininess / 128.0); + metallic = length(specular); + emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); +#elif defined(PROCEDURAL_V3) + TransformCamera cam = getTransformCamera(); + vec4 position = cam._viewInverse * _positionES; + ProceduralFragmentData proceduralData = { + position.xyz, + normal, + vec3(0.0), + DEFAULT_SPECULAR, + DEFAULT_EMISSIVE, + 1.0, + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING + }; + emissiveAmount = getProceduralFragment(proceduralData); + normal = proceduralData.normal; + diffuse = proceduralData.diffuse; + roughness = proceduralData.roughness; + metallic = proceduralData.metallic; + emissive = proceduralData.emissive; + occlusion = proceduralData.occlusion; + scattering = proceduralData.scattering; + position = vec4(proceduralData.position, 1.0); + vec4 posClip = cam._projection * (cam._view * position); + float far = gl_DepthRange.far; + float near = gl_DepthRange.near; + gl_FragDepth = 0.5 * ((far - near) * posClip.z / posClip.w + near + far); #endif if (emissiveAmount > 0.0) { @@ -73,18 +113,18 @@ void main(void) { normal, 1.0, diffuse, - max(0.0, 1.0 - shininess / 128.0), - DEFAULT_METALLIC, - vec3(clamp(emissiveAmount, 0.0, 1.0))); + roughness, + metallic, + emissive); } else { packDeferredFragment( normal, 1.0, diffuse, - max(0.0, 1.0 - shininess / 128.0), - length(specular), - DEFAULT_EMISSIVE, - DEFAULT_OCCLUSION, - DEFAULT_SCATTERING); + roughness, + metallic, + emissive, + occlusion, + scattering); } } diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf index 0e29ed7470..bafec462db 100644 --- a/libraries/render-utils/src/simple_transparent.slf +++ b/libraries/render-utils/src/simple_transparent.slf @@ -16,6 +16,9 @@ <@include DeferredGlobalLight.slh@> <$declareEvalGlobalLightingAlphaBlendedWithHaze()$> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include render-utils/ShaderConstants.h@> // the interpolated normal @@ -50,46 +53,86 @@ float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float s return 1.0; } +float getProceduralFragment(inout ProceduralFragmentData proceduralData) { + return 1.0; +} + //PROCEDURAL_BLOCK_END #line 2030 void main(void) { vec3 normal = normalize(_normalWS.xyz); vec3 diffuse = _color.rgb; - vec3 specular = DEFAULT_SPECULAR; - float shininess = DEFAULT_SHININESS; + float alpha = _color.a; + float occlusion = DEFAULT_OCCLUSION; + vec3 fresnel = DEFAULT_FRESNEL; + float metallic = DEFAULT_METALLIC; + vec3 emissive = DEFAULT_EMISSIVE; + float roughness = DEFAULT_ROUGHNESS; + float emissiveAmount = 0.0; - -#ifdef PROCEDURAL + + TransformCamera cam = getTransformCamera(); + vec3 posEye = _positionES.xyz; #ifdef PROCEDURAL_V1 diffuse = getProceduralColor().rgb; - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //diffuse = pow(diffuse, vec3(2.2)); emissiveAmount = 1.0; -#else + emissive = vec3(1.0); +#elif defined(PROCEDURAL_V2) + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; emissiveAmount = getProceduralColors(diffuse, specular, shininess); -#endif + roughness = max(0.0, 1.0 - shininess / 128.0); + metallic = length(specular); + emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); +#elif defined(PROCEDURAL_V3) + vec4 position = cam._viewInverse * _positionES; + ProceduralFragmentData proceduralData = { + position.xyz, + normal, + vec3(0.0), + DEFAULT_SPECULAR, + DEFAULT_EMISSIVE, + 1.0, + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING + }; + emissiveAmount = getProceduralFragment(proceduralData); + position = vec4(proceduralData.position, 1.0); + vec4 posEye4 = cam._view * position; + posEye = vec3(posEye4); + occlusion = proceduralData.occlusion; + normal = proceduralData.normal; + diffuse = proceduralData.diffuse; + alpha = proceduralData.alpha; + fresnel = proceduralData.specular; + metallic = proceduralData.metallic; + emissive = proceduralData.emissive; + roughness = proceduralData.roughness; + vec4 posClip = cam._projection * posEye4; + float far = gl_DepthRange.far; + float near = gl_DepthRange.near; + gl_FragDepth = 0.5 * ((far - near) * posClip.z / posClip.w + near + far); #endif - TransformCamera cam = getTransformCamera(); - vec3 fragPosition = _positionES.xyz; - if (emissiveAmount > 0.0) { - _fragColor0 = vec4(diffuse, _color.a); + _fragColor0 = vec4(diffuse, alpha); } else { _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, - DEFAULT_OCCLUSION, - fragPosition, + occlusion, + posEye, normal, diffuse, - DEFAULT_FRESNEL, - length(specular), - DEFAULT_EMISSIVE, - max(0.0, 1.0 - shininess / 128.0), _color.a), - _color.a); + fresnel, + metallic, + emissive, + roughness, alpha), + alpha); } } From dc32472bd639aa9c39f87a4985309fc6f39c82cf Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Wed, 28 Nov 2018 20:42:45 -0800 Subject: [PATCH 02/20] fix build error --- libraries/procedural/src/procedural/Procedural.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index a124d4e8be..8348f2fef5 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -248,7 +248,7 @@ void Procedural::prepare(gpu::Batch& batch, _proceduralPipelines.clear(); } - auto& pipeline = _proceduralPipelines.find(key); + auto pipeline = _proceduralPipelines.find(key); bool recompiledShader = false; if (pipeline == _proceduralPipelines.end()) { if (!_vertexShader) { From 66d77d2df8cdac8eb0a16ac087e1afcc99b84e72 Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Wed, 28 Nov 2018 21:45:50 -0800 Subject: [PATCH 03/20] fix build error 2 --- libraries/procedural/src/procedural/Procedural.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 8348f2fef5..fd380f46dd 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -283,7 +283,7 @@ void Procedural::prepare(gpu::Batch& batch, recompiledShader = true; } - batch.setPipeline(_proceduralPipelines[key]); + batch.setPipeline(recompiledShader ? _proceduralPipelines[key] : pipeline->second); if (_shaderDirty || _uniformsDirty) { setupUniforms(); From 7936dd1ac43ff1b0937360e09290355cf81a2b99 Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Wed, 28 Nov 2018 23:01:02 -0800 Subject: [PATCH 04/20] fix build errors again --- libraries/procedural/src/procedural/Procedural.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index fd380f46dd..ebee8a545f 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -308,7 +308,7 @@ void Procedural::prepare(gpu::Batch& batch, gpuTexture->setSampler(sampler); gpuTexture->setAutoGenerateMips(true); } - batch.setResourceTexture(procedural::slot::texture::Channel0 + i, gpuTexture); + batch.setResourceTexture((gpu::uint32)(procedural::slot::texture::Channel0 + i), gpuTexture); } } } From bf704c2d0d2816fceeae91fc9b2045c1ea5ef6c8 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 19 Dec 2018 16:21:12 -0800 Subject: [PATCH 05/20] remove gl_DepthRange --- libraries/render-utils/src/simple.slf | 4 +--- libraries/render-utils/src/simple_transparent.slf | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index c0bb021c2d..ce1e9d5176 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -103,9 +103,7 @@ void main(void) { position = vec4(proceduralData.position, 1.0); vec4 posClip = cam._projection * (cam._view * position); - float far = gl_DepthRange.far; - float near = gl_DepthRange.near; - gl_FragDepth = 0.5 * ((far - near) * posClip.z / posClip.w + near + far); + gl_FragDepth = 0.5 * (posClip.z / posClip.w + 1.0); #endif if (emissiveAmount > 0.0) { diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf index bafec462db..c50ff8fcd9 100644 --- a/libraries/render-utils/src/simple_transparent.slf +++ b/libraries/render-utils/src/simple_transparent.slf @@ -114,9 +114,7 @@ void main(void) { roughness = proceduralData.roughness; vec4 posClip = cam._projection * posEye4; - float far = gl_DepthRange.far; - float near = gl_DepthRange.near; - gl_FragDepth = 0.5 * ((far - near) * posClip.z / posClip.w + near + far); + gl_FragDepth = 0.5 * (posClip.z / posClip.w + 1.0); #endif if (emissiveAmount > 0.0) { From 837c261fd56a8e0772edab3811ea87dafbc8cb11 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 20 Dec 2018 11:28:42 -0800 Subject: [PATCH 06/20] deprecated dashed/isDashed --- interface/src/ui/overlays/Base3DOverlay.cpp | 5 ++++- interface/src/ui/overlays/Circle3DOverlay.cpp | 2 +- interface/src/ui/overlays/Cube3DOverlay.cpp | 2 +- interface/src/ui/overlays/Grid3DOverlay.cpp | 2 +- interface/src/ui/overlays/Image3DOverlay.cpp | 2 +- interface/src/ui/overlays/Line3DOverlay.cpp | 2 +- interface/src/ui/overlays/ModelOverlay.cpp | 2 +- interface/src/ui/overlays/Rectangle3DOverlay.cpp | 2 +- interface/src/ui/overlays/Shape3DOverlay.cpp | 2 +- interface/src/ui/overlays/Sphere3DOverlay.cpp | 2 +- interface/src/ui/overlays/Text3DOverlay.cpp | 2 +- interface/src/ui/overlays/Web3DOverlay.cpp | 2 +- 12 files changed, 15 insertions(+), 12 deletions(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 8599e05332..eab7a96a4b 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -178,9 +178,11 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { } if (properties["isDashedLine"].isValid()) { + qDebug() << "isDashed is deprecated and will be removed in RC79!"; setIsDashedLine(properties["isDashedLine"].toBool()); } if (properties["dashed"].isValid()) { + qDebug() << "dashed is deprecated and will be removed in RC79!"; setIsDashedLine(properties["dashed"].toBool()); } if (properties["ignorePickIntersection"].isValid()) { @@ -223,7 +225,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. @@ -259,6 +261,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) { return !_isSolid; } if (property == "isDashedLine" || property == "dashed") { + qDebug() << "isDashedLine/dashed are deprecated and will be removed in RC79!"; return _isDashedLine; } if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") { diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index a98ea7070e..1a7d3228c7 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -399,7 +399,7 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 581db672a3..9888d696cf 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -156,7 +156,7 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 87ab0fb2e8..881c2885eb 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -142,7 +142,7 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index e24e3b3ed8..d54966a7e1 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -219,7 +219,7 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index e6546686b0..b1c316c6af 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -286,7 +286,7 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 805832760e..14b8182abf 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -382,7 +382,7 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index 73606c0467..e30171bd50 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -138,7 +138,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index b424424369..4adbbf3792 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -158,7 +158,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 97294ae871..b1d5c878c4 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -59,7 +59,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index c8f8550e8e..8b8a820388 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -224,7 +224,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 18b7150b4a..ec6b62e237 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -555,7 +555,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: - * dashed. + * dashed. Deprecated. * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. From c985eb3e809e26614301fb5f70ff1ac89606a223 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 12 Dec 2018 15:01:51 -0800 Subject: [PATCH 07/20] Fix for Oculus reset view issue --- interface/src/Application.cpp | 1 + interface/src/Application.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a3a8478b0d..276710d74e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6687,6 +6687,7 @@ void Application::resetSensors(bool andReload) { DependencyManager::get()->reset(); DependencyManager::get()->reset(); _overlayConductor.centerUI(); + getActiveDisplayPlugin()->resetSensors(); getMyAvatar()->reset(true, andReload); QMetaObject::invokeMethod(DependencyManager::get().data(), "reset", Qt::QueuedConnection); } diff --git a/interface/src/Application.h b/interface/src/Application.h index a78df6afdd..2e40a55df0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -120,7 +120,7 @@ class Application : public QApplication, public: // virtual functions required for PluginContainer virtual ui::Menu* getPrimaryMenu() override; - virtual void requestReset() override { resetSensors(true); } + virtual void requestReset() override { resetSensors(false); } virtual void showDisplayPluginsTools(bool show) override; virtual GLWidget* getPrimaryWidget() override; virtual MainWindow* getPrimaryWindow() override; From 835615fbbd97b8a3d500fade0b6324bc8ec15fc8 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 20 Dec 2018 16:33:13 -0800 Subject: [PATCH 08/20] separate out v3 and v4 --- .../procedural/src/procedural/Procedural.cpp | 2 +- .../src/procedural/ProceduralCommon.slh | 15 +++++++++- libraries/render-utils/src/simple.slf | 22 ++++++++++++-- .../render-utils/src/simple_transparent.slf | 30 ++++++++++++++----- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index ebee8a545f..05cbde374d 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -75,7 +75,7 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { if (versionJson.isDouble()) { version = (uint8_t)(floor(versionJson.toDouble())); // invalid version - if (!(version == 1 || version == 2 || version == 3)) { + if (!(version == 1 || version == 2 || version == 3 || version == 4)) { return; } } else { diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index ab8d74bdfa..bd894a9e92 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -56,7 +56,20 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer { #define iChannelResolution standardInputs.channelResolution #define iWorldOrientation standardInputs.worldOrientation -struct ProceduralFragmentData { +struct ProceduralFragment { + vec3 normal; + vec3 diffuse; + vec3 specular; + vec3 emissive; + float alpha; + float roughness; + float metallic; + float occlusion; + float scattering; +}; + +// Same as ProceduralFragment but with position +struct ProceduralFragmentWithPosition { vec3 position; vec3 normal; vec3 diffuse; diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index ce1e9d5176..d1a70007a2 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -48,7 +48,11 @@ float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float s return 1.0; } -float getProceduralFragment(inout ProceduralFragmentData proceduralData) { +float getProceduralFragment(inout ProceduralFragment proceduralData) { + return 1.0; +} + +float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition proceduralData) { return 1.0; } @@ -77,11 +81,15 @@ void main(void) { roughness = max(0.0, 1.0 - shininess / 128.0); metallic = length(specular); emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); -#elif defined(PROCEDURAL_V3) +#elif defined(PROCEDURAL_V3) || defined(PROCEDURAL_V4) +#if defined(PROCEDURAL_V3) + ProceduralFragment proceduralData = { +#else TransformCamera cam = getTransformCamera(); vec4 position = cam._viewInverse * _positionES; - ProceduralFragmentData proceduralData = { + ProceduralFragmentWithPosition proceduralData = { position.xyz, +#endif normal, vec3(0.0), DEFAULT_SPECULAR, @@ -92,7 +100,12 @@ void main(void) { DEFAULT_OCCLUSION, DEFAULT_SCATTERING }; + +#if defined(PROCEDURAL_V3) emissiveAmount = getProceduralFragment(proceduralData); +#else + emissiveAmount = getProceduralFragmentWithPosition(proceduralData); +#endif normal = proceduralData.normal; diffuse = proceduralData.diffuse; roughness = proceduralData.roughness; @@ -101,9 +114,12 @@ void main(void) { occlusion = proceduralData.occlusion; scattering = proceduralData.scattering; +#if defined(PROCEDURAL_V4) position = vec4(proceduralData.position, 1.0); vec4 posClip = cam._projection * (cam._view * position); gl_FragDepth = 0.5 * (posClip.z / posClip.w + 1.0); +#endif + #endif if (emissiveAmount > 0.0) { diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf index c50ff8fcd9..ea444d6113 100644 --- a/libraries/render-utils/src/simple_transparent.slf +++ b/libraries/render-utils/src/simple_transparent.slf @@ -53,7 +53,11 @@ float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float s return 1.0; } -float getProceduralFragment(inout ProceduralFragmentData proceduralData) { +float getProceduralFragment(inout ProceduralFragment proceduralData) { + return 1.0; +} + +float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition proceduralData) { return 1.0; } @@ -86,10 +90,14 @@ void main(void) { roughness = max(0.0, 1.0 - shininess / 128.0); metallic = length(specular); emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); -#elif defined(PROCEDURAL_V3) +#elif defined(PROCEDURAL_V3) || defined(PROCEDURAL_V4) +#if defined(PROCEDURAL_V3) + ProceduralFragment proceduralData = { +#else vec4 position = cam._viewInverse * _positionES; - ProceduralFragmentData proceduralData = { + ProceduralFragmentWithPosition proceduralData = { position.xyz, +#endif normal, vec3(0.0), DEFAULT_SPECULAR, @@ -100,21 +108,29 @@ void main(void) { DEFAULT_OCCLUSION, DEFAULT_SCATTERING }; + +#if defined(PROCEDURAL_V3) emissiveAmount = getProceduralFragment(proceduralData); - position = vec4(proceduralData.position, 1.0); - vec4 posEye4 = cam._view * position; - posEye = vec3(posEye4); +#else + emissiveAmount = getProceduralFragmentWithPosition(proceduralData); +#endif occlusion = proceduralData.occlusion; normal = proceduralData.normal; diffuse = proceduralData.diffuse; - alpha = proceduralData.alpha; fresnel = proceduralData.specular; metallic = proceduralData.metallic; emissive = proceduralData.emissive; roughness = proceduralData.roughness; + alpha = proceduralData.alpha; +#if defined(PROCEDURAL_V4) + position = vec4(proceduralData.position, 1.0); + vec4 posEye4 = cam._view * position; + posEye = posEye4.xyz; vec4 posClip = cam._projection * posEye4; gl_FragDepth = 0.5 * (posClip.z / posClip.w + 1.0); +#endif + #endif if (emissiveAmount > 0.0) { From 28b4527632753eeed046751651fa3f2d16b74e64 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 14 Dec 2018 12:56:39 -0800 Subject: [PATCH 09/20] Avatar Exporter - joint rotation offsets and file workflow changes --- .../Assets/Editor/AvatarExporter.cs | 358 ++++++++++++++---- .../avatarExporter.unitypackage | Bin 2830 -> 5879 bytes 2 files changed, 279 insertions(+), 79 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 60b5e0e643..3a3cd77496 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -12,8 +12,8 @@ using System; using System.IO; using System.Collections.Generic; -public class AvatarExporter : MonoBehaviour { - public static Dictionary UNITY_TO_HIFI_JOINT_NAME = new Dictionary { +public class AvatarExporter : MonoBehaviour { + public static Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { {"Chest", "Spine1"}, {"Head", "Head"}, {"Hips", "Hips"}, @@ -70,49 +70,97 @@ public class AvatarExporter : MonoBehaviour { {"UpperChest", "Spine2"}, }; - public static string exportedPath = String.Empty; + public static Dictionary referenceAbsoluteRotations = new Dictionary { + {"Head", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"Hips", new Quaternion(-3.043941e-10f, -1.573706e-7f, 5.112975e-6f, 1f)}, + {"LeftHandIndex3", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)}, + {"LeftHandIndex2", new Quaternion(-0.4934928f, 0.5062312f, -0.5064303f, -0.4936835f)}, + {"LeftHandIndex1", new Quaternion(-0.4986293f, 0.5017503f, -0.5013659f, -0.4982448f)}, + {"LeftHandPinky3", new Quaternion(-0.490056f, 0.5143053f, -0.5095307f, -0.4855038f)}, + {"LeftHandPinky2", new Quaternion(-0.5083722f, 0.4954255f, -0.4915887f, -0.5044324f)}, + {"LeftHandPinky1", new Quaternion(-0.5062528f, 0.497324f, -0.4937346f, -0.5025966f)}, + {"LeftHandMiddle3", new Quaternion(-0.4871885f, 0.5123404f, -0.5125002f, -0.4873383f)}, + {"LeftHandMiddle2", new Quaternion(-0.5171652f, 0.4827828f, -0.4822642f, -0.5166069f)}, + {"LeftHandMiddle1", new Quaternion(-0.4955998f, 0.5041052f, -0.5043675f, -0.4958555f)}, + {"LeftHandRing3", new Quaternion(-0.4936301f, 0.5097645f, -0.5061787f, -0.4901562f)}, + {"LeftHandRing2", new Quaternion(-0.5089865f, 0.4943658f, -0.4909532f, -0.5054707f)}, + {"LeftHandRing1", new Quaternion(-0.5020972f, 0.5005084f, -0.4979034f, -0.4994819f)}, + {"LeftHandThumb3", new Quaternion(-0.7228092f, 0.2988393f, -0.4472938f, -0.4337862f)}, + {"LeftHandThumb2", new Quaternion(-0.7554525f, 0.2018595f, -0.3871402f, -0.4885356f)}, + {"LeftHandThumb1", new Quaternion(-0.7276843f, 0.2878546f, -0.439926f, -0.4405459f)}, + {"LeftEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"LeftFoot", new Quaternion(0.009215056f, 0.3612514f, 0.9323555f, -0.01121602f)}, + {"LeftHand", new Quaternion(-0.4797408f, 0.5195366f, -0.5279632f, -0.4703038f)}, + {"LeftForeArm", new Quaternion(-0.4594738f, 0.4594729f, -0.5374805f, -0.5374788f)}, + {"LeftLeg", new Quaternion(-0.0005380471f, -0.03154583f, 0.9994993f, 0.002378627f)}, + {"LeftShoulder", new Quaternion(-0.3840606f, 0.525857f, -0.5957767f, -0.47013f)}, + {"LeftToeBase", new Quaternion(-0.0002536641f, 0.7113448f, 0.7027079f, -0.01379319f)}, + {"LeftArm", new Quaternion(-0.4591927f, 0.4591916f, -0.5377204f, -0.5377193f)}, + {"LeftUpLeg", new Quaternion(-0.0006682819f, 0.0006864658f, 0.9999968f, -0.002333928f)}, + {"Neck", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"RightHandIndex3", new Quaternion(0.5083892f, 0.4911618f, -0.4914584f, 0.5086939f)}, + {"RightHandIndex2", new Quaternion(0.4931984f, 0.5065879f, -0.5067145f, 0.4933202f)}, + {"RightHandIndex1", new Quaternion(0.4991491f, 0.5012957f, -0.5008481f, 0.4987026f)}, + {"RightHandPinky3", new Quaternion(0.4890696f, 0.5154139f, -0.5104482f, 0.4843578f)}, + {"RightHandPinky2", new Quaternion(0.5084175f, 0.495413f, -0.4915423f, 0.5044444f)}, + {"RightHandPinky1", new Quaternion(0.5069782f, 0.4965974f, -0.4930001f, 0.5033045f)}, + {"RightHandMiddle3", new Quaternion(0.4867662f, 0.5129694f, -0.5128888f, 0.4866894f)}, + {"RightHandMiddle2", new Quaternion(0.5167004f, 0.4833596f, -0.4827653f, 0.5160643f)}, + {"RightHandMiddle1", new Quaternion(0.4965845f, 0.5031784f, -0.5033959f, 0.4967981f)}, + {"RightHandRing3", new Quaternion(0.4933217f, 0.5102056f, -0.5064691f, 0.4897075f)}, + {"RightHandRing2", new Quaternion(0.5085972f, 0.494844f, -0.4913519f, 0.505007f)}, + {"RightHandRing1", new Quaternion(0.502959f, 0.4996676f, -0.4970418f, 0.5003144f)}, + {"RightHandThumb3", new Quaternion(0.7221864f, 0.3001843f, -0.4482129f, 0.4329457f)}, + {"RightHandThumb2", new Quaternion(0.755621f, 0.20102f, -0.386691f, 0.4889769f)}, + {"RightHandThumb1", new Quaternion(0.7277303f, 0.2876409f, -0.4398623f, 0.4406733f)}, + {"RightEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"RightFoot", new Quaternion(-0.009482829f, 0.3612484f, 0.9323512f, 0.01144584f)}, + {"RightHand", new Quaternion(0.4797273f, 0.5195542f, -0.5279628f, 0.4702987f)}, + {"RightForeArm", new Quaternion(0.4594217f, 0.4594215f, -0.5375242f, 0.5375237f)}, + {"RightLeg", new Quaternion(0.0005446263f, -0.03177159f, 0.9994922f, -0.002395923f)}, + {"RightShoulder", new Quaternion(0.3841222f, 0.5257177f, -0.5957286f, 0.4702966f)}, + {"RightToeBase", new Quaternion(0.0001034f, 0.7113398f, 0.7027067f, 0.01411251f)}, + {"RightArm", new Quaternion(0.4591419f, 0.4591423f, -0.537763f, 0.5377624f)}, + {"RightUpLeg", new Quaternion(0.0006750703f, 0.0008973633f, 0.9999966f, 0.002352045f)}, + {"Spine", new Quaternion(-0.05427956f, 1.508558e-7f, -2.775203e-6f, 0.9985258f)}, + {"Spine1", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, + {"Spine2", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, + }; + + public static Dictionary userBoneToHumanoidMappings = new Dictionary(); + public static Dictionary userParentNames = new Dictionary(); + public static Dictionary userAbsoluteRotations = new Dictionary(); [MenuItem("High Fidelity/Export New Avatar")] public static void ExportNewAvatar() { ExportSelectedAvatar(false); } - - [MenuItem("High Fidelity/Export New Avatar", true)] - private static bool ExportNewAvatarValidator() { - // only enable Export New Avatar option if we have an asset selected - string[] guids = Selection.assetGUIDs; - return guids.Length > 0; - } - + [MenuItem("High Fidelity/Update Avatar")] public static void UpdateAvatar() { ExportSelectedAvatar(true); } - [MenuItem("High Fidelity/Update Avatar", true)] - private static bool UpdateAvatarValidation() { - // only enable Update Avatar option if the selected avatar is the last one that was exported - if (exportedPath != String.Empty) { - string[] guids = Selection.assetGUIDs; - if (guids.Length > 0) { - string selectedAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]); - string selectedAsset = Path.GetFileNameWithoutExtension(selectedAssetPath); - string exportedAsset = Path.GetFileNameWithoutExtension(exportedPath); - return exportedAsset == selectedAsset; + public static void ExportSelectedAvatar(bool updateAvatar) { + string[] guids = Selection.assetGUIDs; + if (guids.Length != 1) { + if (guids.Length == 0) { + EditorUtility.DisplayDialog("Error", "Please select an asset to export", "Ok"); + } else { + EditorUtility.DisplayDialog("Error", "Please select a single asset to export", "Ok"); } - } - return false; - } - - public static void ExportSelectedAvatar(bool usePreviousPath) { - string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); - if (assetPath.LastIndexOf(".fbx") == -1) { - EditorUtility.DisplayDialog("Error", "Please select an fbx avatar to export", "Ok"); return; } + string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); + string assetName = Path.GetFileNameWithoutExtension(assetPath); + string assetDirectory = Path.GetDirectoryName(assetPath); + if (assetDirectory != "Assets/Resources") { + EditorUtility.DisplayDialog("Error", "Please place asset in the Assets/Resources folder", "Ok"); + return; + } ModelImporter importer = ModelImporter.GetAtPath(assetPath) as ModelImporter; - if (importer == null) { - EditorUtility.DisplayDialog("Error", "Please select a model", "Ok"); + if (assetPath.LastIndexOf(".fbx") == -1 || importer == null) { + EditorUtility.DisplayDialog("Error", "Please select an fbx model asset to export", "Ok"); return; } if (importer.animationType != ModelImporterAnimationType.Human) { @@ -120,88 +168,240 @@ public class AvatarExporter : MonoBehaviour { return; } + userBoneToHumanoidMappings.Clear(); + userParentNames.Clear(); + userAbsoluteRotations.Clear(); + + // instantiate a game object of the user avatar to save out bone parents then destroy it + UnityEngine.Object avatarResource = Resources.Load(assetName); + if (avatarResource) { + GameObject assetGameObject = (GameObject)Instantiate(avatarResource); + SetParentNames(assetGameObject.transform, userParentNames); + DestroyImmediate(assetGameObject); + } + // store joint mappings only for joints that exist in hifi and verify missing joints HumanDescription humanDescription = importer.humanDescription; HumanBone[] boneMap = humanDescription.human; - Dictionary jointMappings = new Dictionary(); + string chestUserBone = ""; + string neckUserBone = ""; foreach (HumanBone bone in boneMap) { - string humanBone = bone.humanName; + string humanName = bone.humanName; + string boneName = bone.boneName; string hifiJointName; - if (UNITY_TO_HIFI_JOINT_NAME.TryGetValue(humanBone, out hifiJointName)) { - jointMappings.Add(hifiJointName, bone.boneName); + if (HUMANOID_TO_HIFI_JOINT_NAME.TryGetValue(humanName, out hifiJointName)) { + userBoneToHumanoidMappings.Add(boneName, humanName); + if (humanName == "Chest") { + chestUserBone = boneName; + } else if (humanName == "Neck") { + neckUserBone = boneName; + } } + } - if (!jointMappings.ContainsKey("Hips")) { + if (!userBoneToHumanoidMappings.ContainsValue("Hips")) { EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok"); return; } - if (!jointMappings.ContainsKey("Spine")) { + if (!userBoneToHumanoidMappings.ContainsValue("Spine")) { EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok"); return; } - if (!jointMappings.ContainsKey("Spine1")) { - EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok"); - return; - } - if (!jointMappings.ContainsKey("Spine2")) { - // if there is no UpperChest (Spine2) bone then we remap Chest (Spine1) to Spine2 in hifi and skip Spine1 - jointMappings["Spine2"] = jointMappings["Spine1"]; - jointMappings.Remove("Spine1"); - } - - // open folder explorer defaulting to user documents folder to select target path if exporting new avatar, - // otherwise use previously exported path if updating avatar - string directoryPath; - string assetName = Path.GetFileNameWithoutExtension(assetPath); - if (!usePreviousPath || exportedPath == String.Empty) { - string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); - if (!SelectExportFolder(assetName, documentsFolder, out directoryPath)) { + if (!userBoneToHumanoidMappings.ContainsValue("Chest")) { + // check to see if there is a child of Spine that could be mapped to Chest + string spineChild = ""; + foreach (var parentRelation in userParentNames) { + string humanName; + if (userBoneToHumanoidMappings.TryGetValue(parentRelation.Value, out humanName) && humanName == "Spine") { + if (spineChild == "") { + spineChild = parentRelation.Key; + } else { + // found more than one Spine child so we can't choose one to remap + spineChild = ""; + break; + } + } + } + if (spineChild != "" && !userBoneToHumanoidMappings.ContainsKey(spineChild)) { + // use child of Spine as Chest + userBoneToHumanoidMappings.Add(spineChild, "Chest"); + chestUserBone = spineChild; + } else { + EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok"); return; } - } else { - directoryPath = Path.GetDirectoryName(exportedPath) + "/"; } - Directory.CreateDirectory(directoryPath); + if (!userBoneToHumanoidMappings.ContainsValue("UpperChest")) { + //if parent of Neck is not Chest then map the parent to UpperChest + if (neckUserBone != "") { + string neckParentUserBone, neckParentHuman; + userParentNames.TryGetValue(neckUserBone, out neckParentUserBone); + userBoneToHumanoidMappings.TryGetValue(neckParentUserBone, out neckParentHuman); + if (neckParentHuman != "Chest" && !userBoneToHumanoidMappings.ContainsKey(neckParentUserBone)) { + userBoneToHumanoidMappings.Add(neckParentUserBone, "UpperChest"); + } + } + // if there is still no UpperChest bone but there is a Chest bone then we remap Chest to UpperChest + if (!userBoneToHumanoidMappings.ContainsValue("UpperChest") && chestUserBone != "") { + userBoneToHumanoidMappings[chestUserBone] = "UpperChest"; + } + } + + bool copyModelToExport = false; + string exportFstPath, exportModelPath; + string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); + if (updateAvatar) { + // open file explorer defaulting to user documents folder to select target fst to update + exportFstPath = EditorUtility.OpenFilePanel("Select fst to update", documentsFolder, "fst"); + if (exportFstPath.Length == 0) { // file selection cancelled + return; + } + exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; + + if (File.Exists(exportModelPath)) { + // if the fbx in Unity Assets/Resources is newer than the fbx in the + // target export folder or vice-versa then ask to copy fbx over + DateTime assetModelWriteTime = File.GetLastWriteTime(assetPath); + DateTime targetModelWriteTime = File.GetLastWriteTime(exportModelPath); + if (assetModelWriteTime > targetModelWriteTime) { + copyModelToExport = EditorUtility.DisplayDialog("Error", "The " + assetName + + ".fbx model in the Unity Assets/Resources folder is newer than the " + exportModelPath + + " model. Do you want to copy the newer .fbx model over?" , "Yes", "No"); + } else if (assetModelWriteTime < targetModelWriteTime) { + bool copyModelToUnity = EditorUtility.DisplayDialog("Error", "The " + exportModelPath + + " model is newer than the " + assetName + + ".fbx model in the Unity Assets/Resources folder. Do you want to copy the newer .fbx model over?", + "Yes", "No"); + if (copyModelToUnity) { + File.Delete(assetPath); + File.Copy(exportModelPath, assetPath); + } + } + } else { + // if no matching fbx exists in the target export folder then ask to copy fbx over + copyModelToExport = EditorUtility.DisplayDialog("Error", "There is no existing " + exportModelPath + + " model. Do you want to copy over the " + assetName + + ".fbx model from the Unity Assets/Resources folder?" , "Yes", "No"); + } + } else { + // open folder explorer defaulting to user documents folder to select target folder to export fst and fbx to + if (!SelectExportFolder(assetName, documentsFolder, out exportFstPath, out exportModelPath)) { + return; + } + copyModelToExport = true; + } - // delete any existing fst since we agreed to overwrite it - string fstPath = directoryPath + assetName + ".fst"; - if (File.Exists(fstPath)) { - File.Delete(fstPath); + // delete any existing fbx since we would have agreed to overwrite it, and copy asset fbx over + if (copyModelToExport) { + if (File.Exists(exportModelPath)) { + File.Delete(exportModelPath); + } + File.Copy(assetPath, exportModelPath); + } + + // delete any existing fst since we agreed to overwrite it or are updating it + // TODO: should updating fst only rewrite joint mappings and joint rotation offsets? + if (File.Exists(exportFstPath)) { + File.Delete(exportFstPath); } // write out core fields to top of fst file - File.WriteAllText(fstPath, "name = " + assetName + "\ntype = body+head\nscale = 1\nfilename = " + + File.WriteAllText(exportFstPath, "name = " + assetName + "\ntype = body+head\nscale = 1\nfilename = " + assetName + ".fbx\n" + "texdir = textures\n"); // write out joint mappings to fst file - foreach (var jointMapping in jointMappings) { - File.AppendAllText(fstPath, "jointMap = " + jointMapping.Key + " = " + jointMapping.Value + "\n"); + foreach (var jointMapping in userBoneToHumanoidMappings) { + string hifiJointName = HUMANOID_TO_HIFI_JOINT_NAME[jointMapping.Value]; + File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + jointMapping.Key + "\n"); } - - // delete any existing fbx since we agreed to overwrite it, and copy fbx over - string targetAssetPath = directoryPath + assetName + ".fbx"; - if (File.Exists(targetAssetPath)) { - File.Delete(targetAssetPath); - } - File.Copy(assetPath, targetAssetPath); - exportedPath = targetAssetPath; + SkeletonBone[] skeletonMap = humanDescription.skeleton; + foreach (SkeletonBone userBone in skeletonMap) { + string userBoneName = userBone.name; + Quaternion userBoneRotation = userBone.rotation; + + string parentName; + userParentNames.TryGetValue(userBoneName, out parentName); + if (parentName == "root") { + // if the parent is root then use bone's rotation + userAbsoluteRotations.Add(userBoneName, userBoneRotation); + } else { + // otherwise multiply bone's rotation by parent bone's absolute rotation + userAbsoluteRotations.Add(userBoneName, userAbsoluteRotations[parentName] * userBoneRotation); + } + + // generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones in user avatar + Quaternion jointOffset = new Quaternion(); + string humanName, outputJointName = ""; + if (userBoneToHumanoidMappings.TryGetValue(userBoneName, out humanName)) { + outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[humanName]; + Quaternion rotation = referenceAbsoluteRotations[outputJointName]; + jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * rotation; + } else if (userAbsoluteRotations.ContainsKey(userBoneName)) { + string lastRequiredParent = FindLastRequiredParentBone(userBoneName); + if (lastRequiredParent != "root") { + // take the previous offset and multiply it by the current local when we have an extra joint + outputJointName = userBoneName; + string lastRequiredParentHifiName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneToHumanoidMappings[lastRequiredParent]]; + Quaternion lastRequiredParentRotation = referenceAbsoluteRotations[lastRequiredParentHifiName]; + jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * lastRequiredParentRotation; + } + } + + // swap from left-handed (Unity) to right-handed (HiFi) coordinate system and write out joint rotation offset to fst + if (outputJointName != "") { + jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); + File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " + + jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); + } + } } - public static bool SelectExportFolder(string assetName, string initialPath, out string directoryPath) { + public static bool SelectExportFolder(string assetName, string initialPath, out string fstPath, out string modelPath) { string selectedPath = EditorUtility.OpenFolderPanel("Select export location", initialPath, ""); if (selectedPath.Length == 0) { // folder selection cancelled - directoryPath = ""; + fstPath = ""; + modelPath = ""; return false; } - directoryPath = selectedPath + "/" + assetName + "/"; - if (Directory.Exists(directoryPath)) { - bool overwrite = EditorUtility.DisplayDialog("Error", "Directory " + assetName + - " already exists here, would you like to overwrite it?", "Yes", "No"); + fstPath = selectedPath + "/" + assetName + ".fst"; + modelPath = selectedPath + "/" + assetName + ".fbx"; + bool fstExists = File.Exists(fstPath); + bool modelExists = File.Exists(modelPath); + if (fstExists || modelExists) { + string overwriteMessage; + if (fstExists && modelExists) { + overwriteMessage = assetName + ".fst and " + assetName + + ".fbx already exist here, would you like to overwrite them?"; + } else if (fstExists) { + overwriteMessage = assetName + ".fst already exists here, would you like to overwrite it?"; + } else { + overwriteMessage = assetName + ".fbx already exists here, would you like to overwrite it?"; + } + bool overwrite = EditorUtility.DisplayDialog("Error", overwriteMessage, "Yes", "No"); if (!overwrite) { - SelectExportFolder(assetName, selectedPath, out directoryPath); + return SelectExportFolder(assetName, selectedPath, out fstPath, out modelPath); } } return true; } + + public static void SetParentNames(Transform modelBone, Dictionary parentNames) { + for (int i = 0; i < modelBone.childCount; i++) { + SetParentNames(modelBone.GetChild(i), parentNames); + } + if (modelBone.parent != null) { + parentNames.Add(modelBone.name, modelBone.parent.name); + } else { + parentNames.Add(modelBone.name, "root"); + } + } + + public static string FindLastRequiredParentBone(string currentBone) { + string result = currentBone; + while (result != "root" && !userBoneToHumanoidMappings.ContainsKey(result)) { + result = userParentNames[result]; + } + return result; + } } diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index bb25cb4072569356c49f6eaf24d334a65827ec0e..15935b3229ba23614aa7c9f9bf556a304d574369 100644 GIT binary patch literal 5879 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 From 58e1df970f0b1c07e9116ba81af844fd36d73193 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 17 Dec 2018 14:06:45 -0800 Subject: [PATCH 10/20] remove need for Asset/Resources folder, fix rotations on root --- .../Assets/Editor/AvatarExporter.cs | 25 ++++++++---------- .../avatarExporter.unitypackage | Bin 5879 -> 5864 bytes 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 3a3cd77496..b62f51f4e8 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -13,7 +13,7 @@ using System.IO; using System.Collections.Generic; public class AvatarExporter : MonoBehaviour { - public static Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { + public static readonly Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { {"Chest", "Spine1"}, {"Head", "Head"}, {"Hips", "Hips"}, @@ -70,7 +70,7 @@ public class AvatarExporter : MonoBehaviour { {"UpperChest", "Spine2"}, }; - public static Dictionary referenceAbsoluteRotations = new Dictionary { + public static readonly Dictionary referenceAbsoluteRotations = new Dictionary { {"Head", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, {"Hips", new Quaternion(-3.043941e-10f, -1.573706e-7f, 5.112975e-6f, 1f)}, {"LeftHandIndex3", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)}, @@ -151,16 +151,10 @@ public class AvatarExporter : MonoBehaviour { } return; } - string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); - string assetName = Path.GetFileNameWithoutExtension(assetPath); - string assetDirectory = Path.GetDirectoryName(assetPath); - if (assetDirectory != "Assets/Resources") { - EditorUtility.DisplayDialog("Error", "Please place asset in the Assets/Resources folder", "Ok"); - return; - } + string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); ModelImporter importer = ModelImporter.GetAtPath(assetPath) as ModelImporter; if (assetPath.LastIndexOf(".fbx") == -1 || importer == null) { - EditorUtility.DisplayDialog("Error", "Please select an fbx model asset to export", "Ok"); + EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export", "Ok"); return; } if (importer.animationType != ModelImporterAnimationType.Human) { @@ -173,7 +167,7 @@ public class AvatarExporter : MonoBehaviour { userAbsoluteRotations.Clear(); // instantiate a game object of the user avatar to save out bone parents then destroy it - UnityEngine.Object avatarResource = Resources.Load(assetName); + UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); if (avatarResource) { GameObject assetGameObject = (GameObject)Instantiate(avatarResource); SetParentNames(assetGameObject.transform, userParentNames); @@ -246,9 +240,10 @@ public class AvatarExporter : MonoBehaviour { userBoneToHumanoidMappings[chestUserBone] = "UpperChest"; } } - + bool copyModelToExport = false; string exportFstPath, exportModelPath; + string assetName = Path.GetFileNameWithoutExtension(assetPath); string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); if (updateAvatar) { // open file explorer defaulting to user documents folder to select target fst to update @@ -338,10 +333,12 @@ public class AvatarExporter : MonoBehaviour { Quaternion rotation = referenceAbsoluteRotations[outputJointName]; jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * rotation; } else if (userAbsoluteRotations.ContainsKey(userBoneName)) { + outputJointName = userBoneName; string lastRequiredParent = FindLastRequiredParentBone(userBoneName); - if (lastRequiredParent != "root") { + if (lastRequiredParent == "root") { + jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]); + } else { // take the previous offset and multiply it by the current local when we have an extra joint - outputJointName = userBoneName; string lastRequiredParentHifiName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneToHumanoidMappings[lastRequiredParent]]; Quaternion lastRequiredParentRotation = referenceAbsoluteRotations[lastRequiredParentHifiName]; jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * lastRequiredParentRotation; diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 15935b3229ba23614aa7c9f9bf556a304d574369..4134d6f09a52e356fd3e6a543b2bcb134a7daf5b 100644 GIT binary patch literal 5864 zcmVvrQb5bp0hg~$&`AWeP0Y$=>V z3tQOTaw%{)OAp6Ql-6uw2iw^aV4sX1JQA?GX+2dEEhZE5srG&35FMt19( zSGHyQkrjs4)zopq$PJl*>IXb2@<-^GS5Ugjadr|F(IW&8)WSYkCDS>67AZXv|Bhif5A*-;xajQ74^F3Xk!E`j*Z=LB_>t{g z;lR4GBkZ~kj*QR`Jv%g=DnWT$ll&s_A8bSC9ZO%SCefH%kN;7m*6w5~k z2e-Gk^!zFg-8Ueuq) zXXlNA57YS^LS5iJAAgJ!oW+yFUT?X&0xr;G4)duYZS)8|O_THkd;SX<7ek*C*NP3fB*RGqqFZnIX`=L z@%^*or>E!$CHS_h@h{LRyMF0^c!TqzKSKR4mmn0g|7ujJd<>jG9sSv;iI+Ll(4Y01 zCwNw%Gf?+W)BvJ6kkP`&Q8K0Fc3T7Z7sRx{(-=%qX=b%H`z%X;iWi!mOihy~aZ${% zhPTh+6)?_2ArBTUNvw@DOi<{NrO2ZW`x7!-D@NZq@pa(v-}; z^1en13s`{^kHbo(%V}*=GXk}t)!MK$1GSN3Z5*mOQGhG(4)_hZd{uM{U%_B&*kOsd^x_nT}2AgJcuwgxVUkGe~MR z>^1z5(nQ0W;)z(PbVaRA+PS0E8upsrNNuEHko%+ZUZoHoNj}tQWEZvmRGQK&2|DSP zR8r}eSOMItWL3{pFHmWPZxRoxbD}k+cPdHYUQbqMP<2o&ekud0dL34V+lEIQ9?jXXGI|y(s+a&`ICkim(8?g7=GgUZ-vCe_ z9^J8NTA}aaeNTmK#*-?z0SpJ8;Swlg?1V;O1Oe%VJeU?_LFoe-mSICp2fU@iQxw|? zEdohEJUGa*1fu8I29X1!@&eoKf;U^>1J4R=4sZG{L!=L|XC~Y;D$8+n+{<;P1s?X} z9z!>QUAIIJUEApQFUNuOPZzvSHcZ^E6>xYT zFiK%VR>>3SmK%DWie6b4mI=pheyIu<)T^;fxstZoy=NOHM-P3^ zaZ9^5eX3h#U#9C>9q6r=1BUN%)dIV2IcbSymIHAe-|#!o+YHA7jxEl+0iBhWgfGq+Z;ROWp>9 zb);PeuI+kV@Ma75z85$)gAe?`bv(f^92tpwhotf9vMDz@_q}e2J&}rQj13U9=?Wia zdoVRkhu(*9%;gG)W56O}dPbYH^%m}jzGDP@{)5$d!lhUwVJhV88@A#ctJ^OSP<* z`<^k4O|J^i*5)7-YqM#3rVNW5mpViiJB4=GiEio89ipKU1mwZAM<2jR3CC{RR&!1^ zx6pcY;4u_y4}^!M5gJASKAWQlU^kuyy)!5xo->36ms>bYAJZ1Rn+8~~6T5w?87aWEV9{MsQGK_Mywr#^{toY6Sc8g>@-}Cso z31as`84(3=amm1pfQn9dy=dSx2#yXpifu#ORt7}AM?+IeL;~w>_!g`2z^+{(1Jh@# zJ`+gjhC*t780h9xS3JaR+4MP!h6O8<+y;2mR;guyA9K6F+iV6@Dbxz4S8CO?VP)bY z0^|3)pj#})l0y$Y&~F(DLC7eAN064*96KW+8VH%-4%yNS<_B|zqk~3Gi+5{Vq2uZ} z?+k=!9OPN12!jku1VNq@H^|!yJCRL2-@b1fWeDUsMp%YGux6498Un%LwayU#iWsQO z!n7%t9a#tL52c2n6f88_E#KvdR^i%zI0x!;T<`#z2rdutW^x zLkBVlvI4IZin8&n<)K=*H(>`-hCp^$34uJH>(MbuoU4Owmo+pZGT`@?ErT8^DSoT$ z!)2PQg4>Iq%P16SV{&bN*2&#+mb5$C;q#|BS)IXo z{GeZZ72$xLx}#@+#g5_o!%Gc|FF}lm(E=%>91LYnl-GTU$yq<1a)Mbj&oLkJt6n!F zUoEG=_hu#--L@PQ*-A3d0;z9#m8NsFs&oc@(SAgST&K@35qUqDD1;zTb&Tm-LLYy1 zc9I{KytQ|r`Lo2 zX_lol|Nq$>gC|8fWei0LdyyCwDZ+Hjgy#H3pGrtxuMh^k-B+iGybUtPTXtQ0%5bsD z5=rr@A$x}TSw!T7j>$cFi~0(B9~0qUq{pIupwrEB<8pXdQ|Z$btWmtLjbd?gRIh;d zSc+o?TQV>NUg}ge%}c`ZlPE9f=Iwko=#OVtKlO)1%ljt!_18+5FuzyxIZ^#i6(``6l7gIU!lbzdT$IwU1W-C7Z+cNo6j5J9 z2AoGPfnrslD_|NeDK|MGOwbg={7Ba*E<^{_SGvaR;{y)$1O8fE^cEl;kt` zz+=?ZVH4N+{?Sc5 zi($G>(MxzuZnj2?I491N6AN%=>cHP5iaezOJjc`NK=3>& z2QQ4i$zfSfRuBHlBJEJU6#dmDqecU22Q)R?hEJ8h9eiW`Z&`ER=yK0#QbgcBnf%#7 z`tWW`&BYDQV5~VxQbce{v&?yP$i3#StIUo=pu4F17y||RhXz3nP#wg<0WrSG3%Z73 zYFLHfID)EpK7|#JanAhXBwaz-6{cvLU0w}_KE-P} zzO6P0QpX@`c$ppIwTvn;K_CvMQ?BAXMYnJtC2ti_lBR$MBt*9v20d@buvD;mU4gD& zwD7;t3w4O}4bm-9%QjO1L|AFd=7K6K0BRb3lrtOOb+wS4jmjn9u(!$k%U1Wg6Rdb5 zHk#c$k@Q9lu3q0a(O1n{9Tb4EsX+8WmN_6r^+wp3f+a+ol4G8-78oD%T)PjwZPHO$yXSc142MDIv>UCgZppd;s)&Z6u#6?$R}5o9FKxCO(&}b*>`_Lzx@X^{?*fI@-oiS zgwzpiR!z6j8iFsEcml4I5sja&PXuaXH{HlaHVrLZ;)H$yMutC6GvH^+eggqvwMePD zB+H2Xz5=Kai6Vf#h8)X`Ss3fB+DTo?=%}#@oJ0Rah@V9Xo^j3!| zRL+~wCtOn(9lKICu}{Fa7c~dbO;0|#ZchFY zlFje97vx(Yy-y~w92`VUZpac#q=>0k=UUOa*Bu<{Yk3&4^I|Lkf3IWEU2~rDes`m; z8uKhm7n{v_gDZIj|Myh&slgJ5d);tJl$UDG3!21-VL_=HowLA*DwGF?gHmWpd_?zB z^}vf4sd2*{A|IWFku&p6hZ|EmH({9T(!9uc$yqNPCEdb*G-n5&@B`6xhH2K08p< z159=*Fb_n$h$9Hmo-tn{iOm#`7*kjrIe~md zOHh?B2~-=3NqJapTLaRtK@*bV40GEIq3BW`TS^qo)|^$AClVQX@pjl6BFQW%2}lSr zDI!B>CsS|brTZxny56IpiMl$=iWs+jqlrWUB=2?`L-m5J{mc2f$rb#zA3gw46LFUA zhjE4OxfJjvdVAyGYER8T=bHS^9Wb{RM^7vtMg@`J4fh)R?39fTH7Dl~w~$sLKVeR1 z5&VjxT78^U-%Dw+a@O}b?GYU{Ukx2mqW0y($nga9IFbBu1&O#Rb6+&^lT4o|6$Go~qvJgZ6;gF$e~F|Idpn(^()`>D%p7Qj z$#28T*}SD24C$|8a7=7JKUrmTJo6Od8Fb6e4tN-zuo=vTLOa}Ax`#^$P-zI~DI*pS|senBA^^zfwE?X@she>-yu* zBUGcl<+JB5L8njxgW_=}8&NX?9gwOTm!txKc1VzxSABdXP<@>G;#*QYByn%|+4rlK zUyH)>pPE!d^8{=pW_=9C1Zku)!@$dlH7I3^a=R-nfY+5r`>kh0jFI{&5mg&?TU1f~ zQJtNBzi|{MI%zu_qFrn}9pXb%0}4JsS*K=I#6hG~hl*O|vhB1;9EZwocW7KaYN8q^ zowJQPAXK;QoT*`*oOP&eP_ojHyzJoDUn|X9HkmSq@f7EIbj<~(2YmBQ1+ac8Xy`~0 zCtVuvl!;`C$Y@S}p%72bAd*HMv1As_d(Pt*xSE=wi}T|BzAkVQqkG-qR2j;*;3zKM z?{B&N+04MzPxrz=uX!{h$+yjEH+gNOfYm%}*`Pf&iaK+#olHf9ako$Qb2 zR%KPoCrQoM*e}Fu>#R@uT1wY|ZNTBS z>nsy_*N%iykkDKdX;w{060YVDnpDXo54ShuWrqPLdV_6Nc6&vNwQcUq%6^;aq35jP zvX*;lw`)c%Ec?~J_HR>c_xr#9+Q0t-9|$-9{)=rs{QZZ&F63*^r)DF~jd=-wrp0xUA1ES`{7zt9iHfb{GHiXZ-r%)MNj yl>E2+{TJI;{{D;eF#rFStM>O_4wOFwu=j91To2d7^>F<+uRj4$XdMp#bN~QY?jqL! literal 5879 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 Date: Tue, 18 Dec 2018 18:15:38 -0800 Subject: [PATCH 11/20] more file workflow changes including export project popup window, add readme --- .gitignore | 1 + .../Assets/Editor/AvatarExporter.cs | 378 ++++++++++++------ tools/unity-avatar-exporter/Assets/README.txt | 11 + .../Assets/README.txt.meta | 7 + .../avatarExporter.unitypackage | Bin 5864 -> 7145 bytes tools/unity-avatar-exporter/packager.bat | 2 +- 6 files changed, 279 insertions(+), 120 deletions(-) create mode 100644 tools/unity-avatar-exporter/Assets/README.txt create mode 100644 tools/unity-avatar-exporter/Assets/README.txt.meta diff --git a/.gitignore b/.gitignore index 09b58d71ef..f3f6954974 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,4 @@ tools/jsdoc/package-lock.json tools/unity-avatar-exporter/Library tools/unity-avatar-exporter/Packages tools/unity-avatar-exporter/ProjectSettings +tools/unity-avatar-exporter/Temp \ No newline at end of file diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index b62f51f4e8..2e17b04643 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -12,8 +12,8 @@ using System; using System.IO; using System.Collections.Generic; -public class AvatarExporter : MonoBehaviour { - public static readonly Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { +class AvatarExporter : MonoBehaviour { + static readonly Dictionary HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary { {"Chest", "Spine1"}, {"Head", "Head"}, {"Hips", "Hips"}, @@ -70,7 +70,7 @@ public class AvatarExporter : MonoBehaviour { {"UpperChest", "Spine2"}, }; - public static readonly Dictionary referenceAbsoluteRotations = new Dictionary { + static readonly Dictionary referenceAbsoluteRotations = new Dictionary { {"Head", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, {"Hips", new Quaternion(-3.043941e-10f, -1.573706e-7f, 5.112975e-6f, 1f)}, {"LeftHandIndex3", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)}, @@ -127,55 +127,176 @@ public class AvatarExporter : MonoBehaviour { {"Spine2", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, }; - public static Dictionary userBoneToHumanoidMappings = new Dictionary(); - public static Dictionary userParentNames = new Dictionary(); - public static Dictionary userAbsoluteRotations = new Dictionary(); + static Dictionary userBoneToHumanoidMappings = new Dictionary(); + static Dictionary userParentNames = new Dictionary(); + static Dictionary userAbsoluteRotations = new Dictionary(); + + static string assetPath = ""; + static string assetName = ""; + static HumanDescription humanDescription; [MenuItem("High Fidelity/Export New Avatar")] - public static void ExportNewAvatar() { + static void ExportNewAvatar() { ExportSelectedAvatar(false); } - [MenuItem("High Fidelity/Update Avatar")] - public static void UpdateAvatar() { + [MenuItem("High Fidelity/Update Existing Avatar")] + static void UpdateAvatar() { ExportSelectedAvatar(true); } - public static void ExportSelectedAvatar(bool updateAvatar) { + static void ExportSelectedAvatar(bool updateAvatar) { string[] guids = Selection.assetGUIDs; if (guids.Length != 1) { if (guids.Length == 0) { - EditorUtility.DisplayDialog("Error", "Please select an asset to export", "Ok"); + EditorUtility.DisplayDialog("Error", "Please select an asset to export.", "Ok"); } else { - EditorUtility.DisplayDialog("Error", "Please select a single asset to export", "Ok"); + EditorUtility.DisplayDialog("Error", "Please select a single asset to export.", "Ok"); } return; } - string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); - ModelImporter importer = ModelImporter.GetAtPath(assetPath) as ModelImporter; - if (assetPath.LastIndexOf(".fbx") == -1 || importer == null) { - EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export", "Ok"); + assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); + assetName = Path.GetFileNameWithoutExtension(assetPath); + ModelImporter modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; + if (assetPath.LastIndexOf(".fbx") == -1 || modelImporter == null) { + EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok"); return; } - if (importer.animationType != ModelImporterAnimationType.Human) { - EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid", "Ok"); + if (modelImporter.animationType != ModelImporterAnimationType.Human) { + EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid in the Rig section of it's Inspector window.", "Ok"); return; } + + humanDescription = modelImporter.humanDescription; + if (!SetJointMappingsAndParentNames()) { + return; + } + + string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); + string hifiFolder = documentsFolder + "\\High Fidelity Projects"; + if (updateAvatar) { // Update Existing Avatar menu option + bool copyModelToExport = false; + string initialPath = Directory.Exists(hifiFolder) ? hifiFolder : documentsFolder; + + // open file explorer defaulting to hifi folder in user documents to select target fst to update + string exportFstPath = EditorUtility.OpenFilePanel("Select fst to update", initialPath, "fst"); + if (exportFstPath.Length == 0) { // file selection cancelled + return; + } + string exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; + + if (File.Exists(exportModelPath)) { + // if the fbx in Unity Assets is newer than the fbx in the target export + // folder or vice-versa then ask to replace the older fbx with the newer fbx + DateTime assetModelWriteTime = File.GetLastWriteTime(assetPath); + DateTime targetModelWriteTime = File.GetLastWriteTime(exportModelPath); + if (assetModelWriteTime > targetModelWriteTime) { + int option = EditorUtility.DisplayDialogComplex("Error", "The " + assetName + + ".fbx model in the Unity Assets folder is newer than the " + exportModelPath + + " model.\n\nDo you want to replace the older .fbx with the newer .fbx?", + "Yes", "No", "Cancel"); + if (option == 2) { // Cancel + return; + } + copyModelToExport = option == 0; + } else if (assetModelWriteTime < targetModelWriteTime) { + int option = EditorUtility.DisplayDialogComplex("Error", "The " + exportModelPath + + " model is newer than the " + assetName + ".fbx model in the Unity Assets folder." + + "\n\nDo you want to replace the older .fbx with the newer .fbx and re-import it?", + "Yes", "No" , "Cancel"); + if (option == 2) { // Cancel + return; + } else if (option == 0) { // Yes - copy model to Unity project + // delete existing fbx and associated meta file in Unity Assets + File.Delete(assetPath); + File.Delete(assetPath + ".meta"); + AssetDatabase.Refresh(); + + // copy the fbx from the project folder to Unity Assets and import it + File.Copy(exportModelPath, assetPath); + AssetDatabase.ImportAsset(assetPath); + + // set model to Humanoid animation type and force another refresh on it to process Humanoid + modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; + modelImporter.animationType = ModelImporterAnimationType.Human; + EditorUtility.SetDirty(modelImporter); + modelImporter.SaveAndReimport(); + humanDescription = modelImporter.humanDescription; + + // redo joint mappings and parent names due to the fbx change + SetJointMappingsAndParentNames(); + } + } + } else { + // if no matching fbx exists in the target export folder then ask to copy fbx over + int option = EditorUtility.DisplayDialogComplex("Error", "There is no existing " + exportModelPath + + " model.\n\nDo you want to copy over the " + assetName + + ".fbx model from the Unity Assets folder?", "Yes", "No", "Cancel"); + if (option == 2) { // Cancel + return; + } + copyModelToExport = option == 0; + } + + // delete any existing fbx if we agreed to overwrite it, and copy asset fbx over + if (copyModelToExport) { + if (File.Exists(exportModelPath)) { + File.Delete(exportModelPath); + } + File.Copy(assetPath, exportModelPath); + } + + // delete any existing fst since we are re-exporting it + // TODO: updating fst should only rewrite joint mappings and joint rotation offsets to existing file + if (File.Exists(exportFstPath)) { + File.Delete(exportFstPath); + } + + WriteFST(exportFstPath); + } else { // Export New Avatar menu option + // create High Fidelity folder in user documents folder if it doesn't exist + if (!Directory.Exists(hifiFolder)) { + Directory.CreateDirectory(hifiFolder); + } + + // open a popup window to enter new export project name and project location + ExportProjectWindow window = ScriptableObject.CreateInstance(); + window.Init(hifiFolder, OnExportProjectWindowClose); + } + } + + static void OnExportProjectWindowClose(string projectDirectory) { + // copy the fbx from the Unity Assets folder to the project directory, + // and then write out the fst file to the project directory + string exportModelPath = projectDirectory + assetName + ".fbx"; + string exportFstPath = projectDirectory + "avatar.fst"; + File.Copy(assetPath, exportModelPath); + WriteFST(exportFstPath); - userBoneToHumanoidMappings.Clear(); + // create empty Textures and Scripts folders in the project directory + string texturesDirectory = projectDirectory + "\\textures"; + string scriptsDirectory = projectDirectory + "\\scripts"; + Directory.CreateDirectory(texturesDirectory); + Directory.CreateDirectory(scriptsDirectory); + + // open File Explorer to the project directory once finished + System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); + } + + static bool SetJointMappingsAndParentNames() { userParentNames.Clear(); - userAbsoluteRotations.Clear(); + userBoneToHumanoidMappings.Clear(); // instantiate a game object of the user avatar to save out bone parents then destroy it UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); - if (avatarResource) { - GameObject assetGameObject = (GameObject)Instantiate(avatarResource); - SetParentNames(assetGameObject.transform, userParentNames); - DestroyImmediate(assetGameObject); + if (!avatarResource) { + return false; } + GameObject assetGameObject = (GameObject)Instantiate(avatarResource); + SetParentNames(assetGameObject.transform, userParentNames); + DestroyImmediate(assetGameObject); // store joint mappings only for joints that exist in hifi and verify missing joints - HumanDescription humanDescription = importer.humanDescription; HumanBone[] boneMap = humanDescription.human; string chestUserBone = ""; string neckUserBone = ""; @@ -195,11 +316,11 @@ public class AvatarExporter : MonoBehaviour { } if (!userBoneToHumanoidMappings.ContainsValue("Hips")) { EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok"); - return; + return false; } if (!userBoneToHumanoidMappings.ContainsValue("Spine")) { EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok"); - return; + return false; } if (!userBoneToHumanoidMappings.ContainsValue("Chest")) { // check to see if there is a child of Spine that could be mapped to Chest @@ -222,7 +343,7 @@ public class AvatarExporter : MonoBehaviour { chestUserBone = spineChild; } else { EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok"); - return; + return false; } } if (!userBoneToHumanoidMappings.ContainsValue("UpperChest")) { @@ -240,66 +361,13 @@ public class AvatarExporter : MonoBehaviour { userBoneToHumanoidMappings[chestUserBone] = "UpperChest"; } } - - bool copyModelToExport = false; - string exportFstPath, exportModelPath; - string assetName = Path.GetFileNameWithoutExtension(assetPath); - string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); - if (updateAvatar) { - // open file explorer defaulting to user documents folder to select target fst to update - exportFstPath = EditorUtility.OpenFilePanel("Select fst to update", documentsFolder, "fst"); - if (exportFstPath.Length == 0) { // file selection cancelled - return; - } - exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; - - if (File.Exists(exportModelPath)) { - // if the fbx in Unity Assets/Resources is newer than the fbx in the - // target export folder or vice-versa then ask to copy fbx over - DateTime assetModelWriteTime = File.GetLastWriteTime(assetPath); - DateTime targetModelWriteTime = File.GetLastWriteTime(exportModelPath); - if (assetModelWriteTime > targetModelWriteTime) { - copyModelToExport = EditorUtility.DisplayDialog("Error", "The " + assetName + - ".fbx model in the Unity Assets/Resources folder is newer than the " + exportModelPath + - " model. Do you want to copy the newer .fbx model over?" , "Yes", "No"); - } else if (assetModelWriteTime < targetModelWriteTime) { - bool copyModelToUnity = EditorUtility.DisplayDialog("Error", "The " + exportModelPath + - " model is newer than the " + assetName + - ".fbx model in the Unity Assets/Resources folder. Do you want to copy the newer .fbx model over?", - "Yes", "No"); - if (copyModelToUnity) { - File.Delete(assetPath); - File.Copy(exportModelPath, assetPath); - } - } - } else { - // if no matching fbx exists in the target export folder then ask to copy fbx over - copyModelToExport = EditorUtility.DisplayDialog("Error", "There is no existing " + exportModelPath + - " model. Do you want to copy over the " + assetName + - ".fbx model from the Unity Assets/Resources folder?" , "Yes", "No"); - } - } else { - // open folder explorer defaulting to user documents folder to select target folder to export fst and fbx to - if (!SelectExportFolder(assetName, documentsFolder, out exportFstPath, out exportModelPath)) { - return; - } - copyModelToExport = true; - } - - // delete any existing fbx since we would have agreed to overwrite it, and copy asset fbx over - if (copyModelToExport) { - if (File.Exists(exportModelPath)) { - File.Delete(exportModelPath); - } - File.Copy(assetPath, exportModelPath); - } - - // delete any existing fst since we agreed to overwrite it or are updating it - // TODO: should updating fst only rewrite joint mappings and joint rotation offsets? - if (File.Exists(exportFstPath)) { - File.Delete(exportFstPath); - } + return true; + } + + static void WriteFST(string exportFstPath) { + userAbsoluteRotations.Clear(); + // write out core fields to top of fst file File.WriteAllText(exportFstPath, "name = " + assetName + "\ntype = body+head\nscale = 1\nfilename = " + assetName + ".fbx\n" + "texdir = textures\n"); @@ -310,6 +378,7 @@ public class AvatarExporter : MonoBehaviour { File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + jointMapping.Key + "\n"); } + // calculate and write out joint rotation offsets to fst file SkeletonBone[] skeletonMap = humanDescription.skeleton; foreach (SkeletonBone userBone in skeletonMap) { string userBoneName = userBone.name; @@ -353,37 +422,8 @@ public class AvatarExporter : MonoBehaviour { } } } - - public static bool SelectExportFolder(string assetName, string initialPath, out string fstPath, out string modelPath) { - string selectedPath = EditorUtility.OpenFolderPanel("Select export location", initialPath, ""); - if (selectedPath.Length == 0) { // folder selection cancelled - fstPath = ""; - modelPath = ""; - return false; - } - fstPath = selectedPath + "/" + assetName + ".fst"; - modelPath = selectedPath + "/" + assetName + ".fbx"; - bool fstExists = File.Exists(fstPath); - bool modelExists = File.Exists(modelPath); - if (fstExists || modelExists) { - string overwriteMessage; - if (fstExists && modelExists) { - overwriteMessage = assetName + ".fst and " + assetName + - ".fbx already exist here, would you like to overwrite them?"; - } else if (fstExists) { - overwriteMessage = assetName + ".fst already exists here, would you like to overwrite it?"; - } else { - overwriteMessage = assetName + ".fbx already exists here, would you like to overwrite it?"; - } - bool overwrite = EditorUtility.DisplayDialog("Error", overwriteMessage, "Yes", "No"); - if (!overwrite) { - return SelectExportFolder(assetName, selectedPath, out fstPath, out modelPath); - } - } - return true; - } - - public static void SetParentNames(Transform modelBone, Dictionary parentNames) { + + static void SetParentNames(Transform modelBone, Dictionary parentNames) { for (int i = 0; i < modelBone.childCount; i++) { SetParentNames(modelBone.GetChild(i), parentNames); } @@ -394,7 +434,7 @@ public class AvatarExporter : MonoBehaviour { } } - public static string FindLastRequiredParentBone(string currentBone) { + static string FindLastRequiredParentBone(string currentBone) { string result = currentBone; while (result != "root" && !userBoneToHumanoidMappings.ContainsKey(result)) { result = userParentNames[result]; @@ -402,3 +442,103 @@ public class AvatarExporter : MonoBehaviour { return result; } } + +class ExportProjectWindow : EditorWindow { + string projectName = ""; + string projectLocation = ""; + string projectDirectory = ""; + string errorLabel = ""; + + public delegate void OnCloseDelegate(string projectDirectory); + OnCloseDelegate onCloseCallback; + + public void Init(string initialPath, OnCloseDelegate closeCallback) { + projectLocation = initialPath; + onCloseCallback = closeCallback; + ShowUtility(); + } + + void OnGUI() { + GUIStyle buttonStyle = new GUIStyle(GUI.skin.button); + buttonStyle.fontSize = 20; + GUIStyle labelStyle = new GUIStyle(GUI.skin.label); + labelStyle.fontSize = 16; + GUIStyle errorStyle = new GUIStyle(GUI.skin.label); + errorStyle.fontSize = 12; + errorStyle.normal.textColor = Color.red; + GUIStyle textStyle = new GUIStyle(GUI.skin.textField); + textStyle.fontSize = 16; + + GUILayout.Space(10); + + GUILayout.Label("Export project name:", labelStyle); + projectName = GUILayout.TextField(projectName, textStyle); + + GUILayout.Space(10); + + GUILayout.Label("Export project location:", labelStyle); + projectLocation = GUILayout.TextField(projectLocation, textStyle); + + if (GUILayout.Button("Browse", buttonStyle)) { + string result = EditorUtility.OpenFolderPanel("Select export location", projectLocation, ""); + if (result.Length > 0) { // folder selection not cancelled + projectLocation = result.Replace('/', '\\'); + } + } + + GUILayout.Label(errorLabel, errorStyle); + + GUILayout.Space(30); + + bool export = false; + if (GUILayout.Button("Export", buttonStyle)) { + export = true; + if (!CheckForErrors(true)) { + Close(); + onCloseCallback(projectDirectory); + } + } + + if (GUILayout.Button("Cancel", buttonStyle)) { + Close(); + } + + if (GUI.changed && !export) { + CheckForErrors(false); + } + } + + bool CheckForErrors(bool exporting) { + errorLabel = ""; + projectDirectory = projectLocation + "\\" + projectName + "\\"; + if (projectName.Length > 0) { + if (Directory.Exists(projectDirectory)) { + errorLabel = "A folder with the name " + projectName + " already exists at that location.\nPlease choose a different project name or location."; + return true; + } + } + if (projectLocation.Length > 0) { + if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') { + errorLabel = "Project location is invalid. Please choose a different project location."; + return true; + } + } + if (exporting) { + if (projectName.Length == 0) { + errorLabel = "Please define a project name."; + return true; + } else if (projectLocation.Length == 0) { + errorLabel = "Please define a project location."; + return true; + } else { + try { + Directory.CreateDirectory(projectDirectory); + } catch { + errorLabel = "Project location is invalid. Please choose a different project location."; + return true; + } + } + } + return false; + } +} diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt new file mode 100644 index 0000000000..7e6c6d4f48 --- /dev/null +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -0,0 +1,11 @@ +To create a new avatar project: +1. Import your .fbx avatar model into Unity Assets (drag and drop file into Assets window or use Assets menu > Import New Assets). +2. Select the .fbx avatar that you imported in the Assets window, and in the Inspector window set the Animation Type to Humanoid and choose Apply. +3. With the .fbx avatar still selected, select High Fidelity menu > Export New Avatar. +4. Select a name for your avatar project (this will be used to create a directory with that name), as well as the target location for your project folder. +5. Once it is exported, your project directory will open in File Explorer. + +To update an existing avatar project: +1. Select the existing .fbx avatar in the Assets window that you would like to re-export. +2. Select High Fidelity menu > Update Avatar and choose the .fst file you would like to update. +3. If the .fbx file in your Unity Assets folder is newer than the existing .fbx file in your avatar project or vice-versa, you will be prompted if you wish to replace the older file with the newer file. \ No newline at end of file diff --git a/tools/unity-avatar-exporter/Assets/README.txt.meta b/tools/unity-avatar-exporter/Assets/README.txt.meta new file mode 100644 index 0000000000..d8bc5b9b66 --- /dev/null +++ b/tools/unity-avatar-exporter/Assets/README.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 71e72751b2810fc4993ff53291c430b6 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 4134d6f09a52e356fd3e6a543b2bcb134a7daf5b..d1cd4efe8caa08bbb332eacb613993b5e232244f 100644 GIT binary patch literal 7145 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 diff --git a/tools/unity-avatar-exporter/packager.bat b/tools/unity-avatar-exporter/packager.bat index 99932f1ead..55b59a9db6 100644 --- a/tools/unity-avatar-exporter/packager.bat +++ b/tools/unity-avatar-exporter/packager.bat @@ -1 +1 @@ -"C:\Program Files\Unity\Editor\Unity.exe" -quit -batchmode -projectPath %CD% -exportPackage "Assets/Editor" "avatarExporter.unitypackage" +"C:\Program Files\Unity\Editor\Unity.exe" -quit -batchmode -projectPath %CD% -exportPackage "Assets" "avatarExporter.unitypackage" From b139e52fb610d5334a59747407512b90617c0f3b Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 18 Dec 2018 18:17:04 -0800 Subject: [PATCH 12/20] update package --- .../avatarExporter.unitypackage | Bin 7145 -> 7579 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index d1cd4efe8caa08bbb332eacb613993b5e232244f..5327c0beed0952d24fb2b86cef3e06922058ed60 100644 GIT binary patch literal 7579 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 xw-dB}{}+C5<^K1-?vKLKD12$TS#000tMlal}d literal 7145 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 Date: Tue, 18 Dec 2018 18:20:59 -0800 Subject: [PATCH 13/20] newline --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f3f6954974..bbb79ad6a9 100644 --- a/.gitignore +++ b/.gitignore @@ -100,4 +100,4 @@ tools/jsdoc/package-lock.json tools/unity-avatar-exporter/Library tools/unity-avatar-exporter/Packages tools/unity-avatar-exporter/ProjectSettings -tools/unity-avatar-exporter/Temp \ No newline at end of file +tools/unity-avatar-exporter/Temp From c16f58bdc7bbd4f3939ab53587654b89a4dcd473 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 19 Dec 2018 18:03:17 -0800 Subject: [PATCH 14/20] UX feedback changes --- .../Assets/Editor/AvatarExporter.cs | 162 +++++++++++++----- tools/unity-avatar-exporter/Assets/README.txt | 10 +- .../avatarExporter.unitypackage | Bin 7579 -> 8795 bytes 3 files changed, 126 insertions(+), 46 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 2e17b04643..1f3c11fc03 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -171,6 +171,13 @@ class AvatarExporter : MonoBehaviour { if (!SetJointMappingsAndParentNames()) { return; } + + //var textures = AssetDatabase.LoadAllAssetsAtPath(assetPath).Where(x => x.GetType() == typeof(Texture)); + var tests = AssetDatabase.LoadAllAssetsAtPath(assetPath); + Debug.Log("assetPath " + assetPath); + foreach (var test in tests) { + Debug.Log("test " + test.GetType()); + } string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); string hifiFolder = documentsFolder + "\\High Fidelity Projects"; @@ -178,13 +185,40 @@ class AvatarExporter : MonoBehaviour { bool copyModelToExport = false; string initialPath = Directory.Exists(hifiFolder) ? hifiFolder : documentsFolder; - // open file explorer defaulting to hifi folder in user documents to select target fst to update + // open file explorer defaulting to hifi projects folder in user documents to select target fst to update string exportFstPath = EditorUtility.OpenFilePanel("Select fst to update", initialPath, "fst"); if (exportFstPath.Length == 0) { // file selection cancelled return; } - string exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; + exportFstPath = exportFstPath.Replace('/', '\\'); + // lookup the project name field from the fst file to update + string projectName = ""; + try { + string[] lines = File.ReadAllLines(exportFstPath); + foreach (string line in lines) { + if (line.StartsWith("name")) { + projectName = line.Substring(line.IndexOf("=") + 2); + break; + } + } + } catch { + EditorUtility.DisplayDialog("Error", "Failed to read from existing file " + exportFstPath + + ". Please check the file and try again.", "Ok"); + return; + } + + // delete existing fst file since we will write a new file + // TODO: updating fst should only rewrite joint mappings and joint rotation offsets to existing file + try { + File.Delete(exportFstPath); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to overwrite existing file " + exportFstPath + + ". Please check the file and try again.", "Ok"); + return; + } + + string exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; if (File.Exists(exportModelPath)) { // if the fbx in Unity Assets is newer than the fbx in the target export // folder or vice-versa then ask to replace the older fbx with the newer fbx @@ -198,7 +232,7 @@ class AvatarExporter : MonoBehaviour { if (option == 2) { // Cancel return; } - copyModelToExport = option == 0; + copyModelToExport = option == 0; // Yes } else if (assetModelWriteTime < targetModelWriteTime) { int option = EditorUtility.DisplayDialogComplex("Error", "The " + exportModelPath + " model is newer than the " + assetName + ".fbx model in the Unity Assets folder." + @@ -235,26 +269,33 @@ class AvatarExporter : MonoBehaviour { if (option == 2) { // Cancel return; } - copyModelToExport = option == 0; + copyModelToExport = option == 0; // Yes } // delete any existing fbx if we agreed to overwrite it, and copy asset fbx over if (copyModelToExport) { if (File.Exists(exportModelPath)) { - File.Delete(exportModelPath); + try { + File.Delete(exportModelPath); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to overwrite existing file " + exportModelPath + + ". Please check the file and try again.", "Ok"); + return; + } + } + try { + File.Copy(assetPath, exportModelPath); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to copy existing file " + assetPath + " to " + exportModelPath + + ". Please check the location and try again.", "Ok"); + return; } - File.Copy(assetPath, exportModelPath); } - - // delete any existing fst since we are re-exporting it - // TODO: updating fst should only rewrite joint mappings and joint rotation offsets to existing file - if (File.Exists(exportFstPath)) { - File.Delete(exportFstPath); - } - - WriteFST(exportFstPath); + + // write out a new fst file in place of the old file + WriteFST(exportFstPath, projectName); } else { // Export New Avatar menu option - // create High Fidelity folder in user documents folder if it doesn't exist + // create High Fidelity Projects folder in user documents folder if it doesn't exist if (!Directory.Exists(hifiFolder)) { Directory.CreateDirectory(hifiFolder); } @@ -265,13 +306,10 @@ class AvatarExporter : MonoBehaviour { } } - static void OnExportProjectWindowClose(string projectDirectory) { - // copy the fbx from the Unity Assets folder to the project directory, - // and then write out the fst file to the project directory + static void OnExportProjectWindowClose(string projectDirectory, string projectName) { + // copy the fbx from the Unity Assets folder to the project directory string exportModelPath = projectDirectory + assetName + ".fbx"; - string exportFstPath = projectDirectory + "avatar.fst"; File.Copy(assetPath, exportModelPath); - WriteFST(exportFstPath); // create empty Textures and Scripts folders in the project directory string texturesDirectory = projectDirectory + "\\textures"; @@ -279,8 +317,14 @@ class AvatarExporter : MonoBehaviour { Directory.CreateDirectory(texturesDirectory); Directory.CreateDirectory(scriptsDirectory); - // open File Explorer to the project directory once finished - System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); + // write out the avatar.fst file to the project directory + string exportFstPath = projectDirectory + "avatar.fst"; + WriteFST(exportFstPath, projectName); + + // remove any double slashes in texture directory path and warn user to copy external textures over + texturesDirectory = texturesDirectory.Replace("\\\\", "\\"); + EditorUtility.DisplayDialog("Warning", "If you are using any external textures with your model, " + + "please copy those textures to " + texturesDirectory, "Ok"); } static bool SetJointMappingsAndParentNames() { @@ -296,7 +340,7 @@ class AvatarExporter : MonoBehaviour { SetParentNames(assetGameObject.transform, userParentNames); DestroyImmediate(assetGameObject); - // store joint mappings only for joints that exist in hifi and verify missing joints + // store joint mappings only for joints that exist in hifi and verify missing required joints HumanBone[] boneMap = humanDescription.human; string chestUserBone = ""; string neckUserBone = ""; @@ -365,12 +409,18 @@ class AvatarExporter : MonoBehaviour { return true; } - static void WriteFST(string exportFstPath) { + static void WriteFST(string exportFstPath, string projectName) { userAbsoluteRotations.Clear(); - + // write out core fields to top of fst file - File.WriteAllText(exportFstPath, "name = " + assetName + "\ntype = body+head\nscale = 1\nfilename = " + - assetName + ".fbx\n" + "texdir = textures\n"); + try { + File.WriteAllText(exportFstPath, "name = " + projectName + "\ntype = body+head\nscale = 1\nfilename = " + + assetName + ".fbx\n" + "texdir = textures\n"); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to write file " + exportFstPath + + ". Please check the location and try again.", "Ok"); + return; + } // write out joint mappings to fst file foreach (var jointMapping in userBoneToHumanoidMappings) { @@ -414,13 +464,16 @@ class AvatarExporter : MonoBehaviour { } } - // swap from left-handed (Unity) to right-handed (HiFi) coordinate system and write out joint rotation offset to fst + // swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst if (outputJointName != "") { jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " + jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); } } + + // open File Explorer to the project directory once finished + System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); } static void SetParentNames(Transform modelBone, Dictionary parentNames) { @@ -443,42 +496,55 @@ class AvatarExporter : MonoBehaviour { } } -class ExportProjectWindow : EditorWindow { +class ExportProjectWindow : EditorWindow { + const int MIN_WIDTH = 450; + const int MIN_HEIGHT = 260; + const int BUTTON_FONT_SIZE = 16; + const int LABEL_FONT_SIZE = 16; + const int TEXT_FIELD_FONT_SIZE = 14; + const int ERROR_FONT_SIZE = 12; + string projectName = ""; string projectLocation = ""; string projectDirectory = ""; - string errorLabel = ""; + string errorLabel = "\n"; - public delegate void OnCloseDelegate(string projectDirectory); + public delegate void OnCloseDelegate(string projectDirectory, string projectName); OnCloseDelegate onCloseCallback; public void Init(string initialPath, OnCloseDelegate closeCallback) { + minSize = new Vector2(MIN_WIDTH, MIN_HEIGHT); + titleContent.text = "Export New Avatar"; projectLocation = initialPath; onCloseCallback = closeCallback; ShowUtility(); } void OnGUI() { + // define UI styles for all GUI elements to be created GUIStyle buttonStyle = new GUIStyle(GUI.skin.button); - buttonStyle.fontSize = 20; + buttonStyle.fontSize = BUTTON_FONT_SIZE; GUIStyle labelStyle = new GUIStyle(GUI.skin.label); - labelStyle.fontSize = 16; - GUIStyle errorStyle = new GUIStyle(GUI.skin.label); - errorStyle.fontSize = 12; - errorStyle.normal.textColor = Color.red; + labelStyle.fontSize = LABEL_FONT_SIZE; GUIStyle textStyle = new GUIStyle(GUI.skin.textField); - textStyle.fontSize = 16; + textStyle.fontSize = TEXT_FIELD_FONT_SIZE; + GUIStyle errorStyle = new GUIStyle(GUI.skin.label); + errorStyle.fontSize = ERROR_FONT_SIZE; + errorStyle.normal.textColor = Color.red; GUILayout.Space(10); + // Project name label and input text field GUILayout.Label("Export project name:", labelStyle); projectName = GUILayout.TextField(projectName, textStyle); GUILayout.Space(10); + // Project location label and input text field GUILayout.Label("Export project location:", labelStyle); projectLocation = GUILayout.TextField(projectLocation, textStyle); + // Browse button to open folder explorer that starts at project location path and then updates project location if (GUILayout.Button("Browse", buttonStyle)) { string result = EditorUtility.OpenFolderPanel("Select export location", projectLocation, ""); if (result.Length > 0) { // folder selection not cancelled @@ -486,44 +552,54 @@ class ExportProjectWindow : EditorWindow { } } + // Red error label text to display any issues under text fields and Browse button GUILayout.Label(errorLabel, errorStyle); - GUILayout.Space(30); + GUILayout.Space(25); + // Export button which will verify project folder can actually be created + // before closing popup window and calling back to initiate the export bool export = false; if (GUILayout.Button("Export", buttonStyle)) { export = true; if (!CheckForErrors(true)) { Close(); - onCloseCallback(projectDirectory); + onCloseCallback(projectDirectory, projectName); } } + // Cancel button just closes the popup window without callback if (GUILayout.Button("Cancel", buttonStyle)) { Close(); } + // When either text field changes check for any errors if we didn't just check errors from clicking Export above if (GUI.changed && !export) { CheckForErrors(false); } } bool CheckForErrors(bool exporting) { - errorLabel = ""; + errorLabel = "\n"; // default to no error projectDirectory = projectLocation + "\\" + projectName + "\\"; if (projectName.Length > 0) { + // new project must have a unique folder name since the folder will be created for it if (Directory.Exists(projectDirectory)) { - errorLabel = "A folder with the name " + projectName + " already exists at that location.\nPlease choose a different project name or location."; + errorLabel = "A folder with the name " + projectName + + " already exists at that location.\nPlease choose a different project name or location."; return true; } } if (projectLocation.Length > 0) { + // before clicking Export we can verify that the project location at least starts with a drive if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') { - errorLabel = "Project location is invalid. Please choose a different project location."; + errorLabel = "Project location is invalid. Please choose a different project location.\n"; return true; } } if (exporting) { + // when exporting, project name and location must both be defined, and project location must + // be valid and accessible (we attempt to create the project folder at this time to verify this) if (projectName.Length == 0) { errorLabel = "Please define a project name."; return true; @@ -534,7 +610,7 @@ class ExportProjectWindow : EditorWindow { try { Directory.CreateDirectory(projectDirectory); } catch { - errorLabel = "Project location is invalid. Please choose a different project location."; + errorLabel = "Project location is invalid. Please choose a different project location.\n"; return true; } } diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index 7e6c6d4f48..034ec23982 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,11 +1,15 @@ To create a new avatar project: 1. Import your .fbx avatar model into Unity Assets (drag and drop file into Assets window or use Assets menu > Import New Assets). -2. Select the .fbx avatar that you imported in the Assets window, and in the Inspector window set the Animation Type to Humanoid and choose Apply. +2. Select the .fbx avatar that you imported in the Assets window, and in the Rig section of the Inspector window set the Animation Type to Humanoid and choose Apply. 3. With the .fbx avatar still selected, select High Fidelity menu > Export New Avatar. 4. Select a name for your avatar project (this will be used to create a directory with that name), as well as the target location for your project folder. 5. Once it is exported, your project directory will open in File Explorer. To update an existing avatar project: 1. Select the existing .fbx avatar in the Assets window that you would like to re-export. -2. Select High Fidelity menu > Update Avatar and choose the .fst file you would like to update. -3. If the .fbx file in your Unity Assets folder is newer than the existing .fbx file in your avatar project or vice-versa, you will be prompted if you wish to replace the older file with the newer file. \ No newline at end of file +2. Select High Fidelity menu > Update Existing Avatar and choose the .fst file you would like to update. +3. If the .fbx file in your Unity Assets folder is newer than the existing .fbx file in your avatar project or vice-versa, you will be prompted if you wish to replace the older file with the newer file. +4. Once it is updated, your project directory will open in File Explorer. + +* WARNING * +If you are using any external textures as part of your .fbx model, be sure they are copied into the textures folder that is created in the project folder after exporting a new avatar. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 5327c0beed0952d24fb2b86cef3e06922058ed60..dfebaa096138bda98ddad5720b4bc3f718294d2e 100644 GIT binary patch literal 8795 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 RJUsu+=MVRhi9!Ih007!G+#LV_ literal 7579 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 xw-dB}{}+C5<^K1-?vKLKD12$TS#000tMlal}d From 1b98944caac7c49bd8110c75d8c11accf14298b9 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 20 Dec 2018 14:24:01 -0800 Subject: [PATCH 15/20] missing newlines --- .../Assets/Editor/AvatarExporter.cs | 10 +++++----- .../avatarExporter.unitypackage | Bin 8795 -> 8794 bytes 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 1f3c11fc03..3499e020f5 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -186,7 +186,7 @@ class AvatarExporter : MonoBehaviour { string initialPath = Directory.Exists(hifiFolder) ? hifiFolder : documentsFolder; // open file explorer defaulting to hifi projects folder in user documents to select target fst to update - string exportFstPath = EditorUtility.OpenFilePanel("Select fst to update", initialPath, "fst"); + string exportFstPath = EditorUtility.OpenFilePanel("Select .fst to update", initialPath, "fst"); if (exportFstPath.Length == 0) { // file selection cancelled return; } @@ -498,7 +498,7 @@ class AvatarExporter : MonoBehaviour { class ExportProjectWindow : EditorWindow { const int MIN_WIDTH = 450; - const int MIN_HEIGHT = 260; + const int MIN_HEIGHT = 250; const int BUTTON_FONT_SIZE = 16; const int LABEL_FONT_SIZE = 16; const int TEXT_FIELD_FONT_SIZE = 14; @@ -555,7 +555,7 @@ class ExportProjectWindow : EditorWindow { // Red error label text to display any issues under text fields and Browse button GUILayout.Label(errorLabel, errorStyle); - GUILayout.Space(25); + GUILayout.Space(20); // Export button which will verify project folder can actually be created // before closing popup window and calling back to initiate the export @@ -601,10 +601,10 @@ class ExportProjectWindow : EditorWindow { // when exporting, project name and location must both be defined, and project location must // be valid and accessible (we attempt to create the project folder at this time to verify this) if (projectName.Length == 0) { - errorLabel = "Please define a project name."; + errorLabel = "Please define a project name.\n"; return true; } else if (projectLocation.Length == 0) { - errorLabel = "Please define a project location."; + errorLabel = "Please define a project location.\n"; return true; } else { try { diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index dfebaa096138bda98ddad5720b4bc3f718294d2e..ebabe5dcc9dddbd0a457adee14b123d11d0a105a 100644 GIT binary patch literal 8794 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 RJUsu+=MVRhi9!Ih007!G+#LV_ From a3206c84f2d090c60ee588a59c863fe572455e55 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 21 Dec 2018 18:00:30 -0800 Subject: [PATCH 16/20] remove unneeded logs --- .../Assets/Editor/AvatarExporter.cs | 9 +-------- .../avatarExporter.unitypackage | Bin 8794 -> 8705 bytes 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 3499e020f5..895550fb0a 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -171,14 +171,7 @@ class AvatarExporter : MonoBehaviour { if (!SetJointMappingsAndParentNames()) { return; } - - //var textures = AssetDatabase.LoadAllAssetsAtPath(assetPath).Where(x => x.GetType() == typeof(Texture)); - var tests = AssetDatabase.LoadAllAssetsAtPath(assetPath); - Debug.Log("assetPath " + assetPath); - foreach (var test in tests) { - Debug.Log("test " + test.GetType()); - } - + string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); string hifiFolder = documentsFolder + "\\High Fidelity Projects"; if (updateAvatar) { // Update Existing Avatar menu option diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index ebabe5dcc9dddbd0a457adee14b123d11d0a105a..5be646c7f21b5c3b57d31e8a5f5104177d478680 100644 GIT binary patch literal 8705 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 Date: Tue, 25 Dec 2018 00:46:42 -0500 Subject: [PATCH 17/20] glsl initializer lists are 4.2 --- libraries/render-utils/src/simple.slf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index d1a70007a2..582549ade1 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -83,11 +83,11 @@ void main(void) { emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); #elif defined(PROCEDURAL_V3) || defined(PROCEDURAL_V4) #if defined(PROCEDURAL_V3) - ProceduralFragment proceduralData = { + ProceduralFragment proceduralData = ProceduralFragment( #else TransformCamera cam = getTransformCamera(); vec4 position = cam._viewInverse * _positionES; - ProceduralFragmentWithPosition proceduralData = { + ProceduralFragmentWithPosition proceduralData = ProceduralFragmentWithPosition( position.xyz, #endif normal, @@ -99,7 +99,7 @@ void main(void) { DEFAULT_METALLIC, DEFAULT_OCCLUSION, DEFAULT_SCATTERING - }; + ); #if defined(PROCEDURAL_V3) emissiveAmount = getProceduralFragment(proceduralData); From 65cc683f800b44afbdf970c9e4af96d6d3ec7714 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 26 Dec 2018 12:16:51 -0800 Subject: [PATCH 18/20] fix case-sensitivity on fbx --- .../Assets/Editor/AvatarExporter.cs | 2 +- .../avatarExporter.unitypackage | Bin 8705 -> 8745 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 895550fb0a..0483e191ff 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -158,7 +158,7 @@ class AvatarExporter : MonoBehaviour { assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); assetName = Path.GetFileNameWithoutExtension(assetPath); ModelImporter modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; - if (assetPath.LastIndexOf(".fbx") == -1 || modelImporter == null) { + if (assetPath.ToLower().LastIndexOf(".fbx") == -1 || modelImporter == null) { EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok"); return; } diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 5be646c7f21b5c3b57d31e8a5f5104177d478680..9562dca3804f67f5de5ba56e09904fe6dd627b19 100644 GIT binary patch literal 8745 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 T?$dqxZ>B#0f-f090JH!An_=EO literal 8705 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 Date: Thu, 27 Dec 2018 12:56:44 -0800 Subject: [PATCH 19/20] CR changes --- .../Assets/Editor/AvatarExporter.cs | 57 +++++++----------- .../avatarExporter.unitypackage | Bin 8745 -> 8716 bytes 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 0483e191ff..8e03dae00c 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -190,9 +190,13 @@ class AvatarExporter : MonoBehaviour { try { string[] lines = File.ReadAllLines(exportFstPath); foreach (string line in lines) { - if (line.StartsWith("name")) { - projectName = line.Substring(line.IndexOf("=") + 2); - break; + int separatorIndex = line.IndexOf("="); + if (separatorIndex >= 0) { + string key = line.Substring(0, separatorIndex).Trim(); + if (key == "name") { + projectName = line.Substring(separatorIndex + 1).Trim(); + break; + } } } } catch { @@ -201,17 +205,7 @@ class AvatarExporter : MonoBehaviour { return; } - // delete existing fst file since we will write a new file - // TODO: updating fst should only rewrite joint mappings and joint rotation offsets to existing file - try { - File.Delete(exportFstPath); - } catch { - EditorUtility.DisplayDialog("Error", "Failed to overwrite existing file " + exportFstPath + - ". Please check the file and try again.", "Ok"); - return; - } - - string exportModelPath = Path.GetDirectoryName(exportFstPath) + "/" + assetName + ".fbx"; + string exportModelPath = Path.GetDirectoryName(exportFstPath) + "\\" + assetName + ".fbx"; if (File.Exists(exportModelPath)) { // if the fbx in Unity Assets is newer than the fbx in the target export // folder or vice-versa then ask to replace the older fbx with the newer fbx @@ -234,13 +228,8 @@ class AvatarExporter : MonoBehaviour { if (option == 2) { // Cancel return; } else if (option == 0) { // Yes - copy model to Unity project - // delete existing fbx and associated meta file in Unity Assets - File.Delete(assetPath); - File.Delete(assetPath + ".meta"); - AssetDatabase.Refresh(); - - // copy the fbx from the project folder to Unity Assets and import it - File.Copy(exportModelPath, assetPath); + // copy the fbx from the project folder to Unity Assets, overwriting the existing fbx, and re-import it + File.Copy(exportModelPath, assetPath, true); AssetDatabase.ImportAsset(assetPath); // set model to Humanoid animation type and force another refresh on it to process Humanoid @@ -265,25 +254,26 @@ class AvatarExporter : MonoBehaviour { copyModelToExport = option == 0; // Yes } - // delete any existing fbx if we agreed to overwrite it, and copy asset fbx over + // copy asset fbx over deleting any existing fbx if we agreed to overwrite it if (copyModelToExport) { - if (File.Exists(exportModelPath)) { - try { - File.Delete(exportModelPath); - } catch { - EditorUtility.DisplayDialog("Error", "Failed to overwrite existing file " + exportModelPath + - ". Please check the file and try again.", "Ok"); - return; - } - } try { - File.Copy(assetPath, exportModelPath); + File.Copy(assetPath, exportModelPath, true); } catch { EditorUtility.DisplayDialog("Error", "Failed to copy existing file " + assetPath + " to " + exportModelPath + ". Please check the location and try again.", "Ok"); return; } } + + // delete existing fst file since we will write a new file + // TODO: updating fst should only rewrite joint mappings and joint rotation offsets to existing file + try { + File.Delete(exportFstPath); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to overwrite existing file " + exportFstPath + + ". Please check the file and try again.", "Ok"); + return; + } // write out a new fst file in place of the old file WriteFST(exportFstPath, projectName); @@ -326,9 +316,6 @@ class AvatarExporter : MonoBehaviour { // instantiate a game object of the user avatar to save out bone parents then destroy it UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); - if (!avatarResource) { - return false; - } GameObject assetGameObject = (GameObject)Instantiate(avatarResource); SetParentNames(assetGameObject.transform, userParentNames); DestroyImmediate(assetGameObject); diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 9562dca3804f67f5de5ba56e09904fe6dd627b19..953387223cf9eeca6372c08e724cb86b32e07ccf 100644 GIT binary patch literal 8716 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 literal 8745 zcmV+^BG%m>iwFpb;UioG0AX@tXmn+5a4vLVascfe+jiqLu+N;sKbSl~3fa_`*iKp& zPT6*$P|v>2DZ!!ju0w|98_g%+ihf^gm6mb?VrTAK0UjeL5KoMu9sF zCZXkpu01isze)ewj(M;D?Uf$iJ(`u{G{ zG@6Bvlf^}leI72-D2cZX`_)&@meFL}SS1^;zA{Z_lW;*cWZSq3(^p^F3#Y+ymK|Ko zlSLLTwqJe4Fv1t`6bG}Xr{9O;EZsJKeu>%3G+gWjS+H&38_C&w%Xl&ipMbleWquwk zq9D#Nf8##gT3W&5yYc@lx1#?CrgiWC-AQ8qZ)<-NWyxaWKKitdrA3Wase|M2q@}Erb#-BB}f#gpXHNEYY<%FK%}UK;Pnww{ES;=^YD)mQAk zaU9M7A0s;tizvW*K!_O;JDd|}2n&^)>H#wOm!s&+NC6i3x<)dktb;h6Lr$`gs6kzZ z5=R#Sl{~qchX!=vgXKjKC((o;j?a?>doZ8Rt^iG^Z+sDD=Ngh}7R_b=6M6s`_QaF% zL3DO*JdS{dKs(w0{THhLR2$$9^6m!)x?;vOSr9iW+%me^c?5mN)=ont?gUU){GCJ# z>g5#_AvA-G;B5kZpjrsk;4fkiK+k{#vt&%Ymkp|x$ni(q+`jQN9s_?f1DXyId+cMW zv}%}fOXgvW+;|LvfgPSD3xf6PDvD+6-3LX`NK`=kfNbI%7WdB%N4yL)R$WcOsD-9<>YalNU z(2!(4);q2M=z@Vm?Rhi~A3TRSUO>!|Qi4S;<}`4oB0oK6qReN(nEOKra08b@z!4Xb z#}cZffV%i6gy2o%i`}Cq2T$HN-hB0y1I8*?pgfZ>#8>%*(#YUxxd>B~+j+2nj!u=D zV*S*^PNq-@vA!Ze<76I%6H-Z-DWNc>nZVc&8aPU&RAk=>rhtTyctEVAKl}fhN$Ps` z|1XaJ4BU$ShiCKN{@+Dvi~p>W4bayxa*oG~Xnr5?`In|uJie3ok8M@vf6o2--yJ0p z|2f*<-TQdIpS}2#Q@#82FH2)Ta0aL0&^~p7&~*o4V2=Ee=ZvhusX26}e^dO&vHbh^ z&t0UI{Qs-tKk#kf+ByGS(?|X9c=!7MPSWqc{AGsq{!rut@|>m7$YB@M=7n76`Y5T;%{Gd(<0v~pDfO{W(+mm5+xrX)bs3OhLcX3T=KkoKR%1%aHo{V>mzyI@l~3I z7nRrk!P9EaqhvPYP*d7}Ka9ggG~U71#xodJ6`~v4#>YvVyceDWm2nRHvjOSVSMXnu z(JUGpz==r`&#r)a1TkTlsVt zPBY^G`2B(;Aeccp%KQLAD3tFs)_{M3Oc&uK0(sQx+0FGnStKu_i<-8qx;lqZmd(N% z^gfB=AFiC{Dkc21igt6w96_y8ErK6MlSwUt{K09e(}E#?wi|0I6!luQI3D33&@sep zr?I?_ovLLw)ygre)~Lnn9OQKPSRW4#$>^bd|pBj9_vd<7UWcfzn)G_Lm@4I@>L~5hSfd$7 z{91=$ML}tG>af#P6z3`()m*0;LHb;WVO34(wd!ybib_4}MRHZ8Ui3n_vQjU4p6wlLuMWIg)6HVv*}2q#0u*!Lj0P4|GZCuQ zcYVh<0jdwLZr`%(k?)2Nyc(EQ2ULe{0?I>}oFXi=+X~-mfIsx?kt5(+zRMu##qnIkpG(<; zK`s8u-Za2BP1j@a79i~A;76`w`U3pWg?8J)+fC$meA^b}cL%oX=Hz#WLti#F7&!Kz z6@DFgnB=ZK6!3ijk`vr_2A%|OyCcu51FuX@19^tNH5|HvKDIM3sR4R%O%prDr5tDI zv^UT|Aj`KrR}g4u`$Ou?1A2k|)wXQSgAm)Oy@4hHa^2BL3Md3?pfO2wh}PunaDm5l z0;()*ll*&*X$kNn-y68O{98UTn5E2eJ-ZEjqx`@Ca0Pz>VK*PXD4=;)+<|ZUZQz^4 z#|E5jfxQW>tr%NRU%MLR%B)WZyOi>ctw>BL3ODh31|Iirhl<_;u(F zKy@+EF?lrfnd*)+vMW608St$Soysg(htCFkIFbh2vOG)qO9PkGBRf?iXVebft^+?n zHsw%&B+Np30gWXUz2n&0=&a7U>%jw-BWaDmb4$W1^6LIk5>o2)muVEIsz*! zBpt?)`E(mV1L$${qHs2#LbkGbzVX6_q# z19O!7Mqo$r1NH*+no}!}e~ELHhiw`R(+H?Kv`4vnG#D1#BWi)XqXDS;W^U@R**mg* zC-;s(VHLb1IYaY7mWH)_Z1#@WA>y$kpOru4+SHJEcIsi*8fx~9fV8&f}GmHEhK|@wjJdS5wXOhK4n*Nu?B=;#%cOAYUmXPAIu+}f@9?6-l%KmmPn7U z>B0@X1W_ZkYLP;8ha5zLA?5o33(uF;rL#6Sgc3)8@_hk~lm`zW|7Ujp;B{Pkaw+q0vja(RHw7kRD}9eQ-lb*7pAz8mX2GTS2D0T*ZAgRIEQcm zTMN3K()Nli?c7Lb^I55p=Xhv^OFltY{kc8aZE*&mnlzoR?yerVCoR4V3 zg^R9@NVZrO@zCJa*E&s-nXxRi1PxIed250}-#jyL(=!SKfk3kNiD~bDcCeT3dZ$!^-h*EcNiAVRETT;;kj0Wf2mv59>Z~?&4}M3TDY!x3j-kB(zHL z$t(ozEsqWbF$)wP)DTBku+Y;VI!q#!UK$}V@YdQjaCgfrT)Xx1>mr167AVd7_tF^J z#b?|pehO{(kpU;kuKeDufmNHdy~KAm zGJg4`iUR1Y?FaH49UY^4twO-v;@3WD!#~@xjC=D& z+I)ns=|EtVL9ObEz?+akraVW}>mW&GI%$euR9AF;?KsRnOyKB}w7$FXq+l?+n?SkM zC|0$fF-gYD3jmTn=EF@7III0X223I)c^QuPeR??(ONu@ zrjcw+YyK^x^X<3g19+eC>8rF;8Aw&-zO~hGNRN;3B}CUUg~&*ZVe=wMuM)!7&tvGF(@gYPm87p8{g2Q$GpfaMtaJQTQUH6KGoE|!`qnoMRX5diTQ!*m?L5;bhCoG_$Tao0zs*0FNyM||?J`^MHA zJ>!jUzkOqK6^~{~^22goJR~U2=|&-({G7rGOZp~Hg|?GjfT%c&DrJkS;t0qfE(0S_z(59BQY=;eVTt}RNLH4jeeHIz{~e)> z>Qkkk3}Jc7O*6pU7(VQ5f=;j%&U!ip)PJacSL|Pr^%6uX8=v3YPkJ0gGd^w&crefN z^SR9Qj`3DS@ZLg6x<=~s4PoHM=MdIMnPSioOChlc&Vndbrs`&cLzC_a47*I7HdbPs zMtJ7;GUNljmy1Z=B0!%tI44i{o^F%i=U`Hbco?`d1#n{s-=lRiE`)i*zKN^_OxT2x zo~9wGv@(g;8YeU?_NYtM0r8iKL;~losdN7cktkUQGMR+B^0aen2t&I{WHjwt&~N3U z!!tX(HD&>sB~5CjJ=84%v5K5Ue5xKmg6=9ErLUf$SEbaY2clnfkcDwApQgvEI2-$+ z_Ru`SFP)j^EFJnDSc8-3f@fCT%P$rY=Luzwq{S&ho?o1~G(zNltqrn7Wd*p(m^v)q zscosz1mKWPgd5y8q8`CHk6HMlFtAR5lqC^bZ`gFyEJv_>&3hKGtpZ)vAE+H}t^aNO zZM>K8^$KvLxn5HGRZ$Un?{<3YH1!p4Qhbu&zmLd2%Ex;h2PNqr8n*DUxRR=|mZm6b z3_aC))v9H42ipbAxw0O&Xq2_L?^@RW4F9VlrHk*Hve}1f>oohPcDgu z1aPg+DhV|H9uZU`QxGu$8IUs`kT?n|%S(%-8(N0B!UYm!LKY_Dh>pkM4H;y5t42ow zZgxdVs{EQ2NP4jKPD|BFNbM+`!hI*_-L@KS1R+97`za>MbuV0z=J$q0udQfeHg zX%3>X(OV8ZR#RJ;ZgrHd8E{3(9+Oj;U6sRvn`96S#p&~+5D_|(enEd|*`T2CAn z;UuY>u*@kUXT-FIHCf_}KOZe)umNXbGuTxDzV!|8%XOCm*lCUv;{s8DX`@>SVFR^bED*z zLAhIFQ4M?Y`x&ptqIK28%bKyyLI}&JGSHW1xzo!NjASb37&S3DnyEcLJ}C!l_jK=izM;`t^z`Kn8t5p%w%0Jz1FtqBeognG**@PUXA>{ zSo=EBIJra=81rPloQw5JijCrNP~s9(ySO$%qgrOO}RV#!7fGhc2AtH)_TcW^&#kbOqP! zk-Sk;bwKYEFY?Ng9estE9rQI|a6tmGdYEabZLU?VicFQ7x`sqj$?HI+n!K(|2nE^D za|Il`enTBgPij7MkvwNtZjvldag7IVsyPpl^^&QDi(K(;KoXS8V8OanT1Nef*0Oh3 zCn0)Xz7*T}7805@_!p61%V>WANC7Fa+M2h>Vl)g3F67m*>&4s3sWT@tEfoc*o4f(x z59l402D?$C(H@1BQ^SI)s_vxhKLV^4MGf4%qf-`c6qAUwd=~Mw(r2iY60!oq`V(j& z`?T>Oc+NxO6q*y0^=}|-` znRa!Uo1%NlpqCqI*hr%`h3MA#eV~Lum@rW9fN@v7Z5}8Bb}Owa$jI&@dn+LO*&>Kj zh%;UEs?08_G$!!Dg}4}^3Z{|CDF|kDHI`Rup-s$Z9tOfeLJfg7ob&+<^=Jx_)+nXX zwg`V*f~ZeKE!7MO9?IZ1&qy{v0F8%Q1cxIwx`7zu4G5pH6!TUIy-WkDBVuGIsvt`)L zk?P^#D}yahh}TdFyOT**qTN#jXo_4EDD=<}SBW*v!8JxUkVj1k^d{(_V`B>>25UCZ zG6{NFQ5bC+G>NkS^dxhFZ>`w8;jYGF01N?1Db0UUK&Z!j-gjNSb#rN7Q7J^Fb?ca-1Jw|P z>r-uL@mUFY;a5+_419bpdqeWm+H!^H#7rO^i!;4j?2Te(M2?@Y*Wydrh1> zoxg4m8@x8t_BCu98MMyG08$p}ilL#C3?NmHLEXx#zO?zgZ7cTlW~98{|3p$2T>5ESpn7Msbof9(?_fB zd$E#|b$W%oh84t$YAY$BV*5h*=xuE7Tub(f;1#f@MyPs|&r>Nz1R_FSYuxEbDBfP9 zUofU>`%@B{wfvv5P!-Lf_+_J53XK{jaItUESgJ29b(bM4O(}^b0Nmm&3<;n*>kCa} z1?=kx$~PrQ&y0s9O0G4!#gkGDc&2TqJ&LKqIdy@dl>y7T1CJ}G_bP-nP8MPpJH{^H zzd5LqJm@bfspT4NnTC*u{pT1}tj41{vDn~lyj4V>LV7XG+SyN^T)lM;8>+vJ({V7v zEbH4C+m@BrSWD3&r{BgMe1s?rgt7{8C&)*dXbVQhP2AjkrHILS8ykNA{1T9U7HFji zlmh_RAI1J!XZ8C+gbK`M%a8&|T|NH={;sLdy9A zyeh(MU`}3~d6U2PV||99j!(;CxehAUl)c|6)?H%*I=d`lX0xsbAzM{p5e261U=n}L z=QE2WDaSE%%P0V%;lt9HMg1r9_ePpCTY+d()?~=bSZl;w|I9BSNgL8GBlv!SXUO1} z3#*91P5+_~@=G9)x+UDTa^EN_KQrE3-M!Op087P z)8cO-g0ffy@cRU%?@?TgZ#RiNH~y5GG#*y(Y}l-i{FNhi^CgA*ODDl@ID6N`K5wet zyA;i@tA!=7T=~}1M6u#w%T2T26xwcpSs|`!0oIc7R0elghg>AXvrXg89HH6~BnYen zDm&~F=yix$th3WNfr7QS>`27US)tLnjd*gXeL!G2CT)RP9cGPU`%B0T2wg8Q14B6r zeh9^iX^6jyk%~c#W>hJ#2s9Nh%8!=|>=L;}a3oF4HO>QC)a2pJo#7VOruzrr609)} zTBZ*=%x6-tTLz3?>u9YZ)ZBw-JAWaIjW?SyUT?=yt71d{p#ZM(P@x#ql!y%P=m_sV}v_AV1vp3PjUTc0$iC@*u zsM0UYw(QD*VD1=aX)*l%ZQ)$Gyaxg1cV}^u!n8c?f5Myh`Mh(tBhTgc!xwa8`xXZX zd)j2L?fRPPV(*Du+~utZ6uW7=IB8zL7(l*G7sWg>a9c0@d0Rp3Q}Ff?1imx)^442b zX|5urDuQD{LDz1y*(?%w+&twEStbJH<&Tm z+iWqHK!YT+N*xuSA?mQ4!DuX&D`0SWj@y0#|KZo`vNZf`2bKGxaLmX_Yd|D-v8hP z3fMIT-uvw2|9zO-6e0=crKE{kZt<>S}d;5p03Z3kKdGg)kgZ;z3a>;>Ka{uV) z=~20monIKX`fi8v*FzE0(M#yt!1N*^MxYLZ({P3#L3x(&n8U8FXiR(d&%h?hoszUI za*s2ww>^@fS3wvFzds6Qvs3uJxEdU|Y1;d#->K5m0FH~`O4580#mCW4p)j?d6RvhQ zm!n=mWQwCZi{MvoaHOCL!4M#lOTR`<)zH~f#8{Q9_8rQVkS~;k8y_!4AhBDu4w5x+ zzg51oOC7nG;$qup2f&f5S;$jb0B^hxSwOM;%9qnn{)%~CAIls=Eeg^@tOuV3@$yey zcz_5J1QNMSfo{cG{V9--X|D~totg$`D3$H)QITSElu>Pshi`jh$kO(Y^!JEpNMQO6Ma(UVVqu%hhWJN8yCWItvkTADKDf6(`!m3>z$$ zVJdz>Rmm#NbjxDUEWXNN=y7}37+QAo(BeTMT1QWId`=eyiOr$a!{TGS({h|G!HQiK z%vK2|a>7Lh-CtEQl=N1|Exu4p9n%7#r2Z>$Kx})xPQ?rxS5kw3=U(!bmA{tb0NnymP!5j*pCz!A1M4Y!{qK z=e2#gw=e+VoKk$Za&RwMs7gf+=zB}n-*Z;sc?GRZQ{TU%VZ+p#u+L9$xeL8|xVcQ{LEqV+|3|T*L^M z0G|i5Xwo-U^Q^$&+v1i|06KGc%_A?-(++*+2+_dfbbX!=v_=dJq5@S3 z#uyTfPBC1Cr~ZLi7jslPVw~3855r>ssF~3%Wr>g+%G>1D8nnyGJGILkWG2Z;b5J75 z_1Pkp2K7;MOIY1pU_{vSTBqq7PdsYs@&;$>?{XlOX|vwR4%H3utnb-XLCuZ-WE%T{ zGdK;0_Nfzu@M|k!V2{DYjxr-}Fc>WJ85-o-v$oyrkvI z#RNbbCT2!stoU;z?ie`OgM0)}OBi?-beqO8N$h(tCFcA7wam{0Kqk(%jsNPHzD`=j z<1LH=p`qNkd1@U0N&)cr_dWQrR}05KWZjSdyGVRx#TLigws_5B|An|IZJci0r~7oD T?$dqxZ>B#0f-f090JH!An_=EO From b0ae323d52c7fa6c55746817308b51feee2cb4e8 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 27 Dec 2018 13:07:33 -0800 Subject: [PATCH 20/20] better extension check --- .../Assets/Editor/AvatarExporter.cs | 2 +- .../avatarExporter.unitypackage | Bin 8716 -> 8711 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 8e03dae00c..18916267f0 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -158,7 +158,7 @@ class AvatarExporter : MonoBehaviour { assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); assetName = Path.GetFileNameWithoutExtension(assetPath); ModelImporter modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; - if (assetPath.ToLower().LastIndexOf(".fbx") == -1 || modelImporter == null) { + if (Path.GetExtension(assetPath).ToLower() != ".fbx" || modelImporter == null) { EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok"); return; } diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 953387223cf9eeca6372c08e724cb86b32e07ccf..f333aecb1299cfa8553a0d4a8d9d657e9ef36bec 100644 GIT binary patch literal 8711 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 literal 8716 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