297 lines
9.6 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_basic_id_t = uint32_t;
using branch_rule_marking_id_t = uint32_t;
enum class token_type_e {
none,
walk,
rotate,
generate,
};
struct token_t {
token_type_e m_token = token_type_e::none;
mtl::fixed m_value;
};
struct group_characteristic_t {
static constexpr size_t g_max_group_size = 32;
uint32_t m_factor; // Number of groups generated in succession
etl::vector<token_id_t, g_max_group_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;
};
struct weighted_group_t {
static constexpr size_t g_max_group_weights = 8;
etl::vector<group_id_t, g_max_group_weights> m_groups;
etl::vector<uint32_t, g_max_group_weights> m_weights;
uint32_t m_weight_total;
};
struct branch_rule_basic_t {
token_id_t m_match;
weighted_group_id_t m_weighted_group;
};
struct branch_rule_marking_t {
token_id_t m_match;
uint32_t m_marker_id;
};
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;
};
} // namespace fractal