diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj
index 3ab6981da5..d427509607 100644
--- a/src/openrct2/libopenrct2.vcxproj
+++ b/src/openrct2/libopenrct2.vcxproj
@@ -340,6 +340,7 @@
+
@@ -850,6 +851,7 @@
+
@@ -1076,4 +1078,4 @@
-
+
\ No newline at end of file
diff --git a/src/openrct2/object/LargeSceneryEntry.h b/src/openrct2/object/LargeSceneryEntry.h
index 13260f0771..53b675514d 100644
--- a/src/openrct2/object/LargeSceneryEntry.h
+++ b/src/openrct2/object/LargeSceneryEntry.h
@@ -11,6 +11,7 @@
#include "../common.h"
#include "../interface/Cursors.h"
#include "../world/Location.hpp"
+#include "../world/Scenery.h"
#include "ObjectTypes.h"
struct LargeSceneryText;
@@ -23,6 +24,8 @@ struct LargeSceneryTile
uint8_t z_clearance;
// CCCC WWWW 0SS0 0000
uint16_t flags;
+ SceneryBoundBoxes boundBoxes = {};
+ CoordsXYZ spriteOffset = {};
};
enum
diff --git a/src/openrct2/object/LargeSceneryObject.cpp b/src/openrct2/object/LargeSceneryObject.cpp
index 56b85a376f..cda2d1defd 100644
--- a/src/openrct2/object/LargeSceneryObject.cpp
+++ b/src/openrct2/object/LargeSceneryObject.cpp
@@ -19,9 +19,53 @@
#include "../localisation/Language.h"
#include "../world/Banner.h"
#include "../world/Location.hpp"
+#include "SceneryBoundingBox.h"
#include
+static DefaultBoundingBoxType boundBoxTypes[16] = {
+ DefaultBoundingBoxType::FullTileBox, // 0000
+ DefaultBoundingBoxType::FullTileSouthQuadrantBox, // 0001
+ DefaultBoundingBoxType::FullTileWestQuadrantBox, // 0010
+ DefaultBoundingBoxType::FullTileSouthwestSideBox, // 0011
+ DefaultBoundingBoxType::FullTileNorthQuadrantBox, // 0100
+ DefaultBoundingBoxType::FullTileBox, // 0101 (diagonal of South and North corners)
+ DefaultBoundingBoxType::FullTileNorthwestSideBox, // 0110
+ DefaultBoundingBoxType::FullTileBox, // 0111 (triangle of South, West, and North corners)
+ DefaultBoundingBoxType::FullTileEastQuadrantBox, // 1000
+ DefaultBoundingBoxType::FullTileSoutheastSideBox, // 1001
+ DefaultBoundingBoxType::FullTileBox, // 1010 (diagonal of East and West corners)
+ DefaultBoundingBoxType::FullTileBox, // 1011 (triangle of South, West, and East corners)
+ DefaultBoundingBoxType::FullTileNortheastSideBox, // 1100
+ DefaultBoundingBoxType::FullTileBox, // 1101 (triangle of South, West, and North corners)
+ DefaultBoundingBoxType::FullTileBox, // 1110 (triangle of West, North, and East corners)
+ DefaultBoundingBoxType::FullTileBox, // 1111
+};
+
+static int32_t getBoundBoxHeight(uint8_t clearanceHeight)
+{
+ return std::min(clearanceHeight, 128) - 3;
+}
+
+static void SetTileBoundingBox(LargeSceneryTile& tile)
+{
+ if (tile.flags & 0xF00)
+ {
+ tile.boundBoxes = GetDefaultSceneryBoundBoxes(boundBoxTypes[(tile.flags & 0xF000) >> 12]);
+ }
+ else
+ {
+ tile.boundBoxes = GetDefaultSceneryBoundBoxes(DefaultBoundingBoxType::FullTileLargeBox);
+ }
+ tile.spriteOffset = GetDefaultSpriteOffset(DefaultSpriteOffsetType::LargeSceneryOffset);
+
+ auto clearanceHeight = getBoundBoxHeight(tile.z_clearance);
+ for (uint8_t i = 0; i < NumOrthogonalDirections; i++)
+ {
+ tile.boundBoxes[i].length.z = clearanceHeight;
+ }
+}
+
static RCTLargeSceneryText ReadLegacy3DFont(OpenRCT2::IStream& stream)
{
RCTLargeSceneryText _3dFontLegacy = {};
@@ -167,6 +211,7 @@ std::vector LargeSceneryObject::ReadTiles(OpenRCT2::IStream* s
tile.z_offset = stream->ReadValue();
tile.z_clearance = stream->ReadValue();
tile.flags = stream->ReadValue();
+ SetTileBoundingBox(tile);
return tile;
};
@@ -255,6 +300,15 @@ std::vector LargeSceneryObject::ReadJsonTiles(json_t& jTiles)
auto walls = Json::GetNumber(jTile["walls"]);
tile.flags |= (walls & 0xFF) << 8;
+ SetTileBoundingBox(tile);
+ auto jBBox = jTile["boundingBox"];
+ if (!jBBox.empty())
+ {
+ tile.boundBoxes = ReadBoundBoxes(jBBox, tile.boundBoxes[0].length.z, false);
+ }
+ auto jSpriteOffset = jTile["spriteOffsetCoordinates"];
+ if (!jSpriteOffset.empty())
+ tile.spriteOffset = ReadSpriteOffset(jSpriteOffset);
tiles.push_back(std::move(tile));
}
diff --git a/src/openrct2/object/SceneryBoundingBox.cpp b/src/openrct2/object/SceneryBoundingBox.cpp
new file mode 100644
index 0000000000..11a50a13fe
--- /dev/null
+++ b/src/openrct2/object/SceneryBoundingBox.cpp
@@ -0,0 +1,261 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2024 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#include "SceneryBoundingBox.h"
+
+constexpr std::array DefaultSpriteOffsets = {
+ CoordsXYZ(7, 7, 0), // quarter tile
+ CoordsXYZ(15, 15, 0), // small scenery full tile w/o VOFFSET_CENTRE
+ CoordsXYZ(3, 3, 0), // small scenery halftile/VOFFSET_CENTRE
+ CoordsXYZ(1, 1, 0), // small scenery VOFFSET_CENTER+NO_WALLS
+ CoordsXYZ(0, 0, 0), // large scenery
+};
+
+constexpr SceneryBoundBoxes QuarterTile = {
+ BoundBoxXYZ({ 7, 7, 0 }, { 2, 2, 0 }),
+ BoundBoxXYZ({ 7, 7, 0 }, { 2, 2, 0 }),
+ BoundBoxXYZ({ 7, 7, 0 }, { 2, 2, 0 }),
+ BoundBoxXYZ({ 7, 7, 0 }, { 2, 2, 0 }),
+};
+
+constexpr SceneryBoundBoxes HalfTile = {
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 26, 0 }),
+ BoundBoxXYZ({ 3, 17, 0 }, { 26, 12, 0 }),
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 26, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 26, 12, 0 }),
+};
+
+// LargeSpecial corner/side match sub-fulltile large scenery boundboxes
+constexpr SceneryBoundBoxes FullTileNorthQuadrant = {
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 3, 17, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 17, 17, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 12, 0 }),
+};
+constexpr SceneryBoundBoxes FullTileNortheastSide = {
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 28, 0 }),
+ BoundBoxXYZ({ 3, 17, 0 }, { 26, 12, 0 }),
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 26, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 28, 12, 0 }),
+};
+constexpr SceneryBoundBoxes FullTileEastQuadrant = {
+ BoundBoxXYZ({ 3, 17, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 17, 17, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 12, 0 }),
+};
+constexpr SceneryBoundBoxes FullTileSoutheastSide = {
+ BoundBoxXYZ({ 3, 17, 0 }, { 26, 12, 0 }),
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 26, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 28, 12, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 28, 0 }),
+};
+constexpr SceneryBoundBoxes FullTileSouthQuadrant = {
+ BoundBoxXYZ({ 17, 17, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 3, 17, 0 }, { 12, 12, 0 }),
+};
+constexpr SceneryBoundBoxes FullTileSouthwestSide = {
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 26, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 28, 12, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 28, 0 }),
+ BoundBoxXYZ({ 3, 17, 0 }, { 26, 12, 0 }),
+};
+constexpr SceneryBoundBoxes FullTileWestQuadrant = {
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 3, 17, 0 }, { 12, 12, 0 }),
+ BoundBoxXYZ({ 17, 17, 0 }, { 12, 12, 0 }),
+};
+constexpr SceneryBoundBoxes FullTileNorthwestSide = {
+ BoundBoxXYZ({ 3, 3, 0 }, { 28, 12, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 12, 28, 0 }),
+ BoundBoxXYZ({ 3, 17, 0 }, { 26, 12, 0 }),
+ BoundBoxXYZ({ 17, 3, 0 }, { 12, 26, 0 }),
+};
+
+// LargeSpecialCenter matches large scenery with allowed walls and small scenery with SMALL_SCENERY_FLAG_VOFFSET_CENTRE
+constexpr SceneryBoundBoxes FullTile = {
+ BoundBoxXYZ({ 3, 3, 0 }, { 26, 26, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 26, 26, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 26, 26, 0 }),
+ BoundBoxXYZ({ 3, 3, 0 }, { 26, 26, 0 }),
+};
+
+// Large matches large scenery and small scenery that do not allow walls.
+constexpr SceneryBoundBoxes FullTileLarge = {
+ BoundBoxXYZ({ 1, 1, 0 }, { 30, 30, 0 }),
+ BoundBoxXYZ({ 1, 1, 0 }, { 30, 30, 0 }),
+ BoundBoxXYZ({ 1, 1, 0 }, { 30, 30, 0 }),
+ BoundBoxXYZ({ 1, 1, 0 }, { 30, 30, 0 }),
+};
+
+// Small Scenery without VOFFSET_CENTRE flag set
+constexpr SceneryBoundBoxes FullTileThin = {
+ BoundBoxXYZ({ 15, 15, 0 }, { 2, 2, 0 }),
+ BoundBoxXYZ({ 15, 15, 0 }, { 2, 2, 0 }),
+ BoundBoxXYZ({ 15, 15, 0 }, { 2, 2, 0 }),
+ BoundBoxXYZ({ 15, 15, 0 }, { 2, 2, 0 }),
+};
+
+static const std::array boundBoxes = {
+ QuarterTile,
+ HalfTile,
+ FullTileNorthQuadrant,
+ FullTileNortheastSide,
+ FullTileEastQuadrant,
+ FullTileSoutheastSide,
+ FullTileSouthQuadrant,
+ FullTileSouthwestSide,
+ FullTileWestQuadrant,
+ FullTileNorthwestSide,
+ FullTile,
+ FullTileLarge,
+ FullTileThin,
+};
+
+#pragma endregion
+
+static const EnumMap BBoxTypeLookup{
+ { "quarterTile", DefaultBoundingBoxType::QuarterTileBox },
+ { "halfTile", DefaultBoundingBoxType::HalfTileBox },
+ { "cornerNorth", DefaultBoundingBoxType::FullTileNorthQuadrantBox },
+ { "sideNortheast", DefaultBoundingBoxType::FullTileNortheastSideBox },
+ { "cornerEast", DefaultBoundingBoxType::FullTileEastQuadrantBox },
+ { "sideSoutheast", DefaultBoundingBoxType::FullTileSoutheastSideBox },
+ { "cornerSouth", DefaultBoundingBoxType::FullTileSouthQuadrantBox },
+ { "sideSouthwest", DefaultBoundingBoxType::FullTileSouthwestSideBox },
+ { "cornerEast", DefaultBoundingBoxType::FullTileWestQuadrantBox },
+ { "sideNorthwest", DefaultBoundingBoxType::FullTileNorthwestSideBox },
+ { "fullTile", DefaultBoundingBoxType::FullTileBox },
+ { "fullTileLarge", DefaultBoundingBoxType::FullTileLargeBox },
+ { "fullTileThin", DefaultBoundingBoxType::FullTileThinBox }
+};
+
+static DefaultBoundingBoxType GetBoundingBoxTypeFromString(const std::string& s)
+{
+ auto result = BBoxTypeLookup.find(s);
+ return (result != BBoxTypeLookup.end()) ? result->second : DefaultBoundingBoxType::FullTileBox;
+}
+
+SceneryBoundBoxes GetDefaultSceneryBoundBoxes(DefaultBoundingBoxType type)
+{
+ if (type >= DefaultBoundingBoxType::CountBox)
+ return boundBoxes[DefaultBoundingBoxType::FullTileBox];
+ return boundBoxes[type];
+}
+
+static BoundBoxXYZ ReadBoundBox(json_t& box)
+{
+ auto jOffset = box["offset"];
+ auto jLength = box["size"];
+
+ Guard::Assert(
+ jOffset.is_array() && jLength.is_array() && jOffset.size() >= 3 && jLength.size() >= 3,
+ "ReadBoundBox expects arrays 'offset' and 'size' with 3 elements");
+ BoundBoxXYZ boundBox = {
+ {
+ Json::GetNumber(jOffset[0], 0),
+ Json::GetNumber(jOffset[1], 0),
+ Json::GetNumber(jOffset[2], 0),
+ },
+ {
+ Json::GetNumber(jLength[0], 0),
+ Json::GetNumber(jLength[1], 0),
+ Json::GetNumber(jLength[2], 0),
+ },
+ };
+ return boundBox;
+}
+
+// Rotates a BoundBoxXYZ clockwise 90 degrees around the vertical axis.
+static BoundBoxXYZ RotateBoundBox(BoundBoxXYZ box, CoordsXY rotationCenter)
+{
+ CoordsXYZ rotatedLength = { box.length.y, box.length.x, box.length.z };
+ // equations are performed in double scale to avoid decimals
+ CoordsXY relativeCentroid = box.offset * 2 + box.length - rotationCenter * 2;
+ CoordsXY rotatedCentroid = { relativeCentroid.y, -relativeCentroid.x };
+ CoordsXY newCorner = (rotatedCentroid - rotatedLength) / 2 + rotationCenter;
+ return { { newCorner.x, newCorner.y, box.offset.z }, rotatedLength };
+}
+
+SceneryBoundBoxes ReadBoundBoxes(json_t& jBBox, int32_t defaultHeight, bool fullTile)
+{
+ SceneryBoundBoxes boxes;
+ if (jBBox.is_array())
+ {
+ Guard::Assert(jBBox.size() >= 4, "boundBox arrays require four elements, one for each view angle");
+ // array of four bboxes
+ for (uint8_t i = 0; i < NumOrthogonalDirections; i++)
+ boxes[i] = ReadBoundBox(jBBox[i]);
+ }
+ else if (jBBox.is_object())
+ {
+ // single box, rotated around (16, 16) if fulltile or (8,8) if quarter tile
+ CoordsXY rotationCenter = { 8, 8 };
+ if (fullTile)
+ {
+ rotationCenter = { 16, 16 };
+ }
+ auto bBox = ReadBoundBox(jBBox);
+ boxes[0] = bBox;
+ boxes[1] = RotateBoundBox(bBox, rotationCenter);
+ boxes[2] = RotateBoundBox(boxes[1], rotationCenter);
+ boxes[3] = RotateBoundBox(boxes[2], rotationCenter);
+ }
+ else
+ {
+ Guard::Assert(
+ jBBox.is_string(),
+ "boundBox must be an array of four boundBox objects, a single boundBox object, or a string matching the "
+ "DefaultBoundingBoxType enum.");
+ boxes = GetDefaultSceneryBoundBoxes(GetBoundingBoxTypeFromString(Json::GetString(jBBox)));
+ for (uint8_t i = 0; i < NumOrthogonalDirections; i++)
+ boxes[i].length.z = defaultHeight;
+ }
+ return boxes;
+}
+
+static const EnumMap SpriteOffsetLookup{
+ { "quarterTile", DefaultSpriteOffsetType::QuarterTileOffset },
+ { "fullTileThin", DefaultSpriteOffsetType::FullTileThinOffset },
+ { "halfTile", DefaultSpriteOffsetType::FullTileOffset },
+ { "fullTile", DefaultSpriteOffsetType::FullTileOffset },
+ { "fullTileLarge", DefaultSpriteOffsetType::FullTileLargeOffset },
+ { "largeScenery", DefaultSpriteOffsetType::LargeSceneryOffset },
+};
+
+static DefaultSpriteOffsetType GetSpriteOffsetTypeFromString(const std::string& s)
+{
+ auto result = SpriteOffsetLookup.find(s);
+ return (result != SpriteOffsetLookup.end()) ? result->second : DefaultSpriteOffsetType::LargeSceneryOffset;
+}
+
+CoordsXYZ GetDefaultSpriteOffset(DefaultSpriteOffsetType type)
+{
+ if (type >= DefaultSpriteOffsetType::CountOffset)
+ return DefaultSpriteOffsets[DefaultSpriteOffsetType::LargeSceneryOffset];
+ return DefaultSpriteOffsets[type];
+}
+
+CoordsXYZ ReadSpriteOffset(json_t& jCoords)
+{
+ if (jCoords.is_string())
+ {
+ return DefaultSpriteOffsets[GetSpriteOffsetTypeFromString(Json::GetString(jCoords))];
+ }
+ Guard::Assert(jCoords.is_array() && jCoords.size() >= 3, "spriteOffsetCoordinates must be an array with three elements");
+ CoordsXYZ coordinates = {
+ Json::GetNumber(jCoords[0], 0),
+ Json::GetNumber(jCoords[1], 0),
+ Json::GetNumber(jCoords[2], 0),
+ };
+ return coordinates;
+}
diff --git a/src/openrct2/object/SceneryBoundingBox.h b/src/openrct2/object/SceneryBoundingBox.h
new file mode 100644
index 0000000000..92aac01280
--- /dev/null
+++ b/src/openrct2/object/SceneryBoundingBox.h
@@ -0,0 +1,49 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2024 OpenRCT2 developers
+ *
+ * For a complete list of all authors, please refer to contributors.md
+ * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
+ *
+ * OpenRCT2 is licensed under the GNU General Public License version 3.
+ *****************************************************************************/
+
+#pragma once
+
+#include "../core/EnumMap.hpp"
+#include "../core/Json.hpp"
+#include "../core/Memory.hpp"
+#include "../paint/Boundbox.h"
+#include "../world/Scenery.h"
+
+enum DefaultBoundingBoxType : uint8_t
+{
+ QuarterTileBox,
+ HalfTileBox,
+ FullTileNorthQuadrantBox,
+ FullTileNortheastSideBox,
+ FullTileEastQuadrantBox,
+ FullTileSoutheastSideBox,
+ FullTileSouthQuadrantBox,
+ FullTileSouthwestSideBox,
+ FullTileWestQuadrantBox,
+ FullTileNorthwestSideBox,
+ FullTileBox,
+ FullTileLargeBox,
+ FullTileThinBox,
+ CountBox
+};
+
+enum DefaultSpriteOffsetType : uint8_t
+{
+ QuarterTileOffset,
+ FullTileThinOffset,
+ FullTileOffset,
+ FullTileLargeOffset,
+ LargeSceneryOffset,
+ CountOffset
+};
+
+SceneryBoundBoxes GetDefaultSceneryBoundBoxes(DefaultBoundingBoxType type);
+SceneryBoundBoxes ReadBoundBoxes(json_t& jBBox, int32_t defaultHeight, bool fullTile);
+CoordsXYZ GetDefaultSpriteOffset(DefaultSpriteOffsetType type);
+CoordsXYZ ReadSpriteOffset(json_t& jCoords);
diff --git a/src/openrct2/object/SmallSceneryEntry.h b/src/openrct2/object/SmallSceneryEntry.h
index 0b6b0d028c..26dc38569c 100644
--- a/src/openrct2/object/SmallSceneryEntry.h
+++ b/src/openrct2/object/SmallSceneryEntry.h
@@ -11,6 +11,7 @@
#include "../common.h"
#include "../interface/Cursors.h"
+#include "../world/Scenery.h"
#include "ObjectTypes.h"
enum SMALL_SCENERY_FLAGS : uint32_t
@@ -67,6 +68,8 @@ struct SmallSceneryEntry
uint16_t animation_mask;
uint16_t num_frames;
ObjectEntryIndex scenery_tab_id;
+ SceneryBoundBoxes boundBoxes;
+ CoordsXYZ spriteOffset;
constexpr bool HasFlag(const uint32_t _flags) const
{
diff --git a/src/openrct2/object/SmallSceneryObject.cpp b/src/openrct2/object/SmallSceneryObject.cpp
index 7c07041125..9b43c8084a 100644
--- a/src/openrct2/object/SmallSceneryObject.cpp
+++ b/src/openrct2/object/SmallSceneryObject.cpp
@@ -19,6 +19,7 @@
#include "../interface/Cursors.h"
#include "../localisation/Language.h"
#include "../world/Scenery.h"
+#include "SceneryBoundingBox.h"
void SmallSceneryObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStream* stream)
{
@@ -33,6 +34,7 @@ void SmallSceneryObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStre
_legacyType.animation_mask = stream->ReadValue();
_legacyType.num_frames = stream->ReadValue();
_legacyType.scenery_tab_id = OBJECT_ENTRY_INDEX_NULL;
+ SetBoundingBoxFromFlags();
GetStringTable().Read(context, stream, ObjectStringID::NAME);
@@ -245,6 +247,17 @@ void SmallSceneryObject::ReadJson(IReadObjectContext* context, json_t& root)
}
}
}
+ SetBoundingBoxFromFlags();
+ auto jBBox = properties["boundingBox"];
+ if (!jBBox.empty())
+ {
+ _legacyType.boundBoxes = ReadBoundBoxes(
+ jBBox, _legacyType.boundBoxes[0].length.z,
+ _legacyType.HasFlag(SMALL_SCENERY_FLAG_FULL_TILE) || _legacyType.HasFlag(SMALL_SCENERY_FLAG_HALF_SPACE));
+ }
+ auto jSpriteOffset = properties["spriteOffsetCoordinates"];
+ if (!jSpriteOffset.empty())
+ _legacyType.spriteOffset = ReadSpriteOffset(jSpriteOffset);
auto jFrameOffsets = properties["frameOffsets"];
if (jFrameOffsets.is_array())
@@ -269,3 +282,50 @@ std::vector SmallSceneryObject::ReadJsonFrameOffsets(json_t& jFrameOffs
}
return offsets;
}
+
+static int32_t getBoundBoxHeight(uint8_t clearanceHeight)
+{
+ int32_t height = clearanceHeight - 4;
+ if (height > 128 || height < 0)
+ {
+ height = 128;
+ }
+ return height - 1;
+}
+
+void SmallSceneryObject::SetBoundingBoxFromFlags()
+{
+ DefaultBoundingBoxType boundBoxType = DefaultBoundingBoxType::QuarterTileBox;
+ DefaultSpriteOffsetType spriteOffsetType = DefaultSpriteOffsetType::QuarterTileOffset;
+ if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
+ {
+ boundBoxType = DefaultBoundingBoxType::FullTileThinBox;
+ spriteOffsetType = DefaultSpriteOffsetType::FullTileThinOffset;
+ if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_HALF_SPACE))
+ {
+ spriteOffsetType = DefaultSpriteOffsetType::FullTileOffset;
+ boundBoxType = DefaultBoundingBoxType::HalfTileBox;
+ }
+ else
+ {
+ if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
+ {
+ boundBoxType = DefaultBoundingBoxType::FullTileBox;
+ spriteOffsetType = DefaultSpriteOffsetType::FullTileOffset;
+ if (_legacyType.HasFlag(SMALL_SCENERY_FLAG_NO_WALLS))
+ {
+ boundBoxType = DefaultBoundingBoxType::FullTileLargeBox;
+ spriteOffsetType = DefaultSpriteOffsetType::FullTileLargeOffset;
+ }
+ }
+ }
+ }
+ _legacyType.spriteOffset = GetDefaultSpriteOffset(spriteOffsetType);
+ _legacyType.boundBoxes = GetDefaultSceneryBoundBoxes(boundBoxType);
+
+ auto clearanceHeight = getBoundBoxHeight(_legacyType.height);
+ for (uint8_t i = 0; i < NumOrthogonalDirections; i++)
+ {
+ _legacyType.boundBoxes[i].length.z = clearanceHeight;
+ }
+}
diff --git a/src/openrct2/object/SmallSceneryObject.h b/src/openrct2/object/SmallSceneryObject.h
index fe2051bd18..1f2c2108d4 100644
--- a/src/openrct2/object/SmallSceneryObject.h
+++ b/src/openrct2/object/SmallSceneryObject.h
@@ -38,4 +38,5 @@ private:
static std::vector ReadFrameOffsets(OpenRCT2::IStream* stream);
static std::vector ReadJsonFrameOffsets(json_t& jFrameOffsets);
void PerformFixes();
+ void SetBoundingBoxFromFlags();
};
diff --git a/src/openrct2/paint/tile_element/Paint.LargeScenery.cpp b/src/openrct2/paint/tile_element/Paint.LargeScenery.cpp
index effd45eefc..39a79e03da 100644
--- a/src/openrct2/paint/tile_element/Paint.LargeScenery.cpp
+++ b/src/openrct2/paint/tile_element/Paint.LargeScenery.cpp
@@ -34,28 +34,6 @@
using namespace OpenRCT2;
-// clang-format off
-static constexpr BoundBoxXY LargeSceneryBoundBoxes[] = {
- { { 3, 3 }, { 26, 26 } },
- { { 17, 17 }, { 12, 12 } },
- { { 17, 3 }, { 12, 12 } },
- { { 17, 3 }, { 12, 26 } },
- { { 3, 3 }, { 12, 12 } },
- { { 3, 3 }, { 26, 26 } },
- { { 3, 3 }, { 28, 12 } },
- { { 3, 3 }, { 26, 26 } },
- { { 3, 17 }, { 12, 12 } },
- { { 3, 17 }, { 26, 12 } },
- { { 3, 3 }, { 26, 26 } },
- { { 3, 3 }, { 26, 26 } },
- { { 3, 3 }, { 12, 28 } },
- { { 3, 3 }, { 26, 26 } },
- { { 3, 3 }, { 26, 26 } },
- { { 3, 3 }, { 26, 26 } },
- { { 1, 1 }, { 30, 30 } },
-};
-// clang-format on
-
static void PaintLargeScenerySupports(
PaintSession& session, uint8_t direction, uint16_t height, const LargeSceneryElement& tileElement, ImageId imageTemplate,
const LargeSceneryTile& tile)
@@ -388,20 +366,13 @@ void PaintLargeScenery(PaintSession& session, uint8_t direction, uint16_t height
}
}
- auto boxlengthZ = std::min(tile->z_clearance, 128) - 3;
- auto flags = tile->flags;
- auto bbIndex = 16;
- if (flags & 0xF00)
- {
- flags &= 0xF000;
- flags = Numerics::rol16(flags, direction);
- bbIndex = (flags & 0xF) | (flags >> 12);
- }
- const CoordsXYZ& bbOffset = { LargeSceneryBoundBoxes[bbIndex].offset, height };
- const CoordsXYZ& bbLength = { LargeSceneryBoundBoxes[bbIndex].length, boxlengthZ };
+ auto boundBox = tile->boundBoxes[direction];
+ auto offset = tile->spriteOffset;
+ boundBox.offset.z += height;
+ offset.z += height;
auto imageIndex = sceneryEntry->image + 4 + (sequenceNum << 2) + direction;
- PaintAddImageAsParent(session, imageTemplate.WithIndex(imageIndex), { 0, 0, height }, { bbOffset, bbLength });
+ PaintAddImageAsParent(session, imageTemplate.WithIndex(imageIndex), offset, boundBox);
if (sceneryEntry->scrolling_mode != SCROLLING_MODE_NONE && direction != 1 && direction != 2)
{
@@ -414,7 +385,8 @@ void PaintLargeScenery(PaintSession& session, uint8_t direction, uint16_t height
auto sequenceDirection2 = (tileElement.GetSequenceIndex() - 1) & 3;
if (sequenceDirection2 == direction)
{
- PaintLargeSceneryScrollingText(session, *sceneryEntry, tileElement, direction, height, bbOffset, isGhost);
+ PaintLargeSceneryScrollingText(
+ session, *sceneryEntry, tileElement, direction, height, boundBox.offset, isGhost);
}
}
}
diff --git a/src/openrct2/paint/tile_element/Paint.SmallScenery.cpp b/src/openrct2/paint/tile_element/Paint.SmallScenery.cpp
index 17f1c507f8..4210285d1e 100644
--- a/src/openrct2/paint/tile_element/Paint.SmallScenery.cpp
+++ b/src/openrct2/paint/tile_element/Paint.SmallScenery.cpp
@@ -26,11 +26,11 @@
using namespace OpenRCT2;
-static constexpr CoordsXY lengths[] = {
- { 12, 26 },
- { 26, 12 },
- { 12, 26 },
- { 26, 12 },
+static constexpr CoordsXY newQuadrantOffsets[NumOrthogonalDirections] = {
+ { 0, 0 },
+ { 0, 16 },
+ { 16, 16 },
+ { 16, 0 },
};
static void PaintSmallScenerySupports(
@@ -119,63 +119,17 @@ static void PaintSmallSceneryBody(
{
PROFILED_FUNCTION();
- BoundBoxXYZ boundBox = { { 0, 0, height }, { 2, 2, 0 } };
+ BoundBoxXYZ boundBox = sceneryEntry->boundBoxes[direction];
+ CoordsXYZ offset = sceneryEntry->spriteOffset;
+ boundBox.offset.z += height;
+ offset.z += height;
- CoordsXYZ offset = { 0, 0, height };
- if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
- {
- if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HALF_SPACE))
- {
- static constexpr CoordsXY sceneryHalfTileOffsets[] = {
- { 3, 3 },
- { 3, 17 },
- { 17, 3 },
- { 3, 3 },
- };
- boundBox.offset.x = sceneryHalfTileOffsets[direction].x;
- boundBox.offset.y = sceneryHalfTileOffsets[direction].y;
- boundBox.length.x = lengths[direction].x;
- boundBox.length.y = lengths[direction].y;
- offset.x = 3;
- offset.y = 3;
- }
- else
- {
- offset.x = 15;
- offset.y = 15;
- if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE))
- {
- offset.x = 3;
- offset.y = 3;
- boundBox.length.x = 26;
- boundBox.length.y = 26;
- if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_NO_WALLS))
- {
- offset.x = 1;
- offset.y = 1;
- boundBox.length.x = 30;
- boundBox.length.y = 30;
- }
- }
- boundBox.offset.x = offset.x;
- boundBox.offset.y = offset.y;
- }
- }
- else
+ if (!sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
{
uint8_t quadrant = (sceneryElement.GetSceneryQuadrant() + session.CurrentRotation) & 3;
- // -1 to maintain compatibility with existing CSOs in context of issue #17616
- offset.x = SceneryQuadrantOffsets[quadrant].x - 1;
- offset.y = SceneryQuadrantOffsets[quadrant].y - 1;
- boundBox.offset.x = offset.x;
- boundBox.offset.y = offset.y;
+ boundBox.offset += newQuadrantOffsets[quadrant];
+ offset += newQuadrantOffsets[quadrant];
}
- boundBox.length.z = sceneryEntry->height - 4;
- if (boundBox.length.z > 128 || boundBox.length.z < 0)
- {
- boundBox.length.z = 128;
- }
- boundBox.length.z--;
ImageIndex baseImageIndex = sceneryEntry->image + direction;
if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_WITHER))
diff --git a/src/openrct2/world/Scenery.h b/src/openrct2/world/Scenery.h
index b30c1795b4..c4b0d39408 100644
--- a/src/openrct2/world/Scenery.h
+++ b/src/openrct2/world/Scenery.h
@@ -10,6 +10,7 @@
#pragma once
#include "../common.h"
+#include "../paint/Boundbox.h"
#include "Location.hpp"
#include "ScenerySelection.h"
@@ -18,6 +19,8 @@
constexpr uint8_t kSceneryWitherAgeThreshold1 = 0x28;
constexpr uint8_t kSceneryWitherAgeThreshold2 = 0x37;
+using SceneryBoundBoxes = std::array;
+
enum
{
SCENERY_TYPE_SMALL,