2024-07-21 21:13:31 +00:00
using Godot ;
using KeepersCompound.LGS ;
using KeepersCompound.LGS.Database ;
using KeepersCompound.LGS.Database.Chunks ;
2024-08-03 09:24:45 +00:00
using KeepersCompound.TMV.UI ;
2024-07-30 20:50:34 +00:00
using RectpackSharp ;
2024-07-21 21:13:31 +00:00
using System ;
using System.Collections.Generic ;
2024-08-11 08:18:02 +00:00
namespace KeepersCompound.TMV ;
2024-07-21 21:13:31 +00:00
public partial class Mission : Node3D
{
2024-08-11 11:28:24 +00:00
private readonly struct LightmapRectData
{
public readonly int cellIndex ;
public readonly int lightmapIndex ;
public readonly int textureId ;
public readonly int uvStart ;
public readonly int uvEnd ;
public LightmapRectData ( int cellIndex , int lightmapIndex , int textureId , int uvStart , int uvEnd )
{
this . cellIndex = cellIndex ;
this . lightmapIndex = lightmapIndex ;
this . textureId = textureId ;
this . uvStart = uvStart ;
this . uvEnd = uvEnd ;
}
}
2024-07-21 21:13:31 +00:00
[Export(PropertyHint.GlobalFile, "*.mis")]
public string FileName { get ; set ; }
[Export]
public bool Build = false ;
2024-07-22 18:37:27 +00:00
[Export]
public bool Clear = false ;
2024-08-03 15:09:51 +00:00
[Export]
public bool Dump = false ;
2024-07-21 21:13:31 +00:00
2024-08-13 18:11:03 +00:00
InstallPaths _installPaths ;
2024-07-21 21:13:31 +00:00
DbFile _file ;
2024-08-11 14:23:56 +00:00
TextureLoader _textureLoader ;
2024-07-21 21:13:31 +00:00
public override void _Ready ( )
{
2024-08-03 09:24:45 +00:00
var missionSelector = GetNode < Control > ( "%MissionSelector" ) as MissionSelector ;
2024-08-13 18:11:03 +00:00
missionSelector . LoadMission + = ( string rootPath , string missionPath ) = >
2024-08-03 09:24:45 +00:00
{
2024-08-13 18:11:03 +00:00
_installPaths = new InstallPaths ( rootPath ) ;
FileName = missionPath ;
2024-08-03 09:24:45 +00:00
Build = true ;
} ;
2024-07-21 21:13:31 +00:00
}
public override void _Process ( double delta )
{
if ( Build )
{
RebuildMap ( ) ;
Build = false ;
}
2024-07-22 18:37:27 +00:00
if ( Clear )
{
ClearMap ( ) ;
Clear = false ;
}
2024-07-21 21:13:31 +00:00
}
public override void _Input ( InputEvent @event )
{
if ( @event is InputEventKey keyEvent & & keyEvent . Pressed )
{
if ( keyEvent . Keycode = = Key . R )
{
Build = true ;
}
}
}
2024-07-22 18:37:27 +00:00
public void ClearMap ( )
2024-07-21 21:13:31 +00:00
{
foreach ( var node in GetChildren ( ) )
{
node . QueueFree ( ) ;
}
2024-07-22 18:37:27 +00:00
}
public void RebuildMap ( )
{
ClearMap ( ) ;
2024-07-21 21:13:31 +00:00
2024-08-13 18:11:03 +00:00
_textureLoader = new TextureLoader ( _installPaths . famPath , FileName . GetBaseDir ( ) ) ;
2024-08-11 14:23:56 +00:00
2024-07-21 21:13:31 +00:00
_file = new ( FileName ) ;
2024-08-03 15:09:51 +00:00
var textureList = ( TxList ) _file . Chunks [ "TXLIST" ] ;
2024-08-05 18:25:44 +00:00
LoadTextures ( textureList ) ;
2024-08-03 15:09:51 +00:00
if ( Dump ) DumpTextureList ( textureList ) ;
2024-07-21 21:13:31 +00:00
var wr = ( WorldRep ) _file . Chunks [ "WREXT" ] ;
2024-08-13 20:38:00 +00:00
BuildMeshes ( wr . Cells , wr . DataHeader . LightmapFormat = = 2 ) ;
2024-07-21 21:13:31 +00:00
}
2024-08-13 20:38:00 +00:00
private void BuildMeshes ( WorldRep . Cell [ ] cells , bool lmHdr )
2024-07-21 21:13:31 +00:00
{
2024-08-13 20:38:00 +00:00
GD . Print ( $"HDR: {lmHdr}" ) ;
2024-08-11 11:28:24 +00:00
var packingRects = new List < PackingRectangle > ( ) ;
var surfaceDataMap = new Dictionary < int , MeshSurfaceData > ( ) ;
var rectDataMap = new Dictionary < int , LightmapRectData > ( ) ;
2024-07-21 21:13:31 +00:00
2024-08-11 11:28:24 +00:00
for ( var cellIdx = 0 ; cellIdx < cells . Length ; cellIdx + + )
2024-07-22 18:37:27 +00:00
{
2024-08-11 11:28:24 +00:00
var cell = cells [ cellIdx ] ;
var numPolys = cell . PolyCount ;
var numRenderPolys = cell . RenderPolyCount ;
var numPortalPolys = cell . PortalPolyCount ;
// There's nothing to render
if ( numRenderPolys = = 0 | | numPortalPolys > = numPolys )
{
continue ;
}
// You'd think these would be the same number, but apparently not
// I think it's because water counts as a render poly and a portal poly
var maxPolyIdx = Math . Min ( numRenderPolys , numPolys - numPortalPolys ) ;
var cellIdxOffset = 0 ;
for ( int polyIdx = 0 ; polyIdx < maxPolyIdx ; polyIdx + + )
{
var lightmapRectData = ProcessCellSurfaceData ( surfaceDataMap , cell , cellIdx , polyIdx , cellIdxOffset ) ;
rectDataMap . Add ( packingRects . Count , lightmapRectData ) ;
2024-07-22 18:37:27 +00:00
2024-08-11 11:28:24 +00:00
var light = cell . LightList [ polyIdx ] ;
var rect = new PackingRectangle ( 0 , 0 , light . Width , light . Height , packingRects . Count ) ;
packingRects . Add ( rect ) ;
2024-08-03 16:22:38 +00:00
2024-08-11 11:28:24 +00:00
cellIdxOffset + = cell . Polys [ polyIdx ] . VertexCount ;
}
}
2024-08-05 18:25:44 +00:00
2024-08-11 11:28:24 +00:00
var lightmapTexture = BuildLightmapTexture ( cells , packingRects . ToArray ( ) , rectDataMap , surfaceDataMap ) ;
2024-08-11 14:43:44 +00:00
foreach ( var ( textureId , surface ) in surfaceDataMap )
2024-08-03 16:24:04 +00:00
{
2024-08-11 11:28:24 +00:00
if ( surface . Empty )
2024-08-05 18:25:44 +00:00
{
2024-08-11 11:28:24 +00:00
continue ;
2024-08-05 18:25:44 +00:00
}
2024-08-11 14:43:44 +00:00
var albedoTexture = _textureLoader . Get ( textureId ) ;
2024-08-11 11:28:24 +00:00
var mesh = new ArrayMesh ( ) ;
2024-08-11 14:43:44 +00:00
mesh . AddSurfaceFromArrays ( Mesh . PrimitiveType . Triangles , surface . BuildSurfaceArray ( ) ) ;
2024-08-13 20:38:00 +00:00
mesh . SurfaceSetMaterial ( 0 , BuildMaterial ( albedoTexture , lightmapTexture , lmHdr ) ) ;
2024-08-11 11:28:24 +00:00
var meshInstance = new MeshInstance3D { Mesh = mesh } ;
AddChild ( meshInstance ) ;
}
}
private LightmapRectData ProcessCellSurfaceData ( Dictionary < int , MeshSurfaceData > surfaceDataMap , WorldRep . Cell cell , int cellIdx , int polyIdx , int indicesOffset )
{
var poly = cell . Polys [ polyIdx ] ;
var normal = cell . Planes [ poly . PlaneId ] . Normal . ToGodotVec3 ( ) ;
var vertices = new List < Vector3 > ( ) ;
var textureUvs = new List < Vector2 > ( ) ;
var lightmapUvs = new List < Vector2 > ( ) ;
var numPolyVertices = poly . VertexCount ;
for ( var j = 0 ; j < numPolyVertices ; j + + )
{
var vertex = cell . Vertices [ cell . Indices [ indicesOffset + j ] ] ;
vertices . Add ( vertex . ToGodotVec3 ( ) ) ;
2024-08-05 18:25:44 +00:00
}
2024-08-03 16:24:04 +00:00
2024-08-11 11:28:24 +00:00
var renderPoly = cell . RenderPolys [ polyIdx ] ;
var light = cell . LightList [ polyIdx ] ;
var textureId = CalcBaseUV ( cell , poly , renderPoly , light , textureUvs , lightmapUvs , indicesOffset ) ;
if ( ! surfaceDataMap . ContainsKey ( textureId ) )
{
2024-08-11 14:43:44 +00:00
surfaceDataMap . Add ( textureId , new MeshSurfaceData ( ) ) ;
2024-08-11 11:28:24 +00:00
}
var surfaceData = surfaceDataMap [ textureId ] ;
var ( start , end ) = surfaceData . AddPolygon ( vertices , normal , textureUvs , lightmapUvs ) ;
2024-08-03 16:24:04 +00:00
2024-08-11 11:28:24 +00:00
if ( polyIdx > = cell . Lightmaps . Length ) GD . Print ( "HUH" ) ;
2024-08-03 16:24:04 +00:00
2024-08-11 11:28:24 +00:00
return new LightmapRectData ( cellIdx , polyIdx , textureId , start , end ) ;
2024-08-03 16:24:04 +00:00
}
2024-08-11 11:28:24 +00:00
private static Texture BuildLightmapTexture ( WorldRep . Cell [ ] cells , PackingRectangle [ ] packingRects , Dictionary < int , LightmapRectData > rectDataMap , Dictionary < int , MeshSurfaceData > surfaceDataMap )
2024-08-03 16:24:04 +00:00
{
2024-07-30 20:50:34 +00:00
RectanglePacker . Pack ( packingRects , out var bounds ) ;
var image = Image . Create ( ( int ) bounds . Width , ( int ) bounds . Height , false , Image . Format . Rgba8 ) ;
foreach ( var rect in packingRects )
{
2024-08-11 11:28:24 +00:00
if ( ! rectDataMap . ContainsKey ( rect . Id ) ) GD . Print ( "Invalid rectDataMap key" ) ;
var info = rectDataMap [ rect . Id ] ;
if ( info . cellIndex > = cells . Length ) GD . Print ( $"CellIndex too big: {info.cellIndex}/{cells.Length}" ) ;
if ( info . lightmapIndex > = cells [ info . cellIndex ] . Lightmaps . Length ) GD . Print ( $"LightmapIndex too big: {info.lightmapIndex}/{cells[info.cellIndex].Lightmaps.Length}" ) ;
var lightmap = cells [ info . cellIndex ] . Lightmaps [ info . lightmapIndex ] ;
2024-08-01 17:23:17 +00:00
var layers = ( uint ) lightmap . Pixels . GetLength ( 0 ) ;
var height = ( uint ) lightmap . Pixels . GetLength ( 1 ) ;
var width = ( uint ) lightmap . Pixels . GetLength ( 2 ) ;
for ( uint y = 0 ; y < height ; y + + )
2024-07-30 20:50:34 +00:00
{
2024-08-01 17:23:17 +00:00
for ( uint x = 0 ; x < width ; x + + )
2024-07-30 20:50:34 +00:00
{
2024-08-01 17:23:17 +00:00
var rawColour = System . Numerics . Vector4 . Zero ;
for ( uint l = 0 ; l < layers ; l + + )
{
rawColour + = lightmap . GetPixel ( l , x , y ) ;
}
2024-08-03 15:09:51 +00:00
2024-08-03 16:24:04 +00:00
var colour = new Color ( MathF . Min ( rawColour . X , 1.0f ) , MathF . Min ( rawColour . Y , 1.0f ) , MathF . Min ( rawColour . Z , 1.0f ) , MathF . Min ( rawColour . W , 1.0f ) ) ;
2024-08-01 17:23:17 +00:00
image . SetPixel ( ( int ) ( rect . X + x ) , ( int ) ( rect . Y + y ) , colour ) ;
2024-07-30 20:50:34 +00:00
}
}
2024-08-11 11:28:24 +00:00
if ( ! surfaceDataMap . ContainsKey ( info . textureId ) ) GD . Print ( "Invalid SurfaceDataMap key" ) ;
surfaceDataMap [ info . textureId ] . TransformLightmapUvs ( info . uvStart , info . uvEnd , ( uv ) = >
2024-07-30 20:50:34 +00:00
{
var u = uv . X ;
var v = uv . Y ;
// Clamp uv range to [0..1]
u % = 1 ;
v % = 1 ;
if ( u < 0 ) u = Math . Abs ( u ) ;
if ( v < 0 ) v = Math . Abs ( v ) ;
// Transform!
u = ( rect . X + rect . Width * u ) / ( int ) bounds . Width ;
v = ( rect . Y + rect . Height * v ) / ( int ) bounds . Height ;
2024-08-11 11:28:24 +00:00
return new Vector2 ( u , v ) ;
} ) ;
2024-08-05 17:46:57 +00:00
}
2024-07-22 19:34:12 +00:00
2024-08-11 11:28:24 +00:00
return ImageTexture . CreateFromImage ( image ) ;
2024-07-21 21:13:31 +00:00
}
2024-07-30 20:50:34 +00:00
2024-08-11 11:28:24 +00:00
private int CalcBaseUV (
2024-07-30 20:50:34 +00:00
WorldRep . Cell cell ,
WorldRep . Cell . Poly poly ,
WorldRep . Cell . RenderPoly renderPoly ,
WorldRep . Cell . LightmapInfo light ,
2024-08-05 18:49:36 +00:00
List < Vector2 > textureUvs ,
2024-07-30 20:50:34 +00:00
List < Vector2 > lightmapUvs ,
int cellIdxOffset )
{
// TODO: This is slightly hardcoded for ND. Check other stuff at some point. Should be handled in LG side imo
// TODO: This is a mess lol
2024-08-05 18:49:36 +00:00
var textureId = renderPoly . TextureId ;
2024-08-11 14:23:56 +00:00
var texture = _textureLoader . Get ( textureId ) ;
2024-08-01 17:29:10 +00:00
var texU = renderPoly . TextureVectors . Item1 . ToGodotVec3 ( ) ;
var texV = renderPoly . TextureVectors . Item2 . ToGodotVec3 ( ) ;
2024-08-05 18:49:36 +00:00
var baseU = renderPoly . TextureBases . Item1 ;
var baseV = renderPoly . TextureBases . Item2 ;
2024-07-30 20:50:34 +00:00
2024-08-05 18:49:36 +00:00
var txUScale = 64.0f / texture . GetWidth ( ) ;
var txVScale = 64.0f / texture . GetHeight ( ) ;
2024-07-30 20:50:34 +00:00
var lmUScale = 4.0f / light . Width ;
var lmVScale = 4.0f / light . Height ;
2024-08-05 18:49:36 +00:00
var txUBase = baseU * txUScale ;
var txVBase = baseV * txVScale ;
2024-07-30 20:50:34 +00:00
var lmUBase = lmUScale * ( baseU + ( 0.5f - light . Bases . Item1 ) / 4.0f ) ;
var lmVBase = lmVScale * ( baseV + ( 0.5f - light . Bases . Item2 ) / 4.0f ) ;
2024-08-05 18:49:36 +00:00
var uu = texU . Dot ( texU ) ;
var vv = texV . Dot ( texV ) ;
var uv = texU . Dot ( texV ) ;
var anchor = cell . Vertices [ cell . Indices [ cellIdxOffset + 0 ] ] . ToGodotVec3 ( ) ; // TODO: This probably shouldn't be hardcoded idx 0
2024-07-30 20:50:34 +00:00
if ( uv = = 0.0 )
{
2024-08-05 18:49:36 +00:00
var txUVec = texU * txUScale / uu ;
var txVVec = texV * txVScale / vv ;
2024-07-30 20:50:34 +00:00
var lmUVec = texU * lmUScale / uu ;
var lmVVec = texV * lmVScale / vv ;
for ( var i = 0 ; i < poly . VertexCount ; i + + )
{
2024-08-01 17:29:10 +00:00
var v = cell . Vertices [ cell . Indices [ cellIdxOffset + i ] ] . ToGodotVec3 ( ) ;
2024-07-30 20:50:34 +00:00
var delta = new Vector3 ( v . X - anchor . X , v . Y - anchor . Y , v . Z - anchor . Z ) ;
2024-08-05 18:49:36 +00:00
var txUV = new Vector2 ( delta . Dot ( txUVec ) + txUBase , delta . Dot ( txVVec ) + txVBase ) ;
2024-07-30 20:50:34 +00:00
var lmUV = new Vector2 ( delta . Dot ( lmUVec ) + lmUBase , delta . Dot ( lmVVec ) + lmVBase ) ;
2024-08-05 18:49:36 +00:00
textureUvs . Add ( txUV ) ;
2024-07-30 20:50:34 +00:00
lightmapUvs . Add ( lmUV ) ;
}
}
else
{
var denom = 1.0f / ( uu * vv - uv * uv ) ;
2024-08-05 18:49:36 +00:00
var txUu = uu * txVScale * denom ;
var txVv = vv * txUScale * denom ;
var txUvu = txUScale * denom * uv ;
var txUvv = txVScale * denom * uv ;
2024-07-30 20:50:34 +00:00
var lmUu = uu * lmVScale * denom ;
var lmVv = vv * lmUScale * denom ;
var lmUvu = lmUScale * denom * uv ;
var lmUvv = lmVScale * denom * uv ;
for ( var i = 0 ; i < poly . VertexCount ; i + + )
{
2024-08-01 17:29:10 +00:00
var v = cell . Vertices [ cell . Indices [ cellIdxOffset + i ] ] . ToGodotVec3 ( ) ;
2024-07-30 20:50:34 +00:00
var delta = new Vector3 ( v . X - anchor . X , v . Y - anchor . Y , v . Z - anchor . Z ) ;
var du = delta . Dot ( texU ) ;
var dv = delta . Dot ( texV ) ;
2024-08-05 18:49:36 +00:00
var txUV = new Vector2 ( txUBase + txVv * du - txUvu * dv , txVBase + txUu * dv - txUvv * du ) ;
2024-07-30 20:50:34 +00:00
var lmUV = new Vector2 ( lmUBase + lmVv * du - lmUvu * dv , lmVBase + lmUu * dv - lmUvv * du ) ;
2024-08-05 18:49:36 +00:00
textureUvs . Add ( txUV ) ;
2024-07-30 20:50:34 +00:00
lightmapUvs . Add ( lmUV ) ;
}
}
2024-08-11 11:28:24 +00:00
return textureId ;
2024-07-30 20:50:34 +00:00
}
2024-08-03 15:09:51 +00:00
private void LoadTextures ( TxList textureList )
{
2024-08-07 17:40:46 +00:00
// TODO: Use PathJoin
2024-08-03 15:09:51 +00:00
var count = textureList . ItemCount ;
for ( var i = 0 ; i < count ; i + + )
{
var item = textureList . Items [ i ] ;
2024-08-10 07:58:02 +00:00
var path = "/" ;
2024-08-03 15:09:51 +00:00
for ( var j = 0 ; j < item . Tokens . Length ; j + + )
{
var token = item . Tokens [ j ] ;
if ( token = = 0 )
{
break ;
}
path + = $"{textureList.Tokens[token - 1]}/" ;
}
2024-08-07 19:10:45 +00:00
path + = item . Name ;
2024-08-03 15:09:51 +00:00
2024-08-11 14:23:56 +00:00
if ( ! _textureLoader . Load ( i , path ) )
2024-08-03 15:09:51 +00:00
{
2024-08-11 14:23:56 +00:00
GD . Print ( $"Failed to load texture: {path}" ) ;
2024-08-03 15:09:51 +00:00
}
}
}
private static void DumpTextureList ( TxList textureList )
{
GD . Print ( $"TXLIST:\n BlockSize: {textureList.BlockSize}\n ItemCount: {textureList.ItemCount}\n TokenCount: {textureList.TokenCount}\n Tokens:" ) ;
for ( var i = 0 ; i < textureList . TokenCount ; i + + )
{
GD . Print ( $" {i}: {textureList.Tokens[i]}" ) ;
}
GD . Print ( $" Items:" ) ;
for ( var i = 0 ; i < textureList . ItemCount ; i + + )
{
var item = textureList . Items [ i ] ;
GD . Print ( $" {i}:\n Tokens: [{item.Tokens[0]}, {item.Tokens[1]}, {item.Tokens[2]}, {item.Tokens[3]}]\n Name: {item.Name}" ) ;
}
}
2024-08-11 14:43:44 +00:00
const string MATERIAL_PATH = "res://project/materials/base.tres" ;
2024-08-13 20:38:00 +00:00
private static Material BuildMaterial ( Texture albedoTexture , Texture lightmapTexture , bool lmHdr )
2024-08-11 14:43:44 +00:00
{
var material = ResourceLoader . Load < ShaderMaterial > ( MATERIAL_PATH ) . Duplicate ( ) as ShaderMaterial ;
material . SetShaderParameter ( "texture_albedo" , albedoTexture ) ;
material . SetShaderParameter ( "lightmap_albedo" , lightmapTexture ) ;
2024-08-13 20:38:00 +00:00
material . SetShaderParameter ( "lightmap_2x" , lmHdr ) ;
2024-08-11 14:43:44 +00:00
return material ;
}
2024-07-21 21:13:31 +00:00
}