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_pal_assignments,
14 const std::array<std::optional<Palette<Rgba32, pal::max_size>>, pal::num_pals> &base_pals,
15 const std::array<std::optional<Palette<Rgba32, pal::max_size>>, pal::num_pals> &prefilled_pals)
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_pal_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_pal_assignments.contains(member.tile_index)) {
34 continue;
35 }
36 std::size_t hw_index = tile_pal_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_pals;
46 for (const auto &rm : resolved) {
47 distinct_pals.insert(rm.hw_pal_index);
48 }
49 if (distinct_pals.size() < 2) {
50 continue;
51 }
52
53 /*
54 * Pick the best reference member that minimizes conflicts with prefilled slots in other members' palettes.
55 * For each candidate reference, we check: if we were to link other members' colors to this reference's colors,
56 * how many of those links would conflict with prefilled slots? We pick the candidate with fewest conflicts.
57 *
58 * Note: unlike the old constraint builder, we don't need to know actual slot positions here. We only need to
59 * know whether a prefilled palette has a non-wildcard color at the slot where the ref color sits. This tells
60 * us the link would be unable to resolve cleanly. For simplicity, we use the base palette slot positions for
61 * this heuristic (same as the old builder did with Pass 1 positions).
62 */
63 std::size_t best_ref_index = 0;
64 std::size_t best_ref_conflicts = std::numeric_limits<std::size_t>::max();
65
66 for (std::size_t candidate_ref = 0; candidate_ref < resolved.size(); ++candidate_ref) {
67 const auto &candidate = resolved.at(candidate_ref);
68 const auto &candidate_pal = base_pals.at(candidate.hw_pal_index).value();
69
70 // Build candidate reference's color -> slot mapping from base palette
71 std::map<ShapeMask, std::size_t> candidate_mask_to_slot;
72 for (const auto &[mask, color] : candidate.colors) {
73 for (std::size_t slot = 1; slot < pal::max_size; ++slot) {
74 if (!candidate_pal.is_wildcard(slot) && candidate_pal.at(slot) == color) {
75 candidate_mask_to_slot[mask] = slot;
76 break;
77 }
78 }
79 }
80
81 // Count conflicts: how many links would conflict with prefilled slots in other members' palettes?
82 std::size_t conflicts = 0;
83 for (std::size_t other = 0; other < resolved.size(); ++other) {
84 if (other == candidate_ref || resolved.at(other).hw_pal_index == candidate.hw_pal_index) {
85 continue;
86 }
87 const auto &other_member = resolved.at(other);
88 if (!prefilled_pals.at(other_member.hw_pal_index).has_value()) {
89 continue;
90 }
91 const auto &prefilled = prefilled_pals.at(other_member.hw_pal_index).value();
92
93 for (const auto &[mask, other_color] : other_member.colors) {
94 if (!candidate_mask_to_slot.contains(mask)) {
95 continue;
96 }
97 std::size_t target_slot = candidate_mask_to_slot.at(mask);
98 if (!prefilled.is_wildcard(target_slot)) {
99 ++conflicts;
100 }
101 }
102 }
103
104 if (conflicts < best_ref_conflicts) {
105 best_ref_conflicts = conflicts;
106 best_ref_index = candidate_ref;
107 }
108 }
109
110 const auto &ref = resolved.at(best_ref_index);
111
112 // For each other member in a different palette, create Indirect links
113 for (std::size_t r = 0; r < resolved.size(); ++r) {
114 if (r == best_ref_index) {
115 continue;
116 }
117 const auto &other = resolved.at(r);
118 if (other.hw_pal_index == ref.hw_pal_index) {
119 continue;
120 }
121
122 for (const auto &[mask, other_color] : other.colors) {
123 // Find corresponding ref_color via same ShapeMask
124 if (!ref.colors.contains(mask)) {
125 continue;
126 }
127 const auto &ref_color = ref.colors.at(mask);
128
129 links.push_back(
131 .source_pal = other.hw_pal_index,
132 .source_color = other_color,
133 .ref_pal = ref.hw_pal_index,
134 .ref_color = ref_color,
135 .source_group_index = group_idx,
136 });
137 }
138 }
139 }
140
141 return links;
142}
143
144} // namespace porytiles
A generic palette container for colors that support transparency checking.
Definition palette.hpp:47
constexpr std::size_t num_pals
Definition palette.hpp:21
constexpr std::size_t max_size
Definition palette.hpp:19
std::vector< IndirectLink > build_indirect_links(const std::vector< ShapeGroup< Rgba32 > > &shape_groups, const std::map< std::size_t, std::size_t > &tile_pal_assignments, const std::array< std::optional< Palette< Rgba32, pal::max_size > >, pal::num_pals > &base_pals, const std::array< std::optional< Palette< Rgba32, pal::max_size > >, pal::num_pals > &prefilled_pals)
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.