Porytiles
Loading...
Searching...
No Matches
indirect_link_builder.cpp
Go to the documentation of this file.
2
3#include <limits>
4#include <map>
5#include <set>
6
8
9namespace porytiles {
10
11std::vector<IndirectLink> build_indirect_links(
12 const std::vector<ShapeGroup<Rgba32>> &shape_groups,
13 const std::map<std::size_t, std::size_t> &tile_palette_assignments,
14 const std::array<std::optional<Palette<Rgba32, palette::max_size>>, palette::num_palettes> &base_palettes,
15 const std::array<std::optional<Palette<Rgba32, palette::max_size>>, palette::num_palettes> &prefilled_palettes)
16{
17 std::vector<IndirectLink> links;
18
19 for (std::size_t group_idx = 0; group_idx < shape_groups.size(); ++group_idx) {
20 const auto &group = shape_groups.at(group_idx);
21
22 // Resolve palette assignment for each member via authoritative packing assignments
23 struct ResolvedMember {
24 std::size_t member_idx;
25 std::size_t hw_palette_index;
26 std::map<ShapeMask, Rgba32> colors;
27 };
28 std::vector<ResolvedMember> resolved;
29
30 for (std::size_t m = 0; m < group.members.size(); ++m) {
31 const auto &member = group.members.at(m);
32
33 if (!tile_palette_assignments.contains(member.tile_index)) {
34 continue;
35 }
36 std::size_t hw_index = tile_palette_assignments.at(member.tile_index);
37 resolved.push_back(ResolvedMember{m, hw_index, member.colors});
38 }
39
40 if (resolved.size() < 2) {
41 continue;
42 }
43
44 // Check if members span multiple palettes
45 std::set<std::size_t> distinct_palettes;
46 for (const auto &rm : resolved) {
47 distinct_palettes.insert(rm.hw_palette_index);
48 }
49 if (distinct_palettes.size() < 2) {
50 continue;
51 }
52
53 // Pick the best reference member that minimizes conflicts with prefilled slots in other members' palettes.
54 // For each candidate reference, we check: if we were to link other members' colors to this reference's colors,
55 // how many of those links would conflict with prefilled slots? We pick the candidate with fewest conflicts.
56 //
57 // Note: unlike the old constraint builder, we don't need to know actual slot positions here. We only need to
58 // know whether a prefilled palette has a non-wildcard color at the slot where the ref color sits. This tells
59 // us the link would be unable to resolve cleanly. For simplicity, we use the base palette slot positions for
60 // this heuristic (same as the old builder did with Pass 1 positions).
61 std::size_t best_ref_index = 0;
62 std::size_t best_ref_conflicts = std::numeric_limits<std::size_t>::max();
63
64 for (std::size_t candidate_ref = 0; candidate_ref < resolved.size(); ++candidate_ref) {
65 const auto &candidate = resolved.at(candidate_ref);
66 const auto &candidate_palette = base_palettes.at(candidate.hw_palette_index).value();
67
68 // Build candidate reference's color -> slot mapping from base palette
69 std::map<ShapeMask, std::size_t> candidate_mask_to_slot;
70 for (const auto &[mask, color] : candidate.colors) {
71 for (std::size_t slot = 1; slot < palette::max_size; ++slot) {
72 if (!candidate_palette.is_wildcard(slot) && candidate_palette.at(slot) == color) {
73 candidate_mask_to_slot[mask] = slot;
74 break;
75 }
76 }
77 }
78
79 // Count conflicts: how many links would conflict with prefilled slots in other members' palettes?
80 std::size_t conflicts = 0;
81 for (std::size_t other = 0; other < resolved.size(); ++other) {
82 if (other == candidate_ref || resolved.at(other).hw_palette_index == candidate.hw_palette_index) {
83 continue;
84 }
85 const auto &other_member = resolved.at(other);
86 if (!prefilled_palettes.at(other_member.hw_palette_index).has_value()) {
87 continue;
88 }
89 const auto &prefilled = prefilled_palettes.at(other_member.hw_palette_index).value();
90
91 for (const auto &[mask, other_color] : other_member.colors) {
92 if (!candidate_mask_to_slot.contains(mask)) {
93 continue;
94 }
95 std::size_t target_slot = candidate_mask_to_slot.at(mask);
96 if (!prefilled.is_wildcard(target_slot)) {
97 ++conflicts;
98 }
99 }
100 }
101
102 if (conflicts < best_ref_conflicts) {
103 best_ref_conflicts = conflicts;
104 best_ref_index = candidate_ref;
105 }
106 }
107
108 const auto &ref = resolved.at(best_ref_index);
109
110 // For each other member in a different palette, create Indirect links
111 for (std::size_t r = 0; r < resolved.size(); ++r) {
112 if (r == best_ref_index) {
113 continue;
114 }
115 const auto &other = resolved.at(r);
116 if (other.hw_palette_index == ref.hw_palette_index) {
117 continue;
118 }
119
120 for (const auto &[mask, other_color] : other.colors) {
121 // Find corresponding ref_color via same ShapeMask
122 if (!ref.colors.contains(mask)) {
123 continue;
124 }
125 const auto &ref_color = ref.colors.at(mask);
126
127 links.push_back(
129 .source_palette = other.hw_palette_index,
130 .source_color = other_color,
131 .ref_palette = ref.hw_palette_index,
132 .ref_color = ref_color,
133 .source_group_index = group_idx,
134 });
135 }
136 }
137 }
138
139 return links;
140}
141
142} // namespace porytiles
A generic palette container for colors that support transparency checking.
Definition palette.hpp:45
constexpr std::size_t max_size
Definition palette.hpp:19
constexpr std::size_t num_palettes
Definition palette.hpp:21
std::vector< IndirectLink > build_indirect_links(const std::vector< ShapeGroup< Rgba32 > > &shape_groups, const std::map< std::size_t, std::size_t > &tile_palette_assignments, const std::array< std::optional< Palette< Rgba32, palette::max_size > >, palette::num_palettes > &base_palettes, const std::array< std::optional< Palette< Rgba32, palette::max_size > >, palette::num_palettes > &prefilled_palettes)
Builds Indirect links from shape groups and pre-computed palette assignments.
std::size_t hw_index
A group of tiles that share the same canonical shape but have different color assignments.