332 lines
11 KiB
C++
332 lines
11 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
|
|
#include <etl/vector.h>
|
|
#include <mtl/fixed.hpp>
|
|
#include <mtl/vec.hpp>
|
|
#include <mtl/mat.hpp>
|
|
#include <mtl/exception.hpp>
|
|
|
|
// TODO: Implement in MTL and create version that doesn't need cos of the angle
|
|
mtl::mat<2, 2> create_rotation(mtl::fixed angle_cos, mtl::fixed angle_sin);
|
|
|
|
namespace fractal {
|
|
|
|
constexpr size_t g_max_tokens = 16;
|
|
constexpr size_t g_max_groups = 8;
|
|
constexpr size_t g_max_wgroups = 4;
|
|
constexpr size_t g_max_branch_rules = 8;
|
|
constexpr size_t g_max_cgroup_tokens = 16;
|
|
constexpr size_t g_max_wgroup_weights = 8;
|
|
constexpr size_t g_max_match_groups = 8;
|
|
constexpr size_t g_max_match_wgroups = 4;
|
|
constexpr size_t g_max_leafs = 128;
|
|
constexpr size_t g_max_wleafs = 128;
|
|
|
|
using token_id_t = uint32_t;
|
|
using group_id_t = uint32_t;
|
|
using weighted_group_id_t = uint32_t;
|
|
using branch_rule_id_t = uint32_t;
|
|
|
|
enum class token_type_e {
|
|
empty,
|
|
walk,
|
|
rotate,
|
|
};
|
|
|
|
struct token_t {
|
|
token_type_e m_type = token_type_e::empty;
|
|
mtl::fixed m_value;
|
|
mtl::fixed m_value2;
|
|
uint32_t m_mark;
|
|
};
|
|
|
|
struct group_characteristic_t {
|
|
uint32_t m_factor; // Number of groups generated in succession
|
|
etl::vector<token_id_t, g_max_cgroup_tokens> m_token_ids;
|
|
};
|
|
|
|
struct marker_t {
|
|
mtl::vec2 m_pos;
|
|
uint32_t m_id;
|
|
};
|
|
|
|
struct leaf_t {
|
|
group_id_t m_group_id;
|
|
mtl::vec2 m_position;
|
|
mtl::mat<2, 2> m_orientation;
|
|
};
|
|
|
|
struct weighted_leaf_t {
|
|
weighted_group_id_t m_weighted_group_id;
|
|
mtl::vec2 m_position;
|
|
mtl::mat<2, 2> m_orientation;
|
|
};
|
|
|
|
struct group_output_t {
|
|
static constexpr size_t g_max_child_wleafs = 8;
|
|
static constexpr size_t g_max_child_leafs = 16;
|
|
static constexpr size_t g_max_child_markers = 8;
|
|
|
|
etl::vector<weighted_leaf_t, g_max_child_wleafs> m_child_wleafs;
|
|
etl::vector<leaf_t, g_max_child_leafs> m_child_leafs;
|
|
etl::vector<marker_t, g_max_child_markers> m_child_markers;
|
|
};
|
|
|
|
struct token_match_t {
|
|
etl::vector<group_id_t, g_max_match_groups> m_groups;
|
|
etl::vector<weighted_group_id_t, g_max_match_wgroups> m_wgroups;
|
|
};
|
|
|
|
struct weighted_group_t {
|
|
etl::vector<group_id_t, g_max_wgroup_weights> m_groups;
|
|
etl::vector<uint32_t, g_max_wgroup_weights> m_weights;
|
|
uint32_t m_weight_total; // Total needed for random selection
|
|
// TODO: Control weight additions so m_weight_total = sum(m_weights) is invariant
|
|
};
|
|
|
|
struct branch_rule_t {
|
|
token_id_t m_match;
|
|
bool m_weighted;
|
|
|
|
union {
|
|
group_id_t basic;
|
|
weighted_group_id_t weighted;
|
|
} m_group;
|
|
};
|
|
|
|
class ruleset_t {
|
|
private:
|
|
etl::vector<token_t, g_max_tokens> m_tokens;
|
|
etl::vector<group_characteristic_t, g_max_groups> m_group_characteristics;
|
|
etl::vector<weighted_group_t, g_max_wgroups> m_weighted_groups;
|
|
|
|
etl::vector<branch_rule_t, g_max_branch_rules> m_branch_rules;
|
|
|
|
group_id_t m_axiom = 0;
|
|
|
|
public:
|
|
|
|
/**
|
|
* @brief Checks if the maximum number of tokens has been reached
|
|
*
|
|
* @ret @c true if the number of tokens in the ruleset equals @c g_max_tokens, @c false otherwise
|
|
*/
|
|
bool tokens_full() const {
|
|
return m_tokens.full();
|
|
}
|
|
bool valid_token(token_id_t tok) const {
|
|
return tok < m_tokens.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Add new token variable that can be used in this ruleset
|
|
*
|
|
* @param type Type of the token to add
|
|
* @param value Value of the token, unused if @p type is @c token_type_e::empty
|
|
*
|
|
* @ret @c token_id_t of the newly added token, local to this ruleset
|
|
*
|
|
* @exception @c mtl::length_error if the maximum number of tokens is reached
|
|
*/
|
|
token_id_t add_token(token_type_e type, mtl::fixed value = 0, mtl::fixed value2 = 0, uint32_t mark = 0);
|
|
|
|
/**
|
|
* @brief Checks if the maximum number of group characteristics has been reached
|
|
*
|
|
* @ret @c true if the number of group characteristics in the ruleset equals @c g_max_cgroups, @c false otherwise
|
|
*/
|
|
bool group_characteristics_full() const {
|
|
return m_group_characteristics.full();
|
|
}
|
|
bool valid_group_characteristic(group_id_t group) const {
|
|
return group < m_group_characteristics.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Add new group characteristic
|
|
*
|
|
* @param tokens @c etl::ivector of @c token_id_t that should be generated inside this group
|
|
* @param factor The number of times the group's tokens are repeatedly generated
|
|
*
|
|
* @ret @c group_id_t of the newly added group characteristic, local to this ruleset
|
|
*
|
|
* @exception mtl::invalid_argument if an invalid token was encountered
|
|
* @exception mtl::length_error if the maximum number of groups is reached
|
|
*/
|
|
group_id_t add_group_characteristic(const etl::ivector<token_id_t>& tokens, uint32_t factor = 1);
|
|
|
|
/**
|
|
* @brief Checks if the maximum number of weighted groups has been reached
|
|
*
|
|
* @ret @c true if the number of weighted groups in the ruleset equals @c g_max_wgroups, @c false otherwise
|
|
*/
|
|
bool weighted_groups_full() const {
|
|
return m_weighted_groups.full();
|
|
}
|
|
bool valid_weighted_group(weighted_group_id_t wgroup) const {
|
|
return wgroup < m_weighted_groups.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Add new weighted group
|
|
*
|
|
* Adds weighted group of groups, where each subgroup has a
|
|
* <tt>(weight) / (total weight)</tt> chance of being selected. Weights
|
|
* are represented as @c uint32_t.
|
|
*
|
|
* @param weights @c etl::ivector of @c etl::pair of @c group_id_t and their corresponding weights
|
|
*
|
|
* @ret @c weighted_group_id_t of the newly created group
|
|
*
|
|
* @exception @c mtl::invalid_argument if no groups were supplied, or an invalid group was encountered
|
|
* @exception @c mtl::length_error if the maximum number of groups is reached
|
|
*/
|
|
weighted_group_id_t add_weighted_group(const etl::ivector<etl::pair<group_id_t, uint32_t>>& weights);
|
|
|
|
/**
|
|
* @brief Checks if the maximum number of branch rules has been reached
|
|
*
|
|
* @ret @c true if the number of branch rules in the ruleset equals @c g_max_branch_rules, @c false otherwise
|
|
*/
|
|
bool branch_rules_full() const {
|
|
return m_branch_rules.full();
|
|
}
|
|
|
|
/**
|
|
* @brief Add a new unweighted branch rule to the ruleset
|
|
*
|
|
* Branch rules describe what groups can be generated given a token.
|
|
* Branch rules can use either plain groups or weighted groups as their output.
|
|
*
|
|
* @param match Token to match and generate the group in place of
|
|
* @param group Group to generate
|
|
*
|
|
* @ret @c branch_rule_id_t of the newly added branch rule
|
|
*
|
|
* @exception @c mtl::invalid_argument if an invalid token or group was supplied
|
|
* @exception @c mtl::length_error if the maximum number of branch rules was reached
|
|
*/
|
|
branch_rule_id_t add_branch_rule(token_id_t match, group_id_t group);
|
|
|
|
/**
|
|
* @brief Add a new weighted branch rule to the ruleset
|
|
*
|
|
* Branch rules describe what groups can be generated given a token.
|
|
* Branch rules can use either plain groups or weighted groups as their output.
|
|
*
|
|
* @param match Token to match and generate the group in place of
|
|
* @param wgroup Weighted group to select from, only one subgroup is generated
|
|
*
|
|
* @ret @c branch_rule_id_t of the newly added branch rule
|
|
*
|
|
* @exception @c mtl::invalid_argument if an invalid token or group was supplied
|
|
* @exception @c mtl::length_error if the maximum number of branch rules was reached
|
|
*/
|
|
branch_rule_id_t add_branch_rule_weighted(token_id_t match, weighted_group_id_t wgroup);
|
|
|
|
/** @brief Sets the axiom
|
|
*
|
|
* @exception @c mtl::invalid_argument if an invalid group ID was supplied
|
|
*/
|
|
void set_axiom(group_id_t axiom) {
|
|
m_axiom = valid_group_characteristic(axiom) ? axiom : throw mtl::invalid_argument();
|
|
}
|
|
group_id_t get_axiom() const { return m_axiom; }
|
|
|
|
const token_t& get_token(token_id_t id) const { return m_tokens[id]; }
|
|
const group_characteristic_t& get_group_characteristic(group_id_t id) const { return m_group_characteristics[id]; }
|
|
const weighted_group_t& get_weighted_group(weighted_group_id_t wgid) const { return m_weighted_groups[wgid]; }
|
|
const branch_rule_t& get_branch_rule(branch_rule_id_t id) const { return m_branch_rules[id]; }
|
|
|
|
size_t num_tokens() const { return m_tokens.size(); }
|
|
size_t num_groups() const { return m_group_characteristics.size(); }
|
|
size_t num_weighted_groups() const { return m_weighted_groups.size(); }
|
|
size_t num_branch_rules() const { return m_branch_rules.size(); }
|
|
|
|
template <typename CONTGROUP_T, typename CONTWGROUP_T>
|
|
void find_token_matches(token_id_t token, CONTGROUP_T& out_groups, CONTWGROUP_T& out_wgroups) const;
|
|
};
|
|
|
|
class generator_t {
|
|
etl::vector<weighted_group_t, g_max_wgroups> m_weighted_groups;
|
|
etl::vector<group_output_t, g_max_groups> m_group_outputs;
|
|
etl::vector<token_match_t, g_max_tokens> m_token_matches;
|
|
|
|
bool m_has_ruleset = false;
|
|
group_id_t m_axiom;
|
|
mtl::fixed m_scale = 1;
|
|
|
|
etl::vector<leaf_t, g_max_leafs> m_leaf_buf1;
|
|
etl::vector<leaf_t, g_max_leafs> m_leaf_buf2;
|
|
|
|
etl::vector<weighted_leaf_t, g_max_leafs> m_wleaf_buf1;
|
|
etl::vector<weighted_leaf_t, g_max_leafs> m_wleaf_buf2;
|
|
|
|
// Need to use pointers betwen etl::ivector does not implement swap :(
|
|
etl::ivector<leaf_t>* m_leafs_prev = &m_leaf_buf1;
|
|
etl::ivector<leaf_t>* m_leafs_cur = &m_leaf_buf2;
|
|
|
|
etl::ivector<weighted_leaf_t>* m_wleafs_prev = &m_wleaf_buf1;
|
|
etl::ivector<weighted_leaf_t>* m_wleafs_cur = &m_wleaf_buf2;
|
|
|
|
uint32_t m_gen_num = 0;
|
|
|
|
void parse_group_characteristic(group_id_t gid, group_output_t& output, const ruleset_t& ruleset);
|
|
|
|
void swap_buffers();
|
|
void clear_current_buffers();
|
|
void clear_previous_buffers();
|
|
void clear_all_buffers();
|
|
|
|
bool generate_group(group_id_t gid, mtl::vec2 pos, mtl::mat<2, 2> orient, etl::ivector<marker_t>& out_markers);
|
|
bool generate_axiom(etl::ivector<marker_t>& out_markers);
|
|
bool generate_leafs(etl::ivector<marker_t>& out_markers);
|
|
|
|
public:
|
|
generator_t() : m_axiom(0) { }
|
|
generator_t(const ruleset_t& ruleset) : m_axiom(0) {
|
|
parse_ruleset(ruleset);
|
|
}
|
|
|
|
void set_scale(mtl::fixed scale) { m_scale = scale; }
|
|
|
|
void parse_ruleset(const ruleset_t& ruleset);
|
|
|
|
/**
|
|
* @brief Steps one generation, outputting generated markers
|
|
*
|
|
* @tparam CONTAINER_T Container type of @c marker_t that supplies an @c insert member
|
|
* function taking an iterator and value, @c size , and @c max_size functions
|
|
*
|
|
* @param out_markers Reference to a container to append generated markers to
|
|
*
|
|
* @ret false if there is not enough room for the child leafs (internally)
|
|
* or there is not enough room for the generated markers in @p out_markers, true otherwise
|
|
*
|
|
* Steps one generation of the fractal, and appends any child markers of
|
|
* the current generation to @p out_markers. If there is not enough room
|
|
* for all of the child markers, @p out_markers is unmodified.
|
|
*/
|
|
bool step_generation(etl::ivector<marker_t>& out_markers);
|
|
|
|
uint32_t generation_num() const { return m_gen_num; }
|
|
};
|
|
|
|
template <typename CONTGROUP_T, typename CONTWGROUP_T>
|
|
void ruleset_t::find_token_matches(token_id_t token, CONTGROUP_T& out_groups, CONTWGROUP_T& out_wgroups) const {
|
|
for (auto rule : m_branch_rules) {
|
|
if (rule.m_match == token) {
|
|
if (rule.m_weighted) {
|
|
out_wgroups.push_back(rule.m_group.weighted);
|
|
} else {
|
|
out_groups.push_back(rule.m_group.basic);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace fractal
|
|
|