1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
|
/*! \page pagellapi Low Level API (NvBlast)
<b>Table of Contents</b>
\subpage llintroduction
\subpage include_and_library
\subpage assets
\subpage actors_and_families
\subpage splitting
<br>
\section llintroduction Introduction
The low-level API is the core of Blast&tm; destruction. It is designed to be a minimal API that allows an experienced user to incorporate destruction
into their application. Summarizing what the low-level API has, and <em>doesn't</em> have:
- There is no physics representation. The low-level API is agnostic with respect to any physics engine, and furthermore does not have
any notion of collision geometry. The NvBlastActor is an abstraction which is intended to correspond to a rigid body. However
it is up to the user to implement that connection. The NvBlastActor references a list of visible chunk indices, which correspond to
NvBlastChunk data in the asset. The NvBlastChunk contains a userData field which can be used to associate collision
geometry with the actor based upon the visible chunks. The same is true for constraints created between actors. Bonds contain a
userData field that can be used to inform the user that actors should have joints created at a particular location, but it is up to the
user to create and manage physical joints between two actors.
- There is no graphics representation. Just as there is no notion of collision geometry, there is also no notion of graphics geometry.
The NvBlastChunk userData field (see the item above) can be used to associate graphics geometry with the actor based upon the visible chunks.
- There is no notion of threading. The API is a collection of free functions which the user may call from appropriate threads.
Blast&tm; guarantees that it is safe to operate on different actors from different threads.
- There is no global memory manager, message handler, etc. All low-level API functions take an optional message function pointer argument
in order to report warnings or errors. Memory is managed by the user, and functions that build objects require an appropriately-sized memory block
to be passed in. A corresponding utility function that calculates the memory requirements is always present alongside such functions. Temporary
storage needed by a function is always handled via user-supplied scratch space. For scratch, there is always a corresponding "RequiredScratch"
function or documentation which lets the user know how much scratch space is needed based upon the function arguments.
- Backwards-compatible, versioned, device-independent serialization is not handled by Blast&tm;. There <em>is</em> however a Blast&tm; extension
which does, see \ref pageextserialization. However, a simple form of serialization may be performed on assets and familes (see \ref pagedefinitions)
via simple memory copy. The data associated with these objects is available to the user, and may be copied and stored by the user.
Simply casting a pointer to such a block of memory to the correct object type will produce a usable object for Blast&tm;. (The only restriction is
that the block must be 16-byte aligned.) Families contain a number of actors and so this form of deserialization recreates all actors in the family.
This form of serialization may be used between two devices which have the same endianness, and contain Blast&tm; SDKs which use the same object format.
- Single-actor serialization and deserialization is, however, supported. This is not as light-weight as family serialization, but may be a better
serialization model for a particular application. To deserialize a single actor, one must have a family to hold the actor, created from the appropriate
asset. If none exists already, the user may create an empty family. After that, all actors that had been in that family may be deserialized
into it one-at-a-time, in any order.
<br>
\section include_and_library Linking and Header Files
To use the low-level Blast&tm; SDK, the application need only inlclude the header NvBlast.h, found in the top-level <b>include</b> folder, and link
against the appropriate version of the NvBlast library. Depending on the platform and configuration, various suffixes will be added to the library
name. The general naming scheme is
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.
<br>
\section assets Creating an Asset from a Descriptor (Authoring)
The NvBlastAsset is an opaque type pointing to an object constructed by Blast&tm; in memory allocated by the user.
To create an asset from a descriptor, use the function \ref NvBlastCreateAsset. See the function documentation for a
description of its parameters.
<b>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
<em>not</em> reorder chunks or modify support flags to meet these conditions. If the conditions are not met, NvBlastCreateAsset
fails and returns NULL. However, Blast&tm; 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.</b>
Example code is given below. Throughout, we assume the user has defined a logging function called <B>logFn</B>, with the
signature of NvBlastLog. In all cases, the log function is optional, and NULL may be passed in its place.
\code
// 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 );
\endcode
<br>
It should be noted that the geometric information (centroid, volume, area, normal) in chunks and bonds is only used by damage
shader functions (see \ref pageextshaders). 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.
<br>
\subsection asset_copying Cloning an Asset
To clone an asset, one only needs to copy the memory associated with the NvBlastAsset.
\code
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.
\endcode
N.B. the comment after the malloc call above. NvBlastAsset memory <B>must</B> be 16-byte aligned.
<br>
\subsection asset_releasing Releasing an Asset
Blast&tm; low-level does no internal allocation; since the memory is allocated by the user, one simply has to free the memory they've
allocated. The asset pointer returned by NvBlastCreateAsset has the same numerical value as the mem block passed in (if the function
is successful, or NULL otherwise). So releasing an asset with memory allocated by <B>malloc</B> is simply done with a call to <B>free</B>:
\code
free( asset );
\endcode
<br>
\section actors_and_families Creating Actors and Families
Actors live within a family created from asset data. To create an actor, one must first create a family. This family is used by the initial actor created from
the asset, as well as all of the descendant actors created by recursively fracturing the initial actor. As with assets, family allocation is done by the user.
To create a family, use:
\code
// 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 );
\endcode
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:
\code
// 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
\endcode
<br>
\subsection actor_copying Copying Actors (Serialization and Deserialization)
There are two ways to copy NvBlastActors: cloning an NvBlastFamily, and single-actor serialization. Cloning an NvBlastFamily is extremely fast as it only requires
a single memory copy. All actors in the family may be saved, loaded, or copied at once in this way.
<br>
\subsection family_serialization Cloning a Family
To clone a family, use the family pointer which may be retrieved
from any active actor in the family if needed, using the NvBlastActorGetFamily function:
\code
const NvBlastFamily* family = NvBlastActorGetFamily( &actor, logFn );
\endcode
Then the size of the family may be obtained using:
\code
size_t size = NvBlastFamilyGetSize( family, logFn );
\endcode
Now this memory may be copied, saved to disk, etc. To clone the family, for example, we can duplicate the memory:
\code
std::vector<char> buffer( size );
NvBlastFamily* family2 = reinterpret_cast<NvBlastFamily*>( buffer.data() );
memcpy( family2, family, size );
\endcode
<B>N.B.</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 <em>must</em> 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:
\code
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 );
\endcode
The data in family2 will contain the same actors as the original family. To access them, use:
\code
uint32_t actorCount = NvBlastFamilyGetActorCount( family2, logFn );
std::vector<NvBlastActor*> actors( actorCount );
uint32_t actorsWritten = NvBlastFamilyGetActors( actors.data(), actorCount, family2, logFn );
\endcode
In the code above, actorsWritten should equal actorCount.
<br>
\subsection single_actor_serialization Single Actor Serialization
To perform single-actor serialization, first find the buffer size required to store the serialization data:
\code
size_t bufferSize = NvBlastActorGetSerializationSize( actor, logFn );
\endcode
If you want to use an upper bound which will be large enough for any actor in a family, you may use:
\code
size_t bufferSize = NvBlastAssetGetActorSerializationSizeUpperBound( asset, logFn );
\endcode
Then create a buffer of that size and use NvBlastActorSerialize to write to the buffer:
\code
std::vector<char> buffer( bufferSize );
size_t bytesWritten = NvBlastActorSerialize( buffer, bufferSize, actor, logFn );
\endcode
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):
\code
void* mem = malloc( NvBlastAssetGetFamilyMemorySize( asset, logFn ) );
NvBlastFamily* family = NvBlastAssetCreateFamily( mem, asset, logFn );
\endcode
Then deserialize into the family:
\code
NvBlastActor* newActor = NvBlastFamilyDeserializeActor( family, buffer.data(), logFn );
\endcode
If newActor is not NULL, then the actor was successfully deserialized.
<br>
\section actor_deactivating Deactivating an Actor
Actors may not be released in the usual sense of deallocation. This is because actors' memory is stored as a block within the owning family. The
memory is only released when the family is released. However, one may deactivate an actor using NvBlastActorDeactivate.
This clears the actor's chunk lists and marks it as invalid, effectively disassociating it from the family. The user should consider this actor
to be destroyed.
\code
bool success = NvBlastActorDeactivate( actor, logFn );
\endcode
<br>
\subsection family_releasing Releasing a family
As mentioned above, releasing an actor does not actually do any deallocation; it simply invalidates the actor within its family.
To actually deallocate memory, you must deallocate the family. Note, this will invalidate all actors in the family. This is
a fast way to delete all actors that were created from repeated fracturing of a single instance. As with NvBlastAsset, memory is
allocated by the user, so to release a family with memory allocated by <B>malloc</B>, simply free that memory with <B>free</B>:
\code
free( family );
\endcode
The family will <em>not</em> 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
\code
uint32_t actorCount = NvBlastFamilyGetActorCount( family, logFn );
\endcode
<br>
\section splitting Damage and Fracturing
Damaging and fracturing is a staged process.
In a first step, a \ref NvBlastDamageProgram creates lists of Bonds and Chunks to damage - so called Fracture Commands.
The lists are created from input specific to the NvBlastDamageProgram.<br>
NvBlastDamagePrograms are composed of a \ref NvBlastGraphShaderFunction and a
\ref NvBlastSubgraphShaderFunction operating on support graphs (support chunks and bonds) and disconnected subsupport chunks respectively.
An implementer can freely define the shader functions and parameters.
Different functions can have the effect of emulating different physical materials.<br>
Blast&tm; provides reference implementations of such functions in \ref pageextshaders, see also NvBlastExtDamageShaders.h.
The NvBlastDamageProgram is used through \ref NvBlastActorGenerateFracture that will provide the necessary internal data for the NvBlastActor being processed.
The shader functions see the internal data as \ref NvBlastGraphShaderActor and \ref NvBlastSubgraphShaderActor respectively.
The second stage is carried out with \ref 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 \ref NvBlastFractureBuffers.
The splitting of the actor into child actors is not done until the third stage, \ref NvBlastActorSplit, is called.
Fractures may be repeatedly applied to an actor before splitting.
The \ref NvBlastActorGenerateFracture, \ref NvBlastActorApplyFracture and \ref 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:
\code
// 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 );
\endcode
<br>
*/
|