Skip to content
On this page

This example shows how to use the Reduction processor with modular seams.

cpp
// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT License. 

#include <string>
#include <stdlib.h>
#include <filesystem>
#include <future>
#include "SimplygonLoader.h"


Simplygon::spScene LoadScene(Simplygon::ISimplygon* sg, const char* path)
{
	// Create scene importer 
	Simplygon::spSceneImporter sgSceneImporter = sg->CreateSceneImporter();
	sgSceneImporter->SetImportFilePath(path);
	
	// Run scene importer. 
	auto importResult = sgSceneImporter->Run();
	if (Simplygon::Failed(importResult))
	{
		throw std::exception("Failed to load scene.");
	}
	Simplygon::spScene sgScene = sgSceneImporter->GetScene();
	return sgScene;
}

void SaveScene(Simplygon::ISimplygon* sg, Simplygon::spScene sgScene, const char* path)
{
	// Create scene exporter. 
	Simplygon::spSceneExporter sgSceneExporter = sg->CreateSceneExporter();
	std::string outputScenePath = std::string("output\\") + std::string("ReductionWithModularSeams") + std::string("_") + std::string(path);
	sgSceneExporter->SetExportFilePath(outputScenePath.c_str());
	sgSceneExporter->SetScene(sgScene);
	
	// Run scene exporter. 
	auto exportResult = sgSceneExporter->Run();
	if (Simplygon::Failed(exportResult))
	{
		throw std::exception("Failed to save scene.");
	}
}

void CheckLog(Simplygon::ISimplygon* sg)
{
	// Check if any errors occurred. 
	bool hasErrors = sg->ErrorOccurred();
	if (hasErrors)
	{
		Simplygon::spStringArray errors = sg->CreateStringArray();
		sg->GetErrorMessages(errors);
		auto errorCount = errors->GetItemCount();
		if (errorCount > 0)
		{
			printf("%s\n", "CheckLog: Errors:");
			for (auto errorIndex = 0U; errorIndex < errorCount; ++errorIndex)
			{
				Simplygon::spString errorString = errors->GetItem((int)errorIndex);
				printf("%s\n", errorString.c_str());
			}
			sg->ClearErrorMessages();
		}
	}
	else
	{
		printf("%s\n", "CheckLog: No errors.");
	}
	
	// Check if any warnings occurred. 
	bool hasWarnings = sg->WarningOccurred();
	if (hasWarnings)
	{
		Simplygon::spStringArray warnings = sg->CreateStringArray();
		sg->GetWarningMessages(warnings);
		auto warningCount = warnings->GetItemCount();
		if (warningCount > 0)
		{
			printf("%s\n", "CheckLog: Warnings:");
			for (auto warningIndex = 0U; warningIndex < warningCount; ++warningIndex)
			{
				Simplygon::spString warningString = warnings->GetItem((int)warningIndex);
				printf("%s\n", warningString.c_str());
			}
			sg->ClearWarningMessages();
		}
	}
	else
	{
		printf("%s\n", "CheckLog: No warnings.");
	}
	
	// Error out if Simplygon has errors. 
	if (hasErrors)
	{
		throw std::exception("Processing failed with an error");
	}
}

Simplygon::spGeometryDataCollection ExtractGeometriesInScene(Simplygon::ISimplygon* sg, Simplygon::spScene sgModularAssetsScene)
{
	// Extract all geometries in the scene into individual geometries 
	Simplygon::spGeometryDataCollection sgGeometryDataCollection = sg->CreateGeometryDataCollection();
	int id = sgModularAssetsScene->SelectNodes("ISceneMesh");
	auto set = sgModularAssetsScene->GetSelectionSetTable()->GetSelectionSet(id);
	auto geometryCount = set->GetItemCount();
	for (auto geomIndex = 0U; geomIndex < geometryCount; ++geomIndex)
	{
		auto guid = set->GetItem(geomIndex);
		Simplygon::spSceneNode sgSceneNode = sgModularAssetsScene->GetNodeByGUID(guid);
		Simplygon::spSceneMesh sgSceneMesh = Simplygon::spSceneMesh::SafeCast(sgSceneNode);
		Simplygon::spGeometryData geom = sgSceneMesh->GetGeometry();
		sgGeometryDataCollection->AddGeometryData(geom);
	}
	return sgGeometryDataCollection;
}

void DebugModularSeams(Simplygon::ISimplygon* sg, bool outputDebugInfo, Simplygon::spModularSeams sgModularSeams)
{
	if (outputDebugInfo)
	{
		// Optional but helpful to be able to see what the analyzer found. 
		// Each unique modular seam can be extracted as a geometry. If the analyzer ran with 
		// IsTranslationIndependent=false then the seam geometry should be exactly located at the same 
		// place as the modular seams in the original scene. 
		// Each modular seam also has a string array with all the names of the geometries that have that 
		// specific modular seam. 
		auto seamCount = sgModularSeams->GetModularSeamCount();
		for (auto seamIndex = 0U; seamIndex < seamCount; ++seamIndex)
		{
			Simplygon::spGeometryData debugGeom = sgModularSeams->NewDebugModularSeamGeometry((int)seamIndex);
			Simplygon::spStringArray geometryNames = sgModularSeams->NewModularSeamGeometryStringArray((int)seamIndex);
			

			Simplygon::spScene debugScene = sg->CreateScene();
			debugScene->GetRootNode()->CreateChildMesh(debugGeom);
			std::string fileName = std::string("output\\") + std::string("ReductionWithModularSeams_seam_") + std::string(std::to_string(seamIndex)) + std::string(".obj");
			

			Simplygon::spSceneExporter sgSceneExporter = sg->CreateSceneExporter();
			sgSceneExporter->SetExportFilePath( fileName.c_str() );
			sgSceneExporter->SetScene( debugScene );
			sgSceneExporter->Run();
			

			auto vertexCount = debugGeom->GetVertexCount();
			auto geometryNamesCount = geometryNames->GetItemCount();
			std::string outputText = std::string("Seam ") + std::string(std::to_string(seamIndex)) + std::string(" consists of ") + std::string(std::to_string(vertexCount)) + std::string(" vertices and is shared among ") + std::string(std::to_string(geometryNamesCount)) + std::string(" geometries:");
			printf("%s\n", outputText.c_str());
			for (auto geomIndex = 0U; geomIndex < geometryNamesCount; ++geomIndex)
			{
				Simplygon::spString geometryName = geometryNames->GetItem((int)geomIndex);
				std::string geometryNameOutput = std::string(" geom ") + std::string(std::to_string(geomIndex)) + std::string(": ") + std::string(geometryName.c_str());
				printf("%s\n", geometryNameOutput.c_str());
			}
		}
	}
}

