Creating an Asset from a Descriptor (Authoring)
NvBlast(config)(arch).(ext)
(config) is DEBUG, CHECKED, OR PROFILE for the corresponding configurations. For a release configuration there is no (config) suffix.
(arch) is _x86 or _x64 for Windows 32- and 64-bit builds, respectively, and empty for non-Windows platforms.
(ext) is .lib for static linking and .dll for dynamic linking on Windows. On XBoxOne it is .lib, and on PS4 it is .a.
N.B., there are strict rules for the ordering of chunks with an asset, and also conditions on the chunks marked as "support" (using the NvBlastChunkDesc::SupportFlag). See the function documentation for these conditions. NvBlastCreateAsset does not reorder chunks or modify support flags to meet these conditions. If the conditions are not met, NvBlastCreateAsset fails and returns NULL. However, Blast™ provides helper functions to reorder chunk descriptors and modify the support flags within those descriptors so that they are valid for asset creation. The helper functions return a mapping from the original chunk ordering to the new chunk ordering, so that corresponding adjustments or mappings may be made for graphics and other data the user associates with chunks.
Example code is given below. Throughout, we assume the user has defined a logging function called logFn, with the signature of NvBlastLog. In all cases, the log function is optional, and NULL may be passed in its place.
// Create chunk descriptors std::vector<NvBlastChunkDesc> chunkDescs; chunkDescs.resize( chunkCount ); // chunkCount > 0 chunkDescs[0].parentChunkIndex = UINT32_MAX; // invalid index denotes a chunk hierarchy root chunkDescs[0].centroid[0] = 0.0f; // centroid position in asset-local space chunkDescs[0].centroid[1] = 0.0f; chunkDescs[0].centroid[2] = 0.0f; chunkDescs[0].volume = 1.0f; // Unit volume chunkDescs[0].flags = NvBlastChunkDesc::NoFlags; chunkDescs[0].userData = 0; // User-supplied ID. For example, this can be the index of the chunkDesc. // The userData can be left undefined. chunkDescs[1].parentChunkIndex = 0; // child of chunk described by chunkDescs[0] chunkDescs[1].centroid[0] = 2.0f; // centroid position in asset-local space chunkDescs[1].centroid[1] = 4.0f; chunkDescs[1].centroid[2] = 6.0f; chunkDescs[1].volume = 1.0f; // Unit volume chunkDescs[1].flags = NvBlastChunkDesc::SupportFlag; // This chunk should be represented in the support graph chunkDescs[1].userData = 1; // ... etc. for all chunks // Create bond descriptors std::vector<NvBlastBondDesc> bondDescs; bondDescs.resize( bondCount ); // bondCount > 0 bondDescs[0].chunkIndices[0] = 1; // chunkIndices refer to chunk descriptor indices for support chunks bondDescs[0].chunkIndices[1] = 2; bondDescs[0].bond.normal[0] = 1.0f; // normal in the +x direction bondDescs[0].bond.normal[1] = 0.0f; bondDescs[0].bond.normal[2] = 0.0f; bondDescs[0].bond.area = 1.0f; // unit area bondDescs[0].bond.centroid[0] = 1.0f; // centroid position in asset-local space bondDescs[0].bond.centroid[1] = 2.0f; bondDescs[0].bond.centroid[2] = 3.0f; bondDescs[0].userData = 0; // this can be used to tell the user more information about this // bond for example to create a joint when this bond breaks bondDescs[1].chunkIndices[0] = 1; bondDescs[1].chunkIndices[1] = ~0; // ~0 (UINT32_MAX) is the "invalid index." This creates a world bond // ... etc. for bondDescs[1], all other fields are filled in as usual // ... etc. for all bonds // Set the fields of the descriptor NvBlastAssetDesc assetDesc; assetDesc.chunkCount = chunkCount; assetDesc.chunkDescs = chunkDescs.data(); assetDesc.bondCount = bondCount; assetDesc.bondDescs = bondDescs.data(); // Now ensure the support coverage in the chunk descriptors is exact, and the chunks are correctly ordered std::vector<char> scratch( chunkCount * sizeof(NvBlastChunkDesc) ); // This is enough scratch for both NvBlastEnsureAssetExactSupportCoverage and NvBlastReorderAssetDescChunks NvBlastEnsureAssetExactSupportCoverage( chunkDescs.data(), chunkCount, scratch.data(), logFn ); std::vector<uint32_t> map(chunkCount); // Will be filled with a map from the original chunk descriptor order to the new one NvBlastReorderAssetDescChunks( chunkDescs.data(), chunkCount, bondDescs.data(), bondCount, map, true, scratch.data(), logFn ); // Create the asset scratch.resize( NvBlastGetRequiredScratchForCreateAsset( &assetDesc ) ); // Provide scratch memory for asset creation void* mem = malloc( NvBlastGetAssetMemorySize( &assetDesc ) ); // Allocate memory for the asset object NvBlastAsset* asset = NvBlastCreateAsset( mem, &assetDesc, scratch.data(), logFn );
It should be noted that the geometric information (centroid, volume, area, normal) in chunks and bonds is only used by damage shader functions (see Damage Shaders (NvBlastExtShaders)). Depending on the shader, some, all, or none of the geometric information will be needed. The user may write damage shader functions that interpret this data in any way they wish.
uint32_t assetSize = NvBlastAssetGetSize( asset ); NvBlastAsset* newAsset = (NvBlastAsset*)malloc(assetSize); // NOTE: the memory buffer MUST be 16-byte aligned! memcpy( newAsset, asset, assetSize ); // this data may be copied into a buffer, stored to a file, etc.
N.B. the comment after the malloc call above. NvBlastAsset memory must be 16-byte aligned.
free( asset );
To create a family, use:
// Allocate memory for the family object - this depends on the asset being represented by the family. void* mem = malloc( NvBlastAssetGetFamilyMemorySize( asset, logFn ) ); NvBlastFamily* family = NvBlastAssetCreateFamily( mem, asset, logFn );
When an actor is first created from an asset, it represents the root of the chunk hierarchy, that is the unfractured object. To create this actor, use:
// Set the fields of the descriptor NvBlastActorDesc actorDesc; actorDesc.asset = asset; // point to a valid asset actorDesc.initialBondHealth = 1.0f; // this health value will be given to all bonds actorDesc.initialChunkHealth = 1.0f; // this health value will be given to all lower-support chunks // Provide scratch memory std::vector<char> scratch( NvBlastFamilyGetRequiredScratchForCreateFirstActor( &actorDesc ) ); // Create the first actor NvBlastActor* actor = NvBlastFamilyCreateFirstActor( family, &actorDesc, scratch.data(), logFn ); // ready to be associated with physics and graphics by the user
const NvBlastFamily* family = NvBlastActorGetFamily( &actor, logFn );
Then the size of the family may be obtained using:
size_t size = NvBlastFamilyGetSize( family, logFn );
Now this memory may be copied, saved to disk, etc. To clone the family, for example, we can duplicate the memory:
std::vector<char> buffer( size ); NvBlastFamily* family2 = reinterpret_cast<NvBlastFamily*>( buffer.data() ); memcpy( family2, family, size );
N.B. If this data has been serialized from an external source, the family will not contain a valid reference to its associated asset. The user must set the family's asset. The family does however contain the asset's ID, to help the user match the correct asset to the family. So one way of restoring the asset to the family follows:
const NvBlastGUID guid = NvBlastFamilyGetAssetID( family2, logFn ); // ... here the user must retrieve the asset using the GUID or by some other means NvBlastFamilySetAsset( family2, asset, logFn );
The data in family2 will contain the same actors as the original family. To access them, use:
uint32_t actorCount = NvBlastFamilyGetActorCount( family2, logFn ); std::vector<NvBlastActor*> actors( actorCount ); uint32_t actorsWritten = NvBlastFamilyGetActors( actors.data(), actorCount, family2, logFn );
In the code above, actorsWritten should equal actorCount.
size_t bufferSize = NvBlastActorGetSerializationSize( actor, logFn );
If you want to use an upper bound which will be large enough for any actor in a family, you may use:
size_t bufferSize = NvBlastAssetGetActorSerializationSizeUpperBound( asset, logFn );
Then create a buffer of that size and use NvBlastActorSerialize to write to the buffer:
std::vector<char> buffer( bufferSize ); size_t bytesWritten = NvBlastActorSerialize( buffer, bufferSize, actor, logFn );
To deserialize the buffer, an appropriate family must be created. It must not already hold a copy of the actor. It must be formed using the correct asset (the one that originally created the actor):
void* mem = malloc( NvBlastAssetGetFamilyMemorySize( asset, logFn ) ); NvBlastFamily* family = NvBlastAssetCreateFamily( mem, asset, logFn );
Then deserialize into the family:
NvBlastActor* newActor = NvBlastFamilyDeserializeActor( family, buffer.data(), logFn );
If newActor is not NULL, then the actor was successfully deserialized.
bool success = NvBlastActorDeactivate( actor, logFn );
free( family );
The family will not be automatically released when all actors within it are invalidated using NvBlastActorDeactivate. However, the user may query the number of active actors in a family using
uint32_t actorCount = NvBlastFamilyGetActorCount( family, logFn );
The second stage is carried out with NvBlastActorApplyFracture. This function takes the previously generated Fracture Commands and applies them to the NvBlastActor. The result of every applied command is reported as a respective Fracture Event if requested.
Fracture Commands and Fracture Events both are represented by NvBlastFractureBuffers. The splitting of the actor into child actors is not done until the third stage, NvBlastActorSplit, is called. Fractures may be repeatedly applied to an actor before splitting.
The NvBlastActorGenerateFracture, NvBlastActorApplyFracture and NvBlastActorSplit functions are profiled in Profile configurations. This is done through a pointer to a NvBlastTimers struct passed into the functions. If this pointer is not NULL, then timing values will be accumulated in the referenced struct.
The following example illustrates the process:
// Step one: Generate Fracture Commands // Damage programs (shader functions), material properties and damage description relate to each other. // Together they define how actors will break by generating the desired set of Fracture Commands for Bonds and Chunks. NvBlastDamageProgram damageProgram = { GraphShader, SubgraphShader }; NvBlastProgramParams programParams = { damageDescs, damageDescCount, materialProperties }; // Generating the set of Fracture Commands does not modify the NvBlastActor. NvBlastActorGenerateFracture( fractureCommands, actor, damageProgram, &programParams, logFn, &timers ); // Step two: Apply Fracture Commands // Applying Fracture Commands does modify the state of the NvBlastActor. // The Fracture Events report the resulting state of each Bond or Chunk involved. // Chunks fractured hard enough will also fracture their children, creating Fracture Events for each. NvBlastActorApplyFracture( fractureEvents, actor, fractureCommands, logFn, &timers ); // Step three: Splitting // The Actor may be split into all its smallest pieces. uint32_t maxNewActorCount = NvBlastActorGetMaxActorCountForSplit( actor, logFn ); std::vector<NvBlastActor*> newActors( maxNewActorCount ); // Make this memory available to NvBlastSplitEvent. NvBlastActorSplitEvent splitEvent; splitEvent.newActors = newActors.data(); // Some temporary memory is necessary as well. std::vector<char> scratch( NvBlastActorGetRequiredScratchForSplit( actor, logFn ) ); // New actors created are reported in splitEvent.newActors. // If newActorCount != 0, then the old actor is deleted and is reported in splitEvent.deletedActor. size_t newActorCount = NvBlastActorSplit( &splitEvent, actor, maxNewActorCount, scratch.data(), logFn, &timers );