├── .github
└── workflows
│ └── CD.yaml
├── .gitignore
├── App.config
├── ApplicationModule.cs
├── Controllers
├── AbstractExportController.cs
├── ConfigController.cs
├── FileOpenController.cs
├── GUI
│ ├── AnimationController.cs
│ ├── CameraController.cs
│ ├── GUIExportController.cs
│ ├── GUILBDController.cs
│ ├── GUIMOMController.cs
│ ├── GUITIMController.cs
│ ├── GUITIXController.cs
│ ├── GUITMDController.cs
│ ├── TreeController.cs
│ └── VRAMController.cs
├── Headless
│ ├── HeadlessConfigController.cs
│ ├── HeadlessExportController.cs
│ ├── HeadlessLBDController.cs
│ ├── HeadlessMOMController.cs
│ ├── HeadlessTIMController.cs
│ ├── HeadlessTIXController.cs
│ ├── HeadlessTMDController.cs
│ └── HeadlessVRAMController.cs
├── Interface
│ ├── IAnimationController.cs
│ ├── ICameraController.cs
│ ├── IConfigController.cs
│ ├── IExportController.cs
│ ├── IFileFormatController.cs
│ ├── IFileOpenController.cs
│ ├── ITreeController.cs
│ ├── IUpdateCheckerController.cs
│ ├── IVRAMController.cs
│ └── MeshExportFormat.cs
└── UpdateCheckerController.cs
├── Fonts
├── IconsFontAwesome5.cs
├── fa-regular-400.ttf
└── fa-solid-900.ttf
├── GUI
├── Components
│ ├── AnimatedMeshListTreeNode.cs
│ ├── ApplicationArea.cs
│ ├── Columns.cs
│ ├── ContextMenu.cs
│ ├── FileDialog.cs
│ ├── FramebufferArea.cs
│ ├── GenericDialog.cs
│ ├── InfoDialog.cs
│ ├── LBDTileTreeNode.cs
│ ├── MainMenuBar.cs
│ ├── MeshListTreeNode.cs
│ ├── Modal.cs
│ ├── TreeNode.cs
│ └── TreeView.cs
├── ImGuiComponent.cs
└── ImGuiRenderer.cs
├── Graphics
├── Camera.cs
├── Framebuffer.cs
├── GLBuffer.cs
├── Headless
│ ├── HeadlessMesh.cs
│ ├── HeadlessTexture2D.cs
│ └── HeadlessVertexArray.cs
├── IBindable.cs
├── IDisposable.cs
├── IRenderable.cs
├── ITexture2D.cs
├── IVertexArray.cs
├── IVertexArrayExtensions.cs
├── Material.cs
├── Mesh.cs
├── Shader.cs
├── Texture2D.cs
├── Vertex.cs
├── VertexArray.cs
└── VertexAttrib.cs
├── GuiApplication.cs
├── Headless
├── AbstractHeadlessCommand.cs
├── ExportLevelCommand.cs
├── GlobalOptions.cs
├── HeadlessException.cs
└── IHeadlessCommand.cs
├── HeadlessApplication.cs
├── LICENSE
├── LSDView.csproj
├── LSDView.sln
├── Math
├── Convert.cs
└── Transform.cs
├── Models
├── AbstractDocument.cs
├── Dream.cs
├── IDocument.cs
├── LBDDocument.cs
├── LSDViewConfig.cs
├── MOMDocument.cs
├── TIMDocument.cs
├── TIXDocument.cs
└── TMDDocument.cs
├── OpenTK.dll.config
├── Program.cs
├── Properties
└── AssemblyInfo.cs
├── README.md
├── Shaders
├── basic.frag
├── basic.vert
├── texture.frag
├── texture.vert
├── untextured.frag
└── untextured.vert
├── Util
├── ConsoleUtil.cs
├── ImageUtil.cs
├── LibLSDUtil.cs
├── LoggingUtil.cs
├── MeshUtil.cs
├── ObjBuilder.cs
├── PathUtil.cs
└── PlyBuilder.cs
├── Version.cs
├── appicon.ico
├── img
├── correct-shading.png
├── screenshot.png
├── split-normals.png
└── strange-shading.png
└── packages.config
/.github/workflows/CD.yaml:
--------------------------------------------------------------------------------
1 | name: CD
2 |
3 | on:
4 | release:
5 | types: [ created ]
6 |
7 | jobs:
8 | buildAndPush:
9 | runs-on: windows-latest
10 | steps:
11 | - uses: actions/checkout@v1
12 | name: Checkout Code
13 | - name: Extract tag name
14 | run: |
15 | echo "GIT_TAG=$(git tag --sort=-creatordate | head -n 1)" >> $env:GITHUB_ENV
16 | - name: Inject version number
17 | uses: cschleiden/replace-tokens@v1.0
18 | with:
19 | files: "**"
20 | env:
21 | VERSION: ${{ env.GIT_TAG }}
22 | - name: Add msbuild to PATH
23 | uses: microsoft/setup-msbuild@v1.0.2
24 | - name: Setup NuGet
25 | uses: nuget/setup-nuget@v1
26 | with:
27 | nuget-version: '5.x'
28 | - name: Restore NuGet packages
29 | run: nuget restore
30 | - name: Build solution
31 | run: msbuild LSDView.sln /p:Configuration=Release
32 | - name: Zip build
33 | run: Compress-Archive -Path bin/Release/* -DestinationPath LSDView.zip
34 | - name: Get Release
35 | id: get-release
36 | uses: bruceadams/get-release@v1.2.0
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | - name: Upload Release Asset
40 | uses: actions/upload-release-asset@v1
41 | env:
42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43 | with:
44 | upload_url: ${{ steps.get-release.outputs.upload_url }}
45 | asset_path: LSDView.zip
46 | asset_name: LSDView.zip
47 | asset_content_type: application/zip
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
290 | /TestFiles
291 |
--------------------------------------------------------------------------------
/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ApplicationModule.cs:
--------------------------------------------------------------------------------
1 | using Autofac;
2 | using libLSD.Formats;
3 | using LSDView.Controller;
4 | using LSDView.Controllers;
5 | using LSDView.Controllers.GUI;
6 | using LSDView.Controllers.Headless;
7 | using LSDView.Controllers.Interface;
8 | using LSDView.Models;
9 |
10 | namespace LSDView
11 | {
12 | public class ApplicationModule : Module
13 | {
14 | public bool Headless { get; set; }
15 |
16 | protected override void Load(ContainerBuilder builder)
17 | {
18 | builder.RegisterType().AsSelf().As();
19 | builder.RegisterType().AsSelf().As();
20 | builder.RegisterType().AsSelf().As();
21 |
22 | if (Headless)
23 | {
24 | registerHeadlessTypes(builder);
25 | }
26 | else
27 | {
28 | registerGUITypes(builder);
29 | }
30 | }
31 |
32 | protected void registerGUITypes(ContainerBuilder builder)
33 | {
34 | builder.RegisterType().AsSelf().As().SingleInstance();
35 | builder.RegisterType().AsSelf().As().SingleInstance();
36 | builder.RegisterType().AsSelf().As().SingleInstance();
37 | builder.RegisterType().AsSelf().As>()
38 | .SingleInstance();
39 | builder.RegisterType().AsSelf().As>()
40 | .SingleInstance();
41 | builder.RegisterType().AsSelf().As>()
42 | .SingleInstance();
43 | builder.RegisterType().AsSelf().As>()
44 | .SingleInstance();
45 | builder.RegisterType().AsSelf().As>()
46 | .SingleInstance();
47 | builder.RegisterType().AsSelf().As().SingleInstance();
48 | builder.RegisterType().AsSelf().As().SingleInstance();
49 | }
50 |
51 | protected void registerHeadlessTypes(ContainerBuilder builder)
52 | {
53 | builder.RegisterType().AsSelf().As().SingleInstance();
54 | builder.RegisterType().AsSelf().As>()
55 | .SingleInstance();
56 | builder.RegisterType().AsSelf().As>()
57 | .SingleInstance();
58 | builder.RegisterType().AsSelf().As>()
59 | .SingleInstance();
60 | builder.RegisterType().AsSelf().As>()
61 | .SingleInstance();
62 | builder.RegisterType().AsSelf().As>()
63 | .SingleInstance();
64 | builder.RegisterType().AsSelf().As().SingleInstance();
65 | builder.RegisterType().AsSelf().As().SingleInstance();
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Controllers/AbstractExportController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Drawing.Imaging;
5 | using System.IO;
6 | using libLSD.Formats;
7 | using libLSD.Interfaces;
8 | using LSDView.Controllers.Interface;
9 | using LSDView.Graphics;
10 | using LSDView.Graphics.Headless;
11 | using LSDView.Models;
12 | using LSDView.Util;
13 | using OpenTK;
14 | using Serilog;
15 |
16 | namespace LSDView.Controllers
17 | {
18 | public abstract class AbstractExportController : IExportController
19 | {
20 | ///
21 | /// Export a LibLSD type as it was originally.
22 | ///
23 | /// The LibLSD type (MOM, TIX, TOD, etc).
24 | /// The path to write the file to.
25 | public void ExportOriginal(IWriteable original, string filePath)
26 | {
27 | Log.Information($"Exporting original to: {filePath}");
28 |
29 | using (BinaryWriter bw = new BinaryWriter(File.Open(filePath, FileMode.Create)))
30 | {
31 | original.Write(bw);
32 | }
33 | }
34 |
35 | ///
36 | /// Export a TIM file to a common image format.
37 | ///
38 | /// The TIM file.
39 | /// The index of the CLUT to export with.
40 | /// The file path to export to.
41 | /// The image format to export to.
42 | public void ExportImage(TIM tim, int clutIndex, string filePath, ImageFormat format)
43 | {
44 | Log.Information($"Exporting image ({format}) to: {filePath}");
45 |
46 | var image = LibLSDUtil.GetImageDataFromTIM(tim, clutIndex, flip: false);
47 | Bitmap bmp = ImageUtil.ImageDataToBitmap(image.data, image.width, image.height);
48 | bmp.Save(filePath, format);
49 | }
50 |
51 | ///
52 | /// Export the TIM files in a TIX to common image formats.
53 | ///
54 | /// The TIX file.
55 | /// The file path to export to.
56 | /// Whether or not to separate images in the output.
57 | /// The image format to export to.
58 | public void ExportImages(TIX tix, string filePath, bool separate, ImageFormat format)
59 | {
60 | if (separate)
61 | {
62 | Log.Information($"Exporting images ({format}) in TIX to: {filePath}");
63 |
64 | var allTims = tix.AllTIMs;
65 | for (int i = 0; i < allTims.Count; i++)
66 | {
67 | var fileName = Path.GetFileNameWithoutExtension(filePath);
68 | var ext = Path.GetExtension(filePath);
69 | var dir = Path.GetDirectoryName(filePath);
70 | ExportImage(allTims[i], 0, Path.Combine(dir, $"{fileName}-{i}{ext}"), format);
71 | }
72 | }
73 | else
74 | {
75 | ITexture2D tixTex = LibLSDUtil.TIXToTexture2D(tix, headless: true, flip: false);
76 | ExportTexture(tixTex, filePath, format);
77 | }
78 | }
79 |
80 | public void ExportTexture(ITexture2D tex, string filePath, ImageFormat format)
81 | {
82 | float[] imageData = tex.GetData();
83 | Bitmap bmp = ImageUtil.ImageDataToBitmap(imageData, tex.Width, tex.Height);
84 | bmp.Save(filePath, format);
85 | }
86 |
87 | public void ExportMesh(IRenderable mesh, string filePath, MeshExportFormat format)
88 | {
89 | switch (format)
90 | {
91 | case MeshExportFormat.OBJ:
92 | ExportOBJ(mesh, filePath);
93 | break;
94 | case MeshExportFormat.PLY:
95 | ExportPLY(mesh, filePath);
96 | break;
97 | default:
98 | throw new ArgumentOutOfRangeException(nameof(format), format, null);
99 | }
100 | }
101 |
102 | public void ExportMeshes(IEnumerable meshes, string filePath, MeshExportFormat format)
103 | {
104 | switch (format)
105 | {
106 | case MeshExportFormat.OBJ:
107 | ExportOBJ(meshes, filePath);
108 | break;
109 | case MeshExportFormat.PLY:
110 | ExportPLY(meshes, filePath);
111 | break;
112 | default:
113 | throw new ArgumentOutOfRangeException(nameof(format), format, null);
114 | }
115 | }
116 |
117 | ///
118 | /// Export a renderable mesh to an OBJ file.
119 | ///
120 | /// The mesh to export.
121 | /// The path to export the OBJ file to.
122 | public void ExportOBJ(IRenderable mesh, string filePath)
123 | {
124 | Log.Information($"Exporting OBJ mesh to: {filePath}");
125 |
126 | if (String.IsNullOrEmpty(Path.GetExtension(filePath)))
127 | {
128 | filePath += ".obj";
129 | }
130 |
131 | var objFile = MeshUtil.RenderableToObjFile(mesh);
132 | File.WriteAllText(filePath, objFile);
133 | }
134 |
135 | ///
136 | /// Export a list of renderable meshes to an OBJ file.
137 | ///
138 | /// The meshes to export.
139 | /// The path to export the OBJ file to.
140 | public void ExportOBJ(IEnumerable meshes, string filePath)
141 | {
142 | Log.Information($"Exporting OBJ meshes to: {filePath}");
143 |
144 | if (String.IsNullOrEmpty(Path.GetExtension(filePath)))
145 | {
146 | filePath += ".obj";
147 | }
148 |
149 | var objFile = MeshUtil.RenderableListToObjFile(meshes);
150 | File.WriteAllText(filePath, objFile);
151 | }
152 |
153 |
154 | ///
155 | /// Export a renderable mesh to an PLY file.
156 | ///
157 | /// The mesh to export.
158 | /// The path to export the PLY file to.
159 | public void ExportPLY(IRenderable mesh, string filePath)
160 | {
161 | Log.Information($"Exporting PLY mesh to: {filePath}");
162 |
163 | if (String.IsNullOrEmpty(Path.GetExtension(filePath)))
164 | {
165 | filePath += ".ply";
166 | }
167 |
168 | var plyFile = MeshUtil.RenderableToPlyFile(mesh);
169 | File.WriteAllText(filePath, plyFile);
170 | }
171 |
172 | ///
173 | /// Export a list of renderable meshes to an PLY file.
174 | ///
175 | /// The meshes to export.
176 | /// The path to export the PLY file to.
177 | public void ExportPLY(IEnumerable meshes, string filePath)
178 | {
179 | Log.Information($"Exporting PLY meshes to: {filePath}");
180 |
181 | if (String.IsNullOrEmpty(Path.GetExtension(filePath)))
182 | {
183 | filePath += ".ply";
184 | }
185 |
186 | var plyFile = MeshUtil.RenderableListToPlyFile(meshes);
187 | File.WriteAllText(filePath, plyFile);
188 | }
189 |
190 | ///
191 | /// Export an entire dream to a 3D format with textures.
192 | ///
193 | /// The dream we're exporting.
194 | /// Whether or not chunks should be combined into a single mesh.
195 | /// The mesh format to export the level mesh in.
196 | /// The directory we're exporting meshes to.
197 | public void ExportDream(Dream dream, bool combineChunks, MeshExportFormat exportFormat, string exportDirectory)
198 | {
199 | Log.Information($"Exporting dream to: {exportDirectory}");
200 |
201 | Log.Information($"Dream width: {dream.LevelWidth}");
202 |
203 | // create the directory
204 | Directory.CreateDirectory(exportDirectory);
205 |
206 | // combine tilelayouts of each LBDDocument into single renderables
207 | List chunkRenderables = new List();
208 | for (int i = 0; i < dream.Chunks.Count; i++)
209 | {
210 | Log.Information($"Processing dream chunk {i + 1}/{dream.Chunks.Count}...");
211 |
212 | var lbdChunk = dream.Chunks[i];
213 | var chunkRenderable = HeadlessMesh.CombineMeshes(lbdChunk.TileLayout.ToArray());
214 |
215 | // position the LBD chunk based on tiling (if we're tiling)
216 | if (dream.LevelWidth > 0)
217 | {
218 | int xPos = i % dream.LevelWidth;
219 | int yPos = i / dream.LevelWidth;
220 |
221 | // handle staggered tiling, every other row
222 | int xMod = 0;
223 | if (yPos % 2 == 1)
224 | {
225 | xMod = Dream.CHUNK_DIMENSION / 2; // stagger by half the width of a chunk
226 | }
227 |
228 | chunkRenderable.Transform.Position = new Vector3(xPos * Dream.CHUNK_DIMENSION - xMod, 0,
229 | yPos * Dream.CHUNK_DIMENSION);
230 | }
231 |
232 | chunkRenderables.Add(chunkRenderable);
233 | }
234 |
235 | string pathToLevel = Path.Combine(exportDirectory, "dream");
236 | if (combineChunks)
237 | {
238 | // if we're combining chunks, we now need to combine everything into a single renderable
239 | IRenderable dreamRenderable = HeadlessMesh.CombineMeshes(chunkRenderables.ToArray());
240 | ExportMesh(dreamRenderable, pathToLevel, exportFormat);
241 | }
242 | else
243 | {
244 | // otherwise we can export the list of chunk renderables
245 | ExportMeshes(chunkRenderables, pathToLevel, exportFormat);
246 | }
247 |
248 | // now, export each TIXDocument texture set as a combined VRAM export
249 | string pathToTextureSet = Path.Combine(exportDirectory, "textureset");
250 | string[] textureSetNames = { "-normal.png", "-kanji.png", "-downer.png", "-upper.png" };
251 | for (int i = 0; i < 4; i++)
252 | {
253 | Log.Information($"Exporting texture set {i + 1}/4...");
254 | var textureSetTix = dream.TextureSets[i].Document;
255 | string exportPath = pathToTextureSet + textureSetNames[i];
256 | ExportImages(textureSetTix, exportPath, separate: false, ImageFormat.Png);
257 | }
258 |
259 | Log.Information("Dream export complete");
260 | }
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/Controllers/ConfigController.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using LSDView.Controllers.Interface;
3 | using LSDView.Models;
4 | using Newtonsoft.Json;
5 | using Serilog;
6 |
7 | namespace LSDView.Controllers
8 | {
9 | public class ConfigController : IConfigController
10 | {
11 | public LSDViewConfig Config { get; protected set; }
12 |
13 | protected const string CONFIG_FILE = "LSDViewConfig.json";
14 | protected const int MAX_RECENT_FILES = 10;
15 |
16 | public ConfigController()
17 | {
18 | ensureConfigExists();
19 | deserializeConfig();
20 | }
21 |
22 | public void Save() { serializeConfig(); }
23 |
24 | public virtual void AddRecentFile(string recentFile)
25 | {
26 | if (Config.RecentFiles.Count + 1 > MAX_RECENT_FILES)
27 | {
28 | Config.RecentFiles.RemoveAt(MAX_RECENT_FILES - 1);
29 | }
30 |
31 | Config.RecentFiles.Insert(0, recentFile);
32 | Save();
33 | }
34 |
35 | protected void deserializeConfig()
36 | {
37 | Log.Information("Deserializing LSDViewConfig.json");
38 | Config = JsonConvert.DeserializeObject(File.ReadAllText(CONFIG_FILE));
39 | }
40 |
41 | protected void serializeConfig()
42 | {
43 | Log.Information("Serializing LSDViewConfig.json");
44 | File.WriteAllText(CONFIG_FILE, JsonConvert.SerializeObject(Config));
45 | }
46 |
47 | protected void ensureConfigExists()
48 | {
49 | if (!File.Exists(CONFIG_FILE))
50 | {
51 | Log.Information("LSDViewConfig.json not found - creating anew");
52 | Config = new LSDViewConfig();
53 | serializeConfig();
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Controllers/FileOpenController.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Models;
5 | using Serilog;
6 |
7 | namespace LSDView.Controllers
8 | {
9 | public class FileOpenController : IFileOpenController
10 | {
11 | protected readonly IFileFormatController _lbdController;
12 | protected readonly IFileFormatController _tmdController;
13 | protected readonly IFileFormatController _momController;
14 | protected readonly IFileFormatController _timController;
15 | protected readonly IFileFormatController _tixController;
16 | protected readonly IConfigController _configController;
17 |
18 | public FileOpenController(IFileFormatController lbdController,
19 | IFileFormatController tmdController,
20 | IFileFormatController momController,
21 | IFileFormatController timController,
22 | IFileFormatController tixController,
23 | IConfigController configController)
24 | {
25 | _lbdController = lbdController;
26 | _tmdController = tmdController;
27 | _momController = momController;
28 | _timController = timController;
29 | _tixController = tixController;
30 | _configController = configController;
31 | }
32 |
33 | public void OpenFile(string filePath)
34 | {
35 | _configController.AddRecentFile(filePath);
36 | Log.Information($"Loading file from: {filePath}");
37 | var ext = Path.GetExtension(filePath)?.ToLowerInvariant();
38 | switch (ext)
39 | {
40 | case ".lbd":
41 | _lbdController.Load(filePath);
42 | break;
43 | case ".tmd":
44 | _tmdController.Load(filePath);
45 | break;
46 | case ".mom":
47 | _momController.Load(filePath);
48 | break;
49 | case ".tim":
50 | _timController.Load(filePath);
51 | break;
52 | case ".tix":
53 | _tixController.Load(filePath);
54 | break;
55 | default:
56 | Log.Error($"Unable to open file {filePath}, unsupported type.");
57 | break;
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Controllers/GUI/AnimationController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using libLSD.Formats;
3 | using libLSD.Formats.Packets;
4 | using LSDView.Controllers.Interface;
5 | using LSDView.Math;
6 | using LSDView.Models;
7 | using OpenTK;
8 |
9 | namespace LSDView.Controllers.GUI
10 | {
11 | public class AnimationController : IAnimationController
12 | {
13 | private TOD _animation;
14 |
15 | public TOD Animation
16 | {
17 | get => _animation;
18 | set
19 | {
20 | _animation = value;
21 | CurrentFrame = -1;
22 | }
23 | }
24 |
25 | public MOMDocument Focus { get; private set; }
26 | public bool Active { get; set; }
27 | public int CurrentFrame { get; set; }
28 |
29 | public const double TICK = 1d / 60;
30 |
31 | protected double time = 0;
32 |
33 | protected readonly Dictionary _objectTable;
34 |
35 | protected class AnimationObjectTableEntry
36 | {
37 | public int TmdID;
38 | public readonly Transform Transform;
39 |
40 | public AnimationObjectTableEntry()
41 | {
42 | TmdID = -1;
43 | Transform = new Transform();
44 | }
45 | }
46 |
47 | protected bool Ready => Animation.Header.ID != 0 && Focus != null;
48 |
49 | public AnimationController()
50 | {
51 | Active = false;
52 | Focus = null;
53 | CurrentFrame = -1;
54 | _objectTable = new Dictionary {[0] = new AnimationObjectTableEntry()};
55 | }
56 |
57 | public void SetFocus(MOMDocument mom, int animationIdx = 0)
58 | {
59 | Active = true;
60 | Focus = mom;
61 | if (mom != null) Animation = mom.Document.MOS.TODs[animationIdx];
62 | CurrentFrame = -1;
63 | }
64 |
65 | public void Update(double dt)
66 | {
67 | if (!Active || !Ready) return;
68 |
69 | double tickRate = Animation.Header.Resolution * TICK;
70 |
71 | time += dt;
72 | if (time > tickRate)
73 | {
74 | time = 0;
75 |
76 | CurrentFrame++;
77 |
78 | processFrame(CurrentFrame);
79 | }
80 | }
81 |
82 | protected void processFrame(int frameNumber)
83 | {
84 | if (frameNumber >= Animation.Header.NumberOfFrames - 1)
85 | {
86 | frameNumber = 0;
87 | CurrentFrame = 0;
88 | }
89 |
90 | if (Animation.Frames.Length <= 0) return;
91 |
92 | TODFrame frame = Animation.Frames[frameNumber];
93 |
94 | if (frame.Packets == null)
95 | return;
96 |
97 | foreach (var packet in frame.Packets)
98 | {
99 | if (packet.Data is TODObjectControlPacketData)
100 | {
101 | handleObjectControlPacket(packet);
102 | }
103 | else if (packet.Data is TODObjectIDPacketData)
104 | {
105 | handleObjectIDPacket(packet);
106 | }
107 | else if (packet.Data is TODCoordinatePacketData)
108 | {
109 | handleCoordinatePacket(packet);
110 | }
111 | }
112 | }
113 |
114 | protected void handleObjectControlPacket(TODPacket packet)
115 | {
116 | TODObjectControlPacketData packetData = packet.Data as TODObjectControlPacketData;
117 | if (packetData.ObjectControl == TODObjectControlPacketData.ObjectControlType.Create)
118 | {
119 | _objectTable[packet.ObjectID] = new AnimationObjectTableEntry();
120 | }
121 | else if (packetData.ObjectControl == TODObjectControlPacketData.ObjectControlType.Kill)
122 | {
123 | _objectTable.Remove(packet.ObjectID);
124 | }
125 | }
126 |
127 | protected void handleObjectIDPacket(TODPacket packet)
128 | {
129 | TODObjectIDPacketData packetData = packet.Data as TODObjectIDPacketData;
130 | if (packet.PacketType == TODPacket.PacketTypes.TMDDataID)
131 | {
132 | // create mapping in object table
133 | _objectTable[packet.ObjectID].TmdID = packetData.ObjectID;
134 | Focus.Models.ObjectMeshes[packetData.ObjectID - 1].Transform.Parent =
135 | _objectTable[packet.ObjectID].Transform;
136 | }
137 | else if (packet.PacketType == TODPacket.PacketTypes.ParentObjectID)
138 | {
139 | // set object parent
140 | Transform parentTransform = _objectTable[packetData.ObjectID].Transform;
141 | _objectTable[packet.ObjectID].Transform.Parent = parentTransform;
142 | }
143 | }
144 |
145 | protected void handleCoordinatePacket(TODPacket packet)
146 | {
147 | TODCoordinatePacketData packetData = packet.Data as TODCoordinatePacketData;
148 |
149 | Transform objTransform = _objectTable[packet.ObjectID].Transform;
150 | if (packetData.HasScale)
151 | {
152 | if (packetData.MatrixType == TODPacketData.PacketDataType.Absolute)
153 | {
154 | objTransform.Scale = new Vector3(packetData.ScaleX / 4096f, packetData.ScaleY / 4096f,
155 | packetData.ScaleZ / 4096f);
156 | }
157 | else
158 | {
159 | objTransform.Scale *= new Vector3(packetData.ScaleX / 4096f, packetData.ScaleY / 4096f,
160 | packetData.ScaleZ / 4096f);
161 | }
162 | }
163 |
164 | if (packetData.HasTranslation)
165 | {
166 | if (packetData.MatrixType == TODPacketData.PacketDataType.Absolute)
167 | {
168 | objTransform.Position =
169 | new Vector3(packetData.TransX, packetData.TransY, packetData.TransZ) / 2048f;
170 | }
171 | else
172 | {
173 | objTransform.Translate(new Vector3(packetData.TransX, packetData.TransY, packetData.TransZ) /
174 | 2048f);
175 | }
176 | }
177 |
178 | if (packetData.HasRotation)
179 | {
180 | float pitch = MathHelper.DegreesToRadians(packetData.RotX / 4096f);
181 | float yaw = MathHelper.DegreesToRadians(packetData.RotY / 4096f);
182 | float roll = MathHelper.DegreesToRadians(packetData.RotZ / 4096f);
183 |
184 | if (packetData.MatrixType == TODPacketData.PacketDataType.Absolute)
185 | {
186 | objTransform.Rotation = Quaternion.Identity;
187 | objTransform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitX, pitch);
188 | objTransform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitY, yaw);
189 | objTransform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitZ, roll);
190 | }
191 | else
192 | {
193 | objTransform.Rotate(pitch, Vector3.UnitX, true);
194 | objTransform.Rotate(yaw, Vector3.UnitY, true);
195 | objTransform.Rotate(roll, Vector3.UnitZ, true);
196 | }
197 | }
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/Controllers/GUI/CameraController.cs:
--------------------------------------------------------------------------------
1 | using LSDView.Controllers.Interface;
2 | using LSDView.Graphics;
3 | using OpenTK;
4 | using OpenTK.Input;
5 |
6 | namespace LSDView.Controllers.GUI
7 | {
8 | public class CameraController : ICameraController
9 | {
10 | protected Camera _cam;
11 | protected MouseState _lastMouseState;
12 | protected float _lastScroll;
13 | protected bool _dragging;
14 |
15 | protected Vector3 _arcBallTarget = Vector3.Zero;
16 | protected float _arcBallDistance = 10f;
17 |
18 | protected const float MIN_ARCBALL_DISTANCE = 1f;
19 | protected const float MAX_ARCBALL_DISTANCE = 80f;
20 | protected const float PAN_SPEED = 0.02f;
21 |
22 | public void ProvideCamera(Camera cam) { _cam = cam; }
23 |
24 | public void Update()
25 | {
26 | if (!GuiApplication.Instance.Visible || !GuiApplication.Instance.Focused) return;
27 |
28 | var mouseState = Mouse.GetCursorState();
29 |
30 | if (mouseState.IsButtonDown(MouseButton.Left)) arcBallRotation(mouseState);
31 | if (mouseState.IsButtonDown(MouseButton.Right)) panning(mouseState);
32 | scrollZooming(mouseState);
33 |
34 | _lastMouseState = mouseState;
35 | _lastScroll = mouseState.Scroll.Y;
36 | }
37 |
38 | public void RecenterView()
39 | {
40 | _arcBallTarget = Vector3.Zero;
41 | _cam.ArcBall(0, 0, _arcBallTarget, _arcBallDistance);
42 | }
43 |
44 | protected Vector2 dragDelta(MouseState state)
45 | {
46 | return new Vector2(state.X, state.Y) - new Vector2(_lastMouseState.X, _lastMouseState.Y);
47 | }
48 |
49 | protected float scrollDelta(MouseState state) { return state.Scroll.Y - _lastScroll; }
50 |
51 | protected void arcBallRotation(MouseState state)
52 | {
53 | var delta = dragDelta(state);
54 | _cam.ArcBall(MathHelper.DegreesToRadians(-delta.X), MathHelper.DegreesToRadians(delta.Y),
55 | _arcBallTarget, _arcBallDistance);
56 | }
57 |
58 | protected void panning(MouseState state)
59 | {
60 | var delta = dragDelta(state);
61 | var translationVec = _cam.Transform.Right * delta.X + _cam.Transform.Up * delta.Y;
62 | translationVec *= PAN_SPEED;
63 | _cam.Transform.Translate(translationVec);
64 | _arcBallTarget += translationVec;
65 | }
66 |
67 | protected void scrollZooming(MouseState state)
68 | {
69 | _arcBallDistance -= scrollDelta(state);
70 | _arcBallDistance = MathHelper.Clamp(_arcBallDistance, MIN_ARCBALL_DISTANCE, MAX_ARCBALL_DISTANCE);
71 | _cam.ArcBall(0, 0, _arcBallTarget, _arcBallDistance);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Controllers/GUI/GUIExportController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LSDView.GUI.GUIComponents;
3 |
4 | namespace LSDView.Controllers.GUI
5 | {
6 | public class GUIExportController : AbstractExportController
7 | {
8 | protected FileDialog _fileExportDialog;
9 |
10 | public void ProvideFileExportDialog(FileDialog fileExportDialog) { _fileExportDialog = fileExportDialog; }
11 |
12 | public void OpenDialog(Action onSubmit, string fileSaveType)
13 | {
14 | GuiApplication.Instance.NextGuiRender += () =>
15 | {
16 | _fileExportDialog.ShowDialog(onSubmit, fileSaveType: fileSaveType);
17 | };
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Controllers/GUI/GUILBDController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using libLSD.Formats;
4 | using LSDView.Controllers.Interface;
5 | using LSDView.Graphics;
6 | using LSDView.Models;
7 | using LSDView.Util;
8 |
9 | namespace LSDView.Controllers.GUI
10 | {
11 | public class GUILBDController : IFileFormatController
12 | {
13 | protected readonly ITreeController _treeController;
14 | protected readonly IVRAMController _vramController;
15 | protected readonly IFileFormatController _tmdController;
16 | protected readonly IFileFormatController _momController;
17 | protected readonly Shader _shader;
18 |
19 | public GUILBDController(ITreeController treeController,
20 | IVRAMController vramController,
21 | IFileFormatController tmdController,
22 | IFileFormatController momController)
23 | {
24 | _treeController = treeController;
25 | _vramController = vramController;
26 | _tmdController = tmdController;
27 | _momController = momController;
28 | _shader = new Shader("basic", "Shaders/basic");
29 | }
30 |
31 | public LBD Load(string lbdPath)
32 | {
33 | var lbd = LibLSDUtil.LoadLBD(lbdPath);
34 |
35 | LBDDocument document = CreateDocument(lbd);
36 | _treeController.PopulateWithDocument(document, Path.GetFileName(lbdPath));
37 |
38 | return lbd;
39 | }
40 |
41 | public LBDDocument CreateDocument(LBD lbd)
42 | {
43 | TMDDocument tileTmd = _tmdController.CreateDocument(lbd.Tiles);
44 | List tileLayout = new List();
45 |
46 | int tileNo = 0;
47 | foreach (LBDTile tile in lbd.TileLayout)
48 | {
49 | int x = tileNo / lbd.Header.TileWidth;
50 | int y = tileNo % lbd.Header.TileWidth;
51 |
52 | if (tile.DrawTile)
53 | {
54 | tileLayout.AddRange(LibLSDUtil.CreateLBDTileMesh(tile, lbd.ExtraTiles, x, y, lbd.Tiles, _shader,
55 | _vramController.VRAM, headless: false));
56 | }
57 |
58 | tileNo++;
59 | }
60 |
61 | List entities = null;
62 | if (lbd.Header.HasMML)
63 | {
64 | entities = new List();
65 | foreach (MOM mom in lbd.MML?.MOMs)
66 | {
67 | entities.Add(_momController.CreateDocument(mom));
68 | }
69 | }
70 |
71 | return new LBDDocument(lbd, tileTmd, tileLayout, entities);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Controllers/GUI/GUIMOMController.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Models;
5 | using LSDView.Util;
6 |
7 | namespace LSDView.Controllers.GUI
8 | {
9 | public class GUIMOMController : IFileFormatController
10 | {
11 | protected readonly ITreeController _treeController;
12 | protected readonly IFileFormatController _tmdController;
13 |
14 | public GUIMOMController(ITreeController treeController,
15 | IFileFormatController tmdController)
16 | {
17 | _treeController = treeController;
18 | _tmdController = tmdController;
19 | }
20 |
21 | public MOM Load(string momPath)
22 | {
23 | var mom = LibLSDUtil.LoadMOM(momPath);
24 |
25 | MOMDocument document = CreateDocument(mom);
26 | _treeController.PopulateWithDocument(document, Path.GetFileName(momPath));
27 |
28 | return mom;
29 | }
30 |
31 | public MOMDocument CreateDocument(MOM mom)
32 | {
33 | TMDDocument models = _tmdController.CreateDocument(mom.TMD);
34 |
35 | return new MOMDocument(mom, models);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Controllers/GUI/GUITIMController.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Graphics;
5 | using LSDView.Models;
6 | using LSDView.Util;
7 |
8 | namespace LSDView.Controllers.GUI
9 | {
10 | public class GUITIMController : IFileFormatController
11 | {
12 | protected readonly ITreeController _treeController;
13 | protected readonly Shader _shader;
14 |
15 | public GUITIMController(ITreeController treeController)
16 | {
17 | _treeController = treeController;
18 | _shader = new Shader("texture", "Shaders/texture");
19 | }
20 |
21 | public TIM Load(string timPath)
22 | {
23 | var tim = LibLSDUtil.LoadTIM(timPath);
24 |
25 | TIMDocument document = CreateDocument(tim);
26 | _treeController.PopulateWithDocument(document, Path.GetFileName(timPath));
27 |
28 | return tim;
29 | }
30 |
31 | public TIMDocument CreateDocument(TIM tim)
32 | {
33 | // 1 if not using CLUT, otherwise number of CLUTs
34 | int numMeshes = tim.ColorLookup?.NumberOfCLUTs ?? 1;
35 | Mesh[] timMeshes = new Mesh[numMeshes];
36 |
37 | for (int i = 0; i < numMeshes; i++)
38 | {
39 | var img = LibLSDUtil.GetImageDataFromTIM(tim, clutIndex: i);
40 | Mesh textureMesh = Mesh.CreateQuad(_shader);
41 | ITexture2D tex = new Texture2D(img.width, img.height, img.data);
42 | textureMesh.Textures.Add(tex);
43 | timMeshes[i] = textureMesh;
44 | }
45 |
46 | return new TIMDocument(tim, timMeshes);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Controllers/GUI/GUITIXController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using libLSD.Formats;
4 | using LSDView.Controllers.Interface;
5 | using LSDView.Models;
6 | using LSDView.Util;
7 |
8 | namespace LSDView.Controllers.GUI
9 | {
10 | public class GUITIXController : IFileFormatController
11 | {
12 | protected readonly ITreeController _treeController;
13 | protected readonly IFileFormatController _timController;
14 |
15 | public GUITIXController(ITreeController treeController, IFileFormatController timController)
16 | {
17 | _treeController = treeController;
18 | _timController = timController;
19 | }
20 |
21 | public TIX Load(string tixPath)
22 | {
23 | var tix = LibLSDUtil.LoadTIX(tixPath);
24 |
25 | TIXDocument document = CreateDocument(tix);
26 | _treeController.PopulateWithDocument(document, Path.GetFileName(tixPath));
27 |
28 | return tix;
29 | }
30 |
31 | public TIXDocument CreateDocument(TIX tix)
32 | {
33 | List tims = new List();
34 | foreach (var tim in tix.AllTIMs)
35 | {
36 | tims.Add(_timController.CreateDocument(tim));
37 | }
38 |
39 | return new TIXDocument(tix, tims);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Controllers/GUI/GUITMDController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using libLSD.Formats;
4 | using LSDView.Controllers.Interface;
5 | using LSDView.Graphics;
6 | using LSDView.Models;
7 | using LSDView.Util;
8 |
9 | namespace LSDView.Controllers.GUI
10 | {
11 | public class GUITMDController : IFileFormatController
12 | {
13 | protected readonly ITreeController _treeController;
14 | protected readonly IVRAMController _vramController;
15 | protected readonly Shader _shader;
16 |
17 | public GUITMDController(ITreeController treeController, IVRAMController vramController)
18 | {
19 | _treeController = treeController;
20 | _vramController = vramController;
21 | _shader = new Shader("basic", "Shaders/basic");
22 | }
23 |
24 | public TMD Load(string tmdPath)
25 | {
26 | var tmd = LibLSDUtil.LoadTMD(tmdPath);
27 |
28 | TMDDocument document = CreateDocument(tmd);
29 | _treeController.PopulateWithDocument(document, Path.GetFileName(tmdPath));
30 |
31 | return tmd;
32 | }
33 |
34 | public TMDDocument CreateDocument(TMD tmd)
35 | {
36 | List objectMeshes =
37 | LibLSDUtil.CreateMeshesFromTMD(tmd, _shader, _vramController.VRAM, headless: false);
38 | return new TMDDocument(tmd, objectMeshes);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Controllers/GUI/VRAMController.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Graphics;
5 | using LSDView.Util;
6 | using OpenTK.Graphics;
7 | using Serilog;
8 |
9 | namespace LSDView.Controller
10 | {
11 | public class VRAMController : IVRAMController
12 | {
13 | public ITexture2D VRAM { get => _vram; protected set => _vram = value; }
14 | public TIX Tix { get; protected set; }
15 |
16 | public bool VRAMLoaded { get; protected set; }
17 |
18 | private ITexture2D _vram;
19 |
20 | public VRAMController()
21 | {
22 | VRAM = Texture2D.Fill(Color4.White, LibLSDUtil.VRAM_WIDTH, LibLSDUtil.VRAM_HEIGHT);
23 | VRAMLoaded = false;
24 | }
25 |
26 | public void LoadTIXIntoVRAM(string tixPath)
27 | {
28 | Log.Information($"Loading TIX from {tixPath} into virtual VRAM");
29 |
30 | using (BinaryReader br = new BinaryReader(File.Open(tixPath, FileMode.Open)))
31 | {
32 | Tix = new TIX(br);
33 | }
34 |
35 | Log.Information("Successfully loaded TIX");
36 |
37 | LibLSDUtil.TIXToTexture2D(Tix, ref _vram, flip: true);
38 |
39 | VRAMLoaded = true;
40 | }
41 |
42 | public void ClearVRAM() { VRAM.Clear(); }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessConfigController.cs:
--------------------------------------------------------------------------------
1 | using LSDView.Controllers.GUI;
2 |
3 | namespace LSDView.Controllers.Headless
4 | {
5 | public class HeadlessConfigController : ConfigController
6 | {
7 | public override void AddRecentFile(string recentFile)
8 | {
9 | // do nothing
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessExportController.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Controllers.Headless
2 | {
3 | public class HeadlessExportController : AbstractExportController
4 | {
5 | // intentionally empty
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessLBDController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Graphics;
5 | using LSDView.Models;
6 | using LSDView.Util;
7 |
8 | namespace LSDView.Controllers.Headless
9 | {
10 | public class HeadlessLBDController : IFileFormatController
11 | {
12 | protected readonly IVRAMController _vramController;
13 | protected readonly IFileFormatController _tmdController;
14 | protected readonly IFileFormatController _momController;
15 |
16 | public HeadlessLBDController(IVRAMController vramController,
17 | IFileFormatController tmdController,
18 | IFileFormatController momController)
19 | {
20 | _vramController = vramController;
21 | _tmdController = tmdController;
22 | _momController = momController;
23 | }
24 |
25 | public LBD Load(string path) { return LibLSDUtil.LoadLBD(path); }
26 |
27 | public LBDDocument CreateDocument(LBD lbd)
28 | {
29 | TMDDocument tileTmd = _tmdController.CreateDocument(lbd.Tiles);
30 | List tileLayout = new List();
31 |
32 | int tileNo = 0;
33 | foreach (LBDTile tile in lbd.TileLayout)
34 | {
35 | int x = tileNo / lbd.Header.TileWidth;
36 | int y = tileNo % lbd.Header.TileWidth;
37 |
38 | if (tile.DrawTile)
39 | {
40 | tileLayout.AddRange(LibLSDUtil.CreateLBDTileMesh(tile, lbd.ExtraTiles, x, y, lbd.Tiles, null,
41 | _vramController.VRAM, headless: true));
42 | }
43 |
44 | tileNo++;
45 | }
46 |
47 | List entities = null;
48 | if (lbd.Header.HasMML)
49 | {
50 | entities = new List();
51 | foreach (MOM mom in lbd.MML?.MOMs)
52 | {
53 | entities.Add(_momController.CreateDocument(mom));
54 | }
55 | }
56 |
57 | return new LBDDocument(lbd, tileTmd, tileLayout, entities);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessMOMController.cs:
--------------------------------------------------------------------------------
1 | using libLSD.Formats;
2 | using LSDView.Controllers.Interface;
3 | using LSDView.Models;
4 | using LSDView.Util;
5 |
6 | namespace LSDView.Controllers.Headless
7 | {
8 | public class HeadlessMOMController : IFileFormatController
9 | {
10 | protected readonly IFileFormatController _tmdController;
11 |
12 | public HeadlessMOMController(IFileFormatController tmdController)
13 | {
14 | _tmdController = tmdController;
15 | }
16 |
17 | public MOM Load(string path) { return LibLSDUtil.LoadMOM(path); }
18 |
19 | public MOMDocument CreateDocument(MOM mom)
20 | {
21 | TMDDocument models = _tmdController.CreateDocument(mom.TMD);
22 |
23 | return new MOMDocument(mom, models);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessTIMController.cs:
--------------------------------------------------------------------------------
1 | using libLSD.Formats;
2 | using LSDView.Controllers.Interface;
3 | using LSDView.Graphics;
4 | using LSDView.Graphics.Headless;
5 | using LSDView.Models;
6 | using LSDView.Util;
7 |
8 | namespace LSDView.Controllers.Headless
9 | {
10 | public class HeadlessTIMController : IFileFormatController
11 | {
12 | public TIM Load(string path) { return LibLSDUtil.LoadTIM(path); }
13 |
14 | public TIMDocument CreateDocument(TIM tim)
15 | {
16 | // 1 if not using CLUT, otherwise number of CLUTs
17 | int numMeshes = tim.ColorLookup?.NumberOfCLUTs ?? 1;
18 | IRenderable[] timMeshes = new IRenderable[numMeshes];
19 |
20 | for (int i = 0; i < numMeshes; i++)
21 | {
22 | var img = LibLSDUtil.GetImageDataFromTIM(tim, clutIndex: i);
23 | HeadlessMesh textureMesh = HeadlessMesh.CreateQuad();
24 | ITexture2D tex = new HeadlessTexture2D(img.width, img.height, img.data);
25 | textureMesh.Textures.Add(tex);
26 | timMeshes[i] = textureMesh;
27 | }
28 |
29 | return new TIMDocument(tim, timMeshes);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessTIXController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Models;
5 | using LSDView.Util;
6 |
7 | namespace LSDView.Controllers.Headless
8 | {
9 | public class HeadlessTIXController : IFileFormatController
10 | {
11 | protected readonly IFileFormatController _timController;
12 |
13 | public HeadlessTIXController(IFileFormatController timController)
14 | {
15 | _timController = timController;
16 | }
17 |
18 | public TIX Load(string path) { return LibLSDUtil.LoadTIX(path); }
19 |
20 | public TIXDocument CreateDocument(TIX tix)
21 | {
22 | List tims = new List();
23 | foreach (var tim in tix.AllTIMs)
24 | {
25 | tims.Add(_timController.CreateDocument(tim));
26 | }
27 |
28 | return new TIXDocument(tix, tims);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessTMDController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Graphics;
5 | using LSDView.Models;
6 | using LSDView.Util;
7 |
8 | namespace LSDView.Controllers.Headless
9 | {
10 | public class HeadlessTMDController : IFileFormatController
11 | {
12 | public TMD Load(string path) { return LibLSDUtil.LoadTMD(path); }
13 |
14 | public TMDDocument CreateDocument(TMD tmd)
15 | {
16 | List objectMeshes =
17 | LibLSDUtil.CreateMeshesFromTMD(tmd, null, null, headless: true);
18 | return new TMDDocument(tmd, objectMeshes);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Controllers/Headless/HeadlessVRAMController.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using libLSD.Formats;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Graphics;
5 | using LSDView.Util;
6 | using Serilog;
7 |
8 | namespace LSDView.Controllers.Headless
9 | {
10 | public class HeadlessVRAMController : IVRAMController
11 | {
12 | public ITexture2D VRAM { get; protected set; }
13 | public TIX Tix { get; protected set; }
14 | public bool VRAMLoaded { get; protected set; }
15 |
16 | public void LoadTIXIntoVRAM(string tixPath)
17 | {
18 | Log.Information($"Loading TIX from {tixPath} into virtual VRAM");
19 |
20 | using (BinaryReader br = new BinaryReader(File.Open(tixPath, FileMode.Open)))
21 | {
22 | Tix = new TIX(br);
23 | }
24 |
25 | Log.Information("Successfully loaded TIX");
26 |
27 | if (VRAM != null)
28 | {
29 | disposeOldVRAM();
30 | }
31 |
32 | VRAM = LibLSDUtil.TIXToTexture2D(Tix, headless: true, flip: false);
33 |
34 | VRAMLoaded = true;
35 | }
36 |
37 | public void ClearVRAM() { VRAM.Clear(); }
38 |
39 | protected void disposeOldVRAM()
40 | {
41 | VRAM.Dispose();
42 | VRAM = null;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Controllers/Interface/IAnimationController.cs:
--------------------------------------------------------------------------------
1 | using libLSD.Formats;
2 | using LSDView.Models;
3 |
4 | namespace LSDView.Controllers.Interface
5 | {
6 | public interface IAnimationController
7 | {
8 | TOD Animation { get; }
9 | MOMDocument Focus { get; }
10 | bool Active { get; }
11 | int CurrentFrame { get; }
12 |
13 | void SetFocus(MOMDocument mom, int animationIdx = 0);
14 | void Update(double dt);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Controllers/Interface/ICameraController.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Controllers.Interface
2 | {
3 | public interface ICameraController
4 | {
5 | void Update();
6 | void RecenterView();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Controllers/Interface/IConfigController.cs:
--------------------------------------------------------------------------------
1 | using LSDView.Models;
2 |
3 | namespace LSDView.Controllers.Interface
4 | {
5 | public interface IConfigController
6 | {
7 | LSDViewConfig Config { get; }
8 |
9 | void Save();
10 | void AddRecentFile(string recentFile);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Controllers/Interface/IExportController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Drawing.Imaging;
3 | using libLSD.Formats;
4 | using libLSD.Interfaces;
5 | using LSDView.Graphics;
6 |
7 | namespace LSDView.Controllers.Interface
8 | {
9 | public interface IExportController
10 | {
11 | void ExportOriginal(IWriteable original, string filePath);
12 | void ExportImage(TIM tim, int clutIndex, string filePath, ImageFormat format);
13 | void ExportImages(TIX tix, string filePath, bool separate, ImageFormat format);
14 | void ExportTexture(ITexture2D tex, string filePath, ImageFormat format);
15 | void ExportMesh(IRenderable mesh, string filePath, MeshExportFormat format);
16 | void ExportMeshes(IEnumerable meshes, string filePath, MeshExportFormat format);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Controllers/Interface/IFileFormatController.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Controllers.Interface
2 | {
3 | public interface IFileFormatController
4 | {
5 | TFile Load(string path);
6 | TDocument CreateDocument(TFile file);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Controllers/Interface/IFileOpenController.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Controllers.Interface
2 | {
3 | public interface IFileOpenController
4 | {
5 | void OpenFile(string filePath);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Controllers/Interface/ITreeController.cs:
--------------------------------------------------------------------------------
1 | using LSDView.GUI.Components;
2 | using LSDView.Models;
3 | using OpenTK;
4 |
5 | namespace LSDView.Controllers.Interface
6 | {
7 | public interface ITreeController
8 | {
9 | TreeView Tree { get; }
10 |
11 | void PopulateWithDocument(IDocument doc, string rootName);
12 | void RenderSelectedNode(Matrix4 view, Matrix4 projection);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Controllers/Interface/IUpdateCheckerController.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Controllers.Interface
2 | {
3 | public interface IUpdateCheckerController
4 | {
5 | bool IsUpdateAvailable();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Controllers/Interface/IVRAMController.cs:
--------------------------------------------------------------------------------
1 | using libLSD.Formats;
2 | using LSDView.Graphics;
3 |
4 | namespace LSDView.Controllers.Interface
5 | {
6 | public interface IVRAMController
7 | {
8 | ITexture2D VRAM { get; }
9 | TIX Tix { get; }
10 | bool VRAMLoaded { get; }
11 |
12 | void LoadTIXIntoVRAM(string tixPath);
13 | void ClearVRAM();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Controllers/Interface/MeshExportFormat.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Controllers
2 | {
3 | public enum MeshExportFormat
4 | {
5 | OBJ,
6 | PLY
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Controllers/UpdateCheckerController.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using LSDView.Controllers.Interface;
3 | using Newtonsoft.Json.Linq;
4 | using Serilog;
5 |
6 | namespace LSDView.Controllers
7 | {
8 | public class UpdateCheckerController : IUpdateCheckerController
9 | {
10 | protected const string UPDATE_API_URL = "https://api.github.com/repos/figglewatts/lsdview/releases/latest";
11 |
12 | public bool IsUpdateAvailable()
13 | {
14 | // if we're developing locally, always return false
15 | if (Version.String.Equals("#{VERSION}#")) return false;
16 |
17 | using (WebClient wc = new WebClient())
18 | {
19 | wc.Headers.Add("User-Agent", "LSDView");
20 | try
21 | {
22 | var json = wc.DownloadString(UPDATE_API_URL);
23 | dynamic releaseInfo = JObject.Parse(json);
24 | string releaseName = releaseInfo.name;
25 | return !Version.String.Equals(releaseName);
26 | }
27 | catch (WebException exception)
28 | {
29 | Log.Warning($"Unable to check for update: {exception}");
30 | return false;
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Fonts/fa-regular-400.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/figglewatts/LSDView/168adbc970a30f572472b2a07181166d198ce1af/Fonts/fa-regular-400.ttf
--------------------------------------------------------------------------------
/Fonts/fa-solid-900.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/figglewatts/LSDView/168adbc970a30f572472b2a07181166d198ce1af/Fonts/fa-solid-900.ttf
--------------------------------------------------------------------------------
/GUI/Components/AnimatedMeshListTreeNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using LSDView.Controllers.Interface;
4 | using LSDView.Graphics;
5 | using LSDView.Models;
6 |
7 | namespace LSDView.GUI.Components
8 | {
9 | public class AnimatedMeshListTreeNode : MeshListTreeNode
10 | {
11 | protected int _animation { get; }
12 | protected MOMDocument _entity { get; }
13 | protected readonly IAnimationController _animationController;
14 |
15 | public AnimatedMeshListTreeNode(string text,
16 | List meshes,
17 | MOMDocument entity,
18 | int animation,
19 | IAnimationController animationController,
20 | IEnumerable children = null,
21 | Action onSelect = null,
22 | ContextMenu contextMenu = null) : base(text, meshes, children, onSelect, contextMenu)
23 | {
24 | _animation = animation;
25 | _animationController = animationController;
26 | _entity = entity;
27 | }
28 |
29 | protected override void internalOnSelect() { _animationController.SetFocus(_entity, _animation); }
30 |
31 | public override void OnDeselect() { _animationController.SetFocus(null); }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/GUI/Components/ApplicationArea.cs:
--------------------------------------------------------------------------------
1 | using System.Numerics;
2 | using ImGuiNET;
3 |
4 | namespace LSDView.GUI.Components
5 | {
6 | public class ApplicationArea : ImGuiComponent
7 | {
8 | private const int TITLEBAR_HEIGHT = 19;
9 |
10 | private readonly ImGuiIOPtr _io;
11 |
12 | public ApplicationArea() { _io = ImGui.GetIO(); }
13 |
14 | protected override void renderSelf()
15 | {
16 | ImGui.SetNextWindowPos(new Vector2(0, TITLEBAR_HEIGHT), ImGuiCond.Always, Vector2.Zero);
17 | ImGui.SetNextWindowSize(new Vector2(_io.DisplaySize.X, _io.DisplaySize.Y - TITLEBAR_HEIGHT),
18 | ImGuiCond.Always);
19 | ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
20 | if (ImGui.Begin("",
21 | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse |
22 | ImGuiWindowFlags.NoSavedSettings |
23 | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoBringToFrontOnFocus))
24 | {
25 | renderChildren();
26 |
27 | ImGui.End();
28 | }
29 |
30 | ImGui.PopStyleVar();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/GUI/Components/Columns.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using ImGuiNET;
4 |
5 | namespace LSDView.GUI.Components
6 | {
7 | public class Columns : ImGuiComponent
8 | {
9 | public int Count { get; private set; }
10 | protected readonly List _content;
11 | protected readonly float[] _widths;
12 |
13 | private readonly bool[] _setWidths;
14 |
15 | public Columns(int count, List content, float[] widths = null)
16 | {
17 | Count = count;
18 |
19 | if (Count != content.Count)
20 | {
21 | throw new ArgumentException("Column count needs to be equal to content count!");
22 | }
23 |
24 | if (widths == null) widths = new float[Count];
25 | _widths = widths;
26 | _setWidths = new bool[Count];
27 |
28 | _content = content;
29 | }
30 |
31 | protected override void renderSelf()
32 | {
33 | ImGui.Columns(Count, GetHashCode().ToString(), false);
34 | for (int i = 0; i < Count; i++)
35 | {
36 | // if we haven't already set the width and the width is valid, then set it
37 | if (!_setWidths[i] && _widths[i] > 0)
38 | {
39 | ImGui.SetColumnWidth(i, _widths[i]);
40 | _setWidths[i] = true;
41 | }
42 |
43 | _content[i].Render();
44 | ImGui.NextColumn();
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/GUI/Components/ContextMenu.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using ImGuiNET;
4 |
5 | namespace LSDView.GUI.Components
6 | {
7 | public class ContextMenu
8 | {
9 | public readonly Dictionary MenuItems;
10 |
11 | public ContextMenu(Dictionary menuItems) { MenuItems = menuItems; }
12 |
13 | public void Render()
14 | {
15 | if (ImGui.BeginPopupContextItem())
16 | {
17 | foreach (var item in MenuItems)
18 | {
19 | if (ImGui.Selectable(item.Key)) item.Value();
20 | }
21 |
22 | ImGui.EndPopup();
23 | }
24 | }
25 |
26 | public bool Equals(ContextMenu other) { return Equals(MenuItems, other.MenuItems); }
27 |
28 | public override bool Equals(object obj) { return obj is ContextMenu other && Equals(other); }
29 |
30 | public override int GetHashCode() { return (MenuItems != null ? MenuItems.GetHashCode() : 0); }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/GUI/Components/FileDialog.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Numerics;
6 | using IconFonts;
7 | using ImGuiNET;
8 |
9 | namespace LSDView.GUI.GUIComponents
10 | {
11 | public class FileDialog : ImGuiComponent
12 | {
13 | public enum DialogType
14 | {
15 | Open,
16 | Save
17 | }
18 |
19 | public DialogType Type { get; }
20 | public string FilePath { get; private set; }
21 |
22 | public string FileSearchPattern
23 | {
24 | set
25 | {
26 | setFileSearchPattern(value);
27 | invalidateFileList();
28 | }
29 | }
30 |
31 | public string InitialDir { get => _initialDir; set => _initialDir = value; }
32 |
33 | private string _fileSaveType = "";
34 | private string[] _fileSearchPattern = new string[0];
35 | private bool _open = true;
36 | private bool _lastOpen = false;
37 | private string _currentDir;
38 | private int _selectedFile = -1;
39 | private string _bottomBarText = "";
40 | private readonly Vector2 _dialogStartSize = new Vector2(400, 300);
41 | private readonly List _directoriesInCurrentDir;
42 | private readonly List _filesInCurrentDir;
43 | private string _initialDir;
44 | private event Action OnDialogAccept;
45 |
46 | public FileDialog(string dir, DialogType type)
47 | : base()
48 | {
49 | FilePath = dir;
50 | _initialDir = dir;
51 | Type = type;
52 | _currentDir = _initialDir;
53 | _directoriesInCurrentDir = new List();
54 | _filesInCurrentDir = new List();
55 | updateFilesInCurrentDir();
56 | }
57 |
58 | public void ShowDialog(Action onDialogAccept,
59 | string fileSearchPattern = "",
60 | string fileSaveType = "")
61 | {
62 | OnDialogAccept = onDialogAccept;
63 | setFileSearchPattern(fileSearchPattern);
64 | _fileSaveType = fileSaveType;
65 | FilePath = _initialDir;
66 | _currentDir = _initialDir;
67 | invalidateFileList();
68 | ImGui.OpenPopup(Type == DialogType.Open
69 | ? $"Open file...##{GetHashCode()}"
70 | : $"Save file...##{GetHashCode()}");
71 | _open = true;
72 | }
73 |
74 | public bool CurrentDirectoryExists() { return Directory.Exists(_currentDir); }
75 |
76 | protected override void renderSelf()
77 | {
78 | _lastOpen = _open;
79 |
80 | if (Type == DialogType.Open)
81 | {
82 | renderFileOpenDialog();
83 | }
84 | else
85 | {
86 | renderFileSaveDialog();
87 | }
88 |
89 | if (_open == false && _lastOpen == true)
90 | {
91 | resetDefaults();
92 | }
93 | }
94 |
95 | private void setFileSearchPattern(string searchPattern)
96 | {
97 | if (string.IsNullOrEmpty(searchPattern)) return;
98 | _fileSearchPattern = searchPattern.Split('|');
99 | }
100 |
101 | private void resetDefaults()
102 | {
103 | OnDialogAccept = null;
104 | _fileSearchPattern = new[] {""};
105 | _fileSaveType = "";
106 | FilePath = _initialDir;
107 | _currentDir = _initialDir;
108 | _directoriesInCurrentDir.Clear();
109 | _filesInCurrentDir.Clear();
110 | }
111 |
112 | private void renderFileOpenDialog()
113 | {
114 | ImGui.SetNextWindowSize(_dialogStartSize, ImGuiCond.FirstUseEver);
115 | if (ImGui.BeginPopupModal($"Open file...##{GetHashCode()}", ref _open))
116 | {
117 | renderTopBar();
118 | renderFileList();
119 | renderBottomBar();
120 |
121 | ImGui.EndPopup();
122 | }
123 | }
124 |
125 | private void renderFileSaveDialog()
126 | {
127 | ImGui.SetNextWindowSize(_dialogStartSize, ImGuiCond.FirstUseEver);
128 | if (ImGui.BeginPopupModal($"Save file...##{GetHashCode()}", ref _open))
129 | {
130 | renderTopBar();
131 | renderFileList();
132 | renderBottomBar();
133 |
134 | ImGui.EndPopup();
135 | }
136 | }
137 |
138 | private void invalidateFileList()
139 | {
140 | _selectedFile = -1;
141 | updateDirectoriesInCurrentDir();
142 | updateFilesInCurrentDir();
143 | }
144 |
145 | private void goToParentDir()
146 | {
147 | DirectoryInfo parentDir = Directory.GetParent(_currentDir);
148 | _currentDir = parentDir?.ToString() ?? _currentDir;
149 | invalidateFileList();
150 | }
151 |
152 | private void updateDirectoriesInCurrentDir()
153 | {
154 | _directoriesInCurrentDir.Clear();
155 |
156 | if (!Directory.Exists(_currentDir)) return;
157 | string[] dirs = Directory.GetDirectories(_currentDir, "*", SearchOption.TopDirectoryOnly);
158 |
159 | foreach (string dir in dirs)
160 | {
161 | _directoriesInCurrentDir.Add(Path.GetFileName(dir.TrimEnd(Path.DirectorySeparatorChar)));
162 | }
163 | }
164 |
165 | private void updateFilesInCurrentDir()
166 | {
167 | _filesInCurrentDir.Clear();
168 |
169 | if (!Directory.Exists(_currentDir)) return;
170 | var files = Directory.EnumerateFiles(_currentDir, "*.*").Where(checkFileAgainstSearchPattern);
171 | foreach (string file in files)
172 | {
173 | _filesInCurrentDir.Add(Path.GetFileName(file));
174 | }
175 | }
176 |
177 | private bool checkFileAgainstSearchPattern(string file)
178 | {
179 | foreach (var pattern in _fileSearchPattern)
180 | {
181 | if (file.EndsWith(pattern, StringComparison.OrdinalIgnoreCase)) return true;
182 | }
183 |
184 | return false;
185 | }
186 |
187 | private bool indexInFilesListIsDirectory(int i) { return i < _directoriesInCurrentDir.Count; }
188 |
189 | private void renderTopBar()
190 | {
191 | Vector2 pos = ImGui.GetCursorScreenPos();
192 | ImGui.SetCursorScreenPos(new Vector2(pos.X, pos.Y + 5));
193 | ImGui.Text(FontAwesome5.FolderOpen);
194 | ImGui.SameLine();
195 | Vector2 posAfter = ImGui.GetCursorScreenPos();
196 | ImGui.SetCursorScreenPos(new Vector2(posAfter.X, pos.Y));
197 | ImGui.PushItemWidth(-40);
198 | bool modified = false;
199 | modified = ImGui.InputText("##current-dir", ref _currentDir, 2048);
200 |
201 | if (CurrentDirectoryExists())
202 | {
203 | ImGui.SameLine();
204 | if (ImGui.Button("Up", new Vector2(30, 0)))
205 | {
206 | goToParentDir();
207 | }
208 | }
209 |
210 | if (modified)
211 | {
212 | invalidateFileList();
213 | }
214 | }
215 |
216 | private void renderFileList()
217 | {
218 | // if the dialog is in save mode we can't select files
219 | if (Type == DialogType.Save) _selectedFile = -1;
220 |
221 | ImGui.BeginChild("fileSelect", new Vector2(-1, -24), true, ImGuiWindowFlags.None);
222 | if (!CurrentDirectoryExists())
223 | {
224 | ImGui.Text("Directory does not exist!");
225 | }
226 | else if (_filesInCurrentDir.Count <= 0 && _directoriesInCurrentDir.Count <= 0)
227 | {
228 | ImGui.Text("Directory is empty!");
229 | }
230 | else
231 | {
232 | int i = 0;
233 | foreach (string dir in _directoriesInCurrentDir)
234 | {
235 | ImGui.PushID(i);
236 | if (ImGui.Selectable($"{FontAwesome5.Folder} {dir}", _selectedFile == i,
237 | ImGuiSelectableFlags.AllowDoubleClick))
238 | {
239 | _selectedFile = i;
240 |
241 | if (Type == DialogType.Open) _bottomBarText = dir;
242 |
243 | if (ImGui.IsMouseDoubleClicked(0))
244 | {
245 | _currentDir = Path.Combine(_currentDir, dir);
246 | invalidateFileList();
247 | ImGui.PopID();
248 | break;
249 | }
250 | }
251 |
252 | ImGui.PopID();
253 | i++;
254 | }
255 |
256 | foreach (string file in _filesInCurrentDir)
257 | {
258 | ImGui.PushID(i);
259 | if (ImGui.Selectable($"{FontAwesome5.File} {file}", _selectedFile == i,
260 | ImGuiSelectableFlags.AllowDoubleClick))
261 | {
262 | _selectedFile = i;
263 |
264 | _bottomBarText = Path.GetFileNameWithoutExtension(file);
265 |
266 | FilePath = Path.Combine(_currentDir, file);
267 |
268 | if (ImGui.IsMouseDoubleClicked(0))
269 | {
270 | dialogAccept();
271 | ImGui.PopID();
272 | break;
273 | }
274 | }
275 |
276 | ImGui.PopID();
277 | i++;
278 | }
279 | }
280 |
281 | ImGui.EndChild();
282 | }
283 |
284 | private void renderBottomBar()
285 | {
286 | if (!CurrentDirectoryExists()) return;
287 |
288 | if (Type == DialogType.Save)
289 | {
290 | ImGui.PushItemWidth(-48 - (_fileSaveType.Length * 7) - 16);
291 | }
292 | else
293 | {
294 | ImGui.PushItemWidth(-48 - 9);
295 | }
296 |
297 | ImGui.InputText("##bottombar", ref _bottomBarText, 2048);
298 | //ImGuiNETExtensions.InputText("##bottombar", ref _bottomBarText);
299 |
300 | if (Type == DialogType.Save)
301 | {
302 | ImGui.SameLine();
303 | ImGui.Text(_fileSaveType);
304 | }
305 |
306 | ImGui.SameLine();
307 | if (Type == DialogType.Open)
308 | {
309 | if (ImGui.Button("Open", new Vector2(48, 0)))
310 | {
311 | if (_selectedFile >= _directoriesInCurrentDir.Count)
312 | {
313 | // selected file was a file
314 | dialogAccept();
315 | }
316 | else
317 | {
318 | // it was a directory
319 | _currentDir = Path.Combine(_currentDir, _bottomBarText);
320 | invalidateFileList();
321 | }
322 | }
323 | }
324 | else
325 | {
326 | if (ImGui.Button("Save", new Vector2(48, 0)))
327 | {
328 | FilePath = Path.Combine(_currentDir, _bottomBarText + _fileSaveType);
329 | dialogAccept();
330 | }
331 | }
332 | }
333 |
334 | private void dialogAccept()
335 | {
336 | OnDialogAccept?.Invoke(FilePath);
337 | _open = false;
338 | }
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/GUI/Components/FramebufferArea.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Numerics;
3 | using ImGuiNET;
4 | using LSDView.Graphics;
5 |
6 | namespace LSDView.GUI.Components
7 | {
8 | public class FramebufferArea : ImGuiComponent
9 | {
10 | private Vector2 _lastDimension;
11 | private readonly Framebuffer _framebuffer;
12 |
13 | public FramebufferArea(Framebuffer frameBuffer) { _framebuffer = frameBuffer; }
14 |
15 | protected override void renderSelf()
16 | {
17 | var region = ImGui.GetContentRegionAvail();
18 | if (_lastDimension != region)
19 | {
20 | _framebuffer.Resize((int)region.X, (int)region.Y);
21 | _lastDimension = region;
22 | }
23 |
24 | ImGui.Image((IntPtr)_framebuffer.Texture.Handle, region, new Vector2(0, 1), new Vector2(1, 0));
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/GUI/Components/GenericDialog.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace LSDView.GUI.Components
4 | {
5 | public class GenericDialog : ImGuiComponent
6 | {
7 | private readonly Action _dialogContents;
8 |
9 | public GenericDialog(Action dialogContents) { _dialogContents = dialogContents; }
10 |
11 | protected override void renderSelf() { _dialogContents(); }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/GUI/Components/InfoDialog.cs:
--------------------------------------------------------------------------------
1 | using ImGuiNET;
2 |
3 | namespace LSDView.GUI.GUIComponents
4 | {
5 | public class InfoDialog : ImGuiComponent
6 | {
7 | public enum DialogType
8 | {
9 | Info,
10 | Warning,
11 | Error
12 | }
13 |
14 | public DialogType Type { get; }
15 | public string Message { get; }
16 |
17 | // TODO: configurable buttons...
18 |
19 | public InfoDialog(DialogType type, string message)
20 | : base()
21 | {
22 | Type = type;
23 | Message = message;
24 | }
25 |
26 | protected override void renderSelf() { ImGui.Text(Message); }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/GUI/Components/LBDTileTreeNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using ImGuiNET;
4 | using libLSD.Formats;
5 | using LSDView.Graphics;
6 | using OpenTK;
7 |
8 | namespace LSDView.GUI.Components
9 | {
10 | public class LBDTileTreeNode : MeshListTreeNode
11 | {
12 | protected readonly IRenderable _tileMesh;
13 |
14 | public LBDTileTreeNode(string text,
15 | IRenderable tileMesh,
16 | LBDTile tile,
17 | List meshes) : base(text, meshes)
18 | {
19 | _tileMesh = tileMesh;
20 |
21 | var footstepSoundAndCollision = Convert.ToString(tile.FootstepSoundAndCollision, 2);
22 | var unknown = tile.UnknownFlag == 1 ? "true" : "false";
23 | _tooltip = () =>
24 | {
25 | ImGui.Text($"{footstepSoundAndCollision} - footstep/collision");
26 | ImGui.Text($"{unknown} - unknown");
27 | ImGui.Text($"{tile.UnknownFlag}");
28 | };
29 | }
30 |
31 | protected override void internalOnSelect()
32 | {
33 | _tileMesh.Material.SetParameter("ColorTint", new Vector4(1.5f, 1.5f, 1.5f, 1f), Vector4.One);
34 | }
35 |
36 | public override void OnDeselect() { _tileMesh.Material.SetParameter("ColorTint", Vector4.One, Vector4.One); }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/GUI/Components/MainMenuBar.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing.Imaging;
3 | using ImGuiNET;
4 | using LSDView.Controllers.GUI;
5 | using LSDView.Controllers.Interface;
6 | using LSDView.GUI.Components;
7 | using LSDView.Util;
8 | using OpenTK;
9 | using Serilog;
10 |
11 | namespace LSDView.GUI.GUIComponents
12 | {
13 | public class MainMenuBar : ImGuiComponent
14 | {
15 | private readonly FileDialog _openDialog;
16 | private readonly FileDialog _openVramDialog;
17 |
18 | private bool _openFileOpenDialog = false;
19 | private bool _openVramOpenDialog = false;
20 |
21 | private readonly IFileOpenController _fileOpenController;
22 | private readonly IVRAMController _vramController;
23 | private readonly IConfigController _configController;
24 | private readonly ICameraController _cameraController;
25 | private readonly GUIExportController _exportController;
26 |
27 | private string _gameDataPathFieldValue;
28 |
29 | public MainMenuBar(IFileOpenController fileOpenController,
30 | IVRAMController vramController,
31 | IConfigController configController,
32 | ICameraController cameraController,
33 | GUIExportController exportController)
34 | {
35 | _configController = configController;
36 | _gameDataPathFieldValue = _configController.Config.GameDataPath;
37 | _openDialog = new FileDialog(_configController.Config.GameDataPath, FileDialog.DialogType.Open);
38 | _openVramDialog = new FileDialog(_configController.Config.GameDataPath, FileDialog.DialogType.Open);
39 | _fileOpenController = fileOpenController;
40 | _vramController = vramController;
41 | _cameraController = cameraController;
42 | _exportController = exportController;
43 |
44 | _configController.Config.OnGameDataPathChange += () =>
45 | _openDialog.InitialDir = _configController.Config.GameDataPath;
46 | _configController.Config.OnGameDataPathChange += () =>
47 | _openVramDialog.InitialDir = _configController.Config.GameDataPath;
48 | }
49 |
50 | public void OpenSetGameDataPath()
51 | {
52 | createModal("Set game data path...", new GenericDialog(setGameDataPathDialog),
53 | new Vector2(500, 85));
54 | }
55 |
56 | protected override void renderSelf()
57 | {
58 | if (ImGui.BeginMainMenuBar())
59 | {
60 | if (ImGui.BeginMenu("File"))
61 | {
62 | renderFileMenu();
63 | ImGui.EndMenu();
64 | }
65 |
66 | if (ImGui.BeginMenu("VRAM"))
67 | {
68 | renderVramMenu();
69 | ImGui.EndMenu();
70 | }
71 |
72 | if (ImGui.BeginMenu("Help"))
73 | {
74 | renderHelpMenu();
75 | ImGui.EndMenu();
76 | }
77 | }
78 |
79 | ImGui.EndMainMenuBar();
80 |
81 | if (_openFileOpenDialog)
82 | {
83 | _openDialog.ShowDialog(path => _fileOpenController.OpenFile(path), ".lbd|.tix|.mom|.tmd|.tim");
84 | _openFileOpenDialog = false;
85 | }
86 |
87 | if (_openVramOpenDialog)
88 | {
89 | _openVramDialog.ShowDialog(path => _vramController.LoadTIXIntoVRAM(path), ".tix");
90 | _openVramOpenDialog = false;
91 | }
92 |
93 | _openDialog.Render();
94 | _openVramDialog.Render();
95 | }
96 |
97 | private void renderFileMenu()
98 | {
99 | if (ImGui.MenuItem("Open"))
100 | {
101 | _openFileOpenDialog = true;
102 | }
103 |
104 | if (ImGui.BeginMenu("Open Recent"))
105 | {
106 | if (_configController.Config.RecentFiles.Count > 0)
107 | {
108 | string fileToOpen = null;
109 | foreach (var recentFile in _configController.Config.RecentFiles)
110 | {
111 | try
112 | {
113 | var relPath = PathUtil.MakeRelative(recentFile,
114 | _configController.Config.GameDataPath);
115 | if (ImGui.MenuItem(relPath))
116 | {
117 | fileToOpen = recentFile;
118 | break;
119 | }
120 | }
121 | catch (UriFormatException e)
122 | {
123 | Log.Warning($"Invalid recent file: '{recentFile}', invalid URI: {e}");
124 | }
125 | }
126 |
127 | if (fileToOpen != null) _fileOpenController.OpenFile(fileToOpen);
128 | }
129 | else
130 | {
131 | ImGui.MenuItem("No recent files!");
132 | }
133 |
134 | ImGui.EndMenu();
135 | }
136 |
137 | ImGui.Separator();
138 |
139 | if (ImGui.MenuItem("Set game data path..."))
140 | {
141 | OpenSetGameDataPath();
142 | }
143 | }
144 |
145 | private void setGameDataPathDialog()
146 | {
147 | ImGui.PushItemWidth(-1);
148 | ImGui.InputText("##gamedata", ref _gameDataPathFieldValue, 1024);
149 | ImGui.PopItemWidth();
150 | ImGui.Spacing();
151 | ImGui.SameLine(ImGui.GetWindowWidth() - 30);
152 | if (ImGui.Button("Ok"))
153 | {
154 | _configController.Config.GameDataPath = _gameDataPathFieldValue;
155 | _configController.Save();
156 | destroyModal("Set game data path...");
157 | }
158 | }
159 |
160 | private void renderVramMenu()
161 | {
162 | if (ImGui.MenuItem("Load VRAM"))
163 | {
164 | _openVramOpenDialog = true;
165 | }
166 |
167 | if (!_vramController.VRAMLoaded) return;
168 |
169 | ImGui.Separator();
170 | if (ImGui.MenuItem("Export VRAM..."))
171 | {
172 | _exportController.OpenDialog(
173 | filePath =>
174 | {
175 | _exportController.ExportImages(_vramController.Tix, filePath, separate: false, ImageFormat.Png);
176 | },
177 | ".png");
178 | }
179 | }
180 |
181 | private void renderHelpMenu()
182 | {
183 | if (ImGui.MenuItem("About LSDView"))
184 | {
185 | createModal("About", new InfoDialog(InfoDialog.DialogType.Info, $@"LSDView {Version.String}
186 | LSD: Dream Emulator data viewer
187 | https://github.com/Figglewatts/LSDView
188 |
189 | Made by Figglewatts, 2020"),
190 | new Vector2(300, 100));
191 | }
192 |
193 | if (ImGui.MenuItem("Recenter view"))
194 | {
195 | _cameraController.RecenterView();
196 | }
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/GUI/Components/MeshListTreeNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using LSDView.Graphics;
4 |
5 | namespace LSDView.GUI.Components
6 | {
7 | public class MeshListTreeNode : TreeNode
8 | {
9 | public readonly List Meshes;
10 |
11 | public MeshListTreeNode(string text,
12 | List meshes,
13 | IEnumerable children = null,
14 | Action onSelect = null,
15 | ContextMenu contextMenu = null) :
16 | base(text, children, onSelect, contextMenu)
17 | {
18 | Meshes = meshes;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/GUI/Components/Modal.cs:
--------------------------------------------------------------------------------
1 | using LSDView.GUI.GUIComponents;
2 | using OpenTK;
3 |
4 | namespace LSDView.GUI.Components
5 | {
6 | public class Modal : ImGuiComponent
7 | {
8 | public string Name { get; }
9 | public string Content { get; }
10 |
11 | public Modal(string name, string content)
12 | {
13 | Name = name;
14 | Content = content;
15 | }
16 |
17 | public void ShowModal()
18 | {
19 | createModal(Name, new InfoDialog(InfoDialog.DialogType.Info, Content), new Vector2(500, 50));
20 | }
21 |
22 | protected override void renderSelf() { }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/GUI/Components/TreeNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using ImGuiNET;
5 |
6 | namespace LSDView.GUI.Components
7 | {
8 | public class TreeNode : ImGuiComponent
9 | {
10 | public string Text;
11 | public ImGuiTreeNodeFlags Flags;
12 | public Action OnSelect;
13 |
14 | public TreeNode(string text,
15 | IEnumerable children = null,
16 | Action onSelect = null,
17 | ContextMenu contextMenu = null)
18 | : base(contextMenu)
19 | {
20 | if (children != null)
21 | {
22 | _children.AddRange(children);
23 | }
24 | else
25 | {
26 | Flags |= ImGuiTreeNodeFlags.Leaf;
27 | }
28 |
29 | OnSelect = onSelect;
30 | Text = text;
31 | }
32 |
33 | public void AddNode(TreeNode node)
34 | {
35 | AddChild(node);
36 | Flags &= ~ImGuiTreeNodeFlags.Leaf;
37 | }
38 |
39 | public void AddNodes(IEnumerable nodes)
40 | {
41 | AddChildren(nodes);
42 | Flags &= ~ImGuiTreeNodeFlags.Leaf;
43 | }
44 |
45 | public void OnSelectInChildren(Action onSelect)
46 | {
47 | OnSelect = onSelect;
48 | foreach (var child in _children.OfType())
49 | {
50 | child.OnSelectInChildren(onSelect);
51 | }
52 | }
53 |
54 | public void FlagsInChildren(ImGuiTreeNodeFlags flags)
55 | {
56 | Flags |= flags;
57 | foreach (var child in _children.OfType())
58 | {
59 | child.FlagsInChildren(flags);
60 | }
61 | }
62 |
63 | protected virtual void internalOnSelect() { }
64 |
65 | public virtual void OnDeselect() { }
66 |
67 | protected override void renderSelf()
68 | {
69 | bool show = ImGui.TreeNodeEx(Text, Flags);
70 | if (ImGui.IsItemClicked())
71 | {
72 | OnSelect?.Invoke(this);
73 | internalOnSelect();
74 | }
75 |
76 | if (_tooltip != null && ImGui.IsItemHovered())
77 | {
78 | ImGui.BeginTooltip();
79 | _tooltip();
80 | ImGui.EndTooltip();
81 | }
82 |
83 | _contextMenu?.Render();
84 |
85 | if (show)
86 | {
87 | renderChildren();
88 | ImGui.TreePop();
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/GUI/Components/TreeView.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ImGuiNET;
3 |
4 | namespace LSDView.GUI.Components
5 | {
6 | public class TreeView : ImGuiComponent where T : TreeNode
7 | {
8 | public List Nodes;
9 |
10 | public TreeNode Selected => _selected;
11 |
12 | private TreeNode _selected = null;
13 |
14 | public TreeView() { Nodes = new List(); }
15 |
16 | public void Deselect()
17 | {
18 | if (_selected == null) return;
19 |
20 | _selected.Flags &= ~ImGuiTreeNodeFlags.Selected;
21 | _selected.OnDeselect();
22 | _selected = null;
23 | }
24 |
25 | public void SetNode(T node)
26 | {
27 | Nodes.Clear();
28 | Nodes.Add(node);
29 | _selected = node;
30 | node.Flags |= ImGuiTreeNodeFlags.Selected;
31 | }
32 |
33 | protected override void renderSelf()
34 | {
35 | ImGui.BeginChild("tree");
36 | foreach (var node in Nodes)
37 | {
38 | node.OnSelectInChildren(select);
39 | node.FlagsInChildren(ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.DefaultOpen |
40 | ImGuiTreeNodeFlags.OpenOnDoubleClick);
41 | node.Render();
42 | }
43 |
44 | ImGui.EndChild();
45 | }
46 |
47 | private void select(TreeNode node)
48 | {
49 | if (_selected != null)
50 | {
51 | _selected.Flags &= ~ImGuiTreeNodeFlags.Selected;
52 | _selected.OnDeselect();
53 | }
54 |
55 | _selected = node;
56 | node.Flags |= ImGuiTreeNodeFlags.Selected;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/GUI/ImGuiComponent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using ImGuiNET;
4 | using LSDView.GUI.Components;
5 | using OpenTK;
6 |
7 | namespace LSDView.GUI
8 | {
9 | public abstract class ImGuiComponent
10 | {
11 | public bool Show = true;
12 |
13 | protected readonly List _children;
14 | protected ContextMenu _contextMenu;
15 | protected Action _tooltip;
16 |
17 | private static int ModalCount = 0;
18 | private readonly Dictionary _modals;
19 | private readonly Queue _modalsToCreate;
20 | private readonly Queue _modalsToDestroy;
21 |
22 | protected ImGuiComponent(ContextMenu contextMenu = null, Action tooltip = null)
23 | {
24 | _children = new List();
25 | _modals = new Dictionary();
26 | _modalsToCreate = new Queue();
27 | _modalsToDestroy = new Queue();
28 | _contextMenu = contextMenu;
29 | _tooltip = tooltip;
30 | }
31 |
32 | public void Render()
33 | {
34 | if (Show)
35 | {
36 | renderSelf();
37 | }
38 |
39 | renderModals();
40 | }
41 |
42 | protected abstract void renderSelf();
43 |
44 | public virtual void AddChild(ImGuiComponent component) { _children.Add(component); }
45 |
46 | public virtual void AddChildren(IEnumerable components) { _children.AddRange(components); }
47 |
48 | protected void renderChildren()
49 | {
50 | foreach (var child in _children)
51 | {
52 | if (child.Show) child.Render();
53 | }
54 | }
55 |
56 | private void renderModals()
57 | {
58 | while (_modalsToCreate.Count > 0)
59 | {
60 | var modalName = _modalsToCreate.Dequeue();
61 | var modal = _modals[modalName];
62 | ImGui.SetNextWindowSize(new System.Numerics.Vector2(modal.InitialSize.X, modal.InitialSize.Y));
63 | ImGui.OpenPopup(modal.Id);
64 | }
65 |
66 | while (_modalsToDestroy.Count > 0)
67 | {
68 | _modals.Remove(_modalsToDestroy.Dequeue());
69 | }
70 |
71 | foreach (KeyValuePair kv in _modals)
72 | {
73 | if (ImGui.BeginPopupModal(kv.Value.Id, ref kv.Value.Active))
74 | {
75 | kv.Value.Component.Render();
76 | ImGui.EndPopup();
77 | }
78 |
79 | if (!kv.Value.Active)
80 | {
81 | _modalsToDestroy.Enqueue(kv.Key);
82 | }
83 | }
84 | }
85 |
86 | protected void createModal(string name, ImGuiComponent component, Vector2 size)
87 | {
88 | string actualName = $"{name}##{ModalCount++}";
89 |
90 | _modals[name] = new Modal(actualName, component, size);
91 | _modalsToCreate.Enqueue(name);
92 | }
93 |
94 | protected void destroyModal(string name)
95 | {
96 | if (!_modals.ContainsKey(name)) return;
97 | _modalsToDestroy.Enqueue(name);
98 | }
99 |
100 | internal class Modal
101 | {
102 | public readonly string Id;
103 | public bool Active = true;
104 | public readonly ImGuiComponent Component;
105 | public readonly Vector2 InitialSize;
106 |
107 | public Modal(string id, ImGuiComponent component, Vector2 initialSize)
108 | {
109 | Id = id;
110 | Component = component;
111 | InitialSize = initialSize;
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Graphics/Camera.cs:
--------------------------------------------------------------------------------
1 | using LSDView.Math;
2 | using OpenTK;
3 |
4 | namespace LSDView.Graphics
5 | {
6 | public class Camera
7 | {
8 | public Transform Transform { get; private set; }
9 |
10 | public Matrix4 View => Matrix4.LookAt(Transform.Position, Transform.Position + Transform.Forward, Transform.Up);
11 |
12 | public Camera() { Transform = new Transform(); }
13 |
14 | public void LookAt(Vector3 pos)
15 | {
16 | Vector3 forward = Transform.Forward;
17 | Vector3 newForward = Vector3.Normalize(pos - Transform.Position);
18 | Vector3 rotAxis = Vector3.Cross(forward, newForward);
19 | float angle = Vector3.CalculateAngle(forward, newForward);
20 |
21 | // rotate to face the pos
22 | Transform.Rotate(angle, rotAxis, false);
23 | }
24 |
25 | ///
26 | /// Rotate around a target at a distance.
27 | ///
28 | /// The angle to rotate around the target from east to west
29 | /// The angle to rotate around the target from north to south
30 | /// The target
31 | /// The distance the camera should be away from the target
32 | public void ArcBall(float longitude, float latitude, Vector3 target, float distance)
33 | {
34 | Transform.Position = Quaternion.FromAxisAngle(Transform.Right, latitude) *
35 | ((Transform.Position - target).Normalized() * distance) + target;
36 | Transform.Position = Quaternion.FromAxisAngle(Transform.Up, longitude) *
37 | ((Transform.Position - target).Normalized() * distance) + target;
38 | LookAt(target);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Graphics/Framebuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using OpenTK.Graphics.OpenGL4;
3 |
4 | namespace LSDView.Graphics
5 | {
6 | public class Framebuffer : IDisposable, IBindable
7 | {
8 | public int Width { get; private set; }
9 | public int Height { get; private set; }
10 | public FramebufferTarget Target { get; private set; }
11 |
12 | public Texture2D Texture => _colorAttachment;
13 |
14 | private readonly Texture2D _colorAttachment;
15 | private readonly Texture2D _depthAttachment;
16 | private readonly int _handle;
17 |
18 | public Framebuffer(int width,
19 | int height,
20 | FramebufferTarget target)
21 | {
22 | Width = width;
23 | Height = height;
24 | Target = target;
25 |
26 | _handle = GL.GenFramebuffer();
27 | _colorAttachment = new Texture2D(Width, Height);
28 | _depthAttachment = new Texture2D(Width, Height, null, PixelInternalFormat.DepthComponent32,
29 | PixelFormat.DepthComponent, PixelType.UnsignedInt);
30 |
31 | Bind();
32 |
33 | GL.FramebufferTexture2D(target, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D,
34 | _colorAttachment.Handle, 0);
35 | GL.FramebufferTexture2D(target, FramebufferAttachment.DepthAttachment, TextureTarget.Texture2D,
36 | _depthAttachment.Handle, 0);
37 |
38 | Unbind();
39 | }
40 |
41 | public void Resize(int newWidth, int newHeight)
42 | {
43 | _colorAttachment.Bind();
44 | GL.TexImage2D(TextureTarget.Texture2D, 0, _colorAttachment.InternalFormat, newWidth, newHeight, 0,
45 | _colorAttachment.Format, _colorAttachment.PixelType, IntPtr.Zero);
46 | _colorAttachment.Unbind();
47 |
48 | _depthAttachment.Bind();
49 | GL.TexImage2D(TextureTarget.Texture2D, 0, _depthAttachment.InternalFormat, newWidth, newHeight, 0,
50 | _depthAttachment.Format, _depthAttachment.PixelType, IntPtr.Zero);
51 | _depthAttachment.Unbind();
52 |
53 | Width = newWidth;
54 | Height = newHeight;
55 | }
56 |
57 | public void Dispose()
58 | {
59 | _colorAttachment.Dispose();
60 | _depthAttachment.Dispose();
61 | GL.DeleteFramebuffer(_handle);
62 | }
63 |
64 | public void Bind() { GL.BindFramebuffer(Target, _handle); }
65 | public void Unbind() { GL.BindFramebuffer(Target, 0); }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Graphics/GLBuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using OpenTK.Graphics.OpenGL4;
3 |
4 | namespace LSDView.Graphics
5 | {
6 | public class GLBuffer : IBindable, IDisposable where T : struct
7 | {
8 | private readonly int _handle;
9 | private readonly T[] _elements;
10 | private readonly int _elementSize;
11 | private readonly BufferTarget _target;
12 |
13 | public T[] Elements => _elements;
14 | public int Length => _elements.Length;
15 |
16 | public GLBuffer(T[] data, int elementSize, BufferTarget target = BufferTarget.ArrayBuffer)
17 | {
18 | _elements = data;
19 | _elementSize = elementSize;
20 | _target = target;
21 |
22 | _handle = GL.GenBuffer();
23 | Bind();
24 | GL.BufferData(target, (IntPtr)(_elementSize * _elements.Length), _elements, BufferUsageHint.StaticDraw);
25 | Unbind();
26 | }
27 |
28 | public void Bind() { GL.BindBuffer(_target, _handle); }
29 |
30 | public void Unbind() { GL.BindBuffer(_target, 0); }
31 |
32 | public void Dispose() { GL.DeleteBuffer(_handle); }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Graphics/Headless/HeadlessMesh.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using LSDView.Math;
4 | using OpenTK;
5 |
6 | namespace LSDView.Graphics.Headless
7 | {
8 | ///
9 | /// An implementation of IRenderable that doesn't need an OpenGL context.
10 | ///
11 | public class HeadlessMesh : IRenderable
12 | {
13 | public Transform Transform { get; }
14 | public List Textures { get; }
15 | public Material Material { get; }
16 | public IVertexArray Verts { get; }
17 |
18 | public HeadlessMesh(Vertex[] vertices, int[] indices)
19 | {
20 | Verts = new HeadlessVertexArray(vertices, indices);
21 | Transform = new Transform();
22 | Textures = new List();
23 | Material = null;
24 | }
25 |
26 | public static HeadlessMesh CreateQuad()
27 | {
28 | Vector3[] vertPositions =
29 | {
30 | new Vector3(-1, -1, 0),
31 | new Vector3(-1, 1, 0),
32 | new Vector3(1, 1, 0),
33 | new Vector3(1, -1, 0)
34 | };
35 |
36 | Vector2[] vertUVs =
37 | {
38 | new Vector2(0, 0),
39 | new Vector2(0, 1),
40 | new Vector2(1, 1),
41 | new Vector2(1, 0)
42 | };
43 |
44 | return new HeadlessMesh(
45 | new[]
46 | {
47 | new Vertex(
48 | vertPositions[0], null, vertUVs[0]),
49 | new Vertex(
50 | vertPositions[1], null, vertUVs[1]),
51 | new Vertex(
52 | vertPositions[2], null, vertUVs[2]),
53 | new Vertex(
54 | vertPositions[3], null, vertUVs[3])
55 | },
56 | new[] { 1, 0, 2, 2, 0, 3 }
57 | );
58 | }
59 |
60 | public void Render(Matrix4 viewMatrix, Matrix4 projectionMatrix)
61 | {
62 | // intentionally empty
63 | }
64 |
65 | public void Dispose()
66 | {
67 | // intentionally empty
68 | }
69 |
70 | public static HeadlessMesh CombineMeshes(params IRenderable[] renderables)
71 | {
72 | var totalVertexCount = renderables.Sum(r => r.Verts.Vertices.Length);
73 | var totalIndexCount = renderables.Sum(r => r.Verts.Length);
74 |
75 | var verts = new Vertex[totalVertexCount];
76 | var indices = new int[totalIndexCount];
77 |
78 | int vertsRunningCount = 0;
79 | int indicesRunningCount = 0;
80 | foreach (var renderable in renderables)
81 | {
82 | renderable.Verts.CopyVertices(verts, vertsRunningCount, renderable.Transform);
83 | renderable.Verts.CopyIndices(indices, vertsRunningCount, indicesRunningCount);
84 |
85 | vertsRunningCount += renderable.Verts.Vertices.Length;
86 | indicesRunningCount += renderable.Verts.Length;
87 | }
88 |
89 | return new HeadlessMesh(verts, indices);
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Graphics/Headless/HeadlessTexture2D.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Graphics.Headless
2 | {
3 | public class HeadlessTexture2D : ITexture2D
4 | {
5 | public int Width { get; }
6 | public int Height { get; }
7 |
8 | protected readonly float[] _data;
9 |
10 | public HeadlessTexture2D(int width, int height, float[] data)
11 | {
12 | Width = width;
13 | Height = height;
14 | _data = data;
15 | }
16 |
17 | public void Dispose()
18 | {
19 | // intentionally empty
20 | }
21 |
22 | public void Bind()
23 | {
24 | // intentionally empty
25 | }
26 |
27 | public void Unbind()
28 | {
29 | // intentionally empty
30 | }
31 |
32 | public void SubImage(float[] data, int x, int y, int width, int height)
33 | {
34 | // this basically just emulates glTexSubImage2D()
35 | int componentsWritten = 0;
36 | int tStart = Height - y - 1 - (height - 1);
37 | for (int t = tStart; t < tStart + height; t++)
38 | {
39 | for (int s = x; s < x + width; s++)
40 | {
41 | int idx = t * Width * 4 + s * 4;
42 | _data[idx] = data[componentsWritten];
43 | _data[idx + 1] = data[componentsWritten + 1];
44 | _data[idx + 2] = data[componentsWritten + 2];
45 | _data[idx + 3] = data[componentsWritten + 3];
46 | componentsWritten += 4;
47 | }
48 | }
49 | }
50 |
51 | public float[] GetData() => _data;
52 |
53 | public void Clear()
54 | {
55 | for (int i = 0; i < Width * Height; i++)
56 | {
57 | _data[i] = 1;
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Graphics/Headless/HeadlessVertexArray.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Graphics.Headless
2 | {
3 | ///
4 | /// An implementation of IVertexArray that doesn't need an OpenGL context.
5 | ///
6 | public class HeadlessVertexArray : IVertexArray
7 | {
8 | public Vertex[] Vertices => _verts;
9 | public int[] Indices => _indices;
10 | public int Length => _indices.Length;
11 | public int Tris => Length / 3;
12 |
13 | protected readonly Vertex[] _verts;
14 | protected readonly int[] _indices;
15 |
16 | public HeadlessVertexArray(Vertex[] vertices, int[] indices)
17 | {
18 | _verts = vertices;
19 | _indices = indices;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Graphics/IBindable.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Graphics
2 | {
3 | public interface IBindable
4 | {
5 | void Bind();
6 | void Unbind();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Graphics/IDisposable.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Graphics
2 | {
3 | public interface IDisposable
4 | {
5 | void Dispose();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Graphics/IRenderable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using LSDView.Math;
3 | using OpenTK;
4 |
5 | namespace LSDView.Graphics
6 | {
7 | public interface IRenderable : IDisposable
8 | {
9 | Transform Transform { get; }
10 | List Textures { get; }
11 | Material Material { get; }
12 | IVertexArray Verts { get; }
13 |
14 |
15 | void Render(Matrix4 viewMatrix, Matrix4 projectionMatrix);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Graphics/ITexture2D.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Graphics
2 | {
3 | public interface ITexture2D : IDisposable, IBindable
4 | {
5 | int Width { get; }
6 | int Height { get; }
7 |
8 | void SubImage(float[] data, int x, int y, int width, int height);
9 | float[] GetData();
10 | void Clear();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Graphics/IVertexArray.cs:
--------------------------------------------------------------------------------
1 | namespace LSDView.Graphics
2 | {
3 | public interface IVertexArray
4 | {
5 | Vertex[] Vertices { get; }
6 | int[] Indices { get; }
7 | int Length { get; }
8 | int Tris { get; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Graphics/IVertexArrayExtensions.cs:
--------------------------------------------------------------------------------
1 | using LSDView.Math;
2 |
3 | namespace LSDView.Graphics
4 | {
5 | public static class IVertexArrayExtensions
6 | {
7 | public static void CopyVertices(this IVertexArray vertexArray,
8 | Vertex[] vertices,
9 | int startIndex = 0,
10 | Transform transform = null)
11 | {
12 | for (int i = 0; i < vertexArray.Vertices.Length; i++)
13 | {
14 | vertices[startIndex + i] = vertexArray.Vertices[i];
15 |
16 | // if a transform is given, transform the copied vertices with it
17 | if (transform != null) vertices[startIndex + i].Transform(transform);
18 | }
19 | }
20 |
21 | public static void CopyIndices(this IVertexArray vertexArray, int[] indices, int indexBase, int startIndex = 0)
22 | {
23 | for (int i = 0; i < vertexArray.Length; i++)
24 | {
25 | indices[startIndex + i] = vertexArray.Indices[i] + indexBase;
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Graphics/Material.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using OpenTK;
4 |
5 | namespace LSDView.Graphics
6 | {
7 | public class Material : IBindable
8 | {
9 | protected struct MaterialParameter
10 | {
11 | public readonly MaterialParameterType ParamType;
12 | public readonly object Value;
13 | public readonly object DefaultValue;
14 |
15 | public MaterialParameter(MaterialParameterType type, object value, object defaultValue)
16 | {
17 | ParamType = type;
18 | Value = value;
19 | DefaultValue = defaultValue;
20 | }
21 |
22 | public void ApplyTo(Shader shader, string withUniformName)
23 | {
24 | switch (ParamType)
25 | {
26 | case MaterialParameterType.Bool:
27 | shader.Uniform(withUniformName, (bool)Value);
28 | break;
29 | case MaterialParameterType.Int:
30 | shader.Uniform(withUniformName, (int)Value);
31 | break;
32 | case MaterialParameterType.Float:
33 | shader.Uniform(withUniformName, (float)Value);
34 | break;
35 | case MaterialParameterType.Matrix3:
36 | shader.Uniform(withUniformName, false, (Matrix3)Value);
37 | break;
38 | case MaterialParameterType.Matrix4:
39 | shader.Uniform(withUniformName, false, (Matrix4)Value);
40 | break;
41 | case MaterialParameterType.Vector2:
42 | shader.Uniform(withUniformName, (Vector2)Value);
43 | break;
44 | case MaterialParameterType.Vector3:
45 | shader.Uniform(withUniformName, (Vector3)Value);
46 | break;
47 | case MaterialParameterType.Vector4:
48 | shader.Uniform(withUniformName, (Vector4)Value);
49 | break;
50 | default:
51 | throw new ArgumentOutOfRangeException(nameof(ParamType), ParamType,
52 | "Unknown MaterialParameterType");
53 | }
54 | }
55 |
56 | public void UnapplyTo(Shader shader, string withUniformName)
57 | {
58 | switch (ParamType)
59 | {
60 | case MaterialParameterType.Bool:
61 | shader.Uniform(withUniformName, (bool)DefaultValue);
62 | break;
63 | case MaterialParameterType.Int:
64 | shader.Uniform(withUniformName, (int)DefaultValue);
65 | break;
66 | case MaterialParameterType.Float:
67 | shader.Uniform(withUniformName, (float)DefaultValue);
68 | break;
69 | case MaterialParameterType.Matrix3:
70 | shader.Uniform(withUniformName, false, (Matrix3)DefaultValue);
71 | break;
72 | case MaterialParameterType.Matrix4:
73 | shader.Uniform(withUniformName, false, (Matrix4)DefaultValue);
74 | break;
75 | case MaterialParameterType.Vector2:
76 | shader.Uniform(withUniformName, (Vector2)DefaultValue);
77 | break;
78 | case MaterialParameterType.Vector3:
79 | shader.Uniform(withUniformName, (Vector3)DefaultValue);
80 | break;
81 | case MaterialParameterType.Vector4:
82 | shader.Uniform(withUniformName, (Vector4)DefaultValue);
83 | break;
84 | default:
85 | throw new ArgumentOutOfRangeException(nameof(ParamType), ParamType,
86 | "Unknown MaterialParameterType");
87 | }
88 | }
89 | }
90 |
91 | protected enum MaterialParameterType
92 | {
93 | Bool,
94 | Int,
95 | Float,
96 | Matrix4,
97 | Matrix3,
98 | Vector2,
99 | Vector3,
100 | Vector4
101 | }
102 |
103 | public readonly Shader Shader;
104 | protected readonly Dictionary _parameters;
105 |
106 | public Material(Shader shader)
107 | {
108 | Shader = shader;
109 | _parameters = new Dictionary();
110 | }
111 |
112 | public void SetParameter(string name, bool value, bool defaultValue = default)
113 | {
114 | _parameters[name] = new MaterialParameter(MaterialParameterType.Bool, value, defaultValue);
115 | }
116 |
117 | public void SetParameter(string name, int value, int defaultValue = default)
118 | {
119 | _parameters[name] = new MaterialParameter(MaterialParameterType.Int, value, defaultValue);
120 | }
121 |
122 | public void SetParameter(string name, float value, float defaultValue = default)
123 | {
124 | _parameters[name] = new MaterialParameter(MaterialParameterType.Float, value, defaultValue);
125 | }
126 |
127 | public void SetParameter(string name, Matrix4 value, Matrix4 defaultValue = default)
128 | {
129 | _parameters[name] = new MaterialParameter(MaterialParameterType.Matrix4, value, defaultValue);
130 | }
131 |
132 | public void SetParameter(string name, Matrix3 value, Matrix3 defaultValue = default)
133 | {
134 | _parameters[name] = new MaterialParameter(MaterialParameterType.Matrix3, value, defaultValue);
135 | }
136 |
137 | public void SetParameter(string name, Vector2 value, Vector2 defaultValue = default)
138 | {
139 | _parameters[name] = new MaterialParameter(MaterialParameterType.Vector2, value, defaultValue);
140 | }
141 |
142 | public void SetParameter(string name, Vector3 value, Vector3 defaultValue = default)
143 | {
144 | _parameters[name] = new MaterialParameter(MaterialParameterType.Vector3, value, defaultValue);
145 | }
146 |
147 | public void SetParameter(string name, Vector4 value, Vector4 defaultValue = default)
148 | {
149 | _parameters[name] = new MaterialParameter(MaterialParameterType.Vector4, value, defaultValue);
150 | }
151 |
152 | protected void apply()
153 | {
154 | foreach (var paramElt in _parameters)
155 | {
156 | var paramName = paramElt.Key;
157 | var param = paramElt.Value;
158 | param.ApplyTo(Shader, paramName);
159 | }
160 | }
161 |
162 | protected void unapply()
163 | {
164 | foreach (var paramElt in _parameters)
165 | {
166 | var paramName = paramElt.Key;
167 | var param = paramElt.Value;
168 | param.UnapplyTo(Shader, paramName);
169 | }
170 | }
171 |
172 | public void Bind()
173 | {
174 | Shader.Bind();
175 | apply();
176 | }
177 |
178 | public void Unbind()
179 | {
180 | unapply();
181 | Shader.Unbind();
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/Graphics/Mesh.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using LSDView.Math;
4 | using OpenTK;
5 | using OpenTK.Graphics.OpenGL4;
6 |
7 | namespace LSDView.Graphics
8 | {
9 | public class Mesh : IRenderable
10 | {
11 | protected readonly VertexArray _verts;
12 |
13 | public IVertexArray Verts => _verts;
14 |
15 | public Material Material { get; set; }
16 | public Transform Transform { get; set; }
17 | public List Textures { get; set; }
18 |
19 | public Mesh(Vertex[] vertices, int[] indices, Shader shader)
20 | {
21 | _verts = new VertexArray(vertices, indices);
22 | Material = new Material(shader);
23 | Transform = new Transform();
24 | Textures = new List();
25 | }
26 |
27 | public static Mesh CreateQuad(Shader shader)
28 | {
29 | Vector3[] vertPositions =
30 | {
31 | new Vector3(-1, -1, 0),
32 | new Vector3(-1, 1, 0),
33 | new Vector3(1, 1, 0),
34 | new Vector3(1, -1, 0)
35 | };
36 |
37 | Vector2[] vertUVs =
38 | {
39 | new Vector2(0, 0),
40 | new Vector2(0, 1),
41 | new Vector2(1, 1),
42 | new Vector2(1, 0)
43 | };
44 |
45 | return new Mesh(
46 | new[]
47 | {
48 | new Vertex(
49 | vertPositions[0], null, vertUVs[0]),
50 | new Vertex(
51 | vertPositions[1], null, vertUVs[1]),
52 | new Vertex(
53 | vertPositions[2], null, vertUVs[2]),
54 | new Vertex(
55 | vertPositions[3], null, vertUVs[3])
56 | },
57 | new[] { 1, 0, 2, 2, 0, 3 },
58 | shader
59 | );
60 | }
61 |
62 | public void Render(Matrix4 view, Matrix4 projection)
63 | {
64 | _verts.Bind();
65 | bindTextures();
66 | Material.Bind();
67 | Material.Shader.Uniform("Projection", false, projection);
68 | Material.Shader.Uniform("View", false, view);
69 | Material.Shader.Uniform("Model", false, Transform.Matrix);
70 | GL.DrawElements(PrimitiveType.Triangles, _verts.Length, DrawElementsType.UnsignedInt, 0);
71 | Material.Unbind();
72 | unbindTextures();
73 | _verts.Unbind();
74 | }
75 |
76 | public void Dispose() { _verts.Dispose(); }
77 |
78 | public void ClearTextures()
79 | {
80 | foreach (ITexture2D tex in Textures)
81 | {
82 | tex.Dispose();
83 | }
84 |
85 | Textures.Clear();
86 | }
87 |
88 | public static Mesh CombineMeshes(Shader shader = null, params IRenderable[] renderables)
89 | {
90 | var totalVertexCount = renderables.Sum(r => r.Verts.Vertices.Length);
91 | var totalIndexCount = renderables.Sum(r => r.Verts.Length);
92 |
93 | var verts = new Vertex[totalVertexCount];
94 | var indices = new int[totalIndexCount];
95 |
96 | int vertsRunningCount = 0;
97 | int indicesRunningCount = 0;
98 | foreach (var renderable in renderables)
99 | {
100 | renderable.Verts.CopyVertices(verts, vertsRunningCount, renderable.Transform);
101 | renderable.Verts.CopyIndices(indices, indicesRunningCount);
102 |
103 | vertsRunningCount += renderable.Verts.Vertices.Length;
104 | indicesRunningCount += renderable.Verts.Length;
105 | }
106 |
107 | return new Mesh(verts, indices, shader);
108 | }
109 |
110 | protected void bindTextures()
111 | {
112 | for (int i = 0; i < Textures.Count; i++)
113 | {
114 | GL.ActiveTexture(TextureUnit.Texture0 + i);
115 | Textures[i].Bind();
116 | }
117 |
118 | GL.ActiveTexture(TextureUnit.Texture0);
119 | }
120 |
121 | protected void unbindTextures()
122 | {
123 | for (int i = 0; i < Textures.Count; i++)
124 | {
125 | GL.ActiveTexture(TextureUnit.Texture0 + i);
126 | Textures[i].Unbind();
127 | }
128 |
129 | GL.ActiveTexture(TextureUnit.Texture0);
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Graphics/Shader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using OpenTK;
4 | using OpenTK.Graphics.OpenGL4;
5 | using Serilog;
6 |
7 | namespace LSDView.Graphics
8 | {
9 | public class Shader : IBindable, IDisposable
10 | {
11 | private int _vertHandle;
12 | private int _fragHandle;
13 | private int _progHandle;
14 |
15 | public string Name { get; }
16 |
17 | public Shader(string name, string path)
18 | {
19 | this.Name = name;
20 | compileAndLink(path);
21 | }
22 |
23 | private void compileAndLink(string path)
24 | {
25 | // load and compile vertex shader
26 | _vertHandle = loadSource(path, ShaderType.VertexShader);
27 | GL.CompileShader(_vertHandle);
28 | checkCompileErr(_vertHandle, ShaderType.VertexShader);
29 |
30 | // load and compile fragment shader
31 | _fragHandle = loadSource(path, ShaderType.FragmentShader);
32 | GL.CompileShader(_fragHandle);
33 | checkCompileErr(_fragHandle, ShaderType.FragmentShader);
34 |
35 | // link the shader program
36 | _progHandle = GL.CreateProgram();
37 | GL.AttachShader(_progHandle, _vertHandle);
38 | GL.AttachShader(_progHandle, _fragHandle);
39 | GL.LinkProgram(_progHandle);
40 | checkLinkErr(_progHandle);
41 |
42 | // clean up unused handles
43 | GL.DeleteShader(_vertHandle);
44 | GL.DeleteShader(_fragHandle);
45 | _vertHandle = _fragHandle = 0;
46 | }
47 |
48 | private int loadSource(string path, ShaderType type)
49 | {
50 | string completePath = path;
51 | if (type == ShaderType.VertexShader)
52 | path += ".vert";
53 | else if (type == ShaderType.FragmentShader)
54 | path += ".frag";
55 | else
56 | throw new ArgumentException("loadSource expects ShaderType to be Vertex or Fragment shader");
57 |
58 | string source = File.ReadAllText(path);
59 | int handle = GL.CreateShader(type);
60 | GL.ShaderSource(handle, source);
61 | return handle;
62 | }
63 |
64 | private bool checkCompileErr(int shader, ShaderType type)
65 | {
66 | GL.GetShader(shader, ShaderParameter.CompileStatus, out int success);
67 | if (success != 1)
68 | {
69 | string infoLog = GL.GetShaderInfoLog(shader);
70 | Log.Error("Could not compile {0}: {1}", type.ToString(), Name);
71 | Log.Error("Info log: {0}", infoLog);
72 | }
73 |
74 | return success == 1;
75 | }
76 |
77 | private bool checkLinkErr(int program)
78 | {
79 | GL.GetProgram(program, GetProgramParameterName.LinkStatus, out int success);
80 | if (success != 1)
81 | {
82 | string infoLog = GL.GetProgramInfoLog(program);
83 | Log.Error("Could not link program: {0}", Name);
84 | Log.Error("Info log: {0}", infoLog);
85 | }
86 |
87 | return success == 1;
88 | }
89 |
90 | private int getUniformLocation(string name) { return GL.GetUniformLocation(_progHandle, name); }
91 |
92 | public void Uniform(string name, bool value) { GL.Uniform1(getUniformLocation(name), value ? 1 : 0); }
93 |
94 | public void Uniform(string name, int value) { GL.Uniform1(getUniformLocation(name), value); }
95 |
96 | public void Uniform(string name, float value) { GL.Uniform1(getUniformLocation(name), value); }
97 |
98 | public void Uniform(string name, bool transpose, Matrix4 value)
99 | {
100 | GL.UniformMatrix4(getUniformLocation(name), transpose, ref value);
101 | }
102 |
103 | public void Uniform(string name, bool transpose, Matrix3 value)
104 | {
105 | GL.UniformMatrix3(getUniformLocation(name), transpose, ref value);
106 | }
107 |
108 | public void Uniform(string name, Vector2 value) { GL.Uniform2(getUniformLocation(name), value); }
109 |
110 | public void Uniform(string name, Vector3 value) { GL.Uniform3(getUniformLocation(name), value); }
111 |
112 | public void Uniform(string name, Vector4 value) { GL.Uniform4(getUniformLocation(name), value); }
113 |
114 | public int GetAttribLocation(string name) { return GL.GetAttribLocation(_progHandle, name); }
115 |
116 | public void Bind() { GL.UseProgram(_progHandle); }
117 |
118 | public void Unbind() { GL.UseProgram(0); }
119 |
120 | public void Dispose() { GL.DeleteProgram(_progHandle); }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Graphics/Texture2D.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using OpenTK.Graphics;
3 | using OpenTK.Graphics.OpenGL4;
4 |
5 | namespace LSDView.Graphics
6 | {
7 | public class Texture2D : ITexture2D
8 | {
9 | public int Width { get; }
10 | public int Height { get; }
11 |
12 | public PixelInternalFormat InternalFormat { get; }
13 | public PixelFormat Format { get; }
14 | public PixelType PixelType { get; }
15 |
16 | public int Handle => _handle;
17 |
18 | private readonly int _handle;
19 |
20 | public Texture2D(
21 | int width,
22 | int height,
23 | float[] data = null,
24 | PixelInternalFormat internalFormat = PixelInternalFormat.Rgba32f,
25 | PixelFormat colorFormat = PixelFormat.Rgba,
26 | PixelType dataType = PixelType.Float)
27 | {
28 | _handle = GL.GenTexture();
29 | Bind();
30 | Width = width;
31 | Height = height;
32 | InternalFormat = internalFormat;
33 | Format = colorFormat;
34 | PixelType = dataType;
35 |
36 | if (data != null)
37 | {
38 | GL.TexImage2D(TextureTarget.Texture2D, 0, internalFormat, width, height, 0, colorFormat,
39 | dataType, data);
40 | }
41 | else
42 | {
43 | GL.TexImage2D(TextureTarget.Texture2D, 0, internalFormat, Width, Height, 0, colorFormat, dataType,
44 | IntPtr.Zero);
45 | }
46 |
47 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS,
48 | (int)TextureWrapMode.ClampToEdge);
49 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT,
50 | (int)TextureWrapMode.ClampToEdge);
51 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
52 | (int)TextureMinFilter.Nearest);
53 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
54 | (int)TextureMagFilter.Nearest);
55 | Unbind();
56 | }
57 |
58 | public static Texture2D Fill(Color4 color, int width, int height)
59 | {
60 | float[] colorData = new float[width * height * 4];
61 | for (int i = 0; i < width * height * 4; i += 4)
62 | {
63 | colorData[i] = color.R;
64 | colorData[i + 1] = color.G;
65 | colorData[i + 2] = color.B;
66 | colorData[i + 3] = color.A;
67 | }
68 |
69 | return new Texture2D(width, height, colorData);
70 | }
71 |
72 | public void Dispose() { GL.DeleteTexture(_handle); }
73 |
74 | public void Bind() { GL.BindTexture(TextureTarget.Texture2D, _handle); }
75 |
76 | public void Unbind() { GL.BindTexture(TextureTarget.Texture2D, 0); }
77 |
78 | public void SubImage(float[] data, int x, int y, int width, int height)
79 | {
80 | Bind();
81 | GL.TexSubImage2D(TextureTarget.Texture2D, 0, x, y, width, height, PixelFormat.Rgba, PixelType.Float, data);
82 | Unbind();
83 | }
84 |
85 | public float[] GetData()
86 | {
87 | if (PixelType != PixelType.Float)
88 | {
89 | throw new NotSupportedException(
90 | "Unable to get pixel data for texture with pixel type not equal to 'Float'");
91 | }
92 |
93 | if (Format != PixelFormat.Rgba)
94 | {
95 | throw new NotSupportedException("Unable to get pixel data for texture with non-RGBA format");
96 | }
97 |
98 | float[] tex = new float[Width * Height * 4];
99 | Bind();
100 | GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Rgba, PixelType.Float, tex);
101 | Unbind();
102 |
103 | // flip the texture data
104 | float[] flippedTex = new float[Width * Height * 4];
105 | int i = 0;
106 | for (int y = Height - 1; y >= 0; y--)
107 | {
108 | for (int x = 0; x < Width; x++)
109 | {
110 | flippedTex[i] = tex[y * (Width * 4) + (x * 4)];
111 | flippedTex[i + 1] = tex[y * (Width * 4) + (x * 4) + 1];
112 | flippedTex[i + 2] = tex[y * (Width * 4) + (x * 4) + 2];
113 | flippedTex[i + 3] = tex[y * (Width * 4) + (x * 4) + 3];
114 | i += 4;
115 | }
116 | }
117 |
118 | return flippedTex;
119 | }
120 |
121 | public void Clear()
122 | {
123 | GL.ClearTexImage(_handle, 0, PixelFormat.Rgba, PixelType.Float, new float[] { 1, 1, 1, 1 });
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Graphics/Vertex.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using LSDView.Math;
3 | using OpenTK;
4 |
5 | namespace LSDView.Graphics
6 | {
7 | [StructLayout(LayoutKind.Sequential, Pack = 1)]
8 | public struct Vertex
9 | {
10 | public static readonly int Size = (3 + 3 + 2 + 4) * 4; // size in bytes
11 | public Vector3 Position;
12 | public Vector3 Normal;
13 | public Vector2 TexCoord;
14 | public Vector4 Color;
15 |
16 | public Vertex(Vector3 position,
17 | Vector3? normal = null,
18 | Vector2? texCoord = null,
19 | Vector4? color = null)
20 | {
21 | Position = position;
22 | Normal = normal ?? Vector3.Zero;
23 | TexCoord = texCoord ?? Vector2.Zero;
24 | Color = color ?? Vector4.One;
25 | }
26 |
27 | ///
28 | /// Manually applies a transformation to this vertex's position.
29 | /// This should only be used once, as multiple times will keep reapplying the transform.
30 | ///
31 | /// The Transform to apply to this vertex.
32 | public void Transform(Transform transform) { Position = (new Vector4(Position, 1) * transform.Matrix).Xyz; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Graphics/VertexArray.cs:
--------------------------------------------------------------------------------
1 | using OpenTK.Graphics.OpenGL4;
2 |
3 | namespace LSDView.Graphics
4 | {
5 | public class VertexArray : IVertexArray, IBindable, IDisposable
6 | {
7 | private readonly int _handle;
8 | private readonly GLBuffer _vertexBuffer;
9 | private readonly GLBuffer _indexBuffer;
10 |
11 | private readonly VertexAttrib[] _attribs = new VertexAttrib[]
12 | {
13 | new VertexAttrib(0, 3, VertexAttribPointerType.Float, Vertex.Size, 0), // in_Position
14 | new VertexAttrib(1, 3, VertexAttribPointerType.Float, Vertex.Size, 3 * 4), // in_Normal
15 | new VertexAttrib(2, 2, VertexAttribPointerType.Float, Vertex.Size, (3 + 3) * 4), // in_TexCoord
16 | new VertexAttrib(3, 4, VertexAttribPointerType.Float, Vertex.Size, (3 + 3 + 2) * 4) // in_Color
17 | };
18 |
19 | public Vertex[] Vertices => _vertexBuffer.Elements;
20 | public int[] Indices => _indexBuffer.Elements;
21 | public int Length => _indexBuffer.Length;
22 | public int Tris => Length / 3;
23 |
24 | public VertexArray(Vertex[] vertices, int[] indices)
25 | {
26 | _handle = GL.GenVertexArray();
27 | Bind();
28 |
29 | _vertexBuffer = new GLBuffer(vertices, Vertex.Size, BufferTarget.ArrayBuffer);
30 | _indexBuffer = new GLBuffer(indices, sizeof(int), BufferTarget.ElementArrayBuffer);
31 |
32 | _vertexBuffer.Bind();
33 | _indexBuffer.Bind();
34 | foreach (var attrib in _attribs)
35 | {
36 | attrib.Set();
37 | }
38 |
39 | Unbind();
40 | }
41 |
42 | public void Bind() { GL.BindVertexArray(_handle); }
43 |
44 | public void Unbind() { GL.BindVertexArray(0); }
45 |
46 | public void Dispose()
47 | {
48 | _vertexBuffer.Dispose();
49 | _indexBuffer.Dispose();
50 | GL.DeleteVertexArray(_handle);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Graphics/VertexAttrib.cs:
--------------------------------------------------------------------------------
1 | using OpenTK.Graphics.OpenGL4;
2 |
3 | namespace LSDView.Graphics
4 | {
5 | public class VertexAttrib
6 | {
7 | private readonly int _index;
8 | private readonly int _size;
9 | private readonly VertexAttribPointerType _type;
10 | private readonly bool _normalize;
11 | private readonly int _stride;
12 | private readonly int _offset;
13 |
14 | public VertexAttrib(int index,
15 | int size,
16 | VertexAttribPointerType type,
17 | int stride,
18 | int offset,
19 | bool normalize = false)
20 | {
21 | _index = index;
22 | _size = size;
23 | _type = type;
24 | _stride = stride;
25 | _offset = offset;
26 | _normalize = normalize;
27 | }
28 |
29 | public void Set()
30 | {
31 | GL.EnableVertexAttribArray(_index);
32 | GL.VertexAttribPointer(_index, _size, _type, _normalize, _stride, _offset);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/GuiApplication.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using Autofac;
5 | using LSDView.Controller;
6 | using LSDView.Controllers;
7 | using LSDView.Controllers.GUI;
8 | using LSDView.Graphics;
9 | using LSDView.GUI;
10 | using LSDView.GUI.Components;
11 | using LSDView.GUI.GUIComponents;
12 | using OpenTK;
13 | using OpenTK.Graphics;
14 | using OpenTK.Graphics.ES30;
15 | using OpenTK.Input;
16 | using Serilog;
17 | using FramebufferTarget = OpenTK.Graphics.OpenGL4.FramebufferTarget;
18 | using Vector2 = System.Numerics.Vector2;
19 |
20 | namespace LSDView
21 | {
22 | public class GuiApplication : GameWindow
23 | {
24 | public static GuiApplication Instance = null;
25 |
26 | public Action NextUpdateActions;
27 | public Action NextGuiRender;
28 |
29 | public Matrix4 ProjectionMatrix => _proj;
30 | public Camera Camera => _cam;
31 | public Vector2 Dimensions => new Vector2(Width, Height);
32 |
33 | private const int WINDOW_WIDTH = 800;
34 | private const int WINDOW_HEIGHT = 600;
35 | private const string WINDOW_TITLE = "LSDView";
36 | private const int GL_MAJOR_VERSION = 4;
37 | private const int GL_MINOR_VERSION = 0;
38 |
39 | private readonly List _guiComponents;
40 |
41 | private readonly Camera _cam;
42 | private Matrix4 _proj;
43 | private readonly Framebuffer _fbo;
44 |
45 | // ui
46 | private FileDialog _fileExportDialog;
47 | private Modal _updateAvailableModal;
48 |
49 | // --------------
50 |
51 | // controllers
52 | private TreeController _treeController;
53 | private VRAMController _vramController;
54 | private CameraController _cameraController;
55 | private ConfigController _configController;
56 | private FileOpenController _fileOpenController;
57 | private AnimationController _animationController;
58 | private GUIExportController _exportController;
59 | private UpdateCheckerController _updateCheckerController;
60 |
61 | // --------------
62 |
63 | public GuiApplication(ILifetimeScope scope) : base(WINDOW_WIDTH, WINDOW_HEIGHT, GraphicsMode.Default,
64 | WINDOW_TITLE,
65 | GameWindowFlags.Default, DisplayDevice.Default, GL_MAJOR_VERSION, GL_MINOR_VERSION,
66 | GraphicsContextFlags.Default)
67 | {
68 | Instance = this;
69 |
70 | createControllers(scope);
71 |
72 | Icon = new Icon(typeof(GuiApplication), "appicon.ico");
73 |
74 | GL.Enable(EnableCap.DepthTest);
75 | GL.Enable(EnableCap.CullFace);
76 |
77 | _cam = new Camera();
78 | _cameraController.ProvideCamera(_cam);
79 | _cam.Transform.Translate(new Vector3(0, 0, -3));
80 | _fbo = new Framebuffer(WINDOW_WIDTH, WINDOW_HEIGHT, FramebufferTarget.Framebuffer);
81 |
82 | _proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(60f),
83 | (float)_fbo.Width / _fbo.Height, 0.1f, 100f);
84 |
85 | ImGuiRenderer.Init();
86 |
87 | _guiComponents = new List();
88 |
89 | ApplicationArea area = new ApplicationArea();
90 |
91 | TreeView outlineView = new TreeView();
92 | _treeController.SetTree(outlineView);
93 |
94 | area.AddChild(new Columns(2, new List
95 | { outlineView, new FramebufferArea(_fbo) }, new[] { 250f, -1 }));
96 |
97 | var menuBar = new MainMenuBar(_fileOpenController, _vramController, _configController, _cameraController,
98 | _exportController);
99 |
100 | if (string.IsNullOrWhiteSpace(_configController.Config.GameDataPath))
101 | {
102 | // show the set game data path dialog if it hasn't yet been set
103 | menuBar.OpenSetGameDataPath();
104 | }
105 |
106 | _updateAvailableModal = new Modal("New update available!",
107 | "Download at https://github.com/Figglewatts/LSDView/releases/latest");
108 |
109 | _fileExportDialog = new FileDialog(_configController.Config.GameDataPath, FileDialog.DialogType.Save);
110 | _configController.Config.OnGameDataPathChange += () =>
111 | _fileExportDialog.InitialDir = _configController.Config.GameDataPath;
112 | _exportController.ProvideFileExportDialog(_fileExportDialog);
113 |
114 | _guiComponents.Add(area);
115 | _guiComponents.Add(menuBar);
116 | _guiComponents.Add(_fileExportDialog);
117 | _guiComponents.Add(_updateAvailableModal);
118 |
119 | if (_updateCheckerController.IsUpdateAvailable())
120 | {
121 | _updateAvailableModal.ShowModal();
122 | }
123 | }
124 |
125 | private void createControllers(ILifetimeScope scope)
126 | {
127 | _configController = scope.Resolve();
128 | _exportController = scope.Resolve();
129 | _animationController = scope.Resolve();
130 | _treeController = scope.Resolve();
131 | _vramController = scope.Resolve();
132 | _cameraController = scope.Resolve();
133 | _fileOpenController = scope.Resolve();
134 | _updateCheckerController = scope.Resolve();
135 | }
136 |
137 | protected override void OnResize(EventArgs e)
138 | {
139 | GL.Viewport(0, 0, Width, Height);
140 | ImGuiRenderer.Resize(Width, Height);
141 | }
142 |
143 | protected override void OnLoad(EventArgs e)
144 | {
145 | CursorVisible = true;
146 | GL.ClearColor(0.2f, 0.2f, 0.2f, 1);
147 |
148 | Log.Information("Initialized LSDView");
149 | }
150 |
151 | protected override void OnUnload(EventArgs e) { ImGuiRenderer.Shutdown(); }
152 |
153 | protected override void OnUpdateFrame(FrameEventArgs e)
154 | {
155 | HandleInput();
156 |
157 | NextUpdateActions?.Invoke();
158 | NextUpdateActions = null;
159 |
160 | _cameraController.Update();
161 | _animationController.Update(e.Time);
162 | }
163 |
164 | protected override void OnRenderFrame(FrameEventArgs e)
165 | {
166 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
167 |
168 | ImGuiRenderer.BeginFrame(e.Time);
169 |
170 | foreach (var component in _guiComponents)
171 | {
172 | component.Render();
173 | }
174 |
175 | NextGuiRender?.Invoke();
176 | NextGuiRender = null;
177 |
178 | ImGuiRenderer.EndFrame();
179 |
180 | _fbo.Bind();
181 | GL.Viewport(0, 0, _fbo.Width, _fbo.Height);
182 | _proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(60f),
183 | (float)_fbo.Width / _fbo.Height, 0.1f, 100f);
184 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
185 |
186 | _treeController.RenderSelectedNode(_cam.View, _proj);
187 |
188 | _fbo.Unbind();
189 |
190 | SwapBuffers();
191 | }
192 |
193 | protected override void OnKeyPress(KeyPressEventArgs e)
194 | {
195 | if (!Focused || !Visible) return;
196 | ImGuiRenderer.AddKeyChar(e.KeyChar);
197 | }
198 |
199 | protected override void OnMouseMove(MouseMoveEventArgs e)
200 | {
201 | if (!Focused || !Visible) return;
202 | ImGuiRenderer.UpdateMousePos(e.X, e.Y);
203 | }
204 |
205 | private void HandleInput() { }
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/Headless/AbstractHeadlessCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using CommandLine;
3 | using LSDView.Util;
4 | using Serilog;
5 |
6 | namespace LSDView.Headless
7 | {
8 | public abstract class AbstractHeadlessCommand : IHeadlessCommand
9 | {
10 | public abstract Type OptionsType { get; }
11 | public abstract void Register(ref ParserResult