void ModifyReductionSettings(Simplygon::spReductionSettings sgReductionSettings, float triangleRatio, float maxDeviation)
{
	sgReductionSettings->SetKeepSymmetry( true );
	sgReductionSettings->SetUseAutomaticSymmetryDetection( true );
	sgReductionSettings->SetUseHighQualityNormalCalculation( true );
	sgReductionSettings->SetReductionHeuristics( Simplygon::EReductionHeuristics::Consistent );
	
	// The importances can be changed here to allow the features to be weighed differently both during 
	// regular reduction and during the analyzing of modular seam 
	sgReductionSettings->SetEdgeSetImportance( 1.0f );
	sgReductionSettings->SetGeometryImportance( 1.0f );
	sgReductionSettings->SetGroupImportance( 1.0f );
	sgReductionSettings->SetMaterialImportance( 1.0f );
	sgReductionSettings->SetShadingImportance( 1.0f );
	sgReductionSettings->SetSkinningImportance( 1.0f );
	sgReductionSettings->SetTextureImportance( 1.0f );
	sgReductionSettings->SetVertexColorImportance( 1.0f );
	
	// The reduction targets below are only used for the regular reduction, not the modular seam 
	// analyzer 
	sgReductionSettings->SetReductionTargetTriangleRatio( triangleRatio );
	sgReductionSettings->SetReductionTargetMaxDeviation( maxDeviation );
	sgReductionSettings->SetReductionTargets(Simplygon::EStopCondition::All, true, false, true, false);
}

void GenerateModularSeams(Simplygon::ISimplygon* sg, Simplygon::spScene sgModularAssetsScene)
{
	Simplygon::spGeometryDataCollection sgGeometryDataCollection = ExtractGeometriesInScene(sg, sgModularAssetsScene);
	
	// Figure out a small value in relation to the scene that will be the tolerance for the modular seams 
	// if a coordinate is moved a distance smaller than the tolerance, then it is regarded as the same 
	// coordinate so two vertices are the at the same place if the distance between them is smaller than 
	// radius * smallValue 
	sgModularAssetsScene->CalculateExtents();
	float smallValue = 0.0001f;
	float sceneRadius = sgModularAssetsScene->GetRadius();
	float tolerance = sceneRadius * smallValue;
	Simplygon::spReductionSettings sgReductionSettings = sg->CreateReductionSettings();
	
	// The triangleRatio and maxDeviation are not important here and will not be used, only the 
	// relative importances and settings 
	ModifyReductionSettings(sgReductionSettings, 0.0f, 0.0f);
	
	// Create the modular seam analyzer. 
	Simplygon::spModularSeamAnalyzer sgModularSeamAnalyzer = sg->CreateModularSeamAnalyzer();
	sgModularSeamAnalyzer->SetTolerance( tolerance );
	sgModularSeamAnalyzer->SetIsTranslationIndependent( false );
	auto modularGeometryCount = sgGeometryDataCollection->GetItemCount();
	
	// Add the geometries to the analyzer 
	for (auto modularGeometryId = 0U; modularGeometryId < modularGeometryCount; ++modularGeometryId)
	{
		auto modularGeometryObject = sgGeometryDataCollection->GetItemAsObject(modularGeometryId);
		Simplygon::spGeometryData modularGeometry = Simplygon::spGeometryData::SafeCast(modularGeometryObject);
		sgModularSeamAnalyzer->AddGeometry(modularGeometry);
	}
	
	// The analyzer needs to know the different reduction settings importances and such because it 
	// runs the reduction as far as possible for all the seams and stores the order and max deviations 
	// for future reductions of assets with the same seams 
	sgModularSeamAnalyzer->Analyze(sgReductionSettings);
	
	// Fetch the modular seams. These can be stored to file and used later 
	Simplygon::spModularSeams sgModularSeams = sgModularSeamAnalyzer->GetModularSeams();
	std::string modularSeamsPath = std::string("output\\") + std::string("ModularAssets.modseam");
	sgModularSeams->SaveToFile(modularSeamsPath.c_str());
}

Simplygon::spModularSeams LoadModularSeams(Simplygon::ISimplygon* sg)
{
	// Load pre-generated modular seams 
	Simplygon::spModularSeams sgModularSeams = sg->CreateModularSeams();
	std::string modularSeamsPath = std::string("output\\") + std::string("ModularAssets.modseam");
	sgModularSeams->LoadFromFile(modularSeamsPath.c_str());
	return sgModularSeams;
}

