Go Back   www.tombraiderforums.com > Tomb Raider Modding > Tomb Raider Modding

Thread Tools
Old 10-03-19, 15:41   #981
FairFriend's Avatar
Join Date: Mar 2012
Posts: 123

I don't visit these forums as often as I used to, so I noticed only now your wonderful technical report. You have to share it with, I don't know, maybe Digital Foundry. I am confindent there must be an outlet out there that would be interested in publishing it as series of articles (perhaps with just a little bit of introduction to your previous work on AOD). It is wonderfully written and the figures you prepared are top notch. Also, I totally did not deserve that mention lol, but still glad you put it in!

Congratulations on this great achievement, I'm really looking forward to the next part.
FairFriend is offline   Reply With Quote
Old 10-03-19, 21:24   #982
nakamichi680's Avatar
Join Date: Feb 2015
Location: Italy
Posts: 864
Lightbulb Heat Haze Effect FIX - Part 2/2


Here we are again with the second part of our journey. In this second episode we will analyze my Heat Haze mod in detail.
The mod is made up of 5 parts:
  • Render_Scene
  • fxDrawHeatHaze
  • fxInsertHeatHaze
  • Render_Scene_PostProcess
Don't worry if these names may not make any sense at the moment, soon they will.

In the last episode we left with a couple of unsuccessful tests on the AoD rendering pipeline. Render_Scene is the function where the sequence of operations in those slides takes place. Given the poor performance of the second attempt I decided to use a different approach.

With this new algorithm, the effect handling on the PC becomes identical to PS2. The texture render target is used both as a source for the sprite texture and as a destination on which the sprite is drawn.
This simpler algorithm has brought significant benefits to the framerate: in scenes with lots of sprites this algorithm is 10 times faster than the previous one, ensuring a 60 fps rock solid framerate.
Moreover, having the Heat Haze effect rendered before Special Cameras (one of the post processing effects) automatically removes an annoying bug that occurs when your health is low and you're near heat sources in Sasho648’s builds.

It seems all hunky-dory, doesn't it? Unfortunately, this solution has one drawback which brings us to the second point.

The main drawback of using a texture render target shows up while playing in fullscreen with Anti-Aliasing.
When rendering a scene, the color buffer and depth buffer must have the same resolution. Depth buffer supports Multi-Sampling while, unfortunately, texture render target does not. With Anti-Aliasing turned on the texture render target resolution doesn't change while the depth buffer resolution increases accordingly to your MSAA setting (2x, 4x or 8x).
Because of this DirectX 9 intrinsic incompatibility, depth information is discarded and all Heat Haze sprites are rendered in the foreground, whereas they shouldn't be drawn when behind other objects.

Z-buffer bug: circled sprites shouldn’t be visible since they’re covered by the floor

To solve this issue, I invented the DMSAA (Dynamic Multi-Sampling Anti-Aliasing). Thanks to the DMSAA it will be possible to use the Heat Haze mod without sacrificing the Anti-Aliasing for most of the game.

How does it work?

When the game detects even a single Heat Haze sprite the entire rendering pipeline is automatically redirected to the texture render target which, not being multisampled, ensures that the effect is depth bug free. As soon as all the Heat Haze sprites disappear from the screen the rendering pipeline is re-routed back to the default multisampled path.

Transition takes place within a single frame so the whole process is completely transparent for the end user who, at most, will notice a slight increase in edges jagginess. Playing at high resolutions and with Depth Of Field enabled helps mitigating the increased amount of aliasing.

720x480 vs 1920x1080

Let's now get to the heart of the mod, the fxDrawHeatHaze function. For educational purposes, the original PS2 and PC functions are shown below.

PC on the left, PS2 on the right

The original fxDrawHeatHaze PC function is testimony to the chaos that has plagued AoD's development. It features several unused instruction blocks, vestige of an earlier implementation of the effect with fixed functions. The remaining working code sections have numerous redundant or pointless instructions.
One of the main differences between this function and mine is that the original draws all the Heat Haze sprites in one shot while my function must be executed many times per frame and each time it draws only one sprite.
For all these reasons I decided to throw away the original function and replace it with a completely new one with similar logic but different implementation.
Efficiency and CPU/GPU load balancing were the ultimate goals of my function. This was achieved with the following measures:
  • by minimizing the number of instructions;
  • by moving some preliminary checks and operations originally performed inside fxDrawHeatHaze out of it so that they are performed only once instead of hundreds of times per frame;
  • by moving some tasks to the Vertex and Pixel Shaders, freeing the CPU, enough overloaded by the enormous increase of draw calls, from some calculations.

Here is a slightly modified version of the decompiled PS2 code that we are going to implement in our function.
float v1_U, v1_V, v4_U, v4_V, Temp_Matrix[4][4];
Vector4f v1_Pos, v4_Pos;
Vector4f v1(-32, -32, 0, 1);
Vector4f v4(32, 32, 0, 1);

mathTranslate(AoD_HeatHazeSlot.Position);	// Applies sprite position to the MatrixStack
mathStoreMatrix(Temp_Matrix);			// Stores the MatrixStack to a temporary matrix
if (Temp_Matrix[3][2] > 20)			// Near clipping plane
   Temp_Matrix[0][0] = AoD_HeatHazeSlot.Size;
   Temp_Matrix[0][1] = 0;
   Temp_Matrix[0][2] = 0;
   Temp_Matrix[0][3] = 0;
   Temp_Matrix[1][0] = 0;
   Temp_Matrix[1][1] = AoD_HeatHazeSlot.Size;
   Temp_Matrix[1][2] = 0;
   Temp_Matrix[1][3] = 0;
   Temp_Matrix[2][0] = 0;
   Temp_Matrix[2][1] = 1;
   Temp_Matrix[2][2] = 0;
   Temp_Matrix[2][3] = 0;
   Temp_Matrix[3][3] = 1;
   mathLoadMatrix(Temp_Matrix);			// Loads the updated temp matrix back to the MatrixStack
   mathRotTransPers(v1, v1_Pos);
   mathRotTransPers(v4, v4_Pos);
   if (v1_Pos.x >= 1)
   if (v1_Pos.y <= -1)
   if (v4_Pos.x <= -1)
   if (v4_Pos.y >= 1)
   if (v1_Pos.x < -1)
      v1_Pos.x = -1;
   if (v1_Pos.y > 1)
      v1_Pos.y = 1;
   if (v4_Pos.x > 1)
      v4_Pos.x = 1;
   if (v4_Pos.y < -1)
      v4_Pos.y = -1;
   v1_U = 0.5 * (v1_Pos.x + 1) + 0.0009765625 - AoD_HeatHazeSlot.Distortion;
   v1_V = -0.5 * (v1_Pos.y - 1); 
   v4_U = 0.5 * (v4_Pos.x + 1) + 0.0009765625 + AoD_HeatHazeSlot.Distortion;
   v4_V = -0.5 * (v4_Pos.y - 1); 
   if (v1_U < 0)
      v1_U = 0;
   if (v1_U > 1)
      v1_U = 1;
   if (v1_V < 0.001953125)
      v1_V = 0.001953125;
   if (v1_V > 1)
      v1_V = 1;
   if (v4_U < 0)
      v4_U = 0;
   if (v4_U > 1)
      v4_U = 1;
   if (v4_V < 0.001953125)
      v4_V = 0.001953125;
   if (v4_V > 1)
      v4_V = 1;
Blue instructions have been placed outside the fxDrawHeatHaze function. When the near clipping plane test fails the sprite is not rendered and CPU cycles are spared. This slightly reduces the number of iterations of the fxDrawHeatHaze loop.

Red instructions will be placed in fxDrawHeatHaze while green instructions will be placed inside the Vertex Shader.

Let´s now analyze my custom fxDrawHeatHaze function step by step.

