All’s well that ends well

Development of Speedy Sketch is now complete and the application available for download for windows. In order to read about the development further please download the full report of the project.

=)


Warning: Creating default object from empty value in /home/mishamo/ssblog.mishagriffiths.com/wp-includes/comment-template.php on line 1056

Apples and Pears? Circles and Squares…

So, now that we can draw pretty much any building plan, lets add some tools that will help the user do so quicker or draw something more irregular (like an ellipse).

Lets start with the way in which this will work. I decided against several buttons for different shapes on the toolbar, opting instead for a single button which then brings up a second window allowing the user to choose from some shapes they can then draw.

This may be a very good time to discuss the way in which the tools are actually implemented which may be obvious to some but not so to others. Each button on the menu has an action listener and when the button is pressed, it becomes disabled, enables all other buttons and changes the “tool” variable within the renderer window:

private void jButton2ActionPerformed(ActionEvent evt) {

renderer.tool=GLRenderer.FREEHAND;
jButton4.setEnabled(true);
jButton3.setEnabled(true);
jButton5.setEnabled(true);
jButton6.setEnabled(true);
jButton2.setEnabled(false);
}

That is an example of the “freehand” tool button. In this manner the renderer always knows which tool is currently selected and the user is also informed which tool they are using both with a label and by checking which tool is not selected in the toolbar.

So… The way I have implemented the circle and square tool selection is as follows:
The user clicks the “Square” button (which may be a circle if the user has already used it). This brings up a new menu; a small window with 2 buttons, a rectangle and a circle. If the user selects the circle, the circle tool is now active and the button image on the main menu changes from a rectangle to a circle. This menu button is NOT disabled as the user may wish to use the rectangle tool after using a circle tool and would not be able to do this if the button was disabled. The selection window then closes and the circle tool is currently in use:

renderer.tool=GLRenderer.CIRCLE;
sketch.jButton3.setIcon(new ImageIcon(getClass().getResource(“/org/ss/circle.png”)));
sketch.setLabel(“Circle”);

this.setVisible(false);

But what does this circle tool do?
Well… It’s probably more appropriate to describe the rectangle tool first:

As with all mouse driven tools, there are 3 primary cases that can occur:

  • The mouse can be pressed down
  • The mouse can be released
  • The mouse can be moved (in this case this only matters if it is moved whilst pressed down)

So I apologise for the code dump here for all three functions but I will describe their functionality straight after the code:

private void rectangleDragged(MouseEvent e) {
cmd = DRAWING;
mouse_x = e.getX();
mouse_y = e.getY();
}

private void rectanglePressed(MouseEvent e) {
x1 = e.getX();
y1 = e.getY();
cmd = DRAWING;
}

private void rectangleRelease(MouseEvent e) {
x2 = e.getX();
y2 = e.getY();
if (shiftPressed) {
if ((y2 – y1) >= (x2 – x1)) {
y2 = y1 + (x2 – x1);
} else {
x2 = x1 + (y2 – y1);
}
}
lines_x.add(x1);
lines_y.add(y1);
lines_x.add(x1);
lines_y.add(y2);
lines_x.add(x2);
lines_y.add(y2);
lines_x.add(x2);
lines_y.add(y1);
lines_x.add(x1);
lines_y.add(y1);
for (int i = 0; i < lines_x.size(); i++) {
addToSquares(lines_x.get(i), lines_y.get(i));
}
Shape curShape = new Shape(linesquares_x, linesquares_y, Shape.RECTANGLE, floorNo);
System.out.println(“” + linesquares_x + “\n” + linesquares_y);
shapes.add(curShape); }

After the rectangle shape is selected, the first thing the user would do is find a place to start drawing the rectangle and press down on the mouse button, invoking the “rectanglePressed” method. This method simply records the first corner of the rectangle and sets the functionality of the program to “DRAWING” which just means that a shape will be changing dynamically (as the user drags). This leads us on to “rectangleDragged”:

