Physical engine Havok tutorial (2)
Havok Basics
Havok sdks are complicated and not suitable for learning. Taking the demo program framework used to demonstrate the effect as an example, its implementation is really mysterious. Beginners will be exposed to a large amount of code from the very beginning, and it is estimated that the enthusiasm will be greatly frustrated. So in order to make it easier for everyone to learn, I will mainly use the actual code to introduce the various features of the SDK during the tutorial. I will try my best to make the code concise and popular. I will sort out the sample code and provide links for you to download. If you think it is good, everyone should support it!
Havok SDK can be divided into three parts: Havok basic library, Havok physical component, and Havok animation component. The Basic Library provides support for common functions for other components of Havok. the physical component of Havok is responsible for real-time Rigid Body simulation, and the animation component of Havok is responsible for processing skeleton and role animation. It works with physical components, it can implement powerful role animation control functions.
For more information about the code habits of the Havok SDK, all the class names of the Havok SDK basically start with "HK *", and then follow a character to indicate the component to which it belongs, for example, hkpworld, it belongs to the physical component (Havok physics) and hkabone, indicating that it belongs to the animation component (Havok animation). It is not followed by a character indicating the component after HK, it indicates that it is a part of the Havok base database.
Havok base library
The Havok base database defines some basic data types and container classes. It also includes platform-independent interfaces for accessing operating system resources, such as memory management and clock. Many classes in the basic library can be modified or replaced. By providing the source code, you can flexibly extend the functions of the basic library. However, some parts cannot be replaced because they are compiled into the Havok library, including the container classes with inline functions, there are also the configuration options under the config directory (it stores the configuration when compiling the Havok library, and any changes to it need to re-build the entire Havok library ).
To use the basic library, simply include hkbase. h. Some basic data types are also defined in hkbase. H, such as hkreal, signed and unsigned shaping.
1. Havok Basic System
1.1 Havok Initialization
The hkbasesystem class is responsible for creating all Havok subsystems. Most of these subsystems are single-State classes (Singleton) with specific functions, such as memory management, error processing, and stream processing.
The main interfaces of these classes are init and quit methods. Call the init method to initialize the Havok subsystem.
Static hkresult hk_call Init (hkmemory * memorymanager,
Hkthreadmemory * threadmemory,
Hkerrorreportfunction errorreportfunction
Void * errorreportobject = hk_null );
The memorymanager parameter is an implementation of the Memory Manager that will be used internally by Havok. This parameter allows you to specify the Memory Manager during initialization without re-constructing the hkbase. Havok provides many default Memory Manager implementations. You can use them without special requirements. Of course, you can also implement your own memory manager. The hkpoolmemory class is recommended for Havok. The implementation of this memory manager and other available implementations can be found under the The threadmemory parameter is the memory manager of the current thread. It can be used to optimize the memory allocation and release performance. If you input hk_null as the parameter, the default hkbasesystem: Init () will create an hkthreadmemory instance for you.
The errorreportfunction and errorreportobject parameters are used by the Havok error processor. The error processor is mainly responsible for processing assertions, errors, and warnings, or reporting the occurrence of these events within the engine. The default error processor is
The following code uses hkpoolmemory to initialize hkbasesystem:
# Include <Common/base/system/hkbasesystem. h> // include for hkbasesystem
# Include <Common/base/memory/pool/hkpoolmemory. h> // hkpoolmemory
Extern "C" int printf (const char * FMT,...); // for printf, used by the error handler
// Stub function to print any error report functionality to stdout
// STD: puts (...) cocould be used here alternatively
Static void errorreportfunction (const char * STR, void * erroutputobject)
{
Printf ("% s", STR );
}
{
...
Hkbasesystem: Init (New hkpoolmemory (), hk_null, errorreportfunction );
...
}
Hkbasesystem also has a quit method, which exits the memory system and destroys all hksingleton instances.
1.2 Havok multi-thread Mode Initialization
Unlike physx, Havok currently uses CPU computing, so Havok uses multithreading to improve performance.
Every thread used by Havok has its own hkthreadmemory Memory Manager, which must be initialized at the beginning of the thread and destroyed at the end of the thread. Hkthreadmemory can be used to cache memory blocks when memory is destroyed and re-allocated.
The following code demonstrates initialization in the main thread.
Hkmemory * memorymanager = new hkpoolmemory ();
Hkthreadmemory threadmemory (memorymanager, 16 );
Hkbasesystem: Init (memorymanager, & threadmemory, errorreportfunction );
Memorymanager-> removereference ();
1.2 manage Havok objects
Havok uses reference count to manage objects.
Hkbaseobject is the base class of all Havok classes and has only virtual functions. Hkreferencedobject is a subclass of hkbaseobject. It is the most basic unit for Havok object management. Rigid Bodies, constraints, and actions are both hkreferencedobject. When hkreferencedobject is just created, the reference count is 1. When it is referenced once, the reference count is incremented by one. When the reference is deleted, the reference count is reduced by one. When hkreferencedobject is not used, call removereference () to reduce the reference count by one.
M_world-> addentity (Rigidbody );
Rigidbody-> removereference ();
2. Havok container class
Havok provides two container classes: arrays and maps.
Hkarray is the default array class of Havok. It is very similar to the STL array class, but there are some key differences. First, it only supports simple data types. If you want to create an array of objects, you should use hkobjectarray instead of hkarray. The hkarray method is named differently from STL, for example, push_back () in STL, and pushback () in hkarray (). Moreover, hkarray does not save the order of elements. For example, when removeat () is called to delete an element, the deleted element is replaced with the last element to improve the performance, the removeatandcopy () method provides the same implementation as STL, and stores the order of elements. In addition, hkinplacearray, which is a subclass of hkarray, is superior to hkarray in performance. For other methods of the array class, you can view the SDK documentation. I will not describe them here.
3. Havok Math Library
Havok math provides some data types related to linear algebra, such as vector, matrix, and 4 elements. Hkmath has different data types on different platforms, and Havok has made corresponding optimizations for different platforms.
First, let's take a look at the basic data types provided by hkmath, including
Hkreal's default floating point type, float
The hkquadreal type uses the SIMD register of the CPU, occupying 4 hkreal space.
When simdreal is enabled, a separate hkreal is stored in the SIMD register, and the X part is an hkquadreal.
When SIMD is banned, it is just a simple hkreal type.
Other hkmath functions include hkmath: SQRT () and hkmath: sin (). You can view the SDK documentation.
In the SDK, the data types related to linear algebra are called compund types ). They include:
Hkvector4 is a common vector class in Havok. It optimizes most platforms, and most other mathematical classes are composed of it. Each hkvector4 has four hkreal elements. For ease of operation, only the first three elements X, Y, Z, and W are used. The default value is zero.
Hkquaternion, which can be used to indicate rotation. In most cases, it is considered to be a unitized Quaternary.
Hkmatrix3 hkreal is a 3x3 matrix.
The hkrotation class stores orthogonal rotation matrices. In Havok, there are two ways to save rotation: hkquaternion and hkrotation. If you rotate hkvector4, the operation to rotate hkvector4 is faster than to rotate hkquaternion. Hkquaternion is more efficient when a large number of images need to be stored and rotated.
Hktransform indicates a rotation and translation ). It consists of an hkrotation and hkvector4 respectively.
Hkqstransform is a decomposed transform (translation vector + four original number + scaling vector ). Havok animation systems often use it.
Here is a brief introduction to the math library. I will explain how to use and pay attention to the specific code.
4. serialization)
The so-called serialization means to convert data into a writable format through certain processing. After serialization, the raw data should be precisely restored. That is to say, the serialized data can be securely stored on the disk or transmitted over the network. In the game development field, serialization is usually used to store and load resources.
Packfiles (I do not know how to translate)
You can use the Havok serialization tool to serialize any data objects. After an object is serialized, all its information and status are precisely saved. This object can be loaded again as needed.
The serialized data is stored in a structure called packfiles. It can be saved as XML or binary. Using the Havok export plug-in, you can save data from Maya and 3D Studio Max to packfiles, and then you can reprint the data in the Havok SDK.
Load packfiles to the game using hkxmlpackfilereader and hkbinarypackfilereader. A packfile in XML format has nothing to do with the platform, and can be created and loaded on any platform. Binary packfiles are specific to the corresponding platform, so they can only be loaded on the specified target platform.
Packfiles can also be converted from one form to another. Use hkxmlpackfilereader to read XML, and then use hkbinarypackfilewriter to convert it into binary format. Conversely, a binary packfile reads data using hkbinarypackfilereader, and hkxmlpackfilewriter converts the data to XML.
4.1 load game data
The game data loading function is provided by the namespace hkserializeutil and hkloader tool classes. They detect whether the data format is XML or binary. If the provided data is created in another version of the SDK, it will update the content to the latest version during loading.
The following code demonstrates the usage of the hkloader tool class:
Hkresource * loadeddata = hkserializeutil: load ("filename. HKx ");
Hkrootlevelcontainer * Container = loadeddata-> getcontent // The loaded data belongs to hkresource. Make sure that hkresource is not destroyed when you access the container.
Hkloader loader;
Hkrootlevelcontainer * Container = loader. Load ("filename. HKx ");
// The loaded data belongs to hkloader. Same as above, you must also ensure that hkloader is not destroyed when you access the container.
After a packfile is loaded, additional processing is required. For example, if you need to set a virtual function table for a multi-state object, cache data or pointers for non-serialized data will also be initialized. Any class that requires final processing is registered by hktypeinforegistry to complete final processing. Once data loading is complete, the packfile reader calls the registration function for each object that requires final processing.
4.2 save game data
Objects containing metadata in the game can also be serialized and saved as packfile. The classes used are hkxmlpackfilewriter and hkbinarypackfilewriter. If the object to which the Pointer Points does not contain any metadata, the operation is skipped and a null pointer is written.
Hkostream ostream (filename );
Kxmlpackfilewriter writer;
Writer. setcontents (& simpleobject, simpleobjectclass );
Kpackfilewriter: Options options; // use default options
Writer. Save (ostream. getstreamwriter (), options );
You can use hkbinarypackfilewriter to export packages in different binary formats for different platforms. To perform this operation, set hkpackfilewriter: Options: m_layout to the desired platform. Hkstructurelayout contains several supported platforms/compiling environments. The exported data to be read is read using hkbinarypackfilereader on the target platform in the same way as above.
Hkostream ostream (filename );
Hkbinarypackfilewriter writer;
Writer. setcontents (& simpleobject, simpleobjectclass );
Hkpackfilewriter: Options options;
// Export data in playstation2 gcc3.2 format
Options. m_layout = hkstructurelayout: gcc32ps2layoutrules;
Writer. Save (ostream. getstreamwriter (), options );
5. snapshot tool set
Havok also provides a snapshot utility, which can easily save the world and hide the underlying details.
The following code demonstrates how to save world:
// Save the world into the file at "path"
Static hkresult saveworld (hkpworld * World, const char * path, bool binary)
{
Hkostream OUTFILE (PATH );
Return hkphavoksnapshot: Save (World, OUTFILE. getstreamwriter (), binary );
}
Note: If you encounter an object that you do not understand when saving the file, a warning message is printed on the console.
The code for loading a snapshot is similar to that for saving:
// Loads snapshot at "path ".
Static hkpworld * loadworld (const char * path, hkpackfilereader: allocateddata ** allocsout)
{
Hkistream infile (PATH );
Hkpphysicsdata * physicsdata = hkphavoksnapshot: load (infile. getstreamreader (), allocsout );
Return physicsdata-> createworld ();
}
6. Multithreading
Havok is specially designed to run in a multi-threaded environment. This section briefly introduces the working principle of Havok multithreading and how to use multithreading in physical simulation. To implement multi-thread running of Havok, two classes are required: hkjobqueue and hkjobthreadpool.
Hkjobqueue class. A work queue is the core container for job completion. A job is a unit of work that can be processed by any thread. A thread can add a job to the queue or request a job from the queue.
Hkjobqueue: processalljobs () can be called by multiple threads at the same time, and each thread starts to process jobs until they know that there is no job in the queue. Hkjobthreadpool is an abstract class that allows multithreading to process jobs from a work queue. Typically, only one hkjobthreadpool instance is created throughout the application lifecycle. This ensures that a group of threads are always running. When you call the hkjobthreadpool: processalljobs () method, it will simply call the hkjobqueue: processalljobs () method on all worker threads. This function is not returned until all work is completed.
Havok supports physical, animation, collision query, fabric, and bihavior operations in multi-threaded environments. Theoretically, you can add a job to hkjobqueue and call processalljobs () to perform all the preceding calculations in parallel. But in theory, there are still many synchronization problems, which makes it impossible to completely parallel.
Here we will only introduce how to run the physical simulation of Havok in a multi-threaded environment. Other components are similar.
6.1 multi-thread Physical Simulation
The Havok SDK carries a separate example to demonstrate how to perform physical simulated multi-threaded computing. The code is in the demo/standalonedemos/consoleexamplemt directory.
To enable multi-threaded physical simulation, you must set hkpworldcinfo: m_simulationtype to hkpworldcinfo: simulation_type_multithread when creating the world. The code is demonstrated as follows:
...
// Create thread pool and Job Queue
Hkjobthreadpool * threadpool = new hkcpujobthreadpool (threadpoolcinfo );
Hkjobqueue * jobqueue = new hkjobqueue ();
// Create a physics world as usual.
// Make sure the simulation type in worldcinfo is simulation_type_multithreaded
Hkpworld * physicsworld = new hkpworld (worldcinfo );
// Register physics job handling functions with Job Queue
Physicsworld-> registerwithjobqueue (& jobqueue );
While (simulating)
{
// Step the world using this thread, and all the threads or SPUs in the thread pool
Physicsworld-> stepmultithreaded (& jobqueue, threadpool, timestep );
}
Stepmultithreaded () internally calls hkjobqueue: processalljobs () and hkjobthreadpool: processalljobs () to complete the actual multi-thread operation.
Now, the second tutorial is over. Because the basic library of the Havok SDK is large, I just took out the important and frequently used content. For more information, see the SDK documentation.