Compare commits

...

5 Commits

Author SHA1 Message Date
spacek531 71484a11bd
Merge 8da2c36ea5 into 9266a6f0d3 2024-04-28 01:48:59 +00:00
Harry Hopkinson 9266a6f0d3
Remove duplicate sprite_map in TrackPaint.cpp 2024-04-27 21:13:22 +02:00
Michael Steenbeek 03926c46d0
Update to v1.4.4 objects release 2024-04-27 19:30:39 +02:00
Spacek531 8da2c36ea5 remove pseudo-test from code 2024-04-21 20:39:51 -07:00
Spacek531 9e30512cb9 add boundbox properties in sceneryEntries 2024-04-21 20:39:37 -07:00
17 changed files with 469 additions and 112 deletions

View File

@ -64,9 +64,9 @@ set(TITLE_SEQUENCE_VERSION "0.4.6")
set(TITLE_SEQUENCE_URL "https://github.com/OpenRCT2/title-sequences/releases/download/v${TITLE_SEQUENCE_VERSION}/title-sequences.zip")
set(TITLE_SEQUENCE_SHA1 "80fefc6ebbabc42a6f4703412daa5c62f661420d")
set(OBJECTS_VERSION "1.4.3")
set(OBJECTS_VERSION "1.4.4")
set(OBJECTS_URL "https://github.com/OpenRCT2/objects/releases/download/v${OBJECTS_VERSION}/objects.zip")
set(OBJECTS_SHA1 "ac78210ef46465c0f51bbffd6fe21845092af48e")
set(OBJECTS_SHA1 "4e9b5d7f85a07b90ec57fe37517e04b19ee0395c")
set(OPENSFX_VERSION "1.0.5")
set(OPENSFX_URL "https://github.com/OpenRCT2/OpenSoundEffects/releases/download/v${OPENSFX_VERSION}/opensound.zip")

View File

@ -11,6 +11,8 @@
- Fix: [#21696] Fullscreen window option not correctly applied on macOS.
- Fix: [#21787] Map generator heightmap should respect increased height limits.
- Fix: [#21829] When creating a new scenario, the default name contains formatting codes.
- Fix: [objects#324] Cannot build Colosseum inside a turn or helix.
- Fix: [objects#325] Sloped castle walls are vertically offset by one pixel (original bug).
0.4.10 (2024-04-02)
------------------------------------------------------------------------

View File

@ -45,8 +45,8 @@
<LibsSha1 Condition="'$(Platform)'=='Win32'">f3636b530110c7592deab23ca0d0c1916fc7e44c</LibsSha1>
<TitleSequencesUrl>https://github.com/OpenRCT2/title-sequences/releases/download/v0.4.6/title-sequences.zip</TitleSequencesUrl>
<TitleSequencesSha1>80fefc6ebbabc42a6f4703412daa5c62f661420d</TitleSequencesSha1>
<ObjectsUrl>https://github.com/OpenRCT2/objects/releases/download/v1.4.3/objects.zip</ObjectsUrl>
<ObjectsSha1>ac78210ef46465c0f51bbffd6fe21845092af48e</ObjectsSha1>
<ObjectsUrl>https://github.com/OpenRCT2/objects/releases/download/v1.4.4/objects.zip</ObjectsUrl>
<ObjectsSha1>4e9b5d7f85a07b90ec57fe37517e04b19ee0395c</ObjectsSha1>
<OpenSFXUrl>https://github.com/OpenRCT2/OpenSoundEffects/releases/download/v1.0.5/opensound.zip</OpenSFXUrl>
<OpenSFXSha1>b1b1f1b241d2cbff63a1889c4dc5a09bdf769bfb</OpenSFXSha1>
<OpenMSXUrl>https://github.com/OpenRCT2/OpenMusic/releases/download/v1.5/openmusic.zip</OpenMSXUrl>

View File

@ -338,6 +338,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" />
@ -846,6 +847,7 @@
<ClCompile Include="object\ObjectTypes.cpp" />
<ClCompile Include="object\ResourceTable.cpp" />
<ClCompile Include="object\RideObject.cpp" />
<ClCompile Include="object\SceneryBoundingBox.cpp" />
<ClCompile Include="object\SceneryGroupObject.cpp" />
<ClCompile Include="object\SmallSceneryObject.cpp" />
<ClCompile Include="object\StationObject.cpp" />
@ -1072,4 +1074,4 @@
</ClCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>

View File

@ -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

View File

@ -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 = {};
@ -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;
};
@ -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));
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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
{

View File

@ -19,6 +19,7 @@
#include "../interface/Cursors.h"
#include "../localisation/Language.h"
#include "../world/Scenery.h"
#include "SceneryBoundingBox.h"
#include <algorithm>
@ -35,6 +36,7 @@ void SmallSceneryObject::ReadLegacy(IReadObjectContext* context, OpenRCT2::IStre
_legacyType.animation_mask = stream->ReadValue<uint16_t>();
_legacyType.num_frames = stream->ReadValue<uint16_t>();
_legacyType.scenery_tab_id = OBJECT_ENTRY_INDEX_NULL;
SetBoundingBoxFromFlags();
GetStringTable().Read(context, stream, ObjectStringID::NAME);
@ -247,6 +249,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())
@ -271,3 +284,50 @@ std::vector<uint8_t> 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;
}
}

View File

@ -38,4 +38,5 @@ private:
static std::vector<uint8_t> ReadFrameOffsets(OpenRCT2::IStream* stream);
static std::vector<uint8_t> ReadJsonFrameOffsets(json_t& jFrameOffsets);
void PerformFixes();
void SetBoundingBoxFromFlags();
};

