/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// //ODA Platform #include "OdaCommon.h" #include "RxDynamicModule.h" #include "DynamicLinker.h" //Visualize SDK #include "TvFactory.h" #include "TvDatabaseUtils.h" #include "TvGroup.h" #include "TvFilerTimer.h" #include "../Extensions/ExServices/ExGiRasterImage.h" #include "TvSubGroupData.h" //glTF import #include "GLTFImport.h" #include "Gltf2Visualize.h" #include "GltfProperties.h" #include "GLTFJsonStructure.h" #include using namespace GLTFFileImport; using namespace GLTF2Visualize; using namespace GLTFFileImport::JSON; //***************************************************************************// // 'OdTvVisualizeGltfFilerProperties' methods implementation //***************************************************************************// OdTvVisualizeGltfFilerProperties::OdTvVisualizeGltfFilerProperties() : m_defaultColor(ODRGB(191, 191, 191)) { } OdRxDictionaryPtr OdTvVisualizeGltfFilerProperties::createObject() { return OdRxObjectImpl::createObject(); } namespace GLTF2Visualize { ODRX_DECLARE_PROPERTY(DefaultColor) ODRX_DECLARE_PROPERTY(AppendTransform) ODRX_DECLARE_PROPERTY(DefaultUnits) ODRX_DECLARE_PROPERTY(NeedCDATree) ODRX_DECLARE_PROPERTY(NeedCollectPropertiesInCDA) ODRX_BEGIN_DYNAMIC_PROPERTY_MAP(OdTvVisualizeGltfFilerProperties); ODRX_GENERATE_PROPERTY(DefaultColor) ODRX_GENERATE_PROPERTY(AppendTransform) ODRX_GENERATE_PROPERTY(DefaultUnits) ODRX_GENERATE_PROPERTY(NeedCDATree) ODRX_GENERATE_PROPERTY(NeedCollectPropertiesInCDA) ODRX_END_DYNAMIC_PROPERTY_MAP(OdTvVisualizeGltfFilerProperties); ODRX_DEFINE_PROPERTY_METHODS(DefaultColor, OdTvVisualizeGltfFilerProperties, getDefaultColor, setDefaultColor, getIntPtr); ODRX_DEFINE_PROPERTY_METHODS(AppendTransform, OdTvVisualizeGltfFilerProperties, getAppendTransform, setAppendTransform, getIntPtr); ODRX_DEFINE_PROPERTY_METHODS(DefaultUnits, OdTvVisualizeGltfFilerProperties, getDefaultUnits, setDefaultUnits, getIntPtr); ODRX_DEFINE_PROPERTY_METHODS(NeedCDATree, OdTvVisualizeGltfFilerProperties, getNeedCDATree, setNeedCDATree, getBool); ODRX_DEFINE_PROPERTY_METHODS(NeedCollectPropertiesInCDA, OdTvVisualizeGltfFilerProperties, getNeedCollectPropertiesInCDA, setNeedCollectPropertiesInCDA, getBool); } void OdTvVisualizeGltfFilerProperties::setDefaultColor(OdIntPtr ptr) { ODCOLORREF* pColor = reinterpret_cast(ptr); if (!pColor) { ODA_ASSERT(false); } m_defaultColor = *pColor; } OdIntPtr OdTvVisualizeGltfFilerProperties::getDefaultColor() const { return reinterpret_cast(&m_defaultColor); } void OdTvVisualizeGltfFilerProperties::setAppendTransform(OdIntPtr pTransform) { const OdTvMatrix* pAppendTransform = (const OdTvMatrix*)(pTransform); if (pAppendTransform) { m_appendTransform = *pAppendTransform; } else { m_appendTransform = OdTvMatrix::kIdentity; } } OdIntPtr OdTvVisualizeGltfFilerProperties::getAppendTransform() const { return (OdIntPtr)(&m_appendTransform); } //***************************************************************************// // 'OdTvVisualizeGltfFiler' methods implementation //***************************************************************************// OdTvVisualizeGltfFiler::OdTvVisualizeGltfFiler() : m_properties(OdTvVisualizeGltfFilerProperties::createObject()) { } OdTvDatabaseId OdTvVisualizeGltfFiler::loadFrom(const OdString& filePath, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { if (rc) { *rc = tvOk; } LoadGltfOptions opt(filePath); return loadGltf(opt, pProfileRes, rc); } OdTvDatabaseId OdTvVisualizeGltfFiler::loadFrom(OdStreamBuf* pBuffer, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { if (rc) { *rc = tvOk; } LoadGltfOptions opt(pBuffer); return loadGltf(opt, pProfileRes, rc); } OdTvDatabaseId OdTvVisualizeGltfFiler::loadFrom(OdDbBaseDatabase* pDatabase, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { if (rc) { *rc = tvNotImplementedYet; } return OdTvDatabaseId(); } OdTvDatabaseId OdTvVisualizeGltfFiler::generate(OdTvFilerTimeProfiling* pProfileRes) const { OdTvDatabaseId tvDbId; // does nothing return tvDbId; } OdTvModelId OdTvVisualizeGltfFiler::appendFrom(const OdTvDatabaseId& databaseId, const OdString& filePath, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { if (rc) { *rc = tvOk; } LoadGltfOptions opt(filePath); return appendGltf(databaseId, opt, pProfileRes, rc); } OdTvModelId OdTvVisualizeGltfFiler::appendFrom(const OdTvDatabaseId& databaseId, OdStreamBuf* pBuffer, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { if (rc) { *rc = tvOk; } LoadGltfOptions opt(pBuffer); return appendGltf(databaseId, opt, pProfileRes, rc); } OdTvModelId OdTvVisualizeGltfFiler::appendFrom(const OdTvDatabaseId& databaseId, OdDbBaseDatabase* pDatabase, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { if (rc) { *rc = tvNotImplementedYet; } return OdTvModelId(); } OdTvDatabaseId OdTvVisualizeGltfFiler::loadGltf(const LoadGltfOptions& opt, OdTvFilerTimeProfiling* pProfileRes /*= NULL*/, OdTvResult* rc /*= NULL*/) const { //prepare timing object OdTvFilerTimer timing(pProfileRes != NULL); OdInt64 nTvTime = 0; timing.startMisc(); OdTvFactoryId tvFactoryId = odTvGetFactory(); OdTvDatabaseId databaseId = tvFactoryId.createDatabase(); timing.endMisc(); nTvTime += timing.getMiscTime() * 1000; OdBool isThereBump = false; OdTvModelId modelId = kernelGltf2Visualize(databaseId, opt, isThereBump, pProfileRes, rc); timing.startMisc(); OdTvFilerTimer tvTiming(pProfileRes != NULL); tvTiming.startTotal(); //Create and set up a view to display the materials. OdString strCurTitle = "Device_GLTF"; OdTvDatabasePtr pDb = databaseId.openObject(OdTv::kForWrite); OdTvGsDeviceId deviceId = pDb->createDevice(strCurTitle); OdTvGsDevicePtr pDevice = deviceId.openObject(OdTv::kForWrite); //create main View OdString strViewName = "View_GLTF"; OdTvGsViewId viewId = pDevice->createView(strViewName, true, rc); pDevice->addView(viewId); //add current model to the view { OdTvGsViewPtr viewPtr = viewId.openObject(OdTv::kForWrite); //set basic parameters for view viewPtr->setView(OdTvPoint(0., 0., 1.), OdTvPoint(0., 0., 0.), OdTvVector(0., 1., 0.), 1., 1.); viewPtr->addModel(modelId); viewPtr->setDefaultLightingIntensity(1.0); viewPtr->setAmbientLightColor(OdTvColorDef(76, 76, 76)); viewPtr->enableDefaultLighting(true, OdTvGsView::kUserDefined); OdTvVector lightDirection = isThereBump ? OdTvVector(0, 0, -1) : OdTvVector(0.6, 0, -1); viewPtr->setUserDefinedLightDirection(lightDirection); // set render mode viewPtr->setMode(OdTvGsView::kGouraudShaded); viewPtr->setActive(true); OdTvExtents3d ext; modelId.openObject()->getExtents(ext); viewPtr->zoomExtents(ext.minPoint(), ext.maxPoint()); } timing.endMisc(); nTvTime += timing.getMiscTime() * 1000.; if (pProfileRes) { pProfileRes->setTvTime(pProfileRes->getTvTime() + nTvTime); pProfileRes->setVectorizingTime(pProfileRes->getVectorizingTime() + pProfileRes->getTvTime()); } return databaseId; } OdTvModelId OdTvVisualizeGltfFiler::appendGltf(const OdTvDatabaseId& databaseId, const LoadGltfOptions& opt, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { OdTvFilerTimer timing(pProfileRes != NULL); OdInt64 nTvTime = 0; timing.startMisc(); OdTvModelId tvModelId; OdTvGsViewPtr viewPtr; if (databaseId.isValid()) { OdTvGsDeviceId devId; OdTvDevicesIteratorPtr pIter = databaseId.openObject()->getDevicesIterator(); if (!pIter->done()) { devId = pIter->getDevice(); if (!devId.openObject()->getActive()) { pIter->step(); while (!pIter->done()) { OdTvGsDeviceId id = pIter->getDevice(); pIter->step(); if (id.openObject()->getActive()) { devId = id; break; } } } } if (!devId.isNull()) { OdTvGsViewId viewId = devId.openObject()->getActiveView(); if (viewId.isNull()) viewId = devId.openObject()->viewAt(0); if (!viewId.isNull()) { viewPtr = viewId.openObject(OdTv::kForWrite); } } } if (viewPtr.isNull()) { return tvModelId; } timing.endMisc(); nTvTime += timing.getMiscTime() * 1000.; OdBool isThereBump = false; tvModelId = kernelGltf2Visualize(databaseId, opt, isThereBump, pProfileRes, rc); timing.startMisc(); viewPtr->setDefaultLightingIntensity(1.0); viewPtr->setAmbientLightColor(OdTvColorDef(76, 76, 76)); viewPtr->enableDefaultLighting(true, OdTvGsView::kUserDefined); OdTvVector lightDirection = isThereBump ? OdTvVector(0, 0, -1) : OdTvVector(0.6, 0, -1); viewPtr->addModel(tvModelId); timing.endMisc(); nTvTime += timing.getMiscTime() * 1000.; if (pProfileRes) { pProfileRes->setTvTime(pProfileRes->getTvTime() + nTvTime); pProfileRes->setVectorizingTime(pProfileRes->getVectorizingTime() + pProfileRes->getTvTime()); } return tvModelId; } void OdTvVisualizeGltfFiler::createCommonDataAccessTree(const OdTvDatabaseId& tvDbId, const OdString& strTreeName) const { OdTvDatabasePtr pDb = tvDbId.openObject(OdTv::kForWrite); OdTvCDATreePtr pTree = OdTvCDATree::createObject(); pTree->createTvDatabaseHierarchyTree(tvDbId, m_properties->getNeedCollectPropertiesInCDA()); //Add tree to the Tv database OdTvResult rc; OdTvCDATreeStorageId cdaTreeId = pDb->addCDATreeStorage(strTreeName, pTree, &rc); if (rc == tvAlreadyExistSameName) { OdUInt32 i = 1; while (rc != tvOk && i < MAX_CDATREENAME_GENERATION_ATTEMPTS) { OdString str; str.format(L"%s_%d", strTreeName.c_str(), i++); //not to fast but it is not a "bottle neck" cdaTreeId = pDb->addCDATreeStorage(str, pTree, &rc); } } //Add new CDA tree to the appropriate models OdTvModelsIteratorPtr modelsIterPtr = pDb->getModelsIterator(); if (!modelsIterPtr->done()) { OdTvModelPtr pModel = modelsIterPtr->getModel().openObject(); if (!pModel.isNull()) { pModel->setCDATreeStorage(cdaTreeId); } } } OdTvModelId OdTvVisualizeGltfFiler::kernelGltf2Visualize(OdTvDatabaseId databaseId, const LoadGltfOptions& opt, OdBool& isThereBump, OdTvFilerTimeProfiling* pProfileRes /*= NULL*/, OdTvResult* rc /*= NULL*/) const { OdInt64 nImportTime = 0; OdInt64 nTvTime = 0; OdTvFilerTimer timing(pProfileRes != NULL); timing.startMisc(); OdSharedPtr pJsonContent = loadGltfContent(opt, pProfileRes, rc); timing.endMisc(); nImportTime = timing.getMiscTime() * 1000.; timing.startMisc(); isThereBump = pJsonContent->isThereBump; OdTvDatabasePtr pDb = databaseId.openObject(OdTv::kForWrite); OdString filename = OdTvDatabaseUtils::getFileNameFromPath(pJsonContent->name); OdTvDatabaseUtils::writeFileNameToTvDatabase(databaseId, pJsonContent->name); OdString modelName = pJsonContent->aScenes[pJsonContent->iMainScene].name.isEmpty() ? "Scene_" + OdString(std::to_string(pJsonContent->iMainScene).c_str()) : pJsonContent->aScenes[pJsonContent->iMainScene].name; OdTvModelId tvModelId = pDb->createModel(modelName); OdTvModelPtr pTvModel = tvModelId.openObject(OdTv::kForWrite); OdTvResult rc0; timing.endMisc(); nTvTime += timing.getMiscTime() * 1000.; timing.startVectorizing(); loadModelFromGltfScene(pTvModel, pDb, *pJsonContent, pProfileRes, &rc0); timing.endVectorizing(); timing.startMisc(); OdTv::Units tvUnits = (OdTv::Units)m_properties->getDefaultUnits(); nTvTime += timing.getMiscTime() * 1000.; pTvModel->setUnits(tvUnits, 1.); timing.endMisc(); if (m_properties->getNeedCDATree() && databaseId.isValid()) { if (!databaseId.isNull()) { timing.startMisc(); createCommonDataAccessTree(databaseId, modelName); timing.endMisc(); if (pProfileRes) pProfileRes->setCDATreeCreationTime(timing.getMiscTime() * 1000.); } } if (pProfileRes) { pProfileRes->setImportTime(nImportTime); pProfileRes->setVectorizingTime(timing.getVectorizingTime() * 1000.); pProfileRes->setTvTime(nTvTime); } return tvModelId; } OdSharedPtr OdTvVisualizeGltfFiler::loadGltfContent(const LoadGltfOptions& opt, OdTvFilerTimeProfiling* pProfileRes /*= NULL*/, OdTvResult* rc /*=NULL*/) const { if (rc) { *rc = tvOk; } //Try to get an GLTF importer OdGltfImportPtr pGltfImporter = createGltfImporter(); if (pGltfImporter.isNull()) { if (rc) { *rc = tvErrorDuringOpenFile; } return OdSharedPtr(); } OdString filePath; //try to perform import if (opt.m_type == LoadGltfOptions::kFilePath) { filePath = opt.m_filePath; if (pGltfImporter->import(filePath) != GLTFFileImport::eNoError) { if (rc) { *rc = tvMissingFilerModule; } return OdSharedPtr(); } } else if (opt.m_type == LoadGltfOptions::kBuffer) { if (pGltfImporter->import(opt.m_pBuffer) != GLTFFileImport::eNoError) { if (rc) { *rc = tvErrorDuringOpenFile; } return OdSharedPtr(); } filePath = opt.m_pBuffer->fileName(); } OdSharedPtr JsonContent = pGltfImporter->GetGltfContent(); JsonContent->setModelName(filePath); pGltfImporter.release(); ::odrxDynamicLinker()->unloadUnreferenced(); return JsonContent; } void OdTvVisualizeGltfFiler::loadModelFromGltfScene(OdTvModelPtr& pTvModel, OdTvDatabasePtr& pDb, const OdJsonContent& rJsonContent, OdTvFilerTimeProfiling* pProfileRes, OdTvResult* rc) const { if (rc) { *rc = tvOk; } OdInt32 MainSceneIdx = rJsonContent.getMainSceneIdx(); const OdArray& aScenes = rJsonContent.getScenes(); const OdSceneContent& MainScene = aScenes[MainSceneIdx]; const OdArray& aNodes = rJsonContent.getNodes(); const OdArray& aActualNodesIdxs = MainScene.aNodesIdxs; for (OdInt32 idx : aActualNodesIdxs) { //Traversal of root nodes of current scene processNode(pTvModel, pDb, idx, rJsonContent); } return; } static void recalculateNodeTransforms(const OdNodeContent& node, OdInt32 currNodeIdx, const OdTvMatrix& parentTransform, std::map& transformMap, const OdArray& aNodes) { OdTvMatrix transform; if (!node.transform.isNull()) { transform = *node.transform; } else { transform = OdGeMatrix3d::translation(node.translation) * node.rotation.getMatrix() * OdGeMatrix3d::scaling(node.scale); } transform = transform.preMultBy(parentTransform); transformMap.emplace(currNodeIdx, transform); for (auto childIdx : node.aChildNodesIdxs) { recalculateNodeTransforms(aNodes[childIdx], childIdx, transform, transformMap, aNodes); } } void OdTvVisualizeGltfFiler::processNode(OdTvModelPtr& pTvModel, OdTvDatabasePtr& pDb, OdUInt32 nodeIdx, const OdJsonContent& rJsonContent, OdTvGroupPtr pParentGroup) const { const OdNodeContent& node = rJsonContent.aNodes[nodeIdx]; OdBool isGroupRequired = !node.aChildNodesIdxs.isEmpty(); OdTvEntityId groupId; OdTvGroupPtr pGroup; if (isGroupRequired) { //Add group for mapping current node to visualize OdString nodeName = node.name.isEmpty() ? "Node_" + OdString(std::to_string(nodeIdx).c_str()) : node.name; groupId = pParentGroup.isNull() ? pTvModel->appendGroup(nodeName) : pParentGroup->appendGroup(nodeName); pGroup = groupId.openObjectAsGroup(OdTv::kForWrite); } OdTvEntityPtr entPtr; if (!node.pMeshIdx.isNull()) { OdUInt32 meshIdx = *node.pMeshIdx.get(); const OdMeshContent& mesh = rJsonContent.aMeshes[meshIdx]; OdString meshName = mesh.name.isEmpty() ? "Mesh_" + OdString(std::to_string(meshIdx).c_str()) : mesh.name; OdTvEntityId entityId; if (isGroupRequired) { entityId = pGroup->appendEntity(meshName); } else { if (pParentGroup.isNull()) { entityId = pTvModel->appendEntity(meshName); } else { entityId = pParentGroup->appendEntity(meshName); } } entPtr = entityId.openObject(OdTv::kForWrite); //set need to check topology entPtr->setNeedCheckShellsTopology(true); OdBool isSinglePrimitive = mesh.aPrimitives.size() == 1; OdInt32 counter = 0; for (auto& primitive : mesh.aPrimitives) { OdTvPointArray vertices; //an array[3] of doubles. OdInt32Array faces; //integers //fill faces and vertices collectShellFromMesh(primitive, rJsonContent, vertices, faces); //skin transform OdSharedPtr skinData; if (node.skin >= 0) { skinData = new OdSkinData; const OdSkinContent& skin = rJsonContent.aSkins[node.skin]; /* * collect skeleton */ { //start fill root node list std::list rootNodeList; for (auto joint : skin.joints) { rootNodeList.push_back(joint); } //filter root node list for (auto joint : skin.joints) { const OdNodeContent& nodeJoint = rJsonContent.aNodes[joint]; for (auto childIdx : nodeJoint.aChildNodesIdxs) { rootNodeList.remove(childIdx); } } //calculate transforms according to hierarchy std::map nodeTransformsMap; for (auto rootNodeIdx : rootNodeList) { recalculateNodeTransforms(rJsonContent.aNodes[rootNodeIdx], rootNodeIdx, OdTvMatrix(), nodeTransformsMap, rJsonContent.aNodes); } //finalize skeleton for (auto joint : skin.joints) { skinData->skeleton.push_back(nodeTransformsMap.at(joint)); } }//collect skeleton /* * collect matrices */ if(skin.inverseBindMatrices >= 0) { const OdAccessorContent& accessor = rJsonContent.aAccessors[skin.inverseBindMatrices]; OdUInt32 accByteOffset = accessor.byteOffset; OdUInt32 count = accessor.count; //accessor //************************** //bufferView //*************************** const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; OdUInt32 bwByteOffset = bufferView.byteOffset; OdUInt32 byteLength = bufferView.byteLength; OdInt32 byteStride = bufferView.byteStride; OdUInt32 numOfComps = 16; OdUInt32 compSize = sizeof(float); OdUInt32 step = byteStride > 0 ? byteStride : compSize * numOfComps; const OdBufferContent& buffer = rJsonContent.aBuffers[bufferView.buffer]; const char* pCurrent = buffer.localData->begin() + bwByteOffset + accByteOffset; for (OdUInt32 i = 0; i < count; ++i) { const float* fPtr = reinterpret_cast(pCurrent); OdTvMatrix matrix; for (int x = 0; x < 4; ++x) { for (int y = 0; y < 4; ++y) { matrix.entry[y][x] = *(fPtr++); } } skinData->invBindMatrices.push_back(matrix); pCurrent += step; } } else { skinData->invBindMatrices.resize(skin.joints.length()); }//collect matrices /* * collect joints info */ { const OdAccessorContent& accessor = rJsonContent.aAccessors[primitive.mJoints.at(0)]; OdUInt32 accByteOffset = accessor.byteOffset; OdUInt32 count = accessor.count; //accessor //************************** //bufferView //*************************** const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; OdUInt32 bwByteOffset = bufferView.byteOffset; OdUInt32 byteLength = bufferView.byteLength; OdInt32 byteStride = bufferView.byteStride; OdUInt32 numOfComps = 4; OdUInt32 compSize = sizeof(OdUInt16); OdUInt32 step = byteStride > 0 ? byteStride : compSize * numOfComps; const OdBufferContent& buffer = rJsonContent.aBuffers[bufferView.buffer]; const char* pCurrent = buffer.localData->begin() + bwByteOffset + accByteOffset; skinData->joints.resize(numOfComps * count); for (OdUInt32 i = 0; i < count; ++i) { const OdUInt16* fPtr = reinterpret_cast(pCurrent); skinData->joints[4 * i] = *fPtr; skinData->joints[4 * i + 1] = *(fPtr + 1); skinData->joints[4 * i + 2] = *(fPtr + 2); skinData->joints[4 * i + 3] = *(fPtr + 3); pCurrent += step; } }//collect joints info /* * collect weights */ { const OdAccessorContent& accessor = rJsonContent.aAccessors[primitive.mWeights.at(0)]; OdUInt32 accByteOffset = accessor.byteOffset; OdUInt32 count = accessor.count; //accessor //************************** //bufferView //*************************** const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; OdUInt32 bwByteOffset = bufferView.byteOffset; OdUInt32 byteLength = bufferView.byteLength; OdInt32 byteStride = bufferView.byteStride; OdUInt32 numOfComps = 4; OdUInt32 compSize = sizeof(float); OdUInt32 step = byteStride > 0 ? byteStride : compSize * numOfComps; const OdBufferContent& buffer = rJsonContent.aBuffers[bufferView.buffer]; const char* pCurrent = buffer.localData->begin() + bwByteOffset + accByteOffset; skinData->weights.resize(numOfComps * count); for (OdUInt32 i = 0; i < count; ++i) { const float* fPtr = reinterpret_cast(pCurrent); skinData->weights[4 * i] = *fPtr; skinData->weights[4 * i + 1] = *(fPtr + 1); skinData->weights[4 * i + 2] = *(fPtr + 2); skinData->weights[4 * i + 3] = *(fPtr + 3); pCurrent += step; } }//collect weights /* * implement skin to vertices */ { for (OdUInt32 i = 0; i < vertices.size(); ++i) { OdTvPoint val(0, 0, 0); for (int u = 0; u < 4; ++u) { float weight = skinData->weights[i * 4 + u]; if (weight <= FLT_EPSILON) { continue; } OdTvPoint tmp = skinData->invBindMatrices[skinData->joints[i * 4 + u]] * vertices[i]; val += (skinData->skeleton[skinData->joints[i * 4 + u]] * tmp * weight).asVector(); } vertices[i] = val; } }//implement skin to vertices } OdTvGeometryDataId shellId; OdTvSubGroupDataPtr subGroupPtr; if (isSinglePrimitive) { shellId = entPtr->appendShell(vertices, faces); } else { OdString subGroupName = ("SubGroup" + std::to_string(counter++)).c_str(); OdTvGeometryDataId subGroupId = entPtr->appendSubGroup( subGroupName ); subGroupPtr = subGroupId.openAsSubGroup(); shellId = subGroupPtr->appendShell(vertices, faces); } // set not to draw edges as isolines { OdTvGeometryDataPtr pGeomDataPtr = shellId.openObject(); if (!pGeomDataPtr.isNull()) pGeomDataPtr->setTargetDisplayMode(OdTvGeometryData::kEveryWhereExceptIsolines); } OdTvShellDataPtr shellPtr = shellId.openAsShell(); OdInt32 materialIdx = primitive.material; OdTvMaterialId materialId; if (materialIdx >= 0) { materialId = createMaterialFromJson(pDb, shellPtr, meshIdx, primitive, rJsonContent); applyShellSpecifiedParameters(pDb, shellPtr, primitive, rJsonContent, skinData); } else { OdTvResult rc0; materialId = pDb->findMaterial(strDefaultMaterialName, &rc0); //get default material if (rc0 != tvOk) { rc0 = tvOk; materialId = pDb->createMaterial(strDefaultMaterialName); OdTvMaterialPtr pDefaultMaterial = materialId.openObject(OdTv::kForWrite, &rc0); //color ODCOLORREF* pDefColor = (ODCOLORREF*)m_properties->getDefaultColor(); Color color(ODGETRED(*pDefColor), ODGETGREEN(*pDefColor), ODGETBLUE(*pDefColor)); OdTvMaterialColor matDiffuseColor; matDiffuseColor.setColor(OdTvColorDef(color.r, color.g, color.b)); matDiffuseColor.setMethod(OdTvMaterialColor::kOverride); OdTvMaterialMap diffuseMap; pDefaultMaterial->setDiffuse(matDiffuseColor, diffuseMap); } //end default material } if (isSinglePrimitive) { entPtr->setMaterial(materialId); } else { subGroupPtr->setMaterial(materialId); } // enable spatial tree shellPtr->setUseSpatialTreeForSelection(true); } } OdTvMatrix transform; if (!node.transform.isNull()) { transform = *node.transform; } else { transform = OdGeMatrix3d::translation(node.translation) * node.rotation.getMatrix() * OdGeMatrix3d::scaling(node.scale); } if (isGroupRequired && node.skin < 0) { pGroup->setModelingMatrix(transform); } else if(!node.pMeshIdx.isNull() && node.skin < 0) { entPtr->setModelingMatrix(transform); } //recursive traversal by children for (OdInt32 idx : node.aChildNodesIdxs) { processNode(pTvModel, pDb, idx, rJsonContent, isGroupRequired ? pGroup : OdTvGroupPtr()); } } void OdTvVisualizeGltfFiler::collectShellFromMesh(const OdMeshContent::OdPrimitive& primitive, const OdJsonContent& rJsonContent, OdTvPointArray& vertices, OdInt32Array& faces, OdTvResult* rc /*= NULL*/) const { if (rc) { *rc = tvOk; } OdUInt32 vertCounter = -1; { //vertices //*************************** //accessor //*************************** const OdAccessorContent& accessor = rJsonContent.aAccessors[primitive.position]; if (accessor.compType != OdAccessorContent::kFloat || accessor.type.compare("VEC3") != 0) { if (rc) { *rc = tvIdWrongDataType; } return; } OdUInt32 accByteOffset = accessor.byteOffset; vertCounter = accessor.count; //accessor //************************** //bufferView //*************************** const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; OdUInt32 bufferIdx = bufferView.buffer; OdUInt32 bwByteOffset = bufferView.byteOffset; OdUInt32 byteLength = bufferView.byteLength; OdInt32 byteStride = bufferView.byteStride; OdUInt32 numOfComps = 3; OdUInt32 compSize = sizeof(float); OdUInt32 step = byteStride > 0 ? byteStride : sizeof(float) * numOfComps; const OdBufferContent& buffer = rJsonContent.aBuffers[bufferIdx]; const char* pCurrent = buffer.localData->begin() + bwByteOffset + accByteOffset; for (OdUInt32 i = 0; i < vertCounter; ++i) { OdTvPoint point; point.x = *reinterpret_cast(pCurrent); point.y = *(reinterpret_cast(pCurrent) + 1); point.z = *(reinterpret_cast(pCurrent) + 2); pCurrent += step; vertices.push_back(point); } } { //faces //*************************** //accessor //*************************** if (primitive.indices < 0) { int counter = 0; for (OdUInt32 i = 0; i < vertCounter; ++i) { if (counter == 0) { faces.push_back(3); } counter = (counter + 1) % 3; faces.push_back(i); } } else { const OdAccessorContent& accessor = rJsonContent.aAccessors[primitive.indices]; OdUInt32 accByteOffset = accessor.byteOffset; OdUInt32 count = accessor.count; //accessor //************************** //bufferView //*************************** const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; OdUInt32 bufferIdx = bufferView.buffer; OdUInt32 bwByteOffset = bufferView.byteOffset; OdUInt32 byteLength = bufferView.byteLength; OdUInt32 step = 0; auto compType = accessor.compType; switch (compType) { case OdAccessorContent::kUnsignedInt: step = 4; break; case OdAccessorContent::kUnsignedShort: case OdAccessorContent::kShort: step = 2; break; case OdAccessorContent::kUnsignedByte: case OdAccessorContent::kByte: step = 1; break; default: break; } const OdBufferContent& buffer = rJsonContent.aBuffers[bufferIdx]; const char* pCurrent = buffer.localData->begin() + bwByteOffset + accByteOffset; int counter = 0; for (OdUInt32 i = 0; i < count; ++i) { if (counter == 0) { faces.push_back(3); } counter = (counter + 1) % 3; if (compType == OdAccessorContent::kUnsignedInt) { OdInt32 face = *reinterpret_cast(pCurrent); faces.push_back(face); } else if(compType == OdAccessorContent::kUnsignedShort ) { OdUInt16 face = *reinterpret_cast(pCurrent); faces.push_back(face); } else if (compType == OdAccessorContent::kUnsignedByte) { OdUInt8 face = *reinterpret_cast(pCurrent); faces.push_back(face); } else if (compType == OdAccessorContent::kShort) { OdInt16 face = *reinterpret_cast(pCurrent); faces.push_back(face); } else if (compType == OdAccessorContent::kByte) { OdInt8 face = *reinterpret_cast(pCurrent); faces.push_back(face); } pCurrent += step; } } } } static float getWrappedValue(float val, OdSamplerContent::Wrap wrapType) { float result = val; if (wrapType == OdSamplerContent::Wrap::CLAMP_TO_EDGE) { if (val < DBL_EPSILON) { result = 0.f; } else if (val - 1.f > DBL_EPSILON) { result = 1.f; } } else if (wrapType == OdSamplerContent::Wrap::MIRRORED_REPEAT) { if (val < DBL_EPSILON) { result = -result; } if (result - 1.f > DBL_EPSILON) { int intPart = static_cast(result); result = result - intPart; if (intPart % 2 == 0) { result = 1.f - result; } } } else if (wrapType == OdSamplerContent::Wrap::REPEAT) { if (val < DBL_EPSILON) { result = -result; } if (result - 1.f > DBL_EPSILON) { int intPart = static_cast(result); result = result - intPart; } } return result; } static OdUInt8 sRgbToLinear(OdUInt8 sRgbVal) { return sRgbVal; double tmp = static_cast(sRgbVal) / 255.0; if (tmp - 0.04045 < DBL_EPSILON) { tmp /= 12.92; } else { tmp = (tmp + 0.055) / 1.055; tmp = pow(tmp, 2.4); } tmp *= 255; return static_cast(tmp); } static Color getColor(OdTvRasterImagePtr& rasterImgPtr, OdTvVector2d point) { OdTvRasterImage::Format format = rasterImgPtr->getFormat(); OdTvVector2d size = rasterImgPtr->getSize(); if (point.y - (size.y - 1.0) > DBL_EPSILON) { point.y = size.y - 1.0; } else if (point.y < 0.0) { point.y = 0.0; } if (point.x - (size.x - 1.0) > DBL_EPSILON) { point.x = size.x - 1.0; } else if (point.x < 0.0) { point.x = 0.0; } OdGiRasterImage* giRaster = rasterImgPtr->getAsGiRasterImage(); OdUInt32 shiftVal = (point.y * size.x + point.x); OdInt32 numBytesPerPixel = giRaster->pixelFormat().bitsPerPixel / 8; const OdUInt8* data = giRaster->scanLines() + shiftVal * numBytesPerPixel; Color result; if (numBytesPerPixel >= 3) { result.r = sRgbToLinear(*(data + 2)); result.g = sRgbToLinear(*(data + 1)); result.b = sRgbToLinear(*data); } else { result.r = *(data + 2);//sRgbToLinear(*data + 2); result.g = *(data + 1);//sRgbToLinear(*data + 1); result.b = *(data);//sRgbToLinear(*data); } return result; } static Color getFilteredValue(OdTvVector2d normVal, OdTvRasterImagePtr& rasterImgPtr, OdSamplerContent::Filter filterType) { OdTvVector2d size = rasterImgPtr->getSize(); OdTvVector2d actualVal{ normVal.x * size.x, normVal.y * size.y }; Color result; if (filterType == OdSamplerContent::Filter::LINEAR) { OdTvVector2d topLeftPoint; topLeftPoint.x = std::floor(actualVal.x); topLeftPoint.y = std::floor(actualVal.y); Color topLeftColor = getColor(rasterImgPtr, topLeftPoint); OdTvVector2d topRightPoint; topRightPoint.x = std::ceil(actualVal.x); topRightPoint.y = std::floor(actualVal.y); Color topRightColor = getColor(rasterImgPtr, topRightPoint); OdTvVector2d bottomLeftPoint; bottomLeftPoint.x = std::floor(actualVal.x); bottomLeftPoint.y = std::ceil(actualVal.y); Color bottomLeftColor = getColor(rasterImgPtr, bottomLeftPoint); OdTvVector2d bottomRightPoint; bottomRightPoint.x = std::ceil(actualVal.x); bottomRightPoint.y = std::ceil(actualVal.y); Color bottomRightColor = getColor(rasterImgPtr, bottomRightPoint); result = (topLeftColor + topRightColor + bottomLeftColor + bottomRightColor) / 4; } else { OdTvVector2d resultPoint{ std::round(actualVal.x), std::round(actualVal.y) }; result = getColor(rasterImgPtr, resultPoint); } return result; } static OdTvPoint2dArray getTexCoord(OdInt32 texCoord, const OdJsonContent& rJsonContent) { OdTvPoint2dArray coords; /* * Fill coords */ { //vertices //*************************** //accessor //*************************** const OdAccessorContent& accessor = rJsonContent.aAccessors[texCoord]; OdUInt32 accByteOffset = accessor.byteOffset; OdUInt32 count = accessor.count; //accessor //************************** //bufferView //*************************** const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; OdUInt32 bufferIdx = bufferView.buffer; OdUInt32 bwByteOffset = bufferView.byteOffset; OdUInt32 byteLength = bufferView.byteLength; OdInt32 byteStride = bufferView.byteStride; OdUInt32 numOfComps = 2; OdUInt32 compSize = sizeof(float); OdUInt32 step = byteStride > 0 ? byteStride : sizeof(float) * numOfComps; const OdBufferContent& buffer = rJsonContent.aBuffers[bufferIdx]; const char* pCurrent = buffer.localData->begin() + bwByteOffset + accByteOffset; for (OdUInt32 i = 0; i < count; ++i) { OdTvPoint2d point; point.x = *reinterpret_cast(pCurrent); point.y = 1.f - *(reinterpret_cast(pCurrent) + 1); pCurrent += step; coords.push_back(point); } }//fill coords return coords; } OdTvMaterialId OdTvVisualizeGltfFiler::createMaterialFromJson(OdTvDatabasePtr& pDb, OdTvShellDataPtr& shellPtr, OdInt32 meshIdx, const OdMeshContent::OdPrimitive& primitive, const OdJsonContent& rJsonContent) const { OdInt32 materialIdx = primitive.material; const OdMaterialContent& material = rJsonContent.aMaterials[materialIdx]; OdString modelName = OdTvDatabaseUtils::getFileNameFromPath(rJsonContent.name); OdString name = material.name.isEmpty() ? "GLTF_material_" + OdString(std::to_string(materialIdx).c_str()) : material.name; OdString localPath = GltfUtils::getPath(rJsonContent.name); OdTvResult rc = tvOk; OdTvMaterialId materialId = pDb->findMaterial(name, &rc); if (rc != tvOk) { rc = tvOk; materialId = pDb->createMaterial(name); OdTvMaterialPtr pMaterial = materialId.openObject(OdTv::kForWrite, &rc); //color { OdTvMaterialMap diffuseMap; if (material.baseColorTexture.index >= 0) { const OdTextureContent& texture = rJsonContent.aTextures[material.baseColorTexture.index]; const OdImageContent& image = rJsonContent.aImages[texture.source]; OdString diffuseMapFile = localPath + image.uri; diffuseMap.setSourceFileName(diffuseMapFile); } OdTvMaterialColor matDiffuseColor(OdTvColorDef(255 * material.baseColorFactor.r, 255 * material.baseColorFactor.g, 255 * material.baseColorFactor.b)); matDiffuseColor.setMethod(OdTvMaterialColor::kOverride); pMaterial->setDiffuse(matDiffuseColor, diffuseMap); } if (material.normalTexture.index >= 0) { const OdTextureContent& texture = rJsonContent.aTextures[material.normalTexture.index]; const OdImageContent& image = rJsonContent.aImages[texture.source]; OdString normalMapFile = localPath + image.uri; OdTvMaterialMap normalMap; normalMap.setSourceFileName(normalMapFile); normalMap.setRawImage(true); pMaterial->setBump(normalMap); } //emissive { OdTvMaterialMap emissionMap; OdTvMaterialColor emissionColor; auto& factor = material.emissiveFactor; emissionColor.setColor(OdTvColorDef(255 * factor[0], 255 * factor[1], 255 * factor[2])); if (material.emissiveTexture.index >= 0) { const OdTextureContent& texture = rJsonContent.aTextures[material.emissiveTexture.index]; const OdImageContent& image = rJsonContent.aImages[texture.source]; OdString emissiveMapFile = localPath + image.uri; emissionMap.setSourceFileName(emissiveMapFile); } pMaterial->setEmission(emissionColor, emissionMap); } if (material.metallicRoughnesTexture.index < 0) { float colorFactor = 1.f - material.roughness; // colorFactor = 1, roughness = 0; colorFactor = 0, roughness = 1 float glossFactor = 1.f - material.metallic; // glossFactor = 0, metallic = 1; glossFactor = 1, metallic = 0 int specColor = 255 * colorFactor; pMaterial->setSpecular(OdTvColorDef(specColor, specColor, specColor), glossFactor); } auto& extensions = material.extensions; if (!extensions.isNull()) { //color if(extensions->diffuseTexture.index >= 0 || extensions->diffuseFactor.r >= 0.f) { OdTvMaterialMap diffuseMap; if (extensions->diffuseTexture.index >= 0) { const OdTextureContent& texture = rJsonContent.aTextures[extensions->diffuseTexture.index]; const OdImageContent& image = rJsonContent.aImages[texture.source]; OdString diffuseMapFile = localPath + image.uri; diffuseMap.setSourceFileName(diffuseMapFile); } OdTvMaterialColor matDiffuseColor; if (extensions->diffuseFactor.r >= 0.f) { matDiffuseColor.setColor(OdTvColorDef(255 * extensions->diffuseFactor.r, 255 * extensions->diffuseFactor.g, 255 * extensions->diffuseFactor.b)); } matDiffuseColor.setMethod(OdTvMaterialColor::kOverride); pMaterial->setDiffuse(matDiffuseColor, diffuseMap); } } } return materialId; } void OdTvVisualizeGltfFiler::applyShellSpecifiedParameters(OdTvDatabasePtr& pDb, OdTvShellDataPtr& shellPtr, const OdMeshContent::OdPrimitive& primitive, const GLTFFileImport::JSON::OdJsonContent& rJsonContent, const OdSharedPtr& skinData) const { OdInt32 materialIdx = primitive.material; const OdMaterialContent& material = rJsonContent.aMaterials[materialIdx]; auto it = primitive.mTexcoord.find(0); OdInt32 texCoord = it != primitive.mTexcoord.end() ? it->second : material.baseColorTexture.texCoord; if (texCoord < 0 && !material.extensions.isNull() && material.extensions->diffuseTexture.texCoord) { texCoord = material.extensions->diffuseTexture.texCoord; } if (texCoord >= 0) { OdTvPoint2dArray texCoords = getTexCoord(texCoord, rJsonContent); shellPtr->setVertexMappingCoordsViaRange(0, texCoords); } if (primitive.normal >= 0) { const OdAccessorContent& accessor = rJsonContent.aAccessors[primitive.normal]; const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; const OdBufferContent& buffer = rJsonContent.aBuffers[bufferView.buffer]; int stride = bufferView.byteStride > 0 ? bufferView.byteStride : sizeof(float) * 3; const char* pCurrent = buffer.localData->begin() + bufferView.byteOffset + accessor.byteOffset; OdTvVectorArray normals; for (int i = 0; i < accessor.count; ++i) { OdTvVector vec; vec.x = *reinterpret_cast(pCurrent); vec.y = *(reinterpret_cast(pCurrent) + 1); vec.z = *(reinterpret_cast(pCurrent) + 2); pCurrent += stride; if (!skinData.isNull()) { OdTvPoint val(0, 0, 0); for (int u = 0; u < 4; ++u) { float weight = skinData->weights[i * 4 + u]; if (weight <= FLT_EPSILON) { continue; } OdTvPoint tmp = skinData->invBindMatrices[skinData->joints[i * 4 + u]] * vec.asPoint(); val += (skinData->skeleton[skinData->joints[i * 4 + u]] * tmp * weight).asVector(); } vec = val.asVector(); } normals.push_back(vec); } shellPtr->setVertexNormalsViaRange(0, normals); } } OdArray OdTvVisualizeGltfFiler::formTextureBuffer(OdTvDatabasePtr& pDb, const OdTextureContent& rTexture, const OdMeshContent::OdPrimitive& primitive, const OdJsonContent& rJsonContent, const ColorFactor& colorFactor) const { const OdSamplerContent& sampler = rJsonContent.aSamplers[rTexture.sampler]; const OdImageContent& image = rJsonContent.aImages[rTexture.source]; const OdMaterialContent& material = rJsonContent.aMaterials[primitive.material]; OdString localPath = GltfUtils::getPath(rJsonContent.name); OdString diffuseMapFile = localPath + image.uri; OdString modelName = OdTvDatabaseUtils::getFileNameFromPath(rJsonContent.name); OdArray textureData; OdTvResult rc = tvOk; /* * Is there texcoord? */ OdInt32 texCoord = material.baseColorTexture.texCoord; auto it = primitive.mTexcoord.find(0); if (texCoord < 0 && it != primitive.mTexcoord.end()) { texCoord = it->second; }// Is there texcoord end //fill textures directly if (texCoord >= 0) { /* * read texture image */ OdString rasterName = modelName + (material.name.isEmpty() ? "GLTF_RasterImage_" : material.name) + std::to_string(primitive.material).c_str() + std::to_string(rTexture.source).c_str(); OdTvRasterImageId imgId = pDb->findRasterImage(rasterName, &rc); if (rc != tvOk) { rc = tvOk; imgId = pDb->createRasterImage(rasterName, diffuseMapFile, true, &rc); } OdTvRasterImagePtr rasterImgPtr = imgId.openObject(OdTv::kForWrite); auto format = rasterImgPtr->getFormat(); OdTvVector2dArray coords; /* * Fill coords */ { //vertices //*************************** //accessor //*************************** const OdAccessorContent& accessor = rJsonContent.aAccessors[texCoord]; OdUInt32 accByteOffset = accessor.byteOffset; OdUInt32 count = accessor.count; //accessor //************************** //bufferView //*************************** const OdBufferViewContent& bufferView = rJsonContent.aBufViews[accessor.bufferView]; OdUInt32 bufferIdx = bufferView.buffer; OdUInt32 bwByteOffset = bufferView.byteOffset; OdUInt32 byteLength = bufferView.byteLength; OdInt32 byteStride = bufferView.byteStride; OdUInt32 numOfComps = 2; OdUInt32 compSize = sizeof(float); OdUInt32 step = byteStride > 0 ? byteStride : sizeof(float) * numOfComps; const OdBufferContent& buffer = rJsonContent.aBuffers[bufferIdx]; const char* pCurrent = buffer.localData->begin() + bwByteOffset + accByteOffset; for (OdUInt32 i = 0; i < count; ++i) { OdTvVector2d point; point.x = *reinterpret_cast(pCurrent); point.y = *(reinterpret_cast(pCurrent) + 1); pCurrent += step; coords.push_back(point); } }//fill coords /* * form colors array and map it to the vertices */ { //fill binary data OdInt32 numComps = format == OdTvRasterImage::Format::kRGB || format == OdTvRasterImage::Format::kBGR ? 3 : 4; //OdUInt8* imageData = new OdUInt8[coords.size() * numComps]; OdInt32 idx = 0; for (OdTvVector2d point : coords) { OdTvVector2d wrappedPoint; wrappedPoint.x = getWrappedValue(point.x, sampler.wrapS); wrappedPoint.y = getWrappedValue(point.y, sampler.wrapT); Color color = getFilteredValue(wrappedPoint, rasterImgPtr, sampler.magFilter); textureData.push_back(OdTvRGBColorDef(color.r * colorFactor.r, color.g * colorFactor.g, color.b * colorFactor.b)); } }//form colors array and map it to the vertices }//texcoord return textureData; } static void fillArray(OdInt32Array& array, OdInt32 size) { if (!array.isEmpty()) { return; } for (OdInt32 i = 0; i < size; ++i) { array.push_back(i); } } //***************************************************************************// // 'OdTvVisualizeGltfFilerModule' methods implementation //***************************************************************************// ODRX_DEFINE_DYNAMIC_MODULE(OdTvVisualizeGltfFilerModule); void OdTvVisualizeGltfFilerModule::initApp() { m_pModule = ::odrxDynamicLinker()->loadModule(L"GLTFImport", false); // initialize the Visualize SDK odTvInitialize(); ::odrxSetMemberConstructor(OdGltfImport::desc(), constructOdGltfImportProperties); } void OdTvVisualizeGltfFilerModule::uninitApp() { // Clear properties for (unsigned i = 0; i < properties.size(); ++i) OdRxMember::deleteMember(properties[i]); properties.clear(); //unload module m_pModule = NULL; ::odrxDynamicLinker()->unloadModule(L"GLTFImport"); // Uninitialize the Visualize SDK odTvUninitialize(); } OdTvVisualizeFilerPtr OdTvVisualizeGltfFilerModule::getVisualizeFiler() const { OdTvVisualizeFilerPtr pGltfFiler = new OdTvVisualizeGltfFiler(); return pGltfFiler; } void OdTvVisualizeGltfFilerModule::constructOdGltfImportProperties(OdRxMemberCollectionBuilder& b, void*) { properties.append(OdGltfImportTypeProperty::createObject(b.owner())); b.add(properties.last()); } OdArray OdTvVisualizeGltfFilerModule::properties;