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"
36 /* Regions are 'electric fences' for mesh building; used in mesh2 to
37 make non-convex shapes */
39 typedef struct region_segment
{
40 int layout
; /* 0 no layout, 1 left, 2 right, 3 layout-only */
41 int cont
; /* is this continuous from last line? */
42 int split
; /* are we splitting the graph into interntionally
43 seperate regions here? */
50 // arc computation cache (if arc)
59 struct region_segment
*next
;
62 typedef struct region
{
74 static region layout_adj
;
75 static region_segment
*segpool
=0;
79 static region_segment
*new_segment(region
*r
, int x1
,int y1
,int x2
, int y2
){
84 segpool
= calloc(CHUNK
,sizeof(*segpool
));
85 for(i
=0;i
<CHUNK
-1;i
++) /* last addition's next points to nothing */
86 segpool
[i
].next
=segpool
+i
+1;
92 memset(ret
,0,sizeof(*ret
));
94 ret
->layout
=r
->layout
;
100 ret
->split
= r
->split_next
;
108 /* angle convention: reversed y (1,-1 is first quadrant, ala X
109 windows), range -PI to PI */
111 static int intersects_arc(edge
*e
, region_segment
*r
){
112 float Ax
= e
->A
->x
- r
->cx
;
113 float Ay
= e
->A
->y
- r
->cy
;
114 float Bx
= e
->B
->x
- r
->cx
;
115 float By
= e
->B
->y
- r
->cy
;
119 float dr2
= dx
*dx
+ dy
*dy
;
120 float D
= Ax
*By
- Bx
*Ay
;
121 float discriminant
=(r
->radius
*r
->radius
)*dr2
- D
*D
;
123 // does it even intersect the full circle?
124 if(discriminant
<=0)return 0;
129 float sqrtd
= sqrt(discriminant
);
130 float sign
= (dy
>0?1.f
:-1.f
);
132 // finite precision required here else 0/inf slope lines will be
133 // slighly off the secant
134 x1
= rint((D
*dy
+ sign
*dx
*sqrtd
) / dr2
);
135 x2
= rint((D
*dy
- sign
*dx
*sqrtd
) / dr2
);
137 y1
= rint((-D
*dx
+ fabs(dy
)*sqrtd
) / dr2
);
138 y2
= rint((-D
*dx
- fabs(dy
)*sqrtd
) / dr2
);
145 // is x1,y1 actually on the segment?
146 if( !(x1
<Ax
&& x1
<Bx
) &&
150 // yes. it is in the angle range we care about?
152 float ang
= acos(x1
/ r
->radius
);
156 if(r
->phi0
< r
->phi1
){
157 if(ang
<= r
->phi0
|| ang
>= r
->phi1
)return 1;
159 if(ang
<= r
->phi0
&& ang
>= r
->phi1
)return 1;
162 if(r
->phi0
< r
->phi1
){
163 if(ang
>= r
->phi0
&& ang
<= r
->phi1
)return 1;
165 if(ang
>= r
->phi0
|| ang
<= r
->phi1
)return 1;
170 // is x2,y2 actually on the segment?
171 // if so, it is in the arc range we care about?
172 if( !(x2
<Ax
&& x2
<Bx
) &&
176 // yes. it is in the angle range we care about?
178 float ang
= acos(x2
/ r
->radius
);
182 if(r
->phi0
< r
->phi1
){
183 if(ang
<= r
->phi0
|| ang
>= r
->phi1
)return 1;
185 if(ang
<= r
->phi0
&& ang
>= r
->phi1
)return 1;
188 if(r
->phi0
< r
->phi1
){
189 if(ang
>= r
->phi0
&& ang
<= r
->phi1
)return 1;
191 if(ang
>= r
->phi0
|| ang
<= r
->phi1
)return 1;
199 static float line_angle(float x1
, float y1
, float x2
, float y2
){
210 return atan(-yd
/xd
)+M_PI
;
212 return atan(-yd
/xd
)-M_PI
;
218 static float line_mag(float x1
, float y1
, float x2
, float y2
){
224 static void compute_arc(region_segment
*r
,float phi
){
244 cr
= line_angle(x1
,y1
,x2
,y2
);
245 a
= line_mag(x1
,y1
,x2
,y2
)/2.f
;
246 br
=(M_PI
/2.f
)-(ar
/2.f
);
250 if(phi
<-M_PI
|| (phi
>0 && phi
<M_PI
)){
251 cx
= x1
+ cos(cr
+M_PI
/2)*c
+ xd
/2;
252 cy
= y1
- sin(cr
+M_PI
/2)*c
+ yd
/2;
254 cx
= x1
+ cos(cr
-M_PI
/2)*c
+ xd
/2;
255 cy
= y1
- sin(cr
-M_PI
/2)*c
+ yd
/2;
262 // have the center of the circle, have radius. Determine the
263 // portion of the arc we want.
264 r
->phi0
= acos( (x1
-cx
) / d
);
265 r
->phi1
= acos( (x2
-cx
) / d
);
266 if(y1
>cy
) r
->phi0
= -r
->phi0
;
267 if(y2
>cy
) r
->phi1
= -r
->phi1
;
271 static region_segment
*region_arc(region
*re
, int x1
, int y1
, int x2
, int y2
, float rad
){
272 region_segment
*n
= new_segment(re
,x1
,y1
,x2
,y2
);
277 static region_segment
*region_line(region
*re
,int x1
, int y1
, int x2
, int y2
){
278 return new_segment(re
,x1
,y1
,x2
,y2
);
281 /* The overall idea here is to massage the region segments and arcs
282 slightly so that when we layout points based on a region, the
283 layout is slightly inside or outside (as requested) the actual
284 region. This also reverses the path when rebuilding into the new
285 region, putting it in the order we actually need to evaluate it in.
290 static void point_adj(float x1
, float y1
, float x2
, float y2
, float *Px
, float *Py
, int left
){
293 float M
= hypot(xd
,yd
);
304 static void line_adj(float *x1
, float *y1
, float *x2
, float *y2
, int left
){
307 float M
= hypot(xd
,yd
);
321 // make sure there's an overlap!
329 static float tangent_distance_from_center(float x1
, float y1
, float x2
, float y2
,
333 return ((x2
-x1
)*(cy
-y1
) - (y2
-y1
)*(cx
-x1
)) / hypot(xd
,yd
);
336 static float radius_adjust(float r
, float arc_phi
, int left
){
353 static void line_line_adj(region_segment
*A
, region_segment
*B
,
354 float *new_x
, float *new_y
, int left
){
368 line_adj(&Ax1
, &Ay1
, &Ax2
, &Ay2
, left
);
369 line_adj(&Bx1
, &By1
, &Bx2
, &By2
, left
);
371 // compute new intersection
372 if(!intersects(Ax1
,Ay1
,Ax2
,Ay2
, Bx1
,By1
,Bx2
,By2
, &newd_x
, &newd_y
)){
373 // odd; do nothing rather than fail unpredictably
382 static void line_arc_adj(float x1
, float y1
, float x2
, float y2
,
383 float cx
, float cy
, float r
, float arc_phi
,
384 float *new_x2
, float *new_y2
, int lleft
, int aleft
){
387 float c
= tangent_distance_from_center(x1
,y1
,x2
,y2
,cx
,cy
);
388 float a
= sqrt(r
*r
- c
*c
),a2
;
389 float M
= hypot(xd
,yd
);
390 float ax
= x2
+ xd
/M
*a
;
391 float ay
= y2
+ yd
/M
*a
;
393 float ax1
= x2
- xd
/M
*a
;
394 float ay1
= y2
- yd
/M
*a
;
395 if(hypot(ax1
-cx
,ay1
-cy
) < hypot(ax
-cx
,ay
-cy
)){
403 point_adj(x1
, y1
, x2
, y2
, &ax
, &ay
, lleft
);
405 r
= radius_adjust(r
,arc_phi
,aleft
);
406 c
= hypot(cx
-ax
,cy
-ay
);
409 *new_x2
= ax
- xd
/a
*a2
;
410 *new_y2
= ay
- yd
/a
*a2
;
413 static void arc_arc_adj(region_segment
*arc
, region_segment
*next
,
414 float *new_x2
, float *new_y2
, int left
){
420 float r1
=arc
->radius
;
424 float r2
=next
->radius
;
429 float d
= hypot(xd
,yd
);
430 float x
= (d
*d
- r1
*r1
+ r2
*r2
) / (2*d
);
432 // is the old x2/y2 to the left or right of the line connecting the
434 float angle_x2y2
= line_angle(cx1
,cy1
,x2
,y2
);
435 float angle_c1c2
= line_angle(cx1
,cy1
,cx2
,cy2
);
436 float angle
= angle_x2y2
- angle_c1c2
;
437 if(angle
< -M_PI
)angle
+= M_PI
*2.f
;
438 if(angle
> M_PI
)angle
-= M_PI
*2.f
;
440 r1
=radius_adjust(r1
,arc
->phi
,left
);
441 r2
=radius_adjust(r2
,arc
->phi
,left
);
444 // still have a valid solution
445 x
= (d
*d
- r1
*r1
+ r2
*r2
) / (2*d
);
446 c
= sqrt(r2
*r2
- x
*x
);
449 // left of c1,c2 segment
450 *new_x2
= cx1
+xd
/d
*x
+ yd
/d
*c
;
451 *new_y2
= cy1
+yd
/d
*x
- xd
/d
*c
;
454 *new_x2
= cx1
+xd
/d
*x
- yd
/d
*c
;
455 *new_y2
= cy1
+yd
/d
*x
+ xd
/d
*c
;
458 // circles shrunk and no longer overlap.
459 fprintf(stderr
,_("region overlap adjustment failed in arc_arc_adj; \n"
460 " This is an internal error that should never happen.\n"));
464 static float phisub(float phi0
, float phi1
, float arcphi
){
465 float phid
= phi1
-phi0
;
467 if(phid
>0) phid
-= M_PI
*2.f
;
469 if(phid
<0) phid
+= M_PI
*2.f
;
474 static void radius_adj_xy(region_segment
*s
,float *x1
,float *y1
, int left
){
475 float xd
= *x1
- s
->cx
;
476 float yd
= *y1
- s
->cy
;
479 float new_r
= radius_adjust(r
,s
->phi
,left
);
480 float delta
= new_r
/r
;
482 *x1
= s
->cx
+ xd
*delta
;
483 *y1
= s
->cy
+ yd
*delta
;
486 static void adjust_layout(){
487 /* build adjustments from intersection region into layout region */
488 region_segment
*s
= r
.l
;
489 region_segment
*endpath
= 0;
490 region_segment
*endpath_adj
= 0;
493 // first, release previous layout
494 region_segment
*l
=layout_adj
.l
;
495 region_segment
*le
=l
;
497 while(le
&& le
->next
)le
=le
->next
;
502 memset(&layout_adj
,0,sizeof(layout_adj
));
505 float x1
=0,y1
=0,phi
=0,radius
=0;
517 // the flags mark beginning and end of the path, but don't say
519 if(!s
->cont
&& endpath_adj
)
520 if(endpath
->x2
!= s
->x1
||
521 endpath
->y2
!= s
->y1
)
524 /* first handle the lone-circle special case */
525 if(s
->x1
==s
->x2
&& s
->y1
==s
->y2
){
527 if(s
->layout
== 1) radius
= s
->radius
+2;
528 if(s
->layout
== 2) radius
= s
->radius
-2;
529 x1
=x2
=s
->x1
;y1
=y2
=s
->y1
;
533 region_segment
*p
= 0;
545 radius_adj_xy(s
,&x2
,&y2
,s
->layout
==1);
550 arc_arc_adj(p
,s
,&x1
,&y1
,s
->layout
==1);
551 phi0
=line_angle(s
->cx
,s
->cy
,x1
,y1
);
552 phi1
=line_angle(s
->cx
,s
->cy
,x2
,y2
);
553 phi
=phisub(phi0
,phi1
,s
->phi
);
557 line_arc_adj(p
->x1
, p
->y1
, p
->x2
, p
->y2
,
558 s
->cx
, s
->cy
, s
->radius
, s
->phi
,
559 &x1
, &y1
, s
->layout
==1, s
->layout
==1);
560 phi0
=line_angle(s
->cx
,s
->cy
,x1
,y1
);
561 phi1
=line_angle(s
->cx
,s
->cy
,x2
,y2
);
562 phi
=phisub(phi0
,phi1
,s
->phi
);
568 point_adj(s
->x1
, s
->y1
, s
->x2
, s
->y2
, &x2
, &y2
, s
->layout
==1);
572 line_arc_adj(s
->x2
, s
->y2
, s
->x1
, s
->y1
,
573 p
->cx
, p
->cy
, p
->radius
, p
->phi
,
574 &x1
, &y1
, s
->layout
==2, s
->layout
==1);
577 line_line_adj(p
, s
, &x1
, &y1
, s
->layout
==1);
586 // lone arc case; alter radius
587 radius_adj_xy(s
,&x1
,&y1
,s
->layout
==1);
588 radius_adj_xy(s
,&x2
,&y2
,s
->layout
==1);
591 // lone line segment case; offset
592 point_adj(s
->x1
, s
->y1
, s
->x2
, s
->y2
, &x1
, &y1
, s
->layout
==1);
593 point_adj(s
->x1
, s
->y1
, s
->x2
, s
->y2
, &x2
, &y2
, s
->layout
==1);
608 // push the region segment
610 region_segment
*n
=new_segment(&layout_adj
,rint(x1
),rint(y1
),rint(x2
),rint(y2
));
612 n
->cont
=(s
->cont
|| endpath_adj
);
616 // circle; radius variable is treated as a flag
627 if(s
->cont
&& !endpath_adj
)endpath_adj
=n
;
631 if(endpath_adj
&& !s
->cont
){
632 // go back and clean up the endpath path member
633 endpath_adj
->x2
= rint(x1
);
634 endpath_adj
->y2
= rint(y1
);
636 if(endpath
->radius
>0){
637 endpath_adj
->phi1
=line_angle(endpath
->cx
,endpath
->cy
,endpath_adj
->x2
,endpath_adj
->y2
);
638 endpath_adj
->phi
=phisub(endpath_adj
->phi0
,endpath_adj
->phi1
,endpath_adj
->phi
);
658 // release any lines and arcs
659 region_segment
*l
=r
.l
;
660 region_segment
*le
=r
.l
;
661 region_segment
*a
=layout_adj
.l
;
662 region_segment
*ae
=layout_adj
.l
;
664 while(le
&& le
->next
)le
=le
->next
;
665 while(ae
&& ae
->next
)ae
=ae
->next
;
676 memset(&r
,0,sizeof(r
));
677 memset(&layout_adj
,0,sizeof(layout_adj
));
680 int region_layout(graph
*g
){
681 // count up the total length of the region segments used in layout
682 float length
=0,acc
=0,ldel
;
683 int num_adj
=g
->vertex_num
;
686 vertex
*v
= g
->verticies
;
694 float xd
=l
->x2
- l
->x1
;
695 float yd
=l
->y2
- l
->y1
;
696 length
+= l
->length
= hypot(xd
,yd
);
698 float diam
= l
->radius
*2.f
*M_PI
;
699 float del
=phisub(l
->phi0
,l
->phi1
,l
->phi
);
703 length
+= l
->length
= diam
*del
*(1.f
/(M_PI
*2.f
));
708 // non-contiguous beginnings sink a single point segment per
716 /* perform layout segment by segment */
718 ldel
= (float)length
/num_adj
;
721 int num_placed
= l
->cont
? rint((l
->length
-acc
)/ldel
) : rint((l
->length
-acc
)/ldel
)+1;
722 float snap_del
= l
->cont
? l
->length
/num_placed
: l
->length
/(num_placed
-1);
723 float snap_acc
=l
->cont
?snap_del
:0;
725 if(l
->split
)activenum
++;
732 float xd
=(x2
-x1
)/l
->length
;
733 float yd
=(y2
-y1
)/l
->length
;
735 for(i
=0;v
&& i
<num_placed
;i
++){
736 v
->x
= rint(x1
+xd
*snap_acc
);
737 v
->y
= rint(y1
+yd
*snap_acc
);
749 float phid
= phisub(l
->phi0
,l
->phi1
,l
->phi
);
753 for(i
=0;v
&& i
<num_placed
;i
++){
754 v
->x
= rint( cos(l
->phi0
+phid
*snap_acc
)*(l
->radius
)+x
);
755 v
->y
= rint( -sin(l
->phi0
+phid
*snap_acc
)*(l
->radius
)+y
);
771 void region_circle(int x
,int y
, float rad
, int layout
){
772 region_segment
*a
=new_segment(&r
,0,0,0,0);
780 a
->cont
=0; // not really necessary, just consistent
784 void region_new_area(int x
, int y
, int layout
){
791 void region_line_to(int x
,int y
){
792 region_line(&r
,r
.x
,r
.y
,x
,y
);
798 void region_arc_to(int x
,int y
, float rad
){
799 region_arc(&r
,r
.x
,r
.y
,x
,y
,rad
);
805 void region_close_line(){
806 region_line(&r
,r
.x
,r
.y
,r
.ox
,r
.oy
);
812 void region_close_arc(float rad
){
813 region_arc(&r
,r
.x
,r
.y
,r
.ox
,r
.oy
,rad
);
819 void region_split_here(){
823 int region_intersects(edge
*e
){
825 region_segment
*s
=r
.l
;
829 if(intersects_arc(e
,s
))return 1;
831 double xdummy
,ydummy
;
833 if(intersects(e
->A
->x
,e
->A
->y
,e
->B
->x
,e
->B
->y
,
834 s
->x1
,s
->y1
,s
->x2
,s
->y2
,
835 &xdummy
,&ydummy
))return 1;