527 lines
14 KiB
C++
527 lines
14 KiB
C++
/*
|
|
* Allen's Fancy Tree Generator
|
|
* 22.08.2015 (dd.mm.yyyy)
|
|
*/
|
|
|
|
// TOP
|
|
|
|
internal
|
|
split_point(real32 base_radius, real32 branch_radius, i32 branch_n, i32 i){
|
|
real32 branch_dist = branch_radius*2.f;
|
|
Vec2 result = {};
|
|
switch (branch_n){
|
|
case 1:
|
|
{
|
|
if (i == 0) result.x = -base_radius;
|
|
else result.x = base_radius;
|
|
}break;
|
|
|
|
case 2:
|
|
{
|
|
real32 a,b,c;
|
|
a = base_radius;
|
|
c = branch_dist;
|
|
a *= a;
|
|
c *= c;
|
|
b = c - a;
|
|
b = SQRT(b);
|
|
switch (i){
|
|
case 0: result.x = -base_radius; result.y = b*.5f; break;
|
|
case 1: result.x = 0; result.y = -b*.5f; break;
|
|
case 2: result.x = base_radius; result.y = b*.5f; break;
|
|
}
|
|
}break;
|
|
|
|
case 3:
|
|
{
|
|
real32 a,b,c,aa,cc;
|
|
a = base_radius - branch_dist*.5f;
|
|
c = branch_dist;
|
|
aa = a*a;
|
|
cc = c*c;
|
|
b = cc - aa;
|
|
b = SQRT(b);
|
|
switch (i){
|
|
case 0: result.x = -base_radius; result.y = b*.5f; break;
|
|
case 1: result.x = a-base_radius; result.y = -b*.5f; break;
|
|
case 2: result.x = base_radius-a; result.y = -b*.5f; break;
|
|
case 3: result.x = base_radius; result.y = b*.5f; break;
|
|
}
|
|
}break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
struct Hot_Spot{
|
|
Vec2 spot;
|
|
};
|
|
|
|
struct Tree_Parameters{
|
|
Vec2 base;
|
|
i32 levels;
|
|
real32 initial_step;
|
|
real32 step_decay;
|
|
real32 initial_thickness;
|
|
real32 thickness_decay;
|
|
real32 dtheta;
|
|
real32 min_weight_threshold;
|
|
real32 center_branch_weight;
|
|
real32 heat_half_distance;
|
|
|
|
Hot_Spot *hot_spots;
|
|
i32 hot_spot_count;
|
|
};
|
|
|
|
struct Tree_Skeleton_Vert{
|
|
Vec2 pos;
|
|
i32 child_count;
|
|
i32 children[3];
|
|
real32 theta;
|
|
real32 width;
|
|
Vec4 paint;
|
|
};
|
|
|
|
struct Skeleton_Branch_Point{
|
|
i32 vert_id;
|
|
real32 step;
|
|
real32 theta;
|
|
real32 width;
|
|
i32 depth;
|
|
};
|
|
|
|
internal Skeleton_Branch_Point
|
|
SBP(i32 id, real32 step, real32 theta, real32 width, i32 depth){
|
|
Skeleton_Branch_Point result;
|
|
result.vert_id = id;
|
|
result.step = step;
|
|
result.theta = theta;
|
|
result.width = width;
|
|
result.depth = depth;
|
|
return result;
|
|
}
|
|
|
|
struct r32_Range{
|
|
real32 start, end;
|
|
};
|
|
|
|
inline r32_Range
|
|
get_range(real32 a, real32 b){
|
|
r32_Range result;
|
|
if (a < b){
|
|
result.start = a;
|
|
result.end = b;
|
|
}
|
|
else{
|
|
result.start = b;
|
|
result.end = a;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
persist Vec4 brown = {.5f, .3333f, 0.f, 1.f};
|
|
persist Vec4 silver = {.7f, .7f, .7f, 1.f};
|
|
persist Vec4 faded_gold = {.7f, .4f, .2f, 0.5f};
|
|
persist Vec4 sky_blue = {0.f, .7f, 1.f, 1.f};
|
|
|
|
internal bool32
|
|
segments_collide(Vec2 a1, Vec2 a2, Vec2 b1, Vec2 b2, Vec2 *p_out = 0){
|
|
bool32 result = 1;
|
|
|
|
Vec3 r1, r2;
|
|
if (a2.y != a1.y){
|
|
r1.x = (a2.y - a1.y);
|
|
r1.y = (a1.x - a2.x);
|
|
r1.z = (a1.x*r1.x + a1.y*r1.y);
|
|
|
|
r2.x = (b2.y - b1.y);
|
|
r2.y = (b1.x - b2.x);
|
|
r2.z = (b1.x*r2.x + b1.y*r2.y);
|
|
}
|
|
else{
|
|
r1.x = (b2.y - b1.y);
|
|
r1.y = (b1.x - b2.x);
|
|
r1.z = (b1.x*r1.x + b1.y*r1.y);
|
|
|
|
r2.x = (a2.y - a1.y);
|
|
r2.y = (a1.x - a2.x);
|
|
r2.z = (a1.x*r2.x + a1.y*r2.y);
|
|
}
|
|
|
|
if (r1.x == 0){
|
|
result = (a1.y == b1.y) && ((b1.x <= a1.x && a1.x < b2.x) || (a1.x <= b1.x && b1.x < a2.x));
|
|
return result;
|
|
}
|
|
|
|
if (r2.x != 0){
|
|
r2.y /= r2.x;
|
|
r2.z /= r2.x;
|
|
r2.y *= r1.x;
|
|
r2.z *= r1.x;
|
|
r2.y -= r1.y;
|
|
r2.z -= r1.z;
|
|
r2.x = 0;
|
|
}
|
|
|
|
if (r2.y == 0){
|
|
result = (r2.z == 0) && ((b1.y <= a1.y && a1.y < b2.y) || (a1.y <= b1.y && b1.y < a2.y));
|
|
return result;
|
|
}
|
|
|
|
if (r1.y != 0){
|
|
r1.x /= r1.y;
|
|
r1.z /= r1.y;
|
|
r1.x *= r2.y;
|
|
r1.z *= r2.y;
|
|
r1.x -= r2.x;
|
|
r1.z -= r2.z;
|
|
r1.y = 0;
|
|
}
|
|
|
|
assert(!(r1.x == 0 || r2.y == 0));
|
|
|
|
r1 *= 1.f / r1.x;
|
|
r2 *= 1.f / r2.y;
|
|
|
|
Vec2 cl;
|
|
cl.x = r1.z;
|
|
cl.y = r2.z;
|
|
if (p_out) *p_out = cl;
|
|
|
|
real32 epsilon = .0001f;
|
|
real32 neg_epsilon = -epsilon;
|
|
real32 xdif;
|
|
|
|
xdif = a1.x - a2.x;
|
|
if (xdif < neg_epsilon && xdif > epsilon){
|
|
r32_Range range = get_range(a1.x, a2.x);
|
|
if (cl.x < range.start || cl.x > range.end){
|
|
result = 0;
|
|
}
|
|
}
|
|
else{
|
|
r32_Range range = get_range(a1.y, a2.y);
|
|
if (cl.y < range.start || cl.y > range.end){
|
|
result = 0;
|
|
}
|
|
}
|
|
|
|
xdif = a1.x - a2.x;
|
|
if (xdif < neg_epsilon && xdif > epsilon){
|
|
r32_Range range = get_range(b1.x, b2.x);
|
|
if (cl.x < range.start || cl.x > range.end){
|
|
result = 0;
|
|
}
|
|
}
|
|
else{
|
|
r32_Range range = get_range(b1.y, b2.y);
|
|
if (cl.y < range.start || cl.y > range.end){
|
|
result = 0;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
internal bool32
|
|
no_collisions(Vec2 base, Vec2 new_pos, Tree_Skeleton_Vert *vertices){
|
|
bool32 result = 1;
|
|
i32 vert_id_stack[16];
|
|
i32 top = 0;
|
|
vert_id_stack[top++] = 0;
|
|
|
|
while (top > 0){
|
|
Tree_Skeleton_Vert *vert = vertices + vert_id_stack[--top];
|
|
i32 count = vert->child_count;
|
|
i32 *children = vert->children;
|
|
Vec2 c = vert->pos;
|
|
for (i32 i = 0; i < count; ++i){
|
|
i32 child_id = children[i];
|
|
Tree_Skeleton_Vert *other_vert = vertices + child_id;
|
|
Vec2 d = other_vert->pos;
|
|
bool32 exempt = (base.x == d.x) && (base.y == d.y);
|
|
if (!exempt) exempt = (base.x == c.x) && (base.y == c.y);
|
|
if (!exempt && segments_collide(base, new_pos, c, d)){
|
|
other_vert->paint = sky_blue;
|
|
result = 0;
|
|
goto no_collisions_end;
|
|
}
|
|
else{
|
|
vert_id_stack[top++] = child_id;
|
|
}
|
|
}
|
|
}
|
|
no_collisions_end:
|
|
return result;
|
|
}
|
|
|
|
struct Branch_Info{
|
|
i32 which_branch;
|
|
|
|
real32 step;
|
|
real32 length;
|
|
real32 contribution_list[3];
|
|
i32 contribution_count;
|
|
};
|
|
|
|
enum Info_To_Post{
|
|
Step_Info,
|
|
Length_Info,
|
|
Contribution_Info,
|
|
Count_Info
|
|
};
|
|
|
|
internal void
|
|
post_branch_info(Branch_Info *info, i32 count, i32 index, Info_To_Post type, real32 x){
|
|
i32 i;
|
|
for (i = 0; i < count; ++i, ++info){
|
|
if (index == info->which_branch){
|
|
switch (type){
|
|
case Step_Info: info->step = x; break;
|
|
case Length_Info: info->length = x; break;
|
|
case Contribution_Info:
|
|
if (info->contribution_count < ArrayCount(info->contribution_list)){
|
|
info->contribution_list[info->contribution_count++] = x;
|
|
}break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal i32
|
|
tree_skeleton_gen(Tree_Skeleton_Vert *vertices, i32 vertex_count, i32 *vertex_count_out,
|
|
Tree_Parameters params, i32 limit, bool32 allow_colliding,
|
|
Branch_Info *opt_output, i32 opt_output_count){
|
|
|
|
Skeleton_Branch_Point queue[730];
|
|
i32 queue_wrap = ArrayCount(queue);
|
|
i32 write_pos = 0;
|
|
i32 read_pos = 0;
|
|
i32 k = 0;
|
|
|
|
vertices[k].pos = params.base;
|
|
queue[write_pos++] =
|
|
SBP(k, params.initial_step, 270.f, params.initial_thickness, 0);
|
|
if (opt_output){
|
|
post_branch_info(opt_output, opt_output_count, k, Step_Info, params.initial_step);
|
|
post_branch_info(opt_output, opt_output_count, k, Length_Info, params.initial_step);
|
|
}
|
|
++k;
|
|
|
|
i32 I = 0;
|
|
for (; read_pos != write_pos; ){
|
|
Skeleton_Branch_Point *bp = queue + read_pos;
|
|
++read_pos;
|
|
read_pos = read_pos % queue_wrap;
|
|
|
|
Tree_Skeleton_Vert *vert = vertices + bp->vert_id;
|
|
vert->theta = bp->theta;
|
|
vert->width = bp->width;
|
|
if (vert->paint.r == 0 && vert->paint.g == 0 && vert->paint.b == 0){
|
|
if (allow_colliding) vert->paint = faded_gold;
|
|
else vert->paint = silver;
|
|
}
|
|
|
|
i32 *index_order = 0;
|
|
persist i32 index_order_case_1[] = {0};
|
|
persist i32 index_order_case_3[] = {1, 0, 2};
|
|
|
|
i32 branch_count;
|
|
if (bp->depth == 0){
|
|
branch_count = 1;
|
|
index_order = index_order_case_1;
|
|
}else if (bp->depth < params.levels){
|
|
branch_count = 3;
|
|
index_order = index_order_case_3;
|
|
}else{
|
|
branch_count = 0;
|
|
}
|
|
|
|
Vec2 p = vert->pos;
|
|
real32 theta_base = bp->theta - (branch_count - 1) * params.dtheta * .5f;
|
|
i32 actual_branch_count = 0;
|
|
_Assert(vert->children[0] == 0);
|
|
_Assert(vert->children[1] == 0);
|
|
_Assert(vert->children[2] == 0);
|
|
|
|
bool32 block_others = 0;
|
|
for (i32 ii = 0; ii < branch_count && !block_others; ++ii){
|
|
i32 i = index_order[ii];
|
|
|
|
real32 theta = theta_base + params.dtheta * i;
|
|
real32 default_step = bp->step;
|
|
#if 0
|
|
Vec2 towards_new_p = polar_to_cartesian(theta, default_step);
|
|
f32 weighted_theta = theta;
|
|
f32 weight = 0.f;
|
|
i32 contribution_count = 0;
|
|
for (i32 j = 0; j < params.hot_spot_count; ++j){
|
|
Vec2 towards_hot_spot = params.hot_spots[j] - p;
|
|
real32 projection = vector_projection(towards_new_p, towards_hot_spot);
|
|
if (projection > 0.f){
|
|
weight += projection;
|
|
++contribution_count;
|
|
}
|
|
if (opt_output){
|
|
post_branch_info(opt_output, opt_output_count, k, Contribution_Info, weight);
|
|
}
|
|
}
|
|
if (theta >= bp->theta - 0.001f && theta <= bp->theta + 0.001f){
|
|
//weight *= params.center_branch_weight;
|
|
}
|
|
if (contribution_count != 0){
|
|
weight /= contribution_count;
|
|
}
|
|
|
|
#elif 1
|
|
f32 weight = default_step;
|
|
f32 weighted_theta = theta;
|
|
|
|
f32 average_arg = 0.f, total_weight = 0.f;
|
|
|
|
Vec2 d_vec;
|
|
f32 arg, d, local_weight;
|
|
Hot_Spot *spot = params.hot_spots;
|
|
for (i32 j = 0; j < params.hot_spot_count; ++j, ++spot){
|
|
d_vec = spot->spot - p;
|
|
arg = argument_degrees(d_vec);
|
|
d = SQRT(dot(d_vec, d_vec));
|
|
d -= default_step;
|
|
if (d > 0 && d < params.heat_half_distance * 5.f){
|
|
local_weight = POW(2.f, -d / params.heat_half_distance);
|
|
total_weight += local_weight;
|
|
average_arg += (arg*local_weight);
|
|
}
|
|
}
|
|
|
|
if (total_weight > 0){
|
|
average_arg /= total_weight;
|
|
weighted_theta = average_arg;
|
|
|
|
if (weighted_theta < 0) weighted_theta += 360.f;
|
|
|
|
f32 max_theta, min_theta;
|
|
max_theta = theta + 20.f;
|
|
min_theta = theta - 20.f;
|
|
|
|
if (min_theta < 0) min_theta += 360.f;
|
|
if (max_theta < min_theta){
|
|
max_theta += 360.f;
|
|
}
|
|
weighted_theta = Max(min_theta, Min(max_theta, weighted_theta));
|
|
|
|
if (weighted_theta >= 360.f) weighted_theta -= 360.f;
|
|
}
|
|
|
|
#else
|
|
f32 weight = default_step;
|
|
f32 weighted_theta = theta;
|
|
#endif
|
|
|
|
if (weight > params.min_weight_threshold){
|
|
Vec2 new_p = p + polar_to_cartesian(weighted_theta, weight);
|
|
|
|
if (I < limit && (allow_colliding || no_collisions(p, new_p, vertices))){
|
|
++I;
|
|
|
|
_Assert(k < vertex_count);
|
|
vert->children[i] = k;
|
|
++actual_branch_count;
|
|
vertices[k].pos = new_p;
|
|
// TODO(allen): should the new length be based on bp->step or on this branch's specific weight?
|
|
queue[write_pos++] =
|
|
SBP(k, bp->step * params.step_decay, theta, bp->width * params.thickness_decay, bp->depth + 1);
|
|
write_pos = write_pos % queue_wrap;
|
|
_Assert(write_pos != read_pos);
|
|
|
|
if (opt_output){
|
|
post_branch_info(opt_output, opt_output_count, k, Step_Info, bp->step);
|
|
post_branch_info(opt_output, opt_output_count, k, Length_Info, weight);
|
|
}
|
|
|
|
++k;
|
|
}
|
|
else{
|
|
if (ii == 0) block_others = 1;
|
|
}
|
|
}
|
|
else{
|
|
if (ii == 0) block_others = 1;
|
|
}
|
|
}
|
|
vert->child_count = actual_branch_count;
|
|
i32 i, j;
|
|
for (i = 0, j = 0; i < 3; ++i){
|
|
if (vert->children[i] != 0){
|
|
vert->children[j] = vert->children[i];
|
|
++j;
|
|
}
|
|
}
|
|
_Assert(j == actual_branch_count);
|
|
for (; j < 3; ++j){
|
|
vert->children[j] = 0;
|
|
}
|
|
}
|
|
*vertex_count_out = k;
|
|
|
|
return I;
|
|
}
|
|
|
|
struct Thickness_Point{
|
|
i32 id;
|
|
i32 next_child;
|
|
};
|
|
|
|
internal Thickness_Point
|
|
TP(i32 id){
|
|
Thickness_Point result;
|
|
result.id = id;
|
|
result.next_child = 0;
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
tree_gen(Vec2 *vertices, i32 vertex_count, i32 *vertex_count_out, Tree_Parameters params,
|
|
Tree_Skeleton_Vert *skeleton){
|
|
Thickness_Point stack[16];
|
|
i32 top = 0;
|
|
i32 k = 0;
|
|
|
|
stack[top++] = TP(0);
|
|
|
|
while (top > 0){
|
|
Thickness_Point *tp = stack + top - 1;
|
|
|
|
Tree_Skeleton_Vert *vert = skeleton + tp->id;
|
|
|
|
{
|
|
Vec2 p = vert->pos;
|
|
Vec2 q = split_point(vert->width, vert->width * params.thickness_decay, vert->child_count, tp->next_child);
|
|
vertices[k++] = p + rotate(q, vert->theta - 270.f);
|
|
}
|
|
|
|
bool32 done = 0;
|
|
if (tp->next_child == 3){
|
|
done = 1;
|
|
}
|
|
else{
|
|
i32 child = vert->children[tp->next_child];
|
|
if (child){
|
|
stack[top++] = TP(child);
|
|
++tp->next_child;
|
|
}
|
|
else{
|
|
done = 1;
|
|
}
|
|
}
|
|
if (done){
|
|
--top;
|
|
}
|
|
}
|
|
*vertex_count_out = k;
|
|
}
|
|
|
|
// BOTTOM
|
|
|