Saving .red Files
Before going any further, the first thing to do is to create a .red file:
Creating a .red File
The .red file API is exposed through the RED::IREDFile
interface. To create an instance of a .red file, you need to use the RED::Factory
:
RED::Object* red_file = RED::Factory::CreateInstance( CID_REDFile );
and get back its interface with:
RED::IREDFile* ired_file = red_file->As< RED::IREDFile >();
The last thing to do is to get access to the file content:
Case of File on Disk
RC_TEST( ired_file->Create( "./my_file.red" ) );
More Generic Case of a RED::IStream
// stream is a pointer to a RED stream.
RC_TEST( ired_file->Create( stream ) );
Note
A file can be stored on disk or in memory depending on the method you used to create/load it through an abstraction provided by HOOPS Luminate: RED::IStream
(see Creating RED Streams).
File Header and Extra Information
A file header (RED::FileHeader
) is always present in a valid .red file. It gives information about the process which wrote the file and if data are encrypted or not. By default, you don’t have anything to do to set this header and any .red file will be saved with default header values. Otherwise, you can set your own information by using RED::IREDFile::SetAuthor
and RED::IREDFile::SetParentApplication
.
An additional optional chunk of information can be written to any .red file: the RED::FileInfo
. It gives extra information about the file content which can be seen as hints about how to process and render data. Notably, it contains a table of correspondence between RED::Object
IDs and human readable names (RED::FileInfo::GetIDToNamesMap
).
Saving Data
The most common usage of data saving in HOOPS Luminate is when you have a full set of data in memory (meshes, materials, lights, textures…) assembled in a DAG and want to make a file out of them (because you want to send the whole thing to a distant render server or just communicate with other developers or customers).
Streaming a DAG to a .red File
Once you get a pointer to the root of your DAG (and your file is properly created), you can stream its whole content to a .red file very easily:
// root is a pointer to a DAG root (or any other shape into a DAG)
// ired_file is a pointer to a RED::IREDFile interface
// resmgr is a pointer to the RED resource manager
RED::StreamingPolicy policy;
RC_TEST( ired_file->WriteDAG( root, policy, resmgr ) );
This call ensures that all the dependencies are saved together with the DAG data (including materials, shaders, textures and so on). You get the whole thing into a single compressed archive which can be reloaded by any other HOOPS Luminate-based application, on any platform.
Note
You can customize the way data are compressed by specifying additional parameter values to the RED::StreamingPolicy
instance (see Defining .red File Policy for further details)
But you also may want to save only a single data or just the things you want to by calling more atomic methods of the RED::IREDFile class (for an example, see How to Load and Save Image to .red File?).
You can also save your own data (not only RED ones) by declaring new chunks to the .red file format manager and adding support for them through callbacks. See under the section below Extending the .red file format for details.
The Streaming Policy
The RED::StreamingPolicy
describes the actions to take when loading or saving a RED file. This is where you can control the behaviour of the data encoders, the default actions to perform on loading unknown data, but also where you set some other miscellaneous informations.
By default, every data is compressed in a .red file using a dedicated encoder. All the encoders perform lossless compression and are used only to try to get files with the minimal bytes size. As the encoder for geometry normals may be different from the one used for vertices, each encoder is associated to a geometry channel by default. Because your data may not be actually bound following the default HOOPS Luminate binding, you can override the encoders associations by using the RED::StreamingPolicy::SetChannelBinding
method (which describes what data is expected on each geometry channel).
The RED::StreamingPolicy
lets you also define a label which can be used to identify parts of a DAG which are roots of animation tracks. Because the .red file format supports saving and replaying of animations, you may need to distinguish between different animated parts and save them in separate animation tracks. This will be covered more in depth in this document in under the section below Recording Animation Tracks.
Defining .red File Policy
The streaming policy controls various aspects of the saving (loading) of .red files.
Here is how you inform HOOPS Luminate of a custom geometry channel data binding (this is needed in order for HOOPS Luminate to use the right compression code regarding to the type of the data being processed: for example, the compressor used is not the same for indices or vertices):
RED::StreamingPolicy policy;
// Here is an example of a custom geometry channels binding:
//
// Vertices are stored in RED::MCL_VERTEX, normals in RED::MCL_USER0 and texture coordinates
// in RED::MCL_COLOR and RED::MCL_USER1.
//
// Detailing the custom binding used lets the engine use the appropriate compressor for the
// channel data and leads to better compression ratio.
//
policy.SetChannelBinding( RED::MCL_VERTEX, RED::StreamingPolicy::DAK_VERTICES );
policy.SetChannelBinding( RED::MCL_USER0, RED::StreamingPolicy::DAK_NORMALS );
policy.SetChannelBinding( RED::MCL_COLOR, RED::StreamingPolicy::DAK_TEXCOORD );
policy.SetChannelBinding( RED::MCL_USER1, RED::StreamingPolicy::DAK_TEXCOORD );
The streaming policy can also be used to filter objects from saving:
// Here, two RED objects are added to the policy filter to be sure they are not saved to a file
// when calling a recursive saving method like RED::IREDFile::WriteDAG.
policy.AddToFilter( collision_mesh );
policy.AddToFilter( hidden_camera );
Finally, the policy can define various behaviours:
Force the loaded images to stay in main memory (no transfer to the video memory)
policy.SetLocalImages( true );
Force the hardware compressed images to be decompressed before saving (to allow usage of the texture on machines without graphics hardware support)
policy.SetForceHWImageDecompression( true );
Disable the automatic saving of material controllers along with the materials
policy.SetMaterialControllerAutoSaving( false );
How to Save Encrypted .red Files?
The .red file format supports encryption of data to make your file exchanges more secured. Encryption can be enabled at file creation time (RED::IREDFileCreate) by providing an encryption key:
Save an Encrypted .red File
RED::Object* red_file = RED::Factory::CreateInstance( CID_REDFile );
RED::IREDFile* ifile = red_file->As< RED::IREDFile >();
RED::String encryption_key = "encryption key used to encrypt the source file";
RC_TEST( ifile->Create( "./my_file.red", true, encryption_key ) );
Reloading of the file is as easy as saving it. See Loading .red Files under the section How to Load Encrypted Files? for detailed information about encrypted file reloading.
Recording Animation Tracks
The .red files can record results of animations (not the animation descriptions themselves). Saving an animated scene is slightly different than saving a static one. You need to first save your data in their initial state (at frame #0) by calling RED::IREDFile::WriteDAG
for example. Then, for each frame of the animation, update the scene to reflect the current animation frame and dump the modifications to the file with that sequence of calls:
RED::IREDFile::WriteDAG
()RED::IResourceManager::BeginState
()Apply modifications to the data to match frame #
RED::IREDFile::WriteDynamic
()RED::IResourceManager::EndState
()Loop to 2. until animation ends
Each call to RED::IREDFile::WriteDynamic
save the modifications applied to the data during the current transaction (RED::State
).
Saving Multiple Animation Tracks
If your scene contains several distinct animated objects or if all the animations are not of the same length, you can choose to save your data as separate animation tracks. You have two choices here: either you save each animated object separately using the method above in the same file, or you save a unique DAG with all your objects linked to. In the second case, you must use a nomenclature to let HOOPS Luminate to detect automatically which part of the DAG refers to distinct animation tracks. The nomenclature is defined by a string label which is set to the RED::StreamingPolicy
(using RED::StreamingPolicy::SetAnimationTrackLabel
). If a node in the DAG contains this label in its name (the check is case sensitive), the node and all its children are considered as being part of the same animation track. When RED::IREDFile::WriteDAG
is called each sub-part of the DAG satisfying the nomenclature criterion will be discarded from the saving operation (the filtering is recursive).
Let’s take the example of two animated spheres (“Sphere01” and “Sphere02”) over a static plane (“Plane01”). If we set the animation track label to “Sphere” by calling RED::StreamingPolicy::SetAnimationTrackLabel
, the DAG will be structured as follows for saving:
The output file will contain only one track made of the root linked to the node “Plane01”, i.e. the part of the DAG which do not satisfy the set label and which are therefore considered as static (not animated). The two others tracks (the animated ones) must be saved explicitly by calling RED::IREDFile::WriteAnimationTrackDescriptor
and RED::IREDFile::WriteDag on each track root (i.e. “Sphere01” and “Sphere02”). To save the two animation tracks, you need to run the process described above twice (one for each sub-DAG).
The resulting file will be made of three different DAGS with no more relationship between them:
One more thing must be added to the file in order the loading process can re-assemble the original scene graph: you need to store the initial DAGs hierarchy. This is accomplished using the RED::FileInfo
object by setting the tracks hierarchy parameter. This parameter is a map which gives the list of child node IDs for each DAG node ID (when relevant). In our example, it will look like a map with a single entry:
{ node Root ID, { node Sphere01 ID, node Sphere 02 ID } }
It’s up to the loading process to take this map into account and to restore the initial hierarchy by looking for the node and link them back to their parents.
Extending the .red File Format
The .red file format can be extended by the user to save some custom data along with the RED ones. This is a two-step process: first a serializable custom class must be created which supports writing to a RED stream; second a callback must be declared which knows how read back data from a RED stream.
Creating a Custom Serializable Class
To make a class serializable to a .red file, it must inherit from both RED::Object
and RED::IChunkSaver
. The RED::IChunkSaver
interface defines the prototype of the method which is called internally by RED to save RED::Object
instances. Your class must also support the dynamic casting mechanism of RED. Some useful macros already exist in RED.h to let you declare the needed methods: SET_CID
and IMPLEMENT_AS
. Finally, you need to define a unique chunk signature for the data to be saved and declare it with the macro SET_CHUNK_SIGNATURE
. Hence, your class declaration should look something like the following:
class MyClass : public RED::Object, public RED::IChunkSaver
{
public:
// Typing information:
// -------------------
SET_CID( CID_class_MyClass ); IMPLEMENT_AS();
SET_CHUNK_SIGNATURE( MyClass_signature );
};
The CID_class_MyClass value describes your class as a unique identifier. You can take any value not already used in REDCID.h
. The MyClass_signature value defines the unique identifier of your class data chunk in a .red file. It can be modified to support new versions of your class in new versions of the .red file.
The IMPLEMENT_AS
macro forces you to implement two dynamic casting methods:
virtual void* As( const RED::CID& iCID );
virtual const void* As( const RED::CID& iCID ) const;
Here is an example of what it could look like:
void* MyClass::As( const RED::CID& type )
{
if( type == RED::MyClass::GetClassID() )
{
return this;
}
else if( type == RED::IChunkSaver::GetClassID() )
{
return (RED::IChunkSaver*)this;
}
return RED::Object::As( type );
}
Now, let’s define the saving method declared by the RED::IChunkSaver
interface:
RED_RC MyClass::Save( RED::IStream* iStream,
RED::StreamingPolicy& iPolicy,
int iState ) const
{
if( iStream == NULL )
return RED_BAD_PARAM;
// Store the current stream position.
RED::uint64 strpos;
RED_RC_ASSERT( iStream->GetCurrentPosition( strpos ) );
// Chunk signature.
RED_RC_ASSERT( iStream->WriteDWord( GetChunkSignature() ) );
// Chunk size (unknown at that moment): written to reserve space in the stream.
RED_RC_ASSERT( iStream->WriteDWord( 0 ) );
RED_RC_ASSERT( iStream->WriteDWord( 0 ) );
// Stream all your class data...
// ...
// ...
// Write the whole chunk size field.
RED::uint64 strpos2;
RED_RC_ASSERT( iStream->GetCurrentPosition( strpos2 ) );
// Set the stream position to the start of the chunk size field.
RED_RC_ASSERT( iStream->Move( strpos + 4 ) );
RED_RC_ASSERT( iStream->WriteDDWord( strpos2 - strpos - 12 ) );
RED_RC_ASSERT( iStream->Move( strpos2 ) );
return RED_OK;
}
A .red file is made of chunks. Each chunk must start with its 32bit signature followed by the 64bit chunk size in bytes. This is mandatory in order HOOPS Luminate to be able to load back .red file. This is what the method above does. The only thing left to the programmer is the actual writing of the class data to the stream.
This is for the writing part. We now must say to HOOPS Luminate how to reload chunks of that type as it’s not a built-in type. To do so, simply add the following macro to the declaration of your class:
class MyClass : public RED::Object, public RED::IChunkSaver
{
public:
// Typing information:
// -------------------
SET_CID( CID_class_MyClass ); IMPLEMENT_AS();
SET_CHUNK_SIGNATURE( MyClass_signature );
// Macro below is for .red file reloading.
IMPLEMENT_CHUNKINTERPRETER();
};
This macro forces you to define an Interpret method in your class which follows the declaration of RED::IChunkInterpreterProto
. You now need to register this method to the RED::ChunkInterpretersDictionary
class to let HOOPS Luminate automatically call it when chunks of that type are encountered in .red streams.
RED_RC_ASSERT( RED::ChunkInterpretersDictionary::RegisterChunkInterpreter( MyClass_signature, MyClass::Interpret ) );
The code of that method is quite simple:
RED_RC MyClass::Interpret( RED::IReferenceManager* iReferenceManager,
RED::IStream* iStream,
const RED::StreamingPolicy& iPolicy,
unsigned int iChunkSignature,
RED::uint64 iChunkSize,
RED::Object** oResult,
const RED::State& iState )
{
// Check input parameters.
if( ( iReferenceManager == NULL ) ||
( iStream == NULL ) ||
( oResult == NULL ) )
return RED_BAD_PARAM;
// Allocate the returned instance if the method needs to return an object.
MyClass* self = rnew MyClass();
if( self == NULL )
return RED_ALLOC_FAILURE;
else
*oResult = self;
// Read the class data from the stream...
// ...
// ...
return RED_OK;
}
Note
The reading code do not read back chunk information as they have already been read by HOOPS Luminate to identify the chunk and call the right Interpret callback. The ‘iStream’ pointer is then set to the very first byte of the chunk data (signature and chunk size excluded).
Note
A complete example of a .red file format customisation can be found in Saving and Reloading a Custom Container.