void RunReduction(Simplygon::ISimplygon* sg, Simplygon::spScene sgModularAssetsScene, Simplygon::spModularSeams sgModularSeams, float triangleRatio, float maxDeviation, float modularSeamReductionRatio, float modularSeamMaxDeviation)
{
	Simplygon::spGeometryDataCollection sgGeometryDataCollection = ExtractGeometriesInScene(sg, sgModularAssetsScene);
	auto modularGeometryCount = sgGeometryDataCollection->GetItemCount();
	
	// Add the geometries to the analyzer 
	for (auto modularGeometryId = 0U; modularGeometryId < modularGeometryCount; ++modularGeometryId)
	{
		auto modularGeometryObject = sgGeometryDataCollection->GetItemAsObject(modularGeometryId);
		Simplygon::spGeometryData modularGeometry = Simplygon::spGeometryData::SafeCast(modularGeometryObject);
		
		// Run reduction on each geometry individually, 
		// feed the modular seams into the reducer with the ModularSeamSettings 
		// so the modular seams are reduced identically and are untouched by the rest of the 
		// geometry reduction 
		Simplygon::spScene sgSingleAssetScene = sgModularAssetsScene->NewCopy();
		
		// Remove all the geometries but keep any textures, materials etc. 
		sgSingleAssetScene->RemoveSceneNodes();
		
		// Add just a copy of the current geometry to the scene 
		Simplygon::spGeometryData modularGeometryCopy = modularGeometry->NewCopy(true);
		Simplygon::spSceneNode sgRootNode = sgSingleAssetScene->GetRootNode();
		Simplygon::spSceneMesh sgSceneMesh = sgRootNode->CreateChildMesh(modularGeometryCopy);
		

		Simplygon::spReductionProcessor sgReductionProcessor = sg->CreateReductionProcessor();
		sgReductionProcessor->SetScene( sgSingleAssetScene );
		Simplygon::spReductionSettings sgReductionSettings = sgReductionProcessor->GetReductionSettings();
		Simplygon::spModularSeamSettings sgModularSeamSettings = sgReductionProcessor->GetModularSeamSettings();
		
		// Set the same reduction (importance) settings as the modular seam analyzer for consistent 
		// quality 
		ModifyReductionSettings(sgReductionSettings, triangleRatio, maxDeviation);
		sgModularSeamSettings->SetReductionRatio( modularSeamReductionRatio );
		sgModularSeamSettings->SetMaxDeviation( modularSeamMaxDeviation );
		sgModularSeamSettings->SetStopCondition( Simplygon::EStopCondition::All );
		sgModularSeamSettings->SetModularSeams( sgModularSeams );
		

		sgReductionProcessor->RunProcessing();
		Simplygon::spString geomName = modularGeometry->GetName();
		std::string outputName = std::string(geomName) + std::string(".obj");
		SaveScene(sg, sgSingleAssetScene, outputName.c_str());
	}
}

void RunReductionWithModularSeams(Simplygon::ISimplygon* sg)
{
	// Set reduction targets. Stop condition is set to 'All' 
	float triangleRatio = 0.5f;
	float maxDeviation = 0.0f;
	float modularSeamReductionRatio = 0.75f;
	float modularSeamMaxDeviation = 0.0f;
	
	// Load a scene that has a few modular assets in it as different scene meshes. 
	Simplygon::spScene sgModularAssetsScene = LoadScene(sg, "../../../Assets/ModularAssets/ModularAssets.obj");
	bool generateNewSeams = true;
	if (generateNewSeams)
	{
		GenerateModularSeams(sg, sgModularAssetsScene);
	}
	Simplygon::spModularSeams sgModularSeams = LoadModularSeams(sg);
	DebugModularSeams(sg, true, sgModularSeams);
	
	// Run the reduction. The seams are reduced identically and the rest of the geometries are reduced 
	// like normal 
	RunReduction(sg, sgModularAssetsScene, sgModularSeams, triangleRatio, maxDeviation, modularSeamReductionRatio, modularSeamMaxDeviation);
	
	// Check log for any warnings or errors. 	
	printf("%s\n", "Check log for any warnings or errors.");
	CheckLog(sg);
}

int main()
{
	Simplygon::ISimplygon* sg = NULL;
	Simplygon::EErrorCodes initval = Simplygon::Initialize( &sg );
	if( initval != Simplygon::EErrorCodes::NoError )
	{
		printf( "Failed to initialize Simplygon: ErrorCode(%d)", (int)initval );
		return int(initval);
	}

	RunReductionWithModularSeams(sg);

	Simplygon::Deinitialize(sg);

	return 0;
}
csharp
// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT License. 

using System;
using System.IO;
using System.Threading.Tasks;

public class Program
{
    static Simplygon.spScene LoadScene(Simplygon.ISimplygon sg, string path)
    {
        // Create scene importer 
        using Simplygon.spSceneImporter sgSceneImporter = sg.CreateSceneImporter();
        sgSceneImporter.SetImportFilePath(path);
        
        // Run scene importer. 
        var importResult = sgSceneImporter.Run();
        if (Simplygon.Simplygon.Failed(importResult))
        {
            throw new System.Exception("Failed to load scene.");
        }
        Simplygon.spScene sgScene = sgSceneImporter.GetScene();
        return sgScene;
    }