The first part applies the sprite scaling information directly to the MatrixStack, where the position was previously saved by mathTranslate. This saves two unnecessary steps, mathStoreMatrix and mathLoadMatrix.
mov edi,[ebx+10]		// Pointer to the current AoD_HeatHazeSlot
mov edx,[009283A0]		// Pointer to MatrixStack
movaps xmm0,[00950000]        
movaps xmm1,[0094FFF0]         
movaps xmm2,[0094FFE0]          
movups [edx],xmm0		// Set the first row of MatrixStack to 1/0/0/0
movups [edx+10],xmm1		// Set the second row of MatrixStack to 0/1/0/0
movups [edx+20],xmm2		// Set the third row of MatrixStack to 0/0/1/0
mov eax,[edi+0C]		// Reads AoD_HeatHazeSlot.Size
mov [edx],eax			// Copies the size to MatrixStack[0][0]
mov [edx+14],eax		// Copies the size to MatrixStack[1][1]
In order to draw the sprite we need the coordinates of at least 2 of the 4 vertices (the other 2 are derived from them, as you can see in this image).

The RotTransPersp function allows you to do exactly this: it takes a matrix and a point as inputs and returns a new point in screen coordinates. Since there are two vertices, the function is executed twice.
lea eax,[esp+10]
push eax
push 0094FF80			// -32/-32/0/1
call mathRotTransPers    
lea eax,[esp+28]
push eax
push 0094FF70			// 32/32/0/1
call mathRotTransPers
Now we have the coordinates of our two vertices of the square at the addresses esp+10 (v1) and esp+20 (v4).
First of all we have to check that the sprite does not fall outside the screen area in order not to waste RAM and CPU cycles on invisible sprites.

movss xmm7,[00950000]		// 1
movss xmm0,[esp+10]		// v1_Pos.x
comiss xmm0,xmm7
movss xmm6,[0071761C]		// -1
movss xmm2,[esp+20]		// v4_Pos.x
comiss xmm2,xmm6
movss xmm1,[esp+14]		// v1_Pos.y
comiss xmm1,xmm6
movss xmm3,[esp+24]		// v4_Pos.y
comiss xmm3,xmm7
A further passage (represented by the minss and maxss instructions) takes care of "forcing" the square sprite in the screen area, even at the cost of squeezing it into a rectangle. This prevents this ugly glitch.

maxss xmm0,xmm6
minss xmm1,xmm7
minss xmm2,xmm7
maxss xmm3,xmm6
Once these tests are finished we are 100% sure that our sprite will be drawn so we can allocate the space for 4 vertices in the buffer.
mov ebp,[ebx+8]
mov ecx,[ebp+000031F4]
lea edx,[esp]                       
push edx
lea edx,[esp+8]
push edx
lea edx,[esp+10]  
push edx
push 04
call SYS_D3D_VB_POOL<VF_VCT,1>::GetBuffer
Once the space is allocated we can save the information of our 4 vertices in structures called FVF. If you missed my previous post on the gas mod, all you need to know is that the FVF is a structure containing vertex attributes (position, color, UV, normal, etc.).
The Heat Haze effect uses this type of FVF:
struct VF_VCT
   float X;
   float Y;
   float Z;
   unsigned int Diffuse;	//A8R8G8B8
   float U;
   float V;
