diff --git a/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseDown.cs b/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseDown.cs index b27509e92..3cf9270ad 100644 --- a/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseDown.cs +++ b/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseDown.cs @@ -161,19 +161,19 @@ private void OnMouseButtonDownLeft(Point location) { if (ModifierKeys.HasFlag(Keys.Shift)) { - EditorActions.RotateTexture(_editor.SelectedRoom, pos, newSectorPicking.Face); + EditorActions.RotateTexture(_editor.SelectedRoom, pos, new(newSectorPicking.Face, _editor.ActiveTextureLayer)); break; } else if (ModifierKeys.HasFlag(Keys.Control)) { - EditorActions.MirrorTexture(_editor.SelectedRoom, pos, newSectorPicking.Face); + EditorActions.MirrorTexture(_editor.SelectedRoom, pos, new(newSectorPicking.Face, _editor.ActiveTextureLayer)); break; } } if (ModifierKeys.HasFlag(Keys.Alt)) { - EditorActions.PickTexture(_editor.SelectedRoom, pos, newSectorPicking.Face); + EditorActions.PickTexture(_editor.SelectedRoom, pos, new(newSectorPicking.Face, _editor.ActiveTextureLayer)); } else if (_editor.Tool.Tool == EditorToolType.GridPaint && !_editor.HighlightedSectors.Empty) { @@ -212,7 +212,7 @@ private void OnMouseButtonDownLeft(Point location) case EditorToolType.Brush: case EditorToolType.Pencil: - EditorActions.ApplyTexture(_editor.SelectedRoom, pos, newSectorPicking.Face, _editor.SelectedTexture); + EditorActions.ApplyTexture(_editor.SelectedRoom, pos, new(newSectorPicking.Face, _editor.ActiveTextureLayer), _editor.SelectedTexture); _toolHandler.Engage(location.X, location.Y, newSectorPicking, false); break; diff --git a/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseMove.cs b/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseMove.cs index 091fa5cb8..ac5b3b8a7 100644 --- a/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseMove.cs +++ b/TombEditor/Controls/Panel3D/MouseHandler/Panel3DMouseMove.cs @@ -296,7 +296,7 @@ private bool OnMouseMovedLeft(Point location) { if (_editor.SelectedSectors.Valid && _editor.SelectedSectors.Area.Contains(pos) || _editor.SelectedSectors.Empty) - return EditorActions.ApplyTexture(_editor.SelectedRoom, pos, newSectorPicking.Face, _editor.SelectedTexture, true); + return EditorActions.ApplyTexture(_editor.SelectedRoom, pos, new(newSectorPicking.Face, _editor.ActiveTextureLayer), _editor.SelectedTexture, true); } else if (_editor.Tool.Tool == EditorToolType.GridPaint && _toolHandler.Engaged) { diff --git a/TombEditor/Controls/Panel3D/Panel3D.cs b/TombEditor/Controls/Panel3D/Panel3D.cs index 61eca6e9f..d5be2ce3f 100644 --- a/TombEditor/Controls/Panel3D/Panel3D.cs +++ b/TombEditor/Controls/Panel3D/Panel3D.cs @@ -136,6 +136,9 @@ public bool DisablePickingForHiddenRooms private Buffer _flybyPathVertexBuffer; private Buffer _ghostBlockVertexBuffer; private Buffer _boxVertexBuffer; + private Buffer _overlayOutlineVertexBuffer; + private List _overlayOutlineVertices; + private Room _lastOverlayOutlineRoom; // Flyby stuff private const float _flybyPathThickness = 32.0f; @@ -217,6 +220,7 @@ protected override void Dispose(bool disposing) _rasterizerWireframe?.Dispose(); _objectHeightLineVertexBuffer?.Dispose(); _flybyPathVertexBuffer?.Dispose(); + _overlayOutlineVertexBuffer?.Dispose(); _gizmo?.Dispose(); _sphere?.Dispose(); _cone?.Dispose(); @@ -234,6 +238,16 @@ protected override void Dispose(bool disposing) private IReadOnlyList _splitHighlightHotkeys; + private void InvalidateOverlayOutlineCache(Room room = null) + { + if (room is null || _lastOverlayOutlineRoom == room) + { + _lastOverlayOutlineRoom = null; + _overlayOutlineVertexBuffer?.Dispose(); + _overlayOutlineVertexBuffer = null; + } + } + private void EditorEventRaised(IEditorEvent obj) { if (obj is Editor.InitEvent) @@ -276,6 +290,15 @@ obj is Editor.ToolChangedEvent || var room = ((IEditorRoomChangedEvent)obj).Room; _renderingCachedRooms.Remove(room); + + // Invalidate overlay outline cache for geometry or face-related changes + if (obj is Editor.RoomGeometryChangedEvent || + obj is Editor.RoomPositionChangedEvent || + obj is Editor.RoomSectorPropertiesChangedEvent) + { + InvalidateOverlayOutlineCache(room); + } + if (obj is Editor.RoomGeometryChangedEvent || obj is Editor.RoomPositionChangedEvent) foreach (var portal in room.Portals) _renderingCachedRooms.Remove(portal.AdjoiningRoom); @@ -292,10 +315,23 @@ obj is Editor.ToolChangedEvent || if (obj is Editor.SelectedSectorsChangedEvent || obj is Editor.HighlightedSectorChangedEvent) _renderingCachedRooms.Remove(_editor.SelectedRoom); + if (obj is Editor.SelectedRoomChangedEvent) + { _renderingCachedRooms.Remove(((Editor.SelectedRoomChangedEvent)obj).Previous); + + // Overlay outlines are room-specific, so invalidate cache when switching rooms + InvalidateOverlayOutlineCache(); + } + if (obj is Editor.RoomSectorPropertiesChangedEvent) - _renderingCachedRooms.Remove(((Editor.RoomSectorPropertiesChangedEvent)obj).Room); + { + var room = ((Editor.RoomSectorPropertiesChangedEvent)obj).Room; + _renderingCachedRooms.Remove(room); + + InvalidateOverlayOutlineCache(room); + } + if (obj is Editor.LoadedTexturesChangedEvent || obj is Editor.LoadedImportedGeometriesChangedEvent || obj is Editor.LevelChangedEvent || diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index 800ed561c..8a89025d3 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -331,38 +331,38 @@ void HandleDiagonal(int x, int z, SectorSurface surface, int yOffset) if (splitIndex is < 0 or > 7) // QA or WS { // PositiveZ Floor - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_PositiveZ_QA))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_PositiveZ_QA, FaceLayer.Base)))) HandlePositiveZ(x, z, targetSector.Floor, yOffset); // PositiveZ Ceiling - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_PositiveZ_WS))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_PositiveZ_WS, FaceLayer.Base)))) HandlePositiveZ(x, z, targetSector.Ceiling, yOffset); // PositiveX Floor - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_PositiveX_QA))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_PositiveX_QA, FaceLayer.Base)))) HandlePositiveX(x, z, targetSector.Floor, yOffset); // PositiveX Ceiling - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_PositiveX_WS))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_PositiveX_WS, FaceLayer.Base)))) HandlePositiveX(x, z, targetSector.Ceiling, yOffset); // NegativeZ Floor - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_NegativeZ_QA))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_NegativeZ_QA, FaceLayer.Base)))) HandleNegativeZ(x, z, targetSector.Floor, yOffset); // NegativeZ Ceiling - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_NegativeZ_WS))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_NegativeZ_WS, FaceLayer.Base)))) HandleNegativeZ(x, z, targetSector.Ceiling, yOffset); // NegativeX Floor - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_NegativeX_QA))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_NegativeX_QA, FaceLayer.Base)))) HandleNegativeX(x, z, targetSector.Floor, yOffset); // NegativeX Ceiling - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_NegativeX_WS))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_NegativeX_WS, FaceLayer.Base)))) HandleNegativeX(x, z, targetSector.Ceiling, yOffset); // Diagonal Floor - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_Diagonal_QA))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_Diagonal_QA, FaceLayer.Base)))) HandleDiagonal(x, z, targetSector.Floor, yOffset); // Diagonal Ceiling - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFace.Wall_Diagonal_WS))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFace.Wall_Diagonal_WS, FaceLayer.Base)))) HandleDiagonal(x, z, targetSector.Ceiling, yOffset); } else // Actual splits @@ -380,23 +380,23 @@ void HandleDiagonal(int x, int z, SectorSurface surface, int yOffset) }; // PositiveZ - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraFloorSplitFace(Direction.PositiveZ, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraFloorSplitFace(Direction.PositiveZ, splitIndex), FaceLayer.Base)))) HandlePositiveZ(x, z, floorSurface, yOffset); // PositiveX - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraFloorSplitFace(Direction.PositiveX, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraFloorSplitFace(Direction.PositiveX, splitIndex), FaceLayer.Base)))) HandlePositiveX(x, z, floorSurface, yOffset); // NegativeZ - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraFloorSplitFace(Direction.NegativeZ, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraFloorSplitFace(Direction.NegativeZ, splitIndex), FaceLayer.Base)))) HandleNegativeZ(x, z, floorSurface, yOffset); // NegativeX - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraFloorSplitFace(Direction.NegativeX, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraFloorSplitFace(Direction.NegativeX, splitIndex), FaceLayer.Base)))) HandleNegativeX(x, z, floorSurface, yOffset); // Diagonal - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraFloorSplitFace(Direction.Diagonal, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraFloorSplitFace(Direction.Diagonal, splitIndex), FaceLayer.Base)))) HandleDiagonal(x, z, floorSurface, yOffset); } @@ -413,23 +413,23 @@ void HandleDiagonal(int x, int z, SectorSurface surface, int yOffset) }; // PositiveZ - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.PositiveZ, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.PositiveZ, splitIndex), FaceLayer.Base)))) HandlePositiveZ(x, z, ceilingSurface, yOffset); // PositiveX - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.PositiveX, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.PositiveX, splitIndex), FaceLayer.Base)))) HandlePositiveX(x, z, ceilingSurface, yOffset); // NegativeZ - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.NegativeZ, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.NegativeZ, splitIndex), FaceLayer.Base)))) HandleNegativeZ(x, z, ceilingSurface, yOffset); // NegativeX - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.NegativeX, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.NegativeX, splitIndex), FaceLayer.Base)))) HandleNegativeX(x, z, ceilingSurface, yOffset); // Diagonal - if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.Diagonal, splitIndex)))) + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(new(x, z, new(SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.Diagonal, splitIndex), FaceLayer.Base)))) HandleDiagonal(x, z, ceilingSurface, yOffset); } } @@ -449,6 +449,184 @@ void HandleDiagonal(int x, int z, SectorSurface surface, int yOffset) _legacyDevice.Draw(PrimitiveType.TriangleList, buffer.ElementCount); } + private static Vector3 ShiftVector3(Vector3 vector, SectorFace face, DiagonalSplit diagonalSplit, float shift) + { + switch (face.GetDirection()) + { + case Direction.PositiveX: + vector.X += shift; + break; + case Direction.NegativeX: + vector.X -= shift; + break; + case Direction.PositiveZ: + vector.Z += shift; + break; + case Direction.NegativeZ: + vector.Z -= shift; + break; + case Direction.Diagonal: + switch (diagonalSplit) + { + case DiagonalSplit.XpZn: + vector.X -= shift; + vector.Z += shift; + break; + case DiagonalSplit.XnZn: + vector.X += shift; + vector.Z += shift; + break; + case DiagonalSplit.XnZp: + vector.X += shift; + vector.Z -= shift; + break; + case DiagonalSplit.XpZp: + vector.X -= shift; + vector.Z -= shift; + break; + } + + break; + default: // Either Floor or Ceiling + if (face.IsFloor()) + vector.Y += shift; + else + vector.Y -= shift; + break; + } + + return vector; + } + + private void DrawOverlayOutlines(Effect effect) + { + const float OverlayOutlineOffset = 16.0f; + + if (_editor.Mode is not EditorMode.FaceEdit) + return; + + Room currentRoom = _editor.SelectedRoom; + + // Check if we need to regenerate the vertex buffer + bool needsUpdate = _lastOverlayOutlineRoom != currentRoom || _overlayOutlineVertexBuffer is null; + + if (needsUpdate) + { + _overlayOutlineVertices.Clear(); + + // Quick check: Does this room have any overlays at all? + bool hasOverlays = false; + + for (int x = currentRoom.LocalArea.X0; x <= currentRoom.LocalArea.X1 && !hasOverlays; x++) + { + for (int z = currentRoom.LocalArea.Y0; z <= currentRoom.LocalArea.Y1 && !hasOverlays; z++) + { + for (SectorFace face = 0; face < SectorFace.Count; face++) + { + var key = new SectorFaceIdentity(x, z, new FaceLayerInfo(face, FaceLayer.Overlay)); + + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(key)) + { + hasOverlays = true; + break; + } + } + } + } + + if (!hasOverlays) + { + _lastOverlayOutlineRoom = currentRoom; + _overlayOutlineVertexBuffer?.Dispose(); + _overlayOutlineVertexBuffer = null; + return; + } + + // Generate vertices for all overlay outlines in this room + for (int x = currentRoom.LocalArea.X0; x <= currentRoom.LocalArea.X1; x++) + { + for (int z = currentRoom.LocalArea.Y0; z <= currentRoom.LocalArea.Y1; z++) + { + for (SectorFace face = 0; face < SectorFace.Count; face++) + { + Sector sector = currentRoom.Sectors[x, z]; + var key = new SectorFaceIdentity(x, z, new FaceLayerInfo(face, FaceLayer.Overlay)); + + if (currentRoom.RoomGeometry.VertexRangeLookup.ContainsKey(key)) + { + VertexRange range = currentRoom.RoomGeometry.VertexRangeLookup[key]; + DiagonalSplit diagonalSplit = face.IsFloorWall() ? sector.Floor.DiagonalSplit : sector.Ceiling.DiagonalSplit; + + if (range.Count == 3) + { + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 1] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 1] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 2] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 2] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + } + else if (range.Count == 6) + { + if (face.IsCeiling()) + { + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 2] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 2] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 3] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 3] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 5] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 5] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + } + else + { + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 1] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 1] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 3] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 3] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 2] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start + 2] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + _overlayOutlineVertices.Add(new SolidVertex(ShiftVector3(currentRoom.RoomGeometry.VertexPositions[range.Start] + currentRoom.WorldPos, face, diagonalSplit, OverlayOutlineOffset))); + } + } + } + } + } + } + + // Create/update the vertex buffer + _overlayOutlineVertexBuffer?.Dispose(); + + _overlayOutlineVertexBuffer = _overlayOutlineVertices.Count > 0 + ? SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, _overlayOutlineVertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Immutable) + : null; + + _lastOverlayOutlineRoom = currentRoom; + } + + // Draw using the cached buffer + if (_overlayOutlineVertexBuffer is not null) + { + _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.Default); + _legacyDevice.SetVertexBuffer(_overlayOutlineVertexBuffer); + _legacyDevice.SetVertexInputLayout(VertexInputLayout.FromBuffer(0, _overlayOutlineVertexBuffer)); + effect.Parameters["ModelViewProjection"].SetValue(_viewProjection.ToSharpDX()); + effect.Parameters["Color"].SetValue(Vector4.One); + effect.CurrentTechnique.Passes[0].Apply(); + _legacyDevice.Draw(PrimitiveType.LineList, _overlayOutlineVertexBuffer.ElementCount); + } + } + private void DrawLights(Effect effect, Room[] roomsWhoseObjectsToDraw, List textToDraw, List sprites) { _legacyDevice.SetRasterizerState(_rasterizerWireframe); @@ -1988,6 +2166,8 @@ private void DrawScene() DrawFlybyPath(effect); // Draw sector split highlights DrawSectorSplitHighlights(effect); + // Draw overlay outlines + DrawOverlayOutlines(effect); } // Draw ghost block cubes diff --git a/TombEditor/Controls/Panel3D/Panel3DInit.cs b/TombEditor/Controls/Panel3D/Panel3DInit.cs index a94d59518..c9ecc6683 100644 --- a/TombEditor/Controls/Panel3D/Panel3DInit.cs +++ b/TombEditor/Controls/Panel3D/Panel3DInit.cs @@ -1,4 +1,5 @@ using SharpDX.Toolkit.Graphics; +using System.Collections.Generic; using System.Numerics; using TombLib.Graphics.Primitives; using TombLib.Graphics; @@ -58,6 +59,10 @@ public override void InitializeRendering(RenderingDevice device, bool antialias, _ghostBlockVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, 84); _boxVertexBuffer = new BoundingBox(new Vector3(-_littleCubeRadius), new Vector3(_littleCubeRadius)).GetVertexBuffer(_legacyDevice); + // Initialize overlay outline cache + _overlayOutlineVertices = new List(); + _lastOverlayOutlineRoom = null; + // Maybe I could use this as bounding box, scaling it properly before drawing _linesCube = GeometricPrimitive.LinesCube.New(_legacyDevice, 128, 128, 128); diff --git a/TombEditor/Controls/ToolBox.Designer.cs b/TombEditor/Controls/ToolBox.Designer.cs index 96fb1e5e4..5e07095de 100644 --- a/TombEditor/Controls/ToolBox.Designer.cs +++ b/TombEditor/Controls/ToolBox.Designer.cs @@ -51,6 +51,8 @@ private void InitializeComponent() this.toolSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.toolPortalDigger = new System.Windows.Forms.ToolStripButton(); this.toolUVFixer = new System.Windows.Forms.ToolStripButton(); + this.toolSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.toolOverlayMode = new System.Windows.Forms.ToolStripButton(); this.toolStrip.SuspendLayout(); this.SuspendLayout(); // @@ -84,12 +86,14 @@ private void InitializeComponent() this.toolInvisibility, this.toolSeparator2, this.toolPortalDigger, - this.toolUVFixer}); + this.toolUVFixer, + this.toolSeparator3, + this.toolOverlayMode}); this.toolStrip.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.VerticalStackWithOverflow; this.toolStrip.Location = new System.Drawing.Point(0, 0); this.toolStrip.Name = "toolStrip"; this.toolStrip.Padding = new System.Windows.Forms.Padding(1, 0, 1, 0); - this.toolStrip.Size = new System.Drawing.Size(28, 453); + this.toolStrip.Size = new System.Drawing.Size(28, 523); this.toolStrip.TabIndex = 3; // // toolSelection @@ -367,19 +371,39 @@ private void InitializeComponent() this.toolUVFixer.Margin = new System.Windows.Forms.Padding(1); this.toolUVFixer.Name = "toolUVFixer"; this.toolUVFixer.Size = new System.Drawing.Size(23, 20); - this.toolUVFixer.Text = "toolStripButton1"; this.toolUVFixer.ToolTipText = "Fix texture coordinates"; this.toolUVFixer.Click += new System.EventHandler(this.toolUVFixer_Click); // + // toolSeparator3 + // + this.toolSeparator3.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65))))); + this.toolSeparator3.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.toolSeparator3.Margin = new System.Windows.Forms.Padding(0, 0, 2, 0); + this.toolSeparator3.Name = "toolSeparator3"; + this.toolSeparator3.Size = new System.Drawing.Size(23, 6); + // + // toolOverlayMode + // + this.toolOverlayMode.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(63)))), ((int)(((byte)(65))))); + this.toolOverlayMode.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.toolOverlayMode.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); + this.toolOverlayMode.Image = global::TombEditor.Properties.Resources.general_layers_16; + this.toolOverlayMode.ImageTransparentColor = System.Drawing.Color.Magenta; + this.toolOverlayMode.Margin = new System.Windows.Forms.Padding(1); + this.toolOverlayMode.Name = "toolOverlayMode"; + this.toolOverlayMode.Size = new System.Drawing.Size(23, 20); + this.toolOverlayMode.ToolTipText = "Toggle overlay mode"; + this.toolOverlayMode.Click += new System.EventHandler(this.toolOverlayMode_Click); + // // ToolBox // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.Controls.Add(this.toolStrip); this.Margin = new System.Windows.Forms.Padding(0); this.Name = "ToolBox"; - this.Size = new System.Drawing.Size(28, 453); + this.Size = new System.Drawing.Size(28, 523); this.toolStrip.ResumeLayout(false); this.toolStrip.PerformLayout(); this.ResumeLayout(false); @@ -411,5 +435,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripButton toolUVFixer; private System.Windows.Forms.ToolStripButton toolGridPaint; private System.Windows.Forms.ToolStripButton toolPortalDigger; + private System.Windows.Forms.ToolStripSeparator toolSeparator3; + private System.Windows.Forms.ToolStripButton toolOverlayMode; } } diff --git a/TombEditor/Controls/ToolBox.cs b/TombEditor/Controls/ToolBox.cs index 149f2adad..1aab009bf 100644 --- a/TombEditor/Controls/ToolBox.cs +++ b/TombEditor/Controls/ToolBox.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Windows.Forms; using TombEditor.Controls.ContextMenus; +using TombLib.LevelData.SectorEnums; using TombLib.Utils; namespace TombEditor.Controls @@ -108,6 +109,8 @@ private void EditorEventRaised(IEditorEvent obj) toolEraser.Visible = !geometryMode; toolInvisibility.Visible = !geometryMode; toolUVFixer.Visible = !geometryMode; + toolSeparator3.Visible = !geometryMode; + toolOverlayMode.Visible = !geometryMode; toolFlatten.Visible = geometryMode; toolShovel.Visible = geometryMode; toolSmooth.Visible = geometryMode; @@ -252,5 +255,16 @@ private void toolGridPaint_MouseDown(object sender, MouseEventArgs e) else ContextMenuTimer_Tick(sender, e); } + + private void toolOverlayMode_Click(object sender, EventArgs e) + { + _editor.ActiveTextureLayer = _editor.ActiveTextureLayer switch + { + FaceLayer.Base => FaceLayer.Overlay, + _ => FaceLayer.Base + }; + + toolOverlayMode.Checked = _editor.ActiveTextureLayer == FaceLayer.Overlay; + } } } diff --git a/TombEditor/Editor.cs b/TombEditor/Editor.cs index f35d8eaa3..a6983da15 100644 --- a/TombEditor/Editor.cs +++ b/TombEditor/Editor.cs @@ -9,6 +9,7 @@ using TombLib.Forms; using TombLib.LevelData; using TombLib.LevelData.IO; +using TombLib.LevelData.SectorEnums; using TombLib.Rendering; using TombLib.Utils; using TombLib.Wad.Catalog; @@ -1449,5 +1450,7 @@ public bool IsPreciseGeometryAllowed => Level.Settings.GameVersion is TRVersion.Game.TombEngine || Configuration.Editor_EnableStepHeightControlsForUnsupportedEngines; public int IncrementReference => IsPreciseGeometryAllowed ? Configuration.Editor_StepHeight : Level.FullClickHeight; + + public FaceLayer ActiveTextureLayer { get; set; } } } diff --git a/TombEditor/EditorActions.cs b/TombEditor/EditorActions.cs index b717a9c92..9d5d5d1b4 100644 --- a/TombEditor/EditorActions.cs +++ b/TombEditor/EditorActions.cs @@ -26,6 +26,7 @@ using TombLib.LevelData.SectorStructs; using TombLib.LevelData.VisualScripting; using TombLib.Rendering; +using TombLib.Results; using TombLib.Utils; using TombLib.Wad; using TombLib.Wad.Catalog; @@ -1419,38 +1420,54 @@ public static void ReplaceEventSetNames(List list, string oldName, str } } - public static void RotateTexture(Room room, VectorInt2 pos, SectorFace face) + public static void RotateTexture(Room room, VectorInt2 pos, FaceLayerInfo face) { _editor.UndoManager.PushGeometryChanged(_editor.SelectedRoom); Sector sector = room.GetSector(pos); TextureArea newTexture = sector.GetFaceTexture(face); - bool isTriangle = room.GetFaceShape(pos.X, pos.Y, face) == FaceShape.Triangle; + bool isTriangle = room.GetFaceShape(pos.X, pos.Y, face.Face) == FaceShape.Triangle; newTexture.Rotate(1, isTriangle); sector.SetFaceTexture(face, newTexture); // Update state - room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, newTexture, newTexture.DoubleSided); - _editor.RoomTextureChange(room); + bool updated = room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, newTexture, newTexture.DoubleSided); + + if (!updated && face.Layer is FaceLayer.Overlay) + { + room.RoomGeometry.Build(room, _editor.Configuration.Rendering3D_HighQualityLightPreview); + updated = true; + } + + if (updated) + _editor.RoomTextureChange(room); } - public static void MirrorTexture(Room room, VectorInt2 pos, SectorFace face) + public static void MirrorTexture(Room room, VectorInt2 pos, FaceLayerInfo face) { _editor.UndoManager.PushGeometryChanged(_editor.SelectedRoom); Sector sector = room.GetSector(pos); TextureArea newTexture = sector.GetFaceTexture(face); - newTexture.Mirror(room.GetFaceShape(pos.X, pos.Y, face) == FaceShape.Triangle); + newTexture.Mirror(room.GetFaceShape(pos.X, pos.Y, face.Face) == FaceShape.Triangle); sector.SetFaceTexture(face, newTexture); // Update state - room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, newTexture, newTexture.DoubleSided); - _editor.RoomTextureChange(room); + bool updated = room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, newTexture, newTexture.DoubleSided); + + if (!updated && face.Layer is FaceLayer.Overlay) + { + room.RoomGeometry.Build(room, _editor.Configuration.Rendering3D_HighQualityLightPreview); + updated = true; + } + + if (updated) + _editor.RoomTextureChange(room); } - public static void PickTexture(Room room, VectorInt2 pos, SectorFace face) + public static void PickTexture(Room room, VectorInt2 pos, FaceLayerInfo face) { var area = room.GetSector(pos).GetFaceTexture(face); @@ -1478,7 +1495,7 @@ public static void PickTexture(Room room, VectorInt2 pos, SectorFace face) area.DoubleSided = _editor.SelectedTexture.DoubleSided; } - if (face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) + if (face.Face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) area.Mirror(area.TextureIsTriangle); _editor.SelectTextureAndCenterView(area.RestoreQuad()); @@ -1499,7 +1516,8 @@ public static List> FindTextures(TextureSearchTyp var sector = room.GetSectorTry(x, z); if (sector == null) continue; - foreach (var face in Enum.GetValues(typeof(SectorFace)).Cast()) + for (SectorFace face = 0; face < SectorFace.Count; face++) + for (FaceLayer layer = 0; layer < FaceLayer.Count; layer++) { // Filter out impossible combinations right away if (face.IsNonWall() && sector.IsAnyWall) continue; @@ -1509,7 +1527,7 @@ public static List> FindTextures(TextureSearchTyp // Filter out undefined faces if (!room.IsFaceDefined(x, z, face)) continue; - var tex = sector.GetFaceTexture(face); + var tex = sector.GetFaceTexture(new FaceLayerInfo(face, layer)); var entry = new KeyValuePair(room, new VectorInt2(x, z)); switch (type) @@ -1616,7 +1634,7 @@ private static void CheckTextureAttributes(Room room, VectorInt2 pos, SectorFace private static bool _textureAtrributeMessageState = false; private static int _textureAttributeMessageCount = 0; - private static bool ApplyTextureToFace(Room room, VectorInt2 pos, SectorFace face, TextureArea texture, bool autocorrectCeiling = true) + private static ApplyTextureResult ApplyTextureToFace(Room room, VectorInt2 pos, FaceLayerInfo face, TextureArea texture, bool autocorrectCeiling = true) { if (_editor.Configuration.UI_AutoSwitchRoomToOutsideOnAppliedInvisibleTexture && !room.Properties.FlagHorizon && texture.TextureIsInvisible) @@ -1626,22 +1644,22 @@ private static bool ApplyTextureToFace(Room room, VectorInt2 pos, SectorFace fac } Sector sector = room.GetSector(pos); - FaceShape shape = room.GetFaceShape(pos.X, pos.Y, face); + FaceShape shape = room.GetFaceShape(pos.X, pos.Y, face.Face); bool wasDoubleSided = sector.GetFaceTexture(face).DoubleSided; - bool textureApplied = false; + bool textureApplied, needsGeometryRebuild = false; // FIXME: Do we really need that now, when TextureOutOfBounds function was fixed? texture.ClampToBounds(); // HACK: Ceiling vertex order is hardly messed up, we need to do some transforms. - if (autocorrectCeiling && face.IsCeiling()) texture.Mirror(); + if (autocorrectCeiling && face.Face.IsCeiling()) texture.Mirror(); if (!_editor.Tool.TextureUVFixer || (shape == FaceShape.Triangle && texture.TextureIsTriangle)) { if (shape == FaceShape.Triangle) { - if (face.IsCeiling()) + if (face.Face.IsCeiling()) texture.Rotate(3); // WTF? But it works! texture.TexCoord3 = texture.TexCoord2; } @@ -1651,19 +1669,23 @@ private static bool ApplyTextureToFace(Room room, VectorInt2 pos, SectorFace fac if (textureApplied) { TextureArea currentTexture = sector.GetFaceTexture(face); - CheckTextureAttributes(room, pos, face, currentTexture); - room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, currentTexture, wasDoubleSided); + CheckTextureAttributes(room, pos, face.Face, currentTexture); + + bool updated = room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, currentTexture, wasDoubleSided); + + if (!updated && face.Layer is FaceLayer.Overlay) + needsGeometryRebuild = true; } - return textureApplied; + return new(textureApplied, needsGeometryRebuild); } TextureArea processedTexture = texture; - switch (face) + switch (face.Face) { case SectorFace.Floor: case SectorFace.Ceiling: - SectorSurface surface = face == SectorFace.Floor ? sector.Floor : sector.Ceiling; + SectorSurface surface = face.Face == SectorFace.Floor ? sector.Floor : sector.Ceiling; if (shape == FaceShape.Quad) break; if (surface.DiagonalSplit != DiagonalSplit.XnZn && @@ -1687,7 +1709,7 @@ private static bool ApplyTextureToFace(Room room, VectorInt2 pos, SectorFace fac case SectorFace.Floor_Triangle2: case SectorFace.Ceiling_Triangle2: - SectorSurface surface2 = face == SectorFace.Floor_Triangle2 ? sector.Floor : sector.Ceiling; + SectorSurface surface2 = face.Face == SectorFace.Floor_Triangle2 ? sector.Floor : sector.Ceiling; if (shape == FaceShape.Quad) break; if (surface2.DiagonalSplit == DiagonalSplit.XnZn || @@ -1714,8 +1736,8 @@ private static bool ApplyTextureToFace(Room room, VectorInt2 pos, SectorFace fac { // Get current face VertexRange vertexRange = new VertexRange(0, 0); - if (!room.RoomGeometry.VertexRangeLookup.TryGetValue(new SectorFaceIdentity(pos.X, pos.Y, face), out vertexRange)) - return false; + if (!room.RoomGeometry.VertexRangeLookup.TryGetValue(new SectorFaceIdentity(pos.X, pos.Y, new(face.Face, FaceLayer.Base)), out vertexRange)) + return ApplyTextureResult.NoChange; if (vertexRange.Count == 6) { @@ -1832,26 +1854,36 @@ private static bool ApplyTextureToFace(Room room, VectorInt2 pos, SectorFace fac if (textureApplied) { TextureArea currentTexture = sector.GetFaceTexture(face); - CheckTextureAttributes(room, pos, face, currentTexture); - room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, currentTexture, wasDoubleSided); + CheckTextureAttributes(room, pos, face.Face, currentTexture); + + bool updated = room.RoomGeometry.UpdateFaceTexture(pos.X, pos.Y, face, currentTexture, wasDoubleSided); + + if (!updated && face.Layer is FaceLayer.Overlay) + needsGeometryRebuild = true; } - return textureApplied; + return new(textureApplied, needsGeometryRebuild); } - public static bool ApplyTexture(Room room, VectorInt2 pos, SectorFace face, TextureArea texture, bool disableUndo = false) + public static bool ApplyTexture(Room room, VectorInt2 pos, FaceLayerInfo face, TextureArea texture, bool disableUndo = false) { if(!disableUndo) _editor.UndoManager.PushGeometryChanged(_editor.SelectedRoom); texture.ParentArea = new Rectangle2(); - bool textureApplied = ApplyTextureToFace(room, pos, face, texture); + ApplyTextureResult result = ApplyTextureToFace(room, pos, face, texture); - if (textureApplied) + if (result.NeedsGeometryRebuild) + { + room.RoomGeometry.Build(room, _editor.Configuration.Rendering3D_HighQualityLightPreview); + _editor.RoomGeometryChange(room); + } + + if (result.Success) _editor.RoomTextureChange(room); - return textureApplied; + return result.Success; } public static Dictionary GetFaces(Room room, VectorInt2 pos, Direction direction, SectorFaceType section) @@ -2082,7 +2114,7 @@ private static float[] GetAreaExtremums(Room room, RectangleInt2 area, Direction return new float[2] { minHeight, maxHeight }; } - public static void TexturizeWallSection(Room room, VectorInt2 pos, Direction direction, SectorFaceType section, TextureArea texture, int subdivisions = 0, int iteration = 0, float[] overrideHeights = null) + public static ApplyTextureResult TexturizeWallSection(Room room, VectorInt2 pos, Direction direction, SectorFaceType section, TextureArea texture, int subdivisions = 0, int iteration = 0, float[] overrideHeights = null) { if (subdivisions < 0 || iteration < 0) subdivisions = 0; @@ -2114,6 +2146,10 @@ public static void TexturizeWallSection(Room room, VectorInt2 pos, Direction dir float sectionHeight = maxSectionHeight - minSectionHeight; bool inverted = false; + bool anyTextureApplied = false; + bool needsGeometryRebuild = false; + ApplyTextureResult result; + foreach (var segment in segments) { float currentHighestPoint = Math.Abs(segment.Value[0] - maxSectionHeight) / sectionHeight; @@ -2171,8 +2207,12 @@ public static void TexturizeWallSection(Room room, VectorInt2 pos, Direction dir } } - ApplyTextureToFace(room, pos, segment.Key, processedTexture); + result = ApplyTextureToFace(room, pos, new FaceLayerInfo(segment.Key, _editor.ActiveTextureLayer), processedTexture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; } + + return new(anyTextureApplied, needsGeometryRebuild); } public static void TexturizeGroup(Room room, SectorSelection selection, SectorSelection workArea, TextureArea texture, SectorFace pickedFace, bool subdivideWalls, bool unifyHeight, bool disableUndo = false) @@ -2183,6 +2223,10 @@ public static void TexturizeGroup(Room room, SectorSelection selection, SectorSe if (pickedFace.IsCeiling()) texture.Mirror(); RectangleInt2 area = selection != SectorSelection.None ? selection.Area : _editor.SelectedRoom.LocalArea; + bool anyTextureApplied = false; + bool needsGeometryRebuild = false; + ApplyTextureResult result; + if (pickedFace.IsWall()) { int xSubs = subdivideWalls ? 0 : area.X1 - area.X0; @@ -2201,16 +2245,22 @@ public static void TexturizeGroup(Room room, SectorSelection selection, SectorSe { case Direction.PositiveZ: case Direction.NegativeZ: - TexturizeWallSection(room, new VectorInt2(x, z), direction, faceType, texture, xSubs, iterX, unifyHeight ? GetAreaExtremums(room, area, direction, faceType) : null); + result = TexturizeWallSection(room, new VectorInt2(x, z), direction, faceType, texture, xSubs, iterX, unifyHeight ? GetAreaExtremums(room, area, direction, faceType) : null); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; break; case Direction.PositiveX: case Direction.NegativeX: - TexturizeWallSection(room, new VectorInt2(x, z), direction, faceType, texture, zSubs, iterZ, unifyHeight ? GetAreaExtremums(room, area, direction, faceType) : null); + result = TexturizeWallSection(room, new VectorInt2(x, z), direction, faceType, texture, zSubs, iterZ, unifyHeight ? GetAreaExtremums(room, area, direction, faceType) : null); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; break; case Direction.Diagonal: - TexturizeWallSection(room, new VectorInt2(x, z), Direction.Diagonal, faceType, texture); + result = TexturizeWallSection(room, new VectorInt2(x, z), Direction.Diagonal, faceType, texture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; break; } } @@ -2252,21 +2302,38 @@ public static void TexturizeGroup(Room room, SectorSelection selection, SectorSe { case SectorFace.Floor: case SectorFace.Floor_Triangle2: - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Floor, currentTexture); - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Floor_Triangle2, currentTexture); + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Floor, _editor.ActiveTextureLayer), currentTexture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; + + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Floor_Triangle2, _editor.ActiveTextureLayer), currentTexture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; break; case SectorFace.Ceiling: case SectorFace.Ceiling_Triangle2: - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Ceiling, currentTexture, false); - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Ceiling_Triangle2, currentTexture, false); + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Ceiling, _editor.ActiveTextureLayer), currentTexture, false); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; + + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Ceiling_Triangle2, _editor.ActiveTextureLayer), currentTexture, false); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; break; } } } } - _editor.RoomTextureChange(room); + if (needsGeometryRebuild) + { + room.RoomGeometry.Build(room, _editor.Configuration.Rendering3D_HighQualityLightPreview); + _editor.RoomGeometryChange(room); + } + + if (anyTextureApplied) + _editor.RoomTextureChange(room); } public static void TexturizeAll(Room room, SectorSelection selection, TextureArea texture, SectorFaceType type) @@ -2278,6 +2345,11 @@ public static void TexturizeAll(Room room, SectorSelection selection, TextureAre texture.ParentArea = new Rectangle2(); + // Track if we need to rebuild geometry for overlays at the end + bool anyTextureApplied = false; + bool needsGeometryRebuild = false; + ApplyTextureResult result; + for (int x = area.X0; x <= area.X1; x++) for (int z = area.Y0; z <= area.Y1; z++) { @@ -2286,29 +2358,50 @@ public static void TexturizeAll(Room room, SectorSelection selection, TextureAre case SectorFaceType.Floor: if (!room.Sectors[x, z].IsFullWall) { - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Floor, texture); - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Floor_Triangle2, texture); + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Floor, _editor.ActiveTextureLayer), texture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; + + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Floor_Triangle2, _editor.ActiveTextureLayer), texture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; } break; case SectorFaceType.Ceiling: if (!room.Sectors[x, z].IsFullWall) { - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Ceiling, texture); - ApplyTextureToFace(room, new VectorInt2(x, z), SectorFace.Ceiling_Triangle2, texture); + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Ceiling, _editor.ActiveTextureLayer), texture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; + + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(SectorFace.Ceiling_Triangle2, _editor.ActiveTextureLayer), texture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; } break; case SectorFaceType.Wall: foreach (SectorFace face in SectorFaceExtensions.GetWalls()) if (room.IsFaceDefined(x, z, face)) - ApplyTextureToFace(room, new VectorInt2(x, z), face, texture); + { + result = ApplyTextureToFace(room, new VectorInt2(x, z), new FaceLayerInfo(face, _editor.ActiveTextureLayer), texture); + anyTextureApplied |= result.Success; + needsGeometryRebuild |= result.NeedsGeometryRebuild; + } break; } } - _editor.RoomTextureChange(room); + if (needsGeometryRebuild) + { + room.RoomGeometry.Build(room, _editor.Configuration.Rendering3D_HighQualityLightPreview); + _editor.RoomGeometryChange(room); + } + + if (anyTextureApplied) + _editor.RoomTextureChange(room); } private static void AllocateScriptIds(PositionBasedObjectInstance instance) @@ -3730,9 +3823,9 @@ public static void MergeRoomsHorizontally(IEnumerable rooms, IWin32Window Sector newSector = sector.Value.GetSector(newSectorVec).Clone(); // Preserve outer wall textures - foreach (SectorFace face in oldSector.GetFaceTextures().Keys.Union(newSector.GetFaceTextures().Keys)) + foreach (FaceLayerInfo face in oldSector.GetAllFaceTextures().Keys.Union(newSector.GetAllFaceTextures().Keys)) { - var direction = face.GetDirection(); + var direction = face.Face.GetDirection(); if (direction == Direction.NegativeX || direction == Direction.PositiveX || direction == Direction.NegativeZ || direction == Direction.PositiveZ) newSector.SetFaceTexture(face, oldSector.GetFaceTexture(face)); } @@ -3761,22 +3854,24 @@ public static void MergeRoomsHorizontally(IEnumerable rooms, IWin32Window // Copy adjacent outer wall textures // Unfortunately they are always on the adjacent sector, so they need extra handling - for (SectorFace face = 0; face < SectorFace.Count; ++face) + for (SectorFace face = 0; face < SectorFace.Count; face++) + for (FaceLayer layer = 0; layer < FaceLayer.Count; layer++) { - var direction = face.GetDirection(); - switch (direction) + var faceLayer = new FaceLayerInfo(face, layer); + + switch (face.GetDirection()) { case Direction.NegativeX: - thisSectorPositiveX.SetFaceTexture(face, otherSectorPositiveX.GetFaceTexture(face)); + thisSectorPositiveX.SetFaceTexture(faceLayer, otherSectorPositiveX.GetFaceTexture(faceLayer)); break; case Direction.PositiveX: - thisSectorNegativeX.SetFaceTexture(face, otherSectorNegativeX.GetFaceTexture(face)); + thisSectorNegativeX.SetFaceTexture(faceLayer, otherSectorNegativeX.GetFaceTexture(faceLayer)); break; case Direction.NegativeZ: - thisSectorPositiveZ.SetFaceTexture(face, otherSectorPositiveZ.GetFaceTexture(face)); + thisSectorPositiveZ.SetFaceTexture(faceLayer, otherSectorPositiveZ.GetFaceTexture(faceLayer)); break; case Direction.PositiveZ: - thisSectorNegativeZ.SetFaceTexture(face, otherSectorNegativeZ.GetFaceTexture(face)); + thisSectorNegativeZ.SetFaceTexture(faceLayer, otherSectorNegativeZ.GetFaceTexture(faceLayer)); break; } } diff --git a/TombEditor/Forms/FormTextureRemap.cs b/TombEditor/Forms/FormTextureRemap.cs index b46774aac..88414607f 100644 --- a/TombEditor/Forms/FormTextureRemap.cs +++ b/TombEditor/Forms/FormTextureRemap.cs @@ -9,6 +9,7 @@ using TombLib; using TombLib.LevelData; using TombLib.LevelData.SectorEnums; +using TombLib.LevelData.SectorStructs; using TombLib.Utils; namespace TombEditor.Forms @@ -153,7 +154,7 @@ private void butOk_Click(object sender, EventArgs e) int roomTextureCount = 0; foreach (Room room in relevantRooms) foreach (Sector sector in room.Sectors) - foreach (SectorFace face in sector.GetFaceTextures().Keys) + foreach (FaceLayerInfo face in sector.GetAllFaceTextures().Keys) { var currentTextureArea = sector.GetFaceTexture(face); if (currentTextureArea.Texture == sourceTexture && diff --git a/TombEditor/Properties/Resources.Designer.cs b/TombEditor/Properties/Resources.Designer.cs index f53d05977..19d7a6bbe 100644 --- a/TombEditor/Properties/Resources.Designer.cs +++ b/TombEditor/Properties/Resources.Designer.cs @@ -530,6 +530,16 @@ internal static System.Drawing.Bitmap general_Import_16 { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap general_layers_16 { + get { + object obj = ResourceManager.GetObject("general_layers_16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/TombEditor/Properties/Resources.resx b/TombEditor/Properties/Resources.resx index a2a35232f..3bc505d81 100644 --- a/TombEditor/Properties/Resources.resx +++ b/TombEditor/Properties/Resources.resx @@ -562,4 +562,7 @@ ..\Resources\icons_texture\texture_MirrorPortal-16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\icons_general\general_layers-16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/TombEditor/Resources/icons_general/general_layers-16.png b/TombEditor/Resources/icons_general/general_layers-16.png new file mode 100644 index 000000000..82aabb559 Binary files /dev/null and b/TombEditor/Resources/icons_general/general_layers-16.png differ diff --git a/TombEditor/SectorsClipboardData.cs b/TombEditor/SectorsClipboardData.cs index 63038ea1e..e3464e87f 100644 --- a/TombEditor/SectorsClipboardData.cs +++ b/TombEditor/SectorsClipboardData.cs @@ -6,6 +6,7 @@ using TombLib; using TombLib.LevelData; using TombLib.LevelData.SectorEnums; +using TombLib.LevelData.SectorStructs; using TombLib.Utils; namespace TombEditor @@ -58,12 +59,13 @@ public SectorsClipboardData(Editor editor) writer.Write((byte)b.Ceiling.DiagonalSplit); writer.Write((short)b.Flags); - Dictionary textures = b.GetFaceTextures(); + Dictionary textures = b.GetAllFaceTextures(); writer.Write(textures.Count); - foreach (KeyValuePair texturePair in textures) + foreach (KeyValuePair texturePair in textures) { - writer.Write((byte)texturePair.Key); + writer.Write((byte)texturePair.Key.Face); + writer.Write((byte)texturePair.Key.Layer); writer.Write(texturePair.Value.Texture.Image.FileName); writer.Write(texturePair.Value.TextureIsInvisible); @@ -136,13 +138,15 @@ public SectorsClipboardData(Editor editor) for (int i = 0; i < texturesCount; i++) { var face = (SectorFace)reader.ReadByte(); + var layer = (FaceLayer)reader.ReadByte(); + var layerInfo = new FaceLayerInfo(face, layer); string textureFileName = reader.ReadString(); bool isInvisible = reader.ReadBoolean(); if (string.IsNullOrEmpty(textureFileName)) { - b.SetFaceTexture(face, isInvisible ? TextureArea.Invisible : TextureArea.None); + b.SetFaceTexture(layerInfo, isInvisible ? TextureArea.Invisible : TextureArea.None); continue; } @@ -165,7 +169,7 @@ public SectorsClipboardData(Editor editor) DoubleSided = reader.ReadBoolean() }; - b.SetFaceTexture(face, texture); + b.SetFaceTexture(layerInfo, texture); } sectors[x, z] = b; diff --git a/TombLib/TombLib.Rendering/Rendering/DirectX11/Dx11RenderingDrawingRoom.cs b/TombLib/TombLib.Rendering/Rendering/DirectX11/Dx11RenderingDrawingRoom.cs index 765eed3bb..86deec728 100644 --- a/TombLib/TombLib.Rendering/Rendering/DirectX11/Dx11RenderingDrawingRoom.cs +++ b/TombLib/TombLib.Rendering/Rendering/DirectX11/Dx11RenderingDrawingRoom.cs @@ -61,7 +61,7 @@ public unsafe Dx11RenderingDrawingRoom(Dx11RenderingDevice device, Description d editorUVAndSectorTexture[i] = editorUv; } { - SectorFaceIdentity lastFaceIdentity = new SectorFaceIdentity(-1, -1, SectorFace.Floor); + SectorFaceIdentity lastFaceIdentity = new SectorFaceIdentity(-1, -1, new FaceLayerInfo(SectorFace.Floor, FaceLayer.Base)); uint lastSectorTexture = 0; uint overlay = 0; for (int i = 0, triangleCount = singleSidedVertexCount / 3; i < triangleCount; ++i) @@ -69,7 +69,7 @@ public unsafe Dx11RenderingDrawingRoom(Dx11RenderingDevice device, Description d SectorFaceIdentity currentFaceIdentity = roomGeometry.TriangleSectorInfo[i]; if (!lastFaceIdentity.Equals(currentFaceIdentity)) { - SectorTextureResult result = description.SectorTextureGet(description.Room, currentFaceIdentity.Position.X, currentFaceIdentity.Position.Y, currentFaceIdentity.Face); + SectorTextureResult result = description.SectorTextureGet(description.Room, currentFaceIdentity.Position.X, currentFaceIdentity.Position.Y, currentFaceIdentity.Face.Face); lastFaceIdentity = currentFaceIdentity; lastSectorTexture = 0; diff --git a/TombLib/TombLib/GeometryIO/RoomGeometryExporter.cs b/TombLib/TombLib/GeometryIO/RoomGeometryExporter.cs index ea701e662..ebc6c9053 100644 --- a/TombLib/TombLib/GeometryIO/RoomGeometryExporter.cs +++ b/TombLib/TombLib/GeometryIO/RoomGeometryExporter.cs @@ -105,7 +105,7 @@ public static RoomExportResult ExportRooms(IEnumerable roomsToExport, stri for (int z = 0; z < room.NumZSectors; z++) { var sector = room.GetSector(new VectorInt2(x, z)); - foreach (SectorFace face in sector.GetFaceTextures().Keys) + foreach (FaceLayerInfo face in sector.GetAllFaceTextures().Keys) { var faceTexture = sector.GetFaceTexture(face); if (faceTexture.TextureIsInvisible || faceTexture.TextureIsUnavailable || faceTexture.Texture == null) @@ -179,7 +179,7 @@ public static RoomExportResult ExportRooms(IEnumerable roomsToExport, stri { var sector = room.GetSector(new VectorInt2(x, z)); - foreach (SectorFace face in sector.GetFaceTextures().Keys) + foreach (FaceLayerInfo face in sector.GetAllFaceTextures().Keys) { var faceTexture = sector.GetFaceTexture(face); @@ -187,7 +187,7 @@ public static RoomExportResult ExportRooms(IEnumerable roomsToExport, stri continue; var range = room.RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)); - var shape = room.GetFaceShape(x, z, face); + var shape = room.GetFaceShape(x, z, face.Face); if (shape == FaceShape.Quad) { @@ -218,7 +218,7 @@ public static RoomExportResult ExportRooms(IEnumerable roomsToExport, stri int textureWidth = textureArea1.Texture.Image.Width; int textureHeight = textureArea1.Texture.Image.Height; - if (face != SectorFace.Ceiling) + if (face.Face != SectorFace.Ceiling) { mesh.Positions.Add(room.RoomGeometry.VertexPositions[i + 3] + offset); mesh.Positions.Add(room.RoomGeometry.VertexPositions[i + 2] + offset); diff --git a/TombLib/TombLib/LevelData/Compilers/Rooms.cs b/TombLib/TombLib/LevelData/Compilers/Rooms.cs index acd7fea76..d162b2f15 100644 --- a/TombLib/TombLib/LevelData/Compilers/Rooms.cs +++ b/TombLib/TombLib/LevelData/Compilers/Rooms.cs @@ -342,15 +342,18 @@ private tr_room BuildRoom(Room room) var roomTriangles = new List(); var roomQuads = new List(); + // Track base polygon rotations for overlay coordination + var baseRotations = new Dictionary<(int, int, SectorFace), int>(); + // Add room's own geometry if (!room.Properties.Hidden) for (int z = 0; z < room.NumZSectors; ++z) for (int x = 0; x < room.NumXSectors; ++x) - foreach (SectorFace face in room.Sectors[x, z].GetFaceTextures().Keys) + foreach (FaceLayerInfo face in room.Sectors[x, z].GetAllFaceTextures().Keys) { var range = room.RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)); - var shape = room.GetFaceShape(x, z, face); + var shape = room.GetFaceShape(x, z, face.Face); if (range.Count == 0) continue; @@ -383,7 +386,7 @@ private tr_room BuildRoom(Room room) { ushort vertex3Index; - if (face == SectorFace.Ceiling) + if (face.Face == SectorFace.Ceiling) { texture.Mirror(); vertex0Index = GetOrAddVertex(room, roomVerticesDictionary, roomVertices, vertexPositions[i + 1], vertexColors[i + 1]); @@ -420,21 +423,56 @@ private tr_room BuildRoom(Room room) roomVertices[vertex3Index] = trVertex; } - var result = _textureInfoManager.AddTexture(texture, true, false); + Util.TexInfoManager.Result result; + + // Coordinate texture rotations between base and overlay layers + var faceKey = (x, z, face.Face); + + if (face.Layer == FaceLayer.Base) + { + // Base layer: Use optimal rotation and remember it + result = _textureInfoManager.AddTexture(texture, true, false); + baseRotations[faceKey] = result.Rotation; + } + else if (face.Layer == FaceLayer.Overlay && baseRotations.ContainsKey(faceKey)) + { + // Overlay layer: Force to use same rotation as base + var baseRotation = baseRotations[faceKey]; + result = _textureInfoManager.AddTexture(texture, true, false, false, baseRotation); + } + else + { + // Fallback: Use optimal rotation + result = _textureInfoManager.AddTexture(texture, true, false); + } + roomQuads.Add(result.CreateFace4(new ushort[] { vertex0Index, vertex1Index, vertex2Index, vertex3Index }, doubleSided, 0)); if (copyFace) { texture.Mirror(); - result = _textureInfoManager.AddTexture(texture, true, false); - roomQuads.Add(result.CreateFace4(new ushort[] { vertex3Index, vertex2Index, vertex1Index, vertex0Index }, + + Util.TexInfoManager.Result mirrorResult; + + if (face.Layer == FaceLayer.Overlay && baseRotations.ContainsKey(faceKey)) + { + var baseRotation = baseRotations[faceKey]; + mirrorResult = _textureInfoManager.AddTexture(texture, true, false, false, baseRotation); + } + else + { + mirrorResult = _textureInfoManager.AddTexture(texture, true, false); + } + + roomQuads.Add(mirrorResult.CreateFace4(new ushort[] { vertex3Index, vertex2Index, vertex1Index, vertex0Index }, doubleSided, 0)); } + i += 3; } else { - if (face == SectorFace.Ceiling || face == SectorFace.Ceiling_Triangle2) + if (face.Face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) texture.Mirror(true); vertex0Index = GetOrAddVertex(room, roomVerticesDictionary, roomVertices, vertexPositions[i + 0], vertexColors[i + 0]); @@ -458,7 +496,28 @@ private tr_room BuildRoom(Room room) roomVertices[vertex2Index] = trVertex; } - var result = _textureInfoManager.AddTexture(texture, true, true); + Util.TexInfoManager.Result result; + + // Coordinate texture rotations between base and overlay layers + var faceKey = (x, z, face.Face); + + if (face.Layer == FaceLayer.Base) + { + // Base layer: Use optimal rotation and remember it + result = _textureInfoManager.AddTexture(texture, true, true); + baseRotations[faceKey] = result.Rotation; + } + else if (face.Layer == FaceLayer.Overlay && baseRotations.ContainsKey(faceKey)) + { + // Overlay layer: Force to use same rotation as base + var baseRotation = baseRotations[faceKey]; + result = _textureInfoManager.AddTexture(texture, true, true, false, baseRotation); + } + else + { + // Fallback: Use optimal rotation + result = _textureInfoManager.AddTexture(texture, true, true); + } if (result.ConvertToQuad) roomQuads.Add(result.CreateFace4(new ushort[] { vertex0Index, vertex1Index, vertex2Index, vertex2Index }, @@ -470,8 +529,20 @@ private tr_room BuildRoom(Room room) if (copyFace) { texture.Mirror(true); - result = _textureInfoManager.AddTexture(texture, true, true); - roomTriangles.Add(result.CreateFace3(new ushort[] { vertex2Index, vertex1Index, vertex0Index }, + + Util.TexInfoManager.Result mirrorResult; + + if (face.Layer == FaceLayer.Overlay && baseRotations.ContainsKey(faceKey)) + { + var baseRotation = baseRotations[faceKey]; + mirrorResult = _textureInfoManager.AddTexture(texture, true, true, false, baseRotation); + } + else + { + mirrorResult = _textureInfoManager.AddTexture(texture, true, true); + } + + roomTriangles.Add(mirrorResult.CreateFace3(new ushort[] { vertex2Index, vertex1Index, vertex0Index }, doubleSided, 0)); } } diff --git a/TombLib/TombLib/LevelData/Compilers/Textures.cs b/TombLib/TombLib/LevelData/Compilers/Textures.cs index bab226ec7..3aaa202e2 100644 --- a/TombLib/TombLib/LevelData/Compilers/Textures.cs +++ b/TombLib/TombLib/LevelData/Compilers/Textures.cs @@ -72,11 +72,11 @@ private TextureFootStep.Type GetTextureSound(Room room, int x, int z) { Sector sector = room.Sectors[x, z]; - TextureFootStep.Type? result0 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(SectorFace.Floor)); + TextureFootStep.Type? result0 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(new(SectorFace.Floor, FaceLayer.Base))); if (result0.HasValue) return result0.Value; - TextureFootStep.Type? result1 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(SectorFace.Floor_Triangle2)); + TextureFootStep.Type? result1 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(new(SectorFace.Floor_Triangle2, FaceLayer.Base))); if (result1.HasValue) return result1.Value; diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs index 45aa201b2..9f0b67e2f 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Rooms.cs @@ -292,10 +292,10 @@ private TombEngineRoom BuildRoom(Room room) if (!room.Properties.Hidden) for (int z = 0; z < room.NumZSectors; ++z) for (int x = 0; x < room.NumXSectors; ++x) - foreach (SectorFace face in room.Sectors[x, z].GetFaceTextures().Keys) + foreach (FaceLayerInfo face in room.Sectors[x, z].GetAllFaceTextures().Keys) { var range = room.RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)); - var shape = room.GetFaceShape(x, z, face); + var shape = room.GetFaceShape(x, z, face.Face); if (range.Count == 0) continue; @@ -329,7 +329,7 @@ private TombEngineRoom BuildRoom(Room room) { int vertex3Index; - if (face == SectorFace.Ceiling) + if (face.Face == SectorFace.Ceiling) { texture.Mirror(); vertex0Index = GetOrAddVertex(room, roomVerticesDictionary, roomVertices, vertexPositions[i + 1], vertexColors[i + 1], 0); @@ -371,7 +371,7 @@ private TombEngineRoom BuildRoom(Room room) } else { - if (face == SectorFace.Ceiling || face == SectorFace.Ceiling_Triangle2) + if (face.Face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) texture.Mirror(true); vertex0Index = GetOrAddVertex(room, roomVerticesDictionary, roomVertices, vertexPositions[i + 0], vertexColors[i + 0], 0); diff --git a/TombLib/TombLib/LevelData/Compilers/TombEngine/Textures.cs b/TombLib/TombLib/LevelData/Compilers/TombEngine/Textures.cs index 2219b3861..25fbfb8ea 100644 --- a/TombLib/TombLib/LevelData/Compilers/TombEngine/Textures.cs +++ b/TombLib/TombLib/LevelData/Compilers/TombEngine/Textures.cs @@ -32,11 +32,11 @@ private TextureFootStep.Type GetTextureSound(Room room, int x, int z) { Sector sector = room.Sectors[x, z]; - TextureFootStep.Type? result0 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(SectorFace.Floor)); + TextureFootStep.Type? result0 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(new(SectorFace.Floor, FaceLayer.Base))); if (result0.HasValue) return result0.Value; - TextureFootStep.Type? result1 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(SectorFace.Floor_Triangle2)); + TextureFootStep.Type? result1 = GetTextureSound(!sector.Floor.IsQuad, sector.GetFaceTexture(new(SectorFace.Floor_Triangle2, FaceLayer.Base))); if (result1.HasValue) return result1.Value; diff --git a/TombLib/TombLib/LevelData/Compilers/Util/TexInfoManager.cs b/TombLib/TombLib/LevelData/Compilers/Util/TexInfoManager.cs index 53428e2d1..22ba6bd56 100644 --- a/TombLib/TombLib/LevelData/Compilers/Util/TexInfoManager.cs +++ b/TombLib/TombLib/LevelData/Compilers/Util/TexInfoManager.cs @@ -679,7 +679,7 @@ public tr_face4 CreateFace4(ushort[] indices, bool doubleSided, ushort lightingE private Result? GetTexInfo(TextureArea areaToLook, List parentList, bool isForRoom, bool isForTriangle, bool topmostAndUnpadded, - bool checkParameters = true, bool scanOtherSets = false, float lookupMargin = 0.0f) + bool checkParameters = true, bool scanOtherSets = false, float lookupMargin = 0.0f, int forceRotation = -1) { var lookupCoordinates = new Vector2[isForTriangle ? 3 : 4]; for (int i = 0; i < lookupCoordinates.Length; i++) @@ -713,6 +713,10 @@ public tr_face4 CreateFace4(ushort[] indices, bool doubleSided, ushort lightingE var result = TestUVSimilarity(child.AbsCoord, lookupCoordinates, lookupMargin); if (result != _noTexInfo) { + // If rotation is forced for overlay coordination, reject this match if rotation doesn't match + if (forceRotation >= 0 && result != forceRotation) + continue; + // Refresh topmost flag, as same texture may be applied to faces with different topmost priority parent.TopmostAndUnpadded |= topmostAndUnpadded; @@ -769,23 +773,8 @@ private void AddParent(TextureArea texture, List parentList, public Result AddTexture(TextureArea texture, bool isForRoom, bool isForTriangle, bool topmostAndUnpadded = false) { - if (_dataHasBeenLaidOut) - throw new InvalidOperationException("Data has been already laid out for this TexInfoManager. Reinitialize it if you want to restart texture collection."); - - if ((isForTriangle && texture.TriangleCoordsOutOfBounds) || (!isForTriangle && texture.QuadCoordsOutOfBounds)) - { - _progressReporter.ReportWarn("Texture (" + texture.TexCoord0 + ", " + texture.TexCoord1 + ", " + - texture.TexCoord2 + ", " + texture.TexCoord3 + ") is out of bounds and will be ignored."); + if (!ValidateTexture(texture, isForTriangle)) return new Result(); - } - - if (texture.ParentArea.Width > MaxTileSize || texture.ParentArea.Height > MaxTileSize) - { - _progressReporter.ReportWarn("Texture (" + texture.TexCoord0 + ", " + texture.TexCoord1 + ", " + - texture.TexCoord2 + ", " + texture.TexCoord3 + ") has incorrect parent area which has been reset. " + - "Possibly UV-mapped mesh with texture bigger than " + MaxTileSize + " in size."); - texture.ParentArea = Rectangle2.Zero; - } // Only try to remap animated textures if fast mode is disabled bool remapAnimatedTextures = _level.Settings.RemapAnimatedTextures && !_level.Settings.FastMode; @@ -834,24 +823,73 @@ public Result AddTexture(TextureArea texture, bool isForRoom, bool isForTriangle return AddTexture(texture, _parentTextures, isForRoom, isForTriangle, topmostAndUnpadded); } + public Result AddTexture(TextureArea texture, bool isForRoom, bool isForTriangle, bool topmostAndUnpadded, int forceRotation) + { + if (!ValidateTexture(texture, isForTriangle)) + return new Result(); + + // Force specific rotation for overlay coordination + return AddTexture(texture, _parentTextures, isForRoom, isForTriangle, topmostAndUnpadded, -1, true, forceRotation); + } + + public bool ValidateTexture(TextureArea texture, bool isForTriangle) + { + if (_dataHasBeenLaidOut) + throw new InvalidOperationException("Data has been already laid out for this TexInfoManager. Reinitialize it if you want to restart texture collection."); + + if ((isForTriangle && texture.TriangleCoordsOutOfBounds) || (!isForTriangle && texture.QuadCoordsOutOfBounds)) + { + _progressReporter.ReportWarn("Texture (" + texture.TexCoord0 + ", " + texture.TexCoord1 + ", " + + texture.TexCoord2 + ", " + texture.TexCoord3 + ") is out of bounds and will be ignored."); + + return false; + } + + if (texture.ParentArea.Width > MaxTileSize || texture.ParentArea.Height > MaxTileSize) + { + _progressReporter.ReportWarn("Texture (" + texture.TexCoord0 + ", " + texture.TexCoord1 + ", " + + texture.TexCoord2 + ", " + texture.TexCoord3 + ") has incorrect parent area which has been reset. " + + "Possibly UV-mapped mesh with texture bigger than " + MaxTileSize + " in size."); + + texture.ParentArea = Rectangle2.Zero; + } + + return true; + } + // Internal AddTexture variation which is capable of adding texture to various ParentTextureArea lists // with customizable parameters. // If animFrameIndex == -1, it means that ordinary texture is added, otherwise it indicates that specific anim // texture frame is being processed. If so, frame index is saved into TexInfoIndex field of resulting child. // Later on, on real anim texture creation, this index is used to sort frames in proper order. - private Result AddTexture(TextureArea texture, List parentList, bool isForRoom, bool isForTriangle, bool topmostAndUnpadded = false, int animFrameIndex = -1, bool makeCanonical = true) + private Result AddTexture(TextureArea texture, List parentList, bool isForRoom, bool isForTriangle, bool topmostAndUnpadded = false, int animFrameIndex = -1, bool makeCanonical = true, int forceRotation = -1) { // In case AddTexture is used with animated seq packing, we don't check frames for full similarity, because // frames can be duplicated with Repeat function or simply because of complex animator functions applied. - var result = animFrameIndex >= 0 ? null : GetTexInfo(texture, parentList, isForRoom, isForTriangle, topmostAndUnpadded); + var result = animFrameIndex >= 0 ? null : GetTexInfo(texture, parentList, isForRoom, isForTriangle, topmostAndUnpadded, forceRotation: forceRotation); if (!result.HasValue) { // Try to create new canonical (top-left-based) texture as child or parent. // makeCanonical parameter is necessary for animated textures, because animators may produce frames // with non-canonically rotated coordinates (e.g. spin animator). - var canonicalTexture = makeCanonical ? texture.GetCanonicalTexture(isForTriangle) : texture; + // If forceRotation is specified, apply that specific rotation instead of canonical optimization. + + TextureArea canonicalTexture; + + if (forceRotation >= 0) + { + // Force specific rotation for overlay coordination + canonicalTexture = texture; + + if (forceRotation > 0) + canonicalTexture.Rotate(forceRotation, isForTriangle); + } + else + { + canonicalTexture = makeCanonical ? texture.GetCanonicalTexture(isForTriangle) : texture; + } // If no any potential parents or children, create as new parent if (!TryToAddToExisting(canonicalTexture, parentList, isForRoom, isForTriangle, topmostAndUnpadded, animFrameIndex)) @@ -861,7 +899,7 @@ private Result AddTexture(TextureArea texture, List parentLis if (animFrameIndex >= 0) result = new Result { TexInfoIndex = _dummyTexInfo, Rotation = 0 }; else - result = GetTexInfo(texture, parentList, isForRoom, isForTriangle, topmostAndUnpadded); + result = GetTexInfo(texture, parentList, isForRoom, isForTriangle, topmostAndUnpadded, forceRotation: forceRotation); } if (!result.HasValue) diff --git a/TombLib/TombLib/LevelData/IO/LegacyRepair.cs b/TombLib/TombLib/LevelData/IO/LegacyRepair.cs index 3d6d90a8e..991fd865c 100644 --- a/TombLib/TombLib/LevelData/IO/LegacyRepair.cs +++ b/TombLib/TombLib/LevelData/IO/LegacyRepair.cs @@ -52,25 +52,25 @@ private static void SwapFloor2FacesWhereApplicable(Room room, int x, int z) if (xn.Sector is not null) { if (split.XnZn > xn.Sector.Ceiling.XpZn || split.XnZp > xn.Sector.Ceiling.XpZp) - sector.SetFaceTexture(SectorFace.Wall_NegativeX_Floor2, sector.GetFaceTexture(SectorFace.Wall_NegativeX_QA)); + sector.SetFaceTexture(new(SectorFace.Wall_NegativeX_Floor2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_NegativeX_QA, FaceLayer.Base))); } if (xp.Sector is not null) { if (split.XpZn > xp.Sector.Ceiling.XnZn || split.XpZp > xp.Sector.Ceiling.XnZp) - sector.SetFaceTexture(SectorFace.Wall_PositiveX_Floor2, sector.GetFaceTexture(SectorFace.Wall_PositiveX_QA)); + sector.SetFaceTexture(new(SectorFace.Wall_PositiveX_Floor2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_PositiveX_QA, FaceLayer.Base))); } if (zn.Sector is not null) { if (split.XnZn > zn.Sector.Ceiling.XnZp || split.XpZn > zn.Sector.Ceiling.XpZp) - sector.SetFaceTexture(SectorFace.Wall_NegativeZ_Floor2, sector.GetFaceTexture(SectorFace.Wall_NegativeZ_QA)); + sector.SetFaceTexture(new(SectorFace.Wall_NegativeZ_Floor2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_NegativeZ_QA, FaceLayer.Base))); } if (zp.Sector is not null) { if (split.XnZp > zp.Sector.Ceiling.XnZn || split.XpZp > zp.Sector.Ceiling.XpZn) - sector.SetFaceTexture(SectorFace.Wall_PositiveZ_Floor2, sector.GetFaceTexture(SectorFace.Wall_PositiveZ_QA)); + sector.SetFaceTexture(new(SectorFace.Wall_PositiveZ_Floor2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_PositiveZ_QA, FaceLayer.Base))); } } @@ -98,25 +98,25 @@ private static void SwapCeiling2FacesWhereApplicable(Room room, int x, int z) if (xn.Sector is not null) { if (split.XnZn < xn.Sector.Floor.XpZn || split.XnZp < xn.Sector.Floor.XpZp) - sector.SetFaceTexture(SectorFace.Wall_NegativeX_Ceiling2, sector.GetFaceTexture(SectorFace.Wall_NegativeX_WS)); + sector.SetFaceTexture(new(SectorFace.Wall_NegativeX_Ceiling2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_NegativeX_WS, FaceLayer.Base))); } if (xp.Sector is not null) { if (split.XpZn < xp.Sector.Floor.XnZn || split.XpZp < xp.Sector.Floor.XnZp) - sector.SetFaceTexture(SectorFace.Wall_PositiveX_Ceiling2, sector.GetFaceTexture(SectorFace.Wall_PositiveX_WS)); + sector.SetFaceTexture(new(SectorFace.Wall_PositiveX_Ceiling2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_PositiveX_WS, FaceLayer.Base))); } if (zn.Sector is not null) { if (split.XnZn < zn.Sector.Floor.XnZp || split.XpZn < zn.Sector.Floor.XpZp) - sector.SetFaceTexture(SectorFace.Wall_NegativeZ_Ceiling2, sector.GetFaceTexture(SectorFace.Wall_NegativeZ_WS)); + sector.SetFaceTexture(new(SectorFace.Wall_NegativeZ_Ceiling2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_NegativeZ_WS, FaceLayer.Base))); } if (zp.Sector is not null) { if (split.XnZp < zp.Sector.Floor.XnZn || split.XpZp < zp.Sector.Floor.XpZn) - sector.SetFaceTexture(SectorFace.Wall_PositiveZ_Ceiling2, sector.GetFaceTexture(SectorFace.Wall_PositiveZ_WS)); + sector.SetFaceTexture(new(SectorFace.Wall_PositiveZ_Ceiling2, FaceLayer.Base), sector.GetFaceTexture(new(SectorFace.Wall_PositiveZ_WS, FaceLayer.Base))); } } @@ -145,43 +145,43 @@ private static void SwapDiagonalFloor2FacesWhereApplicable(Room room, int x, int return; TextureArea - qaPositiveZ = localSector.GetFaceTexture(SectorFace.Wall_PositiveZ_QA), - qaNegativeZ = localSector.GetFaceTexture(SectorFace.Wall_NegativeZ_QA), - qaNegativeX = localSector.GetFaceTexture(SectorFace.Wall_NegativeX_QA), - qaPositiveX = localSector.GetFaceTexture(SectorFace.Wall_PositiveX_QA); + qaPositiveZ = localSector.GetFaceTexture(new(SectorFace.Wall_PositiveZ_QA, FaceLayer.Base)), + qaNegativeZ = localSector.GetFaceTexture(new(SectorFace.Wall_NegativeZ_QA, FaceLayer.Base)), + qaNegativeX = localSector.GetFaceTexture(new(SectorFace.Wall_NegativeX_QA, FaceLayer.Base)), + qaPositiveX = localSector.GetFaceTexture(new(SectorFace.Wall_PositiveX_QA, FaceLayer.Base)); switch (probingSector.Floor.DiagonalSplit) { case DiagonalSplit.XnZp: if (split.XnZn > localSector.Floor.XpZn) - localSector.SetFaceTexture(SectorFace.Wall_NegativeZ_Floor2, qaNegativeZ); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeZ_Floor2, FaceLayer.Base), qaNegativeZ); if (split.XpZp > localSector.Floor.XpZn) - localSector.SetFaceTexture(SectorFace.Wall_PositiveX_Floor2, qaPositiveX); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveX_Floor2, FaceLayer.Base), qaPositiveX); break; case DiagonalSplit.XpZn: if (split.XnZn > localSector.Floor.XnZp) - localSector.SetFaceTexture(SectorFace.Wall_NegativeX_Floor2, qaNegativeX); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeX_Floor2, FaceLayer.Base), qaNegativeX); if (split.XpZp > localSector.Floor.XnZp) - localSector.SetFaceTexture(SectorFace.Wall_PositiveZ_Floor2, qaPositiveZ); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveZ_Floor2, FaceLayer.Base), qaPositiveZ); break; case DiagonalSplit.XpZp: if (split.XpZn > localSector.Floor.XnZn) - localSector.SetFaceTexture(SectorFace.Wall_NegativeZ_Floor2, qaNegativeZ); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeZ_Floor2, FaceLayer.Base), qaNegativeZ); if (split.XnZp > localSector.Floor.XnZn) - localSector.SetFaceTexture(SectorFace.Wall_NegativeX_Floor2, qaNegativeX); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeX_Floor2, FaceLayer.Base), qaNegativeX); break; case DiagonalSplit.XnZn: if (split.XnZp > localSector.Floor.XpZp) - localSector.SetFaceTexture(SectorFace.Wall_PositiveZ_Floor2, qaPositiveZ); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveZ_Floor2, FaceLayer.Base), qaPositiveZ); if (split.XpZn > localSector.Floor.XpZp) - localSector.SetFaceTexture(SectorFace.Wall_PositiveX_Floor2, qaPositiveX); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveX_Floor2, FaceLayer.Base), qaPositiveX); break; } } @@ -211,43 +211,43 @@ private static void SwapDiagonalCeiling2FacesWhereApplicable(Room room, int x, i return; TextureArea - wsPositiveZ = localSector.GetFaceTexture(SectorFace.Wall_PositiveZ_WS), - wsNegativeZ = localSector.GetFaceTexture(SectorFace.Wall_NegativeZ_WS), - wsNegativeX = localSector.GetFaceTexture(SectorFace.Wall_NegativeX_WS), - wsPositiveX = localSector.GetFaceTexture(SectorFace.Wall_PositiveX_WS); + wsPositiveZ = localSector.GetFaceTexture(new(SectorFace.Wall_PositiveZ_WS, FaceLayer.Base)), + wsNegativeZ = localSector.GetFaceTexture(new(SectorFace.Wall_NegativeZ_WS, FaceLayer.Base)), + wsNegativeX = localSector.GetFaceTexture(new(SectorFace.Wall_NegativeX_WS, FaceLayer.Base)), + wsPositiveX = localSector.GetFaceTexture(new(SectorFace.Wall_PositiveX_WS, FaceLayer.Base)); switch (probingSector.Ceiling.DiagonalSplit) { case DiagonalSplit.XnZp: if (split.XnZn < localSector.Ceiling.XpZn) - localSector.SetFaceTexture(SectorFace.Wall_NegativeZ_Ceiling2, wsNegativeZ); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeZ_Ceiling2, FaceLayer.Base), wsNegativeZ); if (split.XpZp < localSector.Ceiling.XpZn) - localSector.SetFaceTexture(SectorFace.Wall_PositiveX_Ceiling2, wsPositiveX); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveX_Ceiling2, FaceLayer.Base), wsPositiveX); break; case DiagonalSplit.XpZn: if (split.XnZn < localSector.Ceiling.XnZp) - localSector.SetFaceTexture(SectorFace.Wall_NegativeX_Ceiling2, wsNegativeX); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeX_Ceiling2, FaceLayer.Base), wsNegativeX); if (split.XpZp < localSector.Ceiling.XnZp) - localSector.SetFaceTexture(SectorFace.Wall_PositiveZ_Ceiling2, wsPositiveZ); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveZ_Ceiling2, FaceLayer.Base), wsPositiveZ); break; case DiagonalSplit.XpZp: if (split.XpZn < localSector.Ceiling.XnZn) - localSector.SetFaceTexture(SectorFace.Wall_NegativeZ_Ceiling2, wsNegativeZ); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeZ_Ceiling2, FaceLayer.Base), wsNegativeZ); if (split.XnZp < localSector.Ceiling.XnZn) - localSector.SetFaceTexture(SectorFace.Wall_NegativeX_Ceiling2, wsNegativeX); + localSector.SetFaceTexture(new(SectorFace.Wall_NegativeX_Ceiling2, FaceLayer.Base), wsNegativeX); break; case DiagonalSplit.XnZn: if (split.XnZp < localSector.Ceiling.XpZp) - localSector.SetFaceTexture(SectorFace.Wall_PositiveZ_Ceiling2, wsPositiveZ); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveZ_Ceiling2, FaceLayer.Base), wsPositiveZ); if (split.XpZn < localSector.Ceiling.XpZp) - localSector.SetFaceTexture(SectorFace.Wall_PositiveX_Ceiling2, wsPositiveX); + localSector.SetFaceTexture(new(SectorFace.Wall_PositiveX_Ceiling2, FaceLayer.Base), wsPositiveX); break; } } diff --git a/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs b/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs index ba995a2cc..79309b9fb 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Chunks.cs @@ -140,7 +140,9 @@ internal static class Prj2Chunks /**************/public static readonly ChunkId SectorCeilingSubdivisions2 = new ChunkId(new byte[] { 10 }); /**************/public static readonly ChunkId TextureLevelTexture = new ChunkId(new byte[] { 16 }); /**************/public static readonly ChunkId TextureLevelTexture2 = new ChunkId(new byte[] { 18 }); + /**************/public static readonly ChunkId TextureLevelTexture3 = new ChunkId(new byte[] { 19 }); /**************/public static readonly ChunkId TextureInvisible = new ChunkId(new byte[] { 17 }); + /**************/public static readonly ChunkId TextureInvisible2 = new ChunkId(new byte[] { 20 }); /******/public static readonly ChunkId RoomAmbientLight = ChunkId.FromString("TeAmbient"); /******/public static readonly ChunkId RoomAlternate = ChunkId.FromString("TeAlternate"); /**********/public static readonly ChunkId AlternateRoom = ChunkId.FromString("TeRoom"); diff --git a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs index 0613f0545..f9bed35cb 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Loader.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Loader.cs @@ -945,9 +945,14 @@ private static bool LoadRooms(ChunkReader chunkIO, ChunkId idOuter, Level level, } } else if (id4 == Prj2Chunks.TextureLevelTexture || - id4 == Prj2Chunks.TextureLevelTexture2) + id4 == Prj2Chunks.TextureLevelTexture2 || + id4 == Prj2Chunks.TextureLevelTexture3) { SectorFace face = (SectorFace)LEB128.ReadLong(chunkIO.Raw); + FaceLayer layer = FaceLayer.Base; + + if (id4 == Prj2Chunks.TextureLevelTexture3) + layer = (FaceLayer)LEB128.ReadLong(chunkIO.Raw); var textureArea = new TextureArea(); textureArea.TexCoord0 = chunkIO.Raw.ReadVector2(); @@ -955,7 +960,7 @@ private static bool LoadRooms(ChunkReader chunkIO, ChunkId idOuter, Level level, textureArea.TexCoord2 = chunkIO.Raw.ReadVector2(); textureArea.TexCoord3 = chunkIO.Raw.ReadVector2(); - if(id4 == Prj2Chunks.TextureLevelTexture2) + if(id4 == Prj2Chunks.TextureLevelTexture2 || id4 == Prj2Chunks.TextureLevelTexture3) { textureArea.ParentArea.Start = chunkIO.Raw.ReadVector2(); textureArea.ParentArea.End = chunkIO.Raw.ReadVector2(); @@ -968,12 +973,18 @@ private static bool LoadRooms(ChunkReader chunkIO, ChunkId idOuter, Level level, textureArea.DoubleSided = (blendFlag & 1) != 0; textureArea.Texture = levelSettingsIds.LevelTextures.TryGetOrDefault(LEB128.ReadLong(chunkIO.Raw)); - sector.SetFaceTexture(face, textureArea); + sector.SetFaceTexture(new FaceLayerInfo(face, layer), textureArea); } else if (id4 == Prj2Chunks.TextureInvisible) { SectorFace face = (SectorFace)LEB128.ReadLong(chunkIO.Raw); - sector.SetFaceTexture(face, TextureArea.Invisible); + sector.SetFaceTexture(new FaceLayerInfo(face, FaceLayer.Base), TextureArea.Invisible); + } + else if (id4 == Prj2Chunks.TextureInvisible2) + { + SectorFace face = (SectorFace)LEB128.ReadLong(chunkIO.Raw); + FaceLayer layer = (FaceLayer)LEB128.ReadLong(chunkIO.Raw); + sector.SetFaceTexture(new FaceLayerInfo(face, layer), TextureArea.Invisible); } else return false; diff --git a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs index 01ab4e49c..aa70ffe77 100644 --- a/TombLib/TombLib/LevelData/IO/Prj2Writer.cs +++ b/TombLib/TombLib/LevelData/IO/Prj2Writer.cs @@ -466,7 +466,7 @@ private static void WriteRooms(ChunkWriter chunkIO, Dictionary rooms, for (SectorEdge edge = 0; edge < SectorEdge.Count; ++edge) LEB128.Write(chunkIO.Raw, b.GetHeight(splitVertical, edge)); } - foreach (SectorFace face in b.GetFaceTextures().Where(texture => room.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, texture.Key))).Select(x => x.Key)) + foreach (FaceLayerInfo face in b.GetAllFaceTextures().Where(texture => room.RoomGeometry.VertexRangeLookup.ContainsKey(new SectorFaceIdentity(x, z, texture.Key))).Select(x => x.Key)) { TextureArea texture = b.GetFaceTexture(face); @@ -476,11 +476,12 @@ private static void WriteRooms(ChunkWriter chunkIO, Dictionary rooms, if (texture.Texture is LevelTexture t) { if (t != null && levelSettingIds.LevelTextures.ContainsKey(t)) - using (var chunkTextureLevelTexture = chunkIO.WriteChunk(Prj2Chunks.TextureLevelTexture2, LEB128.MaximumSize1Byte)) + using (var chunkTextureLevelTexture = chunkIO.WriteChunk(Prj2Chunks.TextureLevelTexture3, LEB128.MaximumSize1Byte)) { int textureIndex = levelSettingIds.LevelTextures[t]; - LEB128.Write(chunkIO.Raw, (long)face); + LEB128.Write(chunkIO.Raw, (long)face.Face); + LEB128.Write(chunkIO.Raw, (long)face.Layer); chunkIO.Raw.Write(texture.TexCoord0); chunkIO.Raw.Write(texture.TexCoord1); chunkIO.Raw.Write(texture.TexCoord2); @@ -494,7 +495,11 @@ private static void WriteRooms(ChunkWriter chunkIO, Dictionary rooms, logger.Warn("Room " + room.Name + " has a texture referring to a texture file " + t.Path + " which is missing from project."); } else if (texture.Texture == TextureInvisible.Instance) - chunkIO.WriteChunkInt(Prj2Chunks.TextureInvisible, (long)face); + using (var chunkTextureInvisible = chunkIO.WriteChunk(Prj2Chunks.TextureInvisible2, LEB128.MaximumSize1Byte)) + { + LEB128.Write(chunkIO.Raw, (long)face.Face); + LEB128.Write(chunkIO.Raw, (long)face.Layer); + } else throw new NotSupportedException("Unsupported texture type " + texture.Texture.GetType().Name); } diff --git a/TombLib/TombLib/LevelData/IO/PrjLoader.cs b/TombLib/TombLib/LevelData/IO/PrjLoader.cs index 5d1aa1e0c..319a2fe85 100644 --- a/TombLib/TombLib/LevelData/IO/PrjLoader.cs +++ b/TombLib/TombLib/LevelData/IO/PrjLoader.cs @@ -2081,10 +2081,10 @@ private static void LoadTextureArea(Room room, int x, int z, SectorFace face, Le { case 0x0000: // TYPE_TEXTURE_NONE default: - sector.SetFaceTexture(face, new TextureArea()); + sector.SetFaceTexture(new(face, FaceLayer.Base), new TextureArea()); return; case 0x0003: // TYPE_TEXTURE_COLOR - sector.SetFaceTexture(face, TextureArea.Invisible); + sector.SetFaceTexture(new(face, FaceLayer.Base), TextureArea.Invisible); return; case 0x0007: // TYPE_TEXTURE_TILE int texIndex = ((prjFace._txtFlags & 0x03) << 8) | prjFace._txtIndex; @@ -2157,7 +2157,7 @@ private static void LoadTextureArea(Room room, int x, int z, SectorFace face, Le break; default: logger.Warn("Unknown texture triangle selection " + prjFace._txtTriangle); - sector.SetFaceTexture(face, new TextureArea()); + sector.SetFaceTexture(new(face, FaceLayer.Base), new TextureArea()); return; } @@ -2231,7 +2231,7 @@ private static void LoadTextureArea(Room room, int x, int z, SectorFace face, Le } } - sector.SetFaceTexture(face, texture); + sector.SetFaceTexture(new(face, FaceLayer.Base), texture); return; } } diff --git a/TombLib/TombLib/LevelData/Level.cs b/TombLib/TombLib/LevelData/Level.cs index 60bc4e380..5c54063ac 100644 --- a/TombLib/TombLib/LevelData/Level.cs +++ b/TombLib/TombLib/LevelData/Level.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using TombLib.LevelData.SectorEnums; +using TombLib.LevelData.SectorStructs; using TombLib.Utils; using TombLib.Wad.Catalog; @@ -274,7 +275,7 @@ public IReadOnlyList TransformRooms(IEnumerable roomsToRotate, RectT VectorInt2 newSectorPosition = transformation.Transform(new VectorInt2(x, z), oldRoom.SectorSize); newRoom.Sectors[newSectorPosition.X, newSectorPosition.Y] = oldRoom.Sectors[x, z].Clone(); newRoom.Sectors[newSectorPosition.X, newSectorPosition.Y].Transform(transformation, null, - oldFace => oldRoom.GetFaceShape(x, z, oldFace)); + oldFace => oldRoom.GetFaceShape(x, z, oldFace.Face)); } newRooms[i] = newRoom; } @@ -368,7 +369,7 @@ public void RemoveTextures(Predicate askIfTextureToRemove) Parallel.ForEach(ExistingRooms, room => { foreach (Sector sector in room.Sectors) - foreach (SectorFace face in sector.GetFaceTextures().Keys) + foreach (FaceLayerInfo face in sector.GetAllFaceTextures().Keys) { TextureArea currentTextureArea = sector.GetFaceTexture(face); LevelTexture currentTexture = currentTextureArea.Texture as LevelTexture; diff --git a/TombLib/TombLib/LevelData/Room.cs b/TombLib/TombLib/LevelData/Room.cs index 6fc995e82..038be998c 100644 --- a/TombLib/TombLib/LevelData/Room.cs +++ b/TombLib/TombLib/LevelData/Room.cs @@ -914,13 +914,14 @@ public bool CornerSelected(RectangleInt2 area) } public bool IsFaceDefined(int x, int z, SectorFace face) - { - return RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)).Count != 0; - } + => RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, new FaceLayerInfo(face, FaceLayer.Base))).Count != 0; + + public bool IsFaceDefined(int x, int z, FaceLayerInfo faceLayer) + => RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, faceLayer)).Count != 0; public float GetFaceHighestPoint(int x, int z, SectorFace face) { - var range = RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)); + var range = RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, new FaceLayerInfo(face, FaceLayer.Base))); float max = float.NegativeInfinity; for (int i = 0; i < range.Count; ++i) max = Math.Max(RoomGeometry.VertexPositions[i + range.Start].Y, max); @@ -929,7 +930,7 @@ public float GetFaceHighestPoint(int x, int z, SectorFace face) public float GetFaceLowestPoint(int x, int z, SectorFace face) { - var range = RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)); + var range = RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, new FaceLayerInfo(face, FaceLayer.Base))); float max = float.PositiveInfinity; for (int i = 0; i < range.Count; ++i) max = Math.Min(RoomGeometry.VertexPositions[i + range.Start].Y, max); @@ -938,7 +939,7 @@ public float GetFaceLowestPoint(int x, int z, SectorFace face) public FaceShape GetFaceShape(int x, int z, SectorFace face) { - switch (RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)).Count) + switch (RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, new FaceLayerInfo(face, FaceLayer.Base))).Count) { case 3: return FaceShape.Triangle; @@ -1670,7 +1671,7 @@ public void CopyDependentLevelSettings(CopyDependentLevelSettingsArgs args) for (int x = 0; x < NumXSectors; ++x) { Sector sector = Sectors[x, z]; - foreach (SectorFace face in sector.GetFaceTextures().Keys) + foreach (FaceLayerInfo face in sector.GetAllFaceTextures().Keys) { TextureArea textureArea = sector.GetFaceTexture(face); var sourceTexture = textureArea.Texture as LevelTexture; @@ -1704,7 +1705,7 @@ public HashSet GetTextures() for (int x = 0; x < NumXSectors; ++x) { Sector sector = Sectors[x, z]; - foreach (SectorFace face in sector.GetFaceTextures().Keys) + foreach (FaceLayerInfo face in sector.GetAllFaceTextures().Keys) result.Add(sector.GetFaceTexture(face).Texture); } return result; @@ -1731,10 +1732,10 @@ public void GetGeometryStatistics(out int vertices, out int faces) if (!Properties.Hidden) for (int z = 0; z < NumZSectors; ++z) for (int x = 0; x < NumXSectors; ++x) - foreach (SectorFace face in Sectors[x, z].GetFaceTextures().Keys) + foreach (FaceLayerInfo face in Sectors[x, z].GetAllFaceTextures().Keys) { var range = RoomGeometry.VertexRangeLookup.TryGetOrDefault(new SectorFaceIdentity(x, z, face)); - var shape = GetFaceShape(x, z, face); + var shape = GetFaceShape(x, z, face.Face); if (range.Count == 0) continue; @@ -1754,7 +1755,7 @@ public void GetGeometryStatistics(out int vertices, out int faces) if (shape == FaceShape.Quad) { - if (face == SectorFace.Ceiling) + if (face.Face == SectorFace.Ceiling) { LevelCompilerClassicTR.GetOrAddVertex(this, roomVerticesDictionary, roomVertices, vertexPositions[i + 1], vertexColors[i + 1]); LevelCompilerClassicTR.GetOrAddVertex(this, roomVerticesDictionary, roomVertices, vertexPositions[i + 2], vertexColors[i + 2]); diff --git a/TombLib/TombLib/LevelData/RoomGeometry.cs b/TombLib/TombLib/LevelData/RoomGeometry.cs index 33b3971a8..35a1dfa9d 100644 --- a/TombLib/TombLib/LevelData/RoomGeometry.cs +++ b/TombLib/TombLib/LevelData/RoomGeometry.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Numerics; using TombLib.LevelData.SectorEnums; +using TombLib.LevelData.SectorEnums.Extensions; using TombLib.LevelData.SectorGeometry; using TombLib.LevelData.SectorStructs; using TombLib.Utils; @@ -326,8 +327,11 @@ public void Build(Room room, bool highQualityLighting, bool useLegacyCode = fals Relight(room, highQualityLighting); } - public void UpdateFaceTexture(int x, int z, SectorFace face, TextureArea texture, bool wasDoubleSided) + public bool UpdateFaceTexture(int x, int z, FaceLayerInfo face, TextureArea texture, bool wasDoubleSided) { + if (face.Layer is FaceLayer.Overlay && texture.TextureIsInvisible) + return false; // Setting invisible texture on overlay should remove the overlay - prevent texture update + VertexRange range = VertexRangeLookup.GetValueOrDefault(new SectorFaceIdentity(x, z, face)); if (range.Count == 3) // Triangle @@ -338,10 +342,11 @@ public void UpdateFaceTexture(int x, int z, SectorFace face, TextureArea texture if (texture.DoubleSided) DoubleSidedTriangleCount++; - if (face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) + if (face.Face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) Swap.Do(ref texture.TexCoord0, ref texture.TexCoord2); TriangleTextureAreas[range.Start / 3] = texture; + return true; } else if (range.Count == 6) // Quad { @@ -356,7 +361,7 @@ public void UpdateFaceTexture(int x, int z, SectorFace face, TextureArea texture texture0.TexCoord1 = texture.TexCoord3; texture0.TexCoord2 = texture.TexCoord1; - if (face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) + if (face.Face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) Swap.Do(ref texture0.TexCoord0, ref texture0.TexCoord2); TriangleTextureAreas[range.Start / 3] = texture0; @@ -366,11 +371,14 @@ public void UpdateFaceTexture(int x, int z, SectorFace face, TextureArea texture texture1.TexCoord1 = texture.TexCoord1; texture1.TexCoord2 = texture.TexCoord3; - if (face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) + if (face.Face is SectorFace.Ceiling or SectorFace.Ceiling_Triangle2) Swap.Do(ref texture1.TexCoord0, ref texture1.TexCoord2); TriangleTextureAreas[(range.Start + 3) / 3] = texture1; + return true; } + + return false; } private enum FaceDirection @@ -381,7 +389,8 @@ private enum FaceDirection private void BuildFloorOrCeilingFace(Room room, int x, int z, int h0, int h1, int h2, int h3, DiagonalSplit splitType, bool diagonalSplitXEqualsY, SectorFace face1, SectorFace face2, Room.RoomConnectionType portalMode) { - SectorType sectorType = room.Sectors[x, z].Type; + Sector sector = room.Sectors[x, z]; + SectorType sectorType = sector.Type; // Exit function if the sector is a complete wall or portal if (portalMode == Room.RoomConnectionType.FullPortal) @@ -428,21 +437,15 @@ private void BuildFloorOrCeilingFace(Room room, int x, int z, int h0, int h1, in // 4----3 // - Sector sector = room.Sectors[x, z]; - TextureArea defaultTexture = room.Level.Settings.DefaultTexture; - bool shouldApplyDefaultTexture1 = sector.GetFaceTexture(face1) == TextureArea.None && defaultTexture != TextureArea.None, - shouldApplyDefaultTexture2 = sector.GetFaceTexture(face2) == TextureArea.None && defaultTexture != TextureArea.None; + bool shouldApplyDefaultTexture1 = sector.GetFaceTexture(new(face1, FaceLayer.Base)) == TextureArea.None && defaultTexture != TextureArea.None, + shouldApplyDefaultTexture2 = sector.GetFaceTexture(new(face2, FaceLayer.Base)) == TextureArea.None && defaultTexture != TextureArea.None; if (shouldApplyDefaultTexture1) - sector.SetFaceTexture(face1, defaultTexture); + sector.SetFaceTexture(new(face1, FaceLayer.Base), defaultTexture); if (shouldApplyDefaultTexture2) - sector.SetFaceTexture(face2, defaultTexture); - - TextureArea - face1Texture = sector.GetFaceTexture(face1), - face2Texture = sector.GetFaceTexture(face2); + sector.SetFaceTexture(new(face2, FaceLayer.Base), defaultTexture); // Build sector if (splitType != DiagonalSplit.None) @@ -452,20 +455,20 @@ private void BuildFloorOrCeilingFace(Room room, int x, int z, int h0, int h1, in case DiagonalSplit.XpZn: if (portalMode != Room.RoomConnectionType.TriangularPortalXnZp) { - AddTriangle(x, z, face1, + TryRenderTriangleFace(sector, x, z, face1, new Vector3(x * Level.SectorSizeUnit, h0, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), - face1Texture, new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), true); + new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), true); } if (portalMode != Room.RoomConnectionType.TriangularPortalXpZn && sectorType != SectorType.Wall) { - AddTriangle(x, z, face2, + TryRenderTriangleFace(sector, x, z, face2, new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), - face2Texture, new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1), true); + new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1), true); } break; @@ -473,20 +476,20 @@ private void BuildFloorOrCeilingFace(Room room, int x, int z, int h0, int h1, in case DiagonalSplit.XnZn: if (portalMode != Room.RoomConnectionType.TriangularPortalXpZp) { - AddTriangle(x, z, face1, + TryRenderTriangleFace(sector, x, z, face1, new Vector3(x * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h1, z * Level.SectorSizeUnit), - face1Texture, new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), false); + new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), false); } if (portalMode != Room.RoomConnectionType.TriangularPortalXnZn && sectorType != SectorType.Wall) { - AddTriangle(x, z, face2, + TryRenderTriangleFace(sector, x, z, face2, new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), - face2Texture, new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0), false); + new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0), false); } break; @@ -494,20 +497,20 @@ private void BuildFloorOrCeilingFace(Room room, int x, int z, int h0, int h1, in case DiagonalSplit.XnZp: if (portalMode != Room.RoomConnectionType.TriangularPortalXpZn) { - AddTriangle(x, z, face2, + TryRenderTriangleFace(sector, x, z, face2, new Vector3((x + 1) * Level.SectorSizeUnit, h2, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), - face2Texture, new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1), true); + new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1), true); } if (portalMode != Room.RoomConnectionType.TriangularPortalXnZp && sectorType != SectorType.Wall) { - AddTriangle(x, z, face1, + TryRenderTriangleFace(sector, x, z, face1, new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), - face1Texture, new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), true); + new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), true); } break; @@ -515,21 +518,21 @@ private void BuildFloorOrCeilingFace(Room room, int x, int z, int h0, int h1, in case DiagonalSplit.XpZp: if (portalMode != Room.RoomConnectionType.TriangularPortalXnZn) { - AddTriangle(x, z, face2, + TryRenderTriangleFace(sector, x, z, face2, new Vector3((x + 1) * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h3, (z + 1) * Level.SectorSizeUnit), - face2Texture, new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0), false); + new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0), false); } if (portalMode != Room.RoomConnectionType.TriangularPortalXpZp && sectorType != SectorType.Wall) { - AddTriangle(x, z, face1, + TryRenderTriangleFace(sector, x, z, face1, new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), - face1Texture, new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), false); + new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), false); } break; @@ -540,51 +543,51 @@ private void BuildFloorOrCeilingFace(Room room, int x, int z, int h0, int h1, in } else if (SectorSurface.IsQuad2(h0, h1, h2, h3) && portalMode == Room.RoomConnectionType.NoPortal) { - AddQuad(x, z, face1, + TryRenderQuadFace(sector, x, z, face1, new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), - face1Texture, new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1)); + new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1)); } else if (diagonalSplitXEqualsY || portalMode == Room.RoomConnectionType.TriangularPortalXnZp || portalMode == Room.RoomConnectionType.TriangularPortalXpZn) { if (portalMode != Room.RoomConnectionType.TriangularPortalXnZp) { - AddTriangle(x, z, face2, + TryRenderTriangleFace(sector, x, z, face2, new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), - face2Texture, new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), true); + new Vector2(0, 1), new Vector2(0, 0), new Vector2(1, 0), true); } if (portalMode != Room.RoomConnectionType.TriangularPortalXpZn) { - AddTriangle(x, z, face1, + TryRenderTriangleFace(sector, x, z, face1, new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), - face1Texture, new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1), true); + new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1), true); } } else { if (portalMode != Room.RoomConnectionType.TriangularPortalXpZp) { - AddTriangle(x, z, face1, + TryRenderTriangleFace(sector, x, z, face1, new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h1, (z + 1) * Level.SectorSizeUnit), new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), - face1Texture, new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), false); + new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), false); } if (portalMode != Room.RoomConnectionType.TriangularPortalXnZn) { - AddTriangle(x, z, face2, + TryRenderTriangleFace(sector, x, z, face2, new Vector3((x + 1) * Level.SectorSizeUnit, h2, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h3, z * Level.SectorSizeUnit), new Vector3(x * Level.SectorSizeUnit, h0, (z + 1) * Level.SectorSizeUnit), - face2Texture, new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0), false); + new Vector2(1, 1), new Vector2(0, 1), new Vector2(0, 0), false); } } } @@ -640,22 +643,45 @@ private void AddVerticalFaces(Room room, int x, int z, FaceDirection faceDirecti private void AddFace(Room room, int x, int z, SectorFaceData face) { Sector sector = room.Sectors[x, z]; + var baseLayer = new FaceLayerInfo(face.Face, FaceLayer.Base); - bool shouldApplyDefaultTexture = sector.GetFaceTexture(face.Face) == TextureArea.None + bool shouldApplyDefaultTexture = sector.GetFaceTexture(baseLayer) == TextureArea.None && room.Level.Settings.DefaultTexture != TextureArea.None; if (shouldApplyDefaultTexture) - sector.SetFaceTexture(face.Face, room.Level.Settings.DefaultTexture); - - TextureArea texture = sector.GetFaceTexture(face.Face); + sector.SetFaceTexture(baseLayer, room.Level.Settings.DefaultTexture); if (face.IsQuad) - AddQuad(x, z, face.Face, face.P0, face.P1, face.P2, face.P3.Value, texture, face.UV0, face.UV1, face.UV2, face.UV3.Value); + TryRenderQuadFace(sector, x, z, face.Face, face.P0, face.P1, face.P2, face.P3.Value, face.UV0, face.UV1, face.UV2, face.UV3.Value); else - AddTriangle(x, z, face.Face, face.P0, face.P1, face.P2, texture, face.UV0, face.UV1, face.UV2, face.IsXEqualYDiagonal.Value); + TryRenderTriangleFace(sector, x, z, face.Face, face.P0, face.P1, face.P2, face.UV0, face.UV1, face.UV2, face.IsXEqualYDiagonal.Value); + } + + private void TryRenderQuadFace(Sector sector, int x, int z, SectorFace face, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector2 uv0, Vector2 uv1, Vector2 uv2, Vector2 uv3) + { + TextureArea + texture = sector.GetFaceTexture(new FaceLayerInfo(face, FaceLayer.Base)), + overlay = sector.GetFaceTexture(new FaceLayerInfo(face, FaceLayer.Overlay)); + + AddQuad(x, z, new FaceLayerInfo(face, FaceLayer.Base), p0, p1, p2, p3, texture, uv0, uv1, uv2, uv3); + + if (overlay != TextureArea.None) + AddQuad(x, z, new FaceLayerInfo(face, FaceLayer.Overlay), p0, p1, p2, p3, overlay, uv0, uv1, uv2, uv3); + } + + private void TryRenderTriangleFace(Sector sector, int x, int z, SectorFace face, Vector3 p0, Vector3 p1, Vector3 p2, Vector2 uv0, Vector2 uv1, Vector2 uv2, bool isXEqualYDiagonal) + { + TextureArea + texture = sector.GetFaceTexture(new FaceLayerInfo(face, FaceLayer.Base)), + overlay = sector.GetFaceTexture(new FaceLayerInfo(face, FaceLayer.Overlay)); + + AddTriangle(x, z, new FaceLayerInfo(face, FaceLayer.Base), p0, p1, p2, texture, uv0, uv1, uv2, isXEqualYDiagonal); + + if (overlay != TextureArea.None) + AddTriangle(x, z, new FaceLayerInfo(face, FaceLayer.Overlay), p0, p1, p2, overlay, uv0, uv1, uv2, isXEqualYDiagonal); } - private void AddQuad(int x, int z, SectorFace face, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, + private void AddQuad(int x, int z, FaceLayerInfo face, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, TextureArea texture, Vector2 editorUV0, Vector2 editorUV1, Vector2 editorUV2, Vector2 editorUV3) { if (texture.DoubleSided) @@ -691,7 +717,7 @@ private void AddQuad(int x, int z, SectorFace face, Vector3 p0, Vector3 p1, Vect TriangleSectorInfo.Add(new SectorFaceIdentity(x, z, face)); } - private void AddTriangle(int x, int z, SectorFace face, Vector3 p0, Vector3 p1, Vector3 p2, TextureArea texture, Vector2 editorUV0, Vector2 editorUV1, Vector2 editorUV2, bool isXEqualYDiagonal) + private void AddTriangle(int x, int z, FaceLayerInfo face, Vector3 p0, Vector3 p1, Vector3 p2, TextureArea texture, Vector2 editorUV0, Vector2 editorUV1, Vector2 editorUV2, bool isXEqualYDiagonal) { if (texture.DoubleSided) DoubleSidedTriangleCount += 1; @@ -1255,7 +1281,7 @@ public struct IntersectionInfo var normal = Vector3.Cross(p1 - p0, p2 - p0); if (Vector3.Dot(ray.Direction, normal) <= 0) if (!(distance > result.Distance)) - result = new IntersectionInfo() { Distance = distance, Face = entry.Key.Face, Pos = entry.Key.Position, VerticalCoord = position.Y }; + result = new IntersectionInfo() { Distance = distance, Face = entry.Key.Face.Face, Pos = entry.Key.Position, VerticalCoord = position.Y }; } } diff --git a/TombLib/TombLib/LevelData/Sector.cs b/TombLib/TombLib/LevelData/Sector.cs index fd46a4f47..da6debfb1 100644 --- a/TombLib/TombLib/LevelData/Sector.cs +++ b/TombLib/TombLib/LevelData/Sector.cs @@ -27,7 +27,7 @@ public enum SlopeCalculationMode public bool ForceFloorSolid { get; set; } // If this is set to true, portals are overwritten for this sector. public List ExtraFloorSplits { get; } = new(); public List ExtraCeilingSplits { get; } = new(); - private Dictionary _faceTextures { get; } = new(); + private Dictionary _faceTextures { get; } = new(); public SectorSurface Floor; public SectorSurface Ceiling; @@ -59,7 +59,7 @@ public Sector Clone() Ceiling = Ceiling }; - foreach (KeyValuePair entry in _faceTextures) + foreach (KeyValuePair entry in _faceTextures) result._faceTextures[entry.Key] = entry.Value; foreach (SectorSplit split in ExtraFloorSplits) @@ -84,15 +84,10 @@ public bool HasFlag(SectorFlags flag) public bool IsAnyPortal => FloorPortal != null || CeilingPortal != null || WallPortal != null; public bool HasGhostBlock => GhostBlock != null; - public bool SetFaceTexture(SectorFace face, TextureArea texture) + public bool SetFaceTexture(FaceLayerInfo face, TextureArea texture) { - if (texture == TextureArea.None) - { - if (_faceTextures.ContainsKey(face)) - _faceTextures.Remove(face); - - return true; - } + if (texture == TextureArea.None || (face.Layer is FaceLayer.Overlay && texture.TextureIsInvisible)) + return _faceTextures.Remove(face); if (texture.TextureIsDegenerate) texture = TextureArea.Invisible; @@ -106,15 +101,14 @@ public bool SetFaceTexture(SectorFace face, TextureArea texture) return false; } - public TextureArea GetFaceTexture(SectorFace face) - { - return _faceTextures.GetValueOrDefault(face); - } + public TextureArea GetFaceTexture(FaceLayerInfo face) + => _faceTextures.GetValueOrDefault(face); - public Dictionary GetFaceTextures() - { - return _faceTextures; - } + public Dictionary GetFaceTextures(FaceLayer layer) + => _faceTextures.Where(kvp => kvp.Key.Layer == layer).ToDictionary(kvp => kvp.Key.Face, kvp => kvp.Value); + + public Dictionary GetAllFaceTextures() + => _faceTextures; public IEnumerable GetVerticals() { @@ -148,7 +142,7 @@ public void ReplaceGeometry(Level level, Sector replacement) Flags = replacement.Flags; ForceFloorSolid = replacement.ForceFloorSolid; - foreach (SectorFace face in replacement.GetFaceTextures().Keys.Union(_faceTextures.Keys)) + foreach (FaceLayerInfo face in replacement.GetAllFaceTextures().Keys.Union(_faceTextures.Keys)) { var texture = replacement.GetFaceTexture(face); if (texture.TextureIsInvisible || level.Settings.Textures.Contains(texture.Texture)) @@ -304,7 +298,7 @@ private static DiagonalSplit TransformDiagonalSplit(DiagonalSplit split, RectTra return split; } - private void MirrorWallTexture(SectorFace oldFace, Func oldFaceIsTriangle) + private void MirrorWallTexture(FaceLayerInfo oldFace, Func oldFaceIsTriangle) { if (!_faceTextures.TryGetValue(oldFace, out TextureArea area)) return; @@ -329,7 +323,7 @@ private void MirrorWallTexture(SectorFace oldFace, Func o // When mirroring is done, a oldFaceIsTriangle must be provided that can return the previous shape of texture faces. // Set "onlyFloor" to true, to only modify the floor. // Set "onlyFloor" to false, to only modify the ceiling. - public void Transform(RectTransformation transformation, bool? onlyFloor = null, Func oldFaceIsTriangle = null) + public void Transform(RectTransformation transformation, bool? onlyFloor = null, Func oldFaceIsTriangle = null) { // Rotate sector flags if (transformation.MirrorX) @@ -389,13 +383,15 @@ public void Transform(RectTransformation transformation, bool? onlyFloor = null, transformation.TransformValueDiagonalQuad(ref GhostBlock.Ceiling.XpZp, ref GhostBlock.Ceiling.XnZp, ref GhostBlock.Ceiling.XnZn, ref GhostBlock.Ceiling.XpZn); } + for (FaceLayer layer = 0; layer < FaceLayer.Count; layer++) + { // Rotate applied textures if (onlyFloor != false) { // Fix lower wall textures if (transformation.MirrorX) { - var faces = _faceTextures.Where(pair => pair.Key.IsFloorWall()).Select(pair => pair.Key).ToList(); + var faces = _faceTextures.Where(pair => pair.Key.Face.IsFloorWall()).Select(pair => pair.Key).ToList(); for (int i = 0; i < faces.Count; i++) MirrorWallTexture(faces[i], oldFaceIsTriangle); @@ -412,44 +408,48 @@ public void Transform(RectTransformation transformation, bool? onlyFloor = null, SectorFaceExtensions.GetExtraFloorSplitFace(Direction.NegativeZ, i)); } + FaceLayerInfo + floorKey = new(SectorFace.Floor, layer), + floorTriangle2Key = new(SectorFace.Floor_Triangle2, layer); + // Fix floor textures if (Floor.IsQuad) { - if (_faceTextures.ContainsKey(SectorFace.Floor)) - _faceTextures[SectorFace.Floor] = _faceTextures[SectorFace.Floor].Transform(transformation * new RectTransformation { QuadrantRotation = 2 }); + if (_faceTextures.ContainsKey(floorKey)) + _faceTextures[floorKey] = _faceTextures[floorKey].Transform(transformation * new RectTransformation { QuadrantRotation = 2 }); } else { // Mirror if (transformation.MirrorX) { - _faceTextures.TrySwap(SectorFace.Floor, SectorFace.Floor_Triangle2); + _faceTextures.TrySwap(floorKey, floorTriangle2Key); - if (_faceTextures.ContainsKey(SectorFace.Floor)) + if (_faceTextures.ContainsKey(floorKey)) { - TextureArea floor = _faceTextures[SectorFace.Floor]; + TextureArea floor = _faceTextures[floorKey]; Swap.Do(ref floor.TexCoord0, ref floor.TexCoord2); - _faceTextures[SectorFace.Floor] = floor; + _faceTextures[floorKey] = floor; } - if (_faceTextures.ContainsKey(SectorFace.Floor_Triangle2)) + if (_faceTextures.ContainsKey(floorTriangle2Key)) { - TextureArea floorTriangle2 = _faceTextures[SectorFace.Floor_Triangle2]; + TextureArea floorTriangle2 = _faceTextures[floorTriangle2Key]; Swap.Do(ref floorTriangle2.TexCoord0, ref floorTriangle2.TexCoord2); - _faceTextures[SectorFace.Floor_Triangle2] = floorTriangle2; + _faceTextures[floorTriangle2Key] = floorTriangle2; } if (Floor.DiagonalSplit != DiagonalSplit.None) // REMOVE this when we have better diaognal steps. - _faceTextures.TrySwap(SectorFace.Floor, SectorFace.Floor_Triangle2); + _faceTextures.TrySwap(floorKey, floorTriangle2Key); } // Rotation for (int i = 0; i < transformation.QuadrantRotation; ++i) { if (!oldFloorSplitDirectionIsXEqualsZReal) - _faceTextures.TrySwap(SectorFace.Floor, SectorFace.Floor_Triangle2); + _faceTextures.TrySwap(floorKey, floorTriangle2Key); if (Floor.DiagonalSplit != DiagonalSplit.None) // REMOVE this when we have better diaognal steps. - _faceTextures.TrySwap(SectorFace.Floor, SectorFace.Floor_Triangle2); + _faceTextures.TrySwap(floorKey, floorTriangle2Key); oldFloorSplitDirectionIsXEqualsZReal = !oldFloorSplitDirectionIsXEqualsZReal; } @@ -460,7 +460,7 @@ public void Transform(RectTransformation transformation, bool? onlyFloor = null, // Fix upper wall textures if (transformation.MirrorX) { - var faces = _faceTextures.Where(pair => pair.Key.IsCeilingWall()).Select(pair => pair.Key).ToList(); + var faces = _faceTextures.Where(pair => pair.Key.Face.IsCeilingWall()).Select(pair => pair.Key).ToList(); for (int i = 0; i < faces.Count; i++) MirrorWallTexture(faces[i], oldFaceIsTriangle); @@ -477,44 +477,48 @@ public void Transform(RectTransformation transformation, bool? onlyFloor = null, SectorFaceExtensions.GetExtraCeilingSplitFace(Direction.NegativeZ, i)); } + FaceLayerInfo + ceilingKey = new(SectorFace.Ceiling, layer), + ceilingTriangle2Key = new(SectorFace.Ceiling_Triangle2, layer); + // Fix ceiling textures if (Ceiling.IsQuad) { - if (_faceTextures.ContainsKey(SectorFace.Ceiling)) - _faceTextures[SectorFace.Ceiling] = _faceTextures[SectorFace.Ceiling].Transform(transformation * new RectTransformation { QuadrantRotation = 2 }); + if (_faceTextures.ContainsKey(ceilingKey)) + _faceTextures[ceilingKey] = _faceTextures[ceilingKey].Transform(transformation * new RectTransformation { QuadrantRotation = 2 }); } else { // Mirror if (transformation.MirrorX) { - _faceTextures.TrySwap(SectorFace.Ceiling, SectorFace.Ceiling_Triangle2); + _faceTextures.TrySwap(ceilingKey, ceilingTriangle2Key); - if (_faceTextures.ContainsKey(SectorFace.Ceiling)) + if (_faceTextures.ContainsKey(ceilingKey)) { - TextureArea ceiling = _faceTextures[SectorFace.Ceiling]; + TextureArea ceiling = _faceTextures[ceilingKey]; Swap.Do(ref ceiling.TexCoord0, ref ceiling.TexCoord2); - _faceTextures[SectorFace.Ceiling] = ceiling; + _faceTextures[ceilingKey] = ceiling; } - if (_faceTextures.ContainsKey(SectorFace.Ceiling_Triangle2)) + if (_faceTextures.ContainsKey(ceilingTriangle2Key)) { - TextureArea ceilingTriangle2 = _faceTextures[SectorFace.Ceiling_Triangle2]; + TextureArea ceilingTriangle2 = _faceTextures[ceilingTriangle2Key]; Swap.Do(ref ceilingTriangle2.TexCoord0, ref ceilingTriangle2.TexCoord2); - _faceTextures[SectorFace.Ceiling_Triangle2] = ceilingTriangle2; + _faceTextures[ceilingTriangle2Key] = ceilingTriangle2; } if (Ceiling.DiagonalSplit != DiagonalSplit.None) // REMOVE this when we have better diaognal steps. - _faceTextures.TrySwap(SectorFace.Ceiling, SectorFace.Ceiling_Triangle2); + _faceTextures.TrySwap(ceilingKey, ceilingTriangle2Key); } // Rotation for (int i = 0; i < transformation.QuadrantRotation; ++i) { if (!oldCeilingSplitDirectionIsXEqualsZReal) - _faceTextures.TrySwap(SectorFace.Ceiling, SectorFace.Ceiling_Triangle2); + _faceTextures.TrySwap(ceilingKey, ceilingTriangle2Key); if (Ceiling.DiagonalSplit != DiagonalSplit.None) // REMOVE this when we have better diaognal steps. - _faceTextures.TrySwap(SectorFace.Ceiling, SectorFace.Ceiling_Triangle2); + _faceTextures.TrySwap(ceilingKey, ceilingTriangle2Key); oldCeilingSplitDirectionIsXEqualsZReal = !oldCeilingSplitDirectionIsXEqualsZReal; } @@ -524,15 +528,16 @@ public void Transform(RectTransformation transformation, bool? onlyFloor = null, { if (transformation.MirrorX) { - MirrorWallTexture(SectorFace.Wall_PositiveX_Middle, oldFaceIsTriangle); - MirrorWallTexture(SectorFace.Wall_PositiveZ_Middle, oldFaceIsTriangle); - MirrorWallTexture(SectorFace.Wall_NegativeX_Middle, oldFaceIsTriangle); - MirrorWallTexture(SectorFace.Wall_NegativeZ_Middle, oldFaceIsTriangle); - MirrorWallTexture(SectorFace.Wall_Diagonal_Middle, oldFaceIsTriangle); + MirrorWallTexture(new(SectorFace.Wall_PositiveX_Middle, layer), oldFaceIsTriangle); + MirrorWallTexture(new(SectorFace.Wall_PositiveZ_Middle, layer), oldFaceIsTriangle); + MirrorWallTexture(new(SectorFace.Wall_NegativeX_Middle, layer), oldFaceIsTriangle); + MirrorWallTexture(new(SectorFace.Wall_NegativeZ_Middle, layer), oldFaceIsTriangle); + MirrorWallTexture(new(SectorFace.Wall_Diagonal_Middle, layer), oldFaceIsTriangle); } transformation.TransformValueQuad(_faceTextures, SectorFace.Wall_PositiveX_Middle, SectorFace.Wall_PositiveZ_Middle, SectorFace.Wall_NegativeX_Middle, SectorFace.Wall_NegativeZ_Middle); } + } } public void FixHeights(SectorVerticalPart? vertical = null, int snapHeight = 0) diff --git a/TombLib/TombLib/LevelData/SectorEnums/FaceLayer.cs b/TombLib/TombLib/LevelData/SectorEnums/FaceLayer.cs new file mode 100644 index 000000000..c10e5648e --- /dev/null +++ b/TombLib/TombLib/LevelData/SectorEnums/FaceLayer.cs @@ -0,0 +1,8 @@ +namespace TombLib.LevelData.SectorEnums; + +public enum FaceLayer : byte +{ + Base, + Overlay, + Count +} diff --git a/TombLib/TombLib/LevelData/SectorStructs/FaceLayerInfo.cs b/TombLib/TombLib/LevelData/SectorStructs/FaceLayerInfo.cs new file mode 100644 index 000000000..b4d4e9ca6 --- /dev/null +++ b/TombLib/TombLib/LevelData/SectorStructs/FaceLayerInfo.cs @@ -0,0 +1,24 @@ +using System; +using TombLib.LevelData.SectorEnums; + +namespace TombLib.LevelData.SectorStructs; + +public readonly struct FaceLayerInfo : IEquatable +{ + public SectorFace Face { get; } + public FaceLayer Layer { get; } + + public FaceLayerInfo(SectorFace face, FaceLayer layer) + { + Face = face; + Layer = layer; + } + + public override int GetHashCode() => HashCode.Combine(Face, Layer); + + public bool Equals(FaceLayerInfo other) => Face == other.Face && Layer == other.Layer; + public override bool Equals(object obj) => obj is FaceLayerInfo other && Equals(other); + + public static bool operator ==(FaceLayerInfo first, FaceLayerInfo second) => first.Equals(second); + public static bool operator !=(FaceLayerInfo first, FaceLayerInfo second) => !first.Equals(second); +} diff --git a/TombLib/TombLib/LevelData/SectorStructs/SectorFaceIdentity.cs b/TombLib/TombLib/LevelData/SectorStructs/SectorFaceIdentity.cs index f61b13730..b9c19fa38 100644 --- a/TombLib/TombLib/LevelData/SectorStructs/SectorFaceIdentity.cs +++ b/TombLib/TombLib/LevelData/SectorStructs/SectorFaceIdentity.cs @@ -1,5 +1,4 @@ using System; -using TombLib.LevelData.SectorEnums; namespace TombLib.LevelData.SectorStructs; @@ -8,21 +7,22 @@ namespace TombLib.LevelData.SectorStructs; /// public readonly struct SectorFaceIdentity : IEquatable, IComparable, IComparable { - public readonly VectorInt2 Position; - public readonly SectorFace Face; + public VectorInt2 Position { get; } + public FaceLayerInfo Face { get; } - public SectorFaceIdentity(int x, int z, SectorFace face) + public SectorFaceIdentity(int x, int z, FaceLayerInfo face) { Position = new VectorInt2(x, z); Face = face; } - public override readonly bool Equals(object other) => other is SectorFaceIdentity identity && identity.Equals(other); - public readonly bool Equals(SectorFaceIdentity other) => Position == other.Position && Face == other.Face; - public override int GetHashCode() => Position.GetHashCode() ^ (1200049507 * (int)Face); // Random prime + public override int GetHashCode() => HashCode.Combine(Position, Face.Face, Face.Layer); - readonly int IComparable.CompareTo(object other) => CompareTo((SectorFaceIdentity)other); - public readonly int CompareTo(SectorFaceIdentity other) + public bool Equals(SectorFaceIdentity other) => Position == other.Position && Face == other.Face; + public override bool Equals(object other) => other is SectorFaceIdentity identity && Equals(identity); + + int IComparable.CompareTo(object other) => CompareTo((SectorFaceIdentity)other); + public int CompareTo(SectorFaceIdentity other) { if (Position.X != other.Position.X) return Position.X > other.Position.X ? 1 : -1; @@ -30,14 +30,17 @@ public readonly int CompareTo(SectorFaceIdentity other) if (Position.Y != other.Position.Y) return Position.Y > other.Position.Y ? 1 : -1; - if (Face != other.Face) - return Face > other.Face ? 1 : -1; + if (Face.Face != other.Face.Face) + return Face.Face > other.Face.Face ? 1 : -1; + + if (Face.Layer != other.Face.Layer) + return Face.Layer > other.Face.Layer ? 1 : -1; return 0; } public static bool operator ==(SectorFaceIdentity left, SectorFaceIdentity right) => left.Equals(right); - public static bool operator !=(SectorFaceIdentity left, SectorFaceIdentity right) => !(left == right); + public static bool operator !=(SectorFaceIdentity left, SectorFaceIdentity right) => !left.Equals(right); public static bool operator <(SectorFaceIdentity left, SectorFaceIdentity right) => left.CompareTo(right) < 0; public static bool operator <=(SectorFaceIdentity left, SectorFaceIdentity right) => left.CompareTo(right) <= 0; public static bool operator >(SectorFaceIdentity left, SectorFaceIdentity right) => left.CompareTo(right) > 0; diff --git a/TombLib/TombLib/Results/ApplyTextureResult.cs b/TombLib/TombLib/Results/ApplyTextureResult.cs new file mode 100644 index 000000000..cd5c5b7e4 --- /dev/null +++ b/TombLib/TombLib/Results/ApplyTextureResult.cs @@ -0,0 +1,28 @@ +namespace TombLib.Results; + +/// +/// Result of applying a texture to a face in a room. +/// +public readonly struct ApplyTextureResult +{ + /// + /// Whether the texture application was successful - changes were made. + /// + public bool Success { get; } + + /// + /// Whether the geometry needs to be rebuilt after applying the texture. + /// + public bool NeedsGeometryRebuild { get; } + + public ApplyTextureResult(bool success, bool needsGeometryRebuild) + { + Success = success; + NeedsGeometryRebuild = needsGeometryRebuild; + } + + /// + /// Result indicating that the texture application was not successful - no changes were made. + /// + public static ApplyTextureResult NoChange => new(false, false); +} diff --git a/TombLib/TombLib/Utils/RectTransformation.cs b/TombLib/TombLib/Utils/RectTransformation.cs index 60b6ced5c..c64772286 100644 --- a/TombLib/TombLib/Utils/RectTransformation.cs +++ b/TombLib/TombLib/Utils/RectTransformation.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Numerics; using TombLib.LevelData.SectorEnums; +using TombLib.LevelData.SectorStructs; namespace TombLib.Utils { @@ -25,24 +26,33 @@ public int QuadrantRotation } } - public void TransformValueQuad(Dictionary faceTextures, SectorFace rotation0, SectorFace rotation1, SectorFace rotation2, SectorFace rotation3) + public void TransformValueQuad(Dictionary faceTextures, SectorFace rotation0, SectorFace rotation1, SectorFace rotation2, SectorFace rotation3) { - if (MirrorX) - faceTextures.TrySwap(rotation0, rotation2); - - for (int i = 0; i < QuadrantRotation; ++i) + for (FaceLayer layer = 0; layer < FaceLayer.Count; layer++) { - TextureArea? temp = null; + FaceLayerInfo + key0 = new(rotation0, layer), + key1 = new(rotation1, layer), + key2 = new(rotation2, layer), + key3 = new(rotation3, layer); + + if (MirrorX) + faceTextures.TrySwap(key0, key2); + + for (int i = 0; i < QuadrantRotation; ++i) + { + TextureArea? temp = null; - if (faceTextures.ContainsKey(rotation0)) - temp = faceTextures[rotation0]; + if (faceTextures.ContainsKey(key0)) + temp = faceTextures[key0]; - faceTextures.TrySwap(rotation0, rotation3); - faceTextures.TrySwap(rotation3, rotation2); - faceTextures.TrySwap(rotation2, rotation1); + faceTextures.TrySwap(key0, key3); + faceTextures.TrySwap(key3, key2); + faceTextures.TrySwap(key2, key1); - if (temp.HasValue) - faceTextures[rotation1] = temp.Value; + if (temp.HasValue) + faceTextures[key1] = temp.Value; + } } }