From 8da9e1864268608240aacda9b8b66f604c23d9d1 Mon Sep 17 00:00:00 2001 From: jupahe64 Date: Tue, 21 Nov 2023 22:00:01 +0100 Subject: [PATCH] make BGUnitRailSceneObj the first proper SceneObject and do some refactoring --- Fushigi/course/CourseUnit.cs | 219 +++++------ Fushigi/ui/CourseAreaEditContext.cs | 113 +++++- Fushigi/ui/CourseAreaScene.cs | 36 +- Fushigi/ui/MainWindow.cs | 42 +-- .../ui/SceneObjects/CourseAreaSceneRoot.cs | 6 + .../SceneObjects/bgunit/BGUnitRailSceneObj.cs | 350 +++++++++--------- .../ui/SceneObjects/bgunit/BGUnitSceneObj.cs | 27 +- .../SceneObjects/bgunit/UnitRailPointUndo.cs | 85 ----- Fushigi/ui/widgets/CourseScene.cs | 147 ++++---- Fushigi/ui/widgets/LevelViewport.cs | 219 ++++++----- 10 files changed, 599 insertions(+), 645 deletions(-) delete mode 100644 Fushigi/ui/SceneObjects/bgunit/UnitRailPointUndo.cs diff --git a/Fushigi/course/CourseUnit.cs b/Fushigi/course/CourseUnit.cs index ba2ebe0e..c11de739 100644 --- a/Fushigi/course/CourseUnit.cs +++ b/Fushigi/course/CourseUnit.cs @@ -1,15 +1,6 @@ using Fushigi.Byml; -using Fushigi.ui.SceneObjects.bgunit; using Fushigi.util; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Numerics; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using static Fushigi.course.CourseUnit; using Vector3 = System.Numerics.Vector3; namespace Fushigi.course @@ -35,10 +26,8 @@ public enum SkinDivision public CourseUnit() { - this.Walls = new List(); - this.mBeltRails = new List(); - this.mModelType = 0; - this.mSkinDivision = 0; + this.mModelType = ModelType.Solid; + this.mSkinDivision = SkinDivision.FieldA; } public CourseUnit(BymlHashTable tbl) @@ -48,58 +37,31 @@ public CourseUnit(BymlHashTable tbl) if (tbl.ContainsKey("BeltRails")) { - BymlArrayNode belts = tbl["BeltRails"] as BymlArrayNode; + BymlArrayNode belts = (BymlArrayNode)tbl["BeltRails"]; foreach (BymlHashTable beltsTbl in belts.Array) { - Rail belt = new Rail(); - belt.IsClosed = BymlUtil.GetNodeData(beltsTbl["IsClosed"]); - belt.mPoints = new(); - - BymlArrayNode beltsArr = beltsTbl["Points"] as BymlArrayNode; - - foreach (BymlHashTable pointsTbl in beltsArr.Array) - { - belt.mPoints.Add(BymlUtil.GetVector3FromArray((BymlArrayNode)pointsTbl["Translate"])); - } - this.mBeltRails.Add(new BGUnitRailSceneObj(this, belt)); + mBeltRails.Add(LoadRailNode(beltsTbl)); } } if (tbl.ContainsKey("Walls")) { - BymlArrayNode wallsNode = tbl["Walls"] as BymlArrayNode; + BymlArrayNode wallsNode = (BymlArrayNode)tbl["Walls"]; this.Walls = new List(); - BGUnitRailSceneObj LoadRail(BymlHashTable railDict, bool isInternal = false) - { - BymlArrayNode pointsArr = railDict["Points"] as BymlArrayNode; - - Rail rail = new(); - rail.IsClosed = BymlUtil.GetNodeData(railDict["IsClosed"]); - rail.mPoints = new List(); - rail.IsInternal = isInternal; - - foreach (BymlHashTable pointsTbl in pointsArr.Array) - { - rail.mPoints.Add(BymlUtil.GetVector3FromArray((BymlArrayNode)pointsTbl["Translate"])); - } - - return new BGUnitRailSceneObj(this, rail); - } - foreach (BymlHashTable wallsTbl in wallsNode.Array) { Wall wall = new Wall(this); this.Walls.Add(wall); if (wallsTbl.ContainsKey("ExternalRail")) - wall.ExternalRail = LoadRail(wallsTbl["ExternalRail"] as BymlHashTable); + wall.ExternalRail = LoadRailNode((BymlHashTable)wallsTbl["ExternalRail"]); if (wallsTbl.ContainsKey("InternalRails")) { - var railList = wallsTbl["InternalRails"] as BymlArrayNode; + var railList = (BymlArrayNode)wallsTbl["InternalRails"]; foreach (BymlHashTable rail in railList.Array) - wall.InternalRails.Add(LoadRail(rail, true)); + wall.InternalRails.Add(LoadRailNode(rail, true)); } } } @@ -107,80 +69,79 @@ BGUnitRailSceneObj LoadRail(BymlHashTable railDict, bool isInternal = false) GenerateTileSubUnits(); } - public BymlHashTable BuildNode() + private BGUnitRail LoadRailNode(BymlHashTable railDict, bool isInternal = false) { - BymlHashTable table = new(); - table.AddNode(BymlNodeId.Int, BymlUtil.CreateNode("ModelType", (int)mModelType), "ModelType"); - table.AddNode(BymlNodeId.Int, BymlUtil.CreateNode("SkinDivision", (int)mSkinDivision), "SkinDivision"); + BymlArrayNode pointsArr = (BymlArrayNode)railDict["Points"]; - BymlArrayNode beltsArray = new((uint)mBeltRails.Count); + BGUnitRail rail = new(this) + { + IsClosed = BymlUtil.GetNodeData(railDict["IsClosed"]), + IsInternal = isInternal + }; - foreach(var belt in mBeltRails) + foreach (BymlHashTable pointsTbl in pointsArr.Array) { - var rail = belt.Save(); + var position = BymlUtil.GetVector3FromArray((BymlArrayNode)pointsTbl["Translate"]); + rail.Points.Add(new BGUnitRail.RailPoint(rail, position)); + } - BymlHashTable beltNode = new(); - beltNode.AddNode(BymlNodeId.Bool, BymlUtil.CreateNode("IsClosed", belt.IsClosed), "IsClosed"); + return rail; + } - BymlArrayNode pointsArr = new((uint)rail.mPoints.Count); + private BymlHashTable BuildRailNode(BGUnitRail rail) + { + BymlHashTable railNode = new(); + railNode.AddNode(BymlNodeId.Bool, BymlUtil.CreateNode(rail.IsClosed), "IsClosed"); - foreach (System.Numerics.Vector3 point in rail.mPoints) - { - BymlHashTable pointTbl = new(); - BymlArrayNode translateNode = new(3); - translateNode.AddNodeToArray(BymlUtil.CreateNode("X", point.X)); - translateNode.AddNodeToArray(BymlUtil.CreateNode("Y", point.Y)); - translateNode.AddNodeToArray(BymlUtil.CreateNode("Z", point.Z)); - - //beltNode.AddNode(BymlNodeId.Array, translateNode, "Translate"); - pointTbl.AddNode(BymlNodeId.Array, translateNode, "Translate"); - pointsArr.AddNodeToArray(pointTbl); - } + BymlArrayNode pointsArrayNode = new(); - beltNode.AddNode(BymlNodeId.Array, pointsArr, "Points"); - beltsArray.AddNodeToArray(beltNode); + foreach (BGUnitRail.RailPoint point in rail.Points) + { + var position = point.Position; + BymlHashTable pointDict = new(); + BymlArrayNode translateNode = new(3); + translateNode.AddNodeToArray(BymlUtil.CreateNode(position.X)); + translateNode.AddNodeToArray(BymlUtil.CreateNode(position.Y)); + translateNode.AddNodeToArray(BymlUtil.CreateNode(position.Z)); + + pointDict.AddNode(BymlNodeId.Array, translateNode, "Translate"); + pointsArrayNode.AddNodeToArray(pointDict); } - table.AddNode(BymlNodeId.Array, beltsArray, "BeltRails"); + railNode.AddNode(BymlNodeId.Array, pointsArrayNode, "Points"); - BymlArrayNode wallsArray = new((uint)this.Walls.Count); + return railNode; + } - foreach (Wall wall in this.Walls) - { - BymlHashTable SaveRail(Rail rail) - { - BymlHashTable railNode = new(); - railNode.AddNode(BymlNodeId.Bool, BymlUtil.CreateNode("IsClosed", rail.IsClosed), "IsClosed"); + public BymlHashTable BuildNode() + { + BymlHashTable table = new(); + table.AddNode(BymlNodeId.Int, BymlUtil.CreateNode((int)mModelType), "ModelType"); + table.AddNode(BymlNodeId.Int, BymlUtil.CreateNode((int)mSkinDivision), "SkinDivision"); - BymlArrayNode pointsArrayNode = new(); + BymlArrayNode beltsArray = new((uint)mBeltRails.Count); - foreach (System.Numerics.Vector3 point in rail.mPoints) - { - BymlHashTable pointDict = new(); - BymlArrayNode translateNode = new(3); - translateNode.AddNodeToArray(BymlUtil.CreateNode("X", point.X)); - translateNode.AddNodeToArray(BymlUtil.CreateNode("Y", point.Y)); - translateNode.AddNodeToArray(BymlUtil.CreateNode("Z", point.Z)); - - pointDict.AddNode(BymlNodeId.Array, translateNode, "Translate"); - pointsArrayNode.AddNodeToArray(pointDict); - } + foreach (var belt in mBeltRails) + { + beltsArray.AddNodeToArray(BuildRailNode(belt)); + } - railNode.AddNode(BymlNodeId.Array, pointsArrayNode, "Points"); + table.AddNode(BymlNodeId.Array, beltsArray, "BeltRails"); - return railNode; - } + BymlArrayNode wallsArray = new((uint)this.Walls.Count); + foreach (Wall wall in this.Walls) + { BymlHashTable wallNode = new(); if (wall.InternalRails.Count > 0) { - BymlArrayNode internaiRailListNode = new BymlArrayNode(); - wallNode.AddNode(BymlNodeId.Array, internaiRailListNode, "InternalRails"); + BymlArrayNode internalRailListNode = new BymlArrayNode(); + wallNode.AddNode(BymlNodeId.Array, internalRailListNode, "InternalRails"); foreach (var rail in wall.InternalRails) - internaiRailListNode.AddNodeToArray(SaveRail(rail.Save())); + internalRailListNode.AddNodeToArray(BuildRailNode(rail)); } - wallNode.AddNode(BymlNodeId.Hash, SaveRail(wall.ExternalRail.Save()), "ExternalRail"); + wallNode.AddNode(BymlNodeId.Hash, BuildRailNode(wall.ExternalRail), "ExternalRail"); wallsArray.AddNodeToArray(wallNode); } @@ -193,11 +154,11 @@ public void GenerateTileSubUnits() { mTileSubUnits.Clear(); - if(mModelType == ModelType.Bridge) + if (mModelType == ModelType.Bridge) { foreach (var rail in mBeltRails) { - mTileSubUnits.Add(TileSubUnits.CreateFromRails(rail, Array.Empty(), + mTileSubUnits.Add(TileSubUnits.CreateFromRails(rail, Array.Empty(), isBridgeModel: true)); } } @@ -211,28 +172,13 @@ public void GenerateTileSubUnits() } } - public struct Rail - { - public bool IsClosed; - public List mPoints; - public bool IsInternal; - - public Rail() - { - IsInternal = false; - IsClosed = true; - mPoints = new List(); - } - } - public ModelType mModelType; public SkinDivision mSkinDivision; - //Editor render objects - internal List Walls = new List(); - internal List mBeltRails = new List(); + internal List Walls = []; + internal List mBeltRails = []; - internal List mTileSubUnits = new List(); + internal List mTileSubUnits = []; //Editor toggle public bool Visible = true; @@ -240,13 +186,32 @@ public Rail() public class Wall { - internal BGUnitRailSceneObj ExternalRail; - internal List InternalRails = new List(); + internal BGUnitRail ExternalRail; + internal List InternalRails = []; internal Wall(CourseUnit unit) { - ExternalRail = new BGUnitRailSceneObj(unit, new CourseUnit.Rail()); + ExternalRail = new BGUnitRail(unit); + } + } + + public class BGUnitRail(CourseUnit unit) + { + public readonly CourseUnit mCourseUnit = unit; + + public List Points = []; + + public bool IsClosed = false; + + public bool IsInternal = false; + + public class RailPoint(BGUnitRail rail, Vector3 position) + { + public readonly BGUnitRail mRail = rail; + public Vector3 Position = position; } + + } public class TileSubUnits @@ -263,7 +228,7 @@ public enum SlopeType public readonly InfiniteTileMap mTileMap = new(); public readonly List<(int x, int y, int width, int height, SlopeType type)> mSlopes = []; - internal static TileSubUnits CreateFromRails(BGUnitRailSceneObj mainRail, IReadOnlyList internalRails, bool isBridgeModel) + internal static TileSubUnits CreateFromRails(BGUnitRail mainRail, IReadOnlyList internalRails, bool isBridgeModel) { TileSubUnits component = new(); @@ -356,22 +321,22 @@ internal static TileSubUnits CreateFromRails(BGUnitRailSceneObj mainRail, IReadO int tileX = Math.Min((int)triP0.X, (int)triP1.X); int tileY = Math.Min((int)triP0.Y, (int)triP1.Y); - - if(slope == 0.0f) + + if (slope == 0.0f) { bridgeTiles.Add((tileX, tileY - 1)); continue; } - blockedTiles.Add((tileX, tileY)); - if(isBridgeModel) + blockedTiles.Add((tileX, tileY)); + if (isBridgeModel) bridgeTiles.Add((tileX, tileY - 1)); if (slopeWidth == 2) { blockedTiles.Add((tileX + 1, tileY)); - if (isBridgeModel) + if (isBridgeModel) bridgeTiles.Add((tileX + 1, tileY - 1)); } @@ -423,7 +388,7 @@ internal static TileSubUnits CreateFromRails(BGUnitRailSceneObj mainRail, IReadO return component; } - foreach (var (x,y) in bridgeTiles) + foreach (var (x, y) in bridgeTiles) { component.mTileMap.AddTile(x, y); } diff --git a/Fushigi/ui/CourseAreaEditContext.cs b/Fushigi/ui/CourseAreaEditContext.cs index 95ecb601..d170498d 100644 --- a/Fushigi/ui/CourseAreaEditContext.cs +++ b/Fushigi/ui/CourseAreaEditContext.cs @@ -1,14 +1,7 @@ using Fushigi.course; using Fushigi.ui.undo; -using Fushigi.ui.widgets; using Fushigi.util; -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; namespace Fushigi.ui { @@ -23,10 +16,68 @@ class CourseAreaEditContext(CourseArea area) private readonly UndoHandler mUndoHandler = new(); + private bool mIsSuspendUpdate = false; + private bool mIsRequireUpdate = false; + private bool mIsRequireSelectionCheck = false; + public event Action? Update; public ulong SelectionVersion { get; private set; } = 0; + private void SelectionChanged() + { + if (mIsSuspendUpdate) + { + mIsRequireSelectionCheck = true; + return; + } + SelectionVersion++; + Update?.Invoke(); + } + + private void DoUpdate() + { + if (mIsSuspendUpdate) + { + mIsRequireUpdate = true; + return; + } + Update?.Invoke(); + } + + public void WithSuspendUpdateDo(Action action) + { + if (mIsSuspendUpdate) + { + action.Invoke(); + return; + } + + List prevSelection = mSelectedObjects.ToList(); + + mIsSuspendUpdate = true; + action.Invoke(); + mIsSuspendUpdate = false; + + if (mIsRequireSelectionCheck) + { + if (prevSelection.Count != mSelectedObjects.Count || + !mSelectedObjects.SetEquals(prevSelection)) + { + SelectionChanged(); + mIsRequireUpdate = true; + } + + mIsRequireSelectionCheck = false; + } + + if (mIsRequireUpdate) + { + Update?.Invoke(); + mIsRequireUpdate = false; + } + } + private bool mHasDialog = false; //For Undo Window @@ -87,7 +138,7 @@ private void EndBatchAction(BatchAction action) public void CommitAction(IRevertable action) { - if(mCurrentActionBatch is not null) + if (mCurrentActionBatch is not null) { mCurrentActionBatch.Add(action); return; @@ -100,7 +151,7 @@ public void CommitAction(IRevertable action) public void DeselectAll() { if (mSelectedObjects.Count > 0) - SelectionVersion++; + SelectionChanged(); mSelectedObjects.Clear(); } @@ -109,10 +160,10 @@ public void DeselectAllOfType() where T : class { int countBefore = mSelectedObjects.Count; - mSelectedObjects.RemoveWhere(x=>x is T); + mSelectedObjects.RemoveWhere(x => x is T); if (mSelectedObjects.Count != countBefore) - SelectionVersion++; + SelectionChanged(); } public void Select(ICollection objects) @@ -121,7 +172,7 @@ public void Select(ICollection objects) mSelectedObjects.UnionWith(objects); if (mSelectedObjects.Count != countBefore) - SelectionVersion++; + SelectionChanged(); } public void Select(object obj) @@ -130,7 +181,7 @@ public void Select(object obj) mSelectedObjects.Add(obj); if (mSelectedObjects.Count != countBefore) - SelectionVersion++; + SelectionChanged(); } public void Deselect(object obj) @@ -139,7 +190,7 @@ public void Deselect(object obj) mSelectedObjects.Remove(obj); if (mSelectedObjects.Count != countBefore) - SelectionVersion++; + SelectionChanged(); } public bool IsSelected(object obj) => @@ -156,7 +207,7 @@ public bool IsSingleObjectSelected([NotNullWhen(true)] out T? obj) return false; var _obj = mSelectedObjects.First(); - if(_obj is not T casted) return false; + if (_obj is not T casted) return false; obj = casted; return true; } @@ -236,7 +287,7 @@ public void DeleteActorFromGroup(CourseGroup group, ulong hash) group.GetActors().RevertableRemoveAt(index, $"Remove actor {hash} from group") ); - } + } } private void DeleteLinksWithDestHash(ulong hash) @@ -274,11 +325,39 @@ public void AddLink(CourseLink link) { Console.WriteLine($"Adding Link: Source: {link.GetSrcHash()} -- Dest: {link.GetDestHash()}"); CommitAction( - area.mLinkHolder.GetLinks().RevertableAdd(link, + area.mLinkHolder.GetLinks().RevertableAdd(link, $"{IconUtil.ICON_PLUS_CIRCLE} Add {link.GetLinkName()} Link") ); } + public void AddBgUnit(CourseUnit unit) + { + Console.WriteLine("Adding Course Unit"); + CommitAction(area.mUnitHolder.mUnits.RevertableAdd(unit, + $"{IconUtil.ICON_PLUS_CIRCLE} Add Tile Unit")); + } + + public void DeleteBgUnit(CourseUnit unit) + { + Console.WriteLine("Deleting Course Unit"); + CommitAction(area.mUnitHolder.mUnits.RevertableRemove(unit, + $"{IconUtil.ICON_PLUS_CIRCLE} Delete Tile Unit")); + } + + public void AddWall(CourseUnit unit, Wall wall) + { + Console.WriteLine("Adding Wall"); + CommitAction(unit.Walls.RevertableAdd(wall, + $"{IconUtil.ICON_PLUS_CIRCLE} Add Wall")); + } + + public void DeleteWall(CourseUnit unit, Wall wall) + { + Console.WriteLine("Deleting Wall"); + CommitAction(unit.Walls.RevertableRemove(wall, + $"{IconUtil.ICON_PLUS_CIRCLE} Delete Wall")); + } + public CourseActorHolder GetActorHolder() { return area.mActorHolder; diff --git a/Fushigi/ui/CourseAreaScene.cs b/Fushigi/ui/CourseAreaScene.cs index 9805d9fd..c063621c 100644 --- a/Fushigi/ui/CourseAreaScene.cs +++ b/Fushigi/ui/CourseAreaScene.cs @@ -2,6 +2,7 @@ using Fushigi.ui.widgets; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -16,13 +17,13 @@ interface ISceneRoot interface ISceneObject { - void Update(ISceneUpdateContext ctx); + void Update(ISceneUpdateContext ctx, bool isSelected); } interface ISceneUpdateContext { - void UpdateOrCreateObjFor(object courseObject, Func createFunc); - void AddOrUpdateChildObject(ISceneObject sceneObject); + ISceneObject UpdateOrCreateObjFor(object courseObject, Func createFunc); + void AddOrUpdateSceneObject(ISceneObject sceneObject); } internal class CourseAreaScene : ISceneUpdateContext @@ -34,6 +35,7 @@ public CourseAreaScene(CourseArea area, ISceneRoot sceneRoot) EditContext = new(area); EditContext.Update += Update; this.mSceneRoot = sceneRoot; + Update(); } bool mIsUpdating = false; @@ -60,7 +62,14 @@ public void Update() mIsUpdating = false; } - void ISceneUpdateContext.AddOrUpdateChildObject(ISceneObject sceneObject) + public bool TryGetObjFor(object courseObject, [NotNullWhen(true)] out ISceneObject? sceneObject) + { + bool success = mCourseSceneObjects.TryGetValue(courseObject, out var entry); + sceneObject = entry.obj; + return success; + } + + void ISceneUpdateContext.AddOrUpdateSceneObject(ISceneObject sceneObject) { if (!mIsUpdating) throw new InvalidOperationException("Cannot call this function outside of Update"); @@ -75,12 +84,16 @@ void ISceneUpdateContext.AddOrUpdateChildObject(ISceneObject sceneObject) mOrderedSceneObjects.Add(entry.obj); - entry.obj.Update(this); + entry.obj.Update(this, false); mCourseSceneObjects[sceneObject] = entry with { isDirty = false }; } - void ISceneUpdateContext.UpdateOrCreateObjFor(object courseObject, Func createFunc) + /// + /// + /// + /// The created/updated scene object + ISceneObject ISceneUpdateContext.UpdateOrCreateObjFor(object courseObject, Func createFunc) { if (!mIsUpdating) throw new InvalidOperationException("Cannot call this function outside of Update"); @@ -92,13 +105,14 @@ void ISceneUpdateContext.UpdateOrCreateObjFor(object courseObject, Func /// /// - public void ForEach(Action action) + public void ForEach(Action action) where T : class { mUpdateBlockers++; @@ -135,8 +149,8 @@ public void ForEach(Action action) for (int i = 0; i < span.Length; i++) { var obj = span[i]; - if(obj is T) - action.Invoke(obj); + if(obj is T casted) + action.Invoke(casted); } mUpdateBlockers--; diff --git a/Fushigi/ui/MainWindow.cs b/Fushigi/ui/MainWindow.cs index 376a9b67..874e9027 100644 --- a/Fushigi/ui/MainWindow.cs +++ b/Fushigi/ui/MainWindow.cs @@ -1,20 +1,12 @@ +using Fushigi.param; +using Fushigi.ui.widgets; using Fushigi.util; using Fushigi.windowing; +using ImGuiNET; using Silk.NET.OpenGL; using Silk.NET.Windowing; -using Fushigi.param; -using Fushigi.ui.widgets; -using ImGuiNET; -using System.Runtime.CompilerServices; using System.Numerics; -using System.Collections.Generic; using System.Runtime.InteropServices; -using static System.Net.Mime.MediaTypeNames; -using System.Reflection; -using System.Diagnostics; -using Fushigi.Bfres; -using Fushigi.SARC; -using Fushigi.gl.Bfres; namespace Fushigi.ui { @@ -26,8 +18,9 @@ public class MainWindow public MainWindow() { - WindowManager.CreateWindow(out mWindow, - onConfigureIO: () => { + WindowManager.CreateWindow(out mWindow, + onConfigureIO: () => + { unsafe { var io = ImGui.GetIO(); @@ -99,7 +92,7 @@ public bool TryCloseCourse(Action onSuccessRetryAction) } } - if(mSelectedCourseScene is not null && + if (mSelectedCourseScene is not null && mSelectedCourseScene.HasUnsavedChanges()) { mCloseCourseRequest = (onSuccessRetryAction, success: false); @@ -160,8 +153,8 @@ void DrawMainMenu(GL gl) if (ImGui.BeginMainMenuBar()) { if (ImGui.BeginMenu("File")) - { - if (!string.IsNullOrEmpty(RomFS.GetRoot()) && + { + if (!string.IsNullOrEmpty(RomFS.GetRoot()) && !string.IsNullOrEmpty(UserSettings.GetModRomFSPath())) { if (ImGui.MenuItem("Open Course")) @@ -172,7 +165,7 @@ void SwitchCourse(string courseLocation) { mCourseSelect = null; return; - } + } if (!TryCloseCourse(onSuccessRetryAction: () => SwitchCourse(courseLocation))) return; @@ -226,13 +219,13 @@ void SwitchCourse(string courseLocation) { string directory = Path.Combine(UserSettings.GetModRomFSPath(), "Phive", "StaticCompoundBody"); - if(!Directory.Exists(directory)) + if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); foreach (var area in mSelectedCourseScene.GetCourse().GetAreas()) { var filePath = Path.Combine(directory, $"{area.GetName()}.Nin_NX_NVN.bphsc.zs"); - File.Copy(Path.Combine(AppContext.BaseDirectory, "res", "BlankStaticCompoundBody.bphsc.zs"), + File.Copy(Path.Combine(AppContext.BaseDirectory, "res", "BlankStaticCompoundBody.bphsc.zs"), filePath, overwrite: true); } } @@ -256,18 +249,19 @@ void SwitchCourse(string courseLocation) mIsChoosingPreferences = true; } - if (ImGui.MenuItem("Regenerate Parameter Database", ParamDB.sIsInit)) { + if (ImGui.MenuItem("Regenerate Parameter Database", ParamDB.sIsInit)) + { mIsGeneratingParamDB = true; } if (ImGui.MenuItem("Undo")) { - mSelectedCourseScene?.activeViewport.mEditContext.Undo(); + mSelectedCourseScene?.Undo(); } if (ImGui.MenuItem("Redo")) { - mSelectedCourseScene?.activeViewport.mEditContext.Redo(); + mSelectedCourseScene?.Redo(); } /* end Edit menu */ @@ -320,7 +314,7 @@ public void Render(GL gl, double delta, ImGuiController controller) // ImGui settings are available frame 3 if (ImGui.GetFrameCount() > 2) { - if (!string.IsNullOrEmpty(RomFS.GetRoot()) && + if (!string.IsNullOrEmpty(RomFS.GetRoot()) && !string.IsNullOrEmpty(UserSettings.GetModRomFSPath())) { if (mCourseSelect != null) @@ -357,7 +351,7 @@ public void Render(GL gl, double delta, ImGuiController controller) mCloseCourseRequest = request with { success = true }; request.onSuccessRetryAction.Invoke(); } - else if(result == CloseConfirmationDialog.Result.No) + else if (result == CloseConfirmationDialog.Result.No) { mCloseCourseRequest = null; } diff --git a/Fushigi/ui/SceneObjects/CourseAreaSceneRoot.cs b/Fushigi/ui/SceneObjects/CourseAreaSceneRoot.cs index c60b2363..5ce6f0d8 100644 --- a/Fushigi/ui/SceneObjects/CourseAreaSceneRoot.cs +++ b/Fushigi/ui/SceneObjects/CourseAreaSceneRoot.cs @@ -1,4 +1,5 @@ using Fushigi.course; +using Fushigi.ui.SceneObjects.bgunit; namespace Fushigi.ui.SceneObjects { @@ -9,6 +10,11 @@ public void Update(ISceneUpdateContext ctx) //call ctx.UpdateOrCreateObjFor() //for every object (actor/rail/etc.) that should be part of the scene //the scene object classes for these objects should go in SceneObjects + + foreach (var unit in area.mUnitHolder.mUnits) + { + ctx.UpdateOrCreateObjFor(unit, () => new BGUnitSceneObj(unit)); + } } } diff --git a/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs b/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs index 238d9b68..e63c8efc 100644 --- a/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs +++ b/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs @@ -1,33 +1,16 @@ using Fushigi.course; +using Fushigi.ui.undo; using Fushigi.ui.widgets; using Fushigi.util; using ImGuiNET; -using Microsoft.VisualBasic; -using Silk.NET.Maths; -using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using System.Reflection; -using System.Text; -using System.Text.Json.Nodes; -using System.Threading.Tasks; -using System.Xml.Linq; namespace Fushigi.ui.SceneObjects.bgunit { - //TODO make this into a proper scene object that creates/updates it's points on update - //via AddOrUpdateChildObject - //and isn't referenced in CourseUnit.cs - internal class BGUnitRailSceneObj + internal class BGUnitRailSceneObj(CourseUnit unit, BGUnitRail rail) : ISceneObject, IViewportDrawable, IViewportSelectable { - public List Points = new List(); - - public List GetSelected(CourseAreaEditContext ctx) => Points.Where(x => ctx.IsSelected(x)).ToList(); - - public bool IsClosed = false; - - public bool IsInternal = false; + public IReadOnlyDictionary ChildPoints; + public List GetSelected(CourseAreaEditContext ctx) => rail.Points.Where(ctx.IsSelected).ToList(); public bool mouseDown = false; public bool transformStart = false; @@ -40,131 +23,120 @@ internal class BGUnitRailSceneObj private Vector3 mouseDownPos; - public CourseUnit CourseUnit; + public CourseUnit CourseUnit = unit; - public BGUnitRailSceneObj(CourseUnit unit, CourseUnit.Rail rail) + public void Update(ISceneUpdateContext ctx, bool isSelected) { - CourseUnit = unit; + Dictionary railPoints = []; - Points.Clear(); - - foreach (var pt in rail.mPoints) + if (isSelected) { - var railPoint = new RailPoint(pt.Value); - railPoint.Transform.Update += unit.GenerateTileSubUnits; - Points.Add(railPoint); - + foreach (var pt in rail.Points) + { + var railPointObj = ctx.UpdateOrCreateObjFor(pt, () => + { + var railPoint = new RailPoint(pt); + railPoint.Transform.Update += unit.GenerateTileSubUnits; + return railPoint; + }); + + railPoints[pt] = (RailPoint)railPointObj; + } } + rail.mCourseUnit.GenerateTileSubUnits(); - IsClosed = rail.IsClosed; - IsInternal = rail.IsInternal; + ChildPoints = railPoints; } - public void Reverse() - { - Points.Reverse(); - } + private bool IsSelected(CourseAreaEditContext ctx) => ctx.IsSelected(rail); - public CourseUnit.Rail Save() + private void DeselectAll(CourseAreaEditContext ctx) { - CourseUnit.Rail rail = new CourseUnit.Rail() + ctx.WithSuspendUpdateDo(() => { - IsClosed = IsClosed, - IsInternal = IsInternal, - mPoints = new List(), - }; - - rail.mPoints = new List(); - foreach (var pt in Points) - rail.mPoints.Add(pt.Position); - - return rail; - } - - public void DeselectAll(CourseAreaEditContext ctx) - { - foreach (var point in Points) - if (ctx.IsSelected(point)) + foreach (var point in rail.Points) ctx.Deselect(point); + }); + } public void SelectAll(CourseAreaEditContext ctx) { - foreach (var point in Points) - ctx.Select(point); + ctx.WithSuspendUpdateDo(() => + { + foreach (var point in rail.Points) + ctx.Select(point); + }); } - public void InsertPoint(LevelViewport viewport, RailPoint point, int index) + public void InsertPoint(CourseAreaEditContext ctx, BGUnitRail.RailPoint point, int index) { - Points.Insert(index, point); - viewport.mEditContext.CommitAction(new UnitRailPointAddUndo(this, point, index)); - viewport.mEditContext.Select(point); + var revertible = rail.Points.RevertableInsert(point, index, + $"{IconUtil.ICON_PLUS_CIRCLE} Rail Point Add"); + + ctx.CommitAction(revertible); + ctx.Select(point); CourseUnit.GenerateTileSubUnits(); } - public void AddPoint(LevelViewport viewport, RailPoint point) + public void AddPoint(CourseAreaEditContext ctx, BGUnitRail.RailPoint point) { - Points.Add(point); - viewport.mEditContext.CommitAction(new UnitRailPointAddUndo(this, point)); - viewport.mEditContext.Select(point); + var revertible = rail.Points.RevertableAdd(point, + $"{IconUtil.ICON_PLUS_CIRCLE} Rail Point Add"); + + ctx.CommitAction(revertible); + ctx.Select(point); CourseUnit.GenerateTileSubUnits(); } - public void RemoveSelected(LevelViewport viewport) + public void RemoveSelected(CourseAreaEditContext ctx, LevelViewport viewport) { - var selected = GetSelected(viewport.mEditContext); + var selected = GetSelected(ctx); if (selected.Count == 0) return; - var batchAction = viewport.mEditContext.BeginBatchAction(); + var batchAction = ctx.BeginBatchAction(); foreach (var point in selected) - viewport.mEditContext.CommitAction(new UnitRailPointDeleteUndo(this, point)); - - batchAction.Commit("Delete Rail Points"); + { + var revertible = rail.Points.RevertableRemove(point); + ctx.CommitAction(revertible); + } - foreach (var point in selected) - Points.Remove(point); + batchAction.Commit($"{IconUtil.ICON_TRASH} Delete Rail Points"); CourseUnit.GenerateTileSubUnits(); } - public void OnKeyDown(LevelViewport viewport) + public void OnKeyDown(CourseAreaEditContext ctx, LevelViewport viewport) { + //TODO move the delete logic over to CourseAreaEditContext and remove this if (ImGui.IsKeyPressed(ImGuiKey.Delete)) - RemoveSelected(viewport); - if (viewport.mEditContext.IsSelected(this) && ImGui.GetIO().KeyCtrl && ImGui.IsKeyPressed(ImGuiKey.A)) - SelectAll(viewport.mEditContext); + RemoveSelected(ctx, viewport); + if (IsSelected(ctx) && ImGui.GetIO().KeyCtrl && ImGui.IsKeyPressed(ImGuiKey.A)) + SelectAll(ctx); } - public bool HitTest(LevelViewport viewport) + private bool HitTest(LevelViewport viewport) { return LevelViewport.HitTestLineLoopPoint(GetPoints(viewport), 10f, ImGui.GetMousePos()); } - public void OnMouseDown(LevelViewport viewport) + public void OnMouseDown(CourseAreaEditContext ctx, LevelViewport viewport) { - var ctx = viewport.mEditContext; - bool isSelected = ctx.IsSelected(this); - - //Line hit test - if (!isSelected && viewport.HoveredObject == this) - { - viewport.SelectBGUnit(this); - isSelected = true; - } + bool isSelected = IsSelected(ctx); if (!isSelected) return; mouseDownPos = viewport.ScreenToWorld(ImGui.GetMousePos()); - var selected = GetSelected(viewport.mEditContext); + var selected = GetSelected(ctx); if (ImGui.GetIO().KeyAlt && selected.Count == 1) { - var index = Points.IndexOf(selected[0]); + var index = rail.Points.IndexOf(selected[0]); //Insert and add Vector3 posVec = viewport.ScreenToWorld(ImGui.GetMousePos()); Vector3 pos = new( @@ -172,12 +144,10 @@ public void OnMouseDown(LevelViewport viewport) MathF.Round(posVec.Y, MidpointRounding.AwayFromZero), selected[0].Position.Z); - DeselectAll(viewport.mEditContext); - - if (Points.Count - 1 == index) //is last point - AddPoint(viewport, new RailPoint(pos)); + if (rail.Points.Count - 1 == index) //is last point + AddPoint(ctx, new BGUnitRail.RailPoint(rail, pos)); else - InsertPoint(viewport, new RailPoint(pos), index + 1); + InsertPoint(ctx, new BGUnitRail.RailPoint(rail, pos), index + 1); } else if (ImGui.GetIO().KeyAlt && selected.Count == 0) //Add new point from last { @@ -188,52 +158,46 @@ public void OnMouseDown(LevelViewport viewport) MathF.Round(posVec.Y, MidpointRounding.AwayFromZero), 2); - DeselectAll(viewport.mEditContext); - AddPoint(viewport, new RailPoint(pos)); + DeselectAll(ctx); + AddPoint(ctx, new BGUnitRail.RailPoint(rail, pos)); } else { if (!ImGui.GetIO().KeyCtrl && !ImGui.GetIO().KeyShift) - DeselectAll(viewport.mEditContext); + DeselectAll(ctx); } - for (int i = 0; i < Points.Count; i++) + for (int i = 0; i < rail.Points.Count; i++) { - Vector3 point = Points[i].Position; - - var pos2D = viewport.WorldToScreen(new(point.X, point.Y, point.Z)); - Vector2 pnt = new(pos2D.X, pos2D.Y); - bool isHovered = (ImGui.GetMousePos() - pnt).Length() < 6.0f; + Vector3 point = rail.Points[i].Position; - if (isHovered) - ctx.Select(Points[i]); - - Points[i].PreviousPosition = point; + if (ChildPoints.TryGetValue(rail.Points[i], out RailPoint? childPoint)) + childPoint.PreviousPosition = point; } mouseDown = true; } private Vector2[] GetPoints(LevelViewport viewport) { - Vector2[] points = new Vector2[Points.Count]; - for (int i = 0; i < Points.Count; i++) + Vector2[] points = new Vector2[rail.Points.Count]; + for (int i = 0; i < rail.Points.Count; i++) { - Vector3 p = Points[i].Position; + Vector3 p = rail.Points[i].Position; points[i] = viewport.WorldToScreen(new(p.X, p.Y, p.Z)); } return points; } - public void OnMouseUp(LevelViewport viewport) + public void OnMouseUp(CourseAreaEditContext ctx, LevelViewport viewport) { mouseDown = false; if (transformStart) { - var batchAction = viewport.mEditContext.BeginBatchAction(); + var batchAction = ctx.BeginBatchAction(); foreach (var item in mTransformUndos) - viewport.mEditContext.CommitAction(item); + ctx.CommitAction(item); batchAction.Commit($"{IconUtil.ICON_ARROWS_ALT} Move Rail Points"); @@ -243,13 +207,11 @@ public void OnMouseUp(LevelViewport viewport) private List mTransformUndos = []; - public void OnSelecting(LevelViewport viewport) + public void OnSelecting(CourseAreaEditContext ctx, LevelViewport viewport) { if (!mouseDown) return; - var ctx = viewport.mEditContext; - Vector3 posVec = viewport.ScreenToWorld(ImGui.GetMousePos()); Vector3 diff = posVec - mouseDownPos; if (diff.X != 0 && diff.Y != 0 && !transformStart) @@ -257,20 +219,28 @@ public void OnSelecting(LevelViewport viewport) transformStart = true; //Store each selected point for undoing mTransformUndos.Clear(); - foreach (var point in GetSelected(viewport.mEditContext)) - mTransformUndos.Add(new TransformUndo(point.Transform)); + foreach (var point in GetSelected(ctx)) + { + if (!ChildPoints.TryGetValue(point, out RailPoint? childPoint)) + continue; + + mTransformUndos.Add(new TransformUndo(childPoint.Transform)); + } } bool anyTransformed = false; - for (int i = 0; i < Points.Count; i++) + for (int i = 0; i < rail.Points.Count; i++) { - if (transformStart && ctx.IsSelected(Points[i])) + if (transformStart && ctx.IsSelected(rail.Points[i])) { + if (!ChildPoints.TryGetValue(rail.Points[i], out RailPoint? childPoint)) + continue; + diff.X = MathF.Round(diff.X, MidpointRounding.AwayFromZero); diff.Y = MathF.Round(diff.Y, MidpointRounding.AwayFromZero); - posVec.Z = Points[i].Position.Z; - Points[i].Position = Points[i].PreviousPosition + diff; + posVec.Z = rail.Points[i].Position.Z; + rail.Points[i].Position = childPoint.PreviousPosition + diff; anyTransformed = true; } } @@ -279,46 +249,48 @@ public void OnSelecting(LevelViewport viewport) CourseUnit.GenerateTileSubUnits(); } - public void Render(LevelViewport viewport, ImDrawListPtr mDrawList) + void IViewportDrawable.Draw2D(CourseAreaEditContext ctx, LevelViewport viewport, ImDrawListPtr dl, ref bool isNewHoveredObj) { if (!Visible) return; - var ctx = viewport.mEditContext; - bool isSelected = viewport.mEditContext.IsSelected(this); + if (HitTest(viewport)) + isNewHoveredObj = true; + + bool isSelected = IsSelected(ctx); if (ImGui.IsMouseClicked(0) && ImGui.IsMouseDown(ImGuiMouseButton.Left)) - OnMouseDown(viewport); + OnMouseDown(ctx, viewport); if (ImGui.IsMouseReleased(0)) - OnMouseUp(viewport); + OnMouseUp(ctx, viewport); if (viewport.mEditorState == LevelViewport.EditorState.Selecting) - OnSelecting(viewport); + OnSelecting(ctx, viewport); - OnKeyDown(viewport); + OnKeyDown(ctx, viewport); - var lineThickness = viewport.HoveredObject == this ? 3.5f : 2.5f; + var lineThickness = viewport.IsHovered(this) ? 3.5f : 2.5f; - for (int i = 0; i < Points.Count; i++) + for (int i = 0; i < rail.Points.Count; i++) { - Vector3 point = Points[i].Position; + Vector3 point = rail.Points[i].Position; var pos2D = viewport.WorldToScreen(new(point.X, point.Y, point.Z)); //Next pos 2D Vector2 nextPos2D = Vector2.Zero; - if (i < Points.Count - 1) //is not last point + if (i < rail.Points.Count - 1) //is not last point { nextPos2D = viewport.WorldToScreen(new( - Points[i + 1].Position.X, - Points[i + 1].Position.Y, - Points[i + 1].Position.Z)); + rail.Points[i + 1].Position.X, + rail.Points[i + 1].Position.Y, + rail.Points[i + 1].Position.Z)); } - else if (IsClosed) //last point to first if closed + else if (rail.IsClosed) //last point to first if closed { nextPos2D = viewport.WorldToScreen(new( - Points[0].Position.X, - Points[0].Position.Y, - Points[0].Position.Z)); + rail.Points[0].Position.X, + rail.Points[0].Position.Y, + rail.Points[0].Position.Z)); } else //last point but not closed, draw no line continue; @@ -327,13 +299,13 @@ public void Render(LevelViewport viewport, ImDrawListPtr mDrawList) if (isSelected && line_color != Color_SlopeError) line_color = Color_SelectionEdit; - mDrawList.AddLine(pos2D, nextPos2D, line_color, lineThickness); + dl.AddLine(pos2D, nextPos2D, line_color, lineThickness); if (isSelected) { //Arrow display - Vector3 next = i < Points.Count - 1 ? Points[i + 1].Position : Points[0].Position; - Vector3 dist = next - Points[i].Position; + Vector3 next = i < rail.Points.Count - 1 ? rail.Points[i + 1].Position : rail.Points[0].Position; + Vector3 dist = next - rail.Points[i].Position; var angleInRadian = MathF.Atan2(dist.Y, dist.X); //angle in radian var rotation = Matrix4x4.CreateRotationZ(angleInRadian); @@ -341,35 +313,14 @@ public void Render(LevelViewport viewport, ImDrawListPtr mDrawList) var line = Vector3.TransformNormal(new Vector3(0, width, 0), rotation); - Vector2[] arrow = new Vector2[2]; - arrow[0] = viewport.WorldToScreen(Points[i].Position + dist / 2f); - arrow[1] = viewport.WorldToScreen(Points[i].Position + dist / 2f + line); - + Vector2[] arrow = + [ + viewport.WorldToScreen(rail.Points[i].Position + dist / 2f), + viewport.WorldToScreen(rail.Points[i].Position + dist / 2f + line), + ]; float alpha = 0.5f; - mDrawList.AddLine(arrow[0], arrow[1], ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, alpha)), lineThickness); - } - } - - if (isSelected) - { - for (int i = 0; i < Points.Count; i++) - { - Vector3 point = Points[i].Position; - var pos2D = viewport.WorldToScreen(new(point.X, point.Y, point.Z)); - Vector2 pnt = new(pos2D.X, pos2D.Y); - - //Display point color - uint color = 0xFFFFFFFF; - if (Points[i].IsHovered || ctx.IsSelected(Points[i])) - color = ImGui.ColorConvertFloat4ToU32(new(0.84f, .437f, .437f, 1)); - - mDrawList.AddCircleFilled(pos2D, 6.0f, color); - - bool isHovered = (ImGui.GetMousePos() - pnt).Length() < 6.0f; - Points[i].IsHovered = isHovered; - if (isHovered) - viewport.HoveredObject = Points[i]; + dl.AddLine(arrow[0], arrow[1], ImGui.ColorConvertFloat4ToU32(new Vector4(1f, 1f, 1f, alpha)), lineThickness); } } } @@ -398,33 +349,64 @@ private bool IsValidAngle(Vector2 point1, Vector2 point2) return validAngles.Contains(MathF.Round(angle)); } - public class RailPoint + void IViewportSelectable.OnSelect(CourseAreaEditContext editContext) { - public Transform Transform = new Transform(); + IViewportSelectable.DefaultSelect(editContext, rail); + } - public Vector3 Position + public class RailPoint : ISceneObject, IViewportSelectable, IViewportDrawable + { + public RailPoint(BGUnitRail.RailPoint point) { - get { return Transform.Position; } - set { Transform.Position = value; } + this.point = point; + //TODO remove this as soon as we have an ITransformable interface with a SetTransform + Transform.Update += () => + { + point.Position = Transform.Position; + }; } - public bool IsHovered { get; set; } + private readonly BGUnitRail.RailPoint point; + + public Transform Transform = new Transform(); //For transforming public Vector3 PreviousPosition { get; set; } - public RailPoint(Vector3 pos) + private bool HitTest(LevelViewport viewport) { - Position = pos; + var pos2D = viewport.WorldToScreen(point.Position); + Vector2 pnt = new(pos2D.X, pos2D.Y); + return (ImGui.GetMousePos() - pnt).Length() < 6.0f; } - public bool HitTest(LevelViewport viewport) + void ISceneObject.Update(ISceneUpdateContext ctx, bool isSelected) { - Vector3 point = Position; - var pos2D = viewport.WorldToScreen(new(point.X, point.Y, point.Z)); - Vector2 pnt = new(pos2D.X, pos2D.Y); - return (ImGui.GetMousePos() - pnt).Length() < 6.0f; + } + + void IViewportSelectable.OnSelect(CourseAreaEditContext ctx) + { + ctx.WithSuspendUpdateDo(() => + { + IViewportSelectable.DefaultSelect(ctx, point); + ctx.Select(point.mRail); + }); + } + + void IViewportDrawable.Draw2D(CourseAreaEditContext ctx, LevelViewport viewport, ImDrawListPtr dl, ref bool isNewHoveredObj) + { + var pos2D = viewport.WorldToScreen(point.Position); + + //Display point color + uint color = 0xFFFFFFFF; + if (ctx.IsSelected(point)) + color = ImGui.ColorConvertFloat4ToU32(new(0.84f, .437f, .437f, 1)); + + dl.AddCircleFilled(pos2D, viewport.IsHovered(this) ? 6.0f : 4.0f, color); + + if (HitTest(viewport)) + isNewHoveredObj = true; } } } diff --git a/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs b/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs index 9a59aef3..696ecfc3 100644 --- a/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs +++ b/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs @@ -1,17 +1,28 @@ using Fushigi.course; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Fushigi.ui.SceneObjects.bgunit { - internal class BgUnitSceneObj(CourseUnit bgunit) : ISceneObject + internal class BGUnitSceneObj(CourseUnit unit) : ISceneObject { - public void Update(ISceneUpdateContext ctx) + public void Update(ISceneUpdateContext ctx, bool isSelected) { - + void CreateOrUpdateRail(BGUnitRail rail) + { + ctx.UpdateOrCreateObjFor(rail, () => new BGUnitRailSceneObj(unit, rail)); + } + + foreach (var wall in unit.Walls) + { + CreateOrUpdateRail(wall.ExternalRail); + foreach (var rail in wall.InternalRails) + { + CreateOrUpdateRail(rail); + } + } + + //Don't include belt for now. TODO how should this be handled? + //foreach (var rail in unit.mBeltRails) + // CreateOrUpdateRail(rail); } } } diff --git a/Fushigi/ui/SceneObjects/bgunit/UnitRailPointUndo.cs b/Fushigi/ui/SceneObjects/bgunit/UnitRailPointUndo.cs deleted file mode 100644 index b83b1ced..00000000 --- a/Fushigi/ui/SceneObjects/bgunit/UnitRailPointUndo.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Fushigi.util; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Linq; - -namespace Fushigi.ui.SceneObjects.bgunit -{ - internal class UnitRailPointAddUndo : IRevertable - { - public string Name { get; set; } - - public BGUnitRailSceneObj Rail; - - public BGUnitRailSceneObj.RailPoint Point; - - public int Index; - - public UnitRailPointAddUndo(BGUnitRailSceneObj rail, BGUnitRailSceneObj.RailPoint point, int index = -1) - { - //Undo display name - Name = $"{IconUtil.ICON_PLUS_CIRCLE} Rail Point Add"; - //The rail to remove the point to - Rail = rail; - //The point to remove - Point = point; - Index = index; - } - - public IRevertable Revert() - { - var index = Index != -1 ? Index : Rail.Points.IndexOf(Point); - - //Revert to removale - if (Rail.Points.Contains(Point)) - Rail.Points.Remove(Point); - - //Create revert stack - return new UnitRailPointDeleteUndo(Rail, Point, index); - } - } - - internal class UnitRailPointDeleteUndo : IRevertable - { - public string Name { get; set; } - - public BGUnitRailSceneObj Rail; - - public BGUnitRailSceneObj.RailPoint Point; - - public int Index; - - public UnitRailPointDeleteUndo(BGUnitRailSceneObj rail, BGUnitRailSceneObj.RailPoint point, int index = -1) - { - //Undo display name - Name = $"{IconUtil.ICON_TRASH} Rail Point Remove"; - //The rail to add the point to - Rail = rail; - //The point to add - Point = point; - Index = index; - //Keep original point placement - if (rail.Points.Contains(Point) && index == -1) - Index = rail.Points.IndexOf(Point); - } - - public IRevertable Revert() - { - //Revert to removale - if (!Rail.Points.Contains(Point)) - { - if (Index != -1) - Rail.Points.Insert(Index, Point); - else - Rail.Points.Add(Point); - } - - //Create revert stack - return new UnitRailPointAddUndo(Rail, Point); - } - } -} diff --git a/Fushigi/ui/widgets/CourseScene.cs b/Fushigi/ui/widgets/CourseScene.cs index 4e688692..3ae5c211 100644 --- a/Fushigi/ui/widgets/CourseScene.cs +++ b/Fushigi/ui/widgets/CourseScene.cs @@ -1,36 +1,14 @@ -using Fushigi.Byml; using Fushigi.course; -using Fushigi.gl; using Fushigi.gl.Bfres; using Fushigi.param; -using Fushigi.rstb; using Fushigi.ui.SceneObjects; using Fushigi.ui.SceneObjects.bgunit; using Fushigi.util; -using FuzzySharp.SimilarityRatio; -using FuzzySharp.SimilarityRatio.Scorer.StrategySensitive; using ImGuiNET; -using Newtonsoft.Json.Linq; -using Silk.NET.Input; using Silk.NET.OpenGL; -using Silk.NET.SDL; -using Silk.NET.Windowing; -using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; -using System.Drawing; -using System.Linq; -using System.Net.Http.Headers; using System.Numerics; -using System.Reflection.Emit; using System.Runtime.CompilerServices; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using ZstdSharp.Unsafe; -using static System.Net.Mime.MediaTypeNames; namespace Fushigi.ui.widgets { @@ -142,11 +120,14 @@ public void PrepareResourcesLoad(GL gl) BfresCache.Load(gl, file); } + public void Undo() => areaScenes[selectedArea].EditContext.Undo(); + public void Redo() => areaScenes[selectedArea].EditContext.Redo(); + public bool HasUnsavedChanges() { foreach (var area in course.GetAreas()) { - if(lastSavedAction[area] != areaScenes[area].EditContext.GetLastAction()) + if (lastSavedAction[area] != areaScenes[area].EditContext.GetLastAction()) return true; } @@ -163,7 +144,7 @@ public void Save() lastSavedAction[area] = areaScenes[area].EditContext.GetLastAction(); } } - catch (Exception ex) + catch (Exception ex) { MessageBox box = new MessageBox(MessageBox.MessageBoxType.Ok); box.Show("Error", ex.Message); @@ -199,7 +180,7 @@ public void DrawUI(GL gl, double deltaSeconds) LinkDeletionCheck(); } - + ulong selectionVersionBefore = areaScenes[selectedArea].EditContext.SelectionVersion; bool status = ImGui.Begin("Viewports"); @@ -215,7 +196,7 @@ public void DrawUI(GL gl, double deltaSeconds) if (ImGui.Begin(area.GetName())) { - if(ImGui.IsWindowFocused()) + if (ImGui.IsWindowFocused()) { selectedArea = area; activeViewport = viewport; @@ -229,7 +210,7 @@ public void DrawUI(GL gl, double deltaSeconds) ImGui.SetNextItemAllowOverlap(); viewport.Draw(ImGui.GetContentRegionAvail(), deltaSeconds, mLayersVisibility); - if(activeViewport != viewport) + if (activeViewport != viewport) ImGui.GetWindowDrawList().AddRectFilled(topLeft, topLeft + size, 0x44000000); //Allow button press, align to top of the screen @@ -240,7 +221,8 @@ public void DrawUI(GL gl, double deltaSeconds) ImGui.OpenPopup("AreaParams"); //Display Mouse Position - if(ImGui.IsWindowHovered()){ + if (ImGui.IsWindowHovered()) + { var _mousePos = activeViewport.ScreenToWorld(ImGui.GetMousePos()); ImGui.Text("X: " + Math.Round(_mousePos.X, 3) + "\nY: " + Math.Round(_mousePos.Y, 3)); } @@ -263,7 +245,7 @@ public void DrawUI(GL gl, double deltaSeconds) for (int i = 0; i < course.GetAreaCount(); i++) { var area = course.GetArea(i); - if(area.mActorHolder.GetActors().Any(x=>x.mActorName=="PlayerLocator")) + if (area.mActorHolder.GetActors().Any(x => x.mActorName == "PlayerLocator")) { ImGui.SetWindowFocus(area.GetName()); break; @@ -301,7 +283,7 @@ private void SelectActorToAdd() } if (ImGui.BeginListBox("Select the actor you want to add.", ImGui.GetContentRegionAvail())) - { + { foreach (string actor in filteredActors) { ImGui.Selectable(actor); @@ -355,7 +337,7 @@ private void SelectActorToAddLayer() } if (ImGui.BeginListBox("Select the layer you want to add the actor to.", ImGui.GetContentRegionAvail())) - { + { foreach (string layer in fileteredLayers) { ImGui.Selectable(layer); @@ -438,9 +420,11 @@ private void LinkDeletionCheck() /* nothing to worry about here */ if (dstMsgStrs.Count == 0 && srcMsgStr.Count == 0) { - if (activeViewport.mEditContext.IsAnySelected()) + var ctx = areaScenes[selectedArea].EditContext; + + if (ctx.IsAnySelected()) { - activeViewport.mEditContext.DeleteSelectedActors(); + ctx.DeleteSelectedActors(); } Console.WriteLine("Switching state to EditorState.Selecting"); activeViewport.mEditorState = LevelViewport.EditorState.Selecting; @@ -605,7 +589,8 @@ private void SelectionParameterPanel() ImGui.NextColumn(); ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - if (ImGui.InputText($"##{name}", ref name, 512, ImGuiInputTextFlags.EnterReturnsTrue)) { + if (ImGui.InputText($"##{name}", ref name, 512, ImGuiInputTextFlags.EnterReturnsTrue)) + { editContext.SetObjectName(mSelectedActor, name); } @@ -623,14 +608,14 @@ private void SelectionParameterPanel() if (ImGui.Selectable(layer)) { //item is selected - Console.WriteLine("Changing "+mSelectedActor.mName+"'s layer from "+mSelectedActor.mLayer+" to "+layer+"."); + Console.WriteLine("Changing " + mSelectedActor.mName + "'s layer from " + mSelectedActor.mLayer + " to " + layer + "."); mSelectedActor.mLayer = layer; } } ImGui.EndCombo(); } - + @@ -650,7 +635,7 @@ private void SelectionParameterPanel() ImGui.Text("Links"); ImGui.Separator(); - + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); if (ImGui.BeginCombo("##Add Link", "Add Link")) @@ -673,7 +658,8 @@ private void SelectionParameterPanel() var destHashes = selectedArea.mLinkHolder.GetDestHashesFromSrc(mSelectedActor.GetHash()); - foreach ((string linkName, List hashArray) in destHashes) { + foreach ((string linkName, List hashArray) in destHashes) + { ImGui.Text(linkName); ImGui.Columns(3); @@ -686,15 +672,15 @@ private void SelectionParameterPanel() ImGui.NextColumn(); CourseActor? destActor = selectedArea.mActorHolder[hashArray[i]]; - + if (destActor != null) { if (ImGui.Button(destActor.mName, new Vector2(ImGui.GetContentRegionAvail().X, 0))) { - mSelectedActor = destActor; - activeViewport.SelectedActor(destActor); - activeViewport.Camera.Target.X = destActor.mTranslation.X; - activeViewport.Camera.Target.Y = destActor.mTranslation.Y; + mSelectedActor = destActor; + activeViewport.SelectedActor(destActor); + activeViewport.Camera.Target.X = destActor.mTranslation.X; + activeViewport.Camera.Target.Y = destActor.mTranslation.Y; } } else @@ -710,13 +696,13 @@ private void SelectionParameterPanel() var cursorSP = ImGui.GetCursorScreenPos(); var padding = ImGui.GetStyle().FramePadding; - uint WithAlphaFactor(uint color, float factor) => color & 0xFFFFFF | ((uint)((color >> 24)*factor) << 24); + uint WithAlphaFactor(uint color, float factor) => color & 0xFFFFFF | ((uint)((color >> 24) * factor) << 24); float deleteButtonWidth = ImGui.GetFrameHeight() * 1.6f; float columnWidth = ImGui.GetContentRegionAvail().X; - ImGui.PushClipRect(cursorSP, + ImGui.PushClipRect(cursorSP, cursorSP + new Vector2(columnWidth - deleteButtonWidth, ImGui.GetFrameHeight()), true); var cursor = ImGui.GetCursorPos(); @@ -742,8 +728,8 @@ private void SelectionParameterPanel() bool clicked = ImGui.InvisibleButton("##Delete Link", new Vector2(deleteButtonWidth, ImGui.GetFrameHeight())); string deleteIcon = IconUtil.ICON_TRASH_ALT; - ImGui.GetWindowDrawList().AddText(cursorSP + new Vector2((deleteButtonWidth - ImGui.CalcTextSize(deleteIcon).X)/2, padding.Y), - WithAlphaFactor(ImGui.GetColorU32(ImGuiCol.Text), ImGui.IsItemHovered() ? 1 : 0.5f), + ImGui.GetWindowDrawList().AddText(cursorSP + new Vector2((deleteButtonWidth - ImGui.CalcTextSize(deleteIcon).X) / 2, padding.Y), + WithAlphaFactor(ImGui.GetColorU32(ImGuiCol.Text), ImGui.IsItemHovered() ? 1 : 0.5f), deleteIcon); if (ImGui.IsItemHovered()) @@ -758,7 +744,7 @@ private void SelectionParameterPanel() } ImGui.Separator(); - + } } @@ -785,7 +771,7 @@ private void SelectionParameterPanel() ImGui.Columns(1); } } - else if (editContext.IsSingleObjectSelected(out BGUnitRailSceneObj? mSelectedUnitRail)) + else if (editContext.IsSingleObjectSelected(out BGUnitRail? mSelectedUnitRail)) { ImGui.AlignTextToFramePadding(); ImGui.Text($"Selected BG Unit Rail"); @@ -797,7 +783,7 @@ private void SelectionParameterPanel() ImGui.Columns(2); ImGui.Text("IsClosed"); ImGui.NextColumn(); if (ImGui.Checkbox("##IsClosed", ref mSelectedUnitRail.IsClosed)) - mSelectedUnitRail.CourseUnit.GenerateTileSubUnits(); + mSelectedUnitRail.mCourseUnit.GenerateTileSubUnits(); ImGui.NextColumn(); @@ -810,7 +796,7 @@ private void SelectionParameterPanel() //Update depth to all points foreach (var p in mSelectedUnitRail.Points) p.Position = new System.Numerics.Vector3(p.Position.X, p.Position.Y, depth); - mSelectedUnitRail.CourseUnit.GenerateTileSubUnits(); + mSelectedUnitRail.mCourseUnit.GenerateTileSubUnits(); } ImGui.NextColumn(); @@ -822,7 +808,7 @@ private void SelectionParameterPanel() ImGui.AlignTextToFramePadding(); ImGui.Text($"Selected Global Link"); ImGui.NewLine(); - + if (ImGui.Button("Delete Link")) { course.RemoveGlobalLink(mSelectedGlobalLink); @@ -837,7 +823,7 @@ private void SelectionParameterPanel() ImGui.Columns(2); ImGui.Text("Source Hash"); ImGui.NextColumn(); string srcHash = mSelectedGlobalLink.mSource.ToString(); - if (ImGui.InputText("##Source Hash", ref srcHash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) + if (ImGui.InputText("##Source Hash", ref srcHash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) { mSelectedGlobalLink.mSource = Convert.ToUInt64(srcHash); } @@ -850,7 +836,7 @@ private void SelectionParameterPanel() { mSelectedGlobalLink.mDest = Convert.ToUInt64(destHash); } - + ImGui.NextColumn(); ImGui.Text("Link Type"); ImGui.NextColumn(); @@ -884,7 +870,7 @@ private void SelectionParameterPanel() } ImGui.NextColumn(); - ImGui.Text("IsClosed"); + ImGui.Text("IsClosed"); ImGui.NextColumn(); ImGui.Checkbox("##IsClosed", ref mSelectedRail.mIsClosed); @@ -895,7 +881,7 @@ private void SelectionParameterPanel() { ImGui.Columns(2); - foreach(KeyValuePair param in mSelectedRail.mParameters) + foreach (KeyValuePair param in mSelectedRail.mParameters) { string type = param.Value.GetType().ToString(); ImGui.Text(param.Key); @@ -1118,12 +1104,19 @@ private void CourseUnitView(CourseUnitHolder unitHolder) { var editContext = areaScenes[selectedArea].EditContext; + BGUnitRailSceneObj GetRailSceneObj(object courseObject) + { + if (!areaScenes[selectedArea].TryGetObjFor(courseObject, out var sceneObj)) + throw new Exception("Couldn't find scene object"); + return (BGUnitRailSceneObj)sceneObj; + } + ImGui.Text("Select a Wall"); ImGui.Text("Alt + Left Click to add point"); if (ImGui.Button("Add Tile Unit", new Vector2(100, 22))) { - unitHolder.mUnits.Add(new CourseUnit()); + editContext.AddBgUnit(new CourseUnit()); } List removed_tile_units = new List(); @@ -1142,9 +1135,9 @@ private void CourseUnitView(CourseUnitHolder unitHolder) { foreach (var wall in unit.Walls) { - wall.ExternalRail.Visible = unit.Visible; + GetRailSceneObj(wall.ExternalRail).Visible = unit.Visible; foreach (var rail in wall.InternalRails) - rail.Visible = unit.Visible; + GetRailSceneObj(rail).Visible = unit.Visible; } } ImGui.SameLine(); @@ -1156,14 +1149,14 @@ private void CourseUnitView(CourseUnitHolder unitHolder) } if (expanded) { - void RailListItem(string type, BGUnitRailSceneObj rail, int id) + void RailListItem(string type, BGUnitRail rail, int id) { bool isSelected = editContext.IsSelected(rail); string wallname = $"{type} {id}"; ImGui.Indent(); - if (ImGui.Checkbox($"##Visible{wallname}", ref rail.Visible)) + if (ImGui.Checkbox($"##Visible{wallname}", ref GetRailSceneObj(rail).Visible)) { } @@ -1173,7 +1166,7 @@ void RailListItem(string type, BGUnitRailSceneObj rail, int id) void SelectRail() { - editContext.DeselectAllOfType(); + editContext.DeselectAllOfType(); editContext.Select(rail); } @@ -1206,7 +1199,7 @@ void SelectRail() if (ImGui.BeginPopupContextWindow("RailMenu", ImGuiPopupFlags.MouseButtonRight)) { if (ImGui.MenuItem("Add Wall")) - unit.Walls.Add(new Wall(unit)); + editContext.AddWall(unit, new Wall(unit)); if (ImGui.MenuItem($"Remove {name}")) removed_tile_units.Add(unit); @@ -1216,12 +1209,19 @@ void SelectRail() } if (ImGui.Button("Add Wall")) - unit.Walls.Add(new Wall(unit)); + editContext.AddWall(unit, new Wall(unit)); ImGui.SameLine(); if (ImGui.Button("Remove Wall")) { - foreach (var wall in unit.Walls.Where(x => editContext.IsSelected(x.ExternalRail)).ToList()) - unit.Walls.Remove(wall); + editContext.WithSuspendUpdateDo(() => + { + for (int i = unit.Walls.Count - 1; i >= 0; i--) + { + //TODO is that REALLY how we want to do this? + if (editContext.IsSelected(unit.Walls[i].ExternalRail)) + editContext.DeleteWall(unit, unit.Walls[i]); + } + }); } foreach (var wall in unit.Walls) @@ -1252,11 +1252,11 @@ void SelectRail() ImGui.TreePop(); } } - + if (removed_tile_units.Count > 0) { foreach (var tile in removed_tile_units) - unitHolder.mUnits.Remove(tile); + editContext.DeleteBgUnit(tile); removed_tile_units.Clear(); } } @@ -1316,7 +1316,8 @@ private void CourseRailsView(CourseRailHolder railHolder) } } - private void CourseGlobalLinksView(CourseLinkHolder linkHolder) { + private void CourseGlobalLinksView(CourseLinkHolder linkHolder) + { foreach (CourseLink link in linkHolder.GetLinks()) { if (ImGui.Selectable($"Link {linkHolder.GetLinks().IndexOf(link)}")) @@ -1341,7 +1342,7 @@ private static bool ToggleButton(string id, string textOn, string textOff, ref b if (size.X <= 0 || size.Y <= 0) { - + size.X = MathF.Max(textOffSize.X, textOnSize.X) + ImGui.GetStyle().FramePadding.X * 2; size.Y = MathF.Max(textOffSize.Y, textOnSize.Y) + ImGui.GetStyle().FramePadding.Y * 2; } @@ -1384,7 +1385,7 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) cp, cp + new Vector2(ImGui.GetContentRegionAvail().X, headerHeight), ImGui.GetColorU32(ImGuiCol.FrameBg)); - ImGui.GetWindowDrawList().AddText(ImGui.GetFont(), em * 0.9f, + ImGui.GetWindowDrawList().AddText(ImGui.GetFont(), em * 0.9f, cp + new Vector2(em, (headerHeight - em) / 2 + 0.05f), 0xFF_FF_FF_FF, "Layers"); @@ -1392,7 +1393,7 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) var wcMax = wcMin + ImGui.GetContentRegionAvail(); ImGui.SetCursorScreenPos(new Vector2(wcMax.X - margin, cp.Y + (headerHeight - em) / 2)); - if (ToggleButton($"VisibleCheckbox All", IconUtil.ICON_EYE, IconUtil.ICON_EYE_SLASH, + if (ToggleButton($"VisibleCheckbox All", IconUtil.ICON_EYE, IconUtil.ICON_EYE_SLASH, ref mAllLayersVisible, new Vector2(em))) UpdateAllLayerVisiblity(); @@ -1475,7 +1476,7 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) { activeViewport.FrameSelectedActor(actor); } - + ImGui.NextColumn(); ImGui.BeginDisabled(); diff --git a/Fushigi/ui/widgets/LevelViewport.cs b/Fushigi/ui/widgets/LevelViewport.cs index 99e55d51..8eea56ef 100644 --- a/Fushigi/ui/widgets/LevelViewport.cs +++ b/Fushigi/ui/widgets/LevelViewport.cs @@ -1,47 +1,47 @@ -using Fushigi.Byml; +using Fushigi.actor_pack.components; using Fushigi.course; +using Fushigi.gl; +using Fushigi.gl.Bfres; +using Fushigi.util; using ImGuiNET; -using System; -using System.Collections.Generic; +using Silk.NET.OpenGL; using System.Drawing; -using System.Linq; using System.Numerics; -using System.Text; -using System.Threading.Tasks; -using System.Security.Cryptography; -using Vector3 = System.Numerics.Vector3; -using static Fushigi.course.CourseUnit; -using System.Xml.Linq; -using System.Reflection; -using Microsoft.VisualBasic; -using System.Runtime.CompilerServices; -using Silk.NET.OpenGL; -using Fushigi.gl; -using Fushigi.util; -using System.Reflection.PortableExecutable; -using static Fushigi.util.MessageBox; -using Fushigi.gl.Bfres; -using Fushigi.actor_pack.components; using System.Runtime.InteropServices; -using static Fushigi.ui.SceneObjects.bgunit.BGUnitRailSceneObj; -using Fushigi.ui.SceneObjects.bgunit; +using static Fushigi.course.CourseUnit; +using Vector3 = System.Numerics.Vector3; namespace Fushigi.ui.widgets { - interface IViewportDrawableObject + interface IViewportDrawable { - void Draw2D(CourseAreaEditContext editContext, LevelViewport viewport, ViewportImGuiInteractionState iState); + void Draw2D(CourseAreaEditContext editContext, LevelViewport viewport, ImDrawListPtr dl, ref bool isNewHoveredObj); } - interface ITransformableObject + interface IViewportSelectable { - Transform Transform { get; } + void OnSelect(CourseAreaEditContext editContext); + public static void DefaultSelect(CourseAreaEditContext ctx, object selectable) + { + if (ImGui.IsKeyDown(ImGuiKey.LeftShift)) + { + ctx.Select(selectable); + } + else + { + ctx.WithSuspendUpdateDo(() => + { + ctx.DeselectAll(); + ctx.Select(selectable); + }); + + } + } } - struct ViewportImGuiInteractionState + interface ITransformableObject { - public bool IsHovered; - public bool IsActive; + Transform Transform { get; } } internal class LevelViewport(CourseArea area, GL gl, CourseAreaScene areaScene) @@ -49,12 +49,15 @@ internal class LevelViewport(CourseArea area, GL gl, CourseAreaScene areaScene) readonly CourseArea mArea = area; //this is only so BgUnitRail works, TODO make private - public readonly CourseAreaEditContext mEditContext = areaScene.EditContext; + private readonly CourseAreaEditContext mEditContext = areaScene.EditContext; ImDrawListPtr mDrawList; public EditorMode mEditorMode = EditorMode.Actors; public EditorState mEditorState = EditorState.Selecting; + public bool IsViewportHovered; + public bool IsViewportActive; + Vector2 mSize = Vector2.Zero; private Vector3? mSelectedPoint; @@ -72,7 +75,8 @@ internal class LevelViewport(CourseArea area, GL gl, CourseAreaScene areaScene) public GLFramebuffer Framebuffer; //Draws opengl data into the viewport public HDRScreenBuffer HDRScreenBuffer = new HDRScreenBuffer(); - public object? HoveredObject; + //TODO make this an ISceneObject? as soon as there's a SceneObj class for each course object + private object? mHoveredObject; public CourseLink? CurCourseLink = null; public Vector3? HoveredPoint; @@ -96,6 +100,8 @@ public enum EditorMode Units } + public bool IsHovered(ISceneObject obj) => mHoveredObject == obj; + public Matrix4x4 GetCameraMatrix() => Camera.ViewProjectionMatrix; public Vector2 WorldToScreen(Vector3 pos) => WorldToScreen(pos, out _); @@ -125,7 +131,7 @@ public Vector3 ScreenToWorld(Vector2 pos, float ndcDepth = 0) var world = Vector4.Transform(ndc, Camera.ViewProjectionMatrixInverse); world /= world.W; - return new (world.X, world.Y, world.Z); + return new(world.X, world.Y, world.Z); } public void FrameSelectedActor(CourseActor actor) @@ -133,9 +139,9 @@ public void FrameSelectedActor(CourseActor actor) this.Camera.Target = new Vector3(actor.mTranslation.X, actor.mTranslation.Y, 0); } - public void SelectBGUnit(BGUnitRailSceneObj rail) + public void SelectBGUnit(BGUnitRail rail) { - mEditContext.DeselectAllOfType(); + mEditContext.DeselectAllOfType(); mEditContext.Select(rail); } @@ -152,18 +158,18 @@ public void SelectedActor(CourseActor actor) } } - public void HandleCameraControls(double deltaSeconds, bool mouseHover, bool mouseActive) + public void HandleCameraControls(double deltaSeconds) { bool isPanGesture = (ImGui.IsMouseDragging(ImGuiMouseButton.Middle)) || (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && ImGui.GetIO().KeyShift); - if (mouseActive && isPanGesture) + if (IsViewportActive && isPanGesture) { Camera.Target += ScreenToWorld(ImGui.GetMousePos() - ImGui.GetIO().MouseDelta) - ScreenToWorld(ImGui.GetMousePos()); } - if (mouseHover) + if (IsViewportHovered) { Camera.Distance *= MathF.Pow(2, -ImGui.GetIO().MouseWheel / 10); @@ -227,7 +233,7 @@ public void DrawScene3D(Vector2 size, IDictionary layersVisibility } Framebuffer.Unbind(); - ImGui.SetCursorScreenPos(mTopLeft); + ImGui.SetCursorScreenPos(mTopLeft); //Draw final output in post buffer HDRScreenBuffer.Render(gl, (int)size.X, (int)size.Y, (GLTexture2D)Framebuffer.Attachments[0]); @@ -265,18 +271,19 @@ private void RenderActor(CourseActor actor, ModelInfo modelInfo) var mat = scaleMat * rotMat * transMat; - var model = render.Models[modelName]; + var model = render.Models[modelName]; //switch for drawing models with different methods easier - switch (modelName){ + switch (modelName) + { case "DokanTop": - var matPTop = - Matrix4x4.CreateScale(actor.mScale.X, actor.mScale.X, actor.mScale.Z) * - Matrix4x4.CreateTranslation(0, (actor.mScale.Y-actor.mScale.X)*2, 0) * + var matPTop = + Matrix4x4.CreateScale(actor.mScale.X, actor.mScale.X, actor.mScale.Z) * + Matrix4x4.CreateTranslation(0, (actor.mScale.Y - actor.mScale.X) * 2, 0) * rotMat * transMat; - var matPMid = - Matrix4x4.CreateScale(actor.mScale.X, actor.mScale.Y*2, actor.mScale.Z) * + var matPMid = + Matrix4x4.CreateScale(actor.mScale.X, actor.mScale.Y * 2, actor.mScale.Z) * rotMat * transMat; @@ -294,13 +301,13 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la mLayersVisibility = layersVisibility; mTopLeft = ImGui.GetCursorScreenPos(); - ImGui.InvisibleButton("canvas", size, + ImGui.InvisibleButton("canvas", size, ImGuiButtonFlags.MouseButtonLeft | ImGuiButtonFlags.MouseButtonRight | ImGuiButtonFlags.MouseButtonMiddle); - bool mouseHover = ImGui.IsItemHovered(); - bool mouseActive = ImGui.IsItemActive(); + IsViewportHovered = ImGui.IsItemHovered(); + IsViewportActive = ImGui.IsItemActive(); - if (size.X*size.Y == 0) + if (size.X * size.Y == 0) return; mSize = size; @@ -308,7 +315,7 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la ImGui.PushClipRect(mTopLeft, mTopLeft + size, true); - HandleCameraControls(deltaSeconds, mouseHover, mouseActive); + HandleCameraControls(deltaSeconds); if (Camera.Width != mSize.X || Camera.Height != mSize.Y) { @@ -324,16 +331,16 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la DrawGrid(); DrawAreaContent(); - if (!mouseHover) - HoveredObject = null; + if (!IsViewportHovered) + mHoveredObject = null; + + CourseActor? hoveredActor = mHoveredObject as CourseActor; - CourseActor? hoveredActor = HoveredObject as CourseActor; - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - if(hoveredActor != null) + if (hoveredActor != null) ImGui.SetTooltip($"{hoveredActor.mActorName}"); - + if (ImGui.IsKeyPressed(ImGuiKey.Z) && ImGui.GetIO().KeySuper) { mEditContext.Undo(); @@ -342,10 +349,12 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la { mEditContext.Redo(); } - } else { - if(hoveredActor != null) + } + else + { + if (hoveredActor != null) ImGui.SetTooltip($"{hoveredActor.mActorName}"); - + if (ImGui.IsKeyPressed(ImGuiKey.Z) && ImGui.GetIO().KeyCtrl) { mEditContext.Undo(); @@ -400,7 +409,7 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la if (ImGui.IsItemClicked()) { - bool isModeActor = HoveredObject != null; + bool isModeActor = mHoveredObject != null; bool isModeUnit = HoveredPoint != null; if (isModeActor && !isModeUnit) @@ -415,22 +424,18 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la /* if the user clicked somewhere and it was not hovered over an element, * we clear our selected actors array */ - if (HoveredObject == null) + if (mHoveredObject == null) { mEditContext.DeselectAll(); } - else if(HoveredObject is not BGUnitRailSceneObj && - HoveredObject is not BGUnitRailSceneObj.RailPoint) + else if (mHoveredObject is IViewportSelectable obj) { - if (ImGui.IsKeyDown(ImGuiKey.LeftShift)) - { - mEditContext.Select(HoveredObject!); - } - else - { - mEditContext.DeselectAll(); - mEditContext.Select(HoveredObject!); - } + obj.OnSelect(mEditContext); + } + else + { + //TODO remove this once all course objects have IViewportSelectable SceneObjs + IViewportSelectable.DefaultSelect(mEditContext, mHoveredObject); } if (HoveredPoint == null) @@ -484,7 +489,7 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la } else if (isFocused && mEditorState == EditorState.DeleteActorLinkCheck) { - + } else if (isFocused && mEditorState == EditorState.DeletingActor) { @@ -500,7 +505,7 @@ public void Draw(Vector2 size, double deltaSeconds, IDictionary la } else { - if(hoveredActor != null) + if (hoveredActor != null) ImGui.SetTooltip($""" Click to delete {hoveredActor.mActorName}. Hold SHIFT to delete multiple actors, ESCAPE to cancel. @@ -578,7 +583,8 @@ Click on any actor to delete it. if (ImGui.IsMouseClicked(ImGuiMouseButton.Left)) { - if (mEditorState == EditorState.SelectingLinkDest) { + if (mEditorState == EditorState.SelectingLinkDest) + { if (hoveredActor != null) { /* new links have a destination of 0 because there is no hash associated with a null actor */ @@ -664,7 +670,7 @@ void DrawGridLines(bool is_vertical, { bool is_major_tick = (i + tick_offset) % major_tick_interval == 0; - float t = ((min_tick_value + i * tick_interval) - min_value) / (max_value-min_value); + float t = ((min_tick_value + i * tick_interval) - min_value) / (max_value - min_value); Vector4 colorVec = ImGui.ColorConvertU32ToFloat4(GridColor); colorVec.W *= is_major_tick ? 1f : blend; @@ -683,7 +689,7 @@ void DrawAreaContent() foreach (var unit in this.mArea.mUnitHolder.mUnits) { - if(!unit.Visible) + if (!unit.Visible) continue; if (unit.mTileSubUnits.Count > 0) @@ -768,37 +774,18 @@ void DrawAreaContent() } } - foreach (var wall in unit.Walls) - { - if (wall.ExternalRail != null) - { - wall.ExternalRail.Render(this, mDrawList); - if (wall.ExternalRail.HitTest(this)) - newHoveredObject = wall.ExternalRail; + ISceneObject? newHoveredSceneObj = null; - foreach (var pt in wall.ExternalRail.Points) - { - if (pt.HitTest(this)) - newHoveredObject = pt; - } - } - foreach (BGUnitRailSceneObj rail in wall.InternalRails) - { - rail.Render(this, mDrawList); - if(rail.HitTest(this)) - newHoveredObject = rail; - - foreach (var pt in rail.Points) - { - if (pt.HitTest(this)) - newHoveredObject = pt; - } - } - } - - //Hide belt for now. TODO how should this be handled? - //foreach (var belt in unit.BeltUnitRenders) - // belt.Render(this, mDrawList); + areaScene.ForEach(obj => + { + bool isNewHoveredObj = false; + obj.Draw2D(mEditContext, this, mDrawList, ref isNewHoveredObj); + if (isNewHoveredObj) + newHoveredObject = obj; + }); + + if (newHoveredSceneObj is not null) + newHoveredObject = newHoveredSceneObj; } if (mArea.mRailHolder.mRails.Count > 0) @@ -937,7 +924,7 @@ Vector2[] GetPoints() if (pointB.mControl.TryGetValue(out control)) //invert control point - cpInB2D = WorldToScreen(pointB.mTranslate - (control - pointB.mTranslate)); + cpInB2D = WorldToScreen(pointB.mTranslate - (control - pointB.mTranslate)); if (cpOutA2D == posA2D && cpInB2D == posB2D) { @@ -948,7 +935,7 @@ Vector2[] GetPoints() mDrawList.PathBezierCubicCurveTo(cpOutA2D, cpInB2D, posB2D); } - float thickness = newHoveredObject == rail ? 3f : 2.5f; + float thickness = newHoveredObject == rail ? 3f : 2.5f; mDrawList.PathStroke(rail_color, ImDrawFlags.None, thickness); } @@ -1005,7 +992,7 @@ Vector2[] GetPoints() color = ImGui.ColorConvertFloat4ToU32(new(0.84f, .437f, .437f, 1)); } - bool isHovered = HoveredObject == actor; + bool isHovered = mHoveredObject == actor; for (int i = 0; i < 4; i++) { @@ -1037,7 +1024,7 @@ Vector2[] GetPoints() } } - HoveredObject = newHoveredObject; + mHoveredObject = newHoveredObject; } /// @@ -1079,7 +1066,7 @@ public static bool HitTestLineLoopPoint(ReadOnlySpan points, float thic { var p1 = points[i]; var p2 = points[(i + 1) % points.Length]; - if (HitTestPointLine(point, + if (HitTestPointLine(point, p1, p2, thickness)) return true; } @@ -1089,10 +1076,10 @@ public static bool HitTestLineLoopPoint(ReadOnlySpan points, float thic static bool HitTestPointLine(Vector2 p, Vector2 a, Vector2 b, float thickness) { - Vector2 pa = p-a, ba = b-a; - float h = Math.Clamp( Vector2.Dot(pa,ba)/ - Vector2.Dot(ba,ba), 0, 1 ); - return ( pa - ba*h ).Length() < thickness/2; + Vector2 pa = p - a, ba = b - a; + float h = Math.Clamp(Vector2.Dot(pa, ba) / + Vector2.Dot(ba, ba), 0, 1); + return (pa - ba * h).Length() < thickness / 2; } }