#pragma once #include #include #include #include #include #include // 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); 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; 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; uint32_t m_mark; }; struct group_characteristic_t { uint32_t m_factor; // Number of groups generated in succession etl::vector 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_output_t { static constexpr size_t g_max_child_leafs = 16; static constexpr size_t g_max_child_markers = 8; etl::vector m_child_leafs; etl::vector m_child_markers; }; struct token_match_t { etl::vector m_groups; etl::vector m_wgroups; }; struct weighted_group_t { etl::vector m_groups; etl::vector 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 m_tokens; etl::vector m_group_characteristics; etl::vector m_weighted_groups; etl::vector m_branch_rules; 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, 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& 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 * (weight) / (total weight) 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>& 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); }; class generator_t { etl::vector m_weighted_groups; etl::vector m_group_outputs; etl::vector m_token_matches; group_id_t m_axiom; uint32_t m_gen_num; public: generator_t(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. */ template bool step_generation(CONTAINER_T& out_markers); uint32_t generation_num() const { return m_gen_num; } }; #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 m_leaf_buf1; etl::vector m_leaf_buf2; etl::vector m_tokens; etl::vector m_group_characteristics; etl::vector m_groups; etl::vector m_weighted_groups; etl::vector m_basic_branch_rules; etl::vector m_marking_branch_rules; etl::ivector* m_leafs_cur; etl::ivector* 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& tokens); /** * @overload * * Convience overload, defaulting to a group with factor 1. */ group_id_t add_group_characteristic(const etl::ivector& tokens); /** * @brief Add a weighted group to the generator. * * Adds weighted group of groups, where each subgroup has a * (weight) / (total weight) 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>& 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& out_markers) noexcept; }; #endif } // namespace fractal