pokeplatinum/docs/3d_rendering.md
2024-06-26 19:22:21 +02:00

123 lines
5.2 KiB
Markdown

## 3D Rendering
The following is a brief overview and introduction to the 3D rendering system in Pokemon Platinum and how to use it.
## Abstraction Layers
The lowest level 3D is interacting directly with the hardware via NitroSDK. NitroSystem provides a slight abstraction over this with `G3D`.
The game has 2 additional layers of abstraction over this:
### 1 - Easy3D
The first layer of abstraction is the Easy3D System (`easy3d.h`). Which provides basic functionality for:
- Loading Models and Textures
- Binding textures to models
- Drawing models with any transform
- Setting up and releasing a 3D graphics state
To elaborate on the last point: Before using any 3D graphics, a "3D Graphics State" must be initialized. The struct for this is currently called `GenericPointerData`. The Easy3D system provides an easy method to set up a simple gfx state via `Easy3D_Init` (With `Easy3D_Shutdown` as its counterpart), which works for most scenarios.
### 2 - Easy3DObject
The second layer of abstraction is `Easy3DObject` (`easy3d_object.h`). This API provides a streamlined interface for:
- Loading models, textures, and animations
- Binding a texture to a model
- Binding one or more animations to a model (both bone animations and texture animations)
- Updating the objects transform (the object itself keeps track of that)
- Updating animation state
- Drawing objects
- Cleanup
One thing to keep in mind is that this system by itself does not establish a 3D GFX State.
This system is also not used everywhere. In a lot of places Easy3D is used directly instead of this object oriented interface.
## Example
The following is an example for using the `Easy3DObject` API for:
1. Loading a model with textures, animation, and texture animation
2. Binding them all together
3. Updating the animations
4. Drawing the model
I will be using the Giratina model from the title screen for this.
### Loading the data
The first thing needed is some static storage to hold our data:
```c
Easy3DObject giratinaObj;
Easy3DModel giratinaModel;
Easy3DAnim giratinaAnim; // Model Animation
Easy3DAnim giratinaTexAnim; // Texture Animation
NNSFndAllocator allocator; // Needed for Animations
```
Next we load all of the data:
```c
// Open the title NARC
NARC *narc = NARC_ctor(NARC_INDEX_DEMO__TITLE__TITLEDEMO, HEAP_ID_FIELD);
// Load the model from the title screen NARC. Member index 1 is the model data.
// There is also Easy3DModel_Load which takes a NARC index and a member index.
Easy3DModel_LoadFrom(&giratinaModel, narc, 1, HEAP_ID_FIELD);
Easy3DObject_Init(&giratinaObj, &giratinaModel);
// Initialize the Allocator used by the animations
Heap_FndInitAllocatorForExpHeap(&allocator, HEAP_ID_FIELD, 4);
// Load the model animation with member index 2.
Easy3DAnim_LoadFrom(&giratinaModelAnim, &giratinaModel, narc, 2, HEAP_ID_FIELD, &allocator);
// Bind the animation to the object
Easy3DObject_AddAnim(&giratinaObj, &giratinaModelAnim);
// Do the same for the texture animation
Easy3DAnim_LoadFrom(&giratinaTexAnim, &giratinaModel, narc, 0, HEAP_ID_FIELD, &allocator);
Easy3DObject_AddAnim(&giratinaObj, &giratinaTexAnim);
NARC_dtor(narc);
```
This is all that needs to be done in terms of setup, now the model is ready to draw (provided a 3D GFX State has been set up).
### Drawing the Model
Before drawing there's a few things that should be configured on the object, one of them being the position of the model obviously. For simplicity's sake I will just set the models position to the player's position.
```c
const VecFx32 *pos = PlayerAvatar_PosVector(fieldSystem->playerAvatar);
Easy3DObject_SetPosition(&giratinaObj, pos->x, pos->y, pos->z);
// Make sure the model actually gets rendered
Easy3DObject_SetVisibility(&giratinaObj, TRUE);
// The model is pretty big so scale it to half its size
Easy3DObject_SetScale(&giratinaObj, FX32_CONST(0.5), FX32_CONST(0.5), FX32_CONST(0.5));
```
Now we can actually render the model:
```c
// Update the animations
// Here we advance the animation by one frame.
// There is also Easy3DAnim_Update which does not loop the animation
Easy3DAnim_UpdateLooped(&giratinaModelAnim, FX32_ONE);
Easy3DAnim_UpdateLooped(&giratinaTexAnim, FX32_ONE);
// Draw the model
Easy3DObject_Draw(&giratinaObj);
```
All of that results in the following:
https://github.com/pret/pokeplatinum/assets/60443001/ba162c62-e64a-4cd6-850f-414774f19bfd
Obviously the rotation and resizing is not outlined above. The code for this is the following:
```c
u16 angle = Easy3DObject_GetRotation(&giratinaObj, ROTATION_AXIS_Y);
angle = (angle + 100) % 0xFFFF;
Easy3DObject_SetRotation(&giratinaObj, angle, ROTATION_AXIS_Y);
// Resize using L and R
if (gCoreSys.heldKeys & PAD_BUTTON_R) {
VecFx32 scale;
Easy3DObject_GetScale(&giratinaObj, &scale.x, &scale.y, &scale.z);
scale.x = FX_Mul(scale.x, FX32_CONST(1.01));
scale.y = FX_Mul(scale.y, FX32_CONST(1.01));
scale.z = FX_Mul(scale.z, FX32_CONST(1.01));
Easy3DObject_SetScale(&giratinaObj, scale.x, scale.y, scale.z);
} else if (gCoreSys.heldKeys & PAD_BUTTON_L) {
VecFx32 scale;
Easy3DObject_GetScale(&giratinaObj, &scale.x, &scale.y, &scale.z);
scale.x = FX_Mul(scale.x, FX32_CONST(0.99));
scale.y = FX_Mul(scale.y, FX32_CONST(0.99));
scale.z = FX_Mul(scale.z, FX32_CONST(0.99));
Easy3DObject_SetScale(&giratinaObj, scale.x, scale.y, scale.z);
}
```