So in our 4 FVFs (one for each vertex) we're going to save X, Y, Z, diffuse color, U and V.
mov eax,[esp+4]			// Pointer to the first FVF
// XYZ
movss [eax],xmm0   
movss [eax+18],xmm0
movss [eax+04],xmm1
movss [eax+34],xmm1
movss [eax+30],xmm2
movss [eax+48],xmm2
movss [eax+1C],xmm3
movss [eax+4C],xmm3
mov edx,[esp+18]
mov [eax+08],edx
mov [eax+20],edx
mov [eax+38],edx
mov [eax+50],edx
// UV
mov [eax+10],0        
mov [eax+14],0
mov [eax+28],0
mov [eax+2C],3F800000
mov [eax+40],3F800000
mov [eax+44],0
mov [eax+58],3F800000
mov [eax+5C],3F800000
// Diffuse color
mov edx,[edi+20]		// AoD_HeatHazeSlot.Color
mov [eax+0C],edx
mov [eax+24],edx
mov [eax+3C],edx
mov [eax+54],edx
From now on, our vertices say goodbye to us and take the road to the GPU.
The more observant may have noticed, with good reason, something strange in the UV coordinates. That's the only difference between PS2 and my implementation.
On PS2, the sprite has sharp edges. If you have always played the game with a composite cable you may not have spotted this detail but using a component cable, which is characterized by greater sharpness (an experience that I absolutely recommend), makes this more visible. The same happens on PC due to the higher resolution. To solve this eyesore, I decided to add faded edges to the PC sprite.
The question arises: how to fade the edges?
Answer: by taking the already existing HeatEMBM.tga sprite, tweaking its alpha channel and applying it to our effect.

Original texture on the left, modified alpha channel on the right

But now a new question arises: our sprite is made up of 2 textures, the texture render target for the color channels and HeatEMBM.tga for transparency. But the FVF can only contain one set of UV coordinates, not two. What do we do?
  1. transparency UV set inside the FVF
  2. color channels UV set rebuilt from XYZ coordinates inside the Vertex Shader
But we will come back to point 2 in a very short time, in the meantime let's go ahead with our function.
After saving our vertices to the buffer we can release it and set some parameters of the renderer we won’t discuss. If you want to learn more here are some links:
mov eax,[esp+8]
mov edi,[eax]
push eax
call dword ptr [edi+30]		// CVertexBufferMT::Unlock
mov eax,[ebp+00001508]
mov edx,[eax+00000908]
lea edi,[eax+00000908]
test edx,edx
je #1
xor edx,edx
push edx			// FALSE
mov [edi],edx
mov edi,[eax+000014F0]
push edi
mov eax,[edi]
call dword ptr [eax+000000E4]	// CD3DHal::SetRenderState
mov eax,[ebp+00001508]
mov edx,[eax+000008EC]
lea edi,[eax+000008EC]
cmp edx,01
je #2
mov edx,01
push edx			// TRUE
mov [edi],edx
mov edi,[eax+000014F0]
push edi
mov eax,[edi]
call dword ptr [eax+000000E4]	// CD3DHal::SetRenderState
mov eax,[ebp+00001508]
mov edx,[eax+00000928]
lea edi,[eax+00000928]
cmp edx,01
je #3
mov edx,00000001
push edx			// NONE
mov [edi],edx
mov edi,[eax+000014F0]
push edi
mov eax,[edi]
call dword ptr [eax+000000E4]	// CD3DHal::SetRenderState
mov eax,[ebp+00001508]
mov edx,[eax+000008F0]
lea edi,[eax+000008F0]
cmp edx,03
je #4
mov edx,00000003
push edx			// SOLID
mov [edi],edx
mov edi,[eax+000014F0]
push edi
mov eax,[edi]
call dword ptr [eax+000000E4]	// CD3DHal::SetRenderState
mov eax,[ebp+00001508]
push 00000142
mov edi,[eax+000014F0]
mov edx,[edi]
push edi
call dword ptr [edx+00000164]	// CD3DBase::SetFVF
Now let's set the textures blend mode, the Vertex Shader and the Pixel Shader.
mov ecx,ebp
call SYS_D3D_DEVICE::Blend_Set_AlphaBlend
lea ecx,[ebp+00002E5C]
call SYS_D3D_VS::SetVertexShader
lea ecx,[ebp+0000252C]
call SYS_D3D_PS::SetPixelShader
Note: at this point the Vertex and Pixel Shaders are only set but not yet executed: this will only happen after calling the draw function.
Let's stop for a moment to analyze my custom Vertex and Pixel Shaders. As with the fxDrawHeatHaze function, they have been rewritten from scratch too.

