# Polygon manipulation

#1

Hi,

We’d like to do a couple of things with possibly the JGoPolygon, or
something similar:

1. Given a triangle, can I change from just manipulating the end points
to, say pressing the shift key and moving the mouse, to resize with
the fixed shape (and vice-versa)? Currently, I can create a node
with JGoPolygon to draw a triangle and manipulate the end points.
We like this feature, but also like to be able to simply make the
shape bigger or smaller without altering the triangle’s current shape.

2. How can I do rotations? Given a JGoBasicNode subclass where we
supply our own polygon shape, we’d like to be able to do rotations
or reflections.

3. How do I make a pie (sector) shape?

Thanks!

– Vince

#2

When I get a chance I’ll code this up for you.
Basically (1) requires overriding gainedSelection and handleResize, (2) is just a matter of rotating or reflecting the points, and (3) is involves defining a GeneralPath in that shape.
But note that because the coordinates used by JGo are integers, rotation will involve loss of precision, unless the angles are always multiples of 90 degrees.

#3

Here’s (1), but I haven’t done (2) yet–some day…
package com.nwoods.jgo.examples.demo1;
import java.awt.;
import com.nwoods.jgo.
;
public class RotatablePolygon extends JGoPolygon {
public RotatablePolygon() {}
protected void gainedSelection(JGoSelection selection) {
if (!isResizable()) {
selection.createBoundingHandle(this);
} else {
if (isReshapable()) {
super.gainedSelection(selection);
} else {
Rectangle rect = getBoundingRect();
int x1 = rect.x;
int x2 = rect.x + (rect.width/2);
int x3 = rect.x + rect.width;
int y1 = rect.y;
int y2 = rect.y + (rect.height/2);
int y3 = rect.y + rect.height;
// create the handles
selection.createResizeHandle(this, x1,y1, TopLeft, true);
selection.createResizeHandle(this, x3,y1, TopRight, true);
selection.createResizeHandle(this, x1,y3, BottomLeft, true);
selection.createResizeHandle(this, x3,y3, BottomRight, true);
// if we’re supposed to, create the middle line handles
if (!is4ResizeHandles()) {
selection.createResizeHandle(this, x2,y1, TopMiddle, true);
selection.createResizeHandle(this, x3,y2, SideRight, true);
selection.createResizeHandle(this, x2,y3, BottomMiddle, true);
selection.createResizeHandle(this, x1,y2, SideLeft, true);
}
}
}
}

public boolean isReshapable() { return myReshapable; }
public void setReshapable(boolean value) {
boolean old = myReshapable;
if (old != value) {
myReshapable = value;
update(ChangedReshapable, (old ? 1 : 0), null);
}
}
public void copyNewValueForRedo(JGoDocumentChangedEdit e) {
switch (e.getFlags()) {
case ChangedReshapable:
e.setNewValueBoolean(isReshapable());
return;
default:
super.copyNewValueForRedo(e);
return;
}
}

public void changeValue(JGoDocumentChangedEdit e, boolean undo) {
switch (e.getFlags()) {
case ChangedReshapable: {
setReshapable(e.getValueBoolean(undo));
return; }
default:
super.changeValue(e, undo);
return;
}
}
public static final int ChangedReshapable = LastChangedHint + 2323;
// RotatablePolygon state
private boolean myReshapable = true;
}

#4

Thanks! That’s the pointers I needed. Actually put together something
similar to your code after the first message, but not as complete.

I was trying out the AWT AffineTransform, which sort of works, but
haven’t worked out all kinks (and may be more complicated than we
really need).

– Vince

#5

I forgot to mention that for (3), you can adapt the code for the Diamond example class.

#6

