├── .gitattributes ├── .gitignore ├── AssetDumper ├── AssetDumper.csproj ├── BaseCommand.cs ├── Collada.cs ├── ExportBundleCommand.cs └── Program.cs ├── ChunkView ├── ChunkView.csproj ├── ChunkViewCompare.Designer.cs ├── ChunkViewCompare.cs ├── ChunkViewCompare.resx ├── ChunkViewMain.Designer.cs ├── ChunkViewMain.cs ├── ChunkViewMain.resx ├── Program.cs └── Resources │ └── ChunkDef.txt ├── Common.Windows ├── Common.Windows.csproj └── MessageUtil.cs ├── Common ├── BasicResource.cs ├── BinaryUtil.cs ├── Chunk.cs ├── ChunkManager.cs ├── ChunkStream.cs ├── Common.csproj ├── Compression.cs ├── GameDetector.cs ├── Geometry │ ├── CarbonSolidListReader.cs │ ├── CarbonSolidReader.cs │ ├── Data │ │ ├── CarbonObject.cs │ │ ├── IEffectBasedMaterial.cs │ │ ├── IMorphableSolid.cs │ │ ├── ISortedMaterial.cs │ │ ├── MostWantedObject.cs │ │ ├── ProStreetObject.cs │ │ ├── SolidList.cs │ │ ├── SolidMeshVertex.cs │ │ ├── SolidObject.cs │ │ ├── SolidObjectMaterial.cs │ │ ├── UndercoverObject.cs │ │ ├── Underground2Object.cs │ │ ├── UndergroundObject.cs │ │ ├── World09Object.cs │ │ └── World15Object.cs │ ├── MostWantedSolidListReader.cs │ ├── MostWantedSolidReader.cs │ ├── ProStreetSolidListReader.cs │ ├── ProStreetSolidReader.cs │ ├── SolidListReader.cs │ ├── SolidReader.cs │ ├── UndercoverEffectId.cs │ ├── UndercoverSolidListReader.cs │ ├── UndercoverSolidReader.cs │ ├── Underground2SolidListReader.cs │ ├── Underground2SolidReader.cs │ ├── UndergroundSolidListReader.cs │ ├── UndergroundSolidReader.cs │ ├── World09SolidListReader.cs │ ├── World09SolidReader.cs │ ├── WorldSolidListReader.cs │ └── WorldSolidReader.cs ├── Hasher.cs ├── Lights │ ├── Data │ │ └── LightPack.cs │ ├── LightPackReader.cs │ └── Structures │ │ ├── LightData.cs │ │ └── LightPackHeader.cs ├── Scenery │ ├── CarbonScenery.cs │ ├── Data │ │ └── ScenerySection.cs │ ├── MostWantedScenery.cs │ ├── ProStreetScenery.cs │ ├── SceneryManager.cs │ ├── Structures │ │ ├── PackedRotationMatrix.cs │ │ └── RotationMatrix.cs │ ├── UndercoverScenery.cs │ ├── Underground2Scenery.cs │ ├── UndergroundScenery.cs │ └── WorldScenery.cs └── Textures │ ├── Data │ ├── DDSHeader.cs │ └── TexturePack.cs │ ├── TpkManager.cs │ ├── Version1Tpk.cs │ ├── Version2Tpk.cs │ ├── Version3Tpk.cs │ ├── Version4Tpk.cs │ ├── Version5Tpk.cs │ └── World10Tpk.cs ├── FBXSharp ├── Connection.cs ├── Core │ ├── CoordSystem.cs │ ├── DataView.cs │ ├── Footer.cs │ ├── FrameRate.cs │ ├── FrontVector.cs │ ├── Header.cs │ ├── IElement.cs │ ├── IElementAttribute.cs │ ├── IElementProperty.cs │ ├── IScene.cs │ └── UpVector.cs ├── Element.cs ├── Elementary │ ├── ArrayBooleanAttribute.cs │ ├── ArrayDoubleAttribute.cs │ ├── ArrayInt32Attribute.cs │ ├── ArrayInt64Attribute.cs │ ├── ArraySingleAttribute.cs │ ├── BinaryAttribute.cs │ ├── ByteAttribute.cs │ ├── DoubleAttribute.cs │ ├── Int16Attribute.cs │ ├── Int32Attribute.cs │ ├── Int64Attribute.cs │ ├── SingleAttribute.cs │ └── StringAttribute.cs ├── ElementaryFactory.cs ├── FBXExporter7400.cs ├── FBXImporter.cs ├── FBXObject.cs ├── FBXProperty.cs ├── FBXSharp.csproj ├── GlobalSettings.cs ├── IOExtensions.cs ├── LoadFlags.cs ├── MathExtensions.cs ├── Objective │ ├── AnimationCurve.cs │ ├── AnimationCurveNode.cs │ ├── AnimationLayer.cs │ ├── AnimationStack.cs │ ├── BindPose.cs │ ├── BlendShape.cs │ ├── BlendShapeChannel.cs │ ├── Camera.cs │ ├── Clip.cs │ ├── Cluster.cs │ ├── Geometry.cs │ ├── Light.cs │ ├── LimbNode.cs │ ├── Material.cs │ ├── Mesh.cs │ ├── Model.cs │ ├── NullNode.cs │ ├── Root.cs │ ├── Shape.cs │ ├── Skin.cs │ └── Texture.cs ├── PropertyFactory.cs ├── Scene.cs ├── TakeInfo.cs ├── TemplateFactory.cs ├── TemplateObject.cs └── ValueTypes │ ├── BinaryBlob.cs │ ├── Color.cs │ ├── Distance.cs │ ├── Enumeration.cs │ ├── Half.cs │ ├── Matrix4x4.cs │ ├── Quaternion.cs │ ├── Reference.cs │ ├── RotationOrder.cs │ ├── TimeBase.cs │ ├── Vector2.cs │ ├── Vector3.cs │ └── Vector4.cs ├── NFS ModTools.sln └── Viewer ├── App.xaml ├── App.xaml.cs ├── AssetRegistry.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ObservableConcurrentDictionary.cs ├── Properties ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── RenderManager.cs ├── Viewer.csproj └── nfsw_IDI_ICON1.ico /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /AssetDumper/AssetDumper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | Exe 5 | true 6 | 7 | 8 | 2.3.0 9 | heyitsleo 10 | NFSTools 11 | AssetDumper 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /AssetDumper/BaseCommand.cs: -------------------------------------------------------------------------------- 1 | namespace AssetDumper; 2 | 3 | public abstract class BaseCommand 4 | { 5 | public abstract int Execute(); 6 | } -------------------------------------------------------------------------------- /AssetDumper/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandLine; 3 | using Serilog; 4 | 5 | namespace AssetDumper 6 | { 7 | internal static class Program 8 | { 9 | /// 10 | /// The entry point of the application. 11 | /// 12 | /// 13 | public static int Main(string[] args) 14 | { 15 | Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); 16 | 17 | try 18 | { 19 | return Parser.Default.ParseArguments(args, typeof(ExportBundleCommand)) 20 | .MapResult((BaseCommand cmd) => cmd.Execute(), _ => 1); 21 | } 22 | catch (Exception e) 23 | { 24 | Log.Error(e, "An unhandled error occurred in the application."); 25 | return 1; 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /ChunkView/ChunkView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0-windows 4 | WinExe 5 | false 6 | true 7 | true 8 | Debug;Release 9 | AnyCPU 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ChunkView/ChunkViewCompare.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace ChunkView 2 | { 3 | partial class ChunkViewCompare 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.hexBox1 = new Be.Windows.Forms.HexBox(); 32 | this.hexBox2 = new Be.Windows.Forms.HexBox(); 33 | this.SuspendLayout(); 34 | // 35 | // hexBox1 36 | // 37 | this.hexBox1.BackColor = System.Drawing.Color.Gainsboro; 38 | this.hexBox1.Font = new System.Drawing.Font("Segoe UI", 9F); 39 | this.hexBox1.Location = new System.Drawing.Point(1, 12); 40 | this.hexBox1.Name = "hexBox1"; 41 | this.hexBox1.ShadowSelectionColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(60)))), ((int)(((byte)(188)))), ((int)(((byte)(255))))); 42 | this.hexBox1.Size = new System.Drawing.Size(487, 684); 43 | this.hexBox1.TabIndex = 0; 44 | // 45 | // hexBox2 46 | // 47 | this.hexBox2.BackColor = System.Drawing.Color.Gainsboro; 48 | this.hexBox2.Font = new System.Drawing.Font("Segoe UI", 9F); 49 | this.hexBox2.Location = new System.Drawing.Point(494, 12); 50 | this.hexBox2.Name = "hexBox2"; 51 | this.hexBox2.ShadowSelectionColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(60)))), ((int)(((byte)(188)))), ((int)(((byte)(255))))); 52 | this.hexBox2.Size = new System.Drawing.Size(513, 684); 53 | this.hexBox2.TabIndex = 1; 54 | // 55 | // ChunkViewCompare 56 | // 57 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 58 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 59 | this.ClientSize = new System.Drawing.Size(1008, 729); 60 | this.Controls.Add(this.hexBox2); 61 | this.Controls.Add(this.hexBox1); 62 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; 63 | this.Name = "ChunkViewCompare"; 64 | this.Text = "ChunkViewCompare"; 65 | this.ResumeLayout(false); 66 | 67 | } 68 | 69 | #endregion 70 | 71 | private Be.Windows.Forms.HexBox hexBox1; 72 | private Be.Windows.Forms.HexBox hexBox2; 73 | } 74 | } -------------------------------------------------------------------------------- /ChunkView/ChunkViewCompare.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace ChunkView 12 | { 13 | public partial class ChunkViewCompare : Form 14 | { 15 | public ChunkViewCompare() 16 | { 17 | hexBox1.Font = new Font("Consolas", 10.25F); 18 | hexBox1.ReadOnly = true; 19 | hexBox1.LineInfoVisible = true; 20 | hexBox1.ShadowSelectionColor = Color.FromArgb(100, 60, 188, 255); 21 | hexBox1.StringViewVisible = true; 22 | hexBox1.UseFixedBytesPerLine = true; 23 | hexBox1.VScrollBarVisible = true; 24 | hexBox1.ColumnInfoVisible = true; 25 | 26 | hexBox2.Font = new Font("Consolas", 10.25F); 27 | hexBox2.ReadOnly = true; 28 | hexBox2.LineInfoVisible = true; 29 | hexBox2.ShadowSelectionColor = Color.FromArgb(100, 60, 188, 255); 30 | hexBox2.StringViewVisible = true; 31 | hexBox2.UseFixedBytesPerLine = true; 32 | hexBox2.VScrollBarVisible = true; 33 | hexBox2.ColumnInfoVisible = true; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ChunkView/ChunkViewCompare.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /ChunkView/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace ChunkView 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// The main entry point for the application. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | Application.Run(new ChunkViewMain()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ChunkView/Resources/ChunkDef.txt: -------------------------------------------------------------------------------- 1 | 0xb3300000 BCHUNK_SPEED_TEXTURE_PACK_LIST_CHUNKS 2 | 0xb0300100 BCHUNK_SPEED_TEXTURE_PACK_LIST_CHUNKS_ANIM 3 | 0x80134000 BCHUNK_SPEED_ESOLID_LIST_CHUNKS 4 | 0x80034100 BCHUNK_SPEED_SCENERY_SECTION 5 | 0x00034101 BCHUNK_SPEED_SCENERY_SECTION_HEADER 6 | 0x00034102 BCHUNK_SPEED_SCENERY_SECTION_INFOS 7 | 0x00034103 BCHUNK_SPEED_SCENERY_SECTION_INSTANCES 8 | 0x00034105 BCHUNK_SPEED_SCENERY_SECTION_TREE 9 | 0x00034106 BCHUNK_SPEED_SCENERY_SECTION_OVERRIDES 10 | 0x00034107 BCHUNK_SPEED_SCENERY_SECTION_PRECULLER_INFO 11 | 0x0003410A BCHUNK_SPEED_SCENERY_SECTION_NGBBS 12 | 0x00034C03 BCHUNK_SPEED_SCENERY_SECTION_VECTORIZED_INFO 13 | 0x0003410D BCHUNK_SPEED_SCENERY_SECTION_LIGHT_TEXTURES 14 | 0x0003410E BCHUNK_SPEED_SCENERY_SECTION_VECTORIZED_CULL_INFO 15 | 0x0003410F BCHUNK_SPEED_SCENERY_SECTION_VECTORIZED_INSTANCE_INFO 16 | 0x00034027 BCHUNK_SPEED_SMOKEABLE_SPAWNER 17 | 0x00037260 BCHUNK_SPEED_BBGANIM_INSTANCE_TREE 18 | 0x00037250 BCHUNK_SPEED_BBGANIM_INSTANCE_NODE 19 | 0x00037240 BCHUNK_SPEED_BBGANIM_KEYFRAMES 20 | 0x00037270 BCHUNK_SPEED_BBGANIM_ENDPACKHEADER 21 | 0x80135000 BCHUNK_SPEED_ELIGHT_CHUNKS 22 | 0x00135001 BCHUNK_SPEED_ELIGHT_CHUNKS_HEADER 23 | 0x00135002 BCHUNK_SPEED_ELIGHT_CHUNKS_LIGHTTREE 24 | 0x00135003 BCHUNK_SPEED_ELIGHT_CHUNKS_LIGHTS 25 | 0x80036000 BCHUNK_SPEED_EMTRIGGER_PACK 26 | 0x00036001 BCHUNK_SPEED_EMTRIGGER_PACK_HEADER 27 | 0x00036002 BCHUNK_SPEED_EMTRIGGER_PACK_EVENTTREE 28 | 0x00036003 BCHUNK_SPEED_EMTRIGGER_PACK_TRIGGERS 29 | 0x00037220 BCHUNK_SPEED_BBGANIM_BLOCKHEADER 30 | 0x0003bc00 BCHUNK_SPEED_EMITTER_LIBRARY 31 | 0x00030201 BCHUNK_FENG_FONT 32 | 0x00030210 BCHUNK_FENG_PACKAGE_COMPRESSED 33 | 0x00030203 BCHUNK_FENG_PACKAGE 34 | 0x00135200 BCHUNK_ELIGHTS 35 | 0x00034600 BCHUNK_CARINFO_ARRAY 36 | 0x00034601 BCHUNK_CARINFO_SKININFO 37 | 0x00034608 BCHUNK_CARINFO_ANIMHOOKUPTABLE 38 | 0x00034609 BCHUNK_CARINFO_ANIMHIDETABLES 39 | 0x00034607 BCHUNK_CARINFO_SLOTTYPES 40 | 0x80034602 BCHUNK_CARINFO_CARPART 41 | 0x00034201 BCHUNK_TRACKINFO 42 | 0x00034202 BCHUNK_SUN 43 | 0x80035000 BCHUNK_ACIDFX 44 | 0x80035010 BCHUNK_ACIDFX 45 | 0x00035021 BCHUNK_ACIDFX 46 | 0x00035020 BCHUNK_ACIDFX_EMITTER 47 | 0x00034b00 BCHUNK_DIFFICULTYINFO 48 | 0x00034a07 BCHUNK_STYLEMOMENTSINFO 49 | 0x00030220 BCHUNK_FEPRESETCARS 50 | 0x00e34009 BCHUNK_EAGLSKELETONS 51 | 0x00e34010 BCHUNK_EAGLANIMATIONS 52 | 0x00039020 BCHUNK_MOVIECATALOG 53 | 0x8003b900 BCHUNK_BOUNDS 54 | 0x0003b901 BCHUNK_BOUNDS_COLLECTION 55 | 0x0003bd00 BCHUNK_EMITTERSYSTEM_TEXTUREPAGE 56 | 0xb0300300 BCHUNK_PCAWEIGHTS 57 | 0x30300201 BCHUNK_COLORCUBE 58 | 0x80037050 BCHUNK_ANIMDIRECTORYDATA 59 | 0x8003b200 BCHUNK_ICECAMERASET 60 | 0x8003B201 BCHUNK_ICECAMERASET 61 | 0x8003b202 BCHUNK_ICECAMERASET 62 | 0x8003b203 BCHUNK_ICECAMERASET 63 | 0x8003b500 BCHUNK_SOUNDSTICHS 64 | 0x80034147 BCHUNK_TRACKPATHMANAGER 65 | 0x0003414A BCHUNK_TRACKPATHMANAGER_ZONES 66 | 0x00034146 BCHUNK_TRACKPOSITIONMARKERS 67 | 0x00034158 BCHUNK_VISIBLESECTIONOVERLAY 68 | 0x80034150 BCHUNK_VISIBLESECTIONMANAGER 69 | 0x00034151 BCHUNK_VISIBLESECTIONMANAGER_INFO 70 | 0x00034152 BCHUNK_VISIBLESECTIONMANAGER_BOUNDARIES 71 | 0x00034153 BCHUNK_VISIBLESECTIONMANAGER_DRIVABLE_SECTIONS 72 | 0x00034155 BCHUNK_VISIBLESECTIONMANAGER_LOADING_SECTIONS 73 | 0x00034156 BCHUNK_VISIBLESECTIONMANAGER_ELEVATION_POLYS 74 | 0x00034157 BCHUNK_VISIBLESECTIONMANAGER_FRAGMENT_FILE_MAP 75 | 0x00034250 BCHUNK_WEATHERMAN 76 | 0x8003b000 BCHUNK_QUICKSPLINE 77 | 0x8003b600 BCHUNK_PARAMETERMAPS 78 | 0x8003b601 BCHUNK_PARAMETERMAP 79 | 0x0003b602 BCHUNK_PARAMETERMAP_HEADER 80 | 0x0003b603 BCHUNK_PARAMETERMAP_FIELD_TYPES 81 | 0x0003b604 BCHUNK_PARAMETERMAP_FIELD_OFFSETS 82 | 0x0003b605 BCHUNK_PARAMETERMAP_PARAMETER_DATA 83 | 0x0003b607 BCHUNK_PARAMETERMAP_PARAMETER_QUAD8 84 | 0x0003b608 BCHUNK_PARAMETERMAP_PARAMETER_QUAD16 85 | 0x00034108 BCHUNK_SCENERY_OVERRIDES 86 | 0x00034109 BCHUNK_SCENERY_GROUPS 87 | 0x8003410b BCHUNK_MODEL_HIERARCHIES 88 | 0x0003410c BCHUNK_MODEL_HIERARCHY 89 | 0x0003b800 BCHUNK_WWORLD 90 | 0x0003b801 BCHUNK_CARP_WCOLLISIONPACK 91 | 0x8003b810 BCHUNK_EVENTSEQUENCE 92 | 0x0003414d BCHUNK_TRACKPATHMANAGER_BARRIERS 93 | 0x00037080 BCHUNK_WORLDANIMENTITYDATA 94 | 0x00037110 BCHUNK_WORLDANIMTREEMARKER 95 | 0x00037150 BCHUNK_WORLDANIMINSTANCEENTRY 96 | 0x00037090 BCHUNK_WORLDANIMDIRECTORYDATA 97 | 0x30300200 BCHUNK_DDSTEXTURE 98 | 0x0003ce12 BCHUNK_SKINREGIONDATABASE 99 | 0x0003ce13 BCHUNK_VINYLMETADATA 100 | 0x0003b200 BCHUNK_ICECAMERAS 101 | 0x00039000 BCHUNK_LANGUAGE 102 | 0x00039001 BCHUNK_LANGUAGEHISTOGRAM 103 | 0x00034a08 BCHUNK_STYLEREWARDCHUNK 104 | 0x00030230 BCHUNK_MAGAZINES 105 | 0x00034026 BCHUNK_SMOKEABLES 106 | 0x00034492 BCHUNK_CAMERA 107 | 0x80034405 BCHUNK_CAMERA 108 | 0x80034425 BCHUNK_CAMERA 109 | 0x80034410 BCHUNK_CAMERA 110 | 0x80034415 BCHUNK_CAMERA 111 | 0x80034420 BCHUNK_CAMERA 112 | 0x0003a000 BCHUNK_ELIPSE_TABLE 113 | 0x00034036 BCHUNK_NIS_SCENE_MAPPER_DATA 114 | 0x00034121 BCHUNK_TRACKROUTE_MANAGER 115 | 0x00034122 BCHUNK_TRACKROUTE_SIGNPOSTS 116 | 0x00034123 BCHUNK_TRACKROUTE_TRAFFIC_INTERSECTIONS 117 | 0x00034124 BCHUNK_TRACKROUTE_CROSS_TRAFFIC_EMITTERS 118 | 0x00034130 BCHUNK_TOPOLOGYTREE 119 | 0x00034131 BCHUNK_TOPOLOGYTREE 120 | 0x00034132 BCHUNK_TOPOLOGYTREE 121 | 0x00034133 BCHUNK_TOPOLOGYTREE 122 | 0x00034134 BCHUNK_TOPOLOGYTREE 123 | 0x0003b300 BCHUNK_WORLDOBJECTS 124 | 0x00034a09 BCHUNK_PERFUPGRADELEVELINFOCHUNK 125 | 0x00034a0a BCHUNK_PERFUPGRADEPACKAGECHUNK 126 | 0x00030240 BCHUNK_WIDEDECALS 127 | 0x00034a03 BCHUNK_RANKINGLADDERS 128 | 0x00039010 BCHUNK_SUBTITLES 129 | 0x00034035 BCHUNK_NISSCENEDATA 130 | 0x80037020 BCHUNK_ANIMSCENEDATA 131 | 0x0003b811 BCHUNK_EVENTSEQUENCE 132 | 0x0003b802 BCHUNK_CARP_WGRIDISLAND 133 | 0x00034110 BCHUNK_TRACKSTREAMER_SECTIONS 134 | 0x00034111 BCHUNK_TRACKSTREAMER_INFO 135 | 0x00034112 BCHUNK_TRACKSTREAMER_BARRIERS 136 | 0x00034113 BCHUNK_TRACKSTREAMER_DISC_SECTIONS 137 | 0x00034114 BCHUNK_SCENERY_GROUP_OVERRIDES 138 | 0x00034185 BCHUNK_VISCURTAINS -------------------------------------------------------------------------------- /Common.Windows/Common.Windows.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0-windows 4 | true 5 | Debug;Release 6 | AnyCPU 7 | 8 | 9 | -------------------------------------------------------------------------------- /Common.Windows/MessageUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace Common.Windows 4 | { 5 | public static class MessageUtil 6 | { 7 | private static string _appName = "UNLOCALIZED"; 8 | 9 | public static void SetAppName(string appName) 10 | { 11 | _appName = appName; 12 | } 13 | 14 | public static DialogResult ShowInfo(string message) 15 | { 16 | return MessageBox.Show(message, _appName, MessageBoxButtons.OK, MessageBoxIcon.Information); 17 | } 18 | 19 | public static DialogResult ShowError(string message) 20 | { 21 | return MessageBox.Show(message, _appName, MessageBoxButtons.OK, MessageBoxIcon.Error); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Common/BasicResource.cs: -------------------------------------------------------------------------------- 1 | namespace Common; 2 | 3 | public class BasicResource 4 | { 5 | } -------------------------------------------------------------------------------- /Common/Chunk.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common; 4 | 5 | public class Chunk 6 | { 7 | public uint Id { get; set; } 8 | 9 | public uint Size { get; set; } 10 | 11 | public long Offset { get; set; } 12 | 13 | public byte[] Data { get; set; } 14 | public BasicResource Resource { get; set; } 15 | 16 | public List SubChunks { get; set; } 17 | } -------------------------------------------------------------------------------- /Common/ChunkStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using SysStream = System.IO.Stream; 6 | 7 | namespace Common 8 | { 9 | // Code in this file is based on Arushan's Most Wanted Geometry Compiler. 10 | 11 | public struct FixedLenString 12 | { 13 | private int _length; 14 | private string _string; 15 | 16 | public FixedLenString(string data) 17 | { 18 | _length = data.Length + 4 - data.Length % 4; 19 | _string = data; 20 | } 21 | 22 | public FixedLenString(string data, int length) 23 | { 24 | _length = length; 25 | _string = data; 26 | } 27 | 28 | public FixedLenString(BinaryReader br, int length) 29 | { 30 | _length = 0; 31 | _string = ""; 32 | Read(br, length); 33 | } 34 | 35 | public FixedLenString(BinaryReader br) 36 | { 37 | _length = 0; 38 | _string = ""; 39 | Read(br); 40 | } 41 | 42 | public byte Length => (byte)_length; 43 | 44 | public void Read(BinaryReader br, int length) 45 | { 46 | _length = length; 47 | var bytes = br.ReadBytes(length); 48 | var data = Encoding.ASCII.GetString(bytes); 49 | _string = data.TrimEnd((char)0); 50 | } 51 | 52 | public void Read(BinaryReader br) 53 | { 54 | _length = 0; 55 | _string = ""; 56 | while (true) 57 | { 58 | var bytes = br.ReadBytes(4); 59 | var data = Encoding.ASCII.GetString(bytes); 60 | _string += data; 61 | _length += 4; 62 | if (data.IndexOf((char)0) < 0) continue; 63 | _string = _string.TrimEnd((char)0); 64 | break; 65 | } 66 | } 67 | 68 | public void Write(BinaryWriter bw) 69 | { 70 | var data = _string.PadRight(_length, (char)0); 71 | var bytes = Encoding.ASCII.GetBytes(data); 72 | bw.Write(bytes); 73 | } 74 | 75 | public override string ToString() 76 | { 77 | return _string; 78 | } 79 | } 80 | 81 | public class RealChunk 82 | { 83 | public bool IsParent => (Type & 0x80000000) != 0; 84 | 85 | public uint Type { get; set; } 86 | 87 | public long EndOffset 88 | { 89 | get => Offset + Length + 0x8; 90 | set => Length = (uint)(value - Offset - 0x8); 91 | } 92 | 93 | public long Offset { get; set; } 94 | 95 | public uint Length { get; set; } 96 | 97 | public void Read(BinaryReader br) 98 | { 99 | Offset = (int)br.BaseStream.Position; 100 | Type = br.ReadUInt32(); 101 | Length = br.ReadUInt32(); 102 | } 103 | 104 | public void Write(BinaryWriter bw) 105 | { 106 | var offset = bw.BaseStream.Position; 107 | bw.BaseStream.Seek(Offset, SeekOrigin.Begin); 108 | bw.Write(Type); 109 | bw.Write(Length); 110 | bw.BaseStream.Seek(offset, SeekOrigin.Begin); 111 | } 112 | 113 | public void GoToStart(SysStream fs) 114 | { 115 | fs.Seek(Offset + 8, SeekOrigin.Begin); 116 | } 117 | 118 | public void Skip(SysStream fs) 119 | { 120 | fs.Seek(EndOffset, SeekOrigin.Begin); 121 | } 122 | 123 | public override string ToString() 124 | { 125 | return "RealChunk {\n\tType=" + $"{Type:X}" 126 | + ",\n\tOffset=" + $"{Offset:X}" 127 | + ",\n\tLength=" + $"{Length:X}" 128 | + "\n}"; 129 | } 130 | } 131 | 132 | public class ChunkStream : IDisposable 133 | { 134 | private readonly BinaryWriter _binaryWriter; 135 | private readonly Stack _chunkStack; 136 | private readonly SysStream _stream; 137 | private bool _canWrite; 138 | 139 | public ChunkStream(SysStream stream) 140 | { 141 | _stream = stream; 142 | _chunkStack = new Stack(); 143 | _binaryWriter = new BinaryWriter(_stream); 144 | _canWrite = true; 145 | } 146 | 147 | public ChunkStream(BinaryReader binaryReader) 148 | { 149 | _stream = binaryReader.BaseStream; 150 | _chunkStack = new Stack(); 151 | } 152 | 153 | public ChunkStream(BinaryWriter binaryWriter) 154 | { 155 | _stream = binaryWriter.BaseStream; 156 | _chunkStack = new Stack(); 157 | _binaryWriter = binaryWriter; 158 | _canWrite = true; 159 | } 160 | 161 | /// 162 | public void Dispose() 163 | { 164 | _stream?.Dispose(); 165 | 166 | if (!_canWrite) return; 167 | 168 | _binaryWriter?.Dispose(); 169 | _canWrite = false; 170 | } 171 | 172 | public SysStream GetStream() 173 | { 174 | return _stream; 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /Common/Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | Library 5 | false 6 | true 7 | Debug;Release 8 | AnyCPU 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Common/Compression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CompLib; 4 | 5 | namespace Common 6 | { 7 | /// 8 | /// Wrapper around CompLib API 9 | /// 10 | public static class Compression 11 | { 12 | public static Span Decompress(ReadOnlySpan input) 13 | { 14 | return BlobDecompressor.Decompress(input); 15 | } 16 | 17 | public static long DecompressCip(Stream src, Stream dst, long srcSize) 18 | { 19 | CipDecompressor.Decompress(src, dst, srcSize, out var decompressedSize); 20 | 21 | return decompressedSize; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Common/GameDetector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Common 5 | { 6 | public static class GameDetector 7 | { 8 | public enum Game 9 | { 10 | MostWanted, 11 | Carbon, 12 | ProStreet, 13 | ProStreetTest, // special value 14 | World, 15 | World09, 16 | World10, 17 | Underground2, 18 | Underground, 19 | Unknown, 20 | Undercover 21 | } 22 | 23 | /// 24 | /// Attempt to determine the NFS game installed in the given directory. 25 | /// 26 | /// 27 | /// 28 | public static Game DetectGame(string directory) 29 | { 30 | // speed.exe can be UG1 or MW 31 | if (File.Exists(Path.Combine(directory, "speed.exe"))) 32 | { 33 | var tracksPath = Path.Combine(directory, "TRACKS"); 34 | if (!Directory.Exists(tracksPath)) 35 | { 36 | throw new ArgumentException("TRACKS folder does not exist! Cannot determine game."); 37 | } 38 | 39 | if (File.Exists(Path.Combine(tracksPath, "L2RA.BUN")) 40 | && File.Exists(Path.Combine(tracksPath, "STREAML2RA.BUN"))) 41 | { 42 | return Game.MostWanted; 43 | } 44 | 45 | if (File.Exists(Path.Combine(tracksPath, "STREAML1RA.BUN"))) 46 | { 47 | return Game.Underground; 48 | } 49 | } 50 | 51 | if (File.Exists(Path.Combine(directory, "speed2.exe"))) 52 | { 53 | return Game.Underground2; 54 | } 55 | 56 | if (File.Exists(Path.Combine(directory, "nfsc.exe"))) 57 | { 58 | return Game.Carbon; 59 | } 60 | 61 | if (File.Exists(Path.Combine(directory, "nfs.exe"))) 62 | { 63 | var tracksPath = Path.Combine(directory, "TRACKS"); 64 | if (!Directory.Exists(tracksPath)) 65 | { 66 | throw new ArgumentException("TRACKS folder does not exist! Cannot determine game."); 67 | } 68 | 69 | if (File.Exists(Path.Combine(tracksPath, "L8R_MW2.BUN")) 70 | && File.Exists(Path.Combine(tracksPath, "STREAML8R_MW2.BUN"))) 71 | { 72 | return Game.Undercover; 73 | } 74 | 75 | return Game.ProStreet; 76 | } 77 | 78 | return File.Exists(Path.Combine(directory, "nfsw.exe")) ? Game.World : Game.Unknown; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Common/Geometry/CarbonSolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry 7 | { 8 | public class CarbonMaterial : SolidObjectMaterial, IEffectBasedMaterial 9 | { 10 | public uint EffectId { get; set; } 11 | } 12 | 13 | public class CarbonSolidListReader : SolidListReader 14 | { 15 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 16 | uint chunkSize) 17 | { 18 | switch (chunkId) 19 | { 20 | case 0x134002: 21 | var info = BinaryUtil.ReadStruct(binaryReader); 22 | solidList.Filename = info.Filename; 23 | solidList.GroupName = info.GroupName; 24 | return true; 25 | case 0x134003: 26 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 27 | return true; 28 | case 0x134004: 29 | ProcessStreamingTable(solidList, binaryReader, chunkSize); 30 | return 31 | false; // We need to stop after processing the streaming table, otherwise we'll run into bad stuff 32 | default: 33 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 34 | } 35 | } 36 | 37 | private void ProcessStreamingTable(SolidList solidList, BinaryReader binaryReader, uint chunkSize) 38 | { 39 | var chunkEndPos = binaryReader.BaseStream.Position + chunkSize; 40 | Debug.Assert(chunkSize % 24 == 0, "chunkSize % 24 == 0"); 41 | while (binaryReader.BaseStream.Position < chunkEndPos) 42 | { 43 | var och = BinaryUtil.ReadUnmanagedStruct(binaryReader); 44 | var curPos = binaryReader.BaseStream.Position; 45 | binaryReader.BaseStream.Position = och.Offset; 46 | 47 | Debug.Assert(och.Length >= 8, "och.Length >= 8"); 48 | 49 | if (och.Length == och.LengthCompressed) 50 | { 51 | // Assume that the object is uncompressed. 52 | // If this ever turns out to be false, I don't know what I'll do. 53 | binaryReader.BaseStream.Position += 8; 54 | solidList.Objects.Add(CreateObjectReader().Read(binaryReader, och.Length - 8)); 55 | } 56 | else 57 | { 58 | // Load decompressed object 59 | using var ms = new MemoryStream(); 60 | Compression.DecompressCip(binaryReader.BaseStream, ms, och.LengthCompressed); 61 | 62 | ms.Position = 8; 63 | using var dcr = new BinaryReader(ms); 64 | solidList.Objects.Add(CreateObjectReader().Read(dcr, och.Length - 8)); 65 | } 66 | 67 | binaryReader.BaseStream.Position = curPos; 68 | } 69 | } 70 | 71 | protected override SolidReader CreateObjectReader() 72 | { 73 | return new CarbonSolidReader(); 74 | } 75 | 76 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 77 | private struct SolidListInfo 78 | { 79 | public readonly long Blank; 80 | 81 | public readonly int Marker; // this doesn't change between games for some reason... rather unfortunate 82 | 83 | public readonly int NumObjects; 84 | 85 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 86 | public readonly string Filename; 87 | 88 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 89 | public readonly string GroupName; 90 | 91 | public readonly int UnknownOffset; 92 | public readonly int UnknownSize; 93 | } 94 | 95 | [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] 96 | private struct SolidObjectOffset 97 | { 98 | public uint Hash; 99 | public uint Offset; 100 | public readonly uint LengthCompressed; 101 | public readonly uint Length; 102 | public uint Flags; 103 | private uint blank; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/CarbonObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common.Geometry.Data 4 | { 5 | public class CarbonObject : SolidObject, IMorphableSolid 6 | { 7 | public List MorphTargets { get; } = new(); 8 | } 9 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/IEffectBasedMaterial.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Geometry.Data; 2 | 3 | public interface IEffectBasedMaterial 4 | { 5 | uint EffectId { get; set; } 6 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/IMorphableSolid.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common.Geometry.Data; 4 | 5 | public interface IMorphableSolid 6 | { 7 | List MorphTargets { get; } 8 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/ISortedMaterial.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Geometry.Data; 2 | 3 | public interface ISortedMaterial 4 | { 5 | uint SortKey { get; set; } 6 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/MostWantedObject.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Geometry.Data 2 | { 3 | public class MostWantedObject : SolidObject 4 | { 5 | // 6 | } 7 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/ProStreetObject.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Geometry.Data 2 | { 3 | public class ProStreetObject : SolidObject 4 | { 5 | // 6 | } 7 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/SolidList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common.Geometry.Data 4 | { 5 | public class SolidList : BasicResource 6 | { 7 | public string Filename { get; set; } 8 | 9 | public string GroupName { get; set; } 10 | 11 | public List Objects { get; } = new List(); 12 | } 13 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/SolidMeshVertex.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Common.Geometry.Data 4 | { 5 | public struct SolidMeshVertex 6 | { 7 | // REQUIRED attributes 8 | public Vector3 Position { get; set; } 9 | public Vector2 TexCoords { get; set; } 10 | 11 | // OPTIONAL attributes 12 | public Vector3? Normal { get; set; } 13 | public Vector3? Tangent { get; set; } 14 | public Vector3? BlendWeight { get; set; } 15 | public Vector3? BlendIndices { get; set; } 16 | 17 | public uint? Color { get; set; } 18 | 19 | // This one is NFSW-specific 20 | public uint? Color2 { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/SolidObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace Common.Geometry.Data 5 | { 6 | public abstract class SolidObject : BasicResource 7 | { 8 | public static IEqualityComparer HashComparer { get; } = new HashEqualityComparer(); 9 | 10 | public string Name { get; set; } 11 | 12 | public Matrix4x4 PivotMatrix { get; set; } 13 | 14 | public Vector3 MinPoint { get; set; } 15 | 16 | public Vector3 MaxPoint { get; set; } 17 | 18 | public uint Hash { get; set; } 19 | 20 | public List Materials { get; } = new(); 21 | 22 | public List> VertexSets { get; } = new(); 23 | 24 | public List TextureHashes { get; } = new(); 25 | 26 | private sealed class HashEqualityComparer : IEqualityComparer 27 | { 28 | public bool Equals(SolidObject x, SolidObject y) 29 | { 30 | if (ReferenceEquals(x, y)) return true; 31 | if (ReferenceEquals(x, null)) return false; 32 | if (ReferenceEquals(y, null)) return false; 33 | if (x.GetType() != y.GetType()) return false; 34 | return x.Hash == y.Hash; 35 | } 36 | 37 | public int GetHashCode(SolidObject obj) 38 | { 39 | return (int)obj.Hash; 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/SolidObjectMaterial.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace Common.Geometry.Data 4 | { 5 | public class SolidObjectMaterial 6 | { 7 | public Vector3 MinPoint { get; set; } 8 | 9 | public Vector3 MaxPoint { get; set; } 10 | 11 | public uint Hash { get; set; } 12 | 13 | public uint Flags { get; set; } 14 | 15 | public uint NumVerts { get; set; } 16 | 17 | public int VertexSetIndex { get; set; } 18 | 19 | public uint NumIndices { get; set; } // NumTris * 3 20 | 21 | public uint DiffuseTextureHash { get; set; } 22 | public uint? NormalTextureHash { get; set; } 23 | public uint? SpecularTextureHash { get; set; } 24 | 25 | public string Name { get; set; } 26 | public ushort[] Indices { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/UndercoverObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common.Geometry.Data 4 | { 5 | public class UndercoverObject : SolidObject 6 | { 7 | public UndercoverObject() 8 | { 9 | TextureTypeList = new List(); 10 | } 11 | 12 | public List TextureTypeList { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/Underground2Object.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Geometry.Data 2 | { 3 | public class Underground2Object : SolidObject 4 | { 5 | // 6 | } 7 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/UndergroundObject.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Geometry.Data 2 | { 3 | public class UndergroundObject : SolidObject 4 | { 5 | // 6 | } 7 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/World09Object.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Common.Geometry.Data; 4 | 5 | public class World09Object : SolidObject, IMorphableSolid 6 | { 7 | public World09Object() 8 | { 9 | // MorphLists = new List>(); 10 | // MorphMatrices = new List(); 11 | MorphTargets = new List(); 12 | } 13 | 14 | // public List> MorphLists { get; set; } 15 | // public List MorphMatrices { get; set; } 16 | 17 | // public struct MorphInfo 18 | // { 19 | // // morph range is [VertexStartIndex, VertexEndIndex] 20 | // public int VertexStartIndex; 21 | // public int VertexEndIndex; 22 | // public int MorphMatrixIndex; 23 | // } 24 | public List MorphTargets { get; } 25 | } -------------------------------------------------------------------------------- /Common/Geometry/Data/World15Object.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace Common.Geometry.Data 5 | { 6 | public class World15Object : SolidObject 7 | { 8 | public World15Object() 9 | { 10 | MorphLists = new List>(); 11 | MorphMatrices = new List(); 12 | } 13 | 14 | public List> MorphLists { get; set; } 15 | public List MorphMatrices { get; set; } 16 | 17 | public struct MorphInfo 18 | { 19 | // morph range is [VertexStartIndex, VertexEndIndex] 20 | public int VertexStartIndex; 21 | public int VertexEndIndex; 22 | public int MorphMatrixIndex; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Common/Geometry/MostWantedSolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry 7 | { 8 | public class MostWantedMaterial : SolidObjectMaterial, IEffectBasedMaterial 9 | { 10 | public uint EffectId { get; set; } 11 | } 12 | 13 | public class MostWantedSolidListReader : SolidListReader 14 | { 15 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 16 | uint chunkSize) 17 | { 18 | switch (chunkId) 19 | { 20 | case 0x134002: 21 | var info = BinaryUtil.ReadStruct(binaryReader); 22 | solidList.Filename = info.Filename; 23 | solidList.GroupName = info.GroupName; 24 | return true; 25 | case 0x134003: 26 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 27 | return true; 28 | case 0x134004: 29 | Debug.Assert(chunkSize % 24 == 0, "chunkSize % 24 == 0"); 30 | return true; 31 | default: 32 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 33 | } 34 | } 35 | 36 | protected override SolidReader CreateObjectReader() 37 | { 38 | return new MostWantedSolidReader(); 39 | } 40 | 41 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 42 | private struct SolidListInfo 43 | { 44 | public readonly long Blank; 45 | 46 | public readonly int Marker; // this doesn't change between games for some reason... rather unfortunate 47 | 48 | public readonly int NumObjects; 49 | 50 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 51 | public readonly string Filename; 52 | 53 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 54 | public readonly string GroupName; 55 | 56 | public readonly int UnknownOffset; 57 | public readonly int UnknownSize; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Common/Geometry/ProStreetSolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry 7 | { 8 | public class ProStreetMaterial : SolidObjectMaterial, IEffectBasedMaterial 9 | { 10 | public uint EffectId { get; set; } 11 | } 12 | 13 | public class ProStreetSolidListReader : SolidListReader 14 | { 15 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 16 | uint chunkSize) 17 | { 18 | switch (chunkId) 19 | { 20 | case 0x134002: 21 | var info = BinaryUtil.ReadStruct(binaryReader); 22 | solidList.Filename = info.Filename; 23 | solidList.GroupName = info.GroupName; 24 | return true; 25 | case 0x134003: 26 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 27 | return true; 28 | case 0x134004: 29 | ProcessStreamingTable(solidList, binaryReader, chunkSize); 30 | return 31 | false; // We need to stop after processing the streaming table, otherwise we'll run into bad stuff 32 | default: 33 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 34 | } 35 | } 36 | 37 | private void ProcessStreamingTable(SolidList solidList, BinaryReader binaryReader, uint chunkSize) 38 | { 39 | var chunkEndPos = binaryReader.BaseStream.Position + chunkSize; 40 | Debug.Assert(chunkSize % 24 == 0, "chunkSize % 24 == 0"); 41 | while (binaryReader.BaseStream.Position < chunkEndPos) 42 | { 43 | var och = BinaryUtil.ReadUnmanagedStruct(binaryReader); 44 | var curPos = binaryReader.BaseStream.Position; 45 | binaryReader.BaseStream.Position = och.Offset; 46 | 47 | Debug.Assert(och.Length >= 8, "och.Length >= 8"); 48 | 49 | if (och.Length == och.LengthCompressed) 50 | { 51 | // Assume that the object is uncompressed. 52 | // If this ever turns out to be false, I don't know what I'll do. 53 | binaryReader.BaseStream.Position += 8; 54 | solidList.Objects.Add(CreateObjectReader().Read(binaryReader, och.Length - 8)); 55 | } 56 | else 57 | { 58 | // Load decompressed object 59 | using var ms = new MemoryStream(); 60 | Compression.DecompressCip(binaryReader.BaseStream, ms, och.LengthCompressed); 61 | 62 | ms.Position = 8; 63 | using var dcr = new BinaryReader(ms); 64 | solidList.Objects.Add(CreateObjectReader().Read(dcr, och.Length - 8)); 65 | } 66 | 67 | binaryReader.BaseStream.Position = curPos; 68 | } 69 | } 70 | 71 | protected override SolidReader CreateObjectReader() 72 | { 73 | return new ProStreetSolidReader(); 74 | } 75 | 76 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 77 | private struct SolidListInfo 78 | { 79 | public long Blank; 80 | 81 | public int Marker; // this doesn't change between games for some reason... rather unfortunate 82 | 83 | public int NumObjects; 84 | 85 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 86 | public readonly string Filename; 87 | 88 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 89 | public readonly string GroupName; 90 | 91 | public int UnknownOffset, UnknownSize; 92 | } 93 | 94 | [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] 95 | private struct SolidObjectOffset 96 | { 97 | public uint Hash; 98 | public uint Offset; 99 | public readonly uint LengthCompressed; 100 | public readonly uint Length; 101 | public uint Flags; 102 | private uint blank; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /Common/Geometry/SolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry; 7 | 8 | public abstract class SolidListReader 9 | { 10 | public SolidList ReadSolidList(BinaryReader br, uint containerSize) 11 | { 12 | var solidList = new SolidList(); 13 | 14 | ReadChunks(solidList, br, containerSize); 15 | 16 | return solidList; 17 | } 18 | 19 | private void ReadChunks(SolidList solidList, BinaryReader binaryReader, uint containerSize) 20 | { 21 | var readState = ReadState.Init; 22 | 23 | var endPos = binaryReader.BaseStream.Position + containerSize; 24 | 25 | while (binaryReader.BaseStream.Position < endPos) 26 | { 27 | var chunkId = binaryReader.ReadUInt32(); 28 | var chunkSize = binaryReader.ReadUInt32(); 29 | var chunkPos = binaryReader.BaseStream.Position; 30 | var chunkEndPos = chunkPos + chunkSize; 31 | 32 | if (chunkId == 0) 33 | { 34 | binaryReader.BaseStream.Position = chunkEndPos; 35 | } 36 | else if ((chunkId & 0x80000000) == 0) 37 | { 38 | if (readState == ReadState.Headers) 39 | { 40 | // We can return false from a header processor to completely stop reading chunks. 41 | if (!ProcessHeaderChunk(solidList, binaryReader, chunkId, chunkSize)) return; 42 | } 43 | else 44 | { 45 | throw new Exception("Impossible state reached"); 46 | } 47 | 48 | Debug.Assert(chunkPos <= binaryReader.BaseStream.Position, 49 | "chunkPos <= binaryReader.BaseStream.Position"); 50 | Debug.Assert(binaryReader.BaseStream.Position <= chunkEndPos, 51 | "binaryReader.BaseStream.Position <= chunkEndPos"); 52 | 53 | binaryReader.BaseStream.Position = chunkEndPos; 54 | } 55 | else 56 | { 57 | switch (chunkId) 58 | { 59 | case 0x80134001: 60 | Debug.Assert(readState == ReadState.Init, "readState == ReadState.Init"); 61 | readState = ReadState.Headers; 62 | break; 63 | case 0x80134008: 64 | Debug.Assert(chunkSize == 0, "chunkSize == 0"); 65 | break; 66 | case 0x80134010: 67 | Debug.Assert(readState != ReadState.Init, "readState != ReadState.Init"); 68 | readState = ReadState.Object; 69 | var solidObjectReader = CreateObjectReader(); 70 | solidList.Objects.Add(solidObjectReader.Read(binaryReader, chunkSize)); 71 | break; 72 | default: 73 | throw new InvalidDataException( 74 | $"Not sure what to make of parent chunk: 0x{chunkId:X8} @ 0x{chunkPos:X}"); 75 | } 76 | } 77 | } 78 | } 79 | 80 | protected abstract bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 81 | uint chunkSize); 82 | 83 | protected abstract SolidReader CreateObjectReader(); 84 | 85 | private enum ReadState 86 | { 87 | Init, 88 | Headers, 89 | Object 90 | } 91 | } -------------------------------------------------------------------------------- /Common/Geometry/UndercoverEffectId.cs: -------------------------------------------------------------------------------- 1 | namespace Common.Geometry; 2 | 3 | public enum UndercoverEffectId 4 | { 5 | car, 6 | car_a, 7 | car_a_nzw, 8 | car_nm, 9 | car_nm_a, 10 | car_nm_v_s, 11 | car_nm_v_s_a, 12 | car_si, 13 | car_si_a, 14 | car_t, 15 | car_t_a, 16 | car_t_nm, 17 | car_v, 18 | diffuse_spec_2sided, 19 | mw2_branches, 20 | mw2_car_heaven, 21 | mw2_car_heaven_default, 22 | mw2_cardebris, 23 | mw2_carhvn_floor, 24 | mw2_combo_refl, 25 | mw2_constant, 26 | mw2_constant_alpha_bias, 27 | mw2_dif_spec_a_bias, 28 | mw2_diffuse_spec, 29 | mw2_diffuse_spec_alpha, 30 | mw2_diffuse_spec_illum, 31 | mw2_diffuse_spec_salpha, 32 | mw2_dirt, 33 | mw2_dirt_overlay, 34 | mw2_dirt_rock, 35 | mw2_fol_alwaysfacing, 36 | mw2_foliage, 37 | mw2_foliage_lod, 38 | mw2_glass_no_n, 39 | mw2_glass_refl, 40 | mw2_grass, 41 | mw2_grass_dirt, 42 | mw2_grass_rock, 43 | mw2_icon, 44 | mw2_illuminated, 45 | mw2_indicator, 46 | mw2_matte, 47 | mw2_matte_alpha, 48 | mw2_normalmap, 49 | mw2_normalmap_bias, 50 | mw2_ocean, 51 | mw2_pano, 52 | mw2_parallax, 53 | mw2_road, 54 | mw2_road_lite, 55 | mw2_road_overlay, 56 | mw2_road_refl, 57 | mw2_road_refl_lite, 58 | mw2_road_refl_overlay, 59 | mw2_road_refl_tile, 60 | mw2_road_tile, 61 | mw2_rock, 62 | mw2_rock_overlay, 63 | mw2_scrub, 64 | mw2_scrub_lod, 65 | mw2_sky, 66 | mw2_smokegeo, 67 | mw2_texture_scroll, 68 | mw2_trunk, 69 | mw2_tunnel_illum, 70 | mw2_tunnel_road, 71 | mw2_tunnel_wall, 72 | normalmap2sided, 73 | shadowmesh, 74 | standardeffect, 75 | ubereffect, 76 | ubereffectblend, 77 | watersplash, 78 | worldbone, 79 | worldbonenocull, 80 | worldbonetransparency, 81 | none 82 | } -------------------------------------------------------------------------------- /Common/Geometry/UndercoverSolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry 7 | { 8 | public class UndercoverMaterial : SolidObjectMaterial, IEffectBasedMaterial 9 | { 10 | public uint NumReducedIndices { get; set; } 11 | public uint EffectId { get; set; } 12 | } 13 | 14 | public class UndercoverSolidListReader : SolidListReader 15 | { 16 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 17 | uint chunkSize) 18 | { 19 | switch (chunkId) 20 | { 21 | case 0x134002: 22 | var info = BinaryUtil.ReadStruct(binaryReader); 23 | solidList.Filename = info.Filename; 24 | solidList.GroupName = info.GroupName; 25 | return true; 26 | case 0x134003: 27 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 28 | return true; 29 | case 0x134004: 30 | ProcessStreamingTable(solidList, binaryReader, chunkSize); 31 | return 32 | false; // We need to stop after processing the streaming table, otherwise we'll run into bad stuff 33 | default: 34 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 35 | } 36 | } 37 | 38 | private void ProcessStreamingTable(SolidList solidList, BinaryReader binaryReader, uint chunkSize) 39 | { 40 | var chunkEndPos = binaryReader.BaseStream.Position + chunkSize; 41 | Debug.Assert(chunkSize % 24 == 0, "chunkSize % 24 == 0"); 42 | while (binaryReader.BaseStream.Position < chunkEndPos) 43 | { 44 | var och = BinaryUtil.ReadUnmanagedStruct(binaryReader); 45 | var curPos = binaryReader.BaseStream.Position; 46 | binaryReader.BaseStream.Position = och.Offset; 47 | 48 | Debug.Assert(och.Length >= 8, "och.Length >= 8"); 49 | 50 | if (och.Length == och.LengthCompressed) 51 | { 52 | // Assume that the object is uncompressed. 53 | // If this ever turns out to be false, I don't know what I'll do. 54 | binaryReader.BaseStream.Position += 8; 55 | solidList.Objects.Add(CreateObjectReader().Read(binaryReader, och.Length - 8)); 56 | } 57 | else 58 | { 59 | // Load decompressed object 60 | using var ms = new MemoryStream(); 61 | Compression.DecompressCip(binaryReader.BaseStream, ms, och.LengthCompressed); 62 | 63 | ms.Position = 8; 64 | using var dcr = new BinaryReader(ms); 65 | solidList.Objects.Add(CreateObjectReader().Read(dcr, och.Length - 8)); 66 | } 67 | 68 | binaryReader.BaseStream.Position = curPos; 69 | } 70 | } 71 | 72 | protected override SolidReader CreateObjectReader() 73 | { 74 | return new UndercoverSolidReader(); 75 | } 76 | 77 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 78 | private struct SolidListInfo 79 | { 80 | public readonly long Blank; 81 | 82 | public readonly int Marker; // this doesn't change between games for some reason... rather unfortunate 83 | 84 | public readonly int NumObjects; 85 | 86 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 87 | public readonly string Filename; 88 | 89 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 90 | public readonly string GroupName; 91 | 92 | public readonly int UnknownOffset; 93 | public readonly int UnknownSize; 94 | } 95 | 96 | [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] 97 | private struct SolidObjectOffset 98 | { 99 | public uint Hash; 100 | public uint Offset; 101 | public readonly uint LengthCompressed; 102 | public readonly uint Length; 103 | public uint Flags; 104 | private uint blank; 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /Common/Geometry/Underground2SolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry; 7 | 8 | public class Underground2Material : SolidObjectMaterial 9 | { 10 | } 11 | 12 | public class Underground2SolidListReader : SolidListReader 13 | { 14 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 15 | uint chunkSize) 16 | { 17 | switch (chunkId) 18 | { 19 | case 0x134002: 20 | var info = BinaryUtil.ReadStruct(binaryReader); 21 | solidList.Filename = info.Filename; 22 | solidList.GroupName = info.GroupName; 23 | return true; 24 | case 0x134003: 25 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 26 | return true; 27 | case 0x134004: 28 | Debug.Assert(chunkSize % 24 == 0, "chunkSize % 24 == 0"); 29 | return true; 30 | default: 31 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 32 | } 33 | } 34 | 35 | protected override SolidReader CreateObjectReader() 36 | { 37 | return new Underground2SolidReader(); 38 | } 39 | 40 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 41 | private struct SolidListInfo 42 | { 43 | public readonly long Blank; 44 | 45 | public readonly int Marker; // this doesn't change between games for some reason... rather unfortunate 46 | 47 | public readonly int NumObjects; 48 | 49 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 50 | public readonly string Filename; 51 | 52 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 53 | public readonly string GroupName; 54 | 55 | public readonly int UnknownOffset; 56 | public readonly int UnknownSize; 57 | } 58 | } -------------------------------------------------------------------------------- /Common/Geometry/UndergroundSolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry 7 | { 8 | public class UndergroundMaterial : SolidObjectMaterial 9 | { 10 | } 11 | 12 | public class UndergroundSolidListReader : SolidListReader 13 | { 14 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 15 | uint chunkSize) 16 | { 17 | switch (chunkId) 18 | { 19 | case 0x134002: 20 | var info = BinaryUtil.ReadStruct(binaryReader); 21 | solidList.Filename = info.Filename; 22 | solidList.GroupName = info.GroupName; 23 | return true; 24 | case 0x134003: 25 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 26 | return true; 27 | case 0x134004: 28 | Debug.Assert(chunkSize % 20 == 0, "chunkSize % 20 == 0"); 29 | return true; 30 | default: 31 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 32 | } 33 | } 34 | 35 | protected override SolidReader CreateObjectReader() 36 | { 37 | return new UndergroundSolidReader(); 38 | } 39 | 40 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 41 | private struct SolidListInfo 42 | { 43 | public readonly long Blank; 44 | 45 | public readonly int Marker; // this doesn't change between games for some reason... rather unfortunate 46 | 47 | public readonly int NumObjects; 48 | 49 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 50 | public readonly string Filename; 51 | 52 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 53 | public readonly string GroupName; 54 | 55 | public readonly int UnknownOffset; 56 | public readonly int UnknownSize; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Common/Geometry/World09SolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry; 7 | 8 | public class World09Material : SolidObjectMaterial, IEffectBasedMaterial 9 | { 10 | public uint EffectId { get; set; } 11 | } 12 | 13 | public class World09SolidListReader : SolidListReader 14 | { 15 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 16 | uint chunkSize) 17 | { 18 | switch (chunkId) 19 | { 20 | case 0x134002: 21 | var info = BinaryUtil.ReadStruct(binaryReader); 22 | solidList.Filename = info.Filename; 23 | solidList.GroupName = info.GroupName; 24 | return true; 25 | case 0x134003: 26 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 27 | return true; 28 | case 0x134004: 29 | ProcessStreamingTable(solidList, binaryReader, chunkSize); 30 | return 31 | false; // We need to stop after processing the streaming table, otherwise we'll run into bad stuff 32 | default: 33 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 34 | } 35 | } 36 | 37 | private void ProcessStreamingTable(SolidList solidList, BinaryReader binaryReader, uint chunkSize) 38 | { 39 | var chunkEndPos = binaryReader.BaseStream.Position + chunkSize; 40 | Debug.Assert(chunkSize % 24 == 0, "chunkSize % 24 == 0"); 41 | while (binaryReader.BaseStream.Position < chunkEndPos) 42 | { 43 | var och = BinaryUtil.ReadUnmanagedStruct(binaryReader); 44 | var curPos = binaryReader.BaseStream.Position; 45 | binaryReader.BaseStream.Position = och.Offset; 46 | 47 | Debug.Assert(och.Length >= 8, "och.Length >= 8"); 48 | 49 | if (och.Length == och.LengthCompressed) 50 | { 51 | // Assume that the object is uncompressed. 52 | // If this ever turns out to be false, I don't know what I'll do. 53 | binaryReader.BaseStream.Position += 8; 54 | solidList.Objects.Add(CreateObjectReader().Read(binaryReader, och.Length - 8)); 55 | } 56 | else 57 | { 58 | // Load decompressed object 59 | using var ms = new MemoryStream(); 60 | Compression.DecompressCip(binaryReader.BaseStream, ms, och.LengthCompressed); 61 | 62 | ms.Position = 8; 63 | using var dcr = new BinaryReader(ms); 64 | solidList.Objects.Add(CreateObjectReader().Read(dcr, och.Length - 8)); 65 | } 66 | 67 | binaryReader.BaseStream.Position = curPos; 68 | } 69 | } 70 | 71 | protected override SolidReader CreateObjectReader() 72 | { 73 | return new World09SolidReader(); 74 | } 75 | 76 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 77 | private struct SolidListInfo 78 | { 79 | public readonly long Blank; 80 | 81 | public readonly int Marker; // this doesn't change between games for some reason... rather unfortunate 82 | 83 | public readonly int NumObjects; 84 | 85 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 86 | public readonly string Filename; 87 | 88 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 89 | public readonly string GroupName; 90 | 91 | public readonly int UnknownOffset; 92 | public readonly int UnknownSize; 93 | } 94 | 95 | [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] 96 | private struct SolidObjectOffset 97 | { 98 | public uint Hash; 99 | public uint Offset; 100 | public readonly uint LengthCompressed; 101 | public readonly uint Length; 102 | public uint Flags; 103 | private uint blank; 104 | } 105 | } -------------------------------------------------------------------------------- /Common/Geometry/WorldSolidListReader.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Common.Geometry.Data; 5 | 6 | namespace Common.Geometry 7 | { 8 | public class World15Material : SolidObjectMaterial, IEffectBasedMaterial, ISortedMaterial 9 | { 10 | public uint EffectId { get; set; } 11 | public uint SortKey { get; set; } 12 | } 13 | 14 | public class WorldSolidListReader : SolidListReader 15 | { 16 | protected override bool ProcessHeaderChunk(SolidList solidList, BinaryReader binaryReader, uint chunkId, 17 | uint chunkSize) 18 | { 19 | switch (chunkId) 20 | { 21 | case 0x134002: 22 | var info = BinaryUtil.ReadStruct(binaryReader); 23 | solidList.Filename = info.Filename; 24 | solidList.GroupName = info.GroupName; 25 | return true; 26 | case 0x134003: 27 | Debug.Assert(chunkSize % 8 == 0, "chunkSize % 8 == 0"); 28 | return true; 29 | case 0x134004: 30 | ProcessStreamingTable(solidList, binaryReader, chunkSize); 31 | return 32 | false; // We need to stop after processing the streaming table, otherwise we'll run into bad stuff 33 | default: 34 | throw new InvalidDataException($"Unexpected header chunk: 0x{chunkId:X}"); 35 | } 36 | } 37 | 38 | private void ProcessStreamingTable(SolidList solidList, BinaryReader binaryReader, uint chunkSize) 39 | { 40 | var chunkEndPos = binaryReader.BaseStream.Position + chunkSize; 41 | Debug.Assert(chunkSize % 36 == 0, "chunkSize % 36 == 0"); 42 | while (binaryReader.BaseStream.Position < chunkEndPos) 43 | { 44 | var och = BinaryUtil.ReadUnmanagedStruct(binaryReader); 45 | var curPos = binaryReader.BaseStream.Position; 46 | binaryReader.BaseStream.Position = och.Offset; 47 | 48 | Debug.Assert(och.Length >= 8, "och.Length >= 8"); 49 | 50 | if (och.Length == och.LengthCompressed) 51 | { 52 | // Assume that the object is uncompressed. 53 | // If this ever turns out to be false, I don't know what I'll do. 54 | binaryReader.BaseStream.Position += 8; 55 | solidList.Objects.Add(CreateObjectReader().Read(binaryReader, och.Length - 8)); 56 | } 57 | else 58 | { 59 | // Load decompressed object 60 | using var ms = new MemoryStream(); 61 | Compression.DecompressCip(binaryReader.BaseStream, ms, och.LengthCompressed); 62 | 63 | ms.Position = 8; 64 | using var dcr = new BinaryReader(ms); 65 | solidList.Objects.Add(CreateObjectReader().Read(dcr, och.Length - 8)); 66 | } 67 | 68 | binaryReader.BaseStream.Position = curPos; 69 | } 70 | } 71 | 72 | protected override SolidReader CreateObjectReader() 73 | { 74 | return new WorldSolidReader(); 75 | } 76 | 77 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 78 | private struct SolidListInfo 79 | { 80 | public long Blank; 81 | 82 | public int Marker; // this doesn't change between games for some reason... rather unfortunate 83 | 84 | public int NumObjects; 85 | 86 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x38)] 87 | public readonly string Filename; 88 | 89 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)] 90 | public readonly string GroupName; 91 | 92 | public long Blank2; 93 | 94 | public uint UnknownOffset; 95 | } 96 | 97 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 98 | private struct SolidObjectOffset 99 | { 100 | public readonly uint Hash; 101 | public readonly uint Offset; 102 | public readonly uint LengthCompressed; 103 | public readonly uint Length; 104 | 105 | public uint Unknown, Unknown2, Unknown3, Unknown4, Unknown5; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /Common/Lights/Data/LightPack.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace Common.Lights.Data; 5 | 6 | public class LightPack : BasicResource 7 | { 8 | public uint ScenerySectionNumber { get; set; } 9 | public List Lights { get; set; } 10 | } 11 | 12 | public class Light 13 | { 14 | public uint NameHash { get; set; } 15 | public string Name { get; set; } 16 | public uint Color { get; set; } 17 | public Vector3 Position { get; set; } 18 | public float Size { get; set; } 19 | public float Intensity { get; set; } 20 | public float FarStart { get; set; } 21 | public float FarEnd { get; set; } 22 | public float Falloff { get; set; } 23 | } -------------------------------------------------------------------------------- /Common/Lights/LightPackReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Common.Lights.Data; 5 | using Common.Lights.Structures; 6 | 7 | namespace Common.Lights; 8 | 9 | public class LightPackReader 10 | { 11 | private LightPack _lightPack; 12 | 13 | public LightPack ReadLights(BinaryReader br, uint containerSize) 14 | { 15 | _lightPack = new LightPack(); 16 | ReadChunks(br, containerSize); 17 | return _lightPack; 18 | } 19 | 20 | private void ReadChunks(BinaryReader br, uint containerSize) 21 | { 22 | var endPos = br.BaseStream.Position + containerSize; 23 | 24 | while (br.BaseStream.Position < endPos) 25 | { 26 | var chunkId = br.ReadUInt32(); 27 | var chunkSize = br.ReadUInt32(); 28 | var chunkEndPos = br.BaseStream.Position + chunkSize; 29 | 30 | chunkSize -= BinaryUtil.AlignReader(br, 0x10); 31 | 32 | switch (chunkId) 33 | { 34 | case 0x135001: 35 | { 36 | ReadHeader(br); 37 | break; 38 | } 39 | case 0x135003: 40 | { 41 | ReadLightList(br, chunkSize); 42 | break; 43 | } 44 | } 45 | 46 | br.BaseStream.Position = chunkEndPos; 47 | } 48 | } 49 | 50 | private void ReadHeader(BinaryReader br) 51 | { 52 | var header = BinaryUtil.ReadStruct(br); 53 | 54 | _lightPack.ScenerySectionNumber = header.ScenerySectionNumber; 55 | _lightPack.Lights = new List(header.NumLights); 56 | } 57 | 58 | private void ReadLightList(BinaryReader br, uint chunkSize) 59 | { 60 | if (chunkSize % 0x60 != 0) 61 | throw new Exception("Light list chunk is weirdly sized"); 62 | for (var i = 0; i < chunkSize / 0x60; i++) 63 | { 64 | var light = BinaryUtil.ReadStruct(br); 65 | 66 | _lightPack.Lights.Add(new Light 67 | { 68 | NameHash = light.NameHash, 69 | Name = light.Name, 70 | Color = light.Color, 71 | Position = light.Position, 72 | Size = light.Size, 73 | Intensity = light.Intensity, 74 | FarStart = light.FarStart, 75 | FarEnd = light.FarEnd, 76 | Falloff = light.FarEnd 77 | }); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /Common/Lights/Structures/LightData.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Common.Lights.Structures; 5 | 6 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 7 | public struct LightData 8 | { 9 | public uint NameHash; 10 | public byte Type, AttenuationType, Shape, State; 11 | public uint ExcludeNameHash, Color; 12 | public Vector3 Position; 13 | public float Size; 14 | public Vector3 Direction; 15 | public float Intensity; 16 | public float FarStart; 17 | public float FarEnd; 18 | public float Falloff; 19 | public short ScenerySectionNumber; 20 | 21 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)] 22 | public string Name; 23 | } -------------------------------------------------------------------------------- /Common/Lights/Structures/LightPackHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Common.Lights.Structures; 4 | 5 | [StructLayout(LayoutKind.Explicit, Size = 0x20)] 6 | public struct LightPackHeader 7 | { 8 | [FieldOffset(0x08)] public ushort Version; 9 | 10 | [FieldOffset(0x0C)] public uint ScenerySectionNumber; 11 | 12 | [FieldOffset(0x1C)] public int NumLights; 13 | } -------------------------------------------------------------------------------- /Common/Scenery/Data/ScenerySection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Numerics; 3 | 4 | namespace Common.Scenery.Data 5 | { 6 | public class SceneryInfo 7 | { 8 | public string Name { get; set; } 9 | public uint SolidKey { get; set; } 10 | public bool IsDeinstanced { get; set; } 11 | } 12 | 13 | public class SceneryInstance 14 | { 15 | public int InfoIndex { get; set; } 16 | public Matrix4x4 Transform { get; set; } 17 | } 18 | 19 | public class ScenerySection : BasicResource 20 | { 21 | public int SectionNumber { get; set; } 22 | public List Infos { get; set; } = new List(); 23 | public List Instances { get; set; } = new List(); 24 | } 25 | } -------------------------------------------------------------------------------- /Common/Scenery/MostWantedScenery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Numerics; 5 | using System.Runtime.InteropServices; 6 | using Common.Scenery.Data; 7 | using Common.Scenery.Structures; 8 | 9 | namespace Common.Scenery 10 | { 11 | public class MostWantedScenery : SceneryManager 12 | { 13 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 14 | public struct ScenerySectionHeader // 0x00034101 15 | { 16 | public long Pointer1; 17 | public int Pointer2; 18 | public int SectionNumber; 19 | public int Pointer3; 20 | public long Pointer4; 21 | public long Pointer5; 22 | public long Pointer6; 23 | public long Pointer7; 24 | public long Pointer8; 25 | } 26 | 27 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 28 | public struct SceneryInfoStruct // 0x00034102 29 | { 30 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)] 31 | public string Name; 32 | 33 | public uint SolidMeshKey1; 34 | public uint SolidMeshKey2; 35 | public uint SolidMeshKey3; 36 | public uint SolidMeshKey4; 37 | 38 | public uint SolidMeshPointer1; 39 | public uint SolidMeshPointer2; 40 | public uint SolidMeshPointer3; 41 | public uint SolidMeshPointer4; 42 | 43 | public float Radius; 44 | public uint MeshChecksum; 45 | public uint HierarchyNameHash; 46 | public uint HierarchyPointer; 47 | } 48 | 49 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 50 | public struct SceneryInstanceInternal // 0x00034103 51 | { 52 | public Vector3 BBoxMin; 53 | public Vector3 BBoxMax; 54 | public uint ExcludeFlags; 55 | public short PrecullerInfoIndex; 56 | public short LightingContextNumber; 57 | public Vector3 Position; 58 | public PackedRotationMatrix Rotation; 59 | public short SceneryInfoNumber; 60 | } 61 | 62 | private ScenerySection _scenerySection; 63 | 64 | public override ScenerySection ReadScenery(BinaryReader br, uint containerSize) 65 | { 66 | _scenerySection = new ScenerySection(); 67 | ReadChunks(br, containerSize); 68 | return _scenerySection; 69 | } 70 | 71 | protected override void ReadChunks(BinaryReader br, uint containerSize) 72 | { 73 | var endPos = br.BaseStream.Position + containerSize; 74 | 75 | while (br.BaseStream.Position < endPos) 76 | { 77 | var chunkId = br.ReadUInt32(); 78 | var chunkSize = br.ReadUInt32(); 79 | var chunkEndPos = br.BaseStream.Position + chunkSize; 80 | 81 | switch (chunkId) 82 | { 83 | case 0x00034101: 84 | { 85 | ReadScenerySectionHeader(br); 86 | break; 87 | } 88 | case 0x00034102: 89 | { 90 | ReadSceneryInfos(br, chunkSize); 91 | break; 92 | } 93 | case 0x00034103: 94 | { 95 | ReadSceneryInstances(br, chunkSize); 96 | break; 97 | } 98 | default: 99 | //Console.WriteLine($"0x{chunkId:X8} [{chunkSize}] @{br.BaseStream.Position}"); 100 | break; 101 | } 102 | 103 | br.BaseStream.Position = chunkEndPos; 104 | } 105 | } 106 | private void ReadScenerySectionHeader(BinaryReader br) 107 | { 108 | var header = BinaryUtil.ReadUnmanagedStruct(br); 109 | _scenerySection.SectionNumber = header.SectionNumber; 110 | //Debug.Log($"ScenerySection number is {_scenerySection.SectionNumber}"); 111 | } 112 | private void ReadSceneryInfos(BinaryReader br, uint size) 113 | { 114 | Debug.Assert(size % 0x48 == 0); 115 | var count = (int)size / 0x48; 116 | _scenerySection.Infos = new List(count); 117 | for (int i = 0; i < count; ++i) 118 | { 119 | var info = BinaryUtil.ReadStruct(br); 120 | _scenerySection.Infos.Add(new SceneryInfo 121 | { 122 | Name = info.Name, 123 | SolidKey = info.SolidMeshKey1 124 | }); 125 | } 126 | //Debug.Log($"Loaded {_scenerySection.SceneryInfos.Count} scenery definitions for ScenerySection {_scenerySection.SectionNumber}"); 127 | } 128 | 129 | private void ReadSceneryInstances(BinaryReader br, uint size) 130 | { 131 | size -= BinaryUtil.AlignReader(br, 0x10); 132 | Debug.Assert(size % 0x40 == 0); 133 | var count = (int)size / 0x40; 134 | _scenerySection.Instances = new List(count); 135 | 136 | for (int i = 0; i < count; ++i) 137 | { 138 | var instance = new SceneryInstance(); 139 | var internalInstance = BinaryUtil.ReadUnmanagedStruct(br); 140 | 141 | instance.InfoIndex = internalInstance.SceneryInfoNumber; 142 | instance.Transform = Matrix4x4.Multiply(internalInstance.Rotation, 143 | Matrix4x4.CreateTranslation(internalInstance.Position)); 144 | 145 | _scenerySection.Instances.Add(instance); 146 | } 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /Common/Scenery/SceneryManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Common.Geometry.Data; 3 | using Common.Scenery.Data; 4 | 5 | namespace Common.Scenery 6 | { 7 | public abstract class SceneryManager 8 | { 9 | protected SceneryManager() { } 10 | 11 | /// 12 | /// Read a scenery pack from the given binary stream. 13 | /// 14 | /// 15 | /// 16 | /// 17 | public abstract ScenerySection ReadScenery(BinaryReader br, uint containerSize); 18 | 19 | protected abstract void ReadChunks(BinaryReader br, uint containerSize); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Common/Scenery/Structures/PackedRotationMatrix.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Common.Scenery.Structures; 5 | 6 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 7 | public struct PackedRotationMatrix 8 | { 9 | public short Value11; 10 | public short Value12; 11 | public short Value13; 12 | public short Value21; 13 | public short Value22; 14 | public short Value23; 15 | public short Value31; 16 | public short Value32; 17 | public short Value33; 18 | 19 | public static implicit operator Matrix4x4(PackedRotationMatrix prm) 20 | { 21 | var vec0 = new Vector3(prm.Value11, prm.Value12, prm.Value13) / 8192f; 22 | var vec1 = new Vector3(prm.Value21, prm.Value22, prm.Value23) / 8192f; 23 | var vec2 = new Vector3(prm.Value31, prm.Value32, prm.Value33) / 8192f; 24 | 25 | return new Matrix4x4 26 | { 27 | M11 = vec0.X, 28 | M12 = vec0.Y, 29 | M13 = vec0.Z, 30 | 31 | M21 = vec1.X, 32 | M22 = vec1.Y, 33 | M23 = vec1.Z, 34 | 35 | M31 = vec2.X, 36 | M32 = vec2.Y, 37 | M33 = vec2.Z, 38 | 39 | M44 = 1 40 | }; 41 | } 42 | } -------------------------------------------------------------------------------- /Common/Scenery/Structures/RotationMatrix.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Common.Scenery.Structures; 5 | 6 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 7 | public struct RotationMatrix 8 | { 9 | public Vector3 Rotation0; 10 | public Vector3 Rotation1; 11 | public Vector3 Rotation2; 12 | 13 | public RotationMatrix(Vector3 rotation0, Vector3 rotation1, Vector3 rotation2) 14 | { 15 | Rotation0 = rotation0; 16 | Rotation1 = rotation1; 17 | Rotation2 = rotation2; 18 | } 19 | 20 | public static implicit operator Matrix4x4(RotationMatrix rm) 21 | { 22 | return new Matrix4x4( 23 | rm.Rotation0.X, rm.Rotation0.Y, rm.Rotation0.Z, 0, 24 | rm.Rotation1.X, rm.Rotation1.Y, rm.Rotation1.Z, 0, 25 | rm.Rotation2.X, rm.Rotation2.Y, rm.Rotation2.Z, 0, 26 | 0, 0, 0, 1); 27 | } 28 | } -------------------------------------------------------------------------------- /Common/Textures/Data/DDSHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Common.Textures.Data 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct PixelFormat 8 | { 9 | public uint Size; 10 | public uint Flags; 11 | public uint FourCC; 12 | public int RGBBitCount; 13 | public int RBitMask; 14 | public int GBitMask; 15 | public int BBitMask; 16 | public int AlphaBitMask; 17 | } 18 | 19 | [StructLayout(LayoutKind.Sequential)] 20 | public struct DDSCaps 21 | { 22 | public int Caps1; 23 | public int Caps2; 24 | 25 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] 26 | public int[] Reserved; 27 | } 28 | 29 | static class DdsConstants 30 | { 31 | #region DDSStruct Flags 32 | public const int DdsdCaps = 0x00000001; 33 | public const int DdsdHeight = 0x00000002; 34 | public const int DdsdWidth = 0x00000004; 35 | public const int DdsdPitch = 0x00000008; 36 | public const int DdsdPixelformat = 0x00001000; 37 | public const int DdsdMipmapcount = 0x00020000; 38 | public const int DdsdLinearsize = 0x00080000; 39 | public const int DdsdDepth = 0x00800000; 40 | #endregion 41 | 42 | #region pixelformat values 43 | public const int DdpfAlphapixels = 0x00000001; 44 | public const int DdpfFourcc = 0x00000004; 45 | public const int DdpfRgb = 0x00000040; 46 | public const int DdpfLuminance = 0x00020000; 47 | #endregion 48 | 49 | #region ddscaps 50 | // caps1 51 | public const int DdscapsComplex = 0x00000008; 52 | public const int DdscapsTexture = 0x00001000; 53 | public const int DdscapsMipmap = 0x00400000; 54 | // caps2 55 | public const int Ddscaps2Cubemap = 0x00000200; 56 | public const int Ddscaps2CubemapPositivex = 0x00000400; 57 | public const int Ddscaps2CubemapNegativex = 0x00000800; 58 | public const int Ddscaps2CubemapPositivey = 0x00001000; 59 | public const int Ddscaps2CubemapNegativey = 0x00002000; 60 | public const int Ddscaps2CubemapPositivez = 0x00004000; 61 | public const int Ddscaps2CubemapNegativez = 0x00008000; 62 | public const int Ddscaps2Volume = 0x00200000; 63 | #endregion 64 | } 65 | 66 | [StructLayout(LayoutKind.Sequential)] 67 | public struct DDSHeader 68 | { 69 | public int Magic; 70 | public int Size; 71 | public uint Flags; 72 | public uint Height; 73 | public uint Width; 74 | public uint PitchOrLinearSize; 75 | public int Depth; 76 | public uint MipMapCount; 77 | 78 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] 79 | public int[] Reserved1; 80 | 81 | public PixelFormat PixelFormat; 82 | public DDSCaps DDSCaps; 83 | 84 | public int Reserved2; 85 | 86 | /// 87 | /// Initialize the DDS header structure with the given texture data. 88 | /// 89 | /// 90 | public void Init(Texture texture) 91 | { 92 | Magic = 0x20534444; // "DDS " 93 | Size = 0x7C; 94 | Flags = DdsConstants.DdsdCaps | DdsConstants.DdsdHeight | DdsConstants.DdsdWidth | 95 | DdsConstants.DdsdPixelformat | DdsConstants.DdsdMipmapcount; 96 | Height = texture.Height; 97 | Width = texture.Width; 98 | Depth = 1; 99 | MipMapCount = texture.MipMapCount; 100 | PixelFormat = new PixelFormat(); 101 | DDSCaps = new DDSCaps(); 102 | 103 | PixelFormat.Size = 0x20; 104 | DDSCaps.Caps1 = DdsConstants.DdscapsComplex | DdsConstants.DdscapsTexture | 105 | DdsConstants.DdscapsMipmap; 106 | 107 | if ((texture.Format & 0x00545844) == 0x00545844) // DXT check 108 | { 109 | PitchOrLinearSize = /*Width * Height*/texture.PitchOrLinearSize; 110 | PixelFormat.Flags = DdsConstants.DdpfFourcc; 111 | PixelFormat.FourCC = texture.Format; 112 | Flags |= DdsConstants.DdsdLinearsize; 113 | } 114 | else if ((texture.Format & 0x00495441) == 0x00495441) // ATI check 115 | { 116 | PitchOrLinearSize = /*Width * Height*/texture.PitchOrLinearSize; 117 | PixelFormat.Flags = DdsConstants.DdpfFourcc; 118 | PixelFormat.FourCC = texture.Format; 119 | Flags |= DdsConstants.DdsdLinearsize; 120 | } 121 | else 122 | { 123 | PixelFormat.Flags = DdsConstants.DdpfAlphapixels | DdsConstants.DdpfRgb; 124 | PixelFormat.RGBBitCount = 0x20; 125 | PixelFormat.RBitMask = 16711680; 126 | PixelFormat.GBitMask = 65280; 127 | PixelFormat.BBitMask = 255; 128 | PixelFormat.AlphaBitMask = unchecked((int)4278190080); 129 | Flags |= DdsConstants.DdsdPitch; 130 | PitchOrLinearSize = /*(Width * 0x20 + 7) / 8*/texture.PitchOrLinearSize; 131 | } 132 | 133 | Reserved1 = new int[11]; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Common/Textures/Data/TexturePack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Common.Textures.Data 6 | { 7 | public enum TextureCompressionType : byte 8 | { 9 | TEXCOMP_DEFAULT = 0x0, 10 | TEXCOMP_4BIT = 0x4, 11 | TEXCOMP_8BIT = 0x8, 12 | TEXCOMP_16BIT = 0x10, 13 | TEXCOMP_24BIT = 0x18, 14 | TEXCOMP_32BIT = 0x20, 15 | TEXCOMP_DXT = 0x21, 16 | TEXCOMP_S3TC = 0x22, 17 | TEXCOMP_DXTC1 = 0x22, 18 | TEXCOMP_DXTC3 = 0x24, 19 | TEXCOMP_DXTC5 = 0x26, 20 | TEXCOMP_DXTN = 0x27, 21 | TEXCOMP_L8 = 0x28, 22 | TEXCOMP_DXTC1_AIR = 0x29, 23 | TEXCOMP_DXTC1_AIG = 0x2A, 24 | TEXCOMP_DXTC1_AIB = 0x2B, 25 | TEXCOMP_16BIT_1555 = 0x11, 26 | TEXCOMP_16BIT_565 = 0x12, 27 | TEXCOMP_16BIT_3555 = 0x13, 28 | TEXCOMP_8BIT_16 = 0x80, 29 | TEXCOMP_8BIT_64 = 0x81, 30 | TEXCOMP_8BIT_IA8 = 0x82, 31 | TEXCOMP_4BIT_IA8 = 0x83, 32 | TEXCOMP_4BIT_RGB24_A8 = 0x8C, 33 | TEXCOMP_8BIT_RGB24_A8 = 0x8D, 34 | TEXCOMP_4BIT_RGB16_A8 = 0x8E, 35 | TEXCOMP_8BIT_RGB16_A8 = 0x8F, 36 | } 37 | 38 | public class Texture : BasicResource 39 | { 40 | public string Name { get; set; } 41 | 42 | public uint TexHash { get; set; } 43 | 44 | public uint TypeHash { get; set; } 45 | 46 | public uint Width { get; set; } 47 | 48 | public uint Height { get; set; } 49 | 50 | public uint MipMapCount { get; set; } 51 | 52 | public uint DataSize { get; set; } 53 | 54 | public uint DataOffset { get; set; } 55 | 56 | public uint PaletteSize { get; set; } 57 | 58 | public uint PaletteOffset { get; set; } 59 | 60 | public byte[] Palette { get; set; } 61 | public byte[] Data { get; set; } 62 | 63 | public uint PitchOrLinearSize { get; set; } 64 | 65 | public uint Format { get; set; } 66 | public TextureCompressionType CompressionType { get; set; } 67 | 68 | /// 69 | /// Writes DDS data to the given stream. 70 | /// 71 | /// 72 | public void GenerateImage(Stream stream) 73 | { 74 | var bw = new BinaryWriter(stream); 75 | 76 | var dh = new DDSHeader(); 77 | dh.Init(this); 78 | 79 | bw.PutStruct(dh); 80 | 81 | if (Format == 0x29) 82 | { 83 | var palette = new uint[0x100]; 84 | var result = new byte[Data.Length * 4]; 85 | 86 | for (var loop = 0; loop < 0x100; ++loop) 87 | { 88 | palette[loop] = BitConverter.ToUInt32(Palette, loop * 4); 89 | } 90 | 91 | for (var loop = 0; loop < Data.Length; ++loop) 92 | { 93 | var color = palette[Data[loop]]; 94 | Array.ConstrainedCopy(BitConverter.GetBytes(color), 0, result, loop * 4, 4); 95 | } 96 | 97 | bw.Write(result); 98 | } 99 | else 100 | { 101 | bw.Write(Data); 102 | } 103 | } 104 | 105 | public void DumpToFile(string path) 106 | { 107 | using (var fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) 108 | GenerateImage(fs); 109 | } 110 | } 111 | 112 | public class TexturePack : BasicResource 113 | { 114 | public string Name { get; set; } 115 | 116 | public string PipelinePath { get; set; } 117 | 118 | public uint Hash { get; set; } 119 | 120 | public uint Version { get; set; } 121 | 122 | public List Textures { get; set; } = new List(); 123 | 124 | public Texture Find(uint hash) => Textures.Find(t => t.TexHash == hash); 125 | public Texture Find(string name) => Textures.Find(t => t.Name.Contains(name)); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Common/Textures/TpkManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Common.Textures.Data; 3 | 4 | namespace Common.Textures 5 | { 6 | public abstract class TpkManager 7 | { 8 | /// 9 | /// Read a texture pack from the given binary stream. 10 | /// 11 | /// 12 | /// 13 | /// 14 | public abstract TexturePack ReadTexturePack(BinaryReader br, uint containerSize); 15 | 16 | protected abstract void ReadChunks(BinaryReader br, uint containerSize); 17 | } 18 | } -------------------------------------------------------------------------------- /FBXSharp/Connection.cs: -------------------------------------------------------------------------------- 1 | using FBXSharp.Core; 2 | 3 | namespace FBXSharp 4 | { 5 | public class Connection 6 | { 7 | public enum ConnectionType 8 | { 9 | Object, 10 | Property 11 | } 12 | 13 | public ConnectionType Type { get; } 14 | public long Source { get; } 15 | public long Destination { get; } 16 | public IElementAttribute Property { get; } 17 | 18 | public Connection(ConnectionType type, long src, long dest, IElementAttribute property = null) 19 | { 20 | Type = type; 21 | Source = src; 22 | Destination = dest; 23 | Property = property; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /FBXSharp/Core/CoordSystem.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.Core 2 | { 3 | public enum CoordSystem 4 | { 5 | RightHanded = 0, 6 | LeftHanded = 1 7 | } 8 | } -------------------------------------------------------------------------------- /FBXSharp/Core/DataView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace FBXSharp.Core 5 | { 6 | public struct DataView 7 | { 8 | public long Start { get; } 9 | public long End { get; } 10 | public bool IsBinary { get; } 11 | public BinaryReader Reader { get; } 12 | 13 | public DataView(long start, long end, bool isBinary, BinaryReader reader) 14 | { 15 | Start = start; 16 | End = end; 17 | IsBinary = isBinary; 18 | Reader = reader; 19 | } 20 | 21 | public override bool Equals(object obj) 22 | { 23 | return obj is DataView dataView && dataView == this; 24 | } 25 | 26 | public override int GetHashCode() 27 | { 28 | return Tuple.Create(Start, End, IsBinary, Reader).GetHashCode(); 29 | } 30 | 31 | public static bool operator ==(DataView dataView1, DataView dataView2) 32 | { 33 | return dataView1.Start == dataView2.Start && dataView1.End == dataView2.End && 34 | dataView1.IsBinary == dataView2.IsBinary && dataView1.Reader == dataView2.Reader; 35 | } 36 | 37 | public static bool operator !=(DataView dataView1, DataView dataView2) 38 | { 39 | return !(dataView1 == dataView2); 40 | } 41 | 42 | public int ToInt32(int offset = 0) 43 | { 44 | if (Reader is null) return 0; 45 | 46 | var current = Reader.BaseStream.Position; 47 | Reader.BaseStream.Position = Start + offset; 48 | 49 | if (IsBinary) 50 | { 51 | var result = Reader.ReadInt32(); 52 | Reader.BaseStream.Position = current; 53 | return result; 54 | } 55 | else 56 | { 57 | var result = Reader.ReadTextInt32(); 58 | Reader.BaseStream.Position = current; 59 | return result; 60 | } 61 | } 62 | 63 | public uint ToUInt32(int offset = 0) 64 | { 65 | if (Reader is null) return 0; 66 | 67 | var current = Reader.BaseStream.Position; 68 | Reader.BaseStream.Position = Start + offset; 69 | 70 | if (IsBinary) 71 | { 72 | var result = Reader.ReadUInt32(); 73 | Reader.BaseStream.Position = current; 74 | return result; 75 | } 76 | else 77 | { 78 | var result = Reader.ReadTextUInt32(); 79 | Reader.BaseStream.Position = current; 80 | return result; 81 | } 82 | } 83 | 84 | public long ToInt64(int offset = 0) 85 | { 86 | if (Reader is null) return 0; 87 | 88 | var current = Reader.BaseStream.Position; 89 | Reader.BaseStream.Position = Start + offset; 90 | 91 | if (IsBinary) 92 | { 93 | var result = Reader.ReadInt64(); 94 | Reader.BaseStream.Position = current; 95 | return result; 96 | } 97 | else 98 | { 99 | var result = Reader.ReadTextInt64(); 100 | Reader.BaseStream.Position = current; 101 | return result; 102 | } 103 | } 104 | 105 | public ulong ToUInt64(int offset = 0) 106 | { 107 | if (Reader is null) return 0; 108 | 109 | var current = Reader.BaseStream.Position; 110 | Reader.BaseStream.Position = Start + offset; 111 | 112 | if (IsBinary) 113 | { 114 | var result = Reader.ReadUInt64(); 115 | Reader.BaseStream.Position = current; 116 | return result; 117 | } 118 | else 119 | { 120 | var result = Reader.ReadTextUInt64(); 121 | Reader.BaseStream.Position = current; 122 | return result; 123 | } 124 | } 125 | 126 | public float ToSingle(int offset = 0) 127 | { 128 | if (Reader is null) return 0; 129 | 130 | var current = Reader.BaseStream.Position; 131 | Reader.BaseStream.Position = Start + offset; 132 | 133 | if (IsBinary) 134 | { 135 | var result = Reader.ReadSingle(); 136 | Reader.BaseStream.Position = current; 137 | return result; 138 | } 139 | else 140 | { 141 | var result = Reader.ReadTextSingle(); 142 | Reader.BaseStream.Position = current; 143 | return result; 144 | } 145 | } 146 | 147 | public double ToDouble(int offset = 0) 148 | { 149 | if (Reader is null) return 0; 150 | 151 | var current = Reader.BaseStream.Position; 152 | Reader.BaseStream.Position = Start + offset; 153 | 154 | if (IsBinary) 155 | { 156 | var result = Reader.ReadDouble(); 157 | Reader.BaseStream.Position = current; 158 | return result; 159 | } 160 | else 161 | { 162 | var result = Reader.ReadTextDouble(); 163 | Reader.BaseStream.Position = current; 164 | return result; 165 | } 166 | } 167 | 168 | public override string ToString() 169 | { 170 | if (Reader is null) return string.Empty; 171 | 172 | var current = Reader.BaseStream.Position; 173 | Reader.BaseStream.Position = Start; 174 | var size = (int)(End - Start); 175 | 176 | var result = Reader.ReadNullTerminated(size); 177 | Reader.BaseStream.Position = current; 178 | return result; 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /FBXSharp/Core/Footer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.Core 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Footer 7 | { 8 | public int Reserved; 9 | public uint Version; 10 | 11 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x78)] 12 | public byte[] Padding; 13 | 14 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x10)] 15 | public byte[] Magic; 16 | 17 | public const int SizeOf = 0x80; 18 | 19 | public static Footer CreateNew(uint version) 20 | { 21 | return new Footer 22 | { 23 | Reserved = 0, 24 | Version = version, 25 | Padding = new byte[0x78], 26 | Magic = new byte[0x10] 27 | { 28 | 0xF8, 29 | 0x5A, 30 | 0x8C, 31 | 0x6A, 32 | 0xDE, 33 | 0xF5, 34 | 0xD9, 35 | 0x7E, 36 | 0xEC, 37 | 0xE9, 38 | 0x0C, 39 | 0xE3, 40 | 0x75, 41 | 0x8F, 42 | 0x29, 43 | 0x0B 44 | } 45 | }; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /FBXSharp/Core/FrameRate.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.Core 2 | { 3 | public enum FrameRate 4 | { 5 | RateDefault = 0, 6 | Rate120 = 1, 7 | Rate100 = 2, 8 | Rate60 = 3, 9 | Rate50 = 4, 10 | Rate48 = 5, 11 | Rate30 = 6, 12 | Rate30Drop = 7, 13 | RateNTSCDropFrame = 8, 14 | RateNTSCFullFrame = 9, 15 | RatePAL = 10, 16 | RateCinema = 11, 17 | Rate1000 = 12, 18 | RateCinemaND = 13, 19 | RateCustom = 14 20 | } 21 | } -------------------------------------------------------------------------------- /FBXSharp/Core/FrontVector.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.Core 2 | { 3 | public enum FrontVector 4 | { 5 | ParityEven = 0, 6 | ParityOdd = 1 7 | } 8 | } -------------------------------------------------------------------------------- /FBXSharp/Core/Header.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.Core 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Header 7 | { 8 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x15)] 9 | public byte[] Magic; 10 | 11 | public byte Reserved1; 12 | public byte Reserved2; 13 | public uint Version; 14 | 15 | public const int SizeOf = 0x1B; // amazing lmao 16 | 17 | public static Header CreateNew(uint version) 18 | { 19 | return new Header 20 | { 21 | Version = version, 22 | Reserved1 = 0x1A, 23 | Reserved2 = 0x00, 24 | Magic = new byte[0x15] 25 | { 26 | (byte)'K', 27 | (byte)'a', 28 | (byte)'y', 29 | (byte)'d', 30 | (byte)'a', 31 | (byte)'r', 32 | (byte)'a', 33 | (byte)' ', 34 | (byte)'F', 35 | (byte)'B', 36 | (byte)'X', 37 | (byte)' ', 38 | (byte)'B', 39 | (byte)'i', 40 | (byte)'n', 41 | (byte)'a', 42 | (byte)'r', 43 | (byte)'y', 44 | (byte)' ', 45 | (byte)' ', 46 | 0 47 | } 48 | }; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /FBXSharp/Core/IElement.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.Core 2 | { 3 | public interface IElement 4 | { 5 | string Name { get; } 6 | IElement[] Children { get; } 7 | IElementAttribute[] Attributes { get; } 8 | 9 | IElement FindChild(string name); 10 | } 11 | } -------------------------------------------------------------------------------- /FBXSharp/Core/IElementAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.Core 2 | { 3 | public enum IElementAttributeType : byte 4 | { 5 | Byte = (byte)'C', 6 | Int16 = (byte)'Y', 7 | Int32 = (byte)'I', 8 | Int64 = (byte)'L', 9 | Single = (byte)'F', 10 | Double = (byte)'D', 11 | String = (byte)'S', 12 | ArrayBoolean = (byte)'b', 13 | ArrayInt32 = (byte)'i', 14 | ArrayInt64 = (byte)'l', 15 | ArraySingle = (byte)'f', 16 | ArrayDouble = (byte)'d', 17 | Binary = (byte)'R' 18 | } 19 | 20 | public interface IElementAttribute 21 | { 22 | IElementAttributeType Type { get; } 23 | int Stride { get; } 24 | int Length { get; } 25 | int Size { get; } 26 | 27 | object GetElementValue(); 28 | } 29 | 30 | public interface IGenericAttribute : IElementAttribute 31 | { 32 | T Value { get; } 33 | } 34 | } -------------------------------------------------------------------------------- /FBXSharp/Core/IElementProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FBXSharp.Core 4 | { 5 | [Flags] 6 | public enum IElementPropertyFlags 7 | { 8 | None = 0, 9 | Static = 1 << 0, 10 | Animatable = 1 << 1, 11 | Animated = 1 << 2, 12 | Imported = 1 << 3, 13 | UserDefined = 1 << 4, 14 | Hidden = 1 << 5, 15 | NotSavable = 1 << 6, 16 | 17 | LockedMember0 = 1 << 7, 18 | LockedMember1 = 1 << 8, 19 | LockedMember2 = 1 << 9, 20 | LockedMember3 = 1 << 10, 21 | LockedAll = LockedMember0 | LockedMember1 | LockedMember2 | LockedMember3, 22 | MutedMember0 = 1 << 11, 23 | MutedMember1 = 1 << 12, 24 | MutedMember2 = 1 << 13, 25 | MutedMember3 = 1 << 14, 26 | MutedAll = MutedMember0 | MutedMember1 | MutedMember2 | MutedMember3, 27 | 28 | UIDisabled = 1 << 15, 29 | UIGroup = 1 << 16, 30 | UIBoolGroup = 1 << 17, 31 | UIExpanded = 1 << 18, 32 | UINoCaption = 1 << 19, 33 | UIPanel = 1 << 20, 34 | UILeftLabel = 1 << 21, 35 | UIHidden = 1 << 22, 36 | 37 | CtrlFlags = 38 | Static | Animatable | Animated | Imported | UserDefined | Hidden | NotSavable | LockedAll | MutedAll, 39 | UIFlags = UIDisabled | UIGroup | UIBoolGroup | UIExpanded | UINoCaption | UIPanel | UILeftLabel | UIHidden, 40 | AllFlags = CtrlFlags | UIFlags, 41 | 42 | FlagCount = 23 43 | } 44 | 45 | public enum IElementPropertyType 46 | { 47 | Undefined, 48 | SByte, 49 | Byte, 50 | Short, 51 | UShort, 52 | UInt, 53 | Long, 54 | ULong, 55 | Half, 56 | Bool, 57 | Int, 58 | Float, 59 | Double, 60 | Double2, 61 | Double3, 62 | Double4, 63 | Double4x4, 64 | Enum, 65 | String, 66 | Time, 67 | Reference, 68 | Blob, 69 | Distance, 70 | DateTime, 71 | TypeCount 72 | } 73 | 74 | public interface IElementProperty 75 | { 76 | string Name { get; set; } 77 | 78 | IElementPropertyFlags Flags { get; set; } 79 | 80 | IElementPropertyType Type { get; } 81 | 82 | string Primary { get; } 83 | 84 | string Secondary { get; } 85 | 86 | bool SupportsMinMax { get; } 87 | 88 | Type GetPropertyType(); 89 | 90 | object GetPropertyValue(); 91 | object GetPropertyMin(); 92 | object GetPropertyMax(); 93 | 94 | void SetPropertyValue(object value); 95 | void SetPropertyMin(object value); 96 | void SetPropertyMax(object value); 97 | } 98 | 99 | public interface IGenericProperty : IElementProperty 100 | { 101 | T Value { get; set; } 102 | 103 | bool GetMinValue(out T min); 104 | bool GetMaxValue(out T max); 105 | 106 | void SetMinValue(in T min); 107 | void SetMaxValue(in T max); 108 | } 109 | } -------------------------------------------------------------------------------- /FBXSharp/Core/IScene.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FBXSharp.Core 4 | { 5 | public interface IScene 6 | { 7 | FBXObject Root { get; } 8 | GlobalSettings Settings { get; } 9 | IReadOnlyList Objects { get; } 10 | IReadOnlyList TakeInfos { get; } 11 | IReadOnlyList Templates { get; } 12 | 13 | TemplateObject CreateEmptyTemplate(FBXClassType classType, TemplateCreationType creationType); 14 | TemplateObject CreatePredefinedTemplate(FBXClassType classType, TemplateCreationType creationType); 15 | 16 | TemplateObject GetTemplateObject(string name); 17 | TemplateObject GetTemplateObject(FBXClassType classType); 18 | 19 | void AddTakeInfo(TakeInfo takeInfo); 20 | void RemoveTakeInfo(TakeInfo takeInfo); 21 | 22 | FBXObject CreateFBXObject(FBXClassType classType, FBXObjectType objectType, IElement element); 23 | void DestroyFBXObject(FBXObject @object); 24 | 25 | IEnumerable GetObjectsOfType() where T : FBXObject; 26 | IEnumerable GetObjectsOfType(FBXClassType classType, FBXObjectType objectType); 27 | } 28 | } -------------------------------------------------------------------------------- /FBXSharp/Core/UpVector.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.Core 2 | { 3 | public enum UpVector 4 | { 5 | AxisX = 0, 6 | AxisY = 1, 7 | AxisZ = 2 8 | } 9 | } -------------------------------------------------------------------------------- /FBXSharp/Element.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp 6 | { 7 | [DebuggerDisplay("{this.Name}")] 8 | public class Element : IElement 9 | { 10 | public string Name { get; } 11 | public IElement[] Children { get; } 12 | public IElementAttribute[] Attributes { get; } 13 | 14 | public Element(string name, IElement[] children, IElementAttribute[] attributes) 15 | { 16 | Name = name ?? string.Empty; 17 | Children = children ?? Array.Empty(); 18 | Attributes = attributes ?? Array.Empty(); 19 | } 20 | 21 | public IElement FindChild(string name) 22 | { 23 | return Array.Find(Children, _ => _.Name == name); 24 | } 25 | 26 | public static Element WithAttribute(string name, IElementAttribute attribute) 27 | { 28 | return new Element(name, null, new[] { attribute }); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/ArrayBooleanAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Elementary 6 | { 7 | [DebuggerDisplay("Boolean : {this.Value.Length} Items")] 8 | public class ArrayBooleanAttribute : IGenericAttribute 9 | { 10 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.ArrayBoolean; 11 | 12 | public static readonly int PropertyStride = 1; 13 | 14 | public static readonly int PropertyLength = -1; 15 | 16 | public IElementAttributeType Type => PropertyType; 17 | 18 | public int Size => 12 + PropertyStride * Length; 19 | 20 | public int Stride => PropertyStride; 21 | 22 | public int Length { get; } 23 | 24 | public bool[] Value { get; } 25 | 26 | public ArrayBooleanAttribute(bool[] value) 27 | { 28 | Value = value ?? Array.Empty(); 29 | Length = Value.Length; 30 | } 31 | 32 | public object GetElementValue() 33 | { 34 | return Value; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/ArrayDoubleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Elementary 6 | { 7 | [DebuggerDisplay("Double : {this.Value.Length} Items")] 8 | public class ArrayDoubleAttribute : IGenericAttribute 9 | { 10 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.ArrayDouble; 11 | 12 | public static readonly int PropertyStride = 8; 13 | 14 | public static readonly int PropertyLength = -1; 15 | 16 | public IElementAttributeType Type => PropertyType; 17 | 18 | public int Size => 12 + PropertyStride * Length; 19 | 20 | public int Stride => PropertyStride; 21 | 22 | public int Length { get; } 23 | 24 | public double[] Value { get; } 25 | 26 | public ArrayDoubleAttribute(double[] value) 27 | { 28 | Value = value ?? Array.Empty(); 29 | Length = Value.Length; 30 | } 31 | 32 | public object GetElementValue() 33 | { 34 | return Value; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/ArrayInt32Attribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Elementary 6 | { 7 | [DebuggerDisplay("Int32 : {this.Value.Length} Items")] 8 | public class ArrayInt32Attribute : IGenericAttribute 9 | { 10 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.ArrayInt32; 11 | 12 | public static readonly int PropertyStride = 4; 13 | 14 | public static readonly int PropertyLength = -1; 15 | 16 | public IElementAttributeType Type => PropertyType; 17 | 18 | public int Size => 12 + PropertyStride * Length; 19 | 20 | public int Stride => PropertyStride; 21 | 22 | public int Length { get; } 23 | 24 | public int[] Value { get; } 25 | 26 | public ArrayInt32Attribute(int[] value) 27 | { 28 | Value = value ?? Array.Empty(); 29 | Length = Value.Length; 30 | } 31 | 32 | public object GetElementValue() 33 | { 34 | return Value; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/ArrayInt64Attribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Elementary 6 | { 7 | [DebuggerDisplay("Int64 : {this.Value.Length} Items")] 8 | public class ArrayInt64Attribute : IGenericAttribute 9 | { 10 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.ArrayInt64; 11 | 12 | public static readonly int PropertyStride = 8; 13 | 14 | public static readonly int PropertyLength = -1; 15 | 16 | public IElementAttributeType Type => PropertyType; 17 | 18 | public int Size => 12 + PropertyStride * Length; 19 | 20 | public int Stride => PropertyStride; 21 | 22 | public int Length { get; } 23 | 24 | public long[] Value { get; } 25 | 26 | public ArrayInt64Attribute(long[] value) 27 | { 28 | Value = value ?? Array.Empty(); 29 | Length = Value.Length; 30 | } 31 | 32 | public object GetElementValue() 33 | { 34 | return Value; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/ArraySingleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Elementary 6 | { 7 | [DebuggerDisplay("Single : {this.Value.Length} Items")] 8 | public class ArraySingleAttribute : IGenericAttribute 9 | { 10 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.ArraySingle; 11 | 12 | public static readonly int PropertyStride = 4; 13 | 14 | public static readonly int PropertyLength = -1; 15 | 16 | public IElementAttributeType Type => PropertyType; 17 | 18 | public int Size => 12 + PropertyStride * Length; 19 | 20 | public int Stride => PropertyStride; 21 | 22 | public int Length { get; } 23 | 24 | public float[] Value { get; } 25 | 26 | public ArraySingleAttribute(float[] value) 27 | { 28 | Value = value ?? Array.Empty(); 29 | Length = Value.Length; 30 | } 31 | 32 | public object GetElementValue() 33 | { 34 | return Value; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/BinaryAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Elementary 6 | { 7 | [DebuggerDisplay("Raw : {this.Value.Length} bytes")] 8 | public class BinaryAttribute : IGenericAttribute 9 | { 10 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.Binary; 11 | 12 | public static readonly int PropertyStride = 1; 13 | 14 | public static readonly int PropertyLength = -1; 15 | 16 | public IElementAttributeType Type => PropertyType; 17 | 18 | public int Size => 4 + PropertyStride * Length; 19 | 20 | public int Stride => PropertyStride; 21 | 22 | public int Length { get; } 23 | 24 | public byte[] Value { get; } 25 | 26 | public BinaryAttribute(byte[] value) 27 | { 28 | Value = value ?? Array.Empty(); 29 | Length = Value.Length; 30 | } 31 | 32 | public object GetElementValue() 33 | { 34 | return Value; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/ByteAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Elementary 5 | { 6 | [DebuggerDisplay("{this.Value}B")] 7 | public class ByteAttribute : IGenericAttribute 8 | { 9 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.Byte; 10 | 11 | public static readonly int PropertyStride = 1; 12 | 13 | public static readonly int PropertyLength = 1; 14 | 15 | public IElementAttributeType Type => PropertyType; 16 | 17 | public int Size => PropertyStride; 18 | 19 | public int Stride => PropertyStride; 20 | 21 | public int Length => PropertyLength; 22 | 23 | public byte Value { get; } 24 | 25 | public ByteAttribute(byte value) 26 | { 27 | Value = value; 28 | } 29 | 30 | public object GetElementValue() 31 | { 32 | return Value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/DoubleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Elementary 5 | { 6 | [DebuggerDisplay("{this.Value}D")] 7 | public class DoubleAttribute : IGenericAttribute 8 | { 9 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.Double; 10 | 11 | public static readonly int PropertyStride = 8; 12 | 13 | public static readonly int PropertyLength = 1; 14 | 15 | public IElementAttributeType Type => PropertyType; 16 | 17 | public int Size => PropertyStride; 18 | 19 | public int Stride => PropertyStride; 20 | 21 | public int Length => PropertyLength; 22 | 23 | public double Value { get; } 24 | 25 | public DoubleAttribute(double value) 26 | { 27 | Value = value; 28 | } 29 | 30 | public object GetElementValue() 31 | { 32 | return Value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/Int16Attribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Elementary 5 | { 6 | [DebuggerDisplay("{this.Value}S")] 7 | public class Int16Attribute : IGenericAttribute 8 | { 9 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.Int16; 10 | 11 | public static readonly int PropertyStride = 2; 12 | 13 | public static readonly int PropertyLength = 1; 14 | 15 | public IElementAttributeType Type => PropertyType; 16 | 17 | public int Size => PropertyStride; 18 | 19 | public int Stride => PropertyStride; 20 | 21 | public int Length => PropertyLength; 22 | 23 | public short Value { get; } 24 | 25 | public Int16Attribute(short value) 26 | { 27 | Value = value; 28 | } 29 | 30 | public object GetElementValue() 31 | { 32 | return Value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/Int32Attribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Elementary 5 | { 6 | [DebuggerDisplay("{this.Value}")] 7 | public class Int32Attribute : IGenericAttribute 8 | { 9 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.Int32; 10 | 11 | public static readonly int PropertyStride = 4; 12 | 13 | public static readonly int PropertyLength = 1; 14 | 15 | public IElementAttributeType Type => PropertyType; 16 | 17 | public int Size => PropertyStride; 18 | 19 | public int Stride => PropertyStride; 20 | 21 | public int Length => PropertyLength; 22 | 23 | public int Value { get; } 24 | 25 | public Int32Attribute(int value) 26 | { 27 | Value = value; 28 | } 29 | 30 | public object GetElementValue() 31 | { 32 | return Value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/Int64Attribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Elementary 5 | { 6 | [DebuggerDisplay("{this.Value}L")] 7 | public class Int64Attribute : IGenericAttribute 8 | { 9 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.Int64; 10 | 11 | public static readonly int PropertyStride = 8; 12 | 13 | public static readonly int PropertyLength = 1; 14 | 15 | public IElementAttributeType Type => PropertyType; 16 | 17 | public int Size => PropertyStride; 18 | 19 | public int Stride => PropertyStride; 20 | 21 | public int Length => PropertyLength; 22 | 23 | public long Value { get; } 24 | 25 | public Int64Attribute(long value) 26 | { 27 | Value = value; 28 | } 29 | 30 | public object GetElementValue() 31 | { 32 | return Value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/SingleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Elementary 5 | { 6 | [DebuggerDisplay("{this.Value}F")] 7 | public class SingleAttribute : IGenericAttribute 8 | { 9 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.Single; 10 | 11 | public static readonly int PropertyStride = 4; 12 | 13 | public static readonly int PropertyLength = 1; 14 | 15 | public IElementAttributeType Type => PropertyType; 16 | 17 | public int Size => PropertyStride; 18 | 19 | public int Stride => PropertyStride; 20 | 21 | public int Length => PropertyLength; 22 | 23 | public float Value { get; } 24 | 25 | public SingleAttribute(float value) 26 | { 27 | Value = value; 28 | } 29 | 30 | public object GetElementValue() 31 | { 32 | return Value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /FBXSharp/Elementary/StringAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Elementary 5 | { 6 | [DebuggerDisplay("{this.Value}")] 7 | public class StringAttribute : IGenericAttribute 8 | { 9 | public static readonly IElementAttributeType PropertyType = IElementAttributeType.String; 10 | 11 | public static readonly int PropertyStride = -1; 12 | 13 | public static readonly int PropertyLength = 1; 14 | 15 | public IElementAttributeType Type => PropertyType; 16 | 17 | public int Size => 4 + Stride; 18 | 19 | public int Stride { get; } 20 | 21 | public int Length => PropertyLength; 22 | 23 | public string Value { get; } 24 | 25 | public StringAttribute(string value) 26 | { 27 | // 4 bytes char count + string length 28 | Value = value ?? string.Empty; 29 | Stride = Value.Length; 30 | } 31 | 32 | public object GetElementValue() 33 | { 34 | return Value; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /FBXSharp/FBXSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net471 5 | Elementary 6 | 7 | 8 | 9 | true 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /FBXSharp/GlobalSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FBXSharp.Core; 3 | using FBXSharp.ValueTypes; 4 | 5 | namespace FBXSharp 6 | { 7 | public class GlobalSettings : FBXObject 8 | { 9 | public static readonly FBXObjectType FType = FBXObjectType.GlobalSettings; 10 | 11 | public static readonly FBXClassType FClass = FBXClassType.GlobalSettings; 12 | 13 | public override FBXObjectType Type => FType; 14 | 15 | public override FBXClassType Class => FClass; 16 | 17 | public int? UpAxis 18 | { 19 | get => InternalGetPrimitive(nameof(UpAxis), IElementPropertyType.Int); 20 | set => InternalSetPrimitive(nameof(UpAxis), IElementPropertyType.Int, value, "int", "Integer"); 21 | } 22 | 23 | public int? UpAxisSign 24 | { 25 | get => InternalGetPrimitive(nameof(UpAxisSign), IElementPropertyType.Int); 26 | set => InternalSetPrimitive(nameof(UpAxisSign), IElementPropertyType.Int, value, "int", "Integer"); 27 | } 28 | 29 | public int? FrontAxis 30 | { 31 | get => InternalGetPrimitive(nameof(FrontAxis), IElementPropertyType.Int); 32 | set => InternalSetPrimitive(nameof(FrontAxis), IElementPropertyType.Int, value, "int", "Integer"); 33 | } 34 | 35 | public int? FrontAxisSign 36 | { 37 | get => InternalGetPrimitive(nameof(FrontAxisSign), IElementPropertyType.Int); 38 | set => InternalSetPrimitive(nameof(FrontAxisSign), IElementPropertyType.Int, value, "int", "Integer"); 39 | } 40 | 41 | public int? CoordAxis 42 | { 43 | get => InternalGetPrimitive(nameof(CoordAxis), IElementPropertyType.Int); 44 | set => InternalSetPrimitive(nameof(CoordAxis), IElementPropertyType.Int, value, "int", "Integer"); 45 | } 46 | 47 | public int? CoordAxisSign 48 | { 49 | get => InternalGetPrimitive(nameof(CoordAxisSign), IElementPropertyType.Int); 50 | set => InternalSetPrimitive(nameof(CoordAxisSign), IElementPropertyType.Int, value, "int", "Integer"); 51 | } 52 | 53 | public int? OriginalUpAxis 54 | { 55 | get => InternalGetPrimitive(nameof(OriginalUpAxis), IElementPropertyType.Int); 56 | set => InternalSetPrimitive(nameof(OriginalUpAxis), IElementPropertyType.Int, value, "int", "Integer"); 57 | } 58 | 59 | public int? OriginalUpAxisSign 60 | { 61 | get => InternalGetPrimitive(nameof(OriginalUpAxisSign), IElementPropertyType.Int); 62 | set => InternalSetPrimitive(nameof(OriginalUpAxisSign), IElementPropertyType.Int, value, "int", "Integer"); 63 | } 64 | 65 | public double? UnitScaleFactor 66 | { 67 | get => InternalGetPrimitive(nameof(UnitScaleFactor), IElementPropertyType.Double); 68 | set => InternalSetPrimitive(nameof(UnitScaleFactor), IElementPropertyType.Double, value, "double", 69 | "Number"); 70 | } 71 | 72 | public double? OriginalUnitScaleFactor 73 | { 74 | get => InternalGetPrimitive(nameof(OriginalUnitScaleFactor), IElementPropertyType.Double); 75 | set => InternalSetPrimitive(nameof(OriginalUnitScaleFactor), IElementPropertyType.Double, value, "double", 76 | "Number"); 77 | } 78 | 79 | public TimeBase? TimeSpanStart 80 | { 81 | get => InternalGetPrimitive(nameof(TimeSpanStart), IElementPropertyType.Time); 82 | set => InternalSetPrimitive(nameof(TimeSpanStart), IElementPropertyType.Time, value, "KTime", "Time"); 83 | } 84 | 85 | public TimeBase? TimeSpanStop 86 | { 87 | get => InternalGetPrimitive(nameof(TimeSpanStop), IElementPropertyType.Time); 88 | set => InternalSetPrimitive(nameof(TimeSpanStop), IElementPropertyType.Time, value, "KTime", "Time"); 89 | } 90 | 91 | public double? CustomFrameRate 92 | { 93 | get => InternalGetPrimitive(nameof(CustomFrameRate), IElementPropertyType.Double); 94 | set => InternalSetPrimitive(nameof(CustomFrameRate), IElementPropertyType.Double, value, "double", 95 | "Number"); 96 | } 97 | 98 | public Enumeration TimeMode 99 | { 100 | get => InternalGetEnumeration(nameof(TimeMode)); 101 | set => InternalSetEnumeration(nameof(TimeMode), value, "enum", string.Empty); 102 | } 103 | 104 | internal GlobalSettings(IElement element, IScene scene) : base(element, scene) 105 | { 106 | Name = nameof(GlobalSettings); 107 | } 108 | 109 | internal void InternalFillWithElement(IElement element) 110 | { 111 | FromElement(element); 112 | } 113 | 114 | public override IElement AsElement(bool binary) 115 | { 116 | throw new NotSupportedException("Global Settings cannot be serialized"); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /FBXSharp/LoadFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FBXSharp 4 | { 5 | [Flags] 6 | public enum LoadFlags 7 | { 8 | None = 0, 9 | Triangulate = 1 << 0, 10 | RemapSubmeshes = 1 << 1, 11 | IgnoreGeometry = 1 << 2, 12 | IgnoreBlendShapes = 1 << 3, 13 | LoadVideoFiles = 1 << 4 14 | } 15 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/AnimationCurve.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Objective 5 | { 6 | public class AnimationCurve : FBXObject 7 | { 8 | private long[] m_keyTimes; 9 | private float[] m_keyValues; 10 | 11 | public static readonly FBXObjectType FType = FBXObjectType.AnimationCurve; 12 | 13 | public static readonly FBXClassType FClass = FBXClassType.AnimationCurve; 14 | 15 | public override FBXObjectType Type => FType; 16 | 17 | public override FBXClassType Class => FClass; 18 | 19 | public long[] KeyTimes 20 | { 21 | get => m_keyTimes; 22 | set => m_keyTimes = value ?? Array.Empty(); 23 | } 24 | 25 | public float[] KeyValues 26 | { 27 | get => m_keyValues; 28 | set => m_keyValues = value ?? Array.Empty(); 29 | } 30 | 31 | internal AnimationCurve(IElement element, IScene scene) : base(element, scene) 32 | { 33 | m_keyTimes = Array.Empty(); 34 | m_keyValues = Array.Empty(); 35 | 36 | if (element is null) return; 37 | 38 | var times = element.FindChild("KeyTime"); 39 | var value = element.FindChild("KeyValueFloat"); 40 | 41 | if (!(times is null) && times.Attributes.Length > 0 && 42 | times.Attributes[0].GetElementValue() is Array timesArray) 43 | { 44 | m_keyTimes = new long[timesArray.Length]; 45 | Array.Copy(timesArray, m_keyTimes, m_keyTimes.Length); 46 | } 47 | 48 | if (!(value is null) && value.Attributes.Length > 0 && 49 | value.Attributes[0].GetElementValue() is Array valueArray) 50 | { 51 | m_keyValues = new float[valueArray.Length]; 52 | Array.Copy(valueArray, m_keyValues, m_keyValues.Length); 53 | } 54 | 55 | if (m_keyValues.Length != m_keyTimes.Length) 56 | throw new Exception($"Invalid animation curve with name {Name}"); 57 | } 58 | 59 | public override IElement AsElement(bool binary) 60 | { 61 | return new Element(Class.ToString(), null, BuildAttributes("AnimCurve", string.Empty, binary)); // #TODO 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/AnimationCurveNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Objective 5 | { 6 | public class AnimationCurveNode : FBXObject 7 | { 8 | private AnimationCurve m_curveX; 9 | private AnimationCurve m_curveY; 10 | private AnimationCurve m_curveZ; 11 | 12 | public static readonly FBXObjectType FType = FBXObjectType.AnimationCurveNode; 13 | 14 | public static readonly FBXClassType FClass = FBXClassType.AnimationCurveNode; 15 | 16 | public override FBXObjectType Type => FType; 17 | 18 | public override FBXClassType Class => FClass; 19 | 20 | public double DeltaX { get; set; } 21 | public double DeltaY { get; set; } 22 | public double DeltaZ { get; set; } 23 | 24 | public AnimationCurve CurveX 25 | { 26 | get => m_curveX; 27 | set => InternalSetCurve(value, ref m_curveX); 28 | } 29 | 30 | public AnimationCurve CurveY 31 | { 32 | get => m_curveY; 33 | set => InternalSetCurve(value, ref m_curveY); 34 | } 35 | 36 | public AnimationCurve CurveZ 37 | { 38 | get => m_curveZ; 39 | set => InternalSetCurve(value, ref m_curveZ); 40 | } 41 | 42 | internal AnimationCurveNode(IElement element, IScene scene) : base(element, scene) 43 | { 44 | } 45 | 46 | private void InternalSetCurve(AnimationCurve curve, ref AnimationCurve target) 47 | { 48 | if (curve is null || curve.Scene == Scene) 49 | { 50 | target = curve; 51 | 52 | return; 53 | } 54 | 55 | throw new Exception("Animation curve should share same scene with animation curve node"); 56 | } 57 | 58 | public override Connection[] GetConnections() 59 | { 60 | var noCurveX = m_curveX is null; 61 | var noCurveY = m_curveY is null; 62 | var noCurveZ = m_curveZ is null; 63 | 64 | if (noCurveX && noCurveY && noCurveZ) return Array.Empty(); 65 | 66 | var currentlyAt = 0; 67 | var thisHashKey = GetHashCode(); 68 | var connections = new Connection[(noCurveX ? 0 : 1) + (noCurveY ? 0 : 1) + (noCurveZ ? 0 : 1)]; 69 | 70 | if (!noCurveX) 71 | connections[currentlyAt++] = new Connection 72 | ( 73 | Connection.ConnectionType.Property, 74 | m_curveX.GetHashCode(), 75 | thisHashKey, 76 | ElementaryFactory.GetElementAttribute("d|X") 77 | ); 78 | 79 | if (!noCurveY) 80 | connections[currentlyAt++] = new Connection 81 | ( 82 | Connection.ConnectionType.Property, 83 | m_curveY.GetHashCode(), 84 | thisHashKey, 85 | ElementaryFactory.GetElementAttribute("d|Y") 86 | ); 87 | 88 | if (!noCurveZ) 89 | connections[currentlyAt++] = new Connection 90 | ( 91 | Connection.ConnectionType.Property, 92 | m_curveZ.GetHashCode(), 93 | thisHashKey, 94 | ElementaryFactory.GetElementAttribute("d|Z") 95 | ); 96 | 97 | return connections; 98 | } 99 | 100 | public override void ResolveLink(FBXObject linker, IElementAttribute attribute) 101 | { 102 | if (linker.Class == FBXClassType.AnimationCurve && linker.Type == FBXObjectType.AnimationCurve) 103 | if (attribute.Type == IElementAttributeType.String) 104 | switch (attribute.GetElementValue().ToString()) 105 | { 106 | case "d|X": 107 | CurveX = linker as AnimationCurve; 108 | return; 109 | case "d|Y": 110 | CurveY = linker as AnimationCurve; 111 | return; 112 | case "d|Z": 113 | CurveZ = linker as AnimationCurve; 114 | return; 115 | } 116 | } 117 | 118 | public override IElement AsElement(bool binary) 119 | { 120 | var elements = Properties.Count == 0 121 | ? Array.Empty() 122 | : new[] { BuildProperties70() }; 123 | 124 | return new Element(Class.ToString(), elements, BuildAttributes("AnimCurveNode", string.Empty, binary)); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/AnimationLayer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Objective 6 | { 7 | public class AnimationLayer : FBXObject 8 | { 9 | private readonly List m_curves; 10 | 11 | public static readonly FBXObjectType FType = FBXObjectType.AnimationLayer; 12 | 13 | public static readonly FBXClassType FClass = FBXClassType.AnimationLayer; 14 | 15 | public override FBXObjectType Type => FType; 16 | 17 | public override FBXClassType Class => FClass; 18 | 19 | public IReadOnlyList CurveNodes => m_curves; 20 | 21 | internal AnimationLayer(IElement element, IScene scene) : base(element, scene) 22 | { 23 | m_curves = new List(); 24 | } 25 | 26 | public override Connection[] GetConnections() 27 | { 28 | if (m_curves.Count == 0) return Array.Empty(); 29 | 30 | var thisHashKey = GetHashCode(); 31 | var connections = new Connection[m_curves.Count]; 32 | 33 | for (var i = 0; i < connections.Length; ++i) 34 | connections[i] = new Connection(Connection.ConnectionType.Object, m_curves[i].GetHashCode(), 35 | thisHashKey); 36 | 37 | return connections; 38 | } 39 | 40 | public override void ResolveLink(FBXObject linker, IElementAttribute attribute) 41 | { 42 | if (linker.Class == FBXClassType.AnimationCurveNode && linker.Type == FBXObjectType.AnimationCurveNode) 43 | m_curves.Add(linker as AnimationCurveNode); // #TODO 44 | } 45 | 46 | public override IElement AsElement(bool binary) 47 | { 48 | var elements = Properties.Count == 0 49 | ? Array.Empty() 50 | : new[] { BuildProperties70() }; 51 | 52 | return new Element(Class.ToString(), elements, BuildAttributes("AnimLayer", string.Empty, binary)); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/AnimationStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Objective 6 | { 7 | public class AnimationStack : FBXObject 8 | { 9 | private readonly List m_layers; 10 | 11 | public static readonly FBXObjectType FType = FBXObjectType.AnimationStack; 12 | 13 | public static readonly FBXClassType FClass = FBXClassType.AnimationStack; 14 | 15 | public override FBXObjectType Type => FType; 16 | 17 | public override FBXClassType Class => FClass; 18 | 19 | public IReadOnlyList Layers => m_layers; 20 | 21 | internal AnimationStack(IElement element, IScene scene) : base(element, scene) 22 | { 23 | m_layers = new List(); 24 | } 25 | 26 | public override Connection[] GetConnections() 27 | { 28 | if (m_layers.Count == 0) return Array.Empty(); 29 | 30 | var thisHashKey = GetHashCode(); 31 | var connections = new Connection[m_layers.Count]; 32 | 33 | for (var i = 0; i < connections.Length; ++i) 34 | connections[i] = new Connection(Connection.ConnectionType.Object, m_layers[i].GetHashCode(), 35 | thisHashKey); 36 | 37 | return connections; 38 | } 39 | 40 | public override void ResolveLink(FBXObject linker, IElementAttribute attribute) 41 | { 42 | if (linker.Class == FBXClassType.AnimationLayer && linker.Type == FBXObjectType.AnimationLayer) 43 | m_layers.Add(linker as AnimationLayer); // #TODO 44 | } 45 | 46 | public override IElement AsElement(bool binary) 47 | { 48 | var elements = Properties.Count == 0 49 | ? Array.Empty() 50 | : new[] { BuildProperties70() }; 51 | 52 | return new Element(Class.ToString(), elements, BuildAttributes("AnimStack", string.Empty, binary)); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/BlendShape.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Objective 6 | { 7 | public class BlendShape : FBXObject 8 | { 9 | private readonly List m_channels; 10 | 11 | public static readonly FBXObjectType FType = FBXObjectType.BlendShape; 12 | 13 | public static readonly FBXClassType FClass = FBXClassType.Deformer; 14 | 15 | public override FBXObjectType Type => FType; 16 | 17 | public override FBXClassType Class => FClass; 18 | 19 | public IReadOnlyList Channels => m_channels; 20 | 21 | internal BlendShape(IElement element, IScene scene) : base(element, scene) 22 | { 23 | m_channels = new List(); 24 | } 25 | 26 | public void AddChannel(BlendShapeChannel channel) 27 | { 28 | if (channel is null) return; 29 | 30 | if (channel.Scene != Scene) 31 | throw new Exception("Blend shape channel should share same scene with blend shape"); 32 | 33 | m_channels.Add(channel); 34 | } 35 | 36 | public void RemoveChannel(BlendShapeChannel channel) 37 | { 38 | if (channel is null || channel.Scene != Scene) return; 39 | 40 | _ = m_channels.Remove(channel); 41 | } 42 | 43 | public void AddChannelAt(BlendShapeChannel channel, int index) 44 | { 45 | if (channel is null) return; 46 | 47 | if (channel.Scene != Scene) 48 | throw new Exception("Blend shape channel should share same scene with blend shape"); 49 | 50 | if (index < 0 || index > m_channels.Count) 51 | throw new ArgumentOutOfRangeException( 52 | "Index should be in range 0 to blend shape channel count inclusively"); 53 | 54 | m_channels.Insert(index, channel); 55 | } 56 | 57 | public void RemoveChannelAt(int index) 58 | { 59 | if (index < 0 || index >= m_channels.Count) 60 | throw new ArgumentOutOfRangeException("Index should be in 0 to blend shape channel count range"); 61 | 62 | m_channels.RemoveAt(index); 63 | } 64 | 65 | public override Connection[] GetConnections() 66 | { 67 | if (m_channels.Count == 0) 68 | { 69 | return Array.Empty(); 70 | } 71 | 72 | var thisHashKey = GetHashCode(); 73 | var connections = new Connection[m_channels.Count]; 74 | 75 | for (var i = 0; i < connections.Length; ++i) 76 | connections[i] = new Connection(Connection.ConnectionType.Object, m_channels[i].GetHashCode(), 77 | thisHashKey); 78 | 79 | return connections; 80 | } 81 | 82 | public override void ResolveLink(FBXObject linker, IElementAttribute attribute) 83 | { 84 | if (linker.Class == FBXClassType.Deformer && linker.Type == FBXObjectType.BlendShapeChannel) 85 | AddChannel(linker as BlendShapeChannel); 86 | } 87 | 88 | public override IElement AsElement(bool binary) 89 | { 90 | var hasAnyProperties = Properties.Count != 0; 91 | 92 | var elements = new IElement[1 + (hasAnyProperties ? 1 : 0)]; 93 | 94 | elements[0] = Element.WithAttribute("Version", ElementaryFactory.GetElementAttribute(100)); 95 | 96 | if (hasAnyProperties) elements[1] = BuildProperties70(); 97 | 98 | return new Element(Class.ToString(), elements, BuildAttributes("Deformer", Type.ToString(), binary)); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/BlendShapeChannel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Objective 6 | { 7 | public class BlendShapeChannel : FBXObject 8 | { 9 | private readonly List m_shapes; 10 | private double[] m_fullWeights; 11 | 12 | public static readonly FBXObjectType FType = FBXObjectType.BlendShapeChannel; 13 | 14 | public static readonly FBXClassType FClass = FBXClassType.Deformer; 15 | 16 | public override FBXObjectType Type => FType; 17 | 18 | public override FBXClassType Class => FClass; 19 | 20 | public double DeformPercent { get; set; } 21 | 22 | public double[] FullWeights 23 | { 24 | get => m_fullWeights; 25 | set => m_fullWeights = value ?? Array.Empty(); 26 | } 27 | 28 | public IReadOnlyList Shapes => m_shapes; 29 | 30 | internal BlendShapeChannel(IElement element, IScene scene) : base(element, scene) 31 | { 32 | m_shapes = new List(); 33 | m_fullWeights = Array.Empty(); 34 | 35 | if (element is null) return; 36 | 37 | var percent = element.FindChild("DeformPercent"); 38 | 39 | if (!(percent is null) && percent.Attributes.Length > 0 && 40 | percent.Attributes[0].Type == IElementAttributeType.Double) 41 | DeformPercent = Convert.ToDouble(percent.Attributes[0].GetElementValue()); 42 | 43 | var weights = element.FindChild("FullWeights"); 44 | 45 | if (!(weights is null) && weights.Attributes.Length > 0 && 46 | weights.Attributes[0].Type == IElementAttributeType.ArrayDouble) 47 | _ = ElementaryFactory.ToDoubleArray(weights.Attributes[0], out m_fullWeights); 48 | } 49 | 50 | public void AddShape(Shape shape) 51 | { 52 | if (shape is null) return; 53 | 54 | if (shape.Scene != Scene) throw new Exception("Shape should share same scene with blend shape channel"); 55 | 56 | m_shapes.Add(shape); 57 | } 58 | 59 | public void RemoveShape(Shape shape) 60 | { 61 | if (shape is null || shape.Scene != Scene) return; 62 | 63 | _ = m_shapes.Remove(shape); 64 | } 65 | 66 | public void AddShapeAt(Shape shape, int index) 67 | { 68 | if (shape is null) return; 69 | 70 | if (shape.Scene != Scene) throw new Exception("Shape should share same scene with blend shape channel"); 71 | 72 | if (index < 0 || index > m_shapes.Count) 73 | throw new ArgumentOutOfRangeException("Index should be in range 0 to shape count inclusively"); 74 | 75 | m_shapes.Insert(index, shape); 76 | } 77 | 78 | public void RemoveMaterialAt(int index) 79 | { 80 | if (index < 0 || index >= m_shapes.Count) 81 | throw new ArgumentOutOfRangeException("Index should be in 0 to shape count range"); 82 | 83 | m_shapes.RemoveAt(index); 84 | } 85 | 86 | public override Connection[] GetConnections() 87 | { 88 | if (m_shapes.Count == 0) 89 | { 90 | return Array.Empty(); 91 | } 92 | 93 | var thisHashKey = GetHashCode(); 94 | var connections = new Connection[m_shapes.Count]; 95 | 96 | for (var i = 0; i < connections.Length; ++i) 97 | connections[i] = new Connection(Connection.ConnectionType.Object, m_shapes[i].GetHashCode(), 98 | thisHashKey); 99 | 100 | return connections; 101 | } 102 | 103 | public override void ResolveLink(FBXObject linker, IElementAttribute attribute) 104 | { 105 | if (linker.Class == FBXClassType.Geometry && linker.Type == FBXObjectType.Shape) AddShape(linker as Shape); 106 | } 107 | 108 | public override IElement AsElement(bool binary) 109 | { 110 | var elements = new IElement[3]; 111 | 112 | elements[0] = Element.WithAttribute("Version", ElementaryFactory.GetElementAttribute(100)); 113 | elements[1] = Element.WithAttribute("DeformPercent", ElementaryFactory.GetElementAttribute(DeformPercent)); 114 | elements[2] = Element.WithAttribute("FullWeights", ElementaryFactory.GetElementAttribute(m_fullWeights)); 115 | 116 | return new Element(Class.ToString(), elements, 117 | BuildAttributes("SubDeformer", Type.ToString(), binary)); // #TODO 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/Light.cs: -------------------------------------------------------------------------------- 1 | using FBXSharp.Core; 2 | 3 | namespace FBXSharp.Objective 4 | { 5 | public class Light : Model 6 | { 7 | public static readonly FBXObjectType FType = FBXObjectType.Light; 8 | 9 | public override FBXObjectType Type => FType; 10 | 11 | public override bool SupportsAttribute => true; 12 | 13 | internal Light(IElement element, IScene scene) : base(element, scene) 14 | { 15 | } 16 | 17 | public override IElement AsElement(bool binary) 18 | { 19 | return MakeElement("Model", binary); 20 | } 21 | } 22 | 23 | public class LightAttribute : NodeAttribute 24 | { 25 | public static readonly FBXObjectType FType = FBXObjectType.Light; 26 | 27 | public override FBXObjectType Type => FType; 28 | 29 | internal LightAttribute(IElement element, IScene scene) : base(element, scene) 30 | { 31 | } 32 | 33 | public override IElement AsElement(bool binary) 34 | { 35 | var elements = new IElement[3]; 36 | 37 | elements[0] = Element.WithAttribute("TypeFlags", ElementaryFactory.GetElementAttribute("Light")); 38 | elements[1] = Element.WithAttribute("GeometryVersion", ElementaryFactory.GetElementAttribute(124)); 39 | elements[2] = BuildProperties70(); 40 | 41 | return new Element(Class.ToString(), elements, BuildAttributes("NodeAttribute", Type.ToString(), binary)); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/LimbNode.cs: -------------------------------------------------------------------------------- 1 | using FBXSharp.Core; 2 | 3 | namespace FBXSharp.Objective 4 | { 5 | public class LimbNode : Model 6 | { 7 | public static readonly FBXObjectType FType = FBXObjectType.LimbNode; 8 | 9 | public override FBXObjectType Type => FType; 10 | 11 | public override bool SupportsAttribute => true; 12 | 13 | internal LimbNode(IElement element, IScene scene) : base(element, scene) 14 | { 15 | } 16 | 17 | public override IElement AsElement(bool binary) 18 | { 19 | return MakeElement("Model", binary); 20 | } 21 | } 22 | 23 | public class LimbNodeAttribute : NodeAttribute 24 | { 25 | private string m_typeFlags; 26 | 27 | public static readonly FBXObjectType FType = FBXObjectType.LimbNode; 28 | 29 | public override FBXObjectType Type => FType; 30 | 31 | public string TypeFlags 32 | { 33 | get => m_typeFlags; 34 | set => m_typeFlags = value ?? string.Empty; 35 | } 36 | 37 | internal LimbNodeAttribute(IElement element, IScene scene) : base(element, scene) 38 | { 39 | if (element is null) return; 40 | 41 | var typeFlags = element.FindChild("TypeFlags"); 42 | 43 | if (typeFlags is null || typeFlags.Attributes.Length == 0 || 44 | typeFlags.Attributes[0].Type != IElementAttributeType.String) 45 | m_typeFlags = string.Empty; 46 | else 47 | m_typeFlags = typeFlags.Attributes[0].GetElementValue().ToString(); 48 | } 49 | 50 | public override IElement AsElement(bool binary) 51 | { 52 | var hasAnyProperties = Properties.Count != 0; 53 | 54 | var elements = new IElement[1 + (hasAnyProperties ? 1 : 0)]; 55 | 56 | elements[0] = Element.WithAttribute("TypeFlags", ElementaryFactory.GetElementAttribute(m_typeFlags)); 57 | 58 | if (hasAnyProperties) elements[1] = BuildProperties70(); 59 | 60 | return new Element(Class.ToString(), elements, BuildAttributes("NodeAttribute", Type.ToString(), binary)); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/Mesh.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Objective 6 | { 7 | public class Mesh : Model 8 | { 9 | private readonly List m_materials; 10 | private Geometry m_geometry; 11 | 12 | public static readonly FBXObjectType FType = FBXObjectType.Mesh; 13 | 14 | public override FBXObjectType Type => FType; 15 | 16 | public override bool SupportsAttribute => false; 17 | 18 | public Geometry Geometry 19 | { 20 | get => m_geometry; 21 | set => InternalSetGeometry(value); 22 | } 23 | 24 | public IReadOnlyList Materials => m_materials; 25 | 26 | internal Mesh(IElement element, IScene scene) : base(element, scene) 27 | { 28 | m_materials = new List(); 29 | } 30 | 31 | private void InternalSetGeometry(Geometry geometry) 32 | { 33 | if (geometry is null) 34 | { 35 | m_geometry = null; 36 | return; 37 | } 38 | 39 | if (geometry.Scene != Scene) throw new Exception("Geometry should share same scene with model"); 40 | 41 | m_geometry = geometry; 42 | } 43 | 44 | public void AddMaterial(Material material) 45 | { 46 | if (material is null) return; 47 | 48 | if (material.Scene != Scene) throw new Exception("Material should share same scene with model"); 49 | 50 | m_materials.Add(material); 51 | } 52 | 53 | public void RemoveMaterial(Material material) 54 | { 55 | if (material is null || material.Scene != Scene) return; 56 | 57 | _ = m_materials.Remove(material); 58 | } 59 | 60 | public void AddMaterialAt(Material material, int index) 61 | { 62 | if (material is null) return; 63 | 64 | if (material.Scene != Scene) throw new Exception("Material should share same scene with model"); 65 | 66 | if (index < 0 || index > m_materials.Count) 67 | throw new ArgumentOutOfRangeException("Index should be in range 0 to material count inclusively"); 68 | 69 | m_materials.Insert(index, material); 70 | } 71 | 72 | public void RemoveMaterialAt(int index) 73 | { 74 | if (index < 0 || index >= m_materials.Count) 75 | throw new ArgumentOutOfRangeException("Index should be in 0 to material count range"); 76 | 77 | m_materials.RemoveAt(index); 78 | } 79 | 80 | public override Connection[] GetConnections() 81 | { 82 | var counter = 0; 83 | var indexer = 0; 84 | 85 | counter += m_geometry is null ? 0 : 1; 86 | counter += m_materials.Count; 87 | counter += Children.Count; 88 | 89 | if (counter == 0) return Array.Empty(); 90 | 91 | var connections = new Connection[counter]; 92 | 93 | if (!(m_geometry is null)) 94 | connections[indexer++] = new Connection(Connection.ConnectionType.Object, m_geometry.GetHashCode(), 95 | GetHashCode()); 96 | 97 | for (var i = 0; i < m_materials.Count; ++i) 98 | connections[indexer++] = new Connection(Connection.ConnectionType.Object, m_materials[i].GetHashCode(), 99 | GetHashCode()); 100 | 101 | for (var i = 0; i < Children.Count; ++i) 102 | connections[indexer++] = new Connection(Connection.ConnectionType.Object, Children[i].GetHashCode(), 103 | GetHashCode()); 104 | 105 | return connections; 106 | } 107 | 108 | public override void ResolveLink(FBXObject linker, IElementAttribute attribute) 109 | { 110 | if (linker.Class == FBXClassType.Model) 111 | { 112 | AddChild(linker as Model); 113 | 114 | return; 115 | } 116 | 117 | if (linker.Class == FBXClassType.Geometry) 118 | if (linker.Type == FBXObjectType.Mesh) 119 | { 120 | InternalSetGeometry(linker as Geometry); 121 | 122 | return; 123 | } 124 | 125 | if (linker.Class == FBXClassType.Material) 126 | { 127 | AddMaterial(linker as Material); 128 | } 129 | } 130 | 131 | public override IElement AsElement(bool binary) 132 | { 133 | return MakeElement("Model", binary); 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/NullNode.cs: -------------------------------------------------------------------------------- 1 | using FBXSharp.Core; 2 | 3 | namespace FBXSharp.Objective 4 | { 5 | public class NullNode : Model 6 | { 7 | public static readonly FBXObjectType FType = FBXObjectType.Null; 8 | 9 | public override FBXObjectType Type => FType; 10 | 11 | public override bool SupportsAttribute => true; 12 | 13 | internal NullNode(IElement element, IScene scene) : base(element, scene) 14 | { 15 | } 16 | 17 | public override IElement AsElement(bool binary) 18 | { 19 | return MakeElement("Model", binary); 20 | } 21 | } 22 | 23 | public class NullAttribute : NodeAttribute 24 | { 25 | public static readonly FBXObjectType FType = FBXObjectType.Null; 26 | 27 | public override FBXObjectType Type => FType; 28 | 29 | internal NullAttribute(IElement element, IScene scene) : base(element, scene) 30 | { 31 | } 32 | 33 | public override IElement AsElement(bool binary) 34 | { 35 | var elements = new IElement[2]; 36 | 37 | elements[0] = Element.WithAttribute("TypeFlags", ElementaryFactory.GetElementAttribute("Null")); 38 | elements[1] = BuildProperties70(); 39 | 40 | return new Element(Class.ToString(), elements, BuildAttributes("NodeAttribute", Type.ToString(), binary)); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/Root.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp.Objective 5 | { 6 | public class Root : Model 7 | { 8 | public static readonly FBXObjectType FType = FBXObjectType.Root; 9 | 10 | public override FBXObjectType Type => FType; 11 | 12 | public override bool SupportsAttribute => false; 13 | 14 | internal Root(IScene scene) : base(null, scene) 15 | { 16 | Name = "RootNode"; 17 | } 18 | 19 | public override Connection[] GetConnections() 20 | { 21 | if (Children.Count == 0) return Array.Empty(); 22 | 23 | var connections = new Connection[Children.Count]; 24 | 25 | for (var i = 0; i < connections.Length; ++i) 26 | connections[i] = new Connection(Connection.ConnectionType.Object, Children[i].GetHashCode(), 0L); 27 | 28 | return connections; 29 | } 30 | 31 | public override IElement AsElement(bool binary) 32 | { 33 | throw new NotSupportedException("Root nodes cannot be serialized"); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/Shape.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FBXSharp.Core; 4 | using FBXSharp.ValueTypes; 5 | 6 | namespace FBXSharp.Objective 7 | { 8 | public class Shape : FBXObject 9 | { 10 | private int[] m_indices; 11 | private Vector3[] m_vertices; 12 | private Vector3[] m_normals; 13 | 14 | public static readonly FBXObjectType FType = FBXObjectType.Shape; 15 | 16 | public static readonly FBXClassType FClass = FBXClassType.Geometry; 17 | 18 | public override FBXObjectType Type => FType; 19 | 20 | public override FBXClassType Class => FClass; 21 | 22 | public IReadOnlyList Indices => m_indices; 23 | 24 | public IReadOnlyList Vertices => m_vertices; 25 | 26 | public IReadOnlyList Normals => m_normals; 27 | 28 | internal Shape(IElement element, IScene scene) : base(element, scene) 29 | { 30 | m_indices = Array.Empty(); 31 | m_vertices = Array.Empty(); 32 | m_normals = Array.Empty(); 33 | 34 | if (element is null) return; 35 | 36 | var indices = element.FindChild("Indexes"); 37 | 38 | if (!(indices is null) && indices.Attributes.Length > 0 && 39 | indices.Attributes[0].Type == IElementAttributeType.ArrayInt32) 40 | _ = ElementaryFactory.ToInt32Array(indices.Attributes[0], out m_indices); 41 | 42 | var vertexs = element.FindChild("Vertices"); 43 | 44 | if (!(vertexs is null) && vertexs.Attributes.Length > 0 && 45 | vertexs.Attributes[0].Type == IElementAttributeType.ArrayDouble) 46 | _ = ElementaryFactory.ToVector3Array(vertexs.Attributes[0], out m_vertices); 47 | 48 | var normals = element.FindChild("Normals"); 49 | 50 | if (!(normals is null) && normals.Attributes.Length > 0 && 51 | normals.Attributes[0].Type == IElementAttributeType.ArrayDouble) 52 | _ = ElementaryFactory.ToVector3Array(normals.Attributes[0], out m_normals); 53 | } 54 | 55 | public void SetupMorphTarget(int[] indices, Vector3[] vertices, Vector3[] normals) 56 | { 57 | if (indices is null || vertices is null || normals is null) 58 | { 59 | m_indices = Array.Empty(); 60 | m_normals = Array.Empty(); 61 | m_vertices = Array.Empty(); 62 | 63 | return; 64 | } 65 | 66 | if (indices.Length != vertices.Length || indices.Length != vertices.Length) 67 | throw new Exception("Indices, vertices, and normals arrays should all have the same length"); 68 | 69 | m_indices = indices; 70 | m_vertices = vertices; 71 | m_normals = normals; 72 | } 73 | 74 | public override IElement AsElement(bool binary) 75 | { 76 | var elements = new IElement[5]; 77 | 78 | var indexes = new int[m_indices.Length]; 79 | var vertexs = new double[m_vertices.Length * 3]; 80 | var normals = new double[m_normals.Length * 3]; 81 | 82 | Array.Copy(m_indices, indexes, indexes.Length); 83 | 84 | for (int i = 0, k = 0; i < m_vertices.Length; ++i) 85 | { 86 | var vector = m_vertices[i]; 87 | 88 | vertexs[k++] = vector.X; 89 | vertexs[k++] = vector.Y; 90 | vertexs[k++] = vector.Z; 91 | } 92 | 93 | for (int i = 0, k = 0; i < m_normals.Length; ++i) 94 | { 95 | var vector = m_normals[i]; 96 | 97 | normals[k++] = vector.X; 98 | normals[k++] = vector.Y; 99 | normals[k++] = vector.Z; 100 | } 101 | 102 | elements[0] = BuildProperties70(); 103 | elements[1] = Element.WithAttribute("Version", ElementaryFactory.GetElementAttribute(100)); 104 | elements[2] = Element.WithAttribute("Indexes", ElementaryFactory.GetElementAttribute(indexes)); 105 | elements[3] = Element.WithAttribute("Vertices", ElementaryFactory.GetElementAttribute(vertexs)); 106 | elements[4] = Element.WithAttribute("Normals", ElementaryFactory.GetElementAttribute(normals)); 107 | 108 | return new Element(Class.ToString(), elements, BuildAttributes("Geometry", Type.ToString(), binary)); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /FBXSharp/Objective/Skin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FBXSharp.Core; 4 | 5 | namespace FBXSharp.Objective 6 | { 7 | public class Skin : FBXObject 8 | { 9 | private readonly List m_clusters; 10 | 11 | public static readonly FBXObjectType FType = FBXObjectType.Skin; 12 | 13 | public static readonly FBXClassType FClass = FBXClassType.Deformer; 14 | 15 | public override FBXObjectType Type => FType; 16 | 17 | public override FBXClassType Class => FClass; 18 | 19 | public double DeformAccuracy { get; set; } 20 | 21 | public IReadOnlyList Clusters => m_clusters; 22 | 23 | internal Skin(IElement element, IScene scene) : base(element, scene) 24 | { 25 | m_clusters = new List(); 26 | 27 | if (element is null) return; 28 | 29 | var deformAccuracy = element.FindChild("Link_DeformAcuracy"); 30 | 31 | if (deformAccuracy is null || deformAccuracy.Attributes.Length == 0 || 32 | deformAccuracy.Attributes[0].Type != IElementAttributeType.Double) return; 33 | 34 | DeformAccuracy = Convert.ToDouble(deformAccuracy.Attributes[0].GetElementValue()); 35 | } 36 | 37 | public void AddCluster(Cluster cluster) 38 | { 39 | AddClusterAt(cluster, m_clusters.Count); 40 | } 41 | 42 | public void RemoveCluster(Cluster cluster) 43 | { 44 | if (cluster is null || cluster.Scene != Scene) return; 45 | 46 | _ = m_clusters.Remove(cluster); 47 | } 48 | 49 | public void AddClusterAt(Cluster cluster, int index) 50 | { 51 | if (cluster is null) return; 52 | 53 | if (cluster.Scene != Scene) throw new Exception("Cluster should share same scene with skin"); 54 | 55 | if (index < 0 || index > m_clusters.Count) 56 | throw new ArgumentOutOfRangeException("Index should be in range 0 to cluster count inclusively"); 57 | 58 | m_clusters.Insert(index, cluster); 59 | } 60 | 61 | public void RemoveClusterAt(int index) 62 | { 63 | if (index < 0 || index >= m_clusters.Count) 64 | throw new ArgumentOutOfRangeException("Index should be in 0 to cluster count range"); 65 | 66 | m_clusters.RemoveAt(index); 67 | } 68 | 69 | public override Connection[] GetConnections() 70 | { 71 | if (m_clusters.Count == 0) return Array.Empty(); 72 | 73 | var thisHashKey = GetHashCode(); 74 | var connections = new Connection[m_clusters.Count]; 75 | 76 | for (var i = 0; i < connections.Length; ++i) 77 | connections[i] = new Connection(Connection.ConnectionType.Object, m_clusters[i].GetHashCode(), 78 | thisHashKey); 79 | 80 | return connections; 81 | } 82 | 83 | public override void ResolveLink(FBXObject linker, IElementAttribute attribute) 84 | { 85 | if (linker.Class == FBXClassType.Deformer && linker.Type == FBXObjectType.Cluster) 86 | AddCluster(linker as Cluster); 87 | } 88 | 89 | public override IElement AsElement(bool binary) 90 | { 91 | var hasAnyProperties = Properties.Count != 0; 92 | 93 | var elements = new IElement[2 + (hasAnyProperties ? 1 : 0)]; 94 | 95 | elements[0] = Element.WithAttribute("Version", ElementaryFactory.GetElementAttribute(101)); 96 | elements[1] = Element.WithAttribute("Link_DeformAcuracy", 97 | ElementaryFactory.GetElementAttribute(DeformAccuracy)); 98 | 99 | if (hasAnyProperties) elements[2] = BuildProperties70(); 100 | 101 | return new Element(Class.ToString(), elements, BuildAttributes("Deformer", Type.ToString(), binary)); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /FBXSharp/TakeInfo.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp 2 | { 3 | public class TakeInfo 4 | { 5 | public string Name { get; } 6 | public string Filename { get; } 7 | public double LocalTimeFrom { get; } 8 | public double LocalTimeTo { get; } 9 | public double ReferenceTimeFrom { get; } 10 | public double ReferenceTimeTo { get; } 11 | 12 | public TakeInfo(string name, string filename, double localFrom, double localTo, double refFrom, double refTo) 13 | { 14 | Name = name ?? string.Empty; 15 | Filename = filename ?? string.Empty; 16 | LocalTimeFrom = localFrom; 17 | LocalTimeTo = localTo; 18 | ReferenceTimeFrom = refFrom; 19 | ReferenceTimeTo = refTo; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return Name; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /FBXSharp/TemplateObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FBXSharp.Core; 3 | 4 | namespace FBXSharp 5 | { 6 | public enum TemplateCreationType 7 | { 8 | DontCreateIfDuplicated, 9 | MergeIfExistingIsFound, 10 | NewOverrideAnyExisting 11 | } 12 | 13 | public class TemplateObject : FBXObject 14 | { 15 | public static readonly FBXObjectType FType = FBXObjectType.Template; 16 | 17 | public static readonly FBXClassType FClass = FBXClassType.Template; 18 | 19 | public override FBXObjectType Type => FType; 20 | 21 | public override FBXClassType Class => FClass; 22 | 23 | public FBXClassType OverridableType { get; } 24 | 25 | public TemplateObject(FBXClassType type, IElement element, IScene scene) : base(null, scene) 26 | { 27 | OverridableType = type; 28 | 29 | Initialize(element); 30 | } 31 | 32 | public void Initialize(IElement element) 33 | { 34 | if (element is null) return; 35 | 36 | RemoveAllProperties(); 37 | 38 | if (element.Attributes.Length > 0 && element.Attributes[0].Type == IElementAttributeType.String) 39 | Name = element.Attributes[0].GetElementValue().ToString(); 40 | 41 | ParseProperties70(element); 42 | } 43 | 44 | public void MergeWith(TemplateObject template) 45 | { 46 | if (template is null) throw new ArgumentNullException("Merger template passed cannot be null"); 47 | 48 | if (template.OverridableType != OverridableType) 49 | throw new ArgumentException( 50 | $"Cannot merge template of type {OverridableType} with template of type {template.OverridableType}"); 51 | 52 | foreach (var property in template.Properties) AddProperty(property); 53 | } 54 | 55 | public override IElement AsElement(bool binary) 56 | { 57 | throw new NotSupportedException("Templates cannot be serialized"); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/BinaryBlob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | public class BinaryBlob 6 | { 7 | private object[] m_datas; 8 | 9 | public object[] Datas 10 | { 11 | get => m_datas; 12 | set => m_datas = value ?? Array.Empty(); 13 | } 14 | 15 | public BinaryBlob() 16 | { 17 | m_datas = Array.Empty(); 18 | } 19 | 20 | public BinaryBlob(object[] datas) 21 | { 22 | m_datas = datas ?? Array.Empty(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Color.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct ColorRGBA 7 | { 8 | public const int SizeOf = 0x20; 9 | 10 | public double R; 11 | public double G; 12 | public double B; 13 | public double A; 14 | 15 | public ColorRGBA(double r, double g, double b, double a = 1.0) 16 | { 17 | R = r; 18 | G = g; 19 | B = b; 20 | A = a; 21 | } 22 | 23 | public ColorRGBA(in Vector3 vector) : this(vector.X, vector.Y, vector.Z) 24 | { 25 | } 26 | 27 | public ColorRGBA(in Vector4 vector) : this(vector.X, vector.Y, vector.Z, vector.W) 28 | { 29 | } 30 | 31 | public static implicit operator Vector4(in ColorRGBA color) 32 | { 33 | return new Vector4(color.R, color.G, color.B, color.A); 34 | } 35 | } 36 | 37 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 38 | public struct ColorRGB 39 | { 40 | public const int SizeOf = 0x18; 41 | 42 | public double R; 43 | public double G; 44 | public double B; 45 | 46 | public ColorRGB(double r, double g, double b) 47 | { 48 | R = r; 49 | G = g; 50 | B = b; 51 | } 52 | 53 | public ColorRGB(in Vector3 vector) : this(vector.X, vector.Y, vector.Z) 54 | { 55 | } 56 | 57 | public ColorRGB(in Vector4 vector) : this(vector.X, vector.Y, vector.Z) 58 | { 59 | } 60 | 61 | public static implicit operator Vector3(in ColorRGB color) 62 | { 63 | return new Vector3(color.R, color.G, color.B); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Distance.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.ValueTypes 2 | { 3 | public struct Distance 4 | { 5 | public float Value; 6 | public string Unit; 7 | 8 | public Distance(float value, string unit) 9 | { 10 | Value = value; 11 | Unit = unit; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Enumeration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | public class Enumeration 6 | { 7 | private string[] m_flags; 8 | 9 | public int Value { get; set; } 10 | 11 | public string[] Flags 12 | { 13 | get => m_flags; 14 | set => m_flags = value ?? Array.Empty(); 15 | } 16 | 17 | public Enumeration() 18 | { 19 | Value = 0; 20 | m_flags = Array.Empty(); 21 | } 22 | 23 | public Enumeration(int value) 24 | { 25 | Value = value; 26 | m_flags = Array.Empty(); 27 | } 28 | 29 | public Enumeration(string[] flags) 30 | { 31 | Value = 0; 32 | m_flags = flags ?? Array.Empty(); 33 | } 34 | 35 | public Enumeration(int value, string[] flags) 36 | { 37 | Value = value; 38 | m_flags = flags ?? Array.Empty(); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Half.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Half 7 | { 8 | [StructLayout(LayoutKind.Explicit, Pack = 1)] 9 | private ref struct Union 10 | { 11 | [FieldOffset(0)] public uint UInt; 12 | 13 | [FieldOffset(0)] public float Float; 14 | 15 | public Union(float value) 16 | { 17 | UInt = 0; 18 | Float = value; 19 | } 20 | 21 | public Union(uint value) 22 | { 23 | Float = 0.0f; 24 | UInt = value; 25 | } 26 | } 27 | 28 | public const int SizeOf = 0x02; 29 | 30 | public readonly ushort Value; 31 | 32 | public Half(short value) 33 | { 34 | Value = (ushort)value; 35 | } 36 | 37 | public Half(ushort value) 38 | { 39 | Value = value; 40 | } 41 | 42 | public Half(float value) 43 | { 44 | var union = new Union(value); 45 | 46 | var sign = union.UInt >> 31; 47 | var expt = union.UInt & 0x7F800000; 48 | var mant = union.UInt & 0x007FFFFF; 49 | 50 | if (expt < 0x47800000u) 51 | { 52 | if (expt > 0x38000000u) 53 | Value = (ushort)((mant >> 13) | ((expt - 0x38000000u) >> 13) | (sign << 15)); 54 | else 55 | Value = (ushort)((mant >> (int)(((0x38000000u - expt) >> 23) + 14u)) | (sign << 15)); 56 | } 57 | else 58 | { 59 | var max = mant != 0u && expt == 0x7F800000u ? 0x007FFFFFu : 0u; 60 | 61 | Value = (ushort)((max >> 13) | (sign << 15) | 0x7C00u); 62 | } 63 | } 64 | 65 | public float ToSingle() 66 | { 67 | var expt = Value & 0x7C00u; 68 | var mant = Value & 0x3FFu; 69 | 70 | if (expt == 0x7C00u) 71 | { 72 | expt = 0x7F800000u; 73 | 74 | if ((Value & 0x3FFu) != 0) mant = 0x7FFFFFu; 75 | } 76 | else if ((Value & 0x7C00u) != 0) 77 | { 78 | mant <<= 13; 79 | expt = (expt << 13) + 0x38000000u; 80 | } 81 | else if ((Value & 0x3FF) != 0) 82 | { 83 | var mu = mant << 1; 84 | expt = 0x38000000u; 85 | 86 | while ((mu & 0x400u) == 0) 87 | { 88 | mu <<= 1; 89 | expt -= 0x800000u; 90 | } 91 | 92 | mant = (mu & 0x3FFu) << 13; 93 | } 94 | 95 | return new Union(mant | expt | (((uint)Value >> 15) << 31)).Float; 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Quaternion.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Quaternion 7 | { 8 | public const int SizeOf = 0x20; 9 | 10 | public double X; 11 | public double Y; 12 | public double Z; 13 | public double W; 14 | } 15 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Reference.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.ValueTypes 2 | { 3 | public struct Reference 4 | { 5 | // yeah uh, fbx does not read those, soooo ??? 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/RotationOrder.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.ValueTypes 2 | { 3 | public enum RotationOrder 4 | { 5 | XYZ, 6 | XZY, 7 | YZX, 8 | YXZ, 9 | ZXY, 10 | ZYX, 11 | SPHERIC // Currently unsupported. Treated as EULER_XYZ. 12 | } 13 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/TimeBase.cs: -------------------------------------------------------------------------------- 1 | namespace FBXSharp.ValueTypes 2 | { 3 | public struct TimeBase 4 | { 5 | public const int SizeOf = 0x08; 6 | 7 | public double Time; 8 | 9 | public TimeBase(double time) 10 | { 11 | Time = time; 12 | } 13 | 14 | public TimeBase(long time) 15 | { 16 | Time = MathExtensions.FBXTimeToSeconds(time); 17 | } 18 | 19 | public long ToLong() 20 | { 21 | return MathExtensions.SecondsToFBXTime(Time); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Vector2.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Vector2 7 | { 8 | public const int SizeOf = 0x10; 9 | 10 | public double X; 11 | public double Y; 12 | 13 | public Vector2(double x, double y) 14 | { 15 | X = x; 16 | Y = y; 17 | } 18 | 19 | public static Vector2 operator -(in Vector2 vector) 20 | { 21 | return new Vector2(-vector.X, -vector.Y); 22 | } 23 | 24 | public static Vector2 operator +(in Vector2 a, in Vector2 b) 25 | { 26 | return new Vector2(a.X + b.X, a.Y + b.Y); 27 | } 28 | 29 | public static Vector2 operator -(in Vector2 a, in Vector2 b) 30 | { 31 | return new Vector2(a.X - b.X, a.Y - b.Y); 32 | } 33 | 34 | public static bool operator ==(in Vector2 lhs, in Vector2 rhs) 35 | { 36 | return lhs.X == rhs.X && lhs.Y == rhs.Y; 37 | } 38 | 39 | public static bool operator !=(in Vector2 lhs, in Vector2 rhs) 40 | { 41 | return lhs.X != rhs.X || lhs.Y != rhs.Y; 42 | } 43 | 44 | public override bool Equals(object obj) 45 | { 46 | return obj is Vector2 vector && this == vector; 47 | } 48 | 49 | public override int GetHashCode() 50 | { 51 | return (X, Y).GetHashCode(); 52 | } 53 | 54 | public override string ToString() 55 | { 56 | return $"<{X}, {Y}>"; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Vector3.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Vector3 7 | { 8 | public const int SizeOf = 0x18; 9 | 10 | public static readonly Vector3 Zero = new Vector3(0.0, 0.0, 0.0); 11 | public static readonly Vector3 One = new Vector3(1.0, 1.0, 1.0); 12 | 13 | public double X; 14 | public double Y; 15 | public double Z; 16 | 17 | public Vector3(double x, double y, double z) 18 | { 19 | X = x; 20 | Y = y; 21 | Z = z; 22 | } 23 | 24 | public static Vector3 operator -(in Vector3 vector) 25 | { 26 | return new Vector3(-vector.X, -vector.Y, -vector.Z); 27 | } 28 | 29 | public static Vector3 operator +(in Vector3 a, in Vector3 b) 30 | { 31 | return new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); 32 | } 33 | 34 | public static Vector3 operator -(in Vector3 a, in Vector3 b) 35 | { 36 | return new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); 37 | } 38 | 39 | public static bool operator ==(in Vector3 lhs, in Vector3 rhs) 40 | { 41 | return lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z; 42 | } 43 | 44 | public static bool operator !=(in Vector3 lhs, in Vector3 rhs) 45 | { 46 | return lhs.X != rhs.X || lhs.Y != rhs.Y || lhs.Z != rhs.Z; 47 | } 48 | 49 | public override bool Equals(object obj) 50 | { 51 | return obj is Vector3 vector && this == vector; 52 | } 53 | 54 | public override int GetHashCode() 55 | { 56 | return (X, Y, Z).GetHashCode(); 57 | } 58 | 59 | public override string ToString() 60 | { 61 | return $"<{X}, {Y}, {Z}>"; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /FBXSharp/ValueTypes/Vector4.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace FBXSharp.ValueTypes 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Vector4 7 | { 8 | public const int SizeOf = 0x20; 9 | 10 | public double X; 11 | public double Y; 12 | public double Z; 13 | public double W; 14 | 15 | public Vector4(double x, double y, double z, double w = 1.0) 16 | { 17 | X = x; 18 | Y = y; 19 | Z = z; 20 | W = w; 21 | } 22 | 23 | public static Vector4 operator -(in Vector4 vector) 24 | { 25 | return new Vector4(-vector.X, -vector.Y, -vector.Z, -vector.W); 26 | } 27 | 28 | public static Vector4 operator +(in Vector4 a, in Vector4 b) 29 | { 30 | return new Vector4(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); 31 | } 32 | 33 | public static Vector4 operator -(in Vector4 a, in Vector4 b) 34 | { 35 | return new Vector4(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W); 36 | } 37 | 38 | public static bool operator ==(in Vector4 lhs, in Vector4 rhs) 39 | { 40 | return lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z && lhs.W == rhs.W; 41 | } 42 | 43 | public static bool operator !=(in Vector4 lhs, in Vector4 rhs) 44 | { 45 | return lhs.X != rhs.X || lhs.Y != rhs.Y || lhs.Z != rhs.Z || lhs.W != rhs.W; 46 | } 47 | 48 | public override bool Equals(object obj) 49 | { 50 | return obj is Vector4 vector && this == vector; 51 | } 52 | 53 | public override int GetHashCode() 54 | { 55 | return (X, Y, Z, W).GetHashCode(); 56 | } 57 | 58 | public override string ToString() 59 | { 60 | return $"<{X}, {Y}, {Z}, {W}>"; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /NFS ModTools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{6FD41920-62D3-462E-B32E-EB3F76206896}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChunkView", "ChunkView\ChunkView.csproj", "{28D87232-2535-451A-A18D-31BC93F3C030}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetDumper", "AssetDumper\AssetDumper.csproj", "{60DFDF76-BBE3-4B5E-9DCE-AE46498216D4}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Viewer", "Viewer\Viewer.csproj", "{F83C7766-12F3-4619-B1C9-74C6241FF1FA}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Windows", "Common.Windows\Common.Windows.csproj", "{9052B02B-55E7-4806-9F83-237ACE7A9203}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FBXSharp", "FBXSharp\FBXSharp.csproj", "{35FA8B81-9AA0-4485-9FBF-1B1E0C4B67CF}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {60DFDF76-BBE3-4B5E-9DCE-AE46498216D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {60DFDF76-BBE3-4B5E-9DCE-AE46498216D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {60DFDF76-BBE3-4B5E-9DCE-AE46498216D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {60DFDF76-BBE3-4B5E-9DCE-AE46498216D4}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {28D87232-2535-451A-A18D-31BC93F3C030}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {28D87232-2535-451A-A18D-31BC93F3C030}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {28D87232-2535-451A-A18D-31BC93F3C030}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {28D87232-2535-451A-A18D-31BC93F3C030}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {6FD41920-62D3-462E-B32E-EB3F76206896}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {6FD41920-62D3-462E-B32E-EB3F76206896}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {6FD41920-62D3-462E-B32E-EB3F76206896}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {6FD41920-62D3-462E-B32E-EB3F76206896}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {9052B02B-55E7-4806-9F83-237ACE7A9203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {9052B02B-55E7-4806-9F83-237ACE7A9203}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {9052B02B-55E7-4806-9F83-237ACE7A9203}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {9052B02B-55E7-4806-9F83-237ACE7A9203}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {F83C7766-12F3-4619-B1C9-74C6241FF1FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {F83C7766-12F3-4619-B1C9-74C6241FF1FA}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {F83C7766-12F3-4619-B1C9-74C6241FF1FA}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {F83C7766-12F3-4619-B1C9-74C6241FF1FA}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {35FA8B81-9AA0-4485-9FBF-1B1E0C4B67CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {35FA8B81-9AA0-4485-9FBF-1B1E0C4B67CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {35FA8B81-9AA0-4485-9FBF-1B1E0C4B67CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {35FA8B81-9AA0-4485-9FBF-1B1E0C4B67CF}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {2A5B2EAF-B0E4-44EC-BFB1-8D6950A76FC4} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /Viewer/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Viewer/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace Viewer 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Viewer/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Viewer.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Viewer.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Viewer/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Viewer/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Viewer.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Viewer/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Viewer/RenderManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Windows.Media.Media3D; 5 | using Common.Geometry.Data; 6 | using Common.Textures.Data; 7 | using HelixToolkit.SharpDX.Core; 8 | using HelixToolkit.Wpf.SharpDX; 9 | using SharpDX; 10 | using DiffuseMaterial = HelixToolkit.Wpf.SharpDX.DiffuseMaterial; 11 | using Geometry3D = HelixToolkit.SharpDX.Core.Geometry3D; 12 | 13 | namespace Viewer 14 | { 15 | public class RenderManager 16 | { 17 | private readonly Color4 _missingTextureColor = new(0xffff03e6); 18 | private readonly Dictionary _textureMaterials = new(); 19 | 20 | public ObservableConcurrentDictionary CurrentElements { get; } = 21 | new(); 22 | 23 | public void Reset() 24 | { 25 | _textureMaterials.Clear(); 26 | foreach (var keyValuePair in CurrentElements.ToList()) 27 | { 28 | CurrentElements.Remove(keyValuePair.Key); 29 | } 30 | } 31 | 32 | public void EnableTexture(Texture texture) 33 | { 34 | var stream = new MemoryStream(); 35 | texture.GenerateImage(stream); 36 | 37 | #if DEBUG 38 | File.WriteAllBytes($"RenderTexDebug_{texture.Name}_0x{texture.TexHash:X8}.dds", stream.ToArray()); 39 | #endif 40 | 41 | if (!_textureMaterials.TryGetValue(texture.TexHash, out var material)) 42 | { 43 | _textureMaterials[texture.TexHash] = 44 | new DiffuseMaterial { DiffuseMap = TextureModel.Create(stream) }; 45 | } 46 | else 47 | { 48 | material.DiffuseMap = TextureModel.Create(stream); 49 | material.DiffuseColor = Color4.White; 50 | } 51 | } 52 | 53 | public void DisableTexture(Texture texture) 54 | { 55 | _textureMaterials[texture.TexHash].DiffuseMap = null; 56 | _textureMaterials[texture.TexHash].DiffuseColor = _missingTextureColor; 57 | } 58 | 59 | public void EnableSolid(SolidObject solidObject) 60 | { 61 | CurrentElements.Add(solidObject, CreateSolidElement(solidObject)); 62 | } 63 | 64 | public void DisableSolid(SolidObject solidObject) 65 | { 66 | CurrentElements.Remove(solidObject); 67 | } 68 | 69 | private Element3D CreateSolidElement(SolidObject solid) 70 | { 71 | return new ItemsModel3D 72 | { 73 | Transform = new Transform3DGroup 74 | { 75 | Children = new Transform3DCollection 76 | { 77 | new MatrixTransform3D(new Matrix3D( 78 | solid.PivotMatrix.M11, solid.PivotMatrix.M12, solid.PivotMatrix.M13, solid.PivotMatrix.M14, 79 | solid.PivotMatrix.M21, solid.PivotMatrix.M22, solid.PivotMatrix.M23, solid.PivotMatrix.M24, 80 | solid.PivotMatrix.M31, solid.PivotMatrix.M32, solid.PivotMatrix.M33, solid.PivotMatrix.M34, 81 | solid.PivotMatrix.M41, solid.PivotMatrix.M42, solid.PivotMatrix.M43, solid.PivotMatrix.M44)) 82 | } 83 | }, 84 | ItemsSource = new List(solid.Materials.Select(mat => CreateMeshElement(solid, mat))) 85 | }; 86 | } 87 | 88 | private Element3D CreateMeshElement(SolidObject solidObject, SolidObjectMaterial material) 89 | { 90 | var model = new MeshGeometryModel3D(); 91 | model.Geometry = CreateMeshGeometry(solidObject, material); 92 | 93 | // If we already have a material for the texture, use it 94 | if (_textureMaterials.TryGetValue(material.DiffuseTextureHash, out var textureMaterial)) 95 | { 96 | model.Material = textureMaterial; 97 | } 98 | else 99 | { 100 | model.Material = _textureMaterials[material.DiffuseTextureHash] = new DiffuseMaterial 101 | { 102 | DiffuseColor = _missingTextureColor 103 | }; 104 | } 105 | 106 | return model; 107 | } 108 | 109 | private static Geometry3D CreateMeshGeometry(SolidObject solidObject, SolidObjectMaterial material) 110 | { 111 | var faces = new List(); 112 | 113 | for (var j = 0; j < material.Indices.Length; j += 3) 114 | { 115 | var idx1 = material.Indices[j]; 116 | var idx2 = material.Indices[j + 1]; 117 | var idx3 = material.Indices[j + 2]; 118 | 119 | faces.Add(new int[] { idx1, idx2, idx3 }); 120 | } 121 | 122 | var materialVerts = solidObject.VertexSets[material.VertexSetIndex]; 123 | var meshBuilder = new MeshBuilder(); 124 | 125 | foreach (var face in faces) 126 | { 127 | meshBuilder.AddTriangle(face); 128 | } 129 | 130 | foreach (var vertex in materialVerts) 131 | { 132 | meshBuilder.Positions.Add(new Vector3(vertex.Position.X, vertex.Position.Y, vertex.Position.Z)); 133 | meshBuilder.TextureCoordinates.Add(new Vector2(vertex.TexCoords.X, vertex.TexCoords.Y)); 134 | } 135 | 136 | meshBuilder.ComputeNormalsAndTangents(MeshFaces.Default); 137 | 138 | return meshBuilder.ToMesh(); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /Viewer/Viewer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0-windows 4 | WinExe 5 | false 6 | true 7 | true 8 | true 9 | Debug;Release 10 | AnyCPU 11 | 12 | 13 | nfsw_IDI_ICON1.ico 14 | 15 | 16 | 17 | False 18 | ..\..\libdevil\projects\DotNet\Debug\DevIL.NET.dll 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Viewer/nfsw_IDI_ICON1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NFSTools/NFS-ModTools/897c170eececb6f0f31c1925c2db4160d135c3b5/Viewer/nfsw_IDI_ICON1.ico --------------------------------------------------------------------------------