4 * The geeky little puzzle game with a big noodly crunch!
6 * gPlanarity copyright (C) 2005 Monty <monty@xiph.org>
7 * Original Flash game by John Tantalo <john.tantalo@case.edu>
8 * Original game concept by Mary Radcliffe
10 * gPlanarity is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
15 * gPlanarity is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with Postfish; see the file COPYING. If not, write to the
22 * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
33 #include "graph_region.h"
35 /* Regions are 'electric fences' for mesh building; used in mesh2 to
36 make non-convex shapes */
38 typedef struct region_segment
{
39 int layout
; /* 0 no layout, 1 left, 2 right, 3 layout-only */
40 int cont
; /* is this continuous from last line? */
41 int split
; /* are we splitting the graph into interntionally
42 seperate regions here? */
49 // arc computation cache (if arc)
58 struct region_segment
*next
;
61 typedef struct region
{
73 static region layout_adj
;
74 static region_segment
*segpool
=0;
78 static region_segment
*new_segment(region
*r
, int x1
,int y1
,int x2
, int y2
){
83 segpool
= calloc(CHUNK
,sizeof(*segpool
));
84 for(i
=0;i
<CHUNK
-1;i
++) /* last addition's next points to nothing */
85 segpool
[i
].next
=segpool
+i
+1;
91 memset(ret
,0,sizeof(*ret
));
93 ret
->layout
=r
->layout
;
99 ret
->split
= r
->split_next
;
107 /* angle convention: reversed y (1,-1 is first quadrant, ala X
108 windows), range -PI to PI */
110 static int intersects_arc(edge
*e
, region_segment
*r
){
111 float Ax
= e
->A
->x
- r
->cx
;
112 float Ay
= e
->A
->y
- r
->cy
;
113 float Bx
= e
->B
->x
- r
->cx
;
114 float By
= e
->B
->y
- r
->cy
;
118 float dr2
= dx
*dx
+ dy
*dy
;
119 float D
= Ax
*By
- Bx
*Ay
;
120 float discriminant
=(r
->radius
*r
->radius
)*dr2
- D
*D
;
122 // does it even intersect the full circle?
123 if(discriminant
<=0)return 0;
128 float sqrtd
= sqrt(discriminant
);
129 float sign
= (dy
>0?1.f
:-1.f
);
131 // finite precision required here else 0/inf slope lines will be
132 // slighly off the secant
133 x1
= rint((D
*dy
+ sign
*dx
*sqrtd
) / dr2
);
134 x2
= rint((D
*dy
- sign
*dx
*sqrtd
) / dr2
);
136 y1
= rint((-D
*dx
+ fabs(dy
)*sqrtd
) / dr2
);
137 y2
= rint((-D
*dx
- fabs(dy
)*sqrtd
) / dr2
);
144 // is x1,y1 actually on the segment?
145 if( !(x1
<Ax
&& x1
<Bx
) &&
149 // yes. it is in the angle range we care about?
151 float ang
= acos(x1
/ r
->radius
);
155 if(r
->phi0
< r
->phi1
){
156 if(ang
<= r
->phi0
|| ang
>= r
->phi1
)return 1;
158 if(ang
<= r
->phi0
&& ang
>= r
->phi1
)return 1;
161 if(r
->phi0
< r
->phi1
){
162 if(ang
>= r
->phi0
&& ang
<= r
->phi1
)return 1;
164 if(ang
>= r
->phi0
|| ang
<= r
->phi1
)return 1;
169 // is x2,y2 actually on the segment?
170 // if so, it is in the arc range we care about?
171 if( !(x2
<Ax
&& x2
<Bx
) &&
175 // yes. it is in the angle range we care about?
177 float ang
= acos(x2
/ r
->radius
);
181 if(r
->phi0
< r
->phi1
){
182 if(ang
<= r
->phi0
|| ang
>= r
->phi1
)return 1;
184 if(ang
<= r
->phi0
&& ang
>= r
->phi1
)return 1;
187 if(r
->phi0
< r
->phi1
){
188 if(ang
>= r
->phi0
&& ang
<= r
->phi1
)return 1;
190 if(ang
>= r
->phi0
|| ang
<= r
->phi1
)return 1;
198 static float line_angle(float x1
, float y1
, float x2
, float y2
){
209 return atan(-yd
/xd
)+M_PI
;
211 return atan(-yd
/xd
)-M_PI
;
217 static float line_mag(float x1
, float y1
, float x2
, float y2
){
223 static void compute_arc(region_segment
*r
,float phi
){
243 cr
= line_angle(x1
,y1
,x2
,y2
);
244 a
= line_mag(x1
,y1
,x2
,y2
)/2.f
;
245 br
=(M_PI
/2.f
)-(ar
/2.f
);
249 if(phi
<-M_PI
|| (phi
>0 && phi
<M_PI
)){
250 cx
= x1
+ cos(cr
+M_PI
/2)*c
+ xd
/2;
251 cy
= y1
- sin(cr
+M_PI
/2)*c
+ yd
/2;
253 cx
= x1
+ cos(cr
-M_PI
/2)*c
+ xd
/2;
254 cy
= y1
- sin(cr
-M_PI
/2)*c
+ yd
/2;
261 // have the center of the circle, have radius. Determine the
262 // portion of the arc we want.
263 r
->phi0
= acos( (x1
-cx
) / d
);
264 r
->phi1
= acos( (x2
-cx
) / d
);
265 if(y1
>cy
) r
->phi0
= -r
->phi0
;
266 if(y2
>cy
) r
->phi1
= -r
->phi1
;
270 static region_segment
*region_arc(region
*re
, int x1
, int y1
, int x2
, int y2
, float rad
){
271 region_segment
*n
= new_segment(re
,x1
,y1
,x2
,y2
);
276 static region_segment
*region_line(region
*re
,int x1
, int y1
, int x2
, int y2
){
277 return new_segment(re
,x1
,y1
,x2
,y2
);
280 /* The overall idea here is to massage the region segments and arcs
281 slightly so that when we layout points based on a region, the
282 layout is slightly inside or outside (as requested) the actual
283 region. This also reverses the path when rebuilding into the new
284 region, putting it in the order we actually need to evaluate it in.
289 static void point_adj(float x1
, float y1
, float x2
, float y2
, float *Px
, float *Py
, int left
){
292 float M
= hypot(xd
,yd
);
303 static void line_adj(float *x1
, float *y1
, float *x2
, float *y2
, int left
){
306 float M
= hypot(xd
,yd
);
320 // make sure there's an overlap!
328 static float tangent_distance_from_center(float x1
, float y1
, float x2
, float y2
,
332 return ((x2
-x1
)*(cy
-y1
) - (y2
-y1
)*(cx
-x1
)) / hypot(xd
,yd
);
335 static float radius_adjust(float r
, float arc_phi
, int left
){
352 static void line_line_adj(region_segment
*A
, region_segment
*B
,
353 float *new_x
, float *new_y
, int left
){
367 line_adj(&Ax1
, &Ay1
, &Ax2
, &Ay2
, left
);
368 line_adj(&Bx1
, &By1
, &Bx2
, &By2
, left
);
370 // compute new intersection
371 if(!intersects(Ax1
,Ay1
,Ax2
,Ay2
, Bx1
,By1
,Bx2
,By2
, &newd_x
, &newd_y
)){
372 // odd; do nothing rather than fail unpredictably
381 static void line_arc_adj(float x1
, float y1
, float x2
, float y2
,
382 float cx
, float cy
, float r
, float arc_phi
,
383 float *new_x2
, float *new_y2
, int lleft
, int aleft
){
386 float c
= tangent_distance_from_center(x1
,y1
,x2
,y2
,cx
,cy
);
387 float a
= sqrt(r
*r
- c
*c
),a2
;
388 float M
= hypot(xd
,yd
);
389 float ax
= x2
+ xd
/M
*a
;
390 float ay
= y2
+ yd
/M
*a
;
392 float ax1
= x2
- xd
/M
*a
;
393 float ay1
= y2
- yd
/M
*a
;
394 if(hypot(ax1
-cx
,ay1
-cy
) < hypot(ax
-cx
,ay
-cy
)){
402 point_adj(x1
, y1
, x2
, y2
, &ax
, &ay
, lleft
);
404 r
= radius_adjust(r
,arc_phi
,aleft
);
405 c
= hypot(cx
-ax
,cy
-ay
);
408 *new_x2
= ax
- xd
/a
*a2
;
409 *new_y2
= ay
- yd
/a
*a2
;
412 static void arc_arc_adj(region_segment
*arc
, region_segment
*next
,
413 float *new_x2
, float *new_y2
, int left
){
419 float r1
=arc
->radius
;
423 float r2
=next
->radius
;
428 float d
= hypot(xd
,yd
);
429 float x
= (d
*d
- r1
*r1
+ r2
*r2
) / (2*d
);
431 // is the old x2/y2 to the left or right of the line connecting the
433 float angle_x2y2
= line_angle(cx1
,cy1
,x2
,y2
);
434 float angle_c1c2
= line_angle(cx1
,cy1
,cx2
,cy2
);
435 float angle
= angle_x2y2
- angle_c1c2
;
436 if(angle
< -M_PI
)angle
+= M_PI
*2.f
;
437 if(angle
> M_PI
)angle
-= M_PI
*2.f
;
439 r1
=radius_adjust(r1
,arc
->phi
,left
);
440 r2
=radius_adjust(r2
,arc
->phi
,left
);
443 // still have a valid solution
444 x
= (d
*d
- r1
*r1
+ r2
*r2
) / (2*d
);
445 c
= sqrt(r2
*r2
- x
*x
);
448 // left of c1,c2 segment
449 *new_x2
= cx1
+xd
/d
*x
+ yd
/d
*c
;
450 *new_y2
= cy1
+yd
/d
*x
- xd
/d
*c
;
453 *new_x2
= cx1
+xd
/d
*x
- yd
/d
*c
;
454 *new_y2
= cy1
+yd
/d
*x
+ xd
/d
*c
;
457 // circles shrunk and no longer overlap.
458 fprintf(stderr
,"region overlap adjustment failed in arc_arc_adj; \n"
459 " This is an internal error that should never happen.\n");
463 static float phisub(float phi0
, float phi1
, float arcphi
){
464 float phid
= phi1
-phi0
;
466 if(phid
>0) phid
-= M_PI
*2.f
;
468 if(phid
<0) phid
+= M_PI
*2.f
;
473 static void radius_adj_xy(region_segment
*s
,float *x1
,float *y1
, int left
){
474 float xd
= *x1
- s
->cx
;
475 float yd
= *y1
- s
->cy
;
478 float new_r
= radius_adjust(r
,s
->phi
,left
);
479 float delta
= new_r
/r
;
481 *x1
= s
->cx
+ xd
*delta
;
482 *y1
= s
->cy
+ yd
*delta
;
485 static void adjust_layout(){
486 /* build adjustments from intersection region into layout region */
487 region_segment
*s
= r
.l
;
488 region_segment
*endpath
= 0;
489 region_segment
*endpath_adj
= 0;
492 // first, release previous layout
493 region_segment
*l
=layout_adj
.l
;
494 region_segment
*le
=l
;
496 while(le
&& le
->next
)le
=le
->next
;
501 memset(&layout_adj
,0,sizeof(layout_adj
));
504 float x1
=0,y1
=0,phi
=0,radius
=0;
516 // the flags mark beginning and end of the path, but don't say
518 if(!s
->cont
&& endpath_adj
)
519 if(endpath
->x2
!= s
->x1
||
520 endpath
->y2
!= s
->y1
)
523 /* first handle the lone-circle special case */
524 if(s
->x1
==s
->x2
&& s
->y1
==s
->y2
){
526 if(s
->layout
== 1) radius
= s
->radius
+2;
527 if(s
->layout
== 2) radius
= s
->radius
-2;
528 x1
=x2
=s
->x1
;y1
=y2
=s
->y1
;
532 region_segment
*p
= 0;
544 radius_adj_xy(s
,&x2
,&y2
,s
->layout
==1);
549 arc_arc_adj(p
,s
,&x1
,&y1
,s
->layout
==1);
550 phi0
=line_angle(s
->cx
,s
->cy
,x1
,y1
);
551 phi1
=line_angle(s
->cx
,s
->cy
,x2
,y2
);
552 phi
=phisub(phi0
,phi1
,s
->phi
);
556 line_arc_adj(p
->x1
, p
->y1
, p
->x2
, p
->y2
,
557 s
->cx
, s
->cy
, s
->radius
, s
->phi
,
558 &x1
, &y1
, s
->layout
==1, s
->layout
==1);
559 phi0
=line_angle(s
->cx
,s
->cy
,x1
,y1
);
560 phi1
=line_angle(s
->cx
,s
->cy
,x2
,y2
);
561 phi
=phisub(phi0
,phi1
,s
->phi
);
567 point_adj(s
->x1
, s
->y1
, s
->x2
, s
->y2
, &x2
, &y2
, s
->layout
==1);
571 line_arc_adj(s
->x2
, s
->y2
, s
->x1
, s
->y1
,
572 p
->cx
, p
->cy
, p
->radius
, p
->phi
,
573 &x1
, &y1
, s
->layout
==2, s
->layout
==1);
576 line_line_adj(p
, s
, &x1
, &y1
, s
->layout
==1);
585 // lone arc case; alter radius
586 radius_adj_xy(s
,&x1
,&y1
,s
->layout
==1);
587 radius_adj_xy(s
,&x2
,&y2
,s
->layout
==1);
590 // lone line segment case; offset
591 point_adj(s
->x1
, s
->y1
, s
->x2
, s
->y2
, &x1
, &y1
, s
->layout
==1);
592 point_adj(s
->x1
, s
->y1
, s
->x2
, s
->y2
, &x2
, &y2
, s
->layout
==1);
607 // push the region segment
609 region_segment
*n
=new_segment(&layout_adj
,rint(x1
),rint(y1
),rint(x2
),rint(y2
));
611 n
->cont
=(s
->cont
|| endpath_adj
);
615 // circle; radius variable is treated as a flag
626 if(s
->cont
&& !endpath_adj
)endpath_adj
=n
;
630 if(endpath_adj
&& !s
->cont
){
631 // go back and clean up the endpath path member
632 endpath_adj
->x2
= rint(x1
);
633 endpath_adj
->y2
= rint(y1
);
635 if(endpath
->radius
>0){
636 endpath_adj
->phi1
=line_angle(endpath
->cx
,endpath
->cy
,endpath_adj
->x2
,endpath_adj
->y2
);
637 endpath_adj
->phi
=phisub(endpath_adj
->phi0
,endpath_adj
->phi1
,endpath_adj
->phi
);
657 // release any lines and arcs
658 region_segment
*l
=r
.l
;
659 region_segment
*le
=r
.l
;
660 region_segment
*a
=layout_adj
.l
;
661 region_segment
*ae
=layout_adj
.l
;
663 while(le
&& le
->next
)le
=le
->next
;
664 while(ae
&& ae
->next
)ae
=ae
->next
;
675 memset(&r
,0,sizeof(r
));
676 memset(&layout_adj
,0,sizeof(layout_adj
));
679 int region_layout(graph
*g
){
680 // count up the total length of the region segments used in layout
681 float length
=0,acc
=0,ldel
;
682 int num_adj
=g
->vertex_num
;
685 vertex
*v
= g
->verticies
;
693 float xd
=l
->x2
- l
->x1
;
694 float yd
=l
->y2
- l
->y1
;
695 length
+= l
->length
= hypot(xd
,yd
);
697 float diam
= l
->radius
*2.f
*M_PI
;
698 float del
=phisub(l
->phi0
,l
->phi1
,l
->phi
);
702 length
+= l
->length
= diam
*del
*(1.f
/(M_PI
*2.f
));
707 // non-contiguous beginnings sink a single point segment per
715 /* perform layout segment by segment */
717 ldel
= (float)length
/num_adj
;
720 int num_placed
= l
->cont
? rint((l
->length
-acc
)/ldel
) : rint((l
->length
-acc
)/ldel
)+1;
721 float snap_del
= l
->cont
? l
->length
/num_placed
: l
->length
/(num_placed
-1);
722 float snap_acc
=l
->cont
?snap_del
:0;
724 if(l
->split
)activenum
++;
731 float xd
=(x2
-x1
)/l
->length
;
732 float yd
=(y2
-y1
)/l
->length
;
734 for(i
=0;v
&& i
<num_placed
;i
++){
735 v
->x
= rint(x1
+xd
*snap_acc
);
736 v
->y
= rint(y1
+yd
*snap_acc
);
748 float phid
= phisub(l
->phi0
,l
->phi1
,l
->phi
);
752 for(i
=0;v
&& i
<num_placed
;i
++){
753 v
->x
= rint( cos(l
->phi0
+phid
*snap_acc
)*(l
->radius
)+x
);
754 v
->y
= rint( -sin(l
->phi0
+phid
*snap_acc
)*(l
->radius
)+y
);
770 void region_circle(int x
,int y
, float rad
, int layout
){
771 region_segment
*a
=new_segment(&r
,0,0,0,0);
779 a
->cont
=0; // not really necessary, just consistent
783 void region_new_area(int x
, int y
, int layout
){
790 void region_line_to(int x
,int y
){
791 region_line(&r
,r
.x
,r
.y
,x
,y
);
797 void region_arc_to(int x
,int y
, float rad
){
798 region_arc(&r
,r
.x
,r
.y
,x
,y
,rad
);
804 void region_close_line(){
805 region_line(&r
,r
.x
,r
.y
,r
.ox
,r
.oy
);
811 void region_close_arc(float rad
){
812 region_arc(&r
,r
.x
,r
.y
,r
.ox
,r
.oy
,rad
);
818 void region_split_here(){
822 int region_intersects(edge
*e
){
824 region_segment
*s
=r
.l
;
828 if(intersects_arc(e
,s
))return 1;
830 double xdummy
,ydummy
;
832 if(intersects(e
->A
->x
,e
->A
->y
,e
->B
->x
,e
->B
->y
,
833 s
->x1
,s
->y1
,s
->x2
,s
->y2
,
834 &xdummy
,&ydummy
))return 1;