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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
|
/*! \page pagehlapi High Level (Toolkit) API (NvBlastTk)
<b>Table of Contents</b>
\subpage tkintroduction
\subpage tk_class_hierarchy
\subpage tk_include_and_library
\subpage framework_init
\subpage tkasset_creation
\subpage tkasset_instancing
\subpage tkgroups
\subpage damage_in_tk
\subpage tkjoints
\subpage tkevents
\subpage tktypes
<br>
\section tkintroduction Introduction to NvBlastTk
The high-level API, NvBlastTk (Tk stands for "toolkit"), is intended to be a more powerful library and a much more convenient entry point
into the use of Blast&tm;. Like the low-level library, Tk is physics and graphics-agnostic. Whereas the low-level API is C-style, Tk uses
a C++ API. Everything in Tk is in the namespace:
\code
Nv::Blast
\endcode
(the only exceptions are global-scope functions to create and access a framework singleton, see below).
Every object in Tk is prefixed with 'Tk'. For example, the Tk framework interface is:
\code
Nv::Blast::TkFramework
\endcode
<b>
For the remainder of this page we will be in the Nv::Blast namespace, and will drop the explicit scope Nv::Blast:: from our names.
</b>
<br>
<br>
BlastTk adds:
- An object class hierarchy (see \ref tk_class_hierarchy, below).
- A global framework, <b>TkFramework</b> (a singleton). This keeps track of <b>TkIdentifiable</b> objects and allows
the user to query them based upon either GUID or <b>TkIdentifiable</b> subclass type, and also provides a number of functions to create the various objects in BlastTk.
- Processing groups with a task interface (see <b>TkGroup</b>).
- Event dispatching for actor families (see <b>TkFamily</b>).
- Intra-actor and inter-actor joint management (see <b>TkJoint</b>). Note, these "joints" only hold descriptor data, since physical objects are not handled by BlastTk.
<br>
\section tk_class_hierarchy NvBlastTk Class Hierarchy
- There are two abstract interfaces, one of which deriving from the other: <b>TkObject <- TkIdentifiable</b>.
- Lightweight objects are derived from <b>TkObject</b>.
- Objects which use a GUID and class identification are derieved from <b>TkIdentifiable</b>.
- <b>TkAsset</b> derives from <b>TkIdentifiable</b>. This is mostly a wrapper for NvBlastAsset, however it also stores
extra data associated with the asset such as internal joint descriptors.
- <b>TkFamily</b> derives from <b>TkIdentifiable</b>. One of these objects is made when a <b>TkActor</b> is instanced
from a <b>TkAsset</b>. All actors that are created by splitting the family's original actor remain within the same family. Actor and joint
events are dispatched from the <b>TkFamily</b>.
- <b>TkGroup</b> derives from <b>TkIdentifiable</b>. Groups are processing units. The user may create as many groups as they please, and add
or remove actors as they please from groups. The group provides a worker (TkGroupWorker) interface which allows the user to process multiple
jobs in the group asynchoronously. These jobs, along with a call to TkGroup::endProcess(), perform the tasks of generating fracture commands,
applying fracture commands, and actor splitting at the low-level. The user is informed of splitting through listeners given to TkFamily objects.
- <b>TkActor</b> derives from <b>TkObject</b>. It is mostly a wrapper for NvBlastActor, but it also provides a number of damage functions
to the user.
- <b>TkJoint</b> derives from <b>TkObject</b>. <b>TkAsset</b> descriptors, cause internal <b>TkJoint</b> obejcts to be created within an actor
(joining chunks within the same actor). Alternatively, the TkFramework provides a function which allows the user to create an external joint
between any two different actors. As actors split, internal joints may become external. The user gets notification whenever joints become
external, or when actors joined by joints change or are deleted, through listeners attached to the associated TkFamily objects.
<br>
\section tk_include_and_library Linking and Header Files
To use the BlastTk library, the application need only inlclude the header NvBlastTk.h, found in the <b>include/toolkit</b> folder, and link
against the appropriate version of the NvBlastTk library. Depending on the platform and configuration, various suffixes will be added to the library
name. The general naming scheme is
NvBlastTk(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 framework_init Creating the TkFramework
As a reminder, in this document we assume we are in the Nv::Blast namespace:
\code
using namespace Nv::Blast;
\endcode
In order to use NvBlastTk, one first has to create a TkFramework singleton. This simply requires a call
to the global function NvBlastTkFrameworkCreate:
\code
TkFramework* framework = NvBlastTkFrameworkCreate();
\endcode
The framework may be accessed via:
\code
TkFramework* framework = NvBlastTkFrameworkGet();
\endcode
In the sections that follow, it is assumed that a framework has been created, and we have a pointer to it named 'framework' within scope.
Finally, to release the framework, use
\code
framework->release();
\endcode
This will release all assets, families, actors, joints, and groups.
<br>
\section tkasset_creation Creating a TkAsset
The TkAsset object is a high-level wrapper for the low-level NvBlastAsset (see \ref assets). The descriptor used to create a TkAsset, a TkAssetDesc, is derived from
NvBlastAssetDesc. The base fields should be filled in as described in (\ref assets). The new field is an optional array of flags to be associated with each bond in
the base descriptor. Currently the only flag is "BondJointed," and if set will cause an "internal joint" to be created in actors (TkActor type) created from the asset.
See (\ref tkjoints) for more on joints in BlastTk.
\code
TkAssetDesc desc;
myFunctionToFillInLowLevelAssetFields(desc); // Fill in the low-level (NvBlastAssetDesc) fields as usual
std::vector<uint8_t*> bondFlags(desc.bondCount, 0); // Clear all flags
// Set BondJointed flags corresponding to joints selected by the user (assumes a myBondIsJointedFunction to make this selection)
for (uint32_t i = 0; i < desc.bondCount; ++i)
{
if (myBondIsJointedFunction(i)) // User-authored
{
bondFlags[i] |= TkAssetDesc::BondJointed;
}
}
TkAsset* asset = framework->createAsset(desc); // Create a new TkAsset
\endcode
The createAsset function used above creates a low-level NvBlastAsset from the base fields of the descriptor, and then adds internal joint descriptors based
upon the bonds' centroids and attached chunks. An alternative method to create a TkAsset allows the user to pass in a pre-existing NvBlastAsset, and a list
of joint descriptors. If the TkAsset is to have no internal joints, then the joint descriptors are not necessary and with an NvBlastAsset
pointer <b>llAsset</b>, a TkAsset may be created simply by using
\code
TkAsset* asset = framework->createAsset(llAsset);
\endcode
By default, such a TkAsset will not "own" the llAsset. When the TkAsset is released, the llAsset memory will be untouched. You can pass ownership to the
TkAsset using all of the default parameters of the createAsset function:
\code
TkAsset* asset = framework->createAsset(llAsset, nullptr, 0, true);
\endcode
The last parameter sets ownership. N.B.: in order for the TkAsset to own the underlying llAsset, and therefore release it when the TkAsset is released,
the memory for the llAsset must be allocated using the allocator accessed through NvBlastGlobals (see \ref pageglobalsapi).
If one wants to author internal joints in a TkAsset using this second createAsset method, one must pass in a valid array of joint descriptors of type
TkAssetJointDesc. Each joint descriptor takes two positions and two node indices. The positions are the joint's attachment positions in asset space, and
the nodes indices are those of the graph nodes that correspond to support chunks. These indices are not, in general, the same as the chunk indices.
An example of initialization of the joint descriptors is given below.
\code
std::vector<TkAssetJointDesc> jointDescs(jointCount); // Assume jointCount = the number of joints to add
jointDescs[0].nodeIndices[0] = 0; // Attach node 0 to node 1
jointDescs[0].nodeIndices[1] = 1;
jointDescs[0].attachPoistions[0] = physx::PxVec3( 1.0f, 2.0f, 3.0f ); // Attachment positions are often the same within an asset, but they don't have to be
jointDescs[0].attachPoistions[1] = physx::PxVec3( 1.0f, 2.0f, 3.0f );
// ... etc.
TkAsset* asset = framework->createAsset(llAsset, jointDescs.data(), jointDescs.size());
\endcode
The code above assumes you know the support graph nodes to which you'd like to attach joints. Often, the user only knows the corresponding chunk indices.
Fortunately it's easy to map chunk indices to graph node indices. In order to get the map, use the low-level function
\code
const uint32_t map = NvBlastAssetGetChunkToGraphNodeMap(llAsset, logFn);
\endcode
This map is an array with an entry for every chunk index. To get the graph node index for a chunk indexed <b>chunkIndex</b>, use
\code
uint32_t nodeIndex = map[chunkIndex];
\endcode
If the chunk indexed by <b>chunkIndex</b> does <em>not</em> correspond to a support chunk, then the mapped value will be UINT32_MAX, the invalid index.
Otherwise, the mapped value will be a valid graph node index.
Finally, to release a TkAsset, as with any TkObject-derived object, use the release() method:
\code
asset->release();
\endcode
<br>
\section tkasset_instancing Instancing a TkAsset: Creation of a TkActor and a TkFamily
Whereas with the Blast&tm; low-level (\ref pagellapi), one must explicitly create a family (NvBlastFamily) from an asset (NvBlastAsset) before creating
the first actor (NvBlastActor) in the family, NvBlastTk creates a TkFamily automatically when an unfractured TkActor is instanced from a TkAsset using
the framework's createActor function. This family is accessible through the actor and any actor that is created from splitting it. The family is
<em>not</em> released automatically when all actors within it have been released. The user must use the TkFamily's release() method (see TkObject base
API) to do so. (Or wait until the framework is released.) If a family is released that contains actors, the actors within will be released as well.
The TkFamily has a special role in NvBlastTk, holding user-supplied event listeners (\ref TkEventListener). All <em>internal</em> actor creation and destruction
events are broadcast to listeners through split events (TkSplitEvent). These signal when a fracturing operation has destroyed an actor and created
child actors from it. TkActor creation or release that occurs from an explicit API call do not produce events. For example when creating a first unfractured
instance of an asset using createAsset, or when calling the release() method on a TkActor. TkJoint events are similarly broadcast to
receivers (TkJointEvent). These signal when the actors which are joined by the joints change, so that the user may update a corresponding physical joint.
They also signal when a joint no longer attaches actors and is therefore unreferenced. The user may invalidate or release the joint using the TkObject
release() method when this occurs (more on joint ownership in \ref tkjoints).
To create an unfractured TkActor instance from a TkAsset, one first fills in a descriptor (\ref TkActorDesc) and passes it to the framework's createActor function.
As with the TkAssetDesc, the TkActorDesc is derived from its low-level counterpart, the NvBlastActorDesc. In addition the TkActorDesc holds a pointer to
the TkAsset being instanced. An example of TkActor creation is given below, given a TkAsset pointer <b>asset</b>.
\code
TkActorDesc desc; // The TkActorDesc constructor sets sane default values for the base (NvBlastActorDesc) fields, giving uniform chunk and bond healths of 1.0.
desc.asset = asset; // This field of TkActorDesc must be set to a valid asset pointer.
TkActor* actor = framework->createActor(desc);
\endcode
The TkFamily created with the actor above may be accessed through the actor's getFamily field:
\code
TkFamily& family = actor->getFamily();
\endcode
The returned value is a reference since a TkActor's family can never be NULL. Actors resulting from the split of a "parent" actor will always belong to the
parent's family.
For most applications, the user will need to create a listener object to pass to every family created, in order to keep their physics and graphics representations
in sync with the splitting of the TkActor. For more on this, see \ref tkevents.
<br>
\section tkgroups Groups
One important feature of NvBlastTk is the ability to multitask damage processing. The mechanism by which the toolkit does this is the group object, TkGroup.
Groups are created at the request of the user; the user may create as many groups as he or she likes. Actors may be added or removed from groups in any way the
user wishes, with the only constraint being that a given actor may belong to no more than one group. A group is a processing object, much like a scene in a physics
simulation. Indeed, a natural pattern would be to associate one group per physics scene, and synchronize the group processing with scene simulation. Another
pattern would be to subdivide the world into neighborhoods, and associate each neighborhood with a group. A distributed game could take advantage of this structure
to similarly distribute computation.
Group processing is performed by <em>workers</em>, which have a TkGroupWorker API exposed to the user. The number of workers may be set by the user, with the idea being
that this should correspond to the number of threads available for group processing. Processing starts with a call to TkGroup::startProcess(). This creates a number
of jobs which the user may assign to workers as they like, each worker potentially on its own thread. The jobs calculate the effects of all damage taken
by the group's actors. After all jobs have been run, the user must call TkGroup::endProcess(). This will result in all events being fired off to listeners associated
with families with actors in the group.
A convenience function, TkGroup::process(), is provided which uses one worker to perform all jobs sequentially on the calling thread. This is useful shortcut to
get BlastTk up and running quickly. A multithreaded group processing implementation is given by Nv::Blast::ExtGroupTaskManager (in NvBlastExtPxTask.h).
This resides in \ref pageextphysx, because it uses physx::PxTask.
Actors resulting from the split of a "parent" actor will be placed automatically into the group that the parent belonged to. This is similar to the assigment of
families from a split, except that unlike families, the user then has the option to move the new actors to other groups, or no group at all.
Also similar to families, groups are not automatically released when the last actor is removed from it. Unlike families, when a group is released, the actors which
belong to the group are <em>not</em> released. They will, however, be removed from the group before the release is complete.
A typical usage is outlined below. See \ref damage_in_tk for methods of applying damage to actors.
\code
// Create actors from descriptors desc1, desc2, ... etc., and attach a listener to each new family created
TkActor* actor1 = framework->createActor(desc1);
actor1->getFamily().addListener(gMyReceiver); // gMyReceiver is a TkEventListener-derived object. More on events in a subsequent section.
TkActor* actor2 = framework->createActor(desc2);
actor2->getFamily().addListener(gMyReceiver);
TkActor* actor3 = framework->createActor(desc3);
actor3->getFamily().addListener(gMyReceiver);
// etc...
// Let's create two groups. First, create a group descriptor. This may be used to create both groups.
TkGroupDesc groupDesc;
groupDesc.workerCount = 1; // this example processes groups on the calling thread only
// Now create the groups
TkGroup* group1 = framework->createGroup(groupDesc);
TkGroup* group2 = framework->createGroup(groupDesc);
// Add actor1 and actor2 to group1, and actor2 to group3...
group1->addActor(actor1);
group1->addActor(actor2);
group2->addActor(actor3);
// etc...
// Now apply damage to all actors - *NOTE* damage is described in detail in the next section.
// For now we will just assume a "myDamageFunction" to apply the damage.
myDamageFunction(actor1);
myDamageFunction(actor2);
myDamageFunction(actor3);
// etc...
// Calling the groups' process functions will (synchronously) run all jobs to process damage taken by the contained actors.
group1->process();
group2->process();
// When the groups are no longer needed, they may be released with the usual release method.
group1->release();
group2->release();
\endcode
<br>
<b>Multithreaded processing</b>
When distributing the jobs as mentioned above, every job must be processed exactly once (over all user tasks).
The number of jobs processed per worker can range from a single job (resulting in a user task per job) to all jobs (like Nv::Blast::TkGroup::process() does).
At any point in time, no more than the set workerCount amount of workers may have been acquired. Return the worker at the end of each task.
\code
Nv::Blast::TkGroupWorker* worker = group->acquireWorker();
// process some jobs
group->returnWorker(worker);
\endcode
<br>
\section damage_in_tk Applying Damage to Actors and Families
Damage in NvBlastTk uses the same damage program scheme as the low-level SDK (see \ref splitting). One passes the program
(NvBlastDamageProgram), damage descriptor (program-dependent), and material (also program-dependent) to a TkActor::damage
function. Ultimately, the damage descriptor and material data are all parameters used by the damage program. The distinction
is that the damage descriptor should describe properties of the thing doing the damage, while the material should
describe properties of the actor (the thing being damaged). The interpretation of this data is entirely up to the program's
functions, however.
For convenience, the user may set a default material in the actor's family. This assumes, of course, that the material parameters
for this default are compatible with the program being used to damage the family's actors.
Examples of the three TkActor damage methods are given below.
<br>
\subsection multiple_damage Multiple Damage Descriptors using NvBlastProgramParams
<b>N.B. - with this method of damage, the lifetime of the NvBlastProgramParams <em>must</em> extend at
least until the TkGroup::endProcess call for the actor.</b>
\code
NvBlastDamageProgram program =
{
myGraphShaderFunction, // A function with the NvBlastGraphShaderFunction signature
mySubgraphShaderFunction // A function with the NvBlastSubgraphShaderFunction signature
};
// The example struct "RadialDamageDesc" is modeled after NvBlastExtRadialDamageDesc in the NvBlastExtShaders extension
RadialDamageDesc damageDescs[2];
damageDescs[0].compressive = 10.0f;
damageDescs[0].position[0] = 1.0f;
damageDescs[0].position[1] = 2.0f;
damageDescs[0].position[2] = 3.0f;
damageDescs[0].minRadius = 0.0f;
damageDescs[0].maxRadius = 1.0f;
damageDescs[1].compressive = 100.0f;
damageDescs[1].position[0] = 3.0f;
damageDescs[1].position[1] = 4.0f;
damageDescs[1].position[2] = 5.0f;
damageDescs[1].minRadius = 0.0f;
damageDescs[1].maxRadius = 5.0f;
// The example material "Material" is modeled after NvBlastExtMaterial in the NvBlastExtShaders extension
Material material;
material.health = 10.0f;
material.minDamageThreshold = 0.1f;
material.maxDamageThreshold = 0.8f;
// Set the damage params struct
NvBlastProgramParams params = { damageDescs, 2, &material };
// Apply damage
actor->damage(program, ¶ms); // params must be kept around until TkGroup::endProcess is called!
\endcode
<br>
\subsection single_damage_desc_default_material Single Damage Descriptor with Default TkFamily Material
This method of damage copies the damage descriptor into a buffer, so the user need <em>not</em> hold onto
a copy after the damage function call. Only one damage descriptor may be passed in at once.
To use this method, the user must first set a default material in the actor's family. For example:
\code
// The example material "Material" is modeled after NvBlastExtMaterial in the NvBlastExtShaders extension
Material material;
material.health = 10.0f;
material.minDamageThreshold = 0.1f;
material.maxDamageThreshold = 0.8f;
// Set the default material used by the material-less TkActor::damage call
actor->getFamily().setMaterial(&material);
\endcode
<b>N.B. the lifetime of the material set <em>must</em> extend at least until the TkGroup::endProcess call for the actor.</b>
Then to apply damage, use:
\code
NvBlastDamageProgram program =
{
myGraphShaderFunction, // A function with the NvBlastGraphShaderFunction signature
mySubgraphShaderFunction // A function with the NvBlastSubgraphShaderFunction signature
};
// The example struct "RadialDamageDesc" is modeled after NvBlastExtRadialDamageDesc in the NvBlastExtShaders extension
RadialDamageDesc damageDesc;
damageDesc.compressive = 10.0f;
damageDesc.position[0] = 1.0f;
damageDesc.position[1] = 2.0f;
damageDesc.position[2] = 3.0f;
damageDesc.minRadius = 0.0f;
damageDesc.maxRadius = 1.0f;
// Apply damage
actor->damage(program, &damageDesc, (uint32_t)sizeof(RadialDamageDesc));
\endcode
<br>
\subsection single_damage_desc_with_material Single Damage Descriptor with Specified Material
This method is just like the one above, except that the user has the opportunity to override the material used during damage.
<b>N.B. - the lifetime of the material passed in <em>must</em> extend at least until the TkGroup::endProcess call for the actor.</b>
This call is just like the one above with an extra material parameter:
\code
actor->damage(program, &damageDesc, (uint32_t)sizeof(RadialDamageDesc), &material);
\endcode
<br>
\section tkjoints Joints
Joints in NvBlastTk are abstract representations of physical joints. When joints become active, change the actors they join,
or become unreferenced (the actors they join disappear), the user will receive notification via a TkJointUpdateEvent
(see \ref tkevents).
Joints may be defined as a part of a TkAsset, in which case they are consisdered "internal" joints. (See \ref tkasset_creation.)
Since the first instance of a TkAsset is a single TkActor, internal joints are defined between chunks within the same actor.
Therefore they are not active (there is no point in joining two locations in a single rigid body). Upon splitting into multiple
actors, however, an internal joint's chunks may now belong to two different TkActors. When this happens, the user will receive a
TkJointUpdateEvent of subtype TkJointUpdateEvent::External. The event contains a pointer to the TkJoint, and from that the user
has access to the information needed to create a physical joint between the rigid bodies that correspond to the joined TkActors.
Joints may also be created externally at runtime, using the TkFramework::createJoint function. A joint created this way must
be between two different TkActors. Because of this, the joint is immediately considered active, and so no TkJointUpdateEvent
is generated from its creation. The user should create a physical joint to correspond to the joint returned by createJoint.
An externally created joint of this type has another distinguishing characteristic: it may join an actor to "the world," or
"Newtonial Reference Frame" (NRF). To do this, one TkFamily pointer in the joint descriptor is set to NULL. Examples are
given below.
\code
TkJointDesc desc;
desc.families[0] = &actor0->getFamily(); // Assume we have a valid actor0 pointer
desc.chunkIndices[0] = 1; // This chunk *must* be a support chunk in the asset that created desc.families[0]
desc.attachPositions[0] = physx::PxVec3(1.0f, 2.0f; 3.0f); // The attach position is in asset space
desc.families[1] = &actor1->getFamily(); // Assume we have a valid actor1 pointer... note, actor0 and actor1 could have the same family
desc.chunkIndices[1] = 10; // This chunk *must* be a support chunk in the asset that created desc.families[1]
desc.attachPositions[1] = physx::PxVec3(4.0f, 5.0f; 6.0f); // The attach position is in asset space
// Create the external joint from the descriptor, which joins actor0 and actor1
TkJoint* joint = framework->createJoint(desc);
// Now join actor0 to the NRF
// desc.families[0] already contains actor0's family
desc.chunkIndices[0] = 2; // Again, this chunk must be a support chunk in the asset that created desc.families[0]
desc.attachPositions[0] = physx::PxVec3(0.0f, 0.0f; 0.0f); // The attach position is in asset space
desc.families[1] = nullptr; // Setting the family to NULL designates the world (NRF)
// The value of desc.chunkIndices[1] is not used, since desc.families[1] is NULL
desc.attachPositions[1] = physx::PxVec3(0.0f, 0.0f, 10.0f); // Attach position in the world
// Create the external joint which joins actor0 to the world
TkJoint* jointNRF = framework->createJoint(desc);
\endcode
<br>
\subsection releasing_joints Releasing Joints
TkJoints are not released by Blast&tm;, except when the TkFramework is released. Otherwise, the user is responsible for
releasing TkJoints after they become unreferenced. This is facilitated by the Unreferenced subtype of the TkJointUpdateEvent. After
receiving this event for joint, the user may choose to release, using the typical TkObject::release() method.
\code
joint->release();
\endcode
Note, this method can be called <em>at any time</em>, even before the joint is unreferenced. When called, it will remove its
references to its attached actors first, causing the joint to then become unreferenced. For example, if the user wishes to break
a physical joint in their simulation, they can then release the corresponding TkJoint.
It should be mentioned, however, that joints created with an asset are allocated differently from external joints created using
TkFramework::createJoint. Internal joints created from the joint descriptors in a TkAsset are <em>block allocated</em> with every
TkFamily that instances the asset. Calling the release() method on those joints will remove any remaining references to them
(as mentioned above), but will not perform any deallocation. Only when the TkFamily itself is released will the internal joint
memory for that family be released. <b>This is true even if the internal joints become "external" from actor splitting.</b> Joints
that <em>become</em> external are still associated with a single family and their memory still resides with that family.
On the other hand, joints that start out life external by way of the TkFramework::createJoint function have a separate allocation,
and do not have memory tied to any TkFamily (even if both actors joined are in the same family). Releasing a family holding one
of the actors in such a "purely external" joint will trigger a TkJointUpdateEvent of subtype Unreferenced, however, signalling that
the joint is ready for user release.
<br>
\section tkevents Events
NvBlastTk uses events to communicate the results of actor splitting, joint updates from actor splitting, and fracture event buffers
that can be used to synchronize fracturing between multiple clients.
Events are broadcast to listeners which implement the TkEventListener interface. Listeners are held by TkFamily objects. During
a TkGroup::endProcess call (see \ref tkgroups), relevant events are broadcast to the listeners in the families associated with the actors
in the group.
A typical user's receiver implementation might take on the form shown below.
\code
class MyActorAndJointListener : public TkEventListener
{
// TkEventListener interface
void receive(const TkEvent* events, uint32_t eventCount) override
{
// Events are batched into an event buffer. Loop over all events:
for (uint32_t i = 0; i < eventCount; ++i)
{
const TkEvent& event = events[i];
// See TkEvent documentation for event types
switch (event.type)
{
case TkSplitEvent::EVENT_TYPE: // A TkActor has split into smaller actors
{
const TkSplitEvent* splitEvent = event.getPayload<TkSplitEvent>(); // Split event payload
// The parent actor may no longer be valid. Instead, we receive the information it held
// which we need to update our app's representation (e.g. removal of the corresponding physics actor)
myRemoveActorFunction(splitEvent->parentData.family, splitEvent->parentData.index, splitEvent->parentData.userData);
// The split event contains an array of "child" actors that came from the parent. These are valid
// TkActor pointers and may be used to create physics and graphics representations in our application
for (uint32_t j = 0; j < splitEvent->numChildren; ++j)
{
myCreateActorFunction(splitEvent->children[j]);
}
}
break;
case TkJointUpdateEvent::EVENT_TYPE:
{
const TkJointUpdateEvent* jointEvent = event.getPayload<TkJointUpdateEvent>(); // Joint update event payload
// Joint events have three subtypes, see which one we have
switch (jointEvent->subtype)
{
case TkJointUpdateEvent::External:
myCreateJointFunction(jointEvent->joint); // An internal joint has been "exposed" (now joins two different actors). Create a physics joint.
break;
case TkJointUpdateEvent::Changed:
myUpdatejointFunction(jointEvent->joint); // A joint's actors have changed, so we need to update its corresponding physics joint.
break;
case TkJointUpdateEvent::Unreferenced:
myDestroyJointFunction(jointEvent->joint); // This joint is no longer referenced, so we may delete the corresponding physics joint.
break;
}
}
// Unhandled:
case TkFractureCommands::EVENT_TYPE:
case TkFractureEvents::EVENT_TYPE:
default:
break;
}
}
}
};
\endcode
Whenever a new TkActor is created by the user (via TkFramework::createActor, see \ref tkasset_instancing), its newly-made family should
be given whatever listeners the user wishes to attach. For example,
\code
TkActor* actor = framework->createActor(actorDesc);
actor->getFamily().addListener(myListener); // myListener is an object which implements TkEventListener (see MyActorAndJointListener above, for example)
\endcode
Listeners may also be removed from families at any time.
<br>
\section tktypes Object and Type Identification
NvBlastTk objects that are derived from TkIdentifiable (TkAsset, TkFamily, and TkGroup) support an object and class (type) identification
system. The TkIdentifiable interfaces setID and getID allow the user to set and access an NvBlastID for each object. The NvBlastID
is a 128-bit identifier. TkIdentifiable objects are tracked by the TkFramework, which may be used to look up an object by its NvBlastID.
Upon creation, TkIdentifiable objects are given a GUID, a unique NvBlastID. The user is welcome to change the object's guid at any time, with
the restriction that the GUID cannot be all zero bytes.
With an object's GUID, one may look up the object using the TkFramework function findObjectByID:
\code
TkIdentifiable* object = framework->findObjectByID(id); // id = an NvBlastID GUID
\endcode
If the object is found, a non-NULL pointer will be returned.
TkIdentifiable-derived classes also have a class identification system, the TkType interface. From an individual object one may use the
TkIdentifiable interface getType to access the class's TkType interface. Alternatively, one may use the TkFramework getType function
with TkTypeIndex::Enum argument. For example, to get the TkType interface for the TkAsset class, use
\code
const TkType* assetType = framework->getType(TkTypeIndex::Asset);
\endcode
The type interface may be used:
- to access class-specific object lists in the framework,
- identify the class of a TkIdentifiable obtained through ID lookup or deserialization, or
- to obtain the class's name and format version number.
For example, to access a list of all families:
\code
// Get the TkFamily type interface
const TkType* familyType = framework->getType(TkTypeIndex::Family);
// Get the family count to allocate a buffer
const uint32_t familyCount = framework->getObjectCount(familyType);
std::vector<TkIdentifiable*> families(familyCount);
// Write the families to the buffer
const uint32_t familiesFound = framework->getObjects(families.data(), familyCount, familyType);
\endcode
In the above code, the values of familyCount and familiesFound should be equal. An alternative usage of TkFramework::getObjects allows the
user to write to a (potentially) smaller buffer, iteratively. For example:
\code
uint32_t familiesFound;
uint32_t totalFamilyCount = 0;
do
{
// Write to a fixed-size buffer
TkIdentifiable* familyBuffer[16];
familiesFound = framework->getObjects(familyBuffer, 16, familyType, totalFamilyCount);
totalFamilyCount += familiesFound;
// Process the families found so far
myProcessFamiliesFunction(familyBuffer, familiesFound);
} while (familiesFound == 16);
\endcode
To use the type interface to identify a class, perhaps after serialization or lookup by ID, one may do something like:
\code
\\ Assume we have a TkIdentifiable pointer called "object"
// Get the type interfaces of interest
const TkType* assetType = framework->getType(TkTypeIndex::Asset);
const TkType* familyType = framework->getType(TkTypeIndex::Family);
if (object->getType() == *assetType)
{
TkAsset* asset = static_cast<TkAsset*>(object);
// Process the object as a TkAsset
}
if (object->getType() == *familyType)
else
{
TkFamily* family = static_cast<TkFamily*>(object);
// Process the object as a TkFamily
}
\endcode
A TkIdentifiable-derived class may be queried for its name using the TkType interface, using TkType::getName().
This function returns a const char pointer to a string.
Finally, one may query the class for its current format version number using TkType::getVersion().
<br>
*/
|