diff --git a/Fushigi/actor_pack/ActorPack.cs b/Fushigi/actor_pack/ActorPack.cs index 46ec92ff..374786c0 100644 --- a/Fushigi/actor_pack/ActorPack.cs +++ b/Fushigi/actor_pack/ActorPack.cs @@ -190,6 +190,11 @@ private void LoadComponents(SARC.SARC sarc, ActorParam param) break; case "ModelExpandRef": this.ModelExpandParamRef ??= BymlSerialize.Deserialize(data); + this.ModelExpandParamRef.LoadParentIfExists(filePath => + { + filePath = GetPathGyml(filePath); + return BymlSerialize.Deserialize(sarc.OpenFile(filePath)); + }); break; case "DrainPipeRef": this.DrainPipeRef ??= BymlSerialize.Deserialize(data); @@ -206,10 +211,9 @@ private void LoadComponents(SARC.SARC sarc, ActorParam param) } } - private ShapeParamList GetActorShape(SARC.SARC sarc) + private ShapeParamList? GetActorShape(SARC.SARC sarc) { - - var file = GetPathGyml(GamePhysicsRef.mPath); + var file = GetPathGyml(this.GamePhysicsRef.mPath); var dat = sarc.OpenFile(file); this.ControllerPath = BymlSerialize.Deserialize(dat); diff --git a/Fushigi/actor_pack/components/GamePhysics.cs b/Fushigi/actor_pack/components/GamePhysics.cs index dafb0b75..fea9e567 100644 --- a/Fushigi/actor_pack/components/GamePhysics.cs +++ b/Fushigi/actor_pack/components/GamePhysics.cs @@ -11,6 +11,9 @@ namespace Fushigi.actor_pack.components [Serializable] public class GamePhysics { + [BymlProperty("$parent")] + public string parent { get; set; } + [BymlProperty("ControllerSetPath", DefaultValue = "")] public string mPath { get; set; } } diff --git a/Fushigi/actor_pack/components/ModelExpandParam.cs b/Fushigi/actor_pack/components/ModelExpandParam.cs index d0a3d194..f5be8bae 100644 --- a/Fushigi/actor_pack/components/ModelExpandParam.cs +++ b/Fushigi/actor_pack/components/ModelExpandParam.cs @@ -1,4 +1,5 @@ using Fushigi.Byml.Serializer; +using Fushigi.SARC; using System; using System.Collections.Generic; using System.Linq; @@ -11,6 +12,20 @@ namespace Fushigi.actor_pack.components [Serializable] public class ModelExpandParam { + public void LoadParentIfExists(Func fileLoader) + { + if (ParentRef == null) + return; + + Parent = fileLoader(ParentRef); + Parent.LoadParentIfExists(fileLoader); + } + + [BymlProperty(Key = "$parent")] + public string? ParentRef { get; set; } + + public ModelExpandParam? Parent { get; set; } + public List Settings { get; set; } } diff --git a/Fushigi/course/Course.cs b/Fushigi/course/Course.cs index 63516255..d612e19e 100644 --- a/Fushigi/course/Course.cs +++ b/Fushigi/course/Course.cs @@ -124,7 +124,7 @@ public void Save() foreach (CourseArea area in mAreas) { - refArr.AddNodeToArray(BymlUtil.CreateNode("", $"Work/Stage/StageParam/{area.GetName()}.game__stage__StageParam.gyml")); + refArr.AddNodeToArray(BymlUtil.CreateNode($"Work/Stage/StageParam/{area.GetName()}.game__stage__StageParam.gyml")); } stageParamRoot.AddNode(BymlNodeId.Array, refArr, "RefStages"); diff --git a/Fushigi/course/CourseActor.cs b/Fushigi/course/CourseActor.cs index 69779402..9d55bffd 100644 --- a/Fushigi/course/CourseActor.cs +++ b/Fushigi/course/CourseActor.cs @@ -13,6 +13,11 @@ namespace Fushigi.course { + public enum WonderViewType{ + Normal, + WonderOff, + WonderOnly + } public class CourseActor { public CourseActor(BymlHashTable actorNode) @@ -222,6 +227,8 @@ public BymlHashTable BuildNode(CourseLinkHolder linkHolder) public string mPackName; public string mName; public string mLayer; + public WonderViewType mWonderView = WonderViewType.Normal; + public bool wonderVisible = true; public System.Numerics.Vector3 mStartingTrans; public System.Numerics.Vector3 mTranslation; public System.Numerics.Vector3 mRotation; @@ -260,7 +267,7 @@ public CourseActor this[ulong hash] get { bool exists = TryGetActor(hash, out CourseActor? actor); - Debug.Assert(exists); + //Debug.Assert(exists); return actor!; } } diff --git a/Fushigi/course/CourseRail.cs b/Fushigi/course/CourseRail.cs index 84697f11..936c5fa2 100644 --- a/Fushigi/course/CourseRail.cs +++ b/Fushigi/course/CourseRail.cs @@ -35,7 +35,16 @@ public CourseRail(BymlHashTable node) string pointParam = Path.GetFileNameWithoutExtension(BymlUtil.GetNodeData(node["Gyaml"])).Split(".game")[0]; var railParams = ParamDB.GetRailComponent(pointParam); + var railParent = ParamDB.GetRailComponentParent(railParams); var comp = ParamDB.GetRailComponentParams(railParams); + if (railParent != "null") + { + var parentComp = ParamDB.GetRailComponentParams(railParent); + foreach (var component in parentComp) + { + comp.TryAdd(component.Key, component.Value); + } + } if (!node.ContainsKey("Dynamic")) { @@ -132,6 +141,7 @@ public CourseRailPoint() { this.mHash = RandomUtil.GetRandom(); this.mTranslate = new System.Numerics.Vector3(); + this.mControl = new(this, mTranslate); } @@ -139,7 +149,7 @@ public CourseRailPoint(CourseRailPoint point) { this.mHash = RandomUtil.GetRandom(); this.mTranslate = point.mTranslate; - this.mControl = point.mControl; + this.mControl = new(this, point.mControl.mTranslate); foreach (var param in point.mParameters) this.mParameters.Add(param.Key, param.Value); } @@ -148,6 +158,7 @@ public CourseRailPoint(BymlHashTable node, string pointParam) { mHash = BymlUtil.GetNodeData(node["Hash"]); mTranslate = BymlUtil.GetVector3FromArray(node["Translate"] as BymlArrayNode); + mControl = new(this, mTranslate); IDictionary comp; if (ParamDB.TryGetRailPointComponent(pointParam, out var componentName)) @@ -169,7 +180,8 @@ public CourseRailPoint(BymlHashTable node, string pointParam) if (node.ContainsKey("Control1")) { - mControl = BymlUtil.GetVector3FromArray(node["Control1"] as BymlArrayNode); + mControl.mTranslate = BymlUtil.GetVector3FromArray(node["Control1"] as BymlArrayNode); + mIsCurve = true; } var dynamicNode = node["Dynamic"] as BymlHashTable; @@ -204,20 +216,20 @@ public BymlHashTable BuildNode() tbl.AddNode(BymlNodeId.Hash, dynamicNode, "Dynamic"); - if (mControl != null) + if (mIsCurve) { BymlArrayNode controlNode = new(3); - controlNode.AddNodeToArray(BymlUtil.CreateNode("X", mControl.Value.X)); - controlNode.AddNodeToArray(BymlUtil.CreateNode("Y", mControl.Value.Y)); - controlNode.AddNodeToArray(BymlUtil.CreateNode("Z", mControl.Value.Z)); + controlNode.AddNodeToArray(BymlUtil.CreateNode(mControl.mTranslate.X)); + controlNode.AddNodeToArray(BymlUtil.CreateNode(mControl.mTranslate.Y)); + controlNode.AddNodeToArray(BymlUtil.CreateNode(mControl.mTranslate.Z)); tbl.AddNode(BymlNodeId.Array, controlNode, "Control1"); } BymlArrayNode translateNode = new(3); - translateNode.AddNodeToArray(BymlUtil.CreateNode("X", mTranslate.X)); - translateNode.AddNodeToArray(BymlUtil.CreateNode("Y", mTranslate.Y)); - translateNode.AddNodeToArray(BymlUtil.CreateNode("Z", mTranslate.Z)); + translateNode.AddNodeToArray(BymlUtil.CreateNode(mTranslate.X)); + translateNode.AddNodeToArray(BymlUtil.CreateNode(mTranslate.Y)); + translateNode.AddNodeToArray(BymlUtil.CreateNode(mTranslate.Z)); tbl.AddNode(BymlNodeId.Array, translateNode, "Translate"); @@ -227,10 +239,20 @@ public BymlHashTable BuildNode() public ulong mHash; public Dictionary mParameters = new(); public System.Numerics.Vector3 mTranslate; - public System.Numerics.Vector3? mControl = null; + public CourseRailPointControl mControl; + public bool mIsCurve; + } + public class CourseRailPointControl + { + public CourseRailPointControl(CourseRail.CourseRailPoint point, System.Numerics.Vector3 pos) + { + this.point = point; + this.mTranslate = pos; + } + public CourseRail.CourseRailPoint point; + public System.Numerics.Vector3 mTranslate; } } - public class CourseRailHolder { public CourseRailHolder() @@ -291,6 +313,7 @@ public CourseActorToRailLink(BymlHashTable table) { mSourceActor = BymlUtil.GetNodeData(table["Src"]); mDestRail = BymlUtil.GetNodeData(table["Dst"]); + mDestPoint = BymlUtil.GetNodeData(table["Point"]); mLinkName = BymlUtil.GetNodeData(table["Name"]); } @@ -299,7 +322,7 @@ public BymlHashTable BuildNode() BymlHashTable tbl = new(); tbl.AddNode(BymlNodeId.UInt64, BymlUtil.CreateNode(mDestRail), "Dst"); tbl.AddNode(BymlNodeId.String, BymlUtil.CreateNode(mLinkName), "Name"); - tbl.AddNode(BymlNodeId.UInt64, BymlUtil.CreateNode(mSourceActor), "Point"); + tbl.AddNode(BymlNodeId.UInt64, BymlUtil.CreateNode(mDestPoint), "Point"); tbl.AddNode(BymlNodeId.UInt64, BymlUtil.CreateNode(mSourceActor), "Src"); return tbl; } diff --git a/Fushigi/course/distance_view/DistantViewManager.cs b/Fushigi/course/distance_view/DistantViewManager.cs index 80936af4..2fbba395 100644 --- a/Fushigi/course/distance_view/DistantViewManager.cs +++ b/Fushigi/course/distance_view/DistantViewManager.cs @@ -12,8 +12,7 @@ namespace Fushigi.course.distance_view public class DistantViewManager { private Dictionary LayerMatrices = new Dictionary(); - - private DVLayerParamTable ParamTable = new DVLayerParamTable(); + public DVLayerParamTable ParamTable = new DVLayerParamTable(); private CourseActor DVLocator; diff --git a/Fushigi/env/EnvPalette.cs b/Fushigi/env/EnvPalette.cs index 00c6b1e8..3e4d43c7 100644 --- a/Fushigi/env/EnvPalette.cs +++ b/Fushigi/env/EnvPalette.cs @@ -58,7 +58,7 @@ public void Load(string name) string file_path = FileUtil.FindContentPath(local_path); if (!File.Exists(file_path)) { - Debug.Fail(null); + //Debug.Fail(null); return; } diff --git a/Fushigi/gl/Bfres/BfresRender.cs b/Fushigi/gl/Bfres/BfresRender.cs index ba1c4065..dc5c5d92 100644 --- a/Fushigi/gl/Bfres/BfresRender.cs +++ b/Fushigi/gl/Bfres/BfresRender.cs @@ -92,7 +92,11 @@ public BfresModel(BfresModel bfresModel) internal void Render(GL gl, BfresRender render, Matrix4x4 transform, Camera camera) { foreach (var mesh in Meshes) + { + var m = mesh.LodMeshes[0].BoundingBox; + m.Transform(transform); BoundingBox.Include(mesh.LodMeshes[0].BoundingBox); + } if (!IsVisible || !camera.InFrustum(BoundingBox)) return; diff --git a/Fushigi/gl/Bfres/TileBfresRenderer.cs b/Fushigi/gl/Bfres/TileBfresRenderer.cs index 46c0fa19..96ae9621 100644 --- a/Fushigi/gl/Bfres/TileBfresRenderer.cs +++ b/Fushigi/gl/Bfres/TileBfresRenderer.cs @@ -77,7 +77,7 @@ private static Model CreateModel(GL gl, BgUnitInfo bgUnitInfo) )); } - public void Load(CourseUnitHolder unitHolder, Camera camera) + public void Load(CourseUnitHolder unitHolder) { SolidModel.TileManager.Clear(); SemisolidModel.TileManager.Clear(); @@ -153,9 +153,9 @@ public void Load(CourseUnitHolder unitHolder, Camera camera) public void Render(GL gl, Camera camera) { + NoCollisionModel.Render(gl, camera); SolidModel.Render(gl, camera); SemisolidModel.Render(gl, camera); - NoCollisionModel.Render(gl, camera); BridgeModel.Render(gl, camera); } diff --git a/Fushigi/param/ParamDB.cs b/Fushigi/param/ParamDB.cs index deec670b..1d588ef5 100644 --- a/Fushigi/param/ParamDB.cs +++ b/Fushigi/param/ParamDB.cs @@ -114,6 +114,7 @@ public static void Init() public static List GetActorComponents(string actor) => sActors[actor].Components; public static Dictionary GetComponentParams(string componentName) => sComponents[componentName].Parameters; + public static string GetComponentParent(string componentName) => sComponents[componentName].Parent; public static string GetRailComponent(string railName) => sRailParamList[railName].Components[0]; public static bool TryGetRailPointComponent(string railName, [NotNullWhen(true)] out string? componentName) { @@ -125,6 +126,7 @@ public static bool TryGetRailPointComponent(string railName, [NotNullWhen(true)] return componentName is not null; } + public static string GetRailComponentParent(string componentName) => sRails[componentName].Parent; public static Dictionary GetRailComponentParams(string componentName) => sRails[componentName].Parameters; public static string[] GetActors() => sActors.Keys.ToArray(); @@ -207,7 +209,7 @@ public static void Load(IProgress<(string operationName, float? progress)> progr { var byml = new Byml.Byml(new MemoryStream(File.ReadAllBytes(railComp))); string name = Path.GetFileNameWithoutExtension(railComp).Split(".engine")[0]; - Component component = ReadByml(byml); + Component component = ReadByml(byml, true); sRails.Add(name, component); } @@ -268,7 +270,7 @@ public static void Reload(IProgress<(string operationName, float? progress)> pro Load(progress); } - static Component ReadByml(Byml.Byml byml) + static Component ReadByml(Byml.Byml byml, bool isRailParam = false) { var root = (BymlHashTable)byml.Root; @@ -340,10 +342,12 @@ static Component ReadByml(Byml.Byml byml) /* if the IsInstanceParam value is False, it means that the parameter is not used in a course context * so, if it is False, we ignore it and move on to the next parameter as we will only read what matters + + * No, this causes some rail types to save without default parameters. -Donavin */ bool isInstParam = ((BymlNode)(ht["IsInstanceParam"])).Data; - if (!isInstParam) + if (!isInstParam && !isRailParam) { continue; } diff --git a/Fushigi/ui/CourseAreaEditContext.cs b/Fushigi/ui/CourseAreaEditContext.cs index 18669421..4813592c 100644 --- a/Fushigi/ui/CourseAreaEditContext.cs +++ b/Fushigi/ui/CourseAreaEditContext.cs @@ -68,15 +68,17 @@ public void AddLink(CourseLink link) LogAdding($": {link.mSource} -{link.mLinkName}-> {link.mDest}"); //Checks if the the source actor already has links - if(linkList.Any(x => x.mSource == link.mSource)){ + if (linkList.Any(x => x.mSource == link.mSource)){ //Looks through the source actor's links //Then looks through it's links of the same type (If it has any) //Placing the new link in the right spot + var index = linkList.FindLastIndex(x => x.mSource == link.mSource && + (!linkList.Any(y => x.mLinkName == link.mLinkName) || + x.mLinkName == link.mLinkName)); + CommitAction( - area.mLinkHolder.mLinks.RevertableInsert(link, - linkList.FindLastIndex(x => x.mSource == link.mSource - && ((!linkList.Any(y => y.mLinkName == link.mLinkName)) || x.mLinkName == link.mLinkName))+1, + area.mLinkHolder.mLinks.RevertableInsert(link, index+1, $"{IconUtil.ICON_PLUS_CIRCLE} Add {link.mLinkName} Link") ); return; @@ -215,6 +217,20 @@ public void DeleteWall(CourseUnit unit, Wall wall) $"{IconUtil.ICON_TRASH} Delete Wall")); } + public void AddBeltRail(CourseUnit unit, BGUnitRail rail) + { + LogAdding(); + CommitAction(unit.mBeltRails.RevertableAdd(rail, + $"{IconUtil.ICON_PLUS_CIRCLE} Add Belt")); + } + + public void DeleteBeltRail(CourseUnit unit, BGUnitRail rail) + { + LogDeleting(); + CommitAction(unit.mBeltRails.RevertableRemove(rail, + $"{IconUtil.ICON_TRASH} Delete Belt")); + } + private void LogAdding(ulong hash) => LogAdding($"[{hash}]"); diff --git a/Fushigi/ui/EditContextBase.cs b/Fushigi/ui/EditContextBase.cs index 00945477..4586e1db 100644 --- a/Fushigi/ui/EditContextBase.cs +++ b/Fushigi/ui/EditContextBase.cs @@ -36,7 +36,7 @@ public void Commit(string name) public ICommittable BeginBatchAction() { - mCurrentActionBatch = []; + mCurrentActionBatch ??= []; var batchAction = new BatchAction(this); mNestedBatchActions.Push(batchAction); return batchAction; diff --git a/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs b/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs index f3a8bf5e..a84673af 100644 --- a/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs +++ b/Fushigi/ui/SceneObjects/bgunit/BGUnitRailSceneObj.cs @@ -7,7 +7,7 @@ namespace Fushigi.ui.SceneObjects.bgunit { - internal class BGUnitRailSceneObj(CourseUnit unit, BGUnitRail rail) : ISceneObject, IViewportDrawable, IViewportSelectable + internal class BGUnitRailSceneObj(CourseUnit unit, BGUnitRail rail, bool isBelt) : ISceneObject, IViewportDrawable, IViewportSelectable { public IReadOnlyDictionary ChildPoints; public List GetSelected(CourseAreaEditContext ctx) => rail.Points.Where(ctx.IsSelected).ToList(); @@ -71,7 +71,6 @@ public void InsertPoint(CourseAreaEditContext ctx, BGUnitRail.RailPoint point, i ctx.CommitAction(revertible); ctx.Select(point); - CourseUnit.GenerateTileSubUnits(); } public void AddPoint(CourseAreaEditContext ctx, BGUnitRail.RailPoint point) @@ -81,7 +80,6 @@ public void AddPoint(CourseAreaEditContext ctx, BGUnitRail.RailPoint point) ctx.CommitAction(revertible); ctx.Select(point); - CourseUnit.GenerateTileSubUnits(); } public void RemoveSelected(CourseAreaEditContext ctx, LevelViewport viewport) @@ -99,8 +97,6 @@ public void RemoveSelected(CourseAreaEditContext ctx, LevelViewport viewport) } batchAction.Commit($"{IconUtil.ICON_TRASH} Delete Rail Points"); - - CourseUnit.GenerateTileSubUnits(); } public void OnKeyDown(CourseAreaEditContext ctx, LevelViewport viewport) @@ -114,7 +110,7 @@ public void OnKeyDown(CourseAreaEditContext ctx, LevelViewport viewport) private bool HitTest(LevelViewport viewport) { - return LevelViewport.HitTestLineLoopPoint(GetPoints(viewport), 10f, + return MathUtil.HitTestLineLoopPoint(GetPoints(viewport), 10f, ImGui.GetMousePos()); } @@ -300,7 +296,7 @@ void IViewportDrawable.Draw2D(CourseAreaEditContext ctx, LevelViewport viewport, addPointPos = EvaluateAddPointPos(ctx, viewport); - if ((addPointPos.HasValue && ctx.IsSelected(rail)) || HitTest(viewport)) + if ((addPointPos.HasValue && ctx.IsSelected(rail)) || (!isBelt && HitTest(viewport))) isNewHoveredObj = true; bool isSelected = IsSelected(ctx); @@ -318,31 +314,49 @@ void IViewportDrawable.Draw2D(CourseAreaEditContext ctx, LevelViewport viewport, var lineThickness = viewport.IsHovered(this) ? 3.5f : 2.5f; + + for (int i = 0; i < rail.Points.Count; i++) { - Vector3 point = rail.Points[i].Position; - var pos2D = viewport.WorldToScreen(new(point.X, point.Y, point.Z)); + Vector3 pos = rail.Points[i].Position; - //Next pos 2D - Vector2 nextPos2D = Vector2.Zero; + Vector3 nextPos; if (i < rail.Points.Count - 1) //is not last point { - nextPos2D = viewport.WorldToScreen( - rail.Points[i + 1].Position); + nextPos = rail.Points[i + 1].Position; } else if (rail.IsClosed) //last point to first if closed { - nextPos2D = viewport.WorldToScreen( - rail.Points[0].Position); + nextPos = rail.Points[0].Position; } else //last point but not closed, draw no line continue; + var pos2D = viewport.WorldToScreen(pos); + var nextPos2D = viewport.WorldToScreen(nextPos); + uint line_color = IsValidAngle(pos2D, nextPos2D) ? Color_Default : Color_SlopeError; if (isSelected && line_color != Color_SlopeError) line_color = Color_SelectionEdit; - dl.AddLine(pos2D, nextPos2D, line_color, lineThickness); + if (isBelt) + { + var bottomPos2D = viewport.WorldToScreen(pos - Vector3.UnitY * 0.5f); + var bottomNextPos2D = viewport.WorldToScreen(nextPos - Vector3.UnitY * 0.5f); + + dl.AddQuadFilled(pos2D, nextPos2D, bottomNextPos2D, bottomPos2D, line_color & 0x00FFFFFF | 0x55000000); + dl.AddQuad(pos2D, nextPos2D, bottomNextPos2D, bottomPos2D, line_color, lineThickness - 1); + + if (MathUtil.HitTestConvexQuad(pos2D, nextPos2D, bottomNextPos2D, bottomPos2D, + ImGui.GetMousePos())) + { + isNewHoveredObj = true; + } + } + else + { + dl.AddLine(pos2D, nextPos2D, line_color, lineThickness); + } if (isSelected) { @@ -379,7 +393,9 @@ void IViewportDrawable.Draw2D(CourseAreaEditContext ctx, LevelViewport viewport, var pointB = viewport.WorldToScreen(rail.Points.GetWrapped(index).Position); var pointC = pos2D; - dl.AddTriangleFilled(pointA, pointB, pointC, 0x99FFFFFF); + if(!isBelt) + dl.AddTriangleFilled(pointA, pointB, pointC, 0x99FFFFFF); + if(rail.IsClosed || index > 0) dl.AddLine(pointA, pointC, 0xFFFFFFFF, 2.5f); if(rail.IsClosed || index < rail.Points.Count) diff --git a/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs b/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs index f3323b73..f22e6b5c 100644 --- a/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs +++ b/Fushigi/ui/SceneObjects/bgunit/BGUnitSceneObj.cs @@ -8,9 +8,15 @@ public void Update(ISceneUpdateContext ctx, bool isSelected) { unit.GenerateTileSubUnits(); - void CreateOrUpdateRail(BGUnitRail rail) + void CreateOrUpdateRail(BGUnitRail rail, bool isBelt = false) { - ctx.UpdateOrCreateObjFor(rail, () => new BGUnitRailSceneObj(unit, rail)); + ctx.UpdateOrCreateObjFor(rail, () => new BGUnitRailSceneObj(unit, rail, isBelt)); + } + + if (unit.mModelType is CourseUnit.ModelType.SemiSolid or CourseUnit.ModelType.Bridge) + { + foreach (var rail in unit.mBeltRails) + CreateOrUpdateRail(rail, true); } foreach (var wall in unit.Walls) @@ -21,10 +27,6 @@ void CreateOrUpdateRail(BGUnitRail rail) 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/widgets/CourseScene.cs b/Fushigi/ui/widgets/CourseScene.cs index a4b580ca..59c22c10 100644 --- a/Fushigi/ui/widgets/CourseScene.cs +++ b/Fushigi/ui/widgets/CourseScene.cs @@ -16,7 +16,11 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Fushigi.rstb; +using Fushigi.course.distance_view; using Fushigi.ui.helpers; +using Fasterflect; +using System.Text.RegularExpressions; +using System.Collections; namespace Fushigi.ui.widgets { @@ -45,7 +49,12 @@ class CourseScene string mActorSearchText = ""; - CourseLink? mSelectedGlobalLink = null; + //CourseLink? mSelectedGlobalLink = null; + + string[] viewMode = [ + "View All Actors", + "View Normal Actors", + "View Wonder Actors"]; string[] linkTypes = [ "BasicSignal", @@ -76,6 +85,69 @@ class CourseScene "EventGuest_11", ]; + public static string[] layerTypes = [ + "DvScreen", + "DvNear2", + "DvNear1", + "DecoAreaFront", + "PlayArea", + "DecoArea", + "DvMiddle1", + "DvMiddle2", + "DvFar1", + "DvFar2", + "DvFar3", + "DvFar4", + "DvFar5", + "DvFar6", + "DvFar7", + "DvFar8", + "DvFar9", + "DvFar10" + ]; + public static Regex NumberRegex = new(@"\d+"); + + // This code sorts the layer order on the layer panel. + // You can look through it before deciding if it's optimized enough to include. + // Just uncomment all of this if it is. + // public static List layerSortTypes = [ + // "DvScreen", + // "DvNear", + // "DecoAreaFront", + // "PlayArea", + // "DvPlayArea", + // "DecoArea", + // "DvMiddle", + // "DvFar" + // ]; + + // public class LayerSorter : IComparer + // { + // public int Compare(string x, string y) + // { + // var idX = layerSortTypes.IndexOf(NumberRegex.Replace(x, "")); + // var idY = layerSortTypes.IndexOf(NumberRegex.Replace(y, "")); + // if(idX != -1) + // { + // int result = idY == -1 ? 1:idX.CompareTo(idY); + // if (result != 0) + // { + // return result; + // } + // else + // { + // result = x.Length.CompareTo(x.Length); + // return result != 0 ? result:x.CompareTo(y); + // } + // } + // else + // { + // return idY != -1 ? -1:0; + // } + // } + // } + // readonly LayerSorter layerSort = new(); + public static async Task Create(Course course, GLTaskScheduler glScheduler, IPopupModalHost popupModalHost, @@ -189,6 +261,7 @@ public void DrawUI(GL gl, double deltaSeconds) RailsPanel(); GlobalLinksPanel(); + RailLinksPanel(); LocalLinksPanel(); @@ -241,8 +314,6 @@ public void DrawUI(GL gl, double deltaSeconds) ImGui.SameLine(); - ImGui.SameLine(); - string current_palette = area.mInitEnvPalette == null ? "" : area.mInitEnvPalette.Name; void SelectPalette(string name, string palette) @@ -261,8 +332,8 @@ void SelectPalette(string name, string palette) ImGui.SetItemDefaultFocus(); } - ImGui.PushItemWidth(30); - if (ImGui.BeginCombo($"##EnvPalette", $"{IconUtil.ICON_PALETTE}", ImGuiComboFlags.NoArrowButton)) + var flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.WidthFitPreview; + if (ImGui.BeginCombo($"##EnvPalette", $"{IconUtil.ICON_PALETTE}", flags)) { SelectPalette($"Default Palette", area.mAreaParams.EnvPaletteSetting.InitPaletteBaseName); @@ -283,7 +354,6 @@ void SelectPalette(string name, string palette) } ImGui.EndCombo(); } - ImGui.PopItemWidth(); ImGui.SameLine(); @@ -293,6 +363,18 @@ void SelectPalette(string name, string palette) UserSettings.SetGameShaders(useGameShaders); } + ImGui.SameLine(); + + if (ImGui.BeginCombo("Wonder View", viewMode[(int)activeViewport.WonderViewMode], ImGuiComboFlags.WidthFitPreview)) + { + for (int n = 0; n < 3; n++) + { + if (ImGui.Selectable(viewMode[n])) + viewport.WonderViewMode = (WonderViewType)n; + } + ImGui.EndCombo(); + } + ImGui.PopStyleColor(1); ImGui.EndChild(); @@ -300,9 +382,12 @@ void SelectPalette(string name, string palette) if (ImGui.IsWindowFocused()) { - selectedArea = area; + if (selectedArea != area) + { + selectedArea = area; + mHasFilledLayers = false; + } activeViewport = viewport; - mHasFilledLayers = false; } var topLeft = ImGui.GetCursorScreenPos(); @@ -439,6 +524,13 @@ private void ActorsPanel() ctx, "Delete actors"); } + ImGui.SameLine(); + + if (ImGui.Button("Add Layer")) + { + _ = AddLayerWithLayerWindow(); + } + ImGui.AlignTextToFramePadding(); ImGui.Text(IconUtil.ICON_SEARCH.ToString()); ImGui.SameLine(); @@ -494,8 +586,6 @@ private void LocalLinksPanel() { ImGui.Begin("Local Links"); - ImGui.Checkbox("Wonder View", ref activeViewport.IsWonderView); - ImGui.Separator(); AreaLocalLinksView(selectedArea); @@ -507,81 +597,83 @@ private void RailLinksPanel() { ImGui.Begin("Actor to Rail Links"); - ImGui.Columns(4); - ImGui.Text("Actor-Hash"); - ImGui.NextColumn(); - ImGui.Text("Rail"); - ImGui.NextColumn(); - ImGui.Text("Point"); - ImGui.NextColumn(); - ImGui.NextColumn(); - var ctx = areaScenes[selectedArea].EditContext; var rails = selectedArea.mRailHolder.mRails; var actors = selectedArea.mActorHolder.mActors; var railLinks = selectedArea.mRailLinksHolder.mLinks; - for (int i = 0; i < railLinks.Count; i++) + if (ImGui.BeginTable("actorRails", 4, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - ImGui.PushID(i); - CourseActorToRailLink link = railLinks[i]; + ImGui.TableSetupColumn("Actor-Hash"); + ImGui.TableSetupColumn("Rail"); + ImGui.TableSetupColumn("Point"); + ImGui.TableHeadersRow(); - string hash = link.mSourceActor.ToString(); - int actorIndex = actors.FindIndex(x => x.mHash == link.mSourceActor); - if (ImGui.InputText("##actor", ref hash, 100) && - ulong.TryParse(hash, out ulong hashInt)) - link.mSourceActor = hashInt; - if(actorIndex == -1) + for (int i = 0; i < railLinks.Count; i++) { - ImGui.SameLine(); - ImGui.TextDisabled("Invalid"); - } + ImGui.PushID(i); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + CourseActorToRailLink link = railLinks[i]; - ImGui.NextColumn(); - int railIndex = rails.FindIndex(x => x.mHash == link.mDestRail); - if (ImGui.BeginCombo("##rail", railIndex >= 0 ? ("rail " + railIndex) : "None")) - { - for (int iRail = 0; iRail < rails.Count; iRail++) + string hash = link.mSourceActor.ToString(); + int actorIndex = actors.FindIndex(x => x.mHash == link.mSourceActor); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputText("##actor", ref hash, 100) && + ulong.TryParse(hash, out ulong hashInt)) + link.mSourceActor = hashInt; + if(actorIndex == -1) { - if (ImGui.Selectable("Rail " + iRail, railIndex == iRail)) - link.mDestRail = rails[iRail].mHash; + ImGui.SameLine(); + ImGui.TextDisabled("Invalid"); } - ImGui.EndCombo(); - } - if (railIndex == -1) - { - ImGui.SameLine(); - ImGui.TextDisabled("Invalid"); - } - ImGui.NextColumn(); - if (railIndex >= 0) - { - int pointIndex = rails[railIndex].mPoints.FindIndex(x => x.mHash == link.mDestPoint); + ImGui.TableNextColumn(); + int railIndex = rails.FindIndex(x => x.mHash == link.mDestRail); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.BeginCombo("##rail", railIndex >= 0 ? ("rail " + railIndex) : "None")) + { + for (int iRail = 0; iRail < rails.Count; iRail++) + { + if (ImGui.Selectable("Rail " + iRail, railIndex == iRail)) + link.mDestRail = rails[iRail].mHash; + } + ImGui.EndCombo(); + } + if (railIndex == -1) + { + ImGui.SameLine(); + ImGui.TextDisabled("Invalid"); + } + ImGui.TableNextColumn(); + if (railIndex >= 0 && rails[railIndex].mPoints.Count > 0) + { + int pointIndex = rails[railIndex].mPoints.FindIndex(x => x.mHash == link.mDestPoint); - if (pointIndex == -1) - pointIndex = 0; + if (pointIndex == -1) + pointIndex = 0; - if (ImGui.InputInt("##railpoint", ref pointIndex)) - pointIndex = Math.Clamp(pointIndex, 0, rails[railIndex].mPoints.Count - 1); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.InputInt("##railpoint", ref pointIndex)) + pointIndex = Math.Clamp(pointIndex, 0, rails[railIndex].mPoints.Count - 1); - link.mDestPoint = rails[railIndex].mPoints[pointIndex].mHash; - } + link.mDestPoint = rails[railIndex].mPoints[pointIndex].mHash; + } - ImGui.NextColumn(); - if (ImGui.Button("Delete", new Vector2(ImGui.GetContentRegionAvail().X * 0.65f, 0))) - { - ctx.DeleteRailLink(link); - i--; - } + ImGui.TableNextColumn(); + if (ImGui.Button("Delete", new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ScrollbarSize, 0))) + { + ctx.DeleteRailLink(link); + i--; + } + ImGui.PopID(); + } - ImGui.NextColumn(); - ImGui.PopID(); + ImGui.EndTable(); } float width = ImGui.GetItemRectMax().X - ImGui.GetCursorScreenPos().X; - ImGui.Columns(1); ImGui.Dummy(new Vector2(0, ImGui.GetFrameHeight() * 0.5f)); if (ImGui.Button("Add", new Vector2(width, ImGui.GetFrameHeight() * 1.5f))) @@ -610,74 +702,75 @@ private void SelectionParameterPanel() string actorName = mSelectedActor.mPackName; string name = mSelectedActor.mName; - ImGui.Columns(2); - ImGui.AlignTextToFramePadding(); - string packName = mSelectedActor.mPackName; - - ImGui.Text("Actor Name"); - ImGui.NextColumn(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - if (ImGui.InputText("##Actor Name", ref packName, 256, ImGuiInputTextFlags.EnterReturnsTrue)) + if (ImGui.BeginTable("Props", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - if (ParamDB.GetActors().Contains(packName)) - { - mSelectedActor.mPackName = packName; - mSelectedActor.InitializeDefaultDynamicParams(); - } - } - ImGui.PopItemWidth(); - ImGui.NextColumn(); - - ImGui.Text("Actor Hash"); - ImGui.NextColumn(); - string hash = mSelectedActor.mHash.ToString(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - ImGui.InputText("##Actor Hash", ref hash, 256, ImGuiInputTextFlags.ReadOnly); - ImGui.PopItemWidth(); - ImGui.NextColumn(); - - ImGui.Separator(); - - ImGui.Columns(2); - - ImGui.AlignTextToFramePadding(); - ImGui.Text("Name"); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.AlignTextToFramePadding(); + string packName = mSelectedActor.mPackName; - ImGui.NextColumn(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - if (ImGui.InputText($"##{name}", ref name, 512, ImGuiInputTextFlags.EnterReturnsTrue)) - { - mSelectedActor.mName = name; - } + ImGui.Text("Actor Name"); + ImGui.TableNextColumn(); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + if (ImGui.InputText("##Actor Name", ref packName, 256, ImGuiInputTextFlags.EnterReturnsTrue)) + { + if (ParamDB.GetActors().Contains(packName)) + { + mSelectedActor.mPackName = packName; + mSelectedActor.InitializeDefaultDynamicParams(); + } + } + ImGui.PopItemWidth(); - ImGui.PopItemWidth(); - ImGui.NextColumn(); + ImGui.TableNextColumn(); + ImGui.Text("Actor Hash"); + ImGui.TableNextColumn(); + string hash = mSelectedActor.mHash.ToString(); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + ImGui.InputText("##Actor Hash", ref hash, 256, ImGuiInputTextFlags.ReadOnly); + ImGui.PopItemWidth(); - ImGui.Text("Layer"); + ImGui.TableNextColumn(); + ImGui.Separator(); + ImGui.TableNextColumn(); + ImGui.Separator(); - ImGui.NextColumn(); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.Text("Name"); - if (ImGui.BeginCombo("##Dropdown", mSelectedActor.mLayer)) - { - foreach (var layer in mLayersVisibility.Keys.ToArray().ToImmutableList()) - { - if (ImGui.Selectable(layer)) + ImGui.TableNextColumn(); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + if (ImGui.InputText($"##{name}", ref name, 512, ImGuiInputTextFlags.EnterReturnsTrue)) { - //item is selected - Console.WriteLine("Changing " + mSelectedActor.mName + "'s layer from " + mSelectedActor.mLayer + " to " + layer + "."); - mSelectedActor.mLayer = layer; + mSelectedActor.mName = name; } - } - - ImGui.EndCombo(); - } - + ImGui.PopItemWidth(); + + ImGui.TableNextColumn(); + ImGui.Text("Layer"); + ImGui.TableNextColumn(); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + if (ImGui.BeginCombo("##Dropdown", mSelectedActor.mLayer)) + { + foreach (var layer in mLayersVisibility.Keys.ToArray().ToImmutableList()) + { + if (ImGui.Selectable(layer)) + { + //item is selected + Console.WriteLine("Changing " + mSelectedActor.mName + "'s layer from " + mSelectedActor.mLayer + " to " + layer + "."); + mSelectedActor.mLayer = layer; + } + } - ImGui.PopItemWidth(); + ImGui.EndCombo(); + } + ImGui.PopItemWidth(); - ImGui.Columns(1); + ImGui.EndTable(); + } PlacementNode(mSelectedActor); @@ -702,19 +795,23 @@ private void SelectionParameterPanel() if (ImGui.Selectable(linkType)) { + KeyboardModifier modifier; ImGui.SetWindowFocus(selectedArea.GetName()); Task.Run(async () => { - var pickedDest = await PickLinkDestInViewportFor(mSelectedActor); - if (pickedDest is null) - return; - - var link = new CourseLink(linkType) + do { - mSource = mSelectedActor.mHash, - mDest = pickedDest.mHash - }; - editContext.AddLink(link); + (var pickedDest, modifier) = await PickLinkDestInViewportFor(mSelectedActor); + if (pickedDest is null) + return; + + var link = new CourseLink(linkType) + { + mSource = mSelectedActor.mHash, + mDest = pickedDest.mHash + }; + editContext.AddLink(link); + } while ((modifier & KeyboardModifier.Shift) > 0); }); } } @@ -728,101 +825,103 @@ private void SelectionParameterPanel() { ImGui.Text(linkName); - ImGui.Columns(3); - - for (int i = 0; i < hashArray.Count; i++) + if (ImGui.BeginTable("##Links", 3, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - ImGui.PushID($"{hashArray[i].ToString()}_{i}"); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); - ImGui.Text("Destination"); - ImGui.NextColumn(); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + for (int i = 0; i < hashArray.Count; i++) + { + ImGui.PushID($"{hashArray[i].ToString()}_{i}"); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + ImGui.GetStyle().FramePadding.X); + ImGui.Text("Destination"); + ImGui.TableNextColumn(); - CourseActor? destActor = selectedArea.mActorHolder[hashArray[i]]; + CourseActor? destActor = selectedArea.mActorHolder[hashArray[i]]; - if (destActor != null) - { - if (ImGui.Button(destActor.mName, new Vector2(ImGui.GetContentRegionAvail().X, 0))) + if (destActor != null) { - mSelectedActor = destActor; - activeViewport.SelectedActor(destActor); - activeViewport.Camera.Target.X = destActor.mTranslation.X; - activeViewport.Camera.Target.Y = destActor.mTranslation.Y; + 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; + } } - } - else - { - if (ImGui.Button("Actor Not Found")) + else { + if (ImGui.Button("Actor Not Found")) + { + } } - } - - ImGui.NextColumn(); - - var cursorSP = ImGui.GetCursorScreenPos(); - var padding = ImGui.GetStyle().FramePadding; - uint WithAlphaFactor(uint color, float factor) => color & 0xFFFFFF | ((uint)((color >> 24) * factor) << 24); + ImGui.TableNextColumn(); - float deleteButtonWidth = ImGui.GetFrameHeight() * 1.6f; + var cursorSP = ImGui.GetCursorScreenPos(); + var padding = ImGui.GetStyle().FramePadding; - float columnWidth = ImGui.GetContentRegionAvail().X; + uint WithAlphaFactor(uint color, float factor) => color & 0xFFFFFF | ((uint)((color >> 24) * factor) << 24); - ImGui.PushClipRect(cursorSP, - cursorSP + new Vector2(columnWidth - deleteButtonWidth, ImGui.GetFrameHeight()), true); + float deleteButtonWidth = ImGui.GetFrameHeight() * 1.6f; - var cursor = ImGui.GetCursorPos(); - ImGui.BeginDisabled(); - if (ImGui.Button("Replace")) - { + float columnWidth = ImGui.GetContentRegionAvail().X; - } - ImGui.EndDisabled(); - cursor.X += ImGui.GetItemRectSize().X + 2; + ImGui.PushClipRect(cursorSP, + cursorSP + new Vector2(columnWidth - deleteButtonWidth, ImGui.GetFrameHeight()), true); - ImGui.SetCursorPos(cursor); - if (ImGui.Button(IconUtil.ICON_EYE_DROPPER)) - { - ImGui.SetWindowFocus(selectedArea.GetName()); - Task.Run(async () => + var cursor = ImGui.GetCursorPos(); + ImGui.BeginDisabled(); + if (ImGui.Button("Replace")) { - var pickedDest = await PickLinkDestInViewportFor(mSelectedActor); - if (pickedDest is null) - return; - //TODO rework GetDestHashesFromSrc to return the actual link objects or do it in another way - var link = selectedArea.mLinkHolder.mLinks.Find( - x => x.mSource == mSelectedActor.mHash && - x.mLinkName == linkName && - x.mDest == destActor!.mHash); - - link.mDest = pickedDest.mHash; - }); - } + } + ImGui.EndDisabled(); + cursor.X += ImGui.GetItemRectSize().X + 2; - ImGui.PopClipRect(); - cursorSP.X += columnWidth - deleteButtonWidth; - ImGui.SetCursorScreenPos(cursorSP); + ImGui.SetCursorPos(cursor); + if (ImGui.Button(IconUtil.ICON_EYE_DROPPER)) + { + ImGui.SetWindowFocus(selectedArea.GetName()); + Task.Run(async () => + { + var (pickedDest, _) = await PickLinkDestInViewportFor(mSelectedActor); + if (pickedDest is null) + return; + + //TODO rework GetDestHashesFromSrc to return the actual link objects or do it in another way + var link = selectedArea.mLinkHolder.mLinks.Find( + x => x.mSource == mSelectedActor.mHash && + x.mLinkName == linkName && + x.mDest == destActor!.mHash); + + link.mDest = pickedDest.mHash; + }); + } - 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), - deleteIcon); + ImGui.PopClipRect(); + cursorSP.X += columnWidth - deleteButtonWidth; + ImGui.SetCursorScreenPos(cursorSP); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Delete Link"); + 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), + deleteIcon); - if (clicked) - editContext.DeleteLink(linkName, mSelectedActor.mHash, hashArray[i]); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Delete Link"); - ImGui.NextColumn(); + if (clicked) + editContext.DeleteLink(linkName, mSelectedActor.mHash, hashArray[i]); - ImGui.PopID(); + ImGui.PopID(); + ImGui.TableNextColumn(); + } + ImGui.EndTable(); } ImGui.Separator(); - } #endregion @@ -856,18 +955,107 @@ private void SelectionParameterPanel() if (ImGui.CollapsingHeader("Properties", ImGuiTreeNodeFlags.DefaultOpen)) { - ImGui.Columns(2); - ImGui.Text("Model Type"); ImGui.NextColumn(); + if (ImGui.BeginTable("Props", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Model Type"); ImGui.TableNextColumn(); + + ImGui.Combo("##mModelType", ref Unsafe.As(ref mSelectedUnit.mModelType), + CourseUnit.ModelTypeNames, CourseUnit.ModelTypeNames.Length); + + ImGui.TableNextColumn(); + + ImGui.Text("Skin Division"); ImGui.TableNextColumn(); + ImGui.Combo("##SkinDivision", ref Unsafe.As(ref mSelectedUnit.mSkinDivision), + CourseUnit.SkinDivisionNames, CourseUnit.SkinDivisionNames.Length); + + ImGui.EndTable(); + } + } + + if(mSelectedUnit.mModelType is CourseUnit.ModelType.SemiSolid) + { + ImGui.Spacing(); + ImGui.Separator(); + ImGui.Spacing(); + + if(ImGui.Button("Remove all Belts")) + { + var batchAction = editContext.BeginBatchAction(); + + for (int i = mSelectedUnit.mBeltRails.Count - 1; i >= 0; i--) + editContext.DeleteBeltRail(mSelectedUnit, mSelectedUnit.mBeltRails[i]); + + batchAction.Commit("Remove all Belts from TileUnit"); + } + + ImGui.SameLine(); + + if (ImGui.Button("Generate Belts")) + { + var batchAction = editContext.BeginBatchAction(); + + void ProcessRail(BGUnitRail rail) + { + if (rail.Points.Count <= 1) + return; + + BGUnitRail? firstBeltRail = null; + BGUnitRail? currentBeltRail = null; + + var lastPoint = new Vector3(float.NaN); + + for (int i = 0; i < rail.Points.Count; i++) + { + var point0 = rail.Points[i].Position; + var point1 = rail.Points.GetWrapped(i + 1).Position; + + if (point0.X >= point1.X) + continue; - ImGui.Combo("##mModelType", ref Unsafe.As(ref mSelectedUnit.mModelType), - CourseUnit.ModelTypeNames, CourseUnit.ModelTypeNames.Length); - ImGui.NextColumn(); + if (point0 != lastPoint) + { + if (currentBeltRail is not null) + editContext.AddBeltRail(mSelectedUnit, currentBeltRail); + + currentBeltRail = new BGUnitRail(mSelectedUnit); + currentBeltRail.Points.Add(new BGUnitRail.RailPoint(currentBeltRail, point0)); + firstBeltRail ??= currentBeltRail; + } + + currentBeltRail!.Points.Add(new BGUnitRail.RailPoint(currentBeltRail, point1)); + lastPoint = point1; + } + + var lastBeltRail = currentBeltRail; + + if(firstBeltRail is not null && lastBeltRail is not null && + firstBeltRail != lastBeltRail && + lastBeltRail.Points[^1].Position == firstBeltRail.Points[0].Position) + { + //connect first and last rail - ImGui.Text("Skin Division"); ImGui.NextColumn(); - ImGui.Combo("##SkinDivision", ref Unsafe.As(ref mSelectedUnit.mSkinDivision), - CourseUnit.SkinDivisionNames, CourseUnit.SkinDivisionNames.Length); + for (int i = 0; i < lastBeltRail.Points.Count-1; i++) + { + var position = lastBeltRail.Points[i].Position; + firstBeltRail.Points.Insert(i, new BGUnitRail.RailPoint(firstBeltRail, position)); + } + } + else if (lastBeltRail is not null) + editContext.AddBeltRail(mSelectedUnit, lastBeltRail); + } + + foreach (var wall in mSelectedUnit.Walls) + { + ProcessRail(wall.ExternalRail); + + foreach (var internalRail in wall.InternalRails) + ProcessRail(internalRail); + } - ImGui.Columns(1); + batchAction.Commit("Add Belts"); + } } } else if (editContext.IsSingleObjectSelected(out BGUnitRail? mSelectedUnitRail)) @@ -879,30 +1067,32 @@ private void SelectionParameterPanel() if (ImGui.CollapsingHeader("Properties", ImGuiTreeNodeFlags.DefaultOpen)) { - ImGui.Columns(2); - ImGui.Text("IsClosed"); ImGui.NextColumn(); - if (ImGui.Checkbox("##IsClosed", ref mSelectedUnitRail.IsClosed)) - mSelectedUnitRail.mCourseUnit.GenerateTileSubUnits(); - - ImGui.NextColumn(); - - //Depth editing for bg unit. All points share the same depth, so batch edit the Z point - float depth = mSelectedUnitRail.Points.Count == 0 ? 0 : mSelectedUnitRail.Points[0].Position.Z; - - ImGui.Text("Z Depth"); ImGui.NextColumn(); - if (ImGui.DragFloat("##Depth", ref depth, 0.1f)) + if (ImGui.BeginTable("Props", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - //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.mCourseUnit.GenerateTileSubUnits(); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("IsClosed"); ImGui.TableNextColumn(); + if (ImGui.Checkbox("##IsClosed", ref mSelectedUnitRail.IsClosed)) + mSelectedUnitRail.mCourseUnit.GenerateTileSubUnits(); + + ImGui.TableNextColumn(); + //Depth editing for bg unit. All points share the same depth, so batch edit the Z point + float depth = mSelectedUnitRail.Points.Count == 0 ? 0 : mSelectedUnitRail.Points[0].Position.Z; + + ImGui.Text("Z Depth"); ImGui.TableNextColumn(); + if (ImGui.DragFloat("##Depth", ref depth, 0.1f)) + { + //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.mCourseUnit.GenerateTileSubUnits(); + } + + ImGui.EndTable(); } - ImGui.NextColumn(); - - ImGui.Columns(1); } } - else if (mSelectedGlobalLink != null) + else if (editContext.IsSingleObjectSelected(out CourseLink? mSelectedGlobalLink)) { ImGui.AlignTextToFramePadding(); ImGui.Text($"Selected Global Link"); @@ -919,36 +1109,38 @@ private void SelectionParameterPanel() if (ImGui.CollapsingHeader("Properties", ImGuiTreeNodeFlags.DefaultOpen)) { - 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.BeginTable("Props", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - mSelectedGlobalLink.mSource = Convert.ToUInt64(srcHash); - } - - ImGui.NextColumn(); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Source Hash"); ImGui.TableNextColumn(); + string srcHash = mSelectedGlobalLink.mSource.ToString(); + if (ImGui.InputText("##Source Hash", ref srcHash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) + { + mSelectedGlobalLink.mSource = Convert.ToUInt64(srcHash); + } - ImGui.Text("Destination Hash"); ImGui.NextColumn(); - string destHash = mSelectedGlobalLink.mDest.ToString(); - if (ImGui.InputText("##Dest Hash", ref destHash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) - { - mSelectedGlobalLink.mDest = Convert.ToUInt64(destHash); - } + ImGui.TableNextColumn(); + ImGui.Text("Destination Hash"); ImGui.TableNextColumn(); + string destHash = mSelectedGlobalLink.mDest.ToString(); + if (ImGui.InputText("##Dest Hash", ref destHash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) + { + mSelectedGlobalLink.mDest = Convert.ToUInt64(destHash); + } - ImGui.NextColumn(); + ImGui.TableNextColumn(); + ImGui.Text("Link Type"); ImGui.TableNextColumn(); - ImGui.Text("Link Type"); ImGui.NextColumn(); + List types = linkTypes.ToList(); + int idx = types.IndexOf(mSelectedGlobalLink.mLinkName); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (ImGui.Combo("##Link Type", ref idx, linkTypes, linkTypes.Length)) + { + mSelectedGlobalLink.mLinkName = linkTypes[idx]; + } - List types = linkTypes.ToList(); - int idx = types.IndexOf(mSelectedGlobalLink.mLinkName); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (ImGui.Combo("##Link Type", ref idx, linkTypes, linkTypes.Length)) - { - mSelectedGlobalLink.mLinkName = linkTypes[idx]; + ImGui.EndTable(); } - - ImGui.Columns(1); } } else if (editContext.IsSingleObjectSelected(out CourseRail? mSelectedRail)) @@ -960,127 +1152,158 @@ private void SelectionParameterPanel() if (ImGui.CollapsingHeader("Properties", ImGuiTreeNodeFlags.DefaultOpen)) { - ImGui.Columns(2); - ImGui.Text("Hash"); ImGui.NextColumn(); - string hash = mSelectedRail.mHash.ToString(); - if (ImGui.InputText("##Hash", ref hash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) + if (ImGui.BeginTable("DynamProps", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - mSelectedRail.mHash = Convert.ToUInt64(hash); - } + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Hash"); ImGui.TableNextColumn(); + string hash = mSelectedRail.mHash.ToString(); + if (ImGui.InputText("##Hash", ref hash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) + { + mSelectedRail.mHash = Convert.ToUInt64(hash); + } - ImGui.NextColumn(); - ImGui.Text("IsClosed"); - ImGui.NextColumn(); - ImGui.Checkbox("##IsClosed", ref mSelectedRail.mIsClosed); + ImGui.TableNextColumn(); + ImGui.Text("IsClosed"); + ImGui.TableNextColumn(); + ImGui.Checkbox("##IsClosed", ref mSelectedRail.mIsClosed); - ImGui.Columns(1); + ImGui.EndTable(); + } } if (ImGui.CollapsingHeader("Dynamic Properties", ImGuiTreeNodeFlags.DefaultOpen)) { - ImGui.Columns(2); - - foreach (KeyValuePair param in mSelectedRail.mParameters) + if (ImGui.BeginTable("DynamProps", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - string type = param.Value.GetType().ToString(); - ImGui.Text(param.Key); - ImGui.NextColumn(); - - switch (type) + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + foreach (KeyValuePair param in mSelectedRail.mParameters) { - case "System.Int32": - int int_val = (int)param.Value; - if (ImGui.InputInt($"##{param.Key}", ref int_val)) - { - mSelectedRail.mParameters[param.Key] = int_val; - } - break; - case "System.Boolean": - bool bool_val = (bool)param.Value; - if (ImGui.Checkbox($"##{param.Key}", ref bool_val)) - { - mSelectedRail.mParameters[param.Key] = bool_val; - } - break; - } + string type = param.Value.GetType().ToString(); + ImGui.Text(param.Key); + ImGui.TableNextColumn(); - ImGui.NextColumn(); + switch (type) + { + case "System.Int32": + int int_val = (int)param.Value; + if (ImGui.InputInt($"##{param.Key}", ref int_val)) + { + mSelectedRail.mParameters[param.Key] = int_val; + } + break; + case "System.Boolean": + bool bool_val = (bool)param.Value; + if (ImGui.Checkbox($"##{param.Key}", ref bool_val)) + { + mSelectedRail.mParameters[param.Key] = bool_val; + } + break; + } + ImGui.TableNextColumn(); + } + ImGui.EndTable(); } } } - else if (editContext.IsSingleObjectSelected(out CourseRail.CourseRailPoint? mSelectedRailPoint)) + else if (editContext.IsSingleObjectSelected(out CourseRail.CourseRailPoint? mSelectedRailPoint) || + editContext.IsSingleObjectSelected(out CourseRail.CourseRailPointControl? mSelectedRailPointCont)) { + if(editContext.IsSingleObjectSelected(out CourseRail.CourseRailPointControl? cont)) + mSelectedRailPoint ??= cont.point; ImGui.AlignTextToFramePadding(); ImGui.Text($"Selected Rail Point"); ImGui.NewLine(); ImGui.Separator(); - + if (ImGui.CollapsingHeader("Properties", ImGuiTreeNodeFlags.DefaultOpen)) { - ImGui.Columns(2); - ImGui.Text("Hash"); ImGui.NextColumn(); - string hash = mSelectedRailPoint.mHash.ToString(); - if (ImGui.InputText("##Hash", ref hash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) + if (ImGui.BeginTable("Props", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - mSelectedRailPoint.mHash = Convert.ToUInt64(hash); - } - ImGui.NextColumn(); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.Text("Hash"); ImGui.TableNextColumn(); + string hash = mSelectedRailPoint.mHash.ToString(); + if (ImGui.InputText("##Hash", ref hash, 256, ImGuiInputTextFlags.CharsDecimal | ImGuiInputTextFlags.EnterReturnsTrue)) + { + mSelectedRailPoint.mHash = Convert.ToUInt64(hash); + } - ImGui.AlignTextToFramePadding(); - ImGui.Text("Translation"); - ImGui.NextColumn(); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.Text("Translation"); + + ImGui.TableNextColumn(); + + ImGui.DragFloat3("##Translation", ref mSelectedRailPoint.mTranslate, 0.25f); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.Text("Curve Control"); + + ImGui.TableNextColumn(); + ImGui.Checkbox("##Curved", ref mSelectedRailPoint.mIsCurve); + ImGui.SameLine(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + ImGui.BeginDisabled(!mSelectedRailPoint.mIsCurve); + + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + ImGui.DragFloat3("##Control", ref mSelectedRailPoint.mControl.mTranslate, 0.25f); + ImGui.PopItemWidth(); - ImGui.DragFloat3("##Translation", ref mSelectedRailPoint.mTranslate, 0.25f); - ImGui.PopItemWidth(); + ImGui.EndDisabled(); - ImGui.Columns(1); + ImGui.EndTable(); + } } if (ImGui.CollapsingHeader("Dynamic Properties", ImGuiTreeNodeFlags.DefaultOpen)) { - ImGui.Columns(2); - - foreach (KeyValuePair param in mSelectedRailPoint.mParameters) + if (ImGui.BeginTable("DynamProps", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - string type = param.Value.GetType().ToString(); - ImGui.Text(param.Key); - ImGui.NextColumn(); - - switch (type) + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + foreach (KeyValuePair param in mSelectedRailPoint.mParameters) { - case "System.UInt32": - int uint_val = Convert.ToInt32(param.Value); - if (ImGui.InputInt($"##{param.Key}", ref uint_val)) - { - mSelectedRailPoint.mParameters[param.Key] = Convert.ToUInt32(uint_val); - } - break; - case "System.Int32": - int int_val = (int)param.Value; - if (ImGui.InputInt($"##{param.Key}", ref int_val)) - { - mSelectedRailPoint.mParameters[param.Key] = int_val; - } - break; - case "System.Single": - float float_val = (float)param.Value; - if (ImGui.InputFloat($"##{param.Key}", ref float_val)) - { - mSelectedRailPoint.mParameters[param.Key] = float_val; - } - break; - case "System.Boolean": - bool bool_val = (bool)param.Value; - if (ImGui.Checkbox($"##{param.Key}", ref bool_val)) - { - mSelectedRailPoint.mParameters[param.Key] = bool_val; - } - break; - } + string type = param.Value.GetType().ToString(); + ImGui.Text(param.Key); + ImGui.TableNextColumn(); - ImGui.NextColumn(); + switch (type) + { + case "System.UInt32": + int uint_val = Convert.ToInt32(param.Value); + if (ImGui.InputInt($"##{param.Key}", ref uint_val)) + { + mSelectedRailPoint.mParameters[param.Key] = Convert.ToUInt32(uint_val); + } + break; + case "System.Int32": + int int_val = (int)param.Value; + if (ImGui.InputInt($"##{param.Key}", ref int_val)) + { + mSelectedRailPoint.mParameters[param.Key] = int_val; + } + break; + case "System.Single": + float float_val = (float)param.Value; + if (ImGui.InputFloat($"##{param.Key}", ref float_val)) + { + mSelectedRailPoint.mParameters[param.Key] = float_val; + } + break; + case "System.Boolean": + bool bool_val = (bool)param.Value; + if (ImGui.Checkbox($"##{param.Key}", ref bool_val)) + { + mSelectedRailPoint.mParameters[param.Key] = bool_val; + } + break; + } + ImGui.TableNextColumn(); + } + ImGui.EndTable(); } } } @@ -1119,71 +1342,76 @@ private static void AreaParameters(AreaParam area) if (ImGui.BeginPopup($"AreaParams", ImGuiWindowFlags.NoMove)) { ImGui.SeparatorText("Area Parameters"); - ImGui.Columns(2); - foreach (string key in areaParams.Keys) + if (ImGui.BeginTable("AreaParms", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - string paramType = areaParams[key]; + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + foreach (string key in areaParams.Keys) + { + + string paramType = areaParams[key]; - //if (!area.ContainsParam(key)) - //{ - // continue; - //} + //if (!area.ContainsParam(key)) + //{ + // continue; + //} - ImGui.Text(key); - ImGui.NextColumn(); + ImGui.Text(key); + ImGui.TableNextColumn(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - 5); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - 5); - switch (paramType) - { - case "String": - { - string value = ""; - if (area.ContainsParam(key)) + switch (paramType) + { + case "String": { - value = (string)area.GetParam(area.GetRoot(), key, paramType); + string value = ""; + if (area.ContainsParam(key)) + { + value = (string)area.GetParam(area.GetRoot(), key, paramType); + } + ImGui.InputText($"##{key}", ref value, 1024); + break; } - ImGui.InputText($"##{key}", ref value, 1024); - break; - } - case "Bool": - { - bool value = false; - if (area.ContainsParam(key)) + case "Bool": { - value = (bool)area.GetParam(area.GetRoot(), key, paramType); + bool value = false; + if (area.ContainsParam(key)) + { + value = (bool)area.GetParam(area.GetRoot(), key, paramType); + } + ImGui.Checkbox($"##{key}", ref value); + break; } - ImGui.Checkbox($"##{key}", ref value); - break; - } - case "Int": - { - int value = 0; - if (area.ContainsParam(key)) + case "Int": { - //value = (int)area.GetParam(area.GetRoot(), key, paramType); + int value = 0; + if (area.ContainsParam(key)) + { + //value = (int)area.GetParam(area.GetRoot(), key, paramType); + } + ImGui.InputInt($"##{key}", ref value); + break; } - ImGui.InputInt($"##{key}", ref value); - break; - } - case "Float": - { - float value = 0.0f; - if (area.ContainsParam(key)) + case "Float": { - value = (float)area.GetParam(area.GetRoot(), key, paramType); + float value = 0.0f; + if (area.ContainsParam(key)) + { + value = (float)area.GetParam(area.GetRoot(), key, paramType); + } + ImGui.InputFloat($"##{key}", ref value); + break; } - ImGui.InputFloat($"##{key}", ref value); + default: + Console.WriteLine(key); break; - } - default: - Console.WriteLine(key); - break; + } + ImGui.PopItemWidth(); + ImGui.TableNextColumn(); } - ImGui.PopItemWidth(); - - ImGui.NextColumn(); + ImGui.EndTable(); } ImGui.EndPopup(); } @@ -1245,7 +1473,7 @@ BGUnitRailSceneObj GetRailSceneObj(object courseObject) if (ImGui.Selectable(name, editContext.IsSelected(unit))) { - editContext.DeselectAllOfType(); + editContext.DeselectAll(); editContext.Select(unit); } if (expanded) @@ -1263,34 +1491,38 @@ void RailListItem(string type, BGUnitRail rail, int id) } ImGui.SameLine(); - ImGui.Columns(2); - - void SelectRail() + if (ImGui.BeginTable("Rails", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - editContext.DeselectAllOfType(); - editContext.Select(rail); - } + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); - if (ImGui.Selectable($"##{name}{wallname}", isSelected, ImGuiSelectableFlags.SpanAllColumns)) - { - SelectRail(); - } - if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Right)) - { - SelectRail(); - } + void SelectRail() + { + editContext.DeselectAll(); + editContext.Select(rail); + } - ImGui.SameLine(); + if (ImGui.Selectable($"##{name}{wallname}", isSelected, ImGuiSelectableFlags.SpanAllColumns)) + { + SelectRail(); + } + if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Right)) + { + SelectRail(); + } + + ImGui.SameLine(); - //Shift text from selection - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 22); - ImGui.Text(wallname); + //Shift text from selection + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + 22); + ImGui.Text(wallname); - ImGui.NextColumn(); + ImGui.TableNextColumn(); - ImGui.TextDisabled($"(Num Points: {rail.Points.Count})"); + ImGui.TextDisabled($"(Num Points: {rail.Points.Count})"); - ImGui.Columns(1); + ImGui.EndTable(); + } ImGui.Unindent(); } @@ -1309,45 +1541,76 @@ void SelectRail() } } - if (ImGui.Button("Add Wall")) - editContext.AddWall(unit, new Wall(unit)); - ImGui.SameLine(); - if (ImGui.Button("Remove Wall")) + if (unit.mModelType is not CourseUnit.ModelType.Bridge) { - editContext.WithSuspendUpdateDo(() => + if (ImGui.Button("Add Wall")) + editContext.AddWall(unit, new Wall(unit)); + ImGui.SameLine(); + if (ImGui.Button("Remove Wall")) { - for (int i = unit.Walls.Count - 1; i >= 0; i--) + editContext.WithSuspendUpdateDo(() => { - //TODO is that REALLY how we want to do this? - if (editContext.IsSelected(unit.Walls[i].ExternalRail)) - editContext.DeleteWall(unit, unit.Walls[i]); - } - }); - } + 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) - { - if (wall.InternalRails.Count > 0) + for (int iWall = 0; iWall < unit.Walls.Count; iWall++) { - bool ex = ImGui.TreeNodeEx($"##{name}Wall{unit.Walls.IndexOf(wall)}", ImGuiTreeNodeFlags.DefaultOpen); - ImGui.SameLine(); + Wall wall = unit.Walls[iWall]; + if (wall.InternalRails.Count > 0) + { + ImGui.Unindent(); + bool ex = ImGui.TreeNodeEx($"##{name}Wall{iWall}", ImGuiTreeNodeFlags.DefaultOpen); + ImGui.SameLine(); - RailListItem("Wall", wall.ExternalRail, unit.Walls.IndexOf(wall)); + RailListItem("Wall", wall.ExternalRail, unit.Walls.IndexOf(wall)); - ImGui.Indent(); + ImGui.Indent(); - if (ex) + if (ex) + { + for (int iInternal = 0; iInternal < wall.InternalRails.Count; iInternal++) + { + BGUnitRail? rail = wall.InternalRails[iInternal]; + RailListItem("Internal Rail", rail, iInternal); + } + } + + ImGui.TreePop(); + } + else { - foreach (var rail in wall.InternalRails) - RailListItem("Internal Rail", rail, wall.InternalRails.IndexOf(rail)); + RailListItem("Wall", wall.ExternalRail, iWall); } - ImGui.Unindent(); + } + } - ImGui.TreePop(); + if (unit.mModelType is CourseUnit.ModelType.SemiSolid or CourseUnit.ModelType.Bridge) + { + if (ImGui.Button("Add Belt")) + editContext.AddBeltRail(unit, new BGUnitRail(unit)); + ImGui.SameLine(); + if (ImGui.Button("Remove Belt")) + { + editContext.WithSuspendUpdateDo(() => + { + for (int i = unit.mBeltRails.Count - 1; i >= 0; i--) + { + if (editContext.IsSelected(unit.mBeltRails[i])) + editContext.DeleteBeltRail(unit, unit.mBeltRails[i]); + } + }); } - else + + for (int iBeltRail = 0; iBeltRail < unit.mBeltRails.Count; iBeltRail++) { - RailListItem("Wall", wall.ExternalRail, unit.Walls.IndexOf(wall)); + BGUnitRail beltRail = unit.mBeltRails[iBeltRail]; + RailListItem("Belt", beltRail, iBeltRail); } } ImGui.TreePop(); @@ -1380,7 +1643,7 @@ private void CourseRailsView(CourseRailHolder railHolder) foreach (CourseRail rail in railHolder.mRails) { - var rail_node_flags = ImGuiTreeNodeFlags.None; + var rail_node_flags = ImGuiTreeNodeFlags.OpenOnArrow; if (editContext.IsSelected(rail) && !editContext.IsAnySelected()) { @@ -1419,18 +1682,22 @@ private void CourseRailsView(CourseRailHolder railHolder) private void CourseGlobalLinksView(CourseLinkHolder linkHolder) { + var editContext = areaScenes[selectedArea].EditContext; for (int i = 0; i < linkHolder.mLinks.Count; i++) { CourseLink link = linkHolder.mLinks[i]; - if (ImGui.Selectable($"Link {i}")) + if (ImGui.Selectable($"Link {i}", editContext.IsSelected(link))) { - mSelectedGlobalLink = link; + editContext.DeselectAll(); + editContext.Select(link); } } } //VERY ROUGH BASE - //Still need to implement recursion on getting links, currently just displays the top most links + //TODO, optomize recursion + List topLinks; + CourseActor? selected; private void AreaLocalLinksView(CourseArea area) { var links = area.mLinkHolder; @@ -1440,27 +1707,55 @@ private void AreaLocalLinksView(CourseArea area) var wcMin = ImGui.GetCursorScreenPos() + new Vector2(0, ImGui.GetScrollY()); var wcMax = wcMin + ImGui.GetContentRegionAvail(); - RecursiveLinkFind(area, links, editContext, em); + topLinks = area.GetActors() + .Where(x => links.mLinks.Any(y => y.mSource == x.mHash)).ToList(); + if (ImGui.BeginTable("##Links", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) + { + CourseActor? selected = null; + RecursiveLinkFind(area, links, editContext, em, topLinks, []); + ImGui.EndTable(); + } ImGui.PopClipRect(); ImGui.EndChild(); } - private void RecursiveLinkFind(CourseArea area, CourseLinkHolder links, CourseAreaEditContext editContext, float em) + private void RecursiveLinkFind(CourseArea area, CourseLinkHolder links, + CourseAreaEditContext editContext, float em, IEnumerable linkList, + Hashtable parentActors) { - foreach (CourseActor actor in area.GetActors().Where(x => !links.GetSrcHashesFromDest(x.mHash).Any() && links.GetDestHashesFromSrc(x.mHash).Any())) + foreach (CourseActor actor in linkList) { - ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags.FramePadding | ImGuiTreeNodeFlags.OpenOnArrow; - ImGui.PushID(actor.mHash.ToString()); + var destLinks = links.GetDestHashesFromSrc(actor.mHash); + ImGui.TableNextRow(); + + string actorName = actor.mPackName; + string name = actor.mName; + ulong actorHash = actor.mHash; + bool isSelected = editContext.IsSelected(actor); + + ImGui.TableSetColumnIndex(1); + ImGui.TextDisabled(name); + bool expanded = false; bool isVisible = true; float margin = 1.5f * em; float headerHeight = 1.4f * em; Vector2 cp = ImGui.GetCursorScreenPos(); - expanded = ImGui.TreeNodeEx(actor.mHash.ToString(), node_flags, actor.mPackName); + ImGui.TableSetColumnIndex(0); + + ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags.FramePadding | ImGuiTreeNodeFlags.OpenOnArrow; + + if (isSelected) + node_flags |= ImGuiTreeNodeFlags.Selected; - if (ImGui.IsItemFocused()) + if (!parentActors.ContainsValue(actor) && destLinks.Count > 0) + expanded = ImGui.TreeNodeEx($"{actorHash}", node_flags, actorName); + else + expanded = ImGui.Selectable(actorName, isSelected); + + if (ImGui.IsItemClicked()) { activeViewport.SelectedActor(actor); } @@ -1468,70 +1763,67 @@ private void RecursiveLinkFind(CourseArea area, CourseLinkHolder links, CourseAr if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(0)) { activeViewport.FrameSelectedActor(actor); + selected ??= actor; } + if (parentActors.Count == 0 && selected == actor) + { + ImGui.SetScrollHereY(); + selected = null; + } + + ImGui.BeginDisabled(!isVisible); - if (!isVisible) - ImGui.BeginDisabled(); + UpdateWonderVisibility(actor, destLinks, area); if (expanded) { - foreach (var link in links.GetDestHashesFromSrc(actor.mHash)) + if(!parentActors.ContainsValue(actor) && destLinks.Count > 0) { - if(ImGui.TreeNodeEx(actor.mHash.ToString() + link.Key, ImGuiTreeNodeFlags.FramePadding, link.Key)) + foreach (var link in destLinks) { - foreach (CourseActor linkActor in area.GetActors().Where(x => link.Value.Contains(x.mHash))) + if(ImGui.TreeNodeEx($"{link.Key}##{actorHash}", ImGuiTreeNodeFlags.FramePadding, link.Key)) { - var act = linkActor; - string actorName = act.mPackName; - string name = act.mName; - ulong actorHash = act.mHash; - string actorLink = link.Key; - //Check if the node is within the necessary search filter requirements if search is used - bool HasText = act.mName.IndexOf(mActorSearchText, StringComparison.OrdinalIgnoreCase) >= 0 || - act.mPackName.IndexOf(mActorSearchText, StringComparison.OrdinalIgnoreCase) >= 0 || - act.ToString().Equals(mActorSearchText); - - if (!HasText) - continue; - - bool isSelected = editContext.IsSelected(act); - - ImGui.PushID(actorHash.ToString()); - ImGui.Columns(2); - - if (ImGui.Selectable(actorName, isSelected, ImGuiSelectableFlags.SpanAllColumns)) - { - activeViewport.SelectedActor(act); - } - else if (ImGui.IsItemFocused()) - { - activeViewport.SelectedActor(act); - } - - if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(0)) - { - activeViewport.FrameSelectedActor(act); - } - - - ImGui.NextColumn(); - ImGui.BeginDisabled(); - ImGui.Text(name); - ImGui.EndDisabled(); - ImGui.Columns(1); - - ImGui.PopID(); + var parents = new Hashtable(parentActors); + parents[actorHash] = actor; + var reLinks = area.GetActors().Where(x => link.Value.Contains(x.mHash)); + RecursiveLinkFind(area, links, editContext, em, reLinks, parents); ImGui.TreePop(); } } - } - ImGui.TreePop(); + ImGui.TreePop(); + } } - if (!isVisible) - ImGui.EndDisabled(); + ImGui.EndDisabled(); + } + parentActors.Clear(); + } - ImGui.PopID(); + static void UpdateWonderVisibility(CourseActor actor, Dictionary> links, CourseArea area) + { + foreach (var link in links) + { + var reLinks = area.GetActors().Where(x => link.Value.Contains(x.mHash)); + if (!link.Key.Contains("CreateRelative") && + (link.Key.Contains("Create") || + link.Key.Contains("PopUp") || + link.Key.Contains("Delete") || + link.Key.Contains("BasicSignal"))) + { + foreach (CourseActor linkActor in reLinks) + { + if ((actor.mPackName == "ObjectWonderTag" || actor.mWonderView == WonderViewType.WonderOnly) && + (!link.Key.Contains("BasicSignal") || (linkActor.mActorPack?.Category.Contains("Tag") ?? false))) + { + if (link.Key.Contains("Delete")) + linkActor.mWonderView = WonderViewType.WonderOff; + else + linkActor.mWonderView = WonderViewType.WonderOnly; + } + else + linkActor.mWonderView = WonderViewType.Normal; + } + } } } @@ -1615,9 +1907,11 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) ImGui.PushClipRect(wcMin, wcMax - new Vector2(margin, 0), true); bool isSearch = !string.IsNullOrWhiteSpace(mActorSearchText); + //var sortedLayers = mLayersVisibility.Keys.ToList(); + //sortedLayers.Sort(layerSort); ImGui.Spacing(); - foreach (string layer in mLayersVisibility.Keys) + foreach (string layer in mLayersVisibility.Keys) //Use sortedLayers if you think the sorting code is good { ImGui.PushID(layer); cp = ImGui.GetCursorScreenPos(); @@ -1629,7 +1923,7 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) expanded = ImGui.TreeNodeEx("TreeNode", ImGuiTreeNodeFlags.FramePadding, layer); ImGui.PushClipRect(wcMin, wcMax, false); - ImGui.SetCursorScreenPos(new Vector2(wcMax.X - (margin + em) / 2, cp.Y)); + ImGui.SetCursorScreenPos(new Vector2(wcMax.X - (margin + em*4) / 2, cp.Y)); isVisible = mLayersVisibility[layer]; if (ToggleButton($"VisibleCheckbox", IconUtil.ICON_EYE, IconUtil.ICON_EYE_SLASH, ref isVisible, new Vector2(em))) @@ -1641,9 +1935,15 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) ImGui.AlignTextToFramePadding(); ImGui.Text(layer); } + var dummy = false; + ImGui.PushClipRect(wcMin, wcMax, false); + ImGui.SetCursorScreenPos(new Vector2(wcMax.X - (margin + em) / 2, cp.Y)); + if (ToggleButton($"Delete Layer", IconUtil.ICON_TRASH, IconUtil.ICON_TRASH, + ref dummy, new Vector2(em))) + _ = DeleteLayerWithWarningPrompt(layer, actorArray, editContext); + ImGui.PopClipRect(); - if (!isVisible) - ImGui.BeginDisabled(); + ImGui.BeginDisabled(!isVisible); if (expanded || isSearch) { @@ -1670,27 +1970,32 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) bool isSelected = editContext.IsSelected(actor); ImGui.PushID(actorHash.ToString()); - ImGui.Columns(2); - if (ImGui.Selectable(actorName, isSelected, ImGuiSelectableFlags.SpanAllColumns)) - { - activeViewport.SelectedActor(actor); - } - else if (ImGui.IsItemFocused()) + if (ImGui.BeginTable("##Links", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - activeViewport.SelectedActor(actor); - } + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + + if (ImGui.Selectable(actorName, isSelected, ImGuiSelectableFlags.SpanAllColumns)) + { + activeViewport.SelectedActor(actor); + } + else if (ImGui.IsItemFocused()) + { + activeViewport.SelectedActor(actor); + } - if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(0)) - { - activeViewport.FrameSelectedActor(actor); - } + if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(0)) + { + activeViewport.FrameSelectedActor(actor); + } - ImGui.NextColumn(); - ImGui.BeginDisabled(); - ImGui.Text(name); - ImGui.EndDisabled(); - ImGui.Columns(1); + ImGui.TableNextColumn(); + ImGui.BeginDisabled(); + ImGui.Text(name); + ImGui.EndDisabled(); + ImGui.EndTable(); + } ImGui.PopID(); } @@ -1699,8 +2004,7 @@ private void CourseActorsLayerView(CourseActorHolder actorArray) ImGui.TreePop(); } - if (!isVisible) - ImGui.EndDisabled(); + ImGui.EndDisabled(); ImGui.PopID(); } @@ -1815,29 +2119,62 @@ private void CourseMiniView() dl.AddRectFilled( MapPointPixelAligned(pos), MapPointPixelAligned(pos + Vector2.One), - 0xFF444444); + 0xFF666688); } } - var foregroundSubUnits = area.mUnitHolder.mUnits - .Where(x => x.mModelType != CourseUnit.ModelType.NoCollision) - .SelectMany(x => x.mTileSubUnits); + var foregroundTileUnits = area.mUnitHolder.mUnits + .Where(x => x.mModelType != CourseUnit.ModelType.NoCollision); + + var foregroundSubUnits = foregroundTileUnits + .SelectMany(x => x.mTileSubUnits) + .OrderBy(x => x.mOrigin.Z); foreach (var subUnit in foregroundSubUnits) { - var origin2D = new Vector2(subUnit.mOrigin.X, subUnit.mOrigin.Y); + var type = foregroundTileUnits.First(x => x.mTileSubUnits.Contains(subUnit)).mModelType; + var unitColor = 0xFF999999; + var edgeColor = 0xFFEEEEEE; + switch (type) + { + case CourseUnit.ModelType.Solid: + unitColor = 0xFFBB9999; + edgeColor = 0xFFFFEEEE; + break; + case CourseUnit.ModelType.SemiSolid: + unitColor = 0xFF99BB99; + edgeColor = 0xFFEEFFEE; + break; + } + + var origin2D = new Vector2(subUnit.mOrigin.X, subUnit.mOrigin.Y); foreach (var tile in subUnit.GetTiles(bb.Min - origin2D, bb.Max - origin2D)) { var pos = tile.pos + origin2D; dl.AddRectFilled( MapPointPixelAligned(pos), MapPointPixelAligned(pos + Vector2.One), - 0xFF999999); + unitColor); + } + if (subUnit == foregroundSubUnits.Last(x => x.mOrigin.Z == subUnit.mOrigin.Z)) + { + foreach(var wall in foregroundTileUnits + .SelectMany(x => x.Walls) + .Where(x => x.ExternalRail.Points.FirstOrDefault()?.Position.Z == subUnit.mOrigin.Z)) + { + var rail = wall.ExternalRail; + + var pos = rail.Points.Select(x => MapPointPixelAligned(new(x.Position.X, x.Position.Y))).ToArray(); + dl.AddPolyline(ref pos[0], + rail.Points.Count, + edgeColor, + rail.IsClosed ? ImDrawFlags.Closed:ImDrawFlags.None, + 1.5f); + } } } - dl.AddRect(lvlRectTopLeft, lvlRectTopLeft + lvlRectSize, ImGui.ColorConvertFloat4ToU32(ImGui.GetStyle().Colors[(int)ImGuiCol.Text]),6,0,3); @@ -1869,7 +2206,7 @@ float DegToRad(float deg) ImGui.AlignTextToFramePadding(); ImGui.Text(label); - ImGui.NextColumn(); + ImGui.TableNextColumn(); ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); @@ -1883,44 +2220,41 @@ float DegToRad(float deg) } ImGui.PopItemWidth(); - - ImGui.NextColumn(); } if (ImGui.CollapsingHeader("Transform", ImGuiTreeNodeFlags.DefaultOpen)) { ImGui.Indent(); - ImGui.Columns(2); - - ImGui.AlignTextToFramePadding(); - ImGui.Text("Scale"); - ImGui.NextColumn(); - - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + if (ImGui.BeginTable("Trans", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.AlignTextToFramePadding(); + ImGui.Text("Scale"); + ImGui.TableNextColumn(); - ImGui.DragFloat3("##Scale", ref actor.mScale, 0.25f, 0, float.MaxValue); - ImGui.PopItemWidth(); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - ImGui.NextColumn(); + ImGui.DragFloat3("##Scale", ref actor.mScale, 0.25f, 0, float.MaxValue); + ImGui.PopItemWidth(); - ImGui.Columns(1); - ImGui.Unindent(); + ImGui.TableNextColumn(); - ImGui.Indent(); - ImGui.Columns(2); + EditFloat3RadAsDeg("Rotation", ref actor.mRotation, 0.25f); - EditFloat3RadAsDeg("Rotation", ref actor.mRotation, 0.25f); + ImGui.TableNextColumn(); - ImGui.AlignTextToFramePadding(); - ImGui.Text("Translation"); - ImGui.NextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.Text("Translation"); + ImGui.TableNextColumn(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - ImGui.DragFloat3("##Translation", ref actor.mTranslation, 0.25f); - ImGui.PopItemWidth(); + ImGui.DragFloat3("##Translation", ref actor.mTranslation, 0.25f); + ImGui.PopItemWidth(); - ImGui.Columns(1); + ImGui.EndTable(); + } ImGui.Unindent(); } } @@ -1947,101 +2281,103 @@ private void DynamicParamNode(CourseActor actor) ImGui.Indent(); - ImGui.Columns(2); - - if (param == "ChildActorSelectName" && ChildActorParam.ActorHasChildParam(actor.mPackName)) + if (ImGui.BeginTable("DynamProps", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable)) { - string id = $"##{param}"; - List list = ChildActorParam.GetActorParams(actor.mPackName); - int selected = list.IndexOf(actor.mActorParameters["ChildActorSelectName"].ToString()); - ImGui.Text("ChildParameters"); - ImGui.NextColumn(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); - if (ImGui.Combo("##Parameters", ref selected, list.ToArray(), list.Count)) + if (param == "ChildActorSelectName" && ChildActorParam.ActorHasChildParam(actor.mPackName)) { - actor.mActorParameters["ChildActorSelectName"] = list[selected]; + string id = $"##{param}"; + List list = ChildActorParam.GetActorParams(actor.mPackName); + int selected = list.IndexOf(actor.mActorParameters["ChildActorSelectName"].ToString()); + ImGui.Text("ChildParameters"); + ImGui.TableNextColumn(); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); + + if (ImGui.Combo("##Parameters", ref selected, list.ToArray(), list.Count)) + { + actor.mActorParameters["ChildActorSelectName"] = list[selected]; + } + ImGui.PopItemWidth(); } - } - else - { - foreach (KeyValuePair pair in ParamDB.GetComponentParams(param)) + else { - string id = $"##{pair.Key}"; + foreach (KeyValuePair pair in ParamDB.GetComponentParams(param)) + { + string id = $"##{pair.Key}"; - ImGui.AlignTextToFramePadding(); - ImGui.Text(pair.Key); - ImGui.NextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.Text(pair.Key); + ImGui.TableNextColumn(); - ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - - if (actor.mActorParameters.ContainsKey(pair.Key)) - { - var actorParam = actor.mActorParameters[pair.Key]; + ImGui.PushItemWidth(ImGui.GetColumnWidth() - ImGui.GetStyle().ScrollbarSize); - if(pair.Value.IsSignedInt(out int minValue, out int maxValue)) + if (actor.mActorParameters.ContainsKey(pair.Key)) { - int val_int = (int)actorParam; - if (ImGui.InputInt(id, ref val_int)) + var actorParam = actor.mActorParameters[pair.Key]; + + if(pair.Value.IsSignedInt(out int minValue, out int maxValue)) { - actor.mActorParameters[pair.Key] = Math.Clamp(val_int, minValue, maxValue); + int val_int = (int)actorParam; + if (ImGui.InputInt(id, ref val_int)) + { + actor.mActorParameters[pair.Key] = Math.Clamp(val_int, minValue, maxValue); + } } - } - else if (pair.Value.IsUnsignedInt(out minValue, out maxValue)) - { - uint val_uint = (uint)actorParam; - int val_int = unchecked((int)val_uint); - if (ImGui.InputInt(id, ref val_int)) + else if (pair.Value.IsUnsignedInt(out minValue, out maxValue)) { - actor.mActorParameters[pair.Key] = unchecked((uint)Math.Clamp(val_int, minValue, maxValue)); + uint val_uint = (uint)actorParam; + int val_int = unchecked((int)val_uint); + if (ImGui.InputInt(id, ref val_int)) + { + actor.mActorParameters[pair.Key] = unchecked((uint)Math.Clamp(val_int, minValue, maxValue)); + } } - } - else if (pair.Value.IsBool()) - { - bool val_bool = (bool)actorParam; - if (ImGui.Checkbox(id, ref val_bool)) + else if (pair.Value.IsBool()) { - actor.mActorParameters[pair.Key] = val_bool; - } + bool val_bool = (bool)actorParam; + if (ImGui.Checkbox(id, ref val_bool)) + { + actor.mActorParameters[pair.Key] = val_bool; + } - } - else if (pair.Value.IsFloat()) - { - float val_float = (float)actorParam; - if (ImGui.InputFloat(id, ref val_float)) + } + else if (pair.Value.IsFloat()) { - actor.mActorParameters[pair.Key] = val_float; + float val_float = (float)actorParam; + if (ImGui.InputFloat(id, ref val_float)) + { + actor.mActorParameters[pair.Key] = val_float; + } } - } - else if (pair.Value.IsString()) - { - string val_string = (string)actorParam; - if (ImGui.InputText(id, ref val_string, 1024)) + else if (pair.Value.IsString()) { - actor.mActorParameters[pair.Key] = val_string; + string val_string = (string)actorParam; + if (ImGui.InputText(id, ref val_string, 1024)) + { + actor.mActorParameters[pair.Key] = val_string; + } } - } - else if (pair.Value.IsDouble()) - { - double val = (double)actorParam; - if (ImGui.InputDouble(id, ref val)) + else if (pair.Value.IsDouble()) { - actor.mActorParameters[pair.Key] = val; + double val = (double)actorParam; + if (ImGui.InputDouble(id, ref val)) + { + actor.mActorParameters[pair.Key] = val; + } } } - } - ImGui.PopItemWidth(); - - ImGui.NextColumn(); + ImGui.PopItemWidth(); + ImGui.TableNextColumn(); + } } - } - - ImGui.Columns(1); + ImGui.EndTable(); + } ImGui.Unindent(); ImGui.Unindent(); - } } } @@ -2051,18 +2387,20 @@ public Course GetCourse() return course; } - private async Task PickLinkDestInViewportFor(CourseActor source) + private async Task<(CourseActor?, KeyboardModifier modifiers)> PickLinkDestInViewportFor(CourseActor source) { - var (picked, _) = await activeViewport.PickObject( - "Select the destination actor you wish to link to.", + var (picked, modifier) = await activeViewport.PickObject( + "Select the destination actor you wish to link to. -- Hold SHIFT to link multiple", x => x is CourseActor && x != source); - return picked as CourseActor; + return (picked as CourseActor, modifier); } private async Task DeleteObjectsWithWarningPrompt(IReadOnlyList objectsToDelete, CourseAreaEditContext ctx, string actionName) { var actors = objectsToDelete.OfType(); + if (actors.Count() == 1) + actionName = "Delete "+actors.ElementAt(0).mPackName; List dstMsgStrs = []; List srcMsgStrs = []; @@ -2128,6 +2466,61 @@ private async Task DeleteObjectsWithWarningPrompt(IReadOnlyList objectsT batchAction.Commit($"{IconUtil.ICON_TRASH} {actionName}"); } + //TODO making this undoable + private async Task DeleteLayerWithWarningPrompt(string layer, + CourseActorHolder actorArray, CourseAreaEditContext ctx) + { + var actors = actorArray.mActors.FindAll(x => x.mLayer == layer); + bool noWarnings = !(actors.Count > 0); + + if (!noWarnings) + { + List warningActors = []; + foreach (var actor in actors) + { + if (selectedArea.mActorHolder.TryGetActor(actor.mHash, out _)) + { + warningActors.Add($"{selectedArea.mActorHolder[actor.mHash].mPackName} [{selectedArea.mActorHolder[actor.mHash].mName}]\n"); + } + } + + var result = await OperationWarningDialog.ShowDialog(mPopupModalHost, + "Deletion warning", + "Deleting " + layer + + " will delete the following actors", + ("Actors", warningActors)); + + if (result == OperationWarningDialog.DialogResult.Cancel) + return; + } + else + { + var result = await OperationWarningDialog.ShowDialog(mPopupModalHost, + "Deletion warning", + "Are you sure you want to delete " + + layer+"?"); + + if (result == OperationWarningDialog.DialogResult.Cancel) + return; + } + + var batchAction = ctx.BeginBatchAction(); + + foreach (var actor in actors) + { + ctx.DeleteActor(actor); + } + ctx.CommitAction(new PropertyFieldsSetUndo( + this, + [("mLayersVisibility", new Dictionary(mLayersVisibility))], + $"{IconUtil.ICON_TRASH} Delete {layer}" + ) + ); + mLayersVisibility.Remove(layer); + + batchAction.Commit($"{IconUtil.ICON_TRASH} Delete Layer: {layer}"); + } + private async Task AddActorsWithSelectActorAndLayerWindow() { var viewport = activeViewport; @@ -2162,39 +2555,99 @@ private async Task AddActorsWithSelectActorAndLayerWindow() posVec.Y = MathF.Round(posVec.Y * 2, MidpointRounding.AwayFromZero) / 2; posVec.Z = 0.0f; actor.mTranslation = posVec; + var i = 0; + do + { + i++; + } while (area.GetActors().Any(x => x.mName == $"{actor.mPackName}{i}")); + actor.mName = $"{actor.mPackName}{i}"; ctx.AddActor(actor); } while ((modifier & KeyboardModifier.Shift) > 0); } + private async Task AddLayerWithLayerWindow() + { + var viewport = activeViewport; + var area = selectedArea; + var ctx = areaScenes[selectedArea].EditContext; + if(mOpenToolWindows.Any(x=>x is SelectActorAndLayerWindow)) + return; + + var window = new SelectActorAndLayerWindow(mLayersVisibility, false); + mOpenToolWindows.Add(window); + + var result = await window.Result(); + if (!result.TryGetValue(out var resultVal)) + return; + + var layer = result.Value.layer; + + if(layer == "PlayArea" || layer == "DecoArea") + { + int startIdx = layer == "DecoArea" ? 0:1; + for (int i = startIdx; /*no condition*/; i++) + { + if (!mLayersVisibility.ContainsKey($"{layer}{i}")) + { + layer += i; + break; + } + } + } + ctx.CommitAction(new PropertyFieldsSetUndo( + this, + [("mLayersVisibility", new Dictionary(mLayersVisibility))], + $"{IconUtil.ICON_LAYER_GROUP} Added Layer: {layer}" + ) + ); + mLayersVisibility[layer] = true; + } interface IToolWindow { void Draw(ref bool windowOpen); } - class SelectActorAndLayerWindow(IReadOnlyDictionary mLayersVisibility) : IToolWindow + class SelectActorAndLayerWindow(IReadOnlyDictionary mLayersVisibility, bool addActors = true) : IToolWindow { public void Draw(ref bool windowOpen) { bool status; - if (mSelectedActor == null) + if(addActors) { - status = ImGui.Begin("Add Actor###SelectActorLayer", ref windowOpen); - SelectActorToAdd(); - } - else if(mSelectedLayer == null) - { - status = ImGui.Begin("Select Layer###SelectActorLayer", ref windowOpen); - SelectActorToAddLayer(); + if (mSelectedActor == null) + { + status = ImGui.Begin("Add Actor###SelectActorLayer", ref windowOpen); + SelectActorToAdd(); + } + else if(mSelectedLayer == null) + { + status = ImGui.Begin("Select Layer###SelectActorLayer", ref windowOpen); + SelectActorToAddLayer(); + } + else + { + mPromise.TrySetResult((addActors ? mSelectedActor:"", mSelectedLayer)); + windowOpen = false; + return; + } } else { - mPromise.TrySetResult((mSelectedActor, mSelectedLayer)); - windowOpen = false; - return; + if(mSelectedLayer == null) + { + status = ImGui.Begin("Select Layer###SelectActorLayer", ref windowOpen); + SelectLayerToAdd(); + } + else + { + mPromise.TrySetResult(("", mSelectedLayer)); + windowOpen = false; + return; + } } if (ImGui.IsKeyDown(ImGuiKey.Escape)) @@ -2247,7 +2700,7 @@ private void SelectActorToAddLayer() ImGui.InputText("Search", ref mAddLayerSearchQuery, 256); var fileteredLayers = mLayersVisibility.Keys.ToArray().ToImmutableList(); - + if (mAddLayerSearchQuery != "") { fileteredLayers = FuzzySharp.Process.ExtractAll(mAddLayerSearchQuery, mLayersVisibility.Keys.ToArray(), cutoff: 65) @@ -2270,6 +2723,53 @@ private void SelectActorToAddLayer() } } + // TODO, maybe find a way to combine with SelectActorToAddLayer(), if that's needed + private void SelectLayerToAdd() + { + const int MaxLayerCount = 10; + int layerCount = 0; + + ImGui.InputText("Search", ref mAddLayerSearchQuery, 256); + + string[] Layers = layerTypes + .Except(mLayersVisibility.Keys) + .ToArray(); + var fileteredLayers = Layers.ToImmutableList(); + + if (mAddLayerSearchQuery != "") + { + fileteredLayers = FuzzySharp.Process.ExtractAll(mAddLayerSearchQuery, Layers, cutoff: 65) + .OrderByDescending(result => result.Score) + .Select(result => result.Value) + .ToImmutableList(); + } + + if (ImGui.BeginListBox("Select the layer you want to add the actor to.", ImGui.GetContentRegionAvail())) + { + for (var i = 0; i < fileteredLayers.Count; i++) + { + var layer = fileteredLayers[i]; + layerCount = mLayersVisibility.Keys + .Count(x => x.StartsWith(layer) && NumberRegex.IsMatch(x.AsSpan(layer.Length..))); + if (layer == "PlayArea" || layer == "DecoArea") + layer += $" ({layerCount}/{MaxLayerCount})"; + + ImGui.BeginDisabled(layerCount == MaxLayerCount); + + ImGui.Selectable(layer); + + if (ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(0)) + { + mSelectedLayer = fileteredLayers[i]; + } + + ImGui.EndDisabled(); + } + + ImGui.EndListBox(); + } + } + public Task<(string actor, string layer)?> Result() => mPromise.Task; private string? mSelectedActor; diff --git a/Fushigi/ui/widgets/LevelViewport.cs b/Fushigi/ui/widgets/LevelViewport.cs index a5d01212..5007e681 100644 --- a/Fushigi/ui/widgets/LevelViewport.cs +++ b/Fushigi/ui/widgets/LevelViewport.cs @@ -18,7 +18,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using static Fushigi.course.CourseUnit; +using Fushigi.ui.undo; using Vector3 = System.Numerics.Vector3; +using Fasterflect; namespace Fushigi.ui.widgets { @@ -36,7 +38,7 @@ public static void DefaultSelect(CourseAreaEditContext ctx, object selectable) { ctx.Select(selectable); } - else if(!ctx.IsSelected(selectable)) + else if(!ctx.IsSelected(selectable)) { ctx.WithSuspendUpdateDo(() => { @@ -82,13 +84,15 @@ internal class LevelViewport(CourseArea area, GL gl, CourseAreaScene areaScene) public bool IsViewportHovered; public bool IsViewportActive; - public bool IsWonderView; + public bool isPanGesture; + public WonderViewType WonderViewMode = WonderViewType.Normal; public bool PlayAnimations = false; public bool ShowGrid = true; Vector2 mSize = Vector2.Zero; public ulong prevSelectVersion { get; private set; } = 0; + public bool dragRelease; private IDictionary? mLayersVisibility; Vector2 mTopLeft = Vector2.Zero; @@ -98,6 +102,8 @@ internal class LevelViewport(CourseArea area, GL gl, CourseAreaScene areaScene) public TileBfresRender TileBfresRenderFieldA; public TileBfresRender TileBfresRenderFieldB; public AreaResourceManager EnvironmentData = new AreaResourceManager(gl, area.mInitEnvPalette); + + private readonly HashSet mRegisteredUnits = []; DistantViewManager DistantViewScrollManager = new DistantViewManager(area); @@ -216,8 +222,9 @@ public void SelectedActor(CourseActor actor) public void HandleCameraControls(double deltaSeconds) { - bool isPanGesture = ImGui.IsMouseDragging(ImGuiMouseButton.Middle) || - (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && ImGui.GetIO().KeyShift && !mEditContext.IsAnySelected()); + isPanGesture = ImGui.IsMouseDragging(ImGuiMouseButton.Middle) || + (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && ImGui.GetIO().KeyShift && + mHoveredObject == null && !mEditContext.IsSelected(mHoveredObject) && !dragRelease); if (IsViewportActive && isPanGesture) { @@ -318,15 +325,7 @@ TileBfresRender CreateTileRendererForSkin(SkinDivision division, string skinName NoHit: table.GetPackName(skinName, "NoHit"), Bridge: table.GetPackName(skinName, "Bridge") ), division); - render.Load(this.mArea.mUnitHolder, this.Camera); - - foreach (var courseUnit in this.mArea.mUnitHolder.mUnits.Where(x => x.mSkinDivision == division)) - { - courseUnit.TilesUpdated += delegate - { - render.Load(this.mArea.mUnitHolder, this.Camera); - }; - } + render.Load(this.mArea.mUnitHolder); return render; } @@ -339,17 +338,42 @@ TileBfresRender CreateTileRendererForSkin(SkinDivision division, string skinName if (TileBfresRenderFieldB == null && !string.IsNullOrEmpty(fieldBSkin)) TileBfresRenderFieldB = CreateTileRendererForSkin(SkinDivision.FieldB, fieldBSkin); + //continuously register all course units that haven't been registered yet + foreach (var courseUnit in mArea.mUnitHolder.mUnits) + { + if (mRegisteredUnits.Contains(courseUnit)) + continue; + + if(courseUnit.mSkinDivision == SkinDivision.FieldA && TileBfresRenderFieldA is not null) + { + courseUnit.TilesUpdated += () => TileBfresRenderFieldA.Load(this.mArea.mUnitHolder); + } + else if (courseUnit.mSkinDivision == SkinDivision.FieldB && TileBfresRenderFieldB is not null) + { + courseUnit.TilesUpdated += () => TileBfresRenderFieldB.Load(this.mArea.mUnitHolder); + } + + mRegisteredUnits.Add(courseUnit); + } + TileBfresRenderFieldA?.Render(gl, this.Camera); TileBfresRenderFieldB?.Render(gl, this.Camera); //Display skybox EnvironmentData.RenderSky(gl, this.Camera); - foreach (var actor in this.mArea.GetActors()) + // Actors are listed in the order they were pulled from the yaml. + // So they are ordered by depth for rendering. + foreach (var actor in this.mArea.GetActors().OrderBy(x => x.mTranslation.Z)) { - if (actor.mActorPack == null || mLayersVisibility.ContainsKey(actor.mLayer) && !mLayersVisibility[actor.mLayer]) - continue; + actor.wonderVisible = WonderViewMode == actor.mWonderView || + WonderViewMode == WonderViewType.Normal || + actor.mWonderView == WonderViewType.Normal; + if (actor.mActorPack == null || (mLayersVisibility.ContainsKey(actor.mLayer) && !mLayersVisibility[actor.mLayer]) || + !actor.wonderVisible) + continue; + RenderActor(actor, actor.mActorPack.ModelInfoRef); RenderActor(actor, actor.mActorPack.DrawArrayModelInfoRef); } @@ -407,6 +431,7 @@ private void RenderActor(CourseActor actor, ModelInfo modelInfo) if(actor.mActorPack.ModelExpandParamRef != null) { ActorModelExpand(actor, model); + ActorModelExpand(actor, model, "Main"); //yeah idk either //TODO SubModels } @@ -486,15 +511,17 @@ private void ActorModelExpand(CourseActor actor, BfresRender.BfresModel model, s { //Model Expand Param - Debug.Assert(actor.mActorPack.ModelExpandParamRef.Settings.Count > 0); - - if (actor.mActorPack.ModelExpandParamRef.Settings.Count == 0) - return; - //TODO is that actually how the game does it? - var setting = actor.mActorPack.ModelExpandParamRef.Settings.FindLast(x=>x.mModelKeyName == modelKeyName); + var param = actor.mActorPack.ModelExpandParamRef; + ModelExpandParamSettings? setting = null; + do + { + if (param.Settings != null) + setting = param.Settings.FindLast(x => x.mModelKeyName == modelKeyName); + + param = param.Parent; + } while (setting == null && param != null); - //Debug.Assert(setting != null); if (setting == null) return; @@ -538,7 +565,7 @@ private void ActorModelExpand(CourseActor actor, BfresRender.BfresModel model, s Dictionary boneScaleLookup = []; - foreach (var boneParam in setting.mBoneSetting.BoneInfoList) + foreach (var boneParam in setting.mBoneSetting?.BoneInfoList ?? []) { Vector2 boneScale; if (boneParam.mIsCustomCalc) @@ -733,7 +760,7 @@ void InteractionWithFocus(KeyboardModifier modifiers) return; } - if (ImGui.IsMouseDragging(ImGuiMouseButton.Left)) + if (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && !isPanGesture) { if (mEditContext.IsAnySelected()) { @@ -771,6 +798,22 @@ void InteractionWithFocus(KeyboardModifier modifiers) rail.mTranslate = posVec; } } + if (mEditContext.IsSingleObjectSelected(out CourseRail.CourseRailPointControl? railCont)) + { + Vector3 posVec = ScreenToWorld(ImGui.GetMousePos()); + + if (ImGui.GetIO().KeyShift) + { + railCont.mTranslate = posVec; + } + else + { + posVec.X = MathF.Round(posVec.X * 2, MidpointRounding.AwayFromZero) / 2; + posVec.Y = MathF.Round(posVec.Y * 2, MidpointRounding.AwayFromZero) / 2; + posVec.Z = railCont.mTranslate.Z; + railCont.mTranslate = posVec; + } + } } @@ -796,13 +839,17 @@ void InteractionWithFocus(KeyboardModifier modifiers) } } - if(mHoveredObject != null && mHoveredObject is CourseActor && - ImGui.IsMouseReleased(ImGuiMouseButton.Left)) + if (ImGui.IsMouseDown(0) && !isPanGesture) + dragRelease = ImGui.IsMouseDragging(0); + + if(ImGui.IsMouseReleased(0)) { - if (ImGui.GetIO().MouseDragMaxDistanceSqr[0] <= ImGui.GetIO().MouseDragThreshold) + if(mHoveredObject != null && + mHoveredObject is CourseActor && + !dragRelease) { - if(ImGui.IsKeyDown(ImGuiKey.LeftShift) - && prevSelectVersion == mEditContext.SelectionVersion) + if(ImGui.IsKeyDown(ImGuiKey.LeftShift) && + prevSelectVersion == mEditContext.SelectionVersion) { mEditContext.Deselect(mHoveredObject!); } @@ -812,11 +859,45 @@ void InteractionWithFocus(KeyboardModifier modifiers) IViewportSelectable.DefaultSelect(mEditContext, mHoveredObject); } } + + var actors = mEditContext.GetSelectedObjects().Where(x => x.mTranslation != x.mStartingTrans) ?? []; + + if (actors.Any()) + { + if (mEditContext.IsSingleObjectSelected(out CourseActor? act)) + { + mEditContext.CommitAction(new PropertyFieldsSetUndo( + act, + [("mTranslation", act.GetFieldValue("mStartingTrans"))], + $"{IconUtil.ICON_ARROWS_ALT} Move {string.Join(", ", act.mPackName)}")); + } + else + { + var batchAction = mEditContext.BeginBatchAction(); + foreach(CourseActor actor in mEditContext.GetSelectedObjects().Where(x => x.mTranslation != x.mStartingTrans)) + { + mEditContext.CommitAction(new PropertyFieldsSetUndo( + actor, + [("mTranslation", actor.GetFieldValue("mStartingTrans"))], + $"{IconUtil.ICON_ARROWS_ALT} Move {string.Join(", ", actor.mName)}")); + } + batchAction.Commit($"{IconUtil.ICON_ARROWS_ALT} Move {string.Join(", ", actors.Count())} Actors"); + } + } + + dragRelease = false; } if (ImGui.IsKeyPressed(ImGuiKey.Delete)) ObjectDeletionRequested?.Invoke(mEditContext.GetSelectedObjects().ToList()); + if (mEditContext.IsSingleObjectSelected(out CourseRail.CourseRailPoint? point) && + mHoveredObject == point && + ImGui.IsMouseDoubleClicked(0)) + { + point.mIsCurve = !point.mIsCurve; + } + if (ImGui.IsKeyPressed(ImGuiKey.Escape)) mEditContext.DeselectAll(); } @@ -894,7 +975,7 @@ void DrawGridLines(bool is_vertical, } private static Vector2[] s_actorRectPolygon = new Vector2[4]; - + void DrawAreaContent() { const float pointSize = 3.0f; @@ -929,13 +1010,20 @@ Vector2[] GetPoints() } bool isSelected = mEditContext.IsSelected(rail); - bool hovered = LevelViewport.HitTestLineLoopPoint(GetPoints(), 10f, ImGui.GetMousePos()); + bool hovered = MathUtil.HitTestLineLoopPoint(GetPoints(), 10f, ImGui.GetMousePos()); + + //Rail selection disabled for now as it conflicts with point selection + //Putting it at the top of the for loop should make it prioritize points + //TODO curved rails still need work-Donavin + // if (hovered) + // newHoveredObject = rail; CourseRail.CourseRailPoint selectedPoint = null; foreach (var point in rail.mPoints) { var pos2D = this.WorldToScreen(new(point.mTranslate.X, point.mTranslate.Y, point.mTranslate.Z)); + var contPos2D = this.WorldToScreen(point.mControl.mTranslate); Vector2 pnt = new(pos2D.X, pos2D.Y); bool isHovered = (ImGui.GetMousePos() - pnt).Length() < 6.0f; if (isHovered) @@ -943,7 +1031,11 @@ Vector2[] GetPoints() bool selected = mEditContext.IsSelected(point); if (selected) + { selectedPoint = point; + if ((ImGui.GetMousePos() - contPos2D).Length() < 6.0f) + newHoveredObject = point.mControl; + } } //Delete selected @@ -954,9 +1046,11 @@ Vector2[] GetPoints() if (selectedPoint != null && ImGui.IsMouseReleased(0)) { //Check if point matches an existing point, remove if intersected - var matching = rail.mPoints.Where(x => x.mTranslate == selectedPoint.mTranslate).ToList(); - if (matching.Count > 1) - rail.mPoints.Remove(selectedPoint); + //The base game has overlapping points, this breaks things + + // var matching = rail.mPoints.Where(x => x.mTranslate == selectedPoint.mTranslate).ToList(); + // if (matching.Count > 1) + // rail.mPoints.Remove(selectedPoint); } bool add_point = ImGui.IsMouseClicked(0) && ImGui.IsMouseDown(0) && ImGui.GetIO().KeyAlt; @@ -998,10 +1092,6 @@ Vector2[] GetPoints() this.mEditContext.Select(newPoint); newHoveredObject = newPoint; } - - //Rail selection disabled for now as it conflicts with point selection - // if (hovered) - // newHoveredObject = rail; } foreach (CourseRail rail in mArea.mRailHolder.mRails) @@ -1013,16 +1103,6 @@ Vector2[] GetPoints() var rail_color = selected ? ImGui.ColorConvertFloat4ToU32(new(1, 1, 0, 1)) : color; List pointsList = []; - foreach (CourseRail.CourseRailPoint pnt in rail.mPoints) - { - bool point_selected = mEditContext.IsSelected(pnt); - var rail_point_color = point_selected ? ImGui.ColorConvertFloat4ToU32(new(1, 1, 0, 1)) : color; - var size = newHoveredObject == pnt ? pointSize * 1.5f : pointSize; - - var pos2D = WorldToScreen(pnt.mTranslate); - mDrawList.AddCircleFilled(pos2D, size, rail_point_color); - pointsList.Add(pos2D); - } var segmentCount = rail.mPoints.Count; if (!rail.mIsClosed) @@ -1040,12 +1120,12 @@ Vector2[] GetPoints() Vector2 cpOutA2D = posA2D; Vector2 cpInB2D = posB2D; - if (pointA.mControl.TryGetValue(out Vector3 control)) - cpOutA2D = WorldToScreen(control); + if (pointA.mIsCurve) + cpOutA2D = WorldToScreen(pointA.mControl.mTranslate); - if (pointB.mControl.TryGetValue(out control)) + if (pointB.mIsCurve) //invert control point - cpInB2D = WorldToScreen(pointB.mTranslate - (control - pointB.mTranslate)); + cpInB2D = WorldToScreen(pointB.mTranslate - (pointB.mControl.mTranslate - pointB.mTranslate)); if (cpOutA2D == posA2D && cpInB2D == posB2D) { @@ -1059,6 +1139,24 @@ Vector2[] GetPoints() float thickness = newHoveredObject == rail ? 3f : 2.5f; mDrawList.PathStroke(rail_color, ImDrawFlags.None, thickness); + + foreach (CourseRail.CourseRailPoint pnt in rail.mPoints) + { + bool point_selected = mEditContext.IsSelected(pnt) || mEditContext.IsSelected(pnt.mControl); + var rail_point_color = point_selected ? ImGui.ColorConvertFloat4ToU32(new(1, 1, 0, 1)) : color; + var size = (newHoveredObject == pnt || newHoveredObject == pnt.mControl) ? pointSize * 1.5f : pointSize; + + var pos2D = WorldToScreen(pnt.mTranslate); + mDrawList.AddCircleFilled(pos2D, size, rail_point_color, pnt.mIsCurve ? 0:4); + pointsList.Add(pos2D); + + if (point_selected && pnt.mIsCurve) + { + var contPos2D = WorldToScreen(pnt.mControl.mTranslate); + mDrawList.AddLine(pos2D, contPos2D, rail_point_color, thickness); + mDrawList.AddCircleFilled(contPos2D, size, rail_point_color, 4); + } + } } } @@ -1070,7 +1168,7 @@ Vector2[] GetPoints() Vector3 center = new(0f); var drawing = "box"; - if (actor.mActorPack.ShapeParams != null) + if (actor.mActorPack?.ShapeParams != null) { var shapes = actor.mActorPack.ShapeParams; var calc = shapes.mCalc; @@ -1095,7 +1193,7 @@ Vector2[] GetPoints() string layer = actor.mLayer; - if (mLayersVisibility!.TryGetValue(layer, out bool isVisible) && isVisible) + if (mLayersVisibility!.TryGetValue(layer, out bool isVisible) && isVisible && actor.wonderVisible) { Matrix4x4 transform = Matrix4x4.CreateScale(actor.mScale.X, actor.mScale.Y, actor.mScale.Z @@ -1111,7 +1209,8 @@ Vector2[] GetPoints() uint color = ImGui.ColorConvertFloat4ToU32(new(0.5f, 1, 0, 1)); - if (actor.mPackName.Contains("CameraArea") || actor.mActorPack?.Category == "AreaObj") + if (actor.mPackName.Contains("CameraArea") || + (actor.mActorPack?.Category == "AreaObj" && actor.mActorPack?.ShapeParams == null)) { if (actor.mPackName.Contains("CameraArea")) color = ImGui.ColorConvertFloat4ToU32(new(1, 0, 0, 1)); @@ -1172,11 +1271,11 @@ Vector2[] GetPoints() string name = actor.mPackName; - isHovered = HitTestConvexPolygonPoint(s_actorRectPolygon, ImGui.GetMousePos()); + isHovered = MathUtil.HitTestConvexPolygonPoint(s_actorRectPolygon, ImGui.GetMousePos()); if (name.Contains("Area")) { - isHovered = HitTestLineLoopPoint(s_actorRectPolygon, 4f, + isHovered = MathUtil.HitTestLineLoopPoint(s_actorRectPolygon, 4f, ImGui.GetMousePos()); } @@ -1189,61 +1288,6 @@ Vector2[] GetPoints() mHoveredObject = newHoveredObject; } - - /// - /// Does a collision check between a convex polygon and a point - /// - /// Points of Polygon a in Clockwise orientation (in screen coordinates) - /// Point - /// - public static bool HitTestConvexPolygonPoint(ReadOnlySpan polygon, Vector2 point) - { - // separating axis theorem (lite) - // we can view the point as a polygon with 0 sides and 1 point - for (int i = 0; i < polygon.Length; i++) - { - var p1 = polygon[i]; - var p2 = polygon[(i + 1) % polygon.Length]; - var vec = (p2 - p1); - var normal = new Vector2(vec.Y, -vec.X); - - (Vector2 origin, Vector2 normal) edge = (p1, normal); - - if (Vector2.Dot(point - edge.origin, edge.normal) >= 0) - return false; - } - - //no separating axis found -> collision - return true; - } - - /// - /// Does a collision check between a LineLoop and a point - /// - /// Points of a LineLoop - /// Point - /// - public static bool HitTestLineLoopPoint(ReadOnlySpan points, float thickness, Vector2 point) - { - for (int i = 0; i < points.Length; i++) - { - var p1 = points[i]; - var p2 = points[(i + 1) % points.Length]; - if (HitTestPointLine(point, - p1, p2, thickness)) - return true; - } - - return false; - } - - 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; - } } static class ColorExtensions diff --git a/Fushigi/ui/widgets/OperationWarningDialog.cs b/Fushigi/ui/widgets/OperationWarningDialog.cs index f52820ef..a48f31de 100644 --- a/Fushigi/ui/widgets/OperationWarningDialog.cs +++ b/Fushigi/ui/widgets/OperationWarningDialog.cs @@ -43,12 +43,8 @@ public void DrawModalContent(Promise promise) float width = ImGui.GetContentRegionAvail().X; ImGui.SetNextWindowSizeConstraints(new Vector2(width, 0), new Vector2(width, ImGui.GetWindowViewport().WorkSize.Y / 3f)); - ImGui.SetNextWindowPos(ImGui.GetCursorScreenPos()); - ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0)); var cursorPos = ImGui.GetCursorPos(); - if (ImGui.Begin("CategorizedWarnings", - ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoNavFocus)) + if(ImGui.BeginChild("Categorized", Vector2.Zero, ImGuiChildFlags.AutoResizeY)) { var headerStickPosY = 0f; foreach (var (category, warnings) in mCategorizedWarnings) @@ -72,7 +68,7 @@ public void DrawModalContent(Promise promise) if(!expanded) continue; - ImGui.PushClipRect(ImGui.GetCursorScreenPos(), + ImGui.PushClipRect(ImGui.GetCursorScreenPos(), new Vector2(float.PositiveInfinity), true); headerStickPosY = ImGui.GetCursorPosY() - ImGui.GetScrollY(); @@ -87,13 +83,9 @@ public void DrawModalContent(Promise promise) } ImGui.Unindent(); ImGui.PopClipRect(); - ImGui.SetWindowSize(new Vector2(100, 100)); } - cursorPos += ImGui.GetWindowSize() with { X = 0 }; - ImGui.End(); + ImGui.EndChild(); } - ImGui.PopStyleVar(2); - ImGui.SetCursorPos(cursorPos); #endregion diff --git a/Fushigi/ui/widgets/PathSelector.cs b/Fushigi/ui/widgets/PathSelector.cs index 2c7b9ea9..b7ff2556 100644 --- a/Fushigi/ui/widgets/PathSelector.cs +++ b/Fushigi/ui/widgets/PathSelector.cs @@ -10,6 +10,8 @@ internal class PathSelector { public static bool Show(string label, ref string path, bool isValid = true) { + bool edited; + bool clicked; //Ensure path isn't null for imgui if (path == null) path = ""; @@ -18,49 +20,50 @@ public static bool Show(string label, ref string path, bool isValid = true) if (!System.IO.Directory.Exists(path)) isValid = false; - ImGui.Columns(2); + ImGui.BeginTable("path", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.Resizable); + { + ImGui.TableSetupColumn("one", ImGuiTableColumnFlags.WidthFixed, 150.0f); + + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + edited = false; - ImGui.SetColumnWidth(0, 150); + ImGui.Text(label); - bool edited = false; + ImGui.TableNextColumn(); - ImGui.Text(label); + ImGui.PushItemWidth(ImGui.GetColumnWidth() - 100); - ImGui.NextColumn(); + if (!isValid) + { + ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0.5f, 0, 0, 1)); + ImGui.InputText($"##{label}", ref path, 500, ImGuiInputTextFlags.ReadOnly); + ImGui.PopStyleColor(); + } + else + { + ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0, 0.5f, 0, 1)); + ImGui.InputText($"##{label}", ref path, 500, ImGuiInputTextFlags.ReadOnly); + ImGui.PopStyleColor(); + } - ImGui.PushItemWidth(ImGui.GetColumnWidth() - 100); + if (ImGui.BeginPopupContextItem($"{label}_clear", ImGuiPopupFlags.MouseButtonRight)) + { + if (ImGui.MenuItem("Clear")) + { + path = ""; + edited = true; + } + ImGui.EndPopup(); + } - if (!isValid) - { - ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0.5f, 0, 0, 1)); - ImGui.InputText($"##{label}", ref path, 500, ImGuiInputTextFlags.ReadOnly); - ImGui.PopStyleColor(); - } - else - { - ImGui.PushStyleColor(ImGuiCol.FrameBg, new Vector4(0, 0.5f, 0, 1)); - ImGui.InputText($"##{label}", ref path, 500, ImGuiInputTextFlags.ReadOnly); - ImGui.PopStyleColor(); - } + ImGui.PopItemWidth(); - if (ImGui.BeginPopupContextItem($"{label}_clear", ImGuiPopupFlags.MouseButtonRight)) - { - if (ImGui.MenuItem("Clear")) - { - path = ""; - edited = true; - } - ImGui.EndPopup(); - } - - ImGui.PopItemWidth(); + ImGui.SameLine(); + clicked = ImGui.Button($"Select##{label}"); - ImGui.SameLine(); - bool clicked = ImGui.Button($"Select##{label}"); - - ImGui.NextColumn(); - - ImGui.Columns(1); + ImGui.EndTable(); + } if (clicked) { diff --git a/Fushigi/util/BymlUtil.cs b/Fushigi/util/BymlUtil.cs index 2d2a0d56..a4656bca 100644 --- a/Fushigi/util/BymlUtil.cs +++ b/Fushigi/util/BymlUtil.cs @@ -62,6 +62,14 @@ public static System.Numerics.Vector3 GetVector3FromArray(BymlArrayNode? array) vec.Z = GetNodeFromArray(array, 2); return vec; } + public static System.Numerics.Vector3 GetVector3FromHashTable(BymlHashTable? table) + { + System.Numerics.Vector3 vec = new System.Numerics.Vector3(); + vec.X = GetNodeData(table["X"]); + vec.Y = GetNodeData(table["Y"]); + vec.Z = GetNodeData(table["Z"]); + return vec; + } public static object GetValueFromDynamicNode(IBymlNode node, ParamDB.ComponentParam param) { diff --git a/Fushigi/util/MathUtil.cs b/Fushigi/util/MathUtil.cs index 8f5f4561..93bb9151 100644 --- a/Fushigi/util/MathUtil.cs +++ b/Fushigi/util/MathUtil.cs @@ -60,6 +60,66 @@ static float isLeft(Vector2 p0, Vector2 p1, Vector2 point) => } return wn; } + + /// + /// Does a collision check between a convex polygon and a point + /// + /// Points of Polygon a in Clockwise orientation (in screen coordinates) + /// Point + /// + public static bool HitTestConvexPolygonPoint(ReadOnlySpan polygon, Vector2 point) + { + // separating axis theorem (lite) + // we can view the point as a polygon with 0 sides and 1 point + for (int i = 0; i < polygon.Length; i++) + { + var p1 = polygon[i]; + var p2 = polygon[(i + 1) % polygon.Length]; + var vec = (p2 - p1); + var normal = new Vector2(vec.Y, -vec.X); + + (Vector2 origin, Vector2 normal) edge = (p1, normal); + + if (Vector2.Dot(point - edge.origin, edge.normal) >= 0) + return false; + } + + //no separating axis found -> collision + return true; + } + + public static bool HitTestConvexQuad(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, Vector2 point) + { + return HitTestConvexPolygonPoint([p1, p2, p3, p4], point); + } + + /// + /// Does a collision check between a LineLoop and a point + /// + /// Points of a LineLoop + /// Point + /// + public static bool HitTestLineLoopPoint(ReadOnlySpan points, float thickness, Vector2 point) + { + for (int i = 0; i < points.Length; i++) + { + var p1 = points[i]; + var p2 = points[(i + 1) % points.Length]; + if (HitTestPointLine(point, + p1, p2, thickness)) + return true; + } + + return false; + } + + 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; + } } struct BoundingBox2D(Vector2 min, Vector2 max)