View File

@ -33,28 +33,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)
@ -387,20 +365,13 @@ void PaintLargeScenery(PaintSession& session, uint8_t direction, uint16_t height
}
}
auto boxlengthZ = std::min<uint8_t>(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)
{
@ -413,7 +384,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);
}
}
}

View File

@ -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))

View File

@ -1625,19 +1625,12 @@ constexpr CoordsXY defaultRightQuarterTurn3TilesBoundLengths[4][3] = {
},
};
static constexpr int8_t right_quarter_turn_3_tiles_sprite_map[] = {
0,
-1,
1,
2,
};
void TrackPaintUtilRightQuarterTurn3TilesPaint(
PaintSession& session, int8_t thickness, int16_t height, Direction direction, uint8_t trackSequence,
const ImageId colourFlags, const uint32_t sprites[4][3], const CoordsXY offsets[4][3], const CoordsXY boundsLengths[4][3],
const CoordsXYZ boundsOffsets[4][3])
{
int32_t index = right_quarter_turn_3_tiles_sprite_map[trackSequence];
int32_t index = kRightQuarterTurn3TilesSpriteMap[trackSequence];
if (index < 0)
{
return;
@ -1665,7 +1658,7 @@ void TrackPaintUtilRightQuarterTurn3TilesPaint2WithHeightOffset(
PaintSession& session, int8_t thickness, int16_t height, Direction direction, uint8_t trackSequence,
const ImageId colourFlags, const uint32_t sprites[4][3], int32_t heightOffset)
{
int8_t sprite = right_quarter_turn_3_tiles_sprite_map[trackSequence];
int8_t sprite = kRightQuarterTurn3TilesSpriteMap[trackSequence];
if (sprite < 0)
{
return;
@ -1753,7 +1746,7 @@ void TrackPaintUtilRightQuarterTurn3TilesPaint3(
PaintSession& session, int16_t height, Direction direction, uint8_t trackSequence, const ImageId colourFlags,
const SpriteBb sprites[4][3])
{
int8_t sprite = right_quarter_turn_3_tiles_sprite_map[trackSequence];
int8_t sprite = kRightQuarterTurn3TilesSpriteMap[trackSequence];
if (sprite < 0)
{
return;
@ -1770,7 +1763,7 @@ void TrackPaintUtilRightQuarterTurn3TilesPaint4(
PaintSession& session, int16_t height, Direction direction, uint8_t trackSequence, const ImageId colourFlags,
const SpriteBb sprites[4][3])
{
int8_t sprite = right_quarter_turn_3_tiles_sprite_map[trackSequence];
int8_t sprite = kRightQuarterTurn3TilesSpriteMap[trackSequence];
if (sprite < 0)
{
return;

View File

@ -258,6 +258,8 @@ extern const CoordsXY defaultRightQuarterTurn3TilesOffsets[4][3];
extern const CoordsXYZ defaultRightQuarterTurn3TilesBoundOffsets[4][3];
extern const CoordsXY defaultRightQuarterTurn3TilesBoundLengths[4][3];
constexpr int8_t kRightQuarterTurn3TilesSpriteMap[] = { 0, -1, 1, 2 };
extern const CoordsXY defaultRightHelixUpSmallQuarterBoundLengths[4][3][2];
extern const CoordsXYZ defaultRightHelixUpSmallQuarterBoundOffsets[4][3][2];

View File

@ -1392,9 +1392,7 @@ static void PaintMiniatureRailwayTrackRightQuarterTurn3Tiles(
miniature_railway_right_quarter_turn_3_tile_track_floor, nullptr, defaultRightQuarterTurn3TilesBoundLengths,
miniature_railway_right_quarter_turn_3_tile_bound_offsets);
static constexpr int8_t _right_quarter_turn_3_tiles_sprite_map[] = { 0, -1, 1, 2 };
int32_t index = _right_quarter_turn_3_tiles_sprite_map[trackSequence];
int32_t index = kRightQuarterTurn3TilesSpriteMap[trackSequence];
auto imageId = session.TrackColours.WithIndex(
miniature_railway_track_pieces_flat_quarter_turn_3_tiles[direction][index]);

View File

@ -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<BoundBoxXYZ, NumOrthogonalDirections>;
enum
{
SCENERY_TYPE_SMALL,