    static void SaveScene(Simplygon.ISimplygon sg, Simplygon.spScene sgScene, string path)
    {
        // Create scene exporter. 
        using Simplygon.spSceneExporter sgSceneExporter = sg.CreateSceneExporter();
        string outputScenePath = string.Join("", new string[] { "output\\", "ReductionWithModularSeams", "_", path });
        sgSceneExporter.SetExportFilePath(outputScenePath);
        sgSceneExporter.SetScene(sgScene);
        
        // Run scene exporter. 
        var exportResult = sgSceneExporter.Run();
        if (Simplygon.Simplygon.Failed(exportResult))
        {
            throw new System.Exception("Failed to save scene.");
        }
    }

    static void CheckLog(Simplygon.ISimplygon sg)
    {
        // Check if any errors occurred. 
        bool hasErrors = sg.ErrorOccurred();
        if (hasErrors)
        {
            Simplygon.spStringArray errors = sg.CreateStringArray();
            sg.GetErrorMessages(errors);
            var errorCount = errors.GetItemCount();
            if (errorCount > 0)
            {
                Console.WriteLine("CheckLog: Errors:");
                for (uint errorIndex = 0; errorIndex < errorCount; ++errorIndex)
                {
                    string errorString = errors.GetItem((int)errorIndex);
                    Console.WriteLine(errorString);
                }
                sg.ClearErrorMessages();
            }
        }
        else
        {
            Console.WriteLine("CheckLog: No errors.");
        }
        
        // Check if any warnings occurred. 
        bool hasWarnings = sg.WarningOccurred();
        if (hasWarnings)
        {
            Simplygon.spStringArray warnings = sg.CreateStringArray();
            sg.GetWarningMessages(warnings);
            var warningCount = warnings.GetItemCount();
            if (warningCount > 0)
            {
                Console.WriteLine("CheckLog: Warnings:");
                for (uint warningIndex = 0; warningIndex < warningCount; ++warningIndex)
                {
                    string warningString = warnings.GetItem((int)warningIndex);
                    Console.WriteLine(warningString);
                }
                sg.ClearWarningMessages();
            }
        }
        else
        {
            Console.WriteLine("CheckLog: No warnings.");
        }
        
        // Error out if Simplygon has errors. 
        if (hasErrors)
        {
            throw new System.Exception("Processing failed with an error");
        }
    }

    static Simplygon.spGeometryDataCollection ExtractGeometriesInScene(Simplygon.ISimplygon sg, Simplygon.spScene sgModularAssetsScene)
    {
        // Extract all geometries in the scene into individual geometries 
        Simplygon.spGeometryDataCollection sgGeometryDataCollection = sg.CreateGeometryDataCollection();
        int id = sgModularAssetsScene.SelectNodes("ISceneMesh");
        var set = sgModularAssetsScene.GetSelectionSetTable().GetSelectionSet(id);

        var geometryCount = set.GetItemCount();
        for (uint geomIndex = 0; geomIndex < geometryCount; ++geomIndex)
        {
            var guid = set.GetItem(geomIndex);
            Simplygon.spSceneNode sgSceneNode = sgModularAssetsScene.GetNodeByGUID(guid);
            Simplygon.spSceneMesh sgSceneMesh = Simplygon.spSceneMesh.SafeCast(sgSceneNode);
            Simplygon.spGeometryData geom = sgSceneMesh.GetGeometry();
            sgGeometryDataCollection.AddGeometryData(geom);
        }
        return sgGeometryDataCollection;
    }

    static void DebugModularSeams(Simplygon.ISimplygon sg, bool outputDebugInfo, Simplygon.spModularSeams sgModularSeams)
    {
        if (outputDebugInfo)
        {
            // Optional but helpful to be able to see what the analyzer found. 
            // Each unique modular seam can be extracted as a geometry. If the analyzer ran with 
            // IsTranslationIndependent=false then the seam geometry should be exactly located at the same 
            // place as the modular seams in the original scene. 
            // Each modular seam also has a string array with all the names of the geometries that have that 
            // specific modular seam. 
            var seamCount = sgModularSeams.GetModularSeamCount();
            for (uint seamIndex = 0; seamIndex < seamCount; ++seamIndex)
            {
                Simplygon.spGeometryData debugGeom = sgModularSeams.NewDebugModularSeamGeometry((int)seamIndex);
                Simplygon.spStringArray geometryNames = sgModularSeams.NewModularSeamGeometryStringArray((int)seamIndex);
                

                Simplygon.spScene debugScene = sg.CreateScene();
                debugScene.GetRootNode().CreateChildMesh(debugGeom);

                string fileName = string.Join("", new string[] { "output\\", "ReductionWithModularSeams_seam_", seamIndex.ToString(), ".obj" });
                

                using Simplygon.spSceneExporter sgSceneExporter = sg.CreateSceneExporter();
                sgSceneExporter.SetExportFilePath( fileName );
                sgSceneExporter.SetScene( debugScene );
                sgSceneExporter.Run();
                

                var vertexCount = debugGeom.GetVertexCount();
                var geometryNamesCount = geometryNames.GetItemCount();
                string outputText = string.Join("", new string[] { "Seam ", seamIndex.ToString(), " consists of ", vertexCount.ToString(), " vertices and is shared among ", geometryNamesCount.ToString(), " geometries:" });
                Console.WriteLine(outputText);
                for (uint geomIndex = 0; geomIndex < geometryNamesCount; ++geomIndex)
                {
                    string geometryName = geometryNames.GetItem((int)geomIndex);
                    string geometryNameOutput = string.Join("", new string[] { " geom ", geomIndex.ToString(), ": ", geometryName });
                    Console.WriteLine(geometryNameOutput);
                }
            }
        }
    }

