Skip to content

Commit

Permalink
add boundbox properties in sceneryEntries
Browse files Browse the repository at this point in the history
  • Loading branch information
spacek531 committed Apr 21, 2024
1 parent 9b7face commit 5b7c40b
Show file tree
Hide file tree
Showing 12 changed files with 511 additions and 31 deletions.
4 changes: 3 additions & 1 deletion src/openrct2/libopenrct2.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@
<ClInclude Include="object\ObjectTypes.h" />
<ClInclude Include="object\ResourceTable.h" />
<ClInclude Include="object\RideObject.h" />
<ClInclude Include="object\SceneryBoundingBox.h" />
<ClInclude Include="object\SceneryGroupEntry.h" />
<ClInclude Include="object\SceneryGroupObject.h" />
<ClInclude Include="object\SceneryObject.h" />
Expand Down Expand Up @@ -842,6 +843,7 @@
<ClCompile Include="object\ObjectTypes.cpp" />
<ClCompile Include="object\ResourceTable.cpp" />
<ClCompile Include="object\RideObject.cpp" />
<ClInclude Include="object\SceneryBoundingBox.cpp" />
<ClCompile Include="object\SceneryGroupObject.cpp" />
<ClCompile Include="object\SmallSceneryObject.cpp" />
<ClCompile Include="object\StationObject.cpp" />
Expand Down Expand Up @@ -1065,4 +1067,4 @@
</ClCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>
3 changes: 3 additions & 0 deletions src/openrct2/object/LargeSceneryEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "../common.h"
#include "../interface/Cursors.h"
#include "../world/Location.hpp"
#include "../world/Scenery.h"
#include "ObjectTypes.h"

struct LargeSceneryText;
Expand All @@ -23,6 +24,8 @@ struct LargeSceneryTile
uint8_t z_clearance;
// CCCC WWWW 0SS0 0000
uint16_t flags;
SceneryBoundBoxes boundBoxes = {};
CoordsXYZ spriteOffset = {};
};

enum
Expand Down
54 changes: 54 additions & 0 deletions src/openrct2/object/LargeSceneryObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,54 @@
#include "../localisation/Language.h"
#include "../world/Banner.h"
#include "../world/Location.hpp"
#include "SceneryBoundingBox.h"

#include <algorithm>
#include <iterator>

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<uint8_t>(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 = {};
Expand Down Expand Up @@ -168,6 +212,7 @@ std::vector<LargeSceneryTile> LargeSceneryObject::ReadTiles(OpenRCT2::IStream* s
tile.z_offset = stream->ReadValue<int16_t>();
tile.z_clearance = stream->ReadValue<uint8_t>();
tile.flags = stream->ReadValue<uint16_t>();
SetTileBoundingBox(tile);
return tile;
};

Expand Down Expand Up @@ -256,6 +301,15 @@ std::vector<LargeSceneryTile> LargeSceneryObject::ReadJsonTiles(json_t& jTiles)

auto walls = Json::GetNumber<int16_t>(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));
}
Expand Down
261 changes: 261 additions & 0 deletions src/openrct2/object/SceneryBoundingBox.cpp
Original file line number Diff line number Diff line change
@@ -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<CoordsXYZ, DefaultSpriteOffsetType::CountOffset> 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<SceneryBoundBoxes, DefaultBoundingBoxType::CountBox> boundBoxes = {
QuarterTile,
HalfTile,
FullTileNorthQuadrant,
FullTileNortheastSide,
FullTileEastQuadrant,
FullTileSoutheastSide,
FullTileSouthQuadrant,
FullTileSouthwestSide,
FullTileWestQuadrant,
FullTileNorthwestSide,
FullTile,
FullTileLarge,
FullTileThin,
};

#pragma endregion

static const EnumMap<DefaultBoundingBoxType> 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<int32_t>(jOffset[0], 0),
Json::GetNumber<int32_t>(jOffset[1], 0),
Json::GetNumber<int32_t>(jOffset[2], 0),
},
{
Json::GetNumber<int32_t>(jLength[0], 0),
Json::GetNumber<int32_t>(jLength[1], 0),
Json::GetNumber<int32_t>(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<DefaultSpriteOffsetType> 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<int32_t>(jCoords[0], 0),
Json::GetNumber<int32_t>(jCoords[1], 0),
Json::GetNumber<int32_t>(jCoords[2], 0),
};
return coordinates;
}
Loading

0 comments on commit 5b7c40b

Please sign in to comment.