Vertex Shader
My custom Vertex Shader takes care of four tasks:
  1. reading the XYZ vertex coordinates and passing them to the Pixel Shader (lines 1, 4);
  2. reading the diffuse color and passing it to the Pixel Shader (lines 3, 13);
  3. reading the HeatEMBM.tga UV set from the FVF and passing it to the Pixel Shader (lines 2, 12);
  4. rebuilding the color channels UV set by combining XY vertex coordinates, HeatEMBM.tga UV set and some constants (lines 5, 6, 7, 8, 9, 10, 11).

- add a, b, c ---> a = b + c
- mad a, b, c, d ---> a = b * c + d

Let's focus on the fourth point.
Our aim is to replicate the green decompiled PS2 code posted above with shader assembly.
We’re going to use 3 constant registers (c0, c1 and c2), each containing 4 floats (.x, .y, .z and .w). c0 and c1 will be passed to the Vertex Shader by fxDrawHeatHaze later on, c2 is defined inside the shader itself.
Their values are as follow:
  x = 0.0009765625 (adjusted for the Aspect Ratio)
  y = 0
  z = 0.5
  w = -0.5
  x = AoD_HeatHazeSlot.Distortion (adjusted for the Aspect Ratio)
  y = 0
  z = 0
  w = 0.001953125
  x = 1
  y = -1
  z = 0
  w = 2
First of all we convert the XY coordinates from screen to texture space with the formula shown in Part 1. (lines 5 and 6)
  • U = 0.5 * (X + 1)
  • V = -0.5 * (Y – 1)
Then 0.0009765625 is added to U coordinates (line 6).
Now, the Vertex Shader doesn't know if the vertex entering is the one with index 1, 2, 3 or 4. So we must be smart and write an algorithm that behaves differently depending on the incoming vertex. To do this we use the transparency UV set: depending on its value we can find out the vertex index.

(0,0) --> v1
(0,1) --> v2
(1,0) --> v3
(1,1) --> v4

Thanks to this trick we can obtain with two lines of code (7 and 8) two different behaviors of the Vertex Shader: AoD_HeatHazeSlot.Distortion is subtracted from v1/v2 and it’s added to v3/v4.
Once these calculations have been completed, we make sure, as we did in fxDrawHeatHaze with the vertex coordinates, that the UV coordinates do not go outside the screen area with the max and min instructions (lines 9 and 10).

Pixel Shader
My Pixel Shader is far simpler than the original one since many instructions are no longer needed and others have been moved to the Vertex Shader.

What does my Pixel Shader do?
  1. Converts the diffuse color from from ABGR to ARGB (lines 1, 2, 3).
  2. Multiplies the texture render target color by the diffuse color (line 4).
  3. Copies the HeatEMBM.tga alpha channel into the alpha channel of the output pixel (line 5).

Enough with shaders… back to fxDrawHeatHaze.