As the user drags the rectangle out into the shape they desire, this method records the opposite corner of the rectangle as the current location of the mouse cursor and the display() loop simply keeps updating it and draws a quadrilateral based on the 4 corners defined by:

  • x1,y1
  • x1,y2
  • x2,y2
  • x2,y1

So we get a dynamically updated quadrilateral being drawn for the user’s reference.

Finally we have the most important “rectangleRelease” method:

As you may have guessed the first thing this method does is records the released coordinates as the opposite corner of the quadrilateral to the coordinates recorded when the mouse was pressed down.

The method then normalises the rectangle to a square based on the smallest difference of the two corners in the x or y direction if the shift key is held down. Now we have to integrate these 2 corners (and therefore the 4 corners derived from those 2 as shown above) into our array of shapes and vertices as if it was drawn freehand. In order to do this we simply add them to an array of vertices as if we had drawn the shape freehand and we make sure to do this in the right order so it draws a rectangle as opposed to a sharp hourglass shape. As long as we draw the first coordinate first, then one other one, then the final coordinate then the only one remaining the shape should draw correctly. So we add them in this order and then we, of course, have to add the first coordinate again to complete the shape.

We push this shape through our “addToSquares” method as before in order to snap it to the grid and convert it to our coordinate system based on the grid. And, finally, we add the completed shape to our array of shapes. Simple enough, but can we apply this to drawing an ellipse?

Well… the answer is yes… and no, we apply some of this to drawing an ellipse but round shapes are somewhat awkward to implement as circular primitives don’t exist on openGL so we must use some mathematical tricks to draw them. There are again three parts as before:

private void circleDragged(MouseEvent e) {
cmd = DRAWING;
mouse_x = e.getX();
mouse_y = e.getY();
}

private void circlePressed(MouseEvent e) {
x1 = e.getX();
y1 = e.getY();
cmd = DRAWING;
}

private void circleRelease(MouseEvent e) {
x2 = e.getX();
y2 = e.getY();
if (shiftPressed) {
if ((y2 – y1) >= (x2 – x1)) {
y2 = y1 + (x2 – x1);
} else {
x2 = x1 + (y2 – y1);
}
}
linesquares_x.add(x1);
linesquares_y.add(y1);
linesquares_x.add(x2);
linesquares_y.add(y2);
Shape curShape = new Shape(linesquares_x, linesquares_y, Shape.CIRCLE, floorNo);
shapes.add(curShape);
}

As you can see the actual interaction code is not much different from that of the rectangle. What is actually drastically different is the actual code used to draw the shape. In the display() loop the program checks the shape type of every shape it is about to draw and if it is a circle:

int k1 = currentx.get(0);
int k2 = currenty.get(0);
int k3 = currentx.get(1);
int k4 = currenty.get(1);

if(loopShape.getFloorNo()==floorNo)
gl.glColor3f(0.0f,0.0f,0.0f);
else if(loopShape.getFloorNo()==floorNo-1){
gl.glColor3f(0.0f, 0.0f, 0.8f);
}
else
gl.glColor3f(0.8f, 0.0f, 0.0f);
gl.glLineWidth(2.0f);
gl.glBegin(GL.GL_LINE_LOOP);
for (int i=0; i < 360; i++)
{
double yDiff=(k4-k2)/2;
double xDiff=(k3-k1)/2;
double yMid=(k2+k4)/2;
double xMid=(k1+k3)/2;
double degInRad=i*DEG2RAD; gl.glVertex2d((Math.cos(degInRad)*xDiff)+xMid,(Math.sin(degInRad)*yDiff)+yMid); }

gl.glEnd();

I’d prefer not to go into too much detail discussing how this actually draws the circle other than saying it goes round all 360 degrees in 360 steps and draws a loop around a vertex at each degree change. Again colours are just there to show differences in floors when we traverse them later on.

So there we have it. Freehand drawing, rectangles, squares, circles, ellipses… We can start drawing some fairly complex plans.