generator.py

3.4 kB · python · 114 lines

1import numpy as np2from typing import List, Tuple3from mrlypy.core.errors import MrlyError4from .models import Network56# ABSTRACT FRACTAL TREE78def tree_2d(9    depth: int = 6,10    angle: float = 30.0,11    ratio: float = 0.7,12    children: int = 2,13    length: float = 1.0,14) -> Network:15    if depth < 1:16        raise MrlyError("Tree depth must be at least 1.")17    if children < 1:18        raise MrlyError("Tree must have at least one child per node.")19    network = Network(dim=2)20    root = network.add_node((0.0, 0.0))21    spread = np.radians(angle)22    _grow_2d(network, root, np.array([0.0, 1.0]), length, depth, spread, ratio, children)23    return network2425def _grow_2d(26    network: Network,27    parent: int,28    direction: np.ndarray,29    length: float,30    depth: int,31    spread: float,32    ratio: float,33    children: int,34):35    if depth == 0:36        return37    base = network.nodes[parent].position38    if children == 1:39        offsets = [0.0]40    else:41        offsets = list(np.linspace(-spread, spread, children))42    for offset in offsets:43        rotated = _rotate(direction, offset)44        tip = (base[0] + rotated[0] * length, base[1] + rotated[1] * length)45        child = network.add_node(tip)46        network.add_branch(parent, child, radius=ratio ** (0) + depth)47        _grow_2d(network, child, rotated, length * ratio, depth - 1, spread, ratio, children)4849def _rotate(vector: np.ndarray, angle: float) -> np.ndarray:50    cos_a, sin_a = np.cos(angle), np.sin(angle)51    matrix = np.array([[cos_a, -sin_a], [sin_a, cos_a]])52    return matrix @ vector5354# L-SYSTEM PLANT5556def plant_2d(57    depth: int = 5,58    angle: float = 25.0,59    ratio: float = 0.65,60    length: float = 1.0,61) -> Network:62    return tree_2d(depth=depth, angle=angle, ratio=ratio, children=2, length=length)6364# 3D TREE6566def tree_3d(67    depth: int = 5,68    angle: float = 30.0,69    ratio: float = 0.7,70    children: int = 3,71    length: float = 1.0,72) -> Network:73    if depth < 1:74        raise MrlyError("Tree depth must be at least 1.")75    network = Network(dim=3)76    root = network.add_node((0.0, 0.0, 0.0))77    spread = np.radians(angle)78    _grow_3d(network, root, np.array([0.0, 0.0, 1.0]), length, depth, spread, ratio, children)79    return network8081def _grow_3d(82    network: Network,83    parent: int,84    direction: np.ndarray,85    length: float,86    depth: int,87    spread: float,88    ratio: float,89    children: int,90):91    if depth == 0:92        return93    base = network.nodes[parent].position94    axis_a, axis_b = _frame(direction)95    for k in range(children):96        phi = 2.0 * np.pi * k / children97        bend = axis_a * np.cos(phi) + axis_b * np.sin(phi)98        rotated = _normalize(direction * np.cos(spread) + bend * np.sin(spread))99        tip = tuple(base[i] + rotated[i] * length for i in range(3))100        child = network.add_node(tip)101        network.add_branch(parent, child, radius=float(depth))102        _grow_3d(network, child, rotated, length * ratio, depth - 1, spread, ratio, children)103104def _frame(direction: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:105    reference = np.array([1.0, 0.0, 0.0]) if abs(direction[0]) < 0.9 else np.array([0.0, 1.0, 0.0])106    axis_a = _normalize(np.cross(direction, reference))107    axis_b = _normalize(np.cross(direction, axis_a))108    return axis_a, axis_b109110def _normalize(vector: np.ndarray) -> np.ndarray:111    norm = np.linalg.norm(vector)112    if norm == 0:113        return vector114    return vector / norm