    static void ModifyReductionSettings(Simplygon.spReductionSettings sgReductionSettings, float triangleRatio, float maxDeviation)
    {
        sgReductionSettings.SetKeepSymmetry( true );
        sgReductionSettings.SetUseAutomaticSymmetryDetection( true );
        sgReductionSettings.SetUseHighQualityNormalCalculation( true );
        sgReductionSettings.SetReductionHeuristics( Simplygon.EReductionHeuristics.Consistent );
        
        // The importances can be changed here to allow the features to be weighed differently both during 
        // regular reduction and during the analyzing of modular seam 
        sgReductionSettings.SetEdgeSetImportance( 1.0f );
        sgReductionSettings.SetGeometryImportance( 1.0f );
        sgReductionSettings.SetGroupImportance( 1.0f );
        sgReductionSettings.SetMaterialImportance( 1.0f );
        sgReductionSettings.SetShadingImportance( 1.0f );
        sgReductionSettings.SetSkinningImportance( 1.0f );
        sgReductionSettings.SetTextureImportance( 1.0f );
        sgReductionSettings.SetVertexColorImportance( 1.0f );
        
        // The reduction targets below are only used for the regular reduction, not the modular seam 
        // analyzer 
        sgReductionSettings.SetReductionTargetTriangleRatio( triangleRatio );
        sgReductionSettings.SetReductionTargetMaxDeviation( maxDeviation );
        sgReductionSettings.SetReductionTargets(Simplygon.EStopCondition.All, true, false, true, false);
    }

    static void GenerateModularSeams(Simplygon.ISimplygon sg, Simplygon.spScene sgModularAssetsScene)
    {
        Simplygon.spGeometryDataCollection sgGeometryDataCollection = ExtractGeometriesInScene(sg, sgModularAssetsScene);
        
        // Figure out a small value in relation to the scene that will be the tolerance for the modular seams 
        // if a coordinate is moved a distance smaller than the tolerance, then it is regarded as the same 
        // coordinate so two vertices are the at the same place if the distance between them is smaller than 
        // radius * smallValue 
        sgModularAssetsScene.CalculateExtents();
        float smallValue = 0.0001f;
        float sceneRadius = sgModularAssetsScene.GetRadius();
        float tolerance = sceneRadius * smallValue;
        using Simplygon.spReductionSettings sgReductionSettings = sg.CreateReductionSettings();
        
        // The triangleRatio and maxDeviation are not important here and will not be used, only the 
        // relative importances and settings 
        ModifyReductionSettings(sgReductionSettings, 0.0f, 0.0f);
        
        // Create the modular seam analyzer. 
        using Simplygon.spModularSeamAnalyzer sgModularSeamAnalyzer = sg.CreateModularSeamAnalyzer();
        sgModularSeamAnalyzer.SetTolerance( tolerance );
        sgModularSeamAnalyzer.SetIsTranslationIndependent( false );
        var modularGeometryCount = sgGeometryDataCollection.GetItemCount();
        
        // Add the geometries to the analyzer 
        for (uint modularGeometryId = 0; modularGeometryId < modularGeometryCount; ++modularGeometryId)
        {
            var modularGeometryObject = sgGeometryDataCollection.GetItemAsObject(modularGeometryId);
            Simplygon.spGeometryData modularGeometry = Simplygon.spGeometryData.SafeCast(modularGeometryObject);
            sgModularSeamAnalyzer.AddGeometry(modularGeometry);
        }
        
        // The analyzer needs to know the different reduction settings importances and such because it 
        // runs the reduction as far as possible for all the seams and stores the order and max deviations 
        // for future reductions of assets with the same seams 
        sgModularSeamAnalyzer.Analyze(sgReductionSettings);
        
        // Fetch the modular seams. These can be stored to file and used later 
        using Simplygon.spModularSeams sgModularSeams = sgModularSeamAnalyzer.GetModularSeams();
        string modularSeamsPath = string.Join("", new string[] { "output\\", "ModularAssets.modseam" });
        sgModularSeams.SaveToFile(modularSeamsPath);
    }

    static Simplygon.spModularSeams LoadModularSeams(Simplygon.ISimplygon sg)
    {
        // Load pre-generated modular seams 
        Simplygon.spModularSeams sgModularSeams = sg.CreateModularSeams();
        string modularSeamsPath = string.Join("", new string[] { "output\\", "ModularAssets.modseam" });
        sgModularSeams.LoadFromFile(modularSeamsPath);
        return sgModularSeams;
    }

    static void RunReduction(Simplygon.ISimplygon sg, Simplygon.spScene sgModularAssetsScene, Simplygon.spModularSeams sgModularSeams, float triangleRatio, float maxDeviation, float modularSeamReductionRatio, float modularSeamMaxDeviation)
    {
        Simplygon.spGeometryDataCollection sgGeometryDataCollection = ExtractGeometriesInScene(sg, sgModularAssetsScene);
        var modularGeometryCount = sgGeometryDataCollection.GetItemCount();
        
        // Add the geometries to the analyzer 
        for (uint modularGeometryId = 0; modularGeometryId < modularGeometryCount; ++modularGeometryId)
        {
            var modularGeometryObject = sgGeometryDataCollection.GetItemAsObject(modularGeometryId);
            Simplygon.spGeometryData modularGeometry = Simplygon.spGeometryData.SafeCast(modularGeometryObject);
            
            // Run reduction on each geometry individually, 
            // feed the modular seams into the reducer with the ModularSeamSettings 
            // so the modular seams are reduced identically and are untouched by the rest of the 
            // geometry reduction 
            Simplygon.spScene sgSingleAssetScene = sgModularAssetsScene.NewCopy();
            
            // Remove all the geometries but keep any textures, materials etc. 
            sgSingleAssetScene.RemoveSceneNodes();
            
            // Add just a copy of the current geometry to the scene 
            Simplygon.spGeometryData modularGeometryCopy = modularGeometry.NewCopy(true);
            Simplygon.spSceneNode sgRootNode = sgSingleAssetScene.GetRootNode();
            Simplygon.spSceneMesh sgSceneMesh = sgRootNode.CreateChildMesh(modularGeometryCopy);
            

            using Simplygon.spReductionProcessor sgReductionProcessor = sg.CreateReductionProcessor();
            sgReductionProcessor.SetScene( sgSingleAssetScene );
            using Simplygon.spReductionSettings sgReductionSettings = sgReductionProcessor.GetReductionSettings();
            using Simplygon.spModularSeamSettings sgModularSeamSettings = sgReductionProcessor.GetModularSeamSettings();
            
            // Set the same reduction (importance) settings as the modular seam analyzer for consistent 
            // quality 
            ModifyReductionSettings(sgReductionSettings, triangleRatio, maxDeviation);
            sgModularSeamSettings.SetReductionRatio( modularSeamReductionRatio );
            sgModularSeamSettings.SetMaxDeviation( modularSeamMaxDeviation );
            sgModularSeamSettings.SetStopCondition( Simplygon.EStopCondition.All );
            sgModularSeamSettings.SetModularSeams( sgModularSeams );
            

            sgReductionProcessor.RunProcessing();
            string geomName = modularGeometry.GetName();
            string outputName = string.Join("", new string[] { geomName, ".obj" });
            SaveScene(sg, sgSingleAssetScene, outputName);
        }
    }

    static void RunReductionWithModularSeams(Simplygon.ISimplygon sg)
    {
        // Set reduction targets. Stop condition is set to 'All' 
        float triangleRatio = 0.5f;
        float maxDeviation = 0.0f;
        float modularSeamReductionRatio = 0.75f;
        float modularSeamMaxDeviation = 0.0f;
        
        // Load a scene that has a few modular assets in it as different scene meshes. 
        Simplygon.spScene sgModularAssetsScene = LoadScene(sg, "../../../Assets/ModularAssets/ModularAssets.obj");
        bool generateNewSeams = true;
        if (generateNewSeams)
        {
            GenerateModularSeams(sg, sgModularAssetsScene);
        }
        Simplygon.spModularSeams sgModularSeams = LoadModularSeams(sg);
        DebugModularSeams(sg, true, sgModularSeams);
        
        // Run the reduction. The seams are reduced identically and the rest of the geometries are reduced 
        // like normal 
        RunReduction(sg, sgModularAssetsScene, sgModularSeams, triangleRatio, maxDeviation, modularSeamReductionRatio, modularSeamMaxDeviation);
        
        // Check log for any warnings or errors.         
        Console.WriteLine("Check log for any warnings or errors.");
        CheckLog(sg);
    }

    static int Main(string[] args)
    {
        using var sg = Simplygon.Loader.InitSimplygon(out var errorCode, out var errorMessage);
        if (errorCode != Simplygon.EErrorCodes.NoError)
        {
            Console.WriteLine( $"Failed to initialize Simplygon: ErrorCode({(int)errorCode}) {errorMessage}" );
            return (int)errorCode;
        }
        RunReductionWithModularSeams(sg);

        return 0;
    }

}
python
# Copyright (c) Microsoft Corporation. 
# Licensed under the MIT License. 

import math
import os
import sys
import glob
import gc
import threading

from pathlib import Path
from simplygon10 import simplygon_loader
from simplygon10 import Simplygon


def LoadScene(sg: Simplygon.ISimplygon, path: str):
    # Create scene importer 
    sgSceneImporter = sg.CreateSceneImporter()
    sgSceneImporter.SetImportFilePath(path)
    
    # Run scene importer. 
    importResult = sgSceneImporter.Run()
    if Simplygon.Failed(importResult):
        raise Exception('Failed to load scene.')
    sgScene = sgSceneImporter.GetScene()
    return sgScene

def SaveScene(sg: Simplygon.ISimplygon, sgScene: Simplygon.spScene, path: str):
    # Create scene exporter. 
    sgSceneExporter = sg.CreateSceneExporter()
    outputScenePath = ''.join(['output\\', 'ReductionWithModularSeams', '_', path])
    sgSceneExporter.SetExportFilePath(outputScenePath)
    sgSceneExporter.SetScene(sgScene)
    
    # Run scene exporter. 
    exportResult = sgSceneExporter.Run()
    if Simplygon.Failed(exportResult):
        raise Exception('Failed to save scene.')

def CheckLog(sg: Simplygon.ISimplygon):
    # Check if any errors occurred. 
    hasErrors = sg.ErrorOccurred()
    if hasErrors:
        errors = sg.CreateStringArray()
        sg.GetErrorMessages(errors)
        errorCount = errors.GetItemCount()
        if errorCount > 0:
            print('CheckLog: Errors:')
            for errorIndex in range(errorCount):
                errorString = errors.GetItem(errorIndex)
                print(errorString)
            sg.ClearErrorMessages()
    else:
        print('CheckLog: No errors.')
    
    # Check if any warnings occurred. 
    hasWarnings = sg.WarningOccurred()
    if hasWarnings:
        warnings = sg.CreateStringArray()
        sg.GetWarningMessages(warnings)
        warningCount = warnings.GetItemCount()
        if warningCount > 0:
            print('CheckLog: Warnings:')
            for warningIndex in range(warningCount):
                warningString = warnings.GetItem(warningIndex)
                print(warningString)
            sg.ClearWarningMessages()
    else:
        print('CheckLog: No warnings.')
    
    # Error out if Simplygon has errors. 
    if hasErrors:
        raise Exception('Processing failed with an error')

def ExtractGeometriesInScene(sg: Simplygon.ISimplygon, sgModularAssetsScene: Simplygon.spScene):
    # Extract all geometries in the scene into individual geometries 
    sgGeometryDataCollection = sg.CreateGeometryDataCollection()
    id = sgModularAssetsScene.SelectNodes("ISceneMesh")
    set = sgModularAssetsScene.GetSelectionSetTable().GetSelectionSet(id)

    geometryCount = set.GetItemCount()
    for geomIndex in range(geometryCount):
        guid = set.GetItem(geomIndex)
        sgSceneNode = sgModularAssetsScene.GetNodeByGUID(guid)
        sgSceneMesh = Simplygon.spSceneMesh.SafeCast(sgSceneNode)
        geom = sgSceneMesh.GetGeometry()
        sgGeometryDataCollection.AddGeometryData(geom)
    return sgGeometryDataCollection

def DebugModularSeams(sg: Simplygon.ISimplygon, outputDebugInfo, sgModularSeams: Simplygon.spModularSeams):
    if outputDebugInfo:
        # Optional but helpful to be able to see what the analyzer found. 
        # Each unique modular seam can be extracted as a geometry. If the analyzer ran with 
        # IsTranslationIndependent=false then the seam geometry should be exactly located at the same 
        # place as the modular seams in the original scene. 
        # Each modular seam also has a string array with all the names of the geometries that have that 
        # specific modular seam. 
        seamCount = sgModularSeams.GetModularSeamCount()
        for seamIndex in range(seamCount):
            debugGeom = sgModularSeams.NewDebugModularSeamGeometry(seamIndex)
            geometryNames = sgModularSeams.NewModularSeamGeometryStringArray(seamIndex)
            

            debugScene = sg.CreateScene()
            debugScene.GetRootNode().CreateChildMesh(debugGeom)

            fileName = ''.join(['output\\', 'ReductionWithModularSeams_seam_', str(seamIndex), '.obj'])
            

            sgSceneExporter = sg.CreateSceneExporter()
            sgSceneExporter.SetExportFilePath( fileName )
            sgSceneExporter.SetScene( debugScene )
            sgSceneExporter.Run()
            

            vertexCount = debugGeom.GetVertexCount()
            geometryNamesCount = geometryNames.GetItemCount()
            outputText = ''.join(['Seam ', str(seamIndex), ' consists of ', str(vertexCount), ' vertices and is shared among ', str(geometryNamesCount), ' geometries:'])
            print(outputText)
            for geomIndex in range(geometryNamesCount):
                geometryName = geometryNames.GetItem(geomIndex)
                geometryNameOutput = ''.join([' geom ', str(geomIndex), ': ', geometryName])
                print(geometryNameOutput)

def ModifyReductionSettings(sgReductionSettings: Simplygon.spReductionSettings, triangleRatio, maxDeviation):
    sgReductionSettings.SetKeepSymmetry( True )
    sgReductionSettings.SetUseAutomaticSymmetryDetection( True )
    sgReductionSettings.SetUseHighQualityNormalCalculation( True )
    sgReductionSettings.SetReductionHeuristics( Simplygon.EReductionHeuristics_Consistent )
    
    # The importances can be changed here to allow the features to be weighed differently both during 
    # regular reduction and during the analyzing of modular seam 
    sgReductionSettings.SetEdgeSetImportance( 1.0 )
    sgReductionSettings.SetGeometryImportance( 1.0 )
    sgReductionSettings.SetGroupImportance( 1.0 )
    sgReductionSettings.SetMaterialImportance( 1.0 )
    sgReductionSettings.SetShadingImportance( 1.0 )
    sgReductionSettings.SetSkinningImportance( 1.0 )
    sgReductionSettings.SetTextureImportance( 1.0 )
    sgReductionSettings.SetVertexColorImportance( 1.0 )
    
    # The reduction targets below are only used for the regular reduction, not the modular seam 
    # analyzer 
    sgReductionSettings.SetReductionTargetTriangleRatio( triangleRatio )
    sgReductionSettings.SetReductionTargetMaxDeviation( maxDeviation )
    sgReductionSettings.SetReductionTargets(Simplygon.EStopCondition_All, True, False, True, False)

def GenerateModularSeams(sg: Simplygon.ISimplygon, sgModularAssetsScene: Simplygon.spScene):
    sgGeometryDataCollection = ExtractGeometriesInScene(sg, sgModularAssetsScene)
    
    # Figure out a small value in relation to the scene that will be the tolerance for the modular seams 
    # if a coordinate is moved a distance smaller than the tolerance, then it is regarded as the same 
    # coordinate so two vertices are the at the same place if the distance between them is smaller than 
    # radius * smallValue 
    sgModularAssetsScene.CalculateExtents()
    smallValue = 0.0001
    sceneRadius = sgModularAssetsScene.GetRadius()
    tolerance = sceneRadius * smallValue
    sgReductionSettings = sg.CreateReductionSettings()
    
    # The triangleRatio and maxDeviation are not important here and will not be used, only the 
    # relative importances and settings 
    ModifyReductionSettings(sgReductionSettings, 0.0, 0.0)
    
    # Create the modular seam analyzer. 
    sgModularSeamAnalyzer = sg.CreateModularSeamAnalyzer()
    sgModularSeamAnalyzer.SetTolerance( tolerance )
    sgModularSeamAnalyzer.SetIsTranslationIndependent( False )
    modularGeometryCount = sgGeometryDataCollection.GetItemCount()
    
    # Add the geometries to the analyzer 
    for modularGeometryId in range(modularGeometryCount):
        modularGeometryObject = sgGeometryDataCollection.GetItemAsObject(modularGeometryId)
        modularGeometry = Simplygon.spGeometryData.SafeCast(modularGeometryObject)
        sgModularSeamAnalyzer.AddGeometry(modularGeometry)
    
    # The analyzer needs to know the different reduction settings importances and such because it 
    # runs the reduction as far as possible for all the seams and stores the order and max deviations 
    # for future reductions of assets with the same seams 
    sgModularSeamAnalyzer.Analyze(sgReductionSettings)
    
    # Fetch the modular seams. These can be stored to file and used later 
    sgModularSeams = sgModularSeamAnalyzer.GetModularSeams()
    modularSeamsPath = ''.join(['output\\', 'ModularAssets.modseam'])
    sgModularSeams.SaveToFile(modularSeamsPath)

def LoadModularSeams(sg: Simplygon.ISimplygon):
    # Load pre-generated modular seams 
    sgModularSeams = sg.CreateModularSeams()
    modularSeamsPath = ''.join(['output\\', 'ModularAssets.modseam'])
    sgModularSeams.LoadFromFile(modularSeamsPath)
    return sgModularSeams

def RunReduction(sg: Simplygon.ISimplygon, sgModularAssetsScene: Simplygon.spScene, sgModularSeams: Simplygon.spModularSeams, triangleRatio, maxDeviation, modularSeamReductionRatio, modularSeamMaxDeviation):
    sgGeometryDataCollection = ExtractGeometriesInScene(sg, sgModularAssetsScene)
    modularGeometryCount = sgGeometryDataCollection.GetItemCount()
    
    # Add the geometries to the analyzer 
    for modularGeometryId in range(modularGeometryCount):
        modularGeometryObject = sgGeometryDataCollection.GetItemAsObject(modularGeometryId)
        modularGeometry = Simplygon.spGeometryData.SafeCast(modularGeometryObject)
        
        # Run reduction on each geometry individually, 
        # feed the modular seams into the reducer with the ModularSeamSettings 
        # so the modular seams are reduced identically and are untouched by the rest of the 
        # geometry reduction 
        sgSingleAssetScene = sgModularAssetsScene.NewCopy()
        
        # Remove all the geometries but keep any textures, materials etc. 
        sgSingleAssetScene.RemoveSceneNodes()
        
        # Add just a copy of the current geometry to the scene 
        modularGeometryCopy = modularGeometry.NewCopy(True)
        sgRootNode = sgSingleAssetScene.GetRootNode()
        sgSceneMesh = sgRootNode.CreateChildMesh(modularGeometryCopy)
        

        sgReductionProcessor = sg.CreateReductionProcessor()
        sgReductionProcessor.SetScene( sgSingleAssetScene )
        sgReductionSettings = sgReductionProcessor.GetReductionSettings()
        sgModularSeamSettings = sgReductionProcessor.GetModularSeamSettings()
        
        # Set the same reduction (importance) settings as the modular seam analyzer for consistent 
        # quality 
        ModifyReductionSettings(sgReductionSettings, triangleRatio, maxDeviation)
        sgModularSeamSettings.SetReductionRatio( modularSeamReductionRatio )
        sgModularSeamSettings.SetMaxDeviation( modularSeamMaxDeviation )
        sgModularSeamSettings.SetStopCondition( Simplygon.EStopCondition_All )
        sgModularSeamSettings.SetModularSeams( sgModularSeams )
        

        sgReductionProcessor.RunProcessing()
        geomName = modularGeometry.GetName()
        outputName = ''.join([geomName, '.obj'])
        SaveScene(sg, sgSingleAssetScene, outputName)

def RunReductionWithModularSeams(sg: Simplygon.ISimplygon):
    # Set reduction targets. Stop condition is set to 'All' 
    triangleRatio = 0.5
    maxDeviation = 0.0
    modularSeamReductionRatio = 0.75
    modularSeamMaxDeviation = 0.0
    
    # Load a scene that has a few modular assets in it as different scene meshes. 
    sgModularAssetsScene = LoadScene(sg, '../../../Assets/ModularAssets/ModularAssets.obj')
    generateNewSeams = True
    if generateNewSeams:
        GenerateModularSeams(sg, sgModularAssetsScene)
    sgModularSeams = LoadModularSeams(sg)
    DebugModularSeams(sg, True, sgModularSeams)
    
    # Run the reduction. The seams are reduced identically and the rest of the geometries are reduced 
    # like normal 
    RunReduction(sg, sgModularAssetsScene, sgModularSeams, triangleRatio, maxDeviation, modularSeamReductionRatio, modularSeamMaxDeviation)
    
    # Check log for any warnings or errors.     
    print("Check log for any warnings or errors.")
    CheckLog(sg)

if __name__ == '__main__':
        sg = simplygon_loader.init_simplygon()
        if sg is None:
            exit(Simplygon.GetLastInitializationError())

        RunReductionWithModularSeams(sg)

        sg = None
        gc.collect()