With the following instructions we set the two textures (Texture 1: texture render target. Texture 2: HeatEMBM.tga) and apply the bilinear filtering (the anisotropic is useless since the sprites are parallel to the screen).
mov eax,[ebp+000014FC]
push [eax+00000100]
push 01
mov ecx,ebp
call SYS_D3D_DEVICE::TSS_SetTexture 
push [ebp+00001518]
push 00
mov ecx,ebp
call SYS_D3D_DEVICE::TSS_SetTexture 
push 01
push 00
mov ecx,ebp
call SYS_D3D_DEVICE::TSS_SetFilter 
push 01
push 01
mov ecx,ebp
call SYS_D3D_DEVICE::TSS_SetFilter
I won't dwell on the instructions that follow, just know that they associate the UV sets to the textures and check that the limit of on-screen meshes is not exceeded (otherwise the sprite is not drawn).
mov eax,[ebp+00001508]
mov esi,[eax+00000CFC]
lea edx,[eax+00000CFC]
test esi,esi
je #5
xor esi,esi
push esi
mov [edx],esi
push esi
mov edx,[eax+000014F0]
push edx
mov eax,[edx]
call dword ptr [eax+0000010C]	// CD3DBase::SetTextureStageState
mov eax,[ebp+00001508]
mov esi,[eax+00000D7C]
lea edx,[eax+00000D7C]
cmp esi,01
je #6
mov esi,00000001
push esi
mov [edx],esi
push esi
mov edx,[eax+000014F0]
push edx
mov eax,[edx]
call dword ptr [eax+0000010C]	// CD3DBase::SetTextureStageState
mov eax,[ebx+C]
lea edi,[eax+00000AA0]
push edi
call dword ptr [0067820C]	// [_imp__EnterCriticalSection]
mov edx,[ebx+C]
mov eax,[edx+00000A90]
mov esi,[edx+00000A94]
lea edx,[esi+02] 
cmp eax,edx
ja #7
mov ecx,[ebx+C]
mov edx,[ecx+00000A98]
cmp edx,00
cmove edx,eax
mov eax,00000001
add edx,01
mov [ecx+00000A98],edx
push esi
push edx
push 00944DE0			// SYS_DRAW_ITEM_POOL run out of objects. Need %d out of %d
push eax
push eax
call dbgLog
add esp,14
push edi
call dword ptr [006781EC]	// [_imp__LeaveCriticalSection]
mov eax,[ebx+C]
add esi,esi
add esi,esi
add esi,esi
add esi,esi
add esi,[eax+00000A9C]
mov [eax+00000A94],edx
push edi
call dword ptr [006781EC]	// [_imp__LeaveCriticalSection]
mov edx,[ebx+C]
lea eax,[edx+00000A78]
mov [esp+0C],eax
push eax
call dword ptr [0067820C]	// [_imp__EnterCriticalSection]
mov eax,[ebx+C]
mov edx,[eax+00000A68]
mov eax,[eax+00000A6C]
lea ecx,[eax+01]
cmp edx,ecx
ja #8
mov ecx,[ebx+C]
mov edi,[ecx+00000A70]
cmp edi,00
cmove edi,edx
mov edx,00000001
add edi,01
mov [ecx+00000A70],edi
push eax
push edi
push 00944DE0			// SYS_DRAW_ITEM_POOL run out of objects. Need %d out of %d
push edx
push edx
call dbgLog
add esp,14
mov eax,[esp+0C]
push eax
call dword ptr [006781EC]	// [_imp__LeaveCriticalSection]
mov edx,[ebx+C]
add eax,eax
add eax,eax
lea edi,[eax+eax]
add edi,edi
add edi,edi
sub edi,eax
add edi,[edx+00000A74]
mov eax,[esp+0C]
mov [edx+00000A6C],ecx
push eax
call dword ptr [006781EC]	// [_imp__LeaveCriticalSection]
mov [edi],00000002
xor eax,eax
mov [edi+08],eax
mov [edi+10],eax
mov [edi+04],esi
lea edx,[esi+20]
mov [edi+0C],edx
add esi,20
mov [edi+14],esi
mov [edi+18],eax
In the following block we pass c0 and c1 constants mentioned above to our Vertex Shader.
The distortion value, read from the AoD_HeatHazeSlot, and the number 0.0009765625 are corrected for the screen aspect ratio (they were originally designed for the 4:3 aspect ratio only).
mov eax,[ebx+10]		// Pointer to AoD_HeatHazeSlot
movss xmm1, [ebx+14]		// AR correction value
movss xmm0,[0095DDA4]		// 0.0009765625
mulss xmm0,xmm1
movss xmm2,[eax+14]		// AoD_HeatHazeSlot.Distortion
mulss xmm2,xmm1
mov edx,[edi+04]
movss [edx],xmm0
mov [edx+04],00000000
mov [edx+08],3F000000		// 0.5
mov [edx+0C],BF000000		// -0.5
movss [edx+10],xmm2
mov [edx+14],00000000
mov [edx+18],00000000
mov [edx+1C],3B000000		// 0.001953125
push 2
push [edi+04]
mov edx,[ebp+00001504]
mov eax,[edx]
push 00
push edx
call dword ptr [eax+00000178]	// CD3DBase::SetVertexShaderConstantF
And we can finally call our sprite drawing functions.
mov edi,[esp]
mov edx,[ebp+000031F4]
add edi,4
mov [edx+04],edi
mov edx,[ebp+00001508]
mov esi,[edx+000014F0]
mov edx,[esi]
push 18
push 00
push [esp+10]
push 00
push esi
call dword ptr [edx+00000190]	// CD3DBase::SetStreamSource
mov edi,[ebp+000031F8]
mov eax,[ebp+00001508]
mov edx,[eax+000014F0]
mov eax,[edx]
push [edi+10]
push edx
call dword ptr [eax+000001A0]	// CD3DBase::SetIndices
mov edx,[ebp+00001504]
mov eax,[edx]
push 02
push 00
push 04
push 00
push [esp+10]
push 04
push edx
call dword ptr [eax+00000148]	// CD3DBase::DrawIndexedPrimitive
I remember that it is only at this point that the instructions of Vertex and Pixel Shaders are actually executed.
Here’s a view of the entire function. As you may notice, it’s waaaay smaller than the original one posted above.

