12uint32_t mixGrammar(uint32_t
value) {
18float grammarUnit(uint32_t
seed) {
return float(mixGrammar(
seed) >> 8u) * (1.f / 16777216.f); }
22 for (
size_t i = 1; i <
points.points().
size(); ++i) {
23 const auto&
a =
points.points()[i - 1];
24 const auto&
b =
points.points()[i];
25 const float dx =
b.x -
a.x;
26 const float dy =
b.y -
a.y;
27 const float dz =
b.z -
a.z;
35 for (
size_t i = 1; i <
points.points().
size(); ++i) {
36 const auto&
a =
points.points()[i - 1];
37 const auto&
b =
points.points()[i];
38 const float dx =
b.x -
a.x;
39 const float dy =
b.y -
a.y;
40 const float dz =
b.z -
a.z;
42 if (
length <= 0.f)
continue;
48 output.yaw = std::atan2(
dz,
dx) * 57.29577951308232f;
63 lastUsedLength_ = 0.f;
64 lastSplineLength_ = 0.f;
68 if (symbol.empty() || asset.empty() ||
length <= 0.f ||
weight <= 0.f)
return false;
69 const auto existing = modules_.find(symbol);
70 if (existing != modules_.end() && !existing->second.empty() &&
71 std::abs(existing->second.front().length -
length) > 0.0001f)
73 if (modules_.find(symbol) == modules_.end()) moduleOrder_.push_back(symbol);
79 if (modules_.erase(symbol) == 0)
return false;
80 moduleOrder_.erase(std::remove(moduleOrder_.begin(), moduleOrder_.end(), symbol), moduleOrder_.end());
86 return index >= 0 &&
index < int(moduleOrder_.size()) ? moduleOrder_[size_t(
index)] : std::string();
89 const auto found = modules_.find(symbol);
90 return found == modules_.end() ? 0 : int(
found->second.size());
93 const auto found = modules_.find(symbol);
99 const auto found = modules_.find(symbol);
107 Parser parser{grammar};
108 const auto parsed = parser.sequence();
109 if (!parser.error.empty()) {
110 error_ = parser.error;
113 if (parsed.empty()) {
114 error_ =
"grammar is empty";
117 std::vector<const Element*> stack;
119 while (!stack.empty()) {
120 const Element*
element = stack.back();
123 error_ =
"unknown module: " +
element->symbol;
126 for (
const auto& child :
element->children) stack.push_back(&child);
132 bool acceptIncomplete) {
134 lastSymbolCount_ = 0;
135 lastUsedLength_ = 0.f;
136 lastSplineLength_ = 0.f;
137 if (!controlPoints || controlPoints->
getCount() < 2) {
138 error_ =
"generate: requires at least two control points";
141 if (!
validate(grammar))
return nullptr;
142 Parser parser{grammar};
143 const auto parsed = parser.sequence();
144 lastSplineLength_ = polylineLength(*controlPoints);
145 if (!acceptIncomplete && sequenceMinLength(parsed) > lastSplineLength_ + 0.0001f) {
146 error_ =
"mandatory grammar does not fit spline";
149 std::vector<std::string> symbols;
151 if (!expandSequence(parsed, lastSplineLength_, symbols, used)) {
152 error_ =
"grammar expansion failed";
158 for (
size_t i = 0; i < symbols.size(); ++i) {
159 const auto*
variant = chooseVariant(symbols[i], mixGrammar(
seed ^ uint32_t(i)));
161 if (!acceptIncomplete) {
162 error_ =
"expanded module does not fit spline: " + symbols[i];
170 const int pointIndex =
output.appendPoint(std::move(
point));
171 output.trySetStringAttribute(pointIndex,
"module", symbols[i]).expect(
"shape grammar module metadata schema");
172 output.trySetStringAttribute(pointIndex,
"asset",
variant->asset).expect(
"shape grammar asset metadata schema");
173 output.trySetFloatAttribute(pointIndex,
"length",
variant->length)
174 .expect(
"shape grammar length metadata schema");
177 lastSymbolCount_ =
output.getCount();
184 std::ostringstream out;
185 out <<
"modules=" << modules_.size() <<
" symbols=" << lastSymbolCount_ <<
" used=" << lastUsedLength_
186 <<
" spline=" << lastSplineLength_;
187 if (!error_.empty()) out <<
" error=" << error_;
191void ShapeGrammar::Parser::skipWhitespace() {
195std::vector<ShapeGrammar::Element> ShapeGrammar::Parser::sequence(
char terminator) {
196 std::vector<Element> result;
200 if (terminator !=
'\0' &&
text[
position] == terminator) {
208 if (!
error.empty())
return {};
209 if (
element.children.empty()) {
210 error =
"empty grammar group";
216 const unsigned char ch =
static_cast<unsigned char>(
text[
position]);
220 if (!std::isalpha(ch) && ch !=
'_' && ch !=
'-' && ch !=
'.')
break;
224 error =
"expected module at character " + std::to_string(
position);
244 result.push_back(std::move(
element));
252 error =
"expected comma at character " + std::to_string(
position);
256 if (terminator !=
'\0')
error =
"unterminated grammar group";
260float ShapeGrammar::elementMinLength(
const Element&
element)
const {
264 if (
found != modules_.end() && !
found->second.empty()) {
274float ShapeGrammar::sequenceMinLength(
const std::vector<Element>&
sequence,
size_t from)
const {
280bool ShapeGrammar::expandSequence(
const std::vector<Element>&
sequence,
float available,
281 std::vector<std::string>& symbols,
float& used)
const {
282 for (
size_t i = 0; i <
sequence.size(); ++i) {
284 float unitLength = !
element.symbol.empty() ? elementMinLength(Element{
element.symbol, {}, 1, 1})
286 if (unitLength <= 0.f)
return false;
287 const float reservedAfter = sequenceMinLength(
sequence, i + 1);
288 int repeats =
element.repeatMin;
290 const float room = std::max(0.f, available - used - reservedAfter);
291 repeats = std::max(
element.repeatMin,
int(std::floor(room / unitLength)));
295 for (
int repeat = 0; repeat < repeats; ++repeat) {
296 if (used + unitLength + reservedAfter > available + 0.0001f)
return true;
298 symbols.push_back(
element.symbol);
301 float groupUsed = 0.f;
302 if (!expandSequence(
element.children, available - used - reservedAfter, symbols, groupUsed))
311const ShapeModuleVariant* ShapeGrammar::chooseVariant(
const std::string& symbol, uint32_t
seed)
const {
312 const auto found = modules_.find(symbol);
313 if (
found == modules_.end() ||
found->second.empty())
return nullptr;
316 float choice = grammarUnit(
seed) * total;
319 if (choice <= 0.f)
return &
variant;
321 return &
found->second.back();
std::array< float, 3 > position
std::unique_ptr< gpgpu::Sequence > sequence
std::shared_ptr< const std::vector< glm::vec2 > > points
Script-friendly collection of attributed 3D samples.
int getCount() const
Returns the count.
std::string getModuleSymbol(int index) const
Returns the module symbol.
bool removeModule(const std::string &symbol)
Removes module.
void clear()
Remove every registered symbol and diagnostic.
int getModuleCount() const
Returns the module count.
float getVariantLength(const std::string &symbol, int index) const
Returns the variant length.
std::string getError() const
Returns the error.
bool validate(const std::string &grammar)
Parse and validate grammar without generating output.
bool hasModule(const std::string &symbol) const
True when module.
int getVariantCount(const std::string &symbol) const
Returns the variant count.
bool addModule(const std::string &symbol, const std::string &asset, float length, float weight=1.f)
Register a weighted asset variant for a symbol.
std::string getVariantAsset(const std::string &symbol, int index) const
Returns the variant asset.
PointSet * generate(const std::string &grammar, PointSet *controlPoints, uint32_t seed, bool acceptIncomplete)
Expand grammar along control points.
std::string debugReport() const
Debug report.
double polylineLength(const Polyline &pl)
Total length of an open polyline.
One deterministic sample used by script-first procedural pipelines.