From fee0a5f071a6e1833ee236b85cab40c696b9e75b Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 7 Dec 2017 13:28:34 -0500 Subject: [PATCH] Minor bug fixes and code cleanup. --- .../Editor/Importer/AlembicImporter.cs | 20 +- .../Editor/Importer/AlembicImporterEditor.cs | 24 +- .../Importer/AlembicStreamPlayerEditor.cs | 4 +- .../Alembic/Scripts/Importer/AlembicStream.cs | 4 + .../Importer/AlembicStreamDescriptor.cs | 4 +- .../ProjectSettings/ProjectSettings.asset | Bin 52858 -> 56967 bytes .../ProjectSettings/ProjectVersion.txt | 2 +- Plugin/abci/Foundation/aiLogger.cpp | 8 +- Plugin/abci/Foundation/aiLogger.h | 245 ++++++++------- Plugin/abci/Foundation/aiThreadPool.cpp | 4 +- Plugin/abci/Foundation/aiThreadPool.h | 2 - Plugin/abci/Importer/aiContext.cpp | 4 +- Plugin/abci/Importer/aiContext.h | 3 +- Plugin/abci/Importer/aiObject.cpp | 10 +- Plugin/abci/Importer/aiObject.h | 8 +- Plugin/abci/Importer/aiPoints.cpp | 8 +- Plugin/abci/Importer/aiPolyMesh.cpp | 280 +++++++++--------- Plugin/abci/Importer/aiPolyMesh.h | 13 +- Plugin/abci/Importer/aiSchema.h | 2 +- 19 files changed, 333 insertions(+), 312 deletions(-) diff --git a/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporter.cs b/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporter.cs index 43a4d77ba..86330daac 100644 --- a/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporter.cs +++ b/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporter.cs @@ -70,13 +70,14 @@ public class AlembicImporter : ScriptedImporter { [SerializeField] public AlembicStreamSettings streamSettings = new AlembicStreamSettings(); [SerializeField] public float scaleFactor = 0.01f; - [SerializeField] public int startFrame =-1; - [SerializeField] public int endFrame =-1; + [SerializeField] public int startFrame = int.MinValue; + [SerializeField] public int endFrame = int.MaxValue; [SerializeField] public float AbcStartTime; [SerializeField] public float AbcEndTime; [SerializeField] public int AbcFrameCount; [SerializeField] public string importWarning; [SerializeField] public List varyingTopologyMeshNames = new List(); + [SerializeField] public List splittingMeshNames = new List(); public override void OnImportAsset(AssetImportContext ctx) { @@ -98,7 +99,7 @@ public override void OnImportAsset(AssetImportContext ctx) go.transform.localScale *= scaleFactor; AlembicStreamDescriptor streamDescriptor = ScriptableObject.CreateInstance(); - streamDescriptor.name = go.name + "ABCDesc"; + streamDescriptor.name = go.name + "_ABCDesc"; streamDescriptor.pathToAbc = destPath; streamDescriptor.settings = streamSettings; @@ -109,8 +110,8 @@ public override void OnImportAsset(AssetImportContext ctx) AbcEndTime = abcStream.AbcEndTime; AbcFrameCount = abcStream.AbcFrameCount; - startFrame = startFrame ==-1 ? 0 : startFrame; - endFrame = endFrame ==-1 ? AbcFrameCount : endFrame; + startFrame = startFrame < 0 ? 0 : startFrame; + endFrame = endFrame > AbcFrameCount-1 ? AbcFrameCount-1 : endFrame; streamDescriptor.minFrame = startFrame; streamDescriptor.maxFrame = endFrame; @@ -156,10 +157,13 @@ private void GenerateSubAssets( AssetImportContext ctx,AlembicTreeNode root,Alem AnimationCurve curve = new AnimationCurve(frames); var animationClip = new AnimationClip(); animationClip.SetCurve("",typeof(AlembicStreamPlayer),"currentTime",curve); - animationClip.name = "Default Animation"; + animationClip.name = root.linkedGameObj.name + "_Clip"; AddObjectToAsset(ctx,"Default Animation", animationClip); } + varyingTopologyMeshNames = new List(); + splittingMeshNames = new List(); + CollectSubAssets(ctx, root, material); streamDescr.hasVaryingTopology = varyingTopologyMeshNames.Count > 0; @@ -175,6 +179,10 @@ private void CollectSubAssets(AssetImportContext ctx, AlembicTreeNode node, Mat { varyingTopologyMeshNames.Add(node.linkedGameObj.name); } + else if (streamSettings.shareVertices && mesh.sampleSummary.splitCount > 1) + { + splittingMeshNames.Add(node.linkedGameObj.name); + } } var meshFilter = node.linkedGameObj.GetComponent(); diff --git a/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporterEditor.cs b/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporterEditor.cs index cf97bedc8..8ff1354db 100644 --- a/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporterEditor.cs +++ b/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicImporterEditor.cs @@ -22,17 +22,29 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(serializedObject.FindProperty("streamSettings.turnQuadEdges")); EditorGUILayout.PropertyField(serializedObject.FindProperty("streamSettings.shareVertices")); + var splittingMeshes = serializedObject.FindProperty("splittingMeshNames"); + if (splittingMeshes.arraySize>0) + { + string message = "Meshes with shared vertices cannot be split."; + if (!splittingMeshes.hasMultipleDifferentValues) + { + message += "The following meshes won't be affected : \n" + string.Join(",", importer.splittingMeshNames.ToArray()) + "."; + } + EditorGUILayout.HelpBox(message,MessageType.Info); + } EditorGUILayout.PropertyField(serializedObject.FindProperty("streamSettings.treatVertexExtraDataAsStatics")); var varyingTopologyMeshes = serializedObject.FindProperty("varyingTopologyMeshNames"); if (varyingTopologyMeshes.arraySize>0) { - string message = "'Merge vertices' and 'Vertex extra data is static' do not apply to meshes with varying topology."; + string message = "'Share vertices' and 'Vertex extra data is static' do not apply to meshes with varying topology."; if (!varyingTopologyMeshes.hasMultipleDifferentValues) { - message = "The following meshes won't be affected : \n" + string.Join(",", importer.varyingTopologyMeshNames.ToArray()) + "."; + message += "The following meshes won't be affected : \n" + string.Join(",", importer.varyingTopologyMeshNames.ToArray()) + "."; } EditorGUILayout.HelpBox(message,MessageType.Info); } + + EditorGUILayout.PropertyField(serializedObject.FindProperty("streamSettings.use32BitsIndexBuffer")); #if !UNITY_2017_3_OR_NEWER @@ -47,14 +59,14 @@ public override void OnInspectorGUI() var abcFrameCount = serializedObject.FindProperty("AbcFrameCount"); var startFrame = serializedObject.FindProperty("startFrame"); var endFrame = serializedObject.FindProperty("endFrame"); - var frameLength = (abcFrameCount.intValue == 0) ? 0 : (abcEndTime.floatValue - abcStartTime.floatValue) / abcFrameCount.intValue; - var frameRate = (abcFrameCount.intValue == 0) ? 0 : (int)(1.0f/ frameLength); + var frameLength = (abcFrameCount.intValue == 1) ? 0 : (abcEndTime.floatValue - abcStartTime.floatValue) / (abcFrameCount.intValue-1); + var frameRate = (abcFrameCount.intValue == 1) ? 0 : (int)(1.0f/ frameLength); float startFrameVal = startFrame.intValue; float endFrameVal = endFrame.intValue; EditorGUI.BeginDisabledGroup(abcStartTime.hasMultipleDifferentValues || abcEndTime.hasMultipleDifferentValues || abcFrameCount.hasMultipleDifferentValues); EditorGUI.BeginChangeCheck(); - EditorGUILayout.MinMaxSlider("Time range",ref startFrameVal,ref endFrameVal,0,abcFrameCount.intValue); + EditorGUILayout.MinMaxSlider("Time range",ref startFrameVal,ref endFrameVal,0,abcFrameCount.intValue-1); startFrameVal = (float)Math.Floor(startFrameVal); endFrameVal = (float)Math.Floor(endFrameVal); @@ -96,7 +108,7 @@ public override void OnInspectorGUI() style.alignment = TextAnchor.LowerRight; if (!endFrame.hasMultipleDifferentValues && !startFrame.hasMultipleDifferentValues && !abcFrameCount.hasMultipleDifferentValues) { - EditorGUILayout.LabelField(new GUIContent(duration.ToString("0.00") +"s at " + frameRate + "fps (" + (frameCount+1) + " frames)"),style); + EditorGUILayout.LabelField(new GUIContent(duration.ToString("0.000") +"s at " + frameRate + "fps (" + (frameCount+1) + " frames)"),style); EditorGUILayout.LabelField(new GUIContent("frame " + startFrameVal.ToString("0") + " to " + endFrameVal.ToString("0")),style); } else diff --git a/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicStreamPlayerEditor.cs b/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicStreamPlayerEditor.cs index 690b827fe..b603d7231 100644 --- a/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicStreamPlayerEditor.cs +++ b/AlembicImporter/Assets/UTJ/Alembic/Editor/Importer/AlembicStreamPlayerEditor.cs @@ -20,7 +20,7 @@ public override void OnInspectorGUI() var minFrame = targetStreamDesc.minFrame; var maxFrame = targetStreamDesc.maxFrame; var frameLength = targetStreamDesc.FrameLength; - var frameRate = 1.0f / frameLength; + var frameRate = frameLength==0.0f ? 0.0f : 1.0f / frameLength; var hasVaryingTopology= false; var hasAcyclicFramerate = false; var multipleFramerates = false; @@ -99,7 +99,7 @@ public override void OnInspectorGUI() { int numFrames = (int)(endFrameVal - startFrameVal); float duration = numFrames * frameLength; - EditorGUILayout.LabelField(new GUIContent(duration.ToString("0.00") + "s at " + frameRate + "fps (" + (numFrames + 1) + " frames).", "Frame rate"), style); + EditorGUILayout.LabelField(new GUIContent(duration.ToString("0.000") + "s at " + frameRate + "fps (" + (numFrames+1) + " frames).", "Frame rate"), style); } else { diff --git a/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStream.cs b/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStream.cs index c03a678c9..38cfef40d 100644 --- a/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStream.cs +++ b/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStream.cs @@ -171,7 +171,11 @@ public void AbcLoad(bool createMissingNodes=false) m_Config.turnQuadEdges = settings.turnQuadEdges; m_Config.aspectRatio = AbcAPI.GetAspectRatio(settings.aspectRatioMode); m_Config.cacheTangentsSplits = true; +#if !UNITY_2017_3_OR_NEWER m_Config.use32BitsIndexBuffer = settings.use32BitsIndexBuffer; +#else + m_Config.use32BitsIndexBuffer = false; +#endif AbcAPI.aiSetConfig(m_Context, ref m_Config); AbcAPI.UpdateAbcTree(m_Context, alembicTreeRoot, m_Time, createMissingNodes); diff --git a/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStreamDescriptor.cs b/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStreamDescriptor.cs index 96a75b190..90ce52000 100644 --- a/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStreamDescriptor.cs +++ b/AlembicImporter/Assets/UTJ/Alembic/Scripts/Importer/AlembicStreamDescriptor.cs @@ -27,8 +27,8 @@ public float FrameLength { get { - if (abcFrameCount == 0) return 0; - return abcDuration / abcFrameCount; + if (abcFrameCount == 1) return 0; + return abcDuration / (abcFrameCount-1); } } } diff --git a/AlembicImporter/ProjectSettings/ProjectSettings.asset b/AlembicImporter/ProjectSettings/ProjectSettings.asset index 78d34872b978899e583c8c9b8214819f1dfac8e9..5dc33d7c8afb41eeb8698f70f30a33107fc03ba2 100644 GIT binary patch literal 56967 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 diff --git a/AlembicImporter/ProjectSettings/ProjectVersion.txt b/AlembicImporter/ProjectSettings/ProjectVersion.txt index ada1fbd90..f0f965c4b 100644 --- a/AlembicImporter/ProjectSettings/ProjectVersion.txt +++ b/AlembicImporter/ProjectSettings/ProjectVersion.txt @@ -1 +1 @@ -m_EditorVersion: 2017.3.0b11 +m_EditorVersion: 2018.1.0b1 diff --git a/Plugin/abci/Foundation/aiLogger.cpp b/Plugin/abci/Foundation/aiLogger.cpp index d5a46910e..b602755f5 100644 --- a/Plugin/abci/Foundation/aiLogger.cpp +++ b/Plugin/abci/Foundation/aiLogger.cpp @@ -115,7 +115,7 @@ void aiLogger::_indent() } } -void aiLogger::warning(const char *fmt, va_list args) +void aiLogger::warning(const char *fmt, va_list args) const { if (isOpened()) { @@ -126,7 +126,7 @@ void aiLogger::warning(const char *fmt, va_list args) } } -void aiLogger::error(const char *fmt, va_list args) +void aiLogger::error(const char *fmt, va_list args) const { if (isOpened()) { @@ -137,7 +137,7 @@ void aiLogger::error(const char *fmt, va_list args) } } -void aiLogger::info(const char *fmt, va_list args) +void aiLogger::info(const char *fmt, va_list args) const { if (isOpened()) { @@ -148,7 +148,7 @@ void aiLogger::info(const char *fmt, va_list args) } } -void aiLogger::debug(const char *fmt, va_list args) +void aiLogger::debug(const char *fmt, va_list args) const { if (isOpened()) { diff --git a/Plugin/abci/Foundation/aiLogger.h b/Plugin/abci/Foundation/aiLogger.h index 8603b46b3..c4deae365 100644 --- a/Plugin/abci/Foundation/aiLogger.h +++ b/Plugin/abci/Foundation/aiLogger.h @@ -1,125 +1,122 @@ #pragma once - -#include -#include -#include - -class aiLogger -{ -public: - - static inline void Enable(bool on, const char *path) - { - if (on) - { - msInstance.open(path); - } - else - { - msInstance.close(); - } - } - - static inline void Indent(int level=1) - { - msInstance.indent(level); - } - - static inline void Unindent(int level=1) - { - msInstance.unindent(level); - } - - static inline void Warning(const char *fmt, ...) - { - if (!msInstance.isOpened()) - { - return; - } - - va_list args; - va_start(args, fmt); - - msInstance.warning(fmt, args); - - va_end(args); - } - - static inline void Error(const char *fmt, ...) - { - if (!msInstance.isOpened()) - { - return; - } - - va_list args; - va_start(args, fmt); - - msInstance.error(fmt, args); - - va_end(args); - } - - static inline void Info(const char *fmt, ...) - { - if (!msInstance.isOpened()) - { - return; - } - - va_list args; - va_start(args, fmt); - - msInstance.info(fmt, args); - - va_end(args); - } - - static inline void Debug(const char *fmt, ...) - { - if (!msInstance.isOpened()) - { - return; - } - - va_list args; - va_start(args, fmt); - - msInstance.debug(fmt, args); - - va_end(args); - } - -private: - - aiLogger(); - ~aiLogger(); - - void open(const char *path); - void close(); - - bool isOpened() const; - - void indent(int level); - void unindent(int level); - - void warning(const char *fmt, va_list args); - void error(const char *fmt, va_list args); - void info(const char *fmt, va_list args); - void debug(const char *fmt, va_list args); - -private: - - void _openDebug(); - void _close(); - void _indent(); - - std::string mPath; - FILE *mFile; - int mIndentLevel; - std::string mIndentString; - -private: - - static aiLogger msInstance; -}; +#include + +class aiLogger +{ +public: + + static inline void Enable(bool on, const char *path) + { + if (on) + { + msInstance.open(path); + } + else + { + msInstance.close(); + } + } + + static inline void Indent(int level=1) + { + msInstance.indent(level); + } + + static inline void Unindent(int level=1) + { + msInstance.unindent(level); + } + + static inline void Warning(const char *fmt, ...) + { + if (!msInstance.isOpened()) + { + return; + } + + va_list args; + va_start(args, fmt); + + msInstance.warning(fmt, args); + + va_end(args); + } + + static inline void Error(const char *fmt, ...) + { + if (!msInstance.isOpened()) + { + return; + } + + va_list args; + va_start(args, fmt); + + msInstance.error(fmt, args); + + va_end(args); + } + + static inline void Info(const char *fmt, ...) + { + if (!msInstance.isOpened()) + { + return; + } + + va_list args; + va_start(args, fmt); + + msInstance.info(fmt, args); + + va_end(args); + } + + static inline void Debug(const char *fmt, ...) + { + if (!msInstance.isOpened()) + { + return; + } + + va_list args; + va_start(args, fmt); + + msInstance.debug(fmt, args); + + va_end(args); + } + +private: + + aiLogger(); + ~aiLogger(); + + void open(const char *path); + void close(); + + bool isOpened() const; + + void indent(int level); + void unindent(int level); + + void warning(const char *fmt, va_list args) const; + void error(const char *fmt, va_list args) const; + void info(const char *fmt, va_list args) const; + void debug(const char *fmt, va_list args) const; + +private: + + void _openDebug(); + void _close(); + void _indent(); + + std::string mPath; + FILE *mFile; + int mIndentLevel; + std::string mIndentString; + +private: + + static aiLogger msInstance; +}; diff --git a/Plugin/abci/Foundation/aiThreadPool.cpp b/Plugin/abci/Foundation/aiThreadPool.cpp index 7ef73f23c..fde0e5b90 100644 --- a/Plugin/abci/Foundation/aiThreadPool.cpp +++ b/Plugin/abci/Foundation/aiThreadPool.cpp @@ -8,7 +8,7 @@ class aiWorkerThread { public: aiWorkerThread(aiThreadPool *pool); - void operator()(); + void operator()() const; private: aiThreadPool *m_pool; @@ -20,7 +20,7 @@ aiWorkerThread::aiWorkerThread(aiThreadPool *pool) { } -void aiWorkerThread::operator()() +void aiWorkerThread::operator()() const { std::function task; diff --git a/Plugin/abci/Foundation/aiThreadPool.h b/Plugin/abci/Foundation/aiThreadPool.h index 427933214..c4352c1c5 100644 --- a/Plugin/abci/Foundation/aiThreadPool.h +++ b/Plugin/abci/Foundation/aiThreadPool.h @@ -6,8 +6,6 @@ #include #include #include -#include -#include class aiWorkerThread; class aiThreadPool; diff --git a/Plugin/abci/Importer/aiContext.cpp b/Plugin/abci/Importer/aiContext.cpp index db6533cde..dbc2b78bd 100644 --- a/Plugin/abci/Importer/aiContext.cpp +++ b/Plugin/abci/Importer/aiContext.cpp @@ -187,7 +187,7 @@ void aiContext::setConfig(const aiConfig &config) m_config = config; } -void aiContext::gatherNodesRecursive(aiObject *n) const +void aiContext::gatherNodesRecursive(aiObject *n) { abcObject &abc = n->getAbcObject(); size_t numChildren = abc.getNumChildren(); @@ -368,7 +368,7 @@ float aiContext::getEndTime() const unsigned int aiContext::getFrameCount() const { - return m_numFrames -1; + return m_numFrames; } aiObject* aiContext::getTopObject() const diff --git a/Plugin/abci/Importer/aiContext.h b/Plugin/abci/Importer/aiContext.h index b7166f0d9..359357de5 100644 --- a/Plugin/abci/Importer/aiContext.h +++ b/Plugin/abci/Importer/aiContext.h @@ -1,6 +1,5 @@ #pragma once #include "aiThreadPool.h" -#include "aiMisc.h" typedef AbcGeom::IObject abcObject; typedef AbcGeom::IXform abcXForm; @@ -77,8 +76,8 @@ class aiContext static std::string normalizePath(const char *path); private: + static void gatherNodesRecursive(aiObject *n); void reset(); - void gatherNodesRecursive(aiObject *n) const; std::string m_path; Abc::IArchive m_archive; diff --git a/Plugin/abci/Importer/aiObject.cpp b/Plugin/abci/Importer/aiObject.cpp index 3a3395a7e..9b66608f7 100644 --- a/Plugin/abci/Importer/aiObject.cpp +++ b/Plugin/abci/Importer/aiObject.cpp @@ -71,7 +71,7 @@ void aiObject::removeChild(aiObject *c) auto it = std::find(m_children.begin(), m_children.end(), c); if (it != m_children.end()) { - c->m_parent = 0; + c->m_parent = nullptr; m_children.erase(it); } } @@ -109,9 +109,9 @@ void aiObject::cacheSamples(int64_t startIndex, int64_t endIndex) } } -aiXForm* aiObject::getXForm() { return m_xform.get(); } -aiPolyMesh* aiObject::getPolyMesh() { return m_polymesh.get(); } -aiCamera* aiObject::getCamera() { return m_camera.get(); } -aiPoints* aiObject::getPoints() { return m_points.get(); } +aiXForm* aiObject::getXForm() const { return m_xform.get(); } +aiPolyMesh* aiObject::getPolyMesh() const { return m_polymesh.get(); } +aiCamera* aiObject::getCamera() const { return m_camera.get(); } +aiPoints* aiObject::getPoints() const { return m_points.get(); } diff --git a/Plugin/abci/Importer/aiObject.h b/Plugin/abci/Importer/aiObject.h index feb275b41..ab122ae5b 100644 --- a/Plugin/abci/Importer/aiObject.h +++ b/Plugin/abci/Importer/aiObject.h @@ -21,10 +21,10 @@ class aiObject void readConfig(); void updateSample(const abcSampleSelector& ss); - aiXForm* getXForm(); - aiPolyMesh* getPolyMesh(); - aiCamera* getCamera(); - aiPoints* getPoints(); + aiXForm* getXForm() const; + aiPolyMesh* getPolyMesh() const; + aiCamera* getCamera() const; + aiPoints* getPoints() const; template void eachChildren(const F &f) diff --git a/Plugin/abci/Importer/aiPoints.cpp b/Plugin/abci/Importer/aiPoints.cpp index 415db9c7d..39797179d 100644 --- a/Plugin/abci/Importer/aiPoints.cpp +++ b/Plugin/abci/Importer/aiPoints.cpp @@ -1,9 +1,10 @@ #include "pch.h" -#include "aiInternal.h" +#include "aiInternal.h" #include "aiContext.h" #include "aiObject.h" #include "aiSchema.h" #include "aiPoints.h" +#include "aiMisc.h" // --- @@ -43,7 +44,6 @@ void aiPointsSample::getDataPointer(aiPointsData &data) } if (m_velocities) { m_tmp_velocities.resize(count); - int v_count = std::min(count, (int)m_velocities->size()); for (int i = 0; i < count; ++i) { m_tmp_velocities[i] = (*m_velocities)[m_sort_data[i].second]; } @@ -51,7 +51,6 @@ void aiPointsSample::getDataPointer(aiPointsData &data) } if (m_ids) { m_tmp_ids.resize(count); - int v_count = std::min(count, (int)m_ids->size()); for (int i = 0; i < count; ++i) { m_tmp_ids[i] = (*m_ids)[m_sort_data[i].second]; } @@ -189,8 +188,7 @@ aiPoints::Sample* aiPoints::newSample() aiPoints::Sample* aiPoints::readSample(const uint64_t idx, bool &topologyChanged) { - auto ss = aiIndexToSampleSelector(idx); - auto ss2 = aiIndexToSampleSelector(idx + 1); + auto ss = aiIndexToSampleSelector(idx); DebugLog("aiPoints::readSample(t=%d)", idx); Sample *ret = newSample(); diff --git a/Plugin/abci/Importer/aiPolyMesh.cpp b/Plugin/abci/Importer/aiPolyMesh.cpp index 7e869709d..4efee1a7f 100644 --- a/Plugin/abci/Importer/aiPolyMesh.cpp +++ b/Plugin/abci/Importer/aiPolyMesh.cpp @@ -5,6 +5,7 @@ #include "aiSchema.h" #include "aiPolyMesh.h" #include +#include "../../Tools/Common/Foundation.h" #define MAX_VERTEX_SPLIT_COUNT_16 65000 @@ -30,15 +31,15 @@ Topology::Topology() , m_TreatVertexExtraDataAsStatic(false) , m_use32BitsIndexBuffer(false) { - m_indices.reset(); - m_counts.reset(); + m_faceIndices.reset(); + m_vertexCountPerFace.reset(); } void Topology::clear() { aiLogger::Info("Topology::clear()"); - m_indices.reset(); - m_counts.reset(); + m_faceIndices.reset(); + m_vertexCountPerFace.reset(); m_tangentIndices.clear(); m_tangentsCount = 0; @@ -62,9 +63,9 @@ int Topology::getSplitCount() const int Topology::getSplitCount(aiPolyMeshSample * meshSample, bool forceRefresh) { - if (m_counts && m_indices) + if (m_vertexCountPerFace && m_faceIndices) { - if (m_faceSplitIndices.size() != m_counts->size() || forceRefresh) + if (m_faceSplitIndices.size() != m_vertexCountPerFace->size() || forceRefresh) { updateSplits(meshSample); } @@ -84,35 +85,35 @@ void Topology::updateSplits(aiPolyMeshSample * meshSample) int splitIndex = 0; size_t indexOffset = 0; - size_t ncounts = m_counts->size(); + size_t faceCount = m_vertexCountPerFace->size(); - m_faceSplitIndices.resize(ncounts); // number of faces + m_faceSplitIndices.resize(faceCount); // number of faces m_splits.clear(); - if (m_vertexSharingEnabled && meshSample != NULL && !meshSample->m_ownTopology) // only fixed topologies get this execution path + if (m_vertexSharingEnabled && meshSample != nullptr && !meshSample->m_ownTopology) // only fixed topologies get this execution path { m_splits.push_back(SplitInfo()); SplitInfo *curSplit = &(m_splits.back()); - for (size_t i = 0; ilastFace = ncounts-1; + curSplit->lastFace = faceCount-1; curSplit->vertexCount = m_FixedTopoPositionsIndexes.size(); } else { const int maxVertexSplitCount = m_use32BitsIndexBuffer ? MAX_VERTEX_SPLIT_COUNT_32 : MAX_VERTEX_SPLIT_COUNT_16; - m_splits.reserve(1 + m_indices->size() / maxVertexSplitCount); + m_splits.reserve(1 + m_faceIndices->size() / maxVertexSplitCount); m_splits.push_back(SplitInfo()); SplitInfo *curSplit = &(m_splits.back()); - for (size_t i = 0; iget()[i]; + size_t nv = (size_t)m_vertexCountPerFace->get()[i]; if (curSplit->vertexCount + nv > maxVertexSplitCount) { @@ -202,8 +203,8 @@ int Topology::prepareSubmeshes(const AbcGeom::IV2fGeomParam::Sample &uvs, } if (defaultFacesetIndex != -1) { - facesetIndices.resize(m_counts->size(), -1); - for (size_t i=0; isize(); ++i) + facesetIndices.resize(m_vertexCountPerFace->size(), -1); + for (size_t i=0; isize(); ++i) { if (facesetIndices[i] == -1) { facesetIndices[i] = defaultFacesetIndex; @@ -213,7 +214,7 @@ int Topology::prepareSubmeshes(const AbcGeom::IV2fGeomParam::Sample &uvs, } else { - facesetIndices.resize(m_counts->size(), -1); + facesetIndices.resize(m_vertexCountPerFace->size(), -1); } int nsplits = getSplitCount(sample, false); @@ -225,9 +226,9 @@ int Topology::prepareSubmeshes(const AbcGeom::IV2fGeomParam::Sample &uvs, Submesh &submesh = m_submeshes.back(); - for (size_t i=0; isize(); ++i) + for (size_t i=0; isize(); ++i) { - submesh.triangleCount += (m_counts->get()[i] - 2); + submesh.triangleCount += (m_vertexCountPerFace->get()[i] - 2); } m_splits[0].submeshCount = 1; @@ -235,17 +236,17 @@ int Topology::prepareSubmeshes(const AbcGeom::IV2fGeomParam::Sample &uvs, else { int vertexIndex = 0; - Submesh *curMesh = 0; - const Util::uint32_t *uvIndices = 0; - const abcV2 *uvValues = 0; + Submesh *curMesh; + const Util::uint32_t *uvIndices = nullptr; + const abcV2 *uvValues = nullptr; std::map submeshIndices; std::vector splitSubmeshIndices(nsplits, 0); - for (size_t i=0; isize(); ++i) + for (size_t i=0; isize(); ++i) { - int nv = m_counts->get()[i]; + int nv = m_vertexCountPerFace->get()[i]; if (nv == 0) { @@ -289,7 +290,7 @@ int Topology::prepareSubmeshes(const AbcGeom::IV2fGeomParam::Sample &uvs, if(m_vertexSharingEnabled) curMesh->vertexIndices.reserve(m_FixedTopoPositionsIndexes.size()); else - curMesh->vertexIndices.reserve(m_indices->size()); + curMesh->vertexIndices.reserve(m_faceIndices->size()); split.submeshCount = splitSubmeshIndices[splitIndex]; } @@ -340,11 +341,10 @@ int Topology::getSplitSubmeshCount(int splitIndex) const // --- -aiPolyMeshSample::aiPolyMeshSample(aiPolyMesh *schema, Topology *topo, bool ownTopo, bool vertexSharingEnabled) +aiPolyMeshSample::aiPolyMeshSample(aiPolyMesh *schema, Topology *topo, bool ownTopo) : super(schema) , m_topology(topo) , m_ownTopology(ownTopo) - , m_vertexSharingEnabled(vertexSharingEnabled) { } @@ -406,8 +406,8 @@ void aiPolyMeshSample::computeSmoothNormals(const aiConfig &config) memset(&m_smoothNormals[0], 0, sizeof(m_smoothNormals[0])*m_smoothNormals.size()); - const auto &counts = *(m_topology->m_counts); - const auto &indices = *(m_topology->m_indices); + const auto &counts = *(m_topology->m_vertexCountPerFace); + const auto &indices = *(m_topology->m_faceIndices); const auto &positions = *m_positions; size_t nf = counts.size(); @@ -458,12 +458,12 @@ void aiPolyMeshSample::computeSmoothNormals(const aiConfig &config) for (abcV3& v : m_smoothNormals) { v.normalize(); } } -void aiPolyMeshSample::computeTangentIndices(const aiConfig &config, const abcV3 *inN, bool indexedNormals) +void aiPolyMeshSample::computeTangentIndices(const aiConfig &config, const abcV3 *inN, bool indexedNormals) const { aiLogger::Info("%s: Compute tangent indices...", getSchema()->getObject()->getFullName()); - const auto &counts = *(m_topology->m_counts); - const auto &indices = *(m_topology->m_indices); + const auto &counts = *(m_topology->m_vertexCountPerFace); + const auto &indices = *(m_topology->m_faceIndices); const auto &uvVals = *(m_uvs.getVals()); const auto &uvIdxs = *(m_uvs.getIndices()); const Util::uint32_t *Nidxs = (indexedNormals ? m_normals.getIndices()->get() : 0); @@ -521,8 +521,8 @@ void aiPolyMeshSample::computeTangents(const aiConfig &config, const abcV3 *inN, { aiLogger::Info("%s: Compute %stangents", getSchema()->getObject()->getFullName(), (config.tangentsMode == aiTangentsMode::Smooth ? "smooth " : "")); - const auto &counts = *(m_topology->m_counts); - const auto &indices = *(m_topology->m_indices); + const auto &counts = *(m_topology->m_vertexCountPerFace); + const auto &indices = *(m_topology->m_faceIndices); const auto &positions = *m_positions; const auto &uvVals = *(m_uvs.getVals()); const auto &uvIdxs = *(m_uvs.getIndices()); @@ -676,7 +676,7 @@ void aiPolyMeshSample::updateConfig(const aiConfig &config, bool &topoChanged, b { bool tangentsModeChanged = (config.tangentsMode != m_config.tangentsMode); - const abcV3 *N = 0; + const abcV3 *N = nullptr; bool Nindexed = false; if (smoothNormalsRequired) @@ -755,7 +755,7 @@ void aiPolyMeshSample::getSummary(bool forceRefresh, aiMeshSampleSummary &summar } -void aiPolyMeshSample::getDataPointer(aiPolyMeshData &dst) +void aiPolyMeshSample::getDataPointer(aiPolyMeshData &dst) const { if (m_positions) { dst.positionCount = m_positions->valid() ? (int)m_positions->size() : 0; @@ -785,13 +785,13 @@ void aiPolyMeshSample::getDataPointer(aiPolyMeshData &dst) } if (m_topology) { - if (m_topology->m_indices) { - dst.indexCount = (int)m_topology->m_indices->size(); - dst.indices = (int*)m_topology->m_indices->get(); + if (m_topology->m_faceIndices) { + dst.indexCount = (int)m_topology->m_faceIndices->size(); + dst.indices = (int*)m_topology->m_faceIndices->get(); } - if (m_topology->m_counts) { - dst.faceCount = (int)m_topology->m_counts->size(); - dst.faces = (int*)m_topology->m_counts->get(); + if (m_topology->m_vertexCountPerFace) { + dst.faceCount = (int)m_topology->m_vertexCountPerFace->size(); + dst.faces = (int*)m_topology->m_vertexCountPerFace->get(); dst.triangulatedIndexCount = m_topology->m_triangulatedIndexCount; } } @@ -1095,8 +1095,8 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) float vertexMotionScale = static_cast(m_config.vertexMotionScale); const SplitInfo &split = m_topology->m_splits[splitIndex]; - const auto &counts = *(m_topology->m_counts); - const auto &indices = m_config.turnQuadEdges ? m_topology->m_indicesSwapedFaceWinding.data() : m_topology->m_indices->get(); + const auto &faceCount = *(m_topology->m_vertexCountPerFace); + const auto &indices = m_config.turnQuadEdges ? m_topology->m_indicesSwapedFaceWinding.data() : m_topology->m_faceIndices->get(); const auto &positions = *m_positions; const auto &nextPositions = *m_nextPositions; @@ -1177,7 +1177,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1208,7 +1208,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1236,7 +1236,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1265,7 +1265,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1298,7 +1298,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if (m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1330,7 +1330,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1359,7 +1359,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1389,7 +1389,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1424,7 +1424,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1457,7 +1457,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1486,7 +1486,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1516,7 +1516,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if ( m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1551,7 +1551,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if (m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1578,7 +1578,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if (m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1602,7 +1602,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if (m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1627,7 +1627,7 @@ void aiPolyMeshSample::fillVertexBuffer(int splitIndex, aiPolyMeshData &data) { for (size_t i=split.firstFace; i<=split.lastFace; ++i) { - int nv = counts[i]; + int nv = faceCount[i]; for (int j = 0; j < nv; ++j, ++o, ++k) { if (m_topology->m_FixedTopoPositionsIndexes.size()) @@ -1704,7 +1704,7 @@ void aiPolyMeshSample::fillSubmeshIndices(const aiSubmeshSummary &summary, aiSub if (it != m_topology->submeshEnd()) { bool ccw = m_config.swapFaceWinding; - const auto &counts = *(m_topology->m_counts); + const auto &counts = *(m_topology->m_vertexCountPerFace); const Submesh &submesh = *it; int index = 0; @@ -1828,11 +1828,11 @@ aiPolyMesh::Sample* aiPolyMesh::newSample() { if (dontUseCache() || !m_varyingTopology) { - sample = new Sample(this, &m_sharedTopology, false, m_config.shareVertices && !m_varyingTopology); + sample = new Sample(this, &m_sharedTopology, false); } else { - sample = new Sample(this, new Topology(), true, m_config.shareVertices && !m_varyingTopology); + sample = new Sample(this, new Topology(), true); } } else @@ -1848,53 +1848,55 @@ aiPolyMesh::Sample* aiPolyMesh::newSample() aiPolyMesh::Sample* aiPolyMesh::readSample(const uint64_t idx, bool &topologyChanged) { + auto ss = aiIndexToSampleSelector(idx); auto ss2 = aiIndexToSampleSelector(idx + 1); DebugLog("aiPolyMesh::readSample(t=%d)", idx); - Sample *ret = newSample(); + Sample *sample = newSample(); + auto topology = sample->m_topology; topologyChanged = m_varyingTopology; - ret->m_topology->EnableVertexSharing(m_config.shareVertices && !m_varyingTopology); - ret->m_topology->Enable32BitsIndexbuffers(m_config.use32BitsIndexBuffer); - ret->m_topology->TreatVertexExtraDataAsStatic(m_config.treatVertexExtraDataAsStatic && !m_varyingTopology); + topology->EnableVertexSharing(m_config.shareVertices && !m_varyingTopology); + topology->Enable32BitsIndexbuffers(m_config.use32BitsIndexBuffer); + topology->TreatVertexExtraDataAsStatic(m_config.treatVertexExtraDataAsStatic && !m_varyingTopology); - if (!ret->m_topology->m_counts || m_varyingTopology) + if (!topology->m_vertexCountPerFace || m_varyingTopology) { DebugLog(" Read face counts"); - m_schema.getFaceCountsProperty().get(ret->m_topology->m_counts, ss); - ret->m_topology->m_triangulatedIndexCount = CalculateTriangulatedIndexCount(*ret->m_topology->m_counts); + m_schema.getFaceCountsProperty().get(topology->m_vertexCountPerFace, ss); + topology->m_triangulatedIndexCount = CalculateTriangulatedIndexCount(*topology->m_vertexCountPerFace); topologyChanged = true; } - if (!ret->m_topology->m_indices || m_varyingTopology) + if (!topology->m_faceIndices || m_varyingTopology) { DebugLog(" Read face indices"); - m_schema.getFaceIndicesProperty().get(ret->m_topology->m_indices, ss); + m_schema.getFaceIndicesProperty().get(topology->m_faceIndices, ss); topologyChanged = true; } DebugLog(" Read positions"); - m_schema.getPositionsProperty().get(ret->m_positions, ss); + m_schema.getPositionsProperty().get(sample->m_positions, ss); if (!m_varyingTopology && m_config.interpolateSamples) { DebugLog(" Read last positions"); - m_schema.getPositionsProperty().get(ret->m_nextPositions, ss2); + m_schema.getPositionsProperty().get(sample->m_nextPositions, ss2); } - ret->m_velocities.reset(); + sample->m_velocities.reset(); auto velocitiesProp = m_schema.getVelocitiesProperty(); if (velocitiesProp.valid()) { DebugLog(" Read velocities"); - velocitiesProp.get(ret->m_velocities, ss); + velocitiesProp.get(sample->m_velocities, ss); } - bool smoothNormalsRequired = ret->smoothNormalsRequired(); + bool smoothNormalsRequired = sample->smoothNormalsRequired(); - ret->m_normals.reset(); + sample->m_normals.reset(); auto normalsParam = m_schema.getNormalsParam(); if (!m_ignoreNormals && normalsParam.valid()) { @@ -1906,16 +1908,16 @@ aiPolyMesh::Sample* aiPolyMesh::readSample(const uint64_t idx, bool &topologyCha normalsParam.getIndexed(m_sharedNormals, ss); } - ret->m_normals = m_sharedNormals; + sample->m_normals = m_sharedNormals; } else { DebugLog(" Read normals"); - normalsParam.getIndexed(ret->m_normals, ss); + normalsParam.getIndexed(sample->m_normals, ss); } } - ret->m_uvs.reset(); + sample->m_uvs.reset(); auto uvsParam = m_schema.getUVsParam(); if (!m_ignoreUVs && uvsParam.valid()) { @@ -1927,61 +1929,61 @@ aiPolyMesh::Sample* aiPolyMesh::readSample(const uint64_t idx, bool &topologyCha uvsParam.getIndexed(m_sharedUVs, ss); } - ret->m_uvs = m_sharedUVs; + sample->m_uvs = m_sharedUVs; } else { DebugLog(" Read uvs"); - uvsParam.getIndexed(ret->m_uvs, ss); + uvsParam.getIndexed(sample->m_uvs, ss); } } auto boundsParam = m_schema.getSelfBoundsProperty(); if (boundsParam) { - boundsParam.get(ret->m_bounds, ss); + boundsParam.get(sample->m_bounds, ss); } if (smoothNormalsRequired) { - ret->computeSmoothNormals(m_config); + sample->computeSmoothNormals(m_config); } - if (ret->tangentsRequired()) + if (sample->tangentsRequired()) { - const abcV3 *normals = 0; + const abcV3 *normals = nullptr; bool indexedNormals = false; if (smoothNormalsRequired) { - normals = &ret->m_smoothNormals[0]; + normals = &sample->m_smoothNormals[0]; } - else if (ret->m_normals.valid()) + else if (sample->m_normals.valid()) { - normals = ret->m_normals.getVals()->get(); - indexedNormals = (ret->m_normals.getScope() == AbcGeom::kFacevaryingScope); + normals = sample->m_normals.getVals()->get(); + indexedNormals = (sample->m_normals.getScope() == AbcGeom::kFacevaryingScope); } - if (normals && ret->m_uvs.valid()) + if (normals && sample->m_uvs.valid()) { // topology may be shared, check tangent indices - if (ret->m_topology->m_tangentIndices.empty() || !m_config.cacheTangentsSplits) + if (topology->m_tangentIndices.empty() || !m_config.cacheTangentsSplits) { - ret->computeTangentIndices(m_config, normals, indexedNormals); + sample->computeTangentIndices(m_config, normals, indexedNormals); } - ret->computeTangents(m_config, normals, indexedNormals); + sample->computeTangents(m_config, normals, indexedNormals); } } if (m_config.turnQuadEdges) { - if (m_varyingTopology || ret->m_topology->m_indicesSwapedFaceWinding.size() == 0) + if (m_varyingTopology || topology->m_indicesSwapedFaceWinding.size() == 0) { - auto faces = ret->m_topology->m_counts; + auto faces = topology->m_vertexCountPerFace; auto totalFaces = faces->size(); - auto * facesIndices = ret->m_topology->m_indices->get(); - ret->m_topology->m_indicesSwapedFaceWinding.reserve(ret->m_topology->m_indices->size()); + auto * facesIndices = topology->m_faceIndices->get(); + topology->m_indicesSwapedFaceWinding.reserve(topology->m_faceIndices->size()); auto index = 0; const uint32_t indexRemap[4] = {3,0,1,2}; @@ -1992,24 +1994,24 @@ aiPolyMesh::Sample* aiPolyMesh::readSample(const uint64_t idx, bool &topologyCha { for (auto i = 0; i < faceSize; i++) { - ret->m_topology->m_indicesSwapedFaceWinding.push_back(facesIndices[index + indexRemap[i]]); + topology->m_indicesSwapedFaceWinding.push_back(facesIndices[index + indexRemap[i]]); } } else { for (auto i = 0; i < faceSize; i++) { - ret->m_topology->m_indicesSwapedFaceWinding.push_back(facesIndices[index + i]); + topology->m_indicesSwapedFaceWinding.push_back(facesIndices[index + i]); } } index += faceSize; } - if (ret->m_uvs.valid()) + if (sample->m_uvs.valid()) { index = 0; - const auto& uvIndices = *ret->m_uvs.getIndices(); - ret->m_topology->m_UvIndicesSwapedFaceWinding.reserve(ret->m_uvs.getIndices()->size()); + const auto& uvIndices = *sample->m_uvs.getIndices(); + topology->m_UvIndicesSwapedFaceWinding.reserve(sample->m_uvs.getIndices()->size()); for (auto faceIndex = 0; faceIndex < totalFaces; faceIndex++) { @@ -2018,14 +2020,14 @@ aiPolyMesh::Sample* aiPolyMesh::readSample(const uint64_t idx, bool &topologyCha { for (auto i = 0; i < faceSize; i++) { - ret->m_topology->m_UvIndicesSwapedFaceWinding.push_back(uvIndices[index + indexRemap[i]]); + topology->m_UvIndicesSwapedFaceWinding.push_back(uvIndices[index + indexRemap[i]]); } } else { for (auto i = 0; i < faceSize; i++) { - ret->m_topology->m_UvIndicesSwapedFaceWinding.push_back(uvIndices[index+i]); + topology->m_UvIndicesSwapedFaceWinding.push_back(uvIndices[index+i]); } } index += faceSize; @@ -2033,15 +2035,15 @@ aiPolyMesh::Sample* aiPolyMesh::readSample(const uint64_t idx, bool &topologyCha } } } - else if (ret->m_topology->m_indicesSwapedFaceWinding.size()>0) + else if (topology->m_indicesSwapedFaceWinding.size()>0) { - ret->m_topology->m_indicesSwapedFaceWinding.clear(); + topology->m_indicesSwapedFaceWinding.clear(); } - if (m_config.shareVertices && !m_varyingTopology && ret != NULL && !ret->m_ownTopology && topologyChanged) - GenerateVerticesToFacesLookup(ret); + if (m_config.shareVertices && !m_varyingTopology && sample != NULL && !sample->m_ownTopology && topologyChanged) + GenerateVerticesToFacesLookup(sample); - return ret; + return sample; } // generates two lookup tables: @@ -2049,11 +2051,12 @@ aiPolyMesh::Sample* aiPolyMesh::readSample(const uint64_t idx, bool &topologyCha // m_FixedTopoPositionsIndexes : list of resulting positions. value is index into the abc "position" vector. size is greter than or equal to "position" array. void aiPolyMesh::GenerateVerticesToFacesLookup(aiPolyMeshSample *sample) const { - auto faces = sample->m_topology->m_counts; + auto topology = sample->m_topology; + auto faces = topology->m_vertexCountPerFace; auto * facesIndices = m_config.turnQuadEdges ? - sample->m_topology->m_indicesSwapedFaceWinding.data() : sample->m_topology->m_indices->get(); - uint32_t totalFaces = faces->size(); + topology->m_indicesSwapedFaceWinding.data() : topology->m_faceIndices->get(); + uint32_t totalFaces = static_cast(faces->size()); // 1st, figure out which face uses which vertices (for sharing identification) std::unordered_map< uint32_t, std::vector> indexesOfFacesValues; @@ -2068,30 +2071,27 @@ void aiPolyMesh::GenerateVerticesToFacesLookup(aiPolyMeshSample *sample) const // 2nd, figure out which vertex can be merged, which cannot. // If all faces targetting a vertex give it the same normal and UV, then it can be shared. const abcV3 * normals = sample->m_smoothNormals.empty() && sample->m_normals.valid() ? sample->m_normals.getVals()->get() : sample->m_smoothNormals.data(); - bool normalsIndexed = !sample->m_smoothNormals.empty() ? false : sample->m_normals.valid() && ( sample->m_normals.getScope() == AbcGeom::kFacevaryingScope); - const int32_t *Vidxs = sample->m_topology->m_indices->get(); - const uint32_t *Nidxs; - if (normalsIndexed) - Nidxs = sample->m_normals.getIndices()->get(); - + bool normalsIndexed = !sample->m_smoothNormals.empty() ? false : sample->m_normals.valid() && (sample->m_normals.getScope() == AbcGeom::kFacevaryingScope); + const uint32_t *Nidxs = normalsIndexed ? sample->m_normals.getIndices()->get() : (uint32_t*)(topology->m_faceIndices->get()); + bool hasUVs = sample->m_uvs.valid(); const auto &uvVals = *(sample->m_uvs.getVals()); - const auto &uvIdxs = m_config.turnQuadEdges || !hasUVs ? sample->m_topology->m_UvIndicesSwapedFaceWinding : *sample->m_uvs.getIndices(); + const auto &uvIdxs = m_config.turnQuadEdges || !hasUVs ? topology->m_UvIndicesSwapedFaceWinding : *sample->m_uvs.getIndices(); - sample->m_topology->m_FixedTopoPositionsIndexes.clear(); - sample->m_topology->m_FaceIndexingReindexed.clear(); - sample->m_topology->m_FaceIndexingReindexed.resize( sample->m_topology->m_indices->size() ); - sample->m_topology->m_FixedTopoPositionsIndexes.reserve(sample->m_positions->size()); - sample->m_topology->m_FreshlyReadTopologyData = true; + topology->m_FixedTopoPositionsIndexes.clear(); + topology->m_FaceIndexingReindexed.clear(); + topology->m_FaceIndexingReindexed.resize(topology->m_faceIndices->size() ); + topology->m_FixedTopoPositionsIndexes.reserve(sample->m_positions->size()); + topology->m_FreshlyReadTopologyData = true; std::unordered_map< uint32_t, std::vector>::iterator itr = indexesOfFacesValues.begin(); while (itr != indexesOfFacesValues.end()) { std::vector& vertexUsages = itr->second; uint32_t vertexUsageIndex = 0; - uint32_t vertexUsageMaxIndex = itr->second.size(); - const Abc::V2f * prevUV = NULL; - const abcV3 * prevN = NULL; + size_t vertexUsageMaxIndex = itr->second.size(); + const Abc::V2f * prevUV = nullptr; + const abcV3 * prevN = nullptr; bool share = true; do { @@ -2099,8 +2099,8 @@ void aiPolyMesh::GenerateVerticesToFacesLookup(aiPolyMeshSample *sample) const // same Normal? if( normals ) { - const abcV3 & N = normals[Nidxs ? (normalsIndexed ? Nidxs[index] : Vidxs[index]) : index]; - if (prevN == NULL) + const abcV3 & N = normals[Nidxs ? Nidxs[index] : index]; + if (prevN == nullptr) prevN = &N; else share = N == *prevN; @@ -2109,7 +2109,7 @@ void aiPolyMesh::GenerateVerticesToFacesLookup(aiPolyMeshSample *sample) const if (hasUVs) { const Abc::V2f & uv = uvVals[uvIdxs[index]]; - if (prevUV == NULL) + if (prevUV == nullptr) prevUV = &uv; else share = uv == *prevUV; @@ -2119,15 +2119,15 @@ void aiPolyMesh::GenerateVerticesToFacesLookup(aiPolyMeshSample *sample) const // Verdict is in for this vertex. if (share) - sample->m_topology->m_FixedTopoPositionsIndexes.push_back(itr->first); + topology->m_FixedTopoPositionsIndexes.push_back(itr->first); std::vector::iterator indexItr = itr->second.begin(); while( indexItr != itr->second.end() ) { if (!share) - sample->m_topology->m_FixedTopoPositionsIndexes.push_back(itr->first); + topology->m_FixedTopoPositionsIndexes.push_back(itr->first); - sample->m_topology->m_FaceIndexingReindexed[*indexItr] = sample->m_topology->m_FixedTopoPositionsIndexes.size() - 1; + topology->m_FaceIndexingReindexed[*indexItr] = topology->m_FixedTopoPositionsIndexes.size() - 1; ++indexItr; } @@ -2135,6 +2135,14 @@ void aiPolyMesh::GenerateVerticesToFacesLookup(aiPolyMeshSample *sample) const } // We now have a lookup for face value indexes that re-routes to shared indexes when possible! + // Splitting does not work with shared vertices, if the resulting mesh still exceeds the splitting threshold, then disable vertex sharing. + const int maxVertexSplitCount = topology->m_use32BitsIndexBuffer ? MAX_VERTEX_SPLIT_COUNT_32 : MAX_VERTEX_SPLIT_COUNT_16; + if(topology->m_FixedTopoPositionsIndexes.size() / maxVertexSplitCount>0) + { + topology->m_vertexSharingEnabled = false; + topology->m_FixedTopoPositionsIndexes.clear(); + topology->m_FaceIndexingReindexed.clear(); + } } void aiPolyMesh::updatePeakIndexCount() const @@ -2149,7 +2157,7 @@ void aiPolyMesh::updatePeakIndexCount() const auto indicesProp = m_schema.getFaceIndicesProperty(); auto countsProp = m_schema.getFaceCountsProperty(); - int numSamples = (int)indicesProp.getNumSamples(); + int numSamples = static_cast(indicesProp.getNumSamples()); if (numSamples == 0) { return; } size_t cMax = 0; diff --git a/Plugin/abci/Importer/aiPolyMesh.h b/Plugin/abci/Importer/aiPolyMesh.h index da59b5635..076631f2e 100644 --- a/Plugin/abci/Importer/aiPolyMesh.h +++ b/Plugin/abci/Importer/aiPolyMesh.h @@ -43,8 +43,6 @@ struct SplitInfo size_t indexOffset; size_t vertexCount; size_t submeshCount; - std::unordered_map verticiesXRefs; // org face index (not it's refred value), index in position vector - size_t uniqueValues; inline SplitInfo(size_t ff=0, size_t io=0) : firstFace(ff) @@ -155,10 +153,10 @@ class Topology inline void TreatVertexExtraDataAsStatic(bool value) { m_TreatVertexExtraDataAsStatic = value; } public: - Abc::Int32ArraySamplePtr m_indices; + Abc::Int32ArraySamplePtr m_faceIndices; std::vector m_indicesSwapedFaceWinding; std::vector m_UvIndicesSwapedFaceWinding; - Abc::Int32ArraySamplePtr m_counts; + Abc::Int32ArraySamplePtr m_vertexCountPerFace; int m_triangulatedIndexCount; Submeshes m_submeshes; @@ -183,7 +181,7 @@ class aiPolyMeshSample : public aiSampleBase { typedef aiSampleBase super; public: - aiPolyMeshSample(aiPolyMesh *schema, Topology *topo, bool ownTopo, bool vertexSharingEnabled ); + aiPolyMeshSample(aiPolyMesh *schema, Topology *topo, bool ownTopo ); virtual ~aiPolyMeshSample(); void updateConfig(const aiConfig &config, bool &topoChanged, bool &dataChanged) override; @@ -196,11 +194,11 @@ typedef aiSampleBase super; bool tangentsRequired() const; void getSummary(bool forceRefresh, aiMeshSampleSummary &summary, aiPolyMeshSample* sample) const; - void getDataPointer(aiPolyMeshData &data); + void getDataPointer(aiPolyMeshData &data) const; void copyData(aiPolyMeshData &data); void copyDataWithTriangulation(aiPolyMeshData &data, bool always_expand_indices); - void computeTangentIndices(const aiConfig &config, const abcV3 *N, bool Nindexed); + void computeTangentIndices(const aiConfig &config, const abcV3 *N, bool Nindexed) const; void computeTangents(const aiConfig &config, const abcV3 *N, bool Nindexed); void computeSmoothNormals(const aiConfig &config); @@ -226,7 +224,6 @@ typedef aiSampleBase super; std::vector m_tangents; Submeshes::iterator m_curSubmesh; - bool m_vertexSharingEnabled; }; diff --git a/Plugin/abci/Importer/aiSchema.h b/Plugin/abci/Importer/aiSchema.h index c99ccbad6..c38fca592 100644 --- a/Plugin/abci/Importer/aiSchema.h +++ b/Plugin/abci/Importer/aiSchema.h @@ -217,7 +217,7 @@ aiSampleBase* aiTSchema::updateSample(const abcSampleSelector& ss) readConfig(); - Sample* sample = NULL; + Sample* sample; bool topologyChanged = false; int64_t sampleIndex = getSampleIndex(ss);