This function has only a small bug, it saves a dummy color (255,255,255,255 or FFFFFFFFh in hexadecimals) in the Heat Haze slot instead of the color passed by the calling function.

Original on the left, modded on the right

This is also an easy fix, all we have to do is delete the original call to fxDrawHeatHaze as the effect has already been rendered at this point.

We have now reached the end of our journey in the Heat Haze mod. I hope you've learned something and I apologize in advance if some parts may not be crystal clear. As you can see even behind fixing small details like this one there is a long work, made of a lot of research, trial and error but also a lot of gratification. I hope this post doesn't discourage anyone from approaching AoD's modding but rather makes it clear that with perseverance and commitment results eventually arrive.
nakamichi680 is offline   Reply With Quote
Old 11-03-19, 13:13   #983
red_lion's Avatar
Join Date: Apr 2015
Location: Netherlands
Posts: 462

Thanx again Nagamichi680 for everything
red_lion is offline   Reply With Quote
Old 12-03-19, 15:48   #984
Relic Hunter
Dustie's Avatar
Join Date: Apr 2005
Location: Poland
Posts: 9,209

Me reading this:

Not to trash the thread with old memes, your work is amazing and it's great to see you dedicated to such details.
Dustie is offline   Reply With Quote
Old 12-03-19, 15:55   #985
Alex Fly
Alex Fly's Avatar
Join Date: Dec 2006
Location: France
Posts: 31,967

Impressive read, Nakamichi! Thanks again for making this possible.
Alex Fly is offline   Reply With Quote
Old 14-03-19, 11:42   #986
SLAYER's Avatar
Join Date: Jun 2006
Location: Jordan
Posts: 2,180

I won't pretend to understand how these functions work fully, that needs a lot of time and coffee, but I'm glad you had this documented. Good Job on adding the effect and writing the details. Your dedication is unparalleled.
Right now, I'm losing patience
SLAYER is offline   Reply With Quote
Old 14-03-19, 13:29   #987
MBog's Avatar
Join Date: Nov 2014
Location: Romania
Posts: 2,076

Great read nakamichi! very impressive work.
You should link those posts in the thread to your modding so they don t get lost, or make anew one entierly where you explain those discoveries
I will become game director at TR .
MBog is offline   Reply With Quote
Old 29-03-19, 02:35   #988
OrangeJuice's Avatar
Join Date: Jun 2016
Location: hangin out with janice and that drug dealer
Posts: 1,714

does any of you know how to rip the character models from the game and view/play their animations in an external editor? and if so, what editor/software would that be?

