Separating fractal generation run from ruleset generation. After the ruleset is processed, there is no reason for the group characteristics, etc to still be used. Additionally, this will avoid the possibility of the user attempting to use the fractal before ruleset is created and processed. Other changes: Branch rules now use unioned weighted or unweighted group. Rewrite ruleset creation usage
338 lines
11 KiB
C++
338 lines
11 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
|
|
#include <etl/vector.h>
|
|
#include <mtl/fixed.hpp>
|
|
#include <mtl/vec.hpp>
|
|
#include <mtl/mat.hpp>
|
|
|
|
namespace fractal {
|
|
|
|
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;
|
|
using mark_rule_id_t = uint32_t;
|
|
|
|
enum class token_type_e {
|
|
empty,
|
|
walk,
|
|
rotate,
|
|
};
|
|
|
|
struct token_t {
|
|
token_type_e m_token = token_type_e::empty;
|
|
mtl::fixed m_value;
|
|
};
|
|
|
|
template <size_t S_MAX_CGROUP_SIZE = 16>
|
|
struct group_characteristic_t {
|
|
uint32_t m_factor; // Number of groups generated in succession
|
|
etl::vector<token_id_t, S_MAX_CGROUP_SIZE> 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_weighed_group_id;
|
|
mtl::vec2 m_position;
|
|
mtl::mat<2, 2> m_orientation;
|
|
};
|
|
|
|
struct group_t {
|
|
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_leafs> m_child_leafs;
|
|
etl::vector<marker_t, g_max_child_markers> m_child_markers;
|
|
};
|
|
|
|
template <size_t S_MAX_GROUP_WEIGHTS = 8>
|
|
struct weighted_group_t {
|
|
etl::vector<group_id_t, S_MAX_GROUP_WEIGHTS> m_groups;
|
|
etl::vector<uint32_t, S_MAX_GROUP_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;
|
|
};
|
|
|
|
struct mark_rule_t {
|
|
token_id_t m_match;
|
|
uint32_t m_tag;
|
|
};
|
|
|
|
template <
|
|
size_t S_MAX_TOKENS = 8,
|
|
size_t S_MAX_CGROUPS = 8,
|
|
size_t S_MAX_WGROUPS = 8,
|
|
size_t S_MAX_BRANCH_RULES = 4,
|
|
size_t S_MAX_MARK_RULES = 4,
|
|
size_t S_MAX_CGROUP_TOKENS = 16,
|
|
size_t S_MAX_WGROUP_WEIGHTS = 8
|
|
>
|
|
class ruleset_t {
|
|
private:
|
|
etl::vector<token_t, S_MAX_TOKENS> m_tokens;
|
|
etl::vector<group_characteristic_t<S_MAX_CGROUP_TOKENS>, S_MAX_CGROUPS> m_group_characteristics;
|
|
etl::vector<weighted_group_t<S_MAX_WGROUP_WEIGHTS>, S_MAX_WGROUPS> m_weighted_groups;
|
|
|
|
etl::vector<branch_rule_t, S_MAX_BRANCH_RULES> m_branch_rules;
|
|
etl::vector<mark_rule_t, S_MAX_MARK_RULES> m_mark_rules;
|
|
|
|
public:
|
|
bool tokens_full() const;
|
|
token_id_t add_token(token_type_e type, mtl::fixed value = 0);
|
|
|
|
bool group_characteristics_full() const;
|
|
group_id_t add_group_characteristic(const etl::ivector<token_id_t>& tokens, uint32_t factor = 1);
|
|
|
|
bool weighted_groups_full() const;
|
|
weighted_group_id_t add_weighted_group(const etl::ivector<group_id_t>& groups);
|
|
|
|
bool branch_rules_full() const;
|
|
branch_rule_id_t add_branch_rule(token_id_t match, group_id_t group);
|
|
branch_rule_id_t add_branch_rule_weighted(token_id_t match, weighted_group_id_t wgroup);
|
|
|
|
bool mark_rules_full() const;
|
|
mark_rule_id_t add_mark_rule(token_id_t match, uint32_t tag);
|
|
};
|
|
|
|
#if 0
|
|
class generator_t {
|
|
private:
|
|
static constexpr size_t g_max_groups = 32;
|
|
static constexpr size_t g_max_weighted_groups = 32;
|
|
static constexpr size_t g_max_tokens = 32;
|
|
static constexpr size_t g_max_leafs_per_gen = 1024; // Maximum leafs per generation
|
|
static constexpr size_t g_max_rules_basic = 16;
|
|
static constexpr size_t g_max_rules_marking = 4;
|
|
|
|
// We are creating two buffers are leafs. One for the current generation
|
|
// and one for the next generation. These will be accessed through current/next
|
|
// ivectors that will point to different buffers as they are swapped.
|
|
etl::vector<leaf_t, g_max_leafs_per_gen> m_leaf_buf1;
|
|
etl::vector<leaf_t, g_max_leafs_per_gen> m_leaf_buf2;
|
|
|
|
etl::vector<token_t, g_max_tokens> m_tokens;
|
|
|
|
etl::vector<group_characteristic_t, g_max_groups> m_group_characteristics;
|
|
etl::vector<group_t, g_max_groups> m_groups;
|
|
etl::vector<weighted_group_t, g_max_weighted_groups> m_weighted_groups;
|
|
|
|
etl::vector<branch_rule_basic_t, g_max_rules_basic> m_basic_branch_rules;
|
|
etl::vector<branch_rule_marking_t, g_max_rules_marking> m_marking_branch_rules;
|
|
|
|
etl::ivector<leaf_t>* m_leafs_cur;
|
|
etl::ivector<leaf_t>* m_leafs_next;
|
|
|
|
bool m_processed;
|
|
uint32_t m_generation;
|
|
|
|
mtl::fixed m_scale_factor;
|
|
|
|
weighted_group_id_t m_axiom;
|
|
mtl::vec2 m_init_position;
|
|
mtl::fixed m_init_orientation;
|
|
|
|
public:
|
|
/**
|
|
* Default constructor
|
|
*/
|
|
generator_t() noexcept :
|
|
m_leafs_cur(&m_leaf_buf1),
|
|
m_leafs_next(&m_leaf_buf2),
|
|
m_axiom(0),
|
|
m_generation(0),
|
|
m_scale_factor(1),
|
|
m_processed(false) {}
|
|
|
|
/**
|
|
* @brief Add new token variable that can be used in this generator's group characteristics.
|
|
*
|
|
* @param type Type of the token to add
|
|
* @param value Value of the token. Unused if @p type is @c token_type_e::generate. Defaults to 0
|
|
*
|
|
* @ret @c token_id_t of the newly added token, local to this generator.
|
|
*
|
|
* @pre preprocess() has not been called on this generator.
|
|
*
|
|
* @exception mtl::system_error If the generator has already been preprocessed.
|
|
* @exception mtl::invalid_argument If the given token type is invalid.
|
|
* @exception mtl::length_error If the maximum number of tokens is reached.
|
|
*/
|
|
token_id_t add_token(token_type_e type, mtl::fixed value = 0);
|
|
|
|
/**
|
|
* @brief Add new group charateristic.
|
|
*
|
|
* After all tokens, groups, and rules have been added, the generator's groups
|
|
* must be processed by calling preprocess(). Once this is done, no new
|
|
* groups may be added. If an exception occurs, no data is modified.
|
|
*
|
|
* @param factor The number of times the group is repeated. Treats the group as if @p tokens was repeated @p factor number of times.
|
|
* @param tokens @c etl::ivector of @token_id_t that describe how the group functions. Processed in order, front to back.
|
|
*
|
|
* @ret @c group_id_t of the newly added group, local to this generator.
|
|
*
|
|
* @pre preprocess() has not been called on this generator.
|
|
*
|
|
* @exception mtl::system_error If the generator has already been preprocessed.
|
|
* @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(uint32_t factor, const etl::ivector<token_id_t>& tokens);
|
|
/**
|
|
* @overload
|
|
*
|
|
* Convience overload, defaulting to a group with factor 1.
|
|
*/
|
|
group_id_t add_group_characteristic(const etl::ivector<token_id_t>& tokens);
|
|
|
|
/**
|
|
* @brief Add a weighted group to the generator.
|
|
*
|
|
* 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. If an exception occurs, no data is modified.
|
|
*
|
|
* @param weights @c etl::ivector of @c etl::pair s of @c group_id_it and their corresponding weights.
|
|
*
|
|
* @ret @c weighted_group_id_t of the newly added group, local to this generator.
|
|
*
|
|
* @pre preprocess() has not been called on this generator.
|
|
*
|
|
* @exception mtl::invalid_argument If no groups were supplied, or an invalid group was encountered.
|
|
* @exception mtl::system_error If the generator has already been preprocessed.
|
|
* @exception mtl::length_error If the maximum number of weighted groups is reached.
|
|
*/
|
|
weighted_group_id_t add_weighted_group(const etl::ivector<etl::pair<group_id_t, uint32_t>>& group_weights);
|
|
/**
|
|
* @overload
|
|
*
|
|
* Convenience overload. Creates a weighted group for a group with only
|
|
* one possibility.
|
|
*/
|
|
weighted_group_id_t add_weighted_group(group_id_t group);
|
|
|
|
/**
|
|
* @brief Add a basic (non-marking) branch rule to the generator.
|
|
*
|
|
* Basic branch rules describe how this generator will create new leafs
|
|
* each generation.
|
|
*
|
|
* @param match Token to match and generate the group in place of.
|
|
* @param wgroup Weighted group to generate. Only one subgroup will be selected to generate.
|
|
*
|
|
* @ret @c branch_rule_basic_id_t of the newly added branch rule, local to this generator.
|
|
*
|
|
* @pre preprocess() has not been called on this generator.
|
|
*
|
|
* @exception mtl::invalid_argument If no groups were supplied, or an invalid group was encountered.
|
|
* @exception mtl::system_error If the generator has already been preprocessed.
|
|
* @exception mtl::length_error If the maximum number of basic branch rules is reached.
|
|
*/
|
|
branch_rule_basic_id_t add_basic_branch_rule(token_id_t match, weighted_group_id_t wgroup);
|
|
|
|
/**
|
|
* @brief Add a marking branch rule to the generator.
|
|
*
|
|
* Marking branch rules describe what points (markers) this generator
|
|
* will output to the caller each generator.
|
|
*
|
|
* @param match Token to match and mark.
|
|
* @param marker_id ID to mark the point with.
|
|
*
|
|
* @ret @c branch_rule_marking_id_t of the newly added branch rule, local to this generator.
|
|
*
|
|
* @pre preprocess() has not been called on this generator.
|
|
*
|
|
* @exception mtl::invalid_argument If an invalid token was supplied.
|
|
* @exception mtl::system_error If the generator has already been preprocessed.
|
|
* @exception mtl::length_error If the maximum number of marking branch rules is reached.
|
|
*/
|
|
branch_rule_marking_id_t add_marking_branch_rule(token_id_t match, uint32_t marker_id);
|
|
|
|
/**
|
|
* @brief Set the axiom used in this generator.
|
|
*
|
|
* The "axiom" is used to generate the very first generation of leafs.
|
|
* Only one subgroup from the weighted group is selected.
|
|
*
|
|
* @exception mtl::invalid If an invalid weighted group ID was suppied.
|
|
*/
|
|
void set_axiom(weighted_group_id_t wgroup);
|
|
|
|
/**
|
|
* @brief Perform preprocessing on the supplied group characteristics.
|
|
*
|
|
* Transforms each group, producing a list of child leafs and markers
|
|
* that should be generated for each group characteristic. Has no effect
|
|
* if called more than once.
|
|
*
|
|
* @pre All tokens, groups characteristics, weighted groups, and branch
|
|
* rules have been added.
|
|
*
|
|
* @exception mtl::length_error If the number of resulting child leafs
|
|
* or markers exceeds the maximum allowed for one group.
|
|
*/
|
|
void preprocess();
|
|
|
|
/**
|
|
* @brief Set the scale factor used each generation.
|
|
*
|
|
* Each time the generation is stepped, the length of each walk is scaled
|
|
* by @p scale_factor. Can be set to a new value at any time.
|
|
*
|
|
* @param scale_factor The new scale factor to use
|
|
*/
|
|
void set_scale_factor(mtl::fixed scale_factor) noexcept;
|
|
|
|
/**
|
|
* @brief Set the initial position the axiom is generated in.
|
|
*
|
|
* @param position The initial position to use.
|
|
*/
|
|
void set_initial_position(mtl::vec2 position) noexcept;
|
|
/**
|
|
* @brief Set the initial orientation the axiom is generated in.
|
|
*
|
|
* @param orientation The initial orientation to use. Given in radians.
|
|
*/
|
|
void set_initial_orientation(mtl::fixed orientation) noexcept;
|
|
|
|
/**
|
|
* @brief Steps one generation in the fractal, generating a vector of markers.
|
|
*
|
|
* The vector of markers will NOT be cleared at the beginning of generation.
|
|
*
|
|
* @param out_markers An @c etl::ivector of @c marker_t. Each marker
|
|
* encountered will be appended to this list in the order encountered.
|
|
*
|
|
* @ret true if the maximum number of markers was reached, otherwise false.
|
|
*/
|
|
bool step_generation(const etl::ivector<marker_t>& out_markers) noexcept;
|
|
};
|
|
#endif
|
|
|
|
} // namespace fractal
|
|
|