载入中...
搜索中...
未找到
PixelMaterial.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4
5#include <algorithm>
6#include <limits>
7#include <type_traits>
8#include <unordered_set>
9#include <utility>
10
11namespace eve::pixelworld {
12namespace {
13
14eve::Result<MaterialCatalog> invalidCatalog(std::string message, std::string path) {
16 eve::DiagnosticCode::InvalidArgument, message, path, {}, "pixelworld.catalog"));
17}
18
19void hashBytes(std::uint64_t& hash, std::string_view bytes) noexcept {
20 for (const unsigned char byte : bytes) {
21 hash ^= byte;
22 hash *= 1099511628211ULL;
23 }
24}
25
26template <class T>
27void hashValue(std::uint64_t& hash, T value) noexcept {
28 using Unsigned = std::make_unsigned_t<T>;
29 const auto bits = static_cast<Unsigned>(value);
30 for (std::size_t i = 0; i < sizeof(T); ++i) {
31 hash ^= std::uint8_t(bits >> (i * 8U));
32 hash *= 1099511628211ULL;
33 }
34}
35
36} // namespace
37
38eve::Result<MaterialCatalog> MaterialCatalog::create(std::vector<MaterialDefinition> definitions,
39 std::vector<MaterialReactionRule> reactions,
40 std::vector<MaterialPhaseRule> phaseRules) {
41 if (definitions.empty()) return invalidCatalog("material catalog must contain air", "materials");
42 if (definitions.size() > std::size_t(std::numeric_limits<std::uint16_t>::max()))
43 return invalidCatalog("material catalog exceeds uint16 id capacity", "materials");
44
45 std::unordered_set<std::string> names;
46 for (std::size_t index = 0; index < definitions.size(); ++index) {
48 if (std::size_t(definition.id) != index)
49 return invalidCatalog("material ids must be contiguous and match canonical order",
50 "materials[" + std::to_string(index) + "].id");
51 if (definition.name.empty() || !names.emplace(definition.name).second)
52 return invalidCatalog("material names must be non-empty and unique",
53 "materials[" + std::to_string(index) + "].name");
54 if (definition.heatCapacity == 0)
55 return invalidCatalog("material heat capacity must be positive",
56 "materials[" + std::to_string(index) + "].heatCapacity");
57 std::sort(definition.tags.begin(), definition.tags.end());
58 if (std::any_of(definition.tags.begin(), definition.tags.end(), [](const std::string& tag) {
59 return tag.empty();
60 }) || std::adjacent_find(definition.tags.begin(), definition.tags.end()) !=
61 definition.tags.end())
62 return invalidCatalog("material tags must be non-empty and unique",
63 "materials[" + std::to_string(index) + "].tags");
64 }
66 return invalidCatalog("material zero must be empty air", "materials[0]");
67
68 std::unordered_set<std::string> ruleIds;
69 const auto validId = [&definitions](MaterialId id) { return std::size_t(id) < definitions.size(); };
70 for (std::size_t index = 0; index < reactions.size(); ++index) {
71 const auto& rule = reactions[index];
72 if (rule.id.empty() || !ruleIds.emplace(rule.id).second)
73 return invalidCatalog("reaction ids must be non-empty and unique",
74 "reactions[" + std::to_string(index) + "].id");
75 if (!validId(rule.first) || !validId(rule.second) || !validId(rule.firstResult) ||
76 !validId(rule.secondResult))
77 return invalidCatalog("reaction references an unknown material",
78 "reactions[" + std::to_string(index) + "]");
79 }
80 std::sort(reactions.begin(), reactions.end(), [](const auto& lhs, const auto& rhs) {
81 return lhs.priority != rhs.priority ? lhs.priority > rhs.priority : lhs.id < rhs.id;
82 });
83 std::unordered_set<std::string> phaseIds;
84 for (std::size_t index = 0; index < phaseRules.size(); ++index) {
85 const auto& rule = phaseRules[index];
86 if (rule.id.empty() || !phaseIds.emplace(rule.id).second)
87 return invalidCatalog("phase rule ids must be non-empty and unique",
88 "phaseRules[" + std::to_string(index) + "].id");
89 if (!validId(rule.source) || !validId(rule.result))
90 return invalidCatalog("phase rule references an unknown material",
91 "phaseRules[" + std::to_string(index) + "]");
92 }
93 std::sort(phaseRules.begin(), phaseRules.end(), [](const auto& lhs, const auto& rhs) {
94 return lhs.priority != rhs.priority ? lhs.priority > rhs.priority : lhs.id < rhs.id;
95 });
96
97 MaterialCatalog catalog;
98 catalog.definitions_ = std::move(definitions);
99 catalog.reactions_ = std::move(reactions);
100 catalog.phaseRules_ = std::move(phaseRules);
101 catalog.reactionMask_.assign(catalog.definitions_.size(), 0);
102 for (const auto& rule : catalog.reactions_) {
103 catalog.reactionMask_[std::size_t(rule.first)] = 1;
104 catalog.reactionMask_[std::size_t(rule.second)] = 1;
105 }
106 catalog.phaseMask_.assign(catalog.definitions_.size(), 0);
107 for (const auto& rule : catalog.phaseRules_) catalog.phaseMask_[std::size_t(rule.source)] = 1;
108 std::uint64_t hash = 1469598103934665603ULL;
109 for (const auto& definition : catalog.definitions_) {
110 hashValue(hash, std::uint16_t(definition.id));
111 hashBytes(hash, definition.name);
112 hashValue(hash, std::uint8_t(definition.state));
113 hashValue(hash, definition.density);
114 hashValue(hash, definition.viscosity);
116 hashValue(hash, definition.heatCapacity);
121 hashValue(hash, definition.defaultLifetime);
122 hashValue(hash, std::uint8_t(definition.flammable));
123 hashValue(hash, definition.blastResistance);
124 hashValue(hash, definition.displayRgba);
125 hashValue(hash, std::uint32_t(definition.tags.size()));
126 for (const auto& tag : definition.tags) hashBytes(hash, tag);
127 }
128 for (const auto& rule : catalog.reactions_) {
129 hashBytes(hash, rule.id);
130 hashValue(hash, std::uint16_t(rule.first));
131 hashValue(hash, std::uint16_t(rule.second));
132 hashValue(hash, std::uint16_t(rule.firstResult));
133 hashValue(hash, std::uint16_t(rule.secondResult));
134 hashValue(hash, rule.minimumTemperature);
135 hashValue(hash, rule.heatDelta);
136 hashValue(hash, rule.priority);
137 }
138 for (const auto& rule : catalog.phaseRules_) {
139 hashBytes(hash, rule.id);
140 hashValue(hash, std::uint16_t(rule.source));
141 hashValue(hash, std::uint16_t(rule.result));
142 hashValue(hash, std::uint8_t(rule.direction));
143 hashValue(hash, rule.threshold);
144 hashValue(hash, rule.temperatureDelta);
145 hashValue(hash, rule.priority);
146 }
147 catalog.fingerprint_ = hash;
148 return eve::Result<MaterialCatalog>::success(std::move(catalog));
149}
150
152 std::vector<MaterialDefinition> materials{
153 {MaterialId::Air, "air", MaterialState::Empty, 0, 0, 8, 1},
154 {MaterialId::Stone, "stone", MaterialState::Solid, 255, 255, 30, 180},
155 {MaterialId::Sand, "sand", MaterialState::Powder, 180, 35, 18, 80},
156 {MaterialId::Water, "water", MaterialState::Liquid, 100, 24, 45, 120, 32767, 0, 100},
157 {MaterialId::Oil, "oil", MaterialState::Liquid, 80, 70, 12, 90, 180, 32767, 280, 0, true},
158 {MaterialId::Fire, "fire", MaterialState::Energy, 1, 0, 80, 1, 32767, 32767, 32767, 30,
159 false, 700},
160 {MaterialId::Steam, "steam", MaterialState::Gas, 2, 5, 22, 30, 32767, 32767, 32767, 180,
161 false, 110},
162 {MaterialId::Wood, "wood", MaterialState::Solid, 220, 255, 8, 140, 240, 32767, 32767, 0, true},
163 {MaterialId::Ice, "ice", MaterialState::Solid, 105, 255, 35, 110, 32767, 0, 32767, 0, false, -10},
164 {MaterialId::Lava, "lava", MaterialState::Liquid, 210, 180, 55, 200, 32767, 900, 32767, 0, false, 1300},
165 {MaterialId::Smoke, "smoke", MaterialState::Gas, 3, 8, 12, 20, 32767, 32767, 32767, 220},
166 {MaterialId::Gunpowder, "gunpowder", MaterialState::Powder, 165, 40, 20, 70, 160, 32767, 32767, 0, true},
167 {MaterialId::Acid, "acid", MaterialState::Liquid, 110, 45, 32, 100, 32767, 32767, 118},
168 };
169 std::vector<MaterialReactionRule> reactions{
171 -32768, 40, 200},
173 -32768, 80, 150},
175 -32768, 60, 140},
176 {"fire.gunpowder.ignite", MaterialId::Fire, MaterialId::Gunpowder, MaterialId::Fire,
177 MaterialId::Fire, -32768, 300, 180},
178 {"acid.stone.dissolve", MaterialId::Acid, MaterialId::Stone, MaterialId::Water,
179 MaterialId::Sand, -32768, 0, 120},
181 MaterialId::Steam, -32768, 80, 220},
182 };
183 materials[std::size_t(MaterialId::Air)].blastResistance = 0;
184 materials[std::size_t(MaterialId::Stone)].blastResistance = 240;
185 materials[std::size_t(MaterialId::Sand)].blastResistance = 8;
186 materials[std::size_t(MaterialId::Water)].blastResistance = 0;
187 materials[std::size_t(MaterialId::Oil)].blastResistance = 0;
188 materials[std::size_t(MaterialId::Fire)].blastResistance = 0;
189 materials[std::size_t(MaterialId::Steam)].blastResistance = 0;
190 materials[std::size_t(MaterialId::Wood)].blastResistance = 45;
191 materials[std::size_t(MaterialId::Ice)].blastResistance = 70;
192 materials[std::size_t(MaterialId::Lava)].blastResistance = 0;
193 materials[std::size_t(MaterialId::Smoke)].blastResistance = 0;
194 materials[std::size_t(MaterialId::Gunpowder)].blastResistance = 5;
195 materials[std::size_t(MaterialId::Acid)].blastResistance = 0;
196 const auto decorate = [&materials](MaterialId id, std::uint32_t color,
197 std::vector<std::string> tags) {
198 auto& material = materials[std::size_t(id)];
199 material.displayRgba = color;
200 material.tags = std::move(tags);
201 };
202 decorate(MaterialId::Air, 0x00000000U, {"empty"});
203 decorate(MaterialId::Stone, 0x4D5261FFU, {"terrain", "solid"});
204 decorate(MaterialId::Sand, 0xE0B04DFFU, {"granular", "mobile"});
205 decorate(MaterialId::Water, 0x1F61F2DBU, {"liquid", "mobile"});
206 decorate(MaterialId::Oil, 0x423314FFU, {"flammable", "liquid", "mobile"});
207 decorate(MaterialId::Fire, 0xFF3D08FFU, {"energy", "hot", "mobile"});
208 decorate(MaterialId::Steam, 0xB8CCE08CU, {"gas", "mobile"});
209 decorate(MaterialId::Wood, 0x6B3312FFU, {"flammable", "solid", "terrain"});
210 decorate(MaterialId::Ice, 0xADDBFFF2U, {"cold", "solid", "terrain"});
211 decorate(MaterialId::Lava, 0xFF2E03FFU, {"hot", "liquid", "mobile"});
212 decorate(MaterialId::Smoke, 0x4D4A5294U, {"gas", "mobile"});
213 decorate(MaterialId::Gunpowder, 0x3D3833FFU, {"explosive", "granular", "mobile"});
214 decorate(MaterialId::Acid, 0x4DEB3DE0U, {"corrosive", "liquid", "mobile"});
215 std::vector<MaterialPhaseRule> phaseRules{
219 {"stone.melt", MaterialId::Stone, MaterialId::Lava, TemperatureDirection::AtOrAbove, 1200, -200, 100},
220 {"lava.solidify", MaterialId::Lava, MaterialId::Stone, TemperatureDirection::AtOrBelow, 800, 100, 100},
221 };
222 return std::move(MaterialCatalog::create(std::move(materials), std::move(reactions), std::move(phaseRules)))
223 .expect("built-in PixelWorld material catalog must be valid");
224}
225
227 for (const auto& definition : definitions_)
230 eve::DiagnosticCode::NotFound, "unknown pixel material: " + std::string(name), "material", {},
231 "pixelworld.catalog"));
232}
233
235 const std::size_t index = std::size_t(id);
236 return definitions_[index < definitions_.size() ? index : 0];
237}
238
239std::span<const MaterialDefinition> MaterialCatalog::definitions() const noexcept { return definitions_; }
240std::span<const MaterialReactionRule> MaterialCatalog::reactions() const noexcept { return reactions_; }
241std::span<const MaterialPhaseRule> MaterialCatalog::phaseRules() const noexcept { return phaseRules_; }
242bool MaterialCatalog::canReact(MaterialId id) const noexcept {
243 const std::size_t index = std::size_t(id);
244 return index < reactionMask_.size() && reactionMask_[index] != 0;
245}
247 const std::size_t index = std::size_t(id);
248 return index < phaseMask_.size() && phaseMask_[index] != 0;
249}
250std::uint64_t MaterialCatalog::fingerprint() const noexcept { return fingerprint_; }
251
252} // namespace eve::pixelworld
double value
Stable, structured diagnostics shared by engine modules.
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
std::string message
std::uint64_t bytes
std::string name
const std::string * tag
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
Material * material
Battle::Reactions reactions
uint32_t index
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Owning immutable material and reaction table used by one PixelWorld.
const MaterialDefinition & definition(MaterialId id) const noexcept
Resolve a validated id; invalid ids safely project to air.
bool canReact(MaterialId id) const noexcept
Whether this material participates in at least one binary reaction.
static MaterialCatalog builtIn()
Construct the engine's canonical built-in catalog.
std::span< const MaterialReactionRule > reactions() const noexcept
Borrow immutable canonical-priority reaction rules.
std::span< const MaterialDefinition > definitions() const noexcept
Borrow immutable definitions until this catalog is destroyed.
std::span< const MaterialPhaseRule > phaseRules() const noexcept
Borrow immutable canonical-priority phase rules.
eve::Result< MaterialId > resolve(std::string_view name) const
Resolve a stable name or return a structured NotFound diagnostic.
std::uint64_t fingerprint() const noexcept
Stable deterministic fingerprint included in world snapshots.
static eve::Result< MaterialCatalog > create(std::vector< MaterialDefinition > definitions, std::vector< MaterialReactionRule > reactions={}, std::vector< MaterialPhaseRule > phaseRules={})
Validate and own a complete catalog candidate transactionally.
bool canPhaseTransition(MaterialId id) const noexcept
Whether this material owns at least one phase transition rule.
MaterialId
Compact stable material identifier stored in each authoritative cell.
Immutable validated properties for one catalog material.
std::uint32_t displayRgba
Canonical preview/render color encoded as 0xRRGGBBAA.
std::vector< std::string > tags
Canonical sorted semantic tags used by authoring and rule queries.
glm::vec4 color