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,