Framework program description


download framework program
download Delphi-7 project

Framework is a program that tests frames for rigidity.
The frames are made of columns and rows of rectangular boxes.
This project is a nice application of graph theory

Introduction

When force is applied to a rectangular box it may be distorted and become a parallellogram.
However, if a brace is attached between opposite joints, the rectangle is now transformed
into two triangles and distortion is not possible.
See picture below:



Using above boxes we may build frames of several rows and columns.
Then the question arises which boxes should be braced to make a rigid structure.
This framework project was built to answer this question.
If a frame is not rigid, the distortion is showed.
Also superfluous braces may be marked and removed to obtain a minimally braced rigid frame.
Below is a picture of the framework program at work.



Coding the problem

Columns and rows count 0..7 (maximum), left to right, top to bottom.
Boxes are coded 0..3
    box type 0 : vertical and horizontal edges. (binary code 00
    box type 1 : rotated vertical edges. (binary code 01)
    box type 2 : rotated horizontal edges. (binary code 10)
    box type 3 : both edges rotated. (binary code 11)
Below is a picture showing columns,rows, box types and braces.



Per box the brace must be recorded.
bbRemoved indicates a removed brace, which was superfluous.

The frame is defined by it's coordinates (x,y).
All drawing is done in a bitmap (map).
The map is displayed in a paintbox when drawing is finished.
This prevents drawing actions to cause flickering of the screen.



So, to display the frame distortion, simply the (x,y) coordinated have to be changed.
Here are the types, constants and variables needed for drawing:
type TBoxBrace = (bbNone,bbBrace,bbRemoved);

const leftmargin = 40;
      topmargin = 40;

var framewidth : byte; //1..8 number of boxes
    frameheight : byte;//1..8
    boxbrace : array[0..7,0..7] of TBoxBrace;
    coords : array[0..8,0..8] of TPoint; //coordinates of joints
    map : TBitmap;     //holds frame
The left-top (x,y) angle of a box is the reference.
The other coordinates of the box angles depend on the box type and are relative to the left-top.



The dx-,dy- values are stored in a constants array[0..3] per box type 0..3:

const offset  : array[0..3] of TOffset =
  ((dx1:  0; dy1: 60; dx2: 100; dy2:  60; dx3: 100; dy3:  0), //0
   (dx1:-25; dy1: 54; dx2:  75; dy2:  54; dx3: 100; dy3:  0), //1
   (dx1:  0; dy1: 60; dx2:  91; dy2: 102; dx3:  91; dy3: 42), //2
   (dx1:-25; dy1: 54; dx2:  66; dy2:  96; dx3:  91; dy3: 42));//3 
Above dx,dy values are calculated using basic goniometric functions.

Procedure testframe tests the frame and modifies the coordinates.
Procedure paintFrame paints the frame in the bitmap and copies the map to a paintbox.

Paintframe

Boxes are painted left to right, top to bottom.
The box position is coded in variable code:
    0 : left top paint all edges
    1 : top row paint top, right and bottom edges
    2 : left columnpaint left , bottom and right edges
    3 : otherpaint bottom and right edges
Reason is to save time by avoiding drawing of the same lines.
Part of typical source code:
      case code of
       0 : begin                                     //left top
            moveto(coords[i,j].x, coords[i,j].y);
            lineto(coords[i+1,j].x, coords[i+1,j].y);
            lineto(coords[i+1,j+1].x, coords[i+1,j+1].y);
            lineto(coords[i,j+1].x, coords[i,j+1].Y);
            lineto(coords[i,j].x, coords[i,j].y);
           end;
      …......................
       2 : begin                                    //left
            moveto(coords[i+1,j].x, coords[i+1,j].y);
            lineto(coords[i+1,j+1].x, coords[i+1,j+1].y);
            lineto(coords[i,j+1].x, coords[i,j+1].Y);
            lineto(coords[i,j].x, coords[i,j].y);
           end;
       ….......................
      end;//case 
For details please refer to procedure paintframe source code.
So far for the painting. Now the harder part: testing the frame for rigidity.

Theory of rigid box testing

A box is called rigid if it cannot be distorted. It keeps straight angles.
So these boxes are of type 0 or 3.
Of course a braced box is rigid.
But look at the next picture where the left-top box is not braced but sure is rigid:



To examine box (0,0) for rigidity :
    look for a brace in row 0 and find one in column 2.
    now look for a brace in this column and find one in row 2.
    next look for a brace in row 2 and find one in column 0.
This is the column where we started, which shows that box(0,0) is rigid.
If such a path from box(0,0) on row 0 to column 0 is not found
then box(0,0) is not rigid and will be a parallellogram, type 1 in this case.
Why is this true?
Looking back to previous pictures we notice that
    in a row all left-right edges of the boxes are parallel.
    In a column all top-bottom edges of the boxes are parallel
Box(0,0) and box(2,0) vertical edges are parallel.
Box(2,0) and box(2,2) horizontal edges are parallel.
Because both boxes are braced also box(2,0) and box(2,2) vertical edges are parallel.
Same is true for box(2,2) and box(0,2).
So, boxes(2,2) and box(0,2) have parallel edges.
So, column 0 is perpendicular to row 0 and box(0,0) is a rectangle.

When is the complete frame rigid?
From the previous theory we conclude that it is sufficient to proof
that all boxes in row-0 and all boxes in column-0 are rigid.
The type of these top and left boxes fully determines the type of all other boxes.

Practice of rigid box testing

Function rigid(xcol,xrow : byte) : boolean; returns true if box(xcol,xrow) is rigid.
This function is the core of the project.
These are the local variables:
var stepNr,n,v : byte;
    box        : array[1..16] of Tbox;  
    colEnables : byte;
    rowEnables : byte;
    GO         : boolean;    //false to exit repeat loop
stepNrcounts 1,2,3... as we step from row to column.
StepNr 1 finds a brace in it's row, stepNr 2 a brace in it's column ...etc.
box[1..16]search path of (x,y) coordinates
ColEnables
rowEnables
start at value $ff [11111111] binary, enabling all rows/columns.
If a column or row is part of the path, the corresponding bit is set to zero to
avoid visiting this row/column again. This avoids loops in the path.
GOa boolean flag for repeat loop exit control.


Other variables defined outside this function are playing a role:
var columnvector : array[0..7] of byte;  
    rowvector : array[0..7] of byte;  
columnvector,
rowvector
bit coded brace positions in rows 0..7, columns 0..7
made by makeconnectionvectors from boxbrace[ ] array
bit is set when brace is present


Function rigid has this little helper function:
function nextRect(var n : byte; vec : byte) : boolean;
//true if brace(bit) in vector vec; n : lowest bit set in vec
begin
 result := vec <> 0;
 if result  then
  begin
   n := 0;
   while ((1 shl n) and vec) = 0 do inc(n);
  end;
end;
byte vec has bits set for a brace in the column/row if this column/row
has not been visited before. Below is the flowchart of function rigid:



Frame testing

Testing the complete frame for rigidity is accomplished by procedure testframe.
It is sufficient to test only the top row boxes by calling function rigid.
This procedure also changes the frame coordinates according to the box type found.

Variables:
var i,j : byte;  //column, row
    bxt : byte;  //box type etc.
    topbox : array[0..7] of byte; //top row box types
    leftbox : array[0..7] of byte;//left column box types
    xref,yref : smallInt;         //left-top box coordinates
    corr : smallInt;              //correction value, shift frame
    RFlag,CFlag : boolean;
The first test is for the left-top box. If rigid, the box type becomes 0, else the type is 1.
Then the next box at (1,0) is tested. This box type depends on 1. rigidity and 2. the previous box(0,0).
For a rigid box, the next boxtype is precoded in constant topcodesR[0..3]
for a non-rigid box this is topcodesNR[0..3]
Example:
if box(0,0) is non-rigid (so type 1) and box (1,0) is rigid then box (1,0) is of type
topcodesR[1] which is type 3.
If box(0,0) is rigid and box (1,0) is non-rigid then box (1,0) is of type topcodesNR[0] = 2.
The previous boxtype is the index into these constant arrays.

Next picture lists the top box choices:



So far the top boxes.
At this point the bottom and top edge directions of all boxes are fixed.
This means that the left- and right edges in a row are also fixed if this row contains a braced box.
Testing continues with the left column box (0,1), just below the top-left box.
If this row has a braced box, boolean variable Cflag sets true.
Source code (without code for coordinates change):
   CFlag := nextRect(bxt,rowvector[j]); //braced box in row?
   if CFlag then
    begin
     bxt := topbox[bxt];
     bxt := ((bxt and $2) shr 1) or (leftbox[0] and $2);
    end
    else bxt := 1;
The vertical edge is derived from the topbox horizontal direction and the horizontal edge
is the left-top horizontal edge direction.
Left column successor box types:



For boxes not at the top row or the left column, this code does the job:
 for j := 1 to frameheight-1 do    //not top or left
  for i := 1 to framewidth-1 do
   begin
    xref := coords[i,j].x;
    yref := coords[i,j].y;
    bxt := (leftbox[j] and $1) or (topbox[i] and $2);
    coords[i+1,j+1].x := xref + offset[bxt].dx2;
    coords[i+1,j+1].y := yref + offset[bxt].dy2;
   end;
The horizontal bit comes from the top box, the vertical bit from the left box.
Afterward the frame is shifted horizontally by value corr to maintain a left margin.

Saving and loading

Frames may be saved / loaded to/from disc.
The data format allows for future expansion to frames of 32 * 32 columns/rows maximum.
Data I/O is typed. Files have no extension.
The data pack consists of 37 dwords (cardinals)



The pack starts with text “davdataframework” for identification.
Braced fields have a bit set for a brace in the row.

Please refer to the source code for more details.

Postscript

This problem was found in the book “Graphs, an introductory Approach” by Wilson and Watkins.
A graph in mathematics is a drawing of points which may, or may not, be connected by lines.
Such a drawing is abstract and may represent topics like:
    road maps
    networks
    chemical molecules
    interdependent processes
There are many varieties of graphs, one being a so called bipartite graph
where points belong to one of two groups and lines connect a point in one group to a point in the other group.
Also graphs may have several properties one being connected.
This means that travel from a point to any other point is possible over the connecting lines.

The frame problem may be represented by a bipartite graph by assigning a point
in group 1 for each column and a point in group 2 for each row.
A connecting line shows that the column / row is braced.
The frame is rigid if the graph is connected.
See examples below:



The top row of points represent the columns (0,1,2,3,4).
The bottom points are the rows (0,1,2,3) of the frame.