Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
SequenceWorld2DSlot.cpp
Go to the documentation of this file.
2#include "ModernIBarSkin.hpp"
3#include "ModernMenuSkin.hpp"
4#include <algorithm>
5#include <cmath>
6#include <set>
7#include <limits>
8
9namespace monopoly::engine
10{
11 std::array<std::int32_t, 2> SequenceWorld2DSlot::transformPoint(
12 const sequence::Matrix2D& matrix, std::int32_t x, std::int32_t y) noexcept
13 {
14 const auto& m = matrix.values;
15 const float px = static_cast<float>(x), py = static_cast<float>(y);
16 const float w = m[2] * px + m[5] * py + m[8];
17 if (w == 0.0F) return {};
18 const auto rounded = [](float value) {
19 if (!std::isfinite(value)) return std::int32_t{};
20 return static_cast<std::int32_t>(std::clamp(
21 std::nearbyint(static_cast<double>(value)),
22 static_cast<double>(std::numeric_limits<std::int32_t>::min()),
23 static_cast<double>(std::numeric_limits<std::int32_t>::max())));
24 };
25 return {rounded((m[0] * px + m[3] * py + m[6]) / w),
26 rounded((m[1] * px + m[4] * py + m[7]) / w)};
27 }
28
29 std::array<std::int32_t, 2> SequenceWorld2DSlot::boundsCenter(
31 const data::Sequence2DBoundingBoxAttribute& bounds) noexcept
32 {
33 const std::array corners{
34 transformPoint(matrix, bounds.left, bounds.top),
35 transformPoint(matrix, bounds.right, bounds.top),
36 transformPoint(matrix, bounds.left, bounds.bottom),
37 transformPoint(matrix, bounds.right, bounds.bottom)};
38 std::array<std::int64_t, 2> sum{};
39 for (const auto& point : corners)
40 for (std::size_t axis = 0; axis < 2; ++axis) sum[axis] += point[axis];
41 return {static_cast<std::int32_t>(sum[0] / 4),
42 static_cast<std::int32_t>(sum[1] / 4)};
43 }
44
46 const std::optional<sequence::SequenceCamera2DView>& camera) noexcept
47 {
48 if (!camera) return;
49 if (!std::ranges::all_of(camera->worldTransform.values,
50 [](float value) { return std::isfinite(value); }) ||
51 (camera->scale && !std::isfinite(*camera->scale))) return;
52 cameraCenter_ = boundsCenter(camera->worldTransform, camera->bounds);
53 const auto origin = transformPoint(camera->worldTransform, 0, 0);
54 const auto xAxis = transformPoint(camera->worldTransform, 1'000'000, 0);
55 const auto dx = static_cast<double>(xAxis[0]) - origin[0];
56 const auto dy = static_cast<double>(xAxis[1]) - origin[1];
57 cameraRotation_ = dx != 0.0 || dy != 0.0
58 ? static_cast<float>(-std::atan2(dy, dx)) : 0.0F;
59 if (camera->scale) cameraScale_ = *camera->scale;
60 // L_Rend2D:393: translate origin, scale, rotate, translate viewport.
62 originShift.values[6] = -static_cast<float>(cameraCenter_[0]);
63 originShift.values[7] = -static_cast<float>(cameraCenter_[1]);
64 worldToScreen_ = sequence::multiply(originShift,
65 sequence::moveXYSRTransform(400, 300, cameraScale_, cameraRotation_));
66 // The slot uses identity for the inverse of a singular camera matrix.
67 screenToWorld_ = sequence::identity2D();
68 if (cameraScale_ != 0.0F)
69 {
70 const auto inverse = sequence::multiply(sequence::translate2D(-400, -300),
71 sequence::moveXYSRTransform(cameraCenter_[0], cameraCenter_[1],
72 1.0F / cameraScale_, -cameraRotation_));
73 if (std::ranges::all_of(inverse.values,
74 [](float value) { return std::isfinite(value); }))
75 screenToWorld_ = inverse;
76 }
77 }
78
79 std::expected<SequenceWorld2DSyncStats, std::string> SequenceWorld2DSlot::sync(
80 const std::vector<sequence::SequenceBitmapRenderItem>& items,
82 {
83 std::set<sequence::SequenceNodeId> incoming;
84 for (const auto& item : items)
85 {
86 if (!incoming.insert(item.node).second)
87 return std::unexpected("duplicate 2D sequence render node");
88 if (!std::ranges::all_of(item.worldTransform.values,
89 [](float value) { return std::isfinite(value); }))
90 return std::unexpected("non-finite 2D sequence matrix");
91 }
92 // Decode first, then select one shell per actual owning CNK. Decorative
93 // child leaves become transparent only after that shell succeeds.
94 std::map<sequence::SequenceNodeId, std::shared_ptr<const data::BitmapRuntimeAsset>> assets;
95 using Owner = std::pair<data::DataId, sequence::SequenceNodeId>;
96 std::map<Owner, sequence::SequenceNodeId> shells;
97 for (const auto& item : items)
98 {
99 auto asset = item.runtimeAsset;
100 if (!asset)
101 {
102 auto decoded = cache.resolve(item.contentsDataId, item.metadata.type, item.bytes);
103 if (!decoded) return std::unexpected(decoded.error().detail);
104 asset = *decoded;
105 }
106 assets.emplace(item.node, asset);
107 if (modernMenuSkin_ && (item.rootSequenceDataId == 0x000502DA ||
108 item.rootSequenceDataId == 0x000502DC || item.rootSequenceDataId == 0x000502DE))
109 {
110 // These pressed cash buttons have larger independent glow siblings.
111 // Qualify each exact measured button leaf instead of picking by area;
112 // the skin rejects every glow and preserves its original pointer.
113 assets[item.node] = modernMenuSkin_->substitute(item.rootSequenceDataId, asset, true);
114 continue;
115 }
116 if (modernSkin_ && modernSkin_->supportsCardFaceIn(item.rootSequenceDataId))
117 {
118 // FaceIn backgrounds animate alongside independent text/art.
119 // Qualify each leaf: never select a largest shell or suppress siblings.
120 assets[item.node] = modernSkin_->substitute(item.rootSequenceDataId, asset, true);
121 continue;
122 }
123 if ((modernSkin_ && modernSkin_->supports(item.rootSequenceDataId)) ||
124 (modernMenuSkin_ && modernMenuSkin_->supports(item.rootSequenceDataId)))
125 {
126 const Owner owner{item.rootSequenceDataId, item.rootSequenceNode};
127 const auto it = shells.find(owner);
128 const auto area = std::uint64_t(asset->image.width) * asset->image.height;
129 if (it == shells.end() || area > std::uint64_t(assets.at(it->second)->image.width) *
130 assets.at(it->second)->image.height)
131 shells[owner] = item.node;
132 }
133 }
134 for (const auto& [owner, node] : shells)
135 {
136 const auto& item = *std::ranges::find_if(items, [node](const auto& item) { return item.node == node; });
138 if (item.bounds)
139 {
140 raster.values[0] = float(item.bounds->right-item.bounds->left) / assets.at(node)->image.width;
141 raster.values[4] = float(item.bounds->bottom-item.bounds->top) / assets.at(node)->image.height;
142 raster.values[6] = float(item.bounds->left); raster.values[7] = float(item.bounds->top);
143 }
144 else
145 { raster.values[6] = float(item.metadata.originX); raster.values[7] = float(item.metadata.originY); }
146 const auto rasterWorld = sequence::multiply(raster,item.worldTransform);
147 const bool menuOwner = modernMenuSkin_ && modernMenuSkin_->supports(owner.first);
148 const bool idleCard=modernSkin_ && modernSkin_->supportsIdleCardPresentation(owner.first);
149 const bool auctionToken=modernSkin_ && modernSkin_->supportsAuctionToken(owner.first);
150 auto replacement = menuOwner ? modernMenuSkin_->substitute(owner.first, assets.at(node), true) :
151 modernSkin_->substitute(owner.first, assets.at(node), true, rasterWorld,
152 idleCard || auctionToken || (!item.bounds && item.metadata.originX == 0 && item.metadata.originY == 0) ?
153 std::optional<std::uint16_t>{idleCard || auctionToken ? item.rootSequencePriority : item.priority} : std::nullopt,
154 idleCard || auctionToken ? std::optional{item.worldTransform} : std::nullopt);
155 if (replacement == assets.at(node)) continue;
156 assets[node] = std::move(replacement);
157 for (const auto& item : items)
158 if (item.node != node && Owner{item.rootSequenceDataId, item.rootSequenceNode} == owner)
159 assets[item.node] = menuOwner ?
160 modernMenuSkin_->substitute(owner.first, assets.at(item.node), false) :
161 modernSkin_->substitute(owner.first, assets.at(item.node), false);
162 }
163 std::map<sequence::SequenceNodeId, SequenceWorld2DObject> next;
164 std::vector<sequence::SequenceNodeId> order;
166 for (const auto& item : items)
167 {
168 auto asset = assets.at(item.node);
169 // L_Seqncr.cpp:4318-4427 keeps an explicit CNK bounding rectangle;
170 // otherwise it uses the bitmap's origin and dimensions. L_Rend2D
171 // transforms those four corners, not an image anchored at (0,0).
172 // Apply this mapping before the sequence and camera transformations.
173 if (!asset->image.width || !asset->image.height)
174 return std::unexpected("zero-sized 2D bitmap asset");
176 if (asset->presentationRect)
177 {
178 const auto& box = *asset->presentationRect;
179 if (!std::ranges::all_of(box, [](float v) { return std::isfinite(v); }) ||
180 box[2] <= box[0] || box[3] <= box[1])
181 return std::unexpected("invalid 2D presentation rectangle");
182 rasterToSequence.values[0] = (box[2] - box[0]) / asset->image.width;
183 rasterToSequence.values[4] = (box[3] - box[1]) / asset->image.height;
184 rasterToSequence.values[6] = box[0];
185 rasterToSequence.values[7] = box[1];
186 }
187 else if (item.bounds)
188 {
189 const auto& box = *item.bounds;
190 const auto width = static_cast<std::int64_t>(box.right) - box.left;
191 const auto height = static_cast<std::int64_t>(box.bottom) - box.top;
192 rasterToSequence.values[0] = static_cast<float>(width) /
193 static_cast<float>(asset->image.width);
194 rasterToSequence.values[4] = static_cast<float>(height) /
195 static_cast<float>(asset->image.height);
196 rasterToSequence.values[6] = static_cast<float>(box.left);
197 rasterToSequence.values[7] = static_cast<float>(box.top);
198 }
199 else
200 {
201 // Supersampled presentation derivatives retain the exact intrinsic
202 // authored footprint and origin, including UAP offsets.
203 if (asset->preferLinearFiltering && item.metadata.width && item.metadata.height)
204 {
205 rasterToSequence.values[0] = static_cast<float>(item.metadata.width) / asset->image.width;
206 rasterToSequence.values[4] = static_cast<float>(item.metadata.height) / asset->image.height;
207 }
208 rasterToSequence.values[6] = static_cast<float>(item.metadata.originX);
209 rasterToSequence.values[7] = static_cast<float>(item.metadata.originY);
210 }
211 const auto positioned = sequence::multiply(rasterToSequence, item.worldTransform);
212 if (!std::ranges::all_of(positioned.values,
213 [](float value) { return std::isfinite(value); }))
214 return std::unexpected("non-finite 2D bitmap placement");
215 const auto* old = find(item.node);
216 if (!old) ++stats.started;
217 else if (old->asset != asset || old->priority != item.priority ||
218 old->worldTransform.values != positioned.values) ++stats.moved;
219 else ++stats.unchanged;
220 next.emplace(item.node, SequenceWorld2DObject{item.node, item.contentsDataId,
221 item.priority, item.clock, positioned, asset});
222 order.push_back(item.node);
223 }
224 for (const auto& [id, object] : objects_)
225 {
226 (void)object;
227 if (!incoming.contains(id)) ++stats.stopped;
228 }
229 // L_Rend2D.cpp:2730 draws depth-first, then siblings. Runtime has
230 // already ordered siblings by priority. A global leaf sort would
231 // incorrectly move children across unrelated parent subtrees.
232 objects_.swap(next);
233 order_.swap(order);
234 return stats;
235 }
236}
std::uint32_t height
std::uint32_t width
unsigned index
std::uint16_t value
Definition Timers.cpp:16
static std::array< std::int32_t, 2 > boundsCenter(const sequence::Matrix2D &matrix, const data::Sequence2DBoundingBoxAttribute &bounds) noexcept
void updateCamera(const std::optional< sequence::SequenceCamera2DView > &camera) noexcept
static std::array< std::int32_t, 2 > transformPoint(const sequence::Matrix2D &matrix, std::int32_t x, std::int32_t y) noexcept
std::expected< SequenceWorld2DSyncStats, std::string > sync(const std::vector< sequence::SequenceBitmapRenderItem > &items, data::BitmapRuntimeCache &cache)
Matrix2D identity2D() noexcept
Matrix2D translate2D(std::int32_t x, std::int32_t y) noexcept
Matrix2D moveXYSRTransform(std::int32_t x, std::int32_t y, float scale, float rotate) noexcept
Matrix2D multiply(const Matrix2D &left, const Matrix2D &right) noexcept