(i'm not looking to edit the animations)
working on AoD 2D for the culture
OrangeJuice is offline   Reply With Quote
Old 29-03-19, 06:04   #989
XNAaraL's Avatar
Join Date: Apr 2009
Location: The worthwhile problems are the U can really solve, the ones U can really contribute something to
Posts: 3,198
Default How to open AoD "chr." files

Extracting 3d models (Lara.chr)
Originally Posted by OrangeJuice View Post
does any of you know how to rip the character models from the game and view/play their animations in an external editor? and if so, what editor/software would that be?

(i'm not looking to edit the animations)
To extract the animations is possible but I'm not looking for the animations.

nakamichi680 had started to decode the AoD CHR (character) models 3 years ago:
Originally Posted by nakamichi680 View Post
Here you are
I'm trying to understand how models are stored in game files with .chr extension. AoD was made with Maya, so maybe you can help me with some tips on how information are stored inside ma and mb files
I'm currently studying the simplest character in the game, a fish

image http://i.imgur.com/S9QYtyI.png

It has 35 verticles (from 0 to 34). Yellow numbers are hidden verticles.
Inside chr file there is a portion describing verticles coordinates (and something else I still have to discover) and another portion describing faces. The latter is a list of verticles:

7 7 21 20 20 31 31 31 8 30 7 7 6 6 6 19 4 18 18 5 5 5 29 4 28 4 12 18 26 18 3 0 0 28 28 28 22 4 23 18 1 0 2 3 27 26 11 11 11 11 11 13 27 24 2 25 1 25 23 9 22 32 28 28 33 33 33 32 10 9 10 25 10 24 24 26 26 26 11 12 34 28 14 32 16 33 34 13 11 11 34 34 16 14 16 15 17 17

Can you help me understanding how to read this list?

I using my own XPS import script to extract poseable AoD character models:
    public static void GetFileExtension(double appTime, ref string fileExtension) {
	// Set the file extension for AoD character data files
        fileExtension = ".CHR";
    public static void ConvertFile(double appTime, string pathName, string fileName,
				   ref string resultFile, ref string hudMessage
				  ) {
        string inputFile = pathName + "\\" + fileName;
        string genericFile = "Generic_Item.mesh.ascii";
        string outputFile = pathName + "\\" + genericFile;

	// Display "working" message
        hudMessage = fileName + " converting ...";
        string result = "";

	BinaryReader file = null;

	try {
	        // Open the file
        	file = new BinaryReader(new FileStream(inputFile, FileMode.Open));

	        // read the file

		// How many bones has the skeleton?
		ushort numberBones = (ushort)file.ReadInt16();
		// write header of the armature section inside the .mesh.ascii format
		result = numberBones + " # bones\r\n";

		// Read the position of the armature inside the CHR data file
		ushort offsetBones = (ushort)file.ReadInt16();

		string boneName = "root ground";
		short parentId = -1;
		string bonePosition = "0.0 0.0 0.0";

		// Read the bone data for each bone
		for (ushort i = 0 ; i < numberBones; i++) {
			file.BaseStream.Seek(offsetBones + 4 + i * 448, System.IO.SeekOrigin.Begin);
			boneName =     ReadString(file);
			parentId =     ReadParentId(file);
			bonePosition = ReadBonePosition(file);
			// Write bone data inside the .mesh.ascii format
			result = result + bone + "\r\n + parentId + "\r\n" + bonePosition + "\r\n;
To play with the AoD characters, I use as 3D application XPS and Blender.
Link removed. - Why ? google, google “Google is your friend!”
XNAaraL is offline   Reply With Quote
Old 29-03-19, 15:53   #990
Relic Hunter
Samz's Avatar
Join Date: Jun 2018
Location: Helheim
Posts: 5,933


So how good is this level editor? I know you can only place items apparently but is it stable? the one fourm post I can find about this says something about levels getting smaller and losing data once you save them.
"What does it mean to be a Tomb Raider?,It means to collect artifacts for sport"

Last edited by Samz; 29-03-19 at 16:43.
Samz is offline   Reply With Quote

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

All times are GMT. The time now is 05:09.

Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2020, vBulletin Solutions Inc.