package com.nwoods.jgo.examples.demo1;
import java.awt.;
import java.awt.geom.
;
import com.nwoods.jgo.;
public class RotatablePolygon extends JGoPolygon {
public RotatablePolygon() {}
public void rotate(float cx, float cy, double angle) { // angle is in Radians, clockwise
if (angle == 0) return;
double rad = angle;
double cosine = Math.cos(rad);
double sine = Math.sin(rad);
int numpts = getNumPoints();
foredate(JGoStroke.ChangedAllPoints);
Point2D.Float temp = new Point2D.Float();
for (int i = 0; i < numpts; i++) {
Point p = getPoint(i);
rotatePoint(p.x, p.y, cx, cy, cosine, sine, temp);
setPoint(i, Math.round(temp.x), Math.round(temp.y));
}
setBoundingRectInvalid(true);
update(JGoStroke.ChangedAllPoints, 0, null);
setAngleInternal(getAngle() + angle);
}
public static Point2D.Float rotatePoint(float px, float py, float cx, float cy, double cosine, double sine, Point2D.Float result) {
if (px == cx && py == cy) {
result.x = px;
result.y = py;
} else {
double dx = (double)px-(double)cx;
double dy = (double)py-(double)cy;
result.x = (float)(cx + cosine
dx - sinedy);
result.y = (float)(cy + sine
dx + cosinedy);
}
return result;
}
private void setAngleInternal(double angle) {
double old = getAngle();
if (angle >= Math.PI
2) angle -= Math.PI2;
else if (angle < 0) angle += Math.PI
2;
if (old != angle) {
myAngle = angle;
update(ChangedAngle, 0, new Double(old));
}
}
public double getAngle() { return myAngle; }
public void setAngle(double value) {
double old = myAngle;
if (old != value) {
rotate(getLeft() + getWidth()/2.0f, getTop() + getHeight()/2.0f, value-old);
}
}

protected void gainedSelection(JGoSelection selection) {
if (!isResizable()) {
selection.createBoundingHandle(this);
} else {
if (isReshapable()) {
super.gainedSelection(selection);
} else {
Rectangle rect = getBoundingRect();
int x1 = rect.x;
int x2 = rect.x + (rect.width/2);
int x3 = rect.x + rect.width;
int y1 = rect.y;
int y2 = rect.y + (rect.height/2);
int y3 = rect.y + rect.height;
// create the handles
selection.createResizeHandle(this, x1,y1, TopLeft, true);
selection.createResizeHandle(this, x3,y1, TopRight, true);
selection.createResizeHandle(this, x1,y3, BottomLeft, true);
selection.createResizeHandle(this, x3,y3, BottomRight, true);
// if we’re supposed to, create the middle line handles
if (!is4ResizeHandles()) {
selection.createResizeHandle(this, x2,y1, TopMiddle, true);
selection.createResizeHandle(this, x3,y2, SideRight, true);
selection.createResizeHandle(this, x2,y3, BottomMiddle, true);
selection.createResizeHandle(this, x1,y2, SideLeft, true);
}
}
}
}

public boolean isReshapable() { return myReshapable; }
public void setReshapable(boolean value) {
boolean old = myReshapable;
if (old != value) {
myReshapable = value;
update(ChangedReshapable, (old ? 1 : 0), null);
}
}
public void copyNewValueForRedo(JGoDocumentChangedEdit e) {
switch (e.getFlags()) {
case ChangedReshapable:
e.setNewValueBoolean(isReshapable());
return;
case ChangedAngle:
e.setNewValueDouble(getAngle());
return;
default:
super.copyNewValueForRedo(e);
return;
}
}

public void changeValue(JGoDocumentChangedEdit e, boolean undo) {
switch (e.getFlags()) {
case ChangedReshapable:
setReshapable(e.getValueBoolean(undo));
return;
case ChangedAngle:
setAngle(e.getValueDouble(undo));
return;
default:
super.changeValue(e, undo);
return;
}
}
public static final int ChangedReshapable = LastChangedHint + 2323;
public static final int ChangedAngle = LastChangedHint + 2324;
// RotatablePolygon state
private boolean myReshapable = true;
private double myAngle = 0; // in radians
}