The run-time model format, .stsdk, is parsed by the SDK and the model content is made available via several functions in the CCore
class, defined in Core/Core.h
. The listing below details how an .stsdk file is loaded and its data extracted.
This example uses SpeedTree::CCore::SLoadConfig
to set up a file load but could also use a memory block preloaded by the application as illustrated by the complex example on the Loading .stsdk Files page.
Billboard geometry will be stored in the last level of detail (LOD), if present. Use CCore::HasBillboard()
to query if billboard geometry is present.
#include "Core/Core.h" #include <cassert> // speedtree types using SpeedTree::st_uint32; using SpeedTree::st_float16; using SpeedTree::st_uint8; using SpeedTree::st_uint16; void MyErrorCallback(const char* pMsg) { fprintf(stderr, "SpeedTree SDK Error: [%s]\n", pMsg); } void ShowGeometryAccess(const char* pFilename) { SpeedTree::Callbacks::Error( ) = MyErrorCallback;; // set up error callback // parameters needed to load SpeedTree::CCore::SLoadConfig sLoadConfig; sLoadConfig.m_strFilename = pFilename; // create a model to hold and read the stsdk file SpeedTree::CCore cModel; if (cModel.LoadTree(sLoadConfig)) { printf("# LODs: %d\n", cModel.LodData( ).Count( )); printf("Billboard present: %s\n", cModel.HasBillboard( ) ? "yes" : "no"); // run through LODs, printing info about each; highest is first for (st_uint32 i = 0; i < cModel.LodData( ).Count( ); ++i) { // each LOD has a set of vertices shared by one or more draw calls; CLodData and CLodInfo // are separated so that we can free the bulk of the geometry data, housed in CLodData, // after passing it to the GPU and keep CLodInfo for the render loop if needed const SpeedTree::CLodData cLodData = cModel.LodData( )[i]; const SpeedTree::CLodInfo cLodInfo = cModel.LodInfo( )[i]; printf("\nLOD %d -----\n", i); // print vertex info -- each LOD has one or more vertex streams, but we're just // looking at the first one if (cLodData.VertexStreams( ).Count( ) > 0) { printf(" # vertices: %d\n", cLodData.VertexStreams( )[0].Data( ).Count( )); // print first vertex for example if (cLodData.VertexStreams( )[0].Data( ).Count( ) > 0) { // must match the Lua script used to export this model; this structure // matches the standard tree vertex packer that ships with the Modeler struct SMyVertexFormat { st_float16 m_vPos[3], m_fTexCoordU; st_float16 m_vLodPos[3], m_fTexCoordV; // the next two attrs are heavily packed st_uint8 m_uiNormal, m_uiBinormal, m_uiTangent, m_uiWindBranchDir; st_uint8 m_uiWindWeight, m_uiWindRipple, m_uiWindBranchOffset, m_uiMisc; }; // get raw VB data, reinterpret as our struct const SMyVertexFormat* pVB = reinterpret_cast<const SMyVertexFormat*>(cLodData.VertexStreams( )[0].Data( )[0]); // position of first vertex printf(" vert[0].pos = (%g, %g, %g)\n", static_cast<float>(pVB[0].m_vPos[0]), static_cast<float>(pVB[0].m_vPos[1]), static_cast<float>(pVB[0].m_vPos[2])); } } // print info about the draw calls printf(" # draw calls: %d\n", cLodInfo.DrawCalls( ).Count( )); for (st_uint32 j = 0; j < cLodInfo.DrawCalls( ).Count( ); ++j) { const SpeedTree::SDrawCall sDrawCall = cLodInfo.DrawCalls( )[j]; const st_uint32 uiIndexSize = cLodData.Indices( ).ElementSize( ); printf(" # triangles: %d\n", sDrawCall.m_uiIndexCount / 3); printf(" index size: %d bytes\n", uiIndexSize); // print the first triangle's indices (if 2 byte indices) if (uiIndexSize == 2) { const st_uint16* pIndices = reinterpret_cast<const st_uint16*>(cLodData.Indices( )[0]); printf(" triangle[0].indices = { %d, %d, %d }\n", pIndices[0], pIndices[1], pIndices[2]); } } } } }