Skip to content

Instantly share code, notes, and snippets.

@dov
Created July 12, 2025 18:27
Show Gist options
  • Save dov/85e967bd0598edccac4a245893721858 to your computer and use it in GitHub Desktop.
Save dov/85e967bd0598edccac4a245893721858 to your computer and use it in GitHub Desktop.
VSG example of how to modify the color of an STL file
//======================================================================
// load-stl-and-change-color.cpp -
//
// Based on vsghelloworld.cpp
//
// Dov Grobgeld <[email protected]>
// Sat Jul 12 04:38:30 2025
//----------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <fmt/core.h>
#include <vsgXchange/all.h>
#include <vsg/all.h>
#include <iostream>
using namespace std;
using fmt::print;
template <typename... Args>
static void die(fmt::format_string<Args...> FormatStr, Args &&... args)
{
string msg = fmt::format(FormatStr, std::forward<Args>(args)...);
if (msg[msg.size()-1] != '\n')
msg += "\n";
fmt::print(stderr, "{}", msg);
exit(-1);
}
// Extract a pointer to the materialValue
vsg::PbrMaterialValue *extractMaterialValue(vsg::Node* node, bool makeDynamic=true)
{
struct ExtractMaterialVisitor : public vsg::Visitor
{
ExtractMaterialVisitor() {}
void apply(vsg::Object& object) override
{
object.traverse(*this);
}
// Find and return the PbrMaterial value
void apply(vsg::DescriptorBuffer& db) override
{
print("Found descriptor buffer with {} descriptors\n", db.bufferInfoList.size());
for (auto& bfi : db.bufferInfoList)
{
auto data = bfi->data;
print("Found bfi of type {}\n", data->type_info().name());
if (data->is_compatible(typeid(vsg::PbrMaterialValue))) {
if (makeDynamic)
data->properties.dataVariance = vsg::DYNAMIC_DATA;
// Store the materialValue so that it can be returned to the user
this->materialValue = data->cast<vsg::PbrMaterialValue>();
}
}
}
bool makeDynamic = true;
vsg::PbrMaterialValue *materialValue = nullptr;
};
ExtractMaterialVisitor emv;
emv.makeDynamic = makeDynamic;
node->accept(emv);
if (!emv.materialValue)
throw std::runtime_error("Failed finding the material value!");
return emv.materialValue;
}
int main(int argc, char** argv)
{
// set up defaults and read command line arguments to override them
vsg::CommandLine arguments(&argc, argv);
// set up vsg::Options to pass in filepaths, ReaderWriters and other IO related options to use when reading and writing files.
auto options = vsg::Options::create(vsgXchange::all::create());
options->fileCache = vsg::getEnv("VSG_FILE_CACHE");
options->paths = vsg::getEnvPaths("VSG_FILE_PATH");
arguments.read(options);
if (argc == 1)
die("Need filename of model!\n");
vsg::Path filename = arguments[1];
if (arguments.errors()) return arguments.writeErrorMessages(std::cerr);
// load the scene graph
vsg::ref_ptr<vsg::Node> vsg_scene = vsg::read_cast<vsg::Node>(filename, options);
// Extract the materialValue which can be used to change the property
auto materialValue = extractMaterialValue(vsg_scene);
// This is how the material is set
auto material = (vsg::PbrMaterial*)(materialValue->dataPointer(0));
material->diffuseFactor = vsg::vec4{1.0,0.0,0.0,1.0};
materialValue->dirty();
// vsg_scene->setValue("name", filename);
// Save the scene to a vsgt file for manual inspection
vsg::write(vsg_scene, "/tmp/vsg-scene.vsgt", options);
if (!vsg_scene) return 0;
// create the viewer and assign window(s) to it
auto windowTraits = vsg::WindowTraits::create();
windowTraits->windowTitle = filename;
auto viewer = vsg::Viewer::create();
auto window = vsg::Window::create(windowTraits);
if (!window)
{
std::cout << "Could not create window." << std::endl;
return 1;
}
viewer->addWindow(window);
// compute the bounds of the scene graph to help position camera
vsg::ComputeBounds computeBounds;
vsg_scene->accept(computeBounds);
vsg::dvec3 centre = (computeBounds.bounds.min + computeBounds.bounds.max) * 0.5;
double radius = vsg::length(computeBounds.bounds.max - computeBounds.bounds.min) * 0.6;
double nearFarRatio = 0.001;
// set up the camera
auto lookAt = vsg::LookAt::create(centre + vsg::dvec3(0.0, -radius * 3.5, 0.0), centre, vsg::dvec3(0.0, 0.0, 1.0));
vsg::ref_ptr<vsg::ProjectionMatrix> perspective;
auto ellipsoidModel = vsg_scene->getRefObject<vsg::EllipsoidModel>("EllipsoidModel");
if (ellipsoidModel)
{
double horizonMountainHeight = 0.0;
perspective = vsg::EllipsoidPerspective::create(lookAt, ellipsoidModel, 30.0, static_cast<double>(window->extent2D().width) / static_cast<double>(window->extent2D().height), nearFarRatio, horizonMountainHeight);
}
else
{
perspective = vsg::Perspective::create(30.0, static_cast<double>(window->extent2D().width) / static_cast<double>(window->extent2D().height), nearFarRatio * radius, radius * 4.5);
}
auto camera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(window->extent2D()));
// add close handler to respond to the close window button and pressing escape
viewer->addEventHandler(vsg::CloseHandler::create(viewer));
// add trackball to control the Camera
viewer->addEventHandler(vsg::Trackball::create(camera, ellipsoidModel));
// add the CommandGraph to render the scene
auto commandGraph = vsg::createCommandGraphForView(window, camera, vsg_scene);
viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph});
// compile all Vulkan objects and transfer image, vertex and primitive data to GPU
viewer->compile();
// rendering main loop
int frameId = 0;
int colorId = 0;
while (viewer->advanceToNextFrame())
{
// pass any events into EventHandlers assigned to the Viewer
viewer->handleEvents();
viewer->update();
viewer->recordAndSubmit();
viewer->present();
// Toggle the material diffuse color once a second
if (frameId % 60 == 0)
{
if (colorId == 0)
material->diffuseFactor = vsg::vec4{1.0,0.0,0.0,1.0};
else
material->diffuseFactor = vsg::vec4{0.0,1.0,0.0,1.0};
colorId = (colorId+1)%2;
materialValue->dirty();
}
frameId++;
}
// clean up done automatically thanks to ref_ptr<>
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment