Circles stick to things in game engine - java

In the physics (written following this tutorial) part of my game engine (on github), sometimes collisions between AABBs and circles or between circles and circles stick together, like so (apologies for the gif artifacts):
I've confirmed that this happens even when I don't call applyFriction, so it's not that. I've also made the positionalCorrection algorithm uses 1.05f for percent, but even that doesn't fix anything, so I'm stumped.
public final class Collisions {
private Collisions() {
// cant instantiate this class
}
/**
* Returns whether two GameObjects are colliding
*
* #param a
* must be a RectObject or CircleObject
* #param b
* must be a RectObject or CircleObject
* #return
*/
public static boolean isColliding(final GameEntity a, final GameEntity b) {
final CShape as = a.shape;
final CShape bs = b.shape;
if (as instanceof RectShape && bs instanceof RectShape) {
return isColliding((RectShape) as, (RectShape) bs);
}
if (as instanceof CircleShape && bs instanceof CircleShape) {
return isColliding((CircleShape) as, (CircleShape) bs);
}
if (as instanceof RectShape && bs instanceof CircleShape) {
return isColliding((RectShape) as, (CircleShape) bs);
}
if (as instanceof CircleShape && bs instanceof RectShape) {
return isColliding((RectShape) bs, (CircleShape) as);
}
throw new UnsupportedOperationException();
}
private static boolean isColliding(final RectShape a, final RectShape b) {
return collisionNormal(a, b) != null;
}
private static boolean isColliding(final CircleShape o1, final CircleShape o2) {
final float c = o1.radius + o2.radius;
final float b = o1.center.x - o2.center.x;
final float a = o1.center.y - o2.center.y;
return c * c > b * b + a * a;
}
private static boolean isColliding(final RectShape a, final CircleShape b) {
final float circleDistance_x = Math.abs(b.center().x - (a.min.x + a.width() / 2));
final float circleDistance_y = Math.abs(b.center().y - (a.min.y + a.height() / 2));
if (circleDistance_x > a.width() / 2 + b.radius) {
return false;
}
if (circleDistance_y > a.height() / 2 + b.radius) {
return false;
}
if (circleDistance_x <= a.width() / 2) {
return true;
}
if (circleDistance_y <= a.height() / 2) {
return true;
}
final int cornerDistance_sq = (int) Math.pow(circleDistance_x - a.width() / 2, 2) + (int) Math.pow(circleDistance_y - a.height() / 2, 2);
return cornerDistance_sq <= (int) Math.pow(b.radius, 2);
}
/**
* Returns the face normal of a collision between a and b
*
* #param a
* #param b
* #return null if no collision
*/
private static Vec2D collisionNormal(final RectShape a, final RectShape b) {
final float w = 0.5f * (a.width() + b.width());
final float h = 0.5f * (a.height() + b.height());
final float dx = a.center().x - b.center().x;
final float dy = a.center().y - b.center().y;
if (Math.abs(dx) <= w && Math.abs(dy) <= h) {
/* collision! */
final float wy = w * dy;
final float hx = h * dx;
if (wy > hx) {
if (wy > -hx) {
/* collision at the top */
return new Vec2D(0, -1);
} else {
/* on the left */
return new Vec2D(1, 0);
}
} else {
if (wy > -hx) {
/* on the right */
return new Vec2D(-1, 0);
} else {
/* at the bottom */
return new Vec2D(0, 1);
}
}
}
return null;
}
public static void fixCollision(final GameEntity a, final GameEntity b) {
fixCollision(generateManifold(a, b), true);
}
/**
* Fixes a collision between two objects by correcting their positions and applying impulses.
*
*/
public static void fixCollision(final CManifold m, final boolean applyFriction) {
final GameEntity a = m.a;
final GameEntity b = m.b;
// Calculate relative velocity
final Vec2D rv = b.velocity.minus(a.velocity);
// Calculate relative velocity in terms of the normal direction
final float velAlongNormal = rv.dotProduct(m.normal);
// Calculate restitution
final float e = Math.min(a.getRestitution(), b.getRestitution());
// Calculate impulse scalar
float j = -(1 + e) * velAlongNormal;
j /= a.getInvMass() + b.getInvMass();
// Apply impulse
final Vec2D impulse = m.normal.multiply(j);
a.velocity = a.velocity.minus(impulse.multiply(a.getInvMass()));
b.velocity = b.velocity.plus(impulse.multiply(b.getInvMass()));
if (applyFriction) {
applyFriction(m, j);
}
positionalCorrection(m);
}
private static void applyFriction(final CManifold m, final float normalMagnitude) {
final GameEntity a = m.a;
final GameEntity b = m.b;
// relative velocity
final Vec2D rv = b.velocity.minus(a.velocity);
// normalized tangent force
final Vec2D tangent = rv.minus(m.normal.multiply(m.normal.dotProduct(rv))).unitVector();
// friction magnitude
final float jt = -rv.dotProduct(tangent) / (a.getInvMass() + b.getInvMass());
// friction coefficient
final float mu = (a.getStaticFriction() + b.getStaticFriction()) / 2;
final float dynamicFriction = (a.getDynamicFriction() + b.getDynamicFriction()) / 2;
// Coulomb's law: force of friction <= force along normal * mu
final Vec2D frictionImpulse = Math.abs(jt) < normalMagnitude * mu ? tangent.multiply(jt) : tangent.multiply(-normalMagnitude
* dynamicFriction);
// apply friction
a.velocity = a.velocity.minus(frictionImpulse.multiply(a.getInvMass()));
b.velocity = b.velocity.plus(frictionImpulse.multiply(b.getInvMass()));
}
/**
* Generates a collision manifold from two colliding objects.
*
* #param a
* #param b
* #return
*/
private static CManifold generateManifold(final GameEntity a, final GameEntity b) {
final CManifold m = new CManifold();
m.a = a;
m.b = b;
final CShape as = a.shape;
final CShape bs = b.shape;
if (as instanceof RectShape && bs instanceof RectShape) {
return generateManifold((RectShape) as, (RectShape) bs, m);
} else if (as instanceof CircleShape && bs instanceof CircleShape) {
return generateManifold((CircleShape) as, (CircleShape) bs, m);
} else if (as instanceof RectShape && bs instanceof CircleShape) {
return generateManifold((RectShape) as, (CircleShape) bs, m);
} else if (as instanceof CircleShape && bs instanceof RectShape) {
m.b = a;
m.a = b;
return generateManifold((RectShape) bs, (CircleShape) as, m);
} else {
throw new UnsupportedOperationException();
}
}
private static CManifold generateManifold(final RectShape a, final RectShape b, final CManifold m) {
final Rectangle2D r = a.getRect().createIntersection(b.getRect());
m.normal = collisionNormal(a, b);
// penetration is the min resolving distance
m.penetration = (float) Math.min(r.getWidth(), r.getHeight());
return m;
}
private static CManifold generateManifold(final CircleShape a, final CircleShape b, final CManifold m) {
// A to B
final Vec2D n = b.center.minus(a.center);
final float dist = n.length();
if (dist == 0) {
// circles are on the same position, choose random but consistent values
m.normal = new Vec2D(0, 1);
m.penetration = Math.min(a.radius, b.radius);
return m;
}
// don't recalculate dist to normalize
m.normal = n.divide(dist);
m.penetration = b.radius + a.radius - dist;
return m;
}
private static CManifold generateManifold(final RectShape a, final CircleShape b, final CManifold m) {
// Vector from A to B
final Vec2D n = b.center.minus(a.center());
// Closest point on A to center of B
Vec2D closest = n;
// Calculate half extents along each axis
final float x_extent = a.width() / 2;
final float y_extent = a.height() / 2;
// Clamp point to edges of the AABB
closest = new Vec2D(clamp(closest.x, -x_extent, x_extent), clamp(closest.y, -y_extent, y_extent));
boolean inside = false;
// Circle is inside the AABB, so we need to clamp the circle's center
// to the closest edge
if (n.equals(closest)) {
inside = true;
// Find closest axis
if (Math.abs(closest.x) > Math.abs(closest.y)) {
// Clamp to closest extent
closest = new Vec2D(closest.x > 0 ? x_extent : -x_extent, closest.y);
}
// y axis is shorter
else {
// Clamp to closest extent
closest = new Vec2D(closest.x, closest.y > 0 ? y_extent : -y_extent);
}
}
// closest point to center of the circle
final Vec2D normal = n.minus(closest);
final float d = normal.length();
final float r = b.radius;
// Collision normal needs to be flipped to point outside if circle was
// inside the AABB
m.normal = inside ? normal.unitVector().multiply(-1) : normal.unitVector();
m.penetration = r - d;
return m;
}
private static float clamp(final float n, final float lower, final float upper) {
return Math.max(lower, Math.min(n, upper));
}
/**
* Corrects positions between two colliding objects to avoid "sinking."
*
* #param m
*/
private static void positionalCorrection(final CManifold m) {
final GameEntity a = m.a;
final GameEntity b = m.b;
// the amount to correct by
final float percent = 1f; // usually .2 to .8
// the amount in which we don't really care, this avoids vibrating objects.
final float slop = 0.05f; // usually 0.01 to 0.1
final float correctionMag = m.penetration + (m.penetration > 0 ? -slop : slop);
final Vec2D correction = m.normal.multiply(correctionMag / (a.getInvMass() + b.getInvMass()) * percent);
a.moveRelative(correction.multiply(-1 * a.getInvMass()));
b.moveRelative(correction.multiply(b.getInvMass()));
}
}
Update:
I've fixed the problem so that circles don't stick to other circles anymore by making it so that I only check and fix between two objects once per tick (and not possible twice), but circles still sometimes stick to rectangles and can still ride underneath them.

I fixed the problem. I just had to change my calculations a bit in my fixCollision method.
This is a link to the commit that fixed the problem, if you're curious.

Related

How to more realistically simulate light on a sphere?

I am attempting to simulate a sphere, and shade it realistically given an origin vector for the light, and the sphere being centered around the origin. Moreover, the light's vector is the normal vector on a larger invisible sphere at a chosen point. The sphere looks off.
https://imgur.com/a/IDIwQQF
The problem, is that it is very difficult to bug fix this kind of program. Especially considering that I know how I want it to look in my head, but when looking at the numbers in my program there is very little meaning attached to them.
Since I don't know where the issue is, I'm forced to paste all of it here.
public class SphereDrawing extends JPanel {
private static final long serialVersionUID = 1L;
private static final int ADJ = 320;
private static final double LIGHT_SPHERE_RADIUS = 5;
private static final double LIGHT_X = 3;
private static final double LIGHT_Y = 4;
private static final double LIGHT_Z = 0;
private static final double DRAWN_SPHERE_RADIUS = 1;
private static final int POINT_COUNT = 1000000;
private static Coord[] points;
private static final double SCALE = 200;
public SphereDrawing() {
setPreferredSize(new Dimension(640, 640));
setBackground(Color.white);
points = new Coord[POINT_COUNT];
initializePoints();
for (int i = 0; i < points.length; i++) {
points[i].scale();
}
new Timer(17, (ActionEvent e) -> {
repaint();
}).start();
}
public void initializePoints() { //finding the points on the surface of the sphere (hopefully somewhat equidistant)
double random = Math.random() * (double)POINT_COUNT;
double offset = 2/(double)POINT_COUNT;
double increment = Math.PI * (3 - Math.sqrt(5));
for (int i = 0; i < POINT_COUNT; i++) {
double y = ((i * offset) - 1) + (offset / 2);
double r = Math.sqrt(1 - Math.pow(y, 2));
double phi = ((i + random) % (double)POINT_COUNT) * increment;
double x = Math.cos(phi) * r;
double z = Math.sin(phi) * r;
points[i] = new Coord(x, y, z);
}
}
public void drawSphere(Graphics2D g) {
g.translate(ADJ, ADJ); //shifting from origin for drawing purposes
Arrays.sort(points); //sorting points by their z coordinates
double iHat = -2 * LIGHT_X;
double jHat = -2 * LIGHT_Y; //Light vector
double kHat = -2 * LIGHT_Z;
double angL1 = 0;
if (Math.abs(iHat) != 0.0)
angL1 = Math.atan(jHat / iHat); //converting light vector to spherical coordinates
else
angL1 = Math.PI/2;
double angL2 = Math.atan(Math.sqrt(Math.pow(iHat, 2) + Math.pow(jHat, 2))/ kHat);
double maxArcLength = LIGHT_SPHERE_RADIUS * Math.PI; // maximum arc length
for (int i = 0; i < points.length; i++) {
if(points[i].checkValid()) {
double siHat = -2 * points[i].x;
double sjHat = -2 * points[i].y; //finding normal vector for the given point on the sphere
double skHat = -2 * points[i].z;
double angSF1 = -1 * Math.abs(Math.atan(sjHat / siHat)); // converting vector to spherical coordinates
double angSF2 = Math.atan(Math.sqrt(Math.pow(siHat, 2) + Math.pow(sjHat, 2))/ skHat);
double actArcLength = LIGHT_SPHERE_RADIUS * Math.acos(Math.cos(angL1) * Math.cos(angSF1) + Math.sin(angL1) * Math.sin(angSF1) * Math.cos(angL2 - angSF2)); //calculating arc length at this point
double comp = actArcLength / maxArcLength; // comparing the maximum arc length to the calculated arc length for this vector
int col = (int)(comp * 255);
col = Math.abs(col);
g.setColor(new Color(col, col, col));
double ovalDim = (4 * Math.PI * Math.pow(DRAWN_SPHERE_RADIUS, 2))/POINT_COUNT; //using surface area to determine how large size of each point should be drawn
if (ovalDim < 1) // if it too small, make less small
ovalDim = 2;
g.fillOval((int)points[i].x, (int)points[i].y, (int)ovalDim, (int)ovalDim); //draw this oval
}
}
}
#Override
public void paintComponent(Graphics gg) {
super.paintComponent(gg);
Graphics2D g = (Graphics2D) gg;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawSphere(g);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setTitle("Sphere");
f.setResizable(false);
f.add(new SphereDrawing(), BorderLayout.CENTER);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
#SuppressWarnings("rawtypes")
private class Coord implements Comparable {
public double x;
public double y;
public double z;
public Coord(double x2, double y2, double z2) {
x = x2;
y = y2;
z = z2;
}
public void scale() {
x *= SCALE;
y *= SCALE; //drawing purposes
z *= SCALE;
}
public String toString() {
return x + " " + y + " " + z;
}
public int compareTo(Object c) {
double diff = this.z - ((Coord)c).z;
if (diff < 0)
return -1;
else if (diff > 0) //for sorting the array of points
return 1;
else
return 0;
}
public boolean checkValid() {
return (z > 0); //checks if need to draw this point
}
}
}
I was hoping to at least draw a realistic looking sphere, even if not completely accurate, and I couldn't tell you what exactly is off with mine

How to check object class

I am doing something wrong when i try get type of object.
I am trying to sum two vectors, it can be in cartesian coordinate (x,y) or polar coordinate (azimuth, length).
How i can check what type of object i have?
Here is my code:
import java.lang.Math;
/**
* Write a description of class Velocity here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Velocity
{
// instance variables - replace the example below with your own
private double x;
private double y;
private double azimuth;
private double length;
private double prod;
private PolarCoordinate pt;
private CartesianCoordinate ct;
private boolean compare;
private double s;
/**
* Constructor for objects of class Velocity
*/
public Velocity(CartesianCoordinate c)
{
x = c.getX();
y = c.getY();
}
public Velocity(Velocity z)
{
}
public Velocity(PolarCoordinate p)
{
ct = new CartesianCoordinate(x, y);
pt = new PolarCoordinate(azimuth, length);
azimuth = p.getAzimuth();
length = p.getLength();
}
private Velocity sumOfPolar(double s)
{
this.s = s;
return null;
}
private Velocity PolarCoordinate(double azimuth, double length)
{
return null;
}
private Velocity CartesianCoordinate(double x, double y)
{
return null;
}
private Velocity dotProduct(double prod)
{
return null;
}
//private boolean compare(java.lang.Object velocity,java.lang.Object ct)
//{
// if (velocity.getClass().equals(ct.getClass())) {
// return true;
// } else {
// return false;
// }
//}
/**
* This method performs a vector addition
* and returns a new object representing the
* sum of two vectors.
*
*/
public Velocity add(final Velocity velocity)
{
if(velocity.getClass().equals(ct.getClass()))
{
double sumX = x + velocity.x;
double sumY = y + velocity.y;
Velocity v = new Velocity(CartesianCoordinate(x,y));
v.x = sumX;
v.y = sumY;
System.out.println("BLYABLYA");
return v;
}
if(compare == false)
{
System.out.println("YEAAAAA");
Velocity v = new Velocity(PolarCoordinate(azimuth, length));
double xFirst = length * Math.cos(azimuth);
System.out.println("xFirst: " + xFirst);
double xSecond = velocity.length * Math.cos(velocity.azimuth);
System.out.println("xSecond: " + xSecond);
double yFirst = length * Math.sin(azimuth);
System.out.println("yFirst: " + yFirst);
double ySecond = velocity.length * Math.sin(velocity.azimuth);
System.out.println("ySecond: " + ySecond);
double sumX = xFirst + xSecond;
System.out.println("sumX: " + sumX);
double sumY = yFirst + ySecond;
System.out.println("sumY: " + sumY);
double sumXY = sumX + sumY;
System.out.println("sumXY: " + sumXY);
Velocity sum = new Velocity(sumOfPolar(sumXY));
sum.s = sumXY;
return sum;
}
return null;
}
/**
* This method performs a vector subtraction
* and returns a new object representing the
* sub of two vectors.
*
*/
public Velocity subtarct(final Velocity velocity)
{
double subX = x - velocity.x;
double subY = y - velocity.y;
Velocity v = new Velocity(CartesianCoordinate(x,y));
v.x = subX;
v.y = subY;
return v;
}
/**
* This method performs a vector dot product
* and returns a new object representing the
* product of two vectors.
*
*/
public Velocity dotProduct(final Velocity velocity)
{
double prodX = x * velocity.x;
double prodY = y * velocity.y;
double sumProdXY = prodX + prodY;
Velocity v = new Velocity(dotProduct(prod));
v.prod = sumProdXY;
return v;
}
/**
* This method performs a scaling on a vector.
*
*/
public Velocity scale(final double scaleFactor)
{
double prodX = x * scaleFactor;
double prodY = y * scaleFactor;
Velocity v = new Velocity(CartesianCoordinate(x, y));
v.x = prodX;
v.y = prodY;
return v;
}
public double getAzimuth()
{
PolarCoordinate p = new PolarCoordinate(azimuth,length);
return p.getAzimuth();
}
public double getLength()
{
PolarCoordinate p = new PolarCoordinate(azimuth,length);
return p.getLength();
}
}
You can use instanceof to determine the object type.
if (velocity instanceof PolarCoordinate) {
return true;
} else {
return false;
}
use instanceof.
String s = "TGTGGQCC";
System.out.println(s instanceof String); // true

How to Rotate Circle with text on Canvas in Blackberry

How to Rotate Circle with Text on TouchEvent or on TrackBallMoveEvent.
How do I create this kind of circle?
I had created a circle and rotated it also, but it always starts from 0 degrees.
Is there any other option to create this kind of circle?
Each circle have different text and each of the circles can move independently.
So, this is definitely not complete, but I think it's most of what you need.
Limitations/Assumptions
I have so far only implemented touch handling, as I think that's more difficult. If I get time later, I'll come back and add trackball handling.
I did not give the spinning discs any momentum. After the user's finger leaves the disc, it stops spinning.
I'm not sure the focus transitions between discs are 100% right. You'll have to do some testing. They're mostly right, at least.
When you mentioned Canvas in the title, I assumed that didn't mean you required this to utilize the J2ME Canvas. Writing BlackBerry apps with the RIM UI libraries is pretty much all I've done.
Solution
Essentially, I created a Field subclass to represent each disc. You create the field by passing in an array of labels, to be spaced around the perimeter, a radius, and a color. Hardcoded in each DiscField is an edge inset for the text, which kind of assumes a certain size difference between discs. You should probably make that more dynamic.
public class DiscField extends Field {
/** Used to map Manager's TouchEvents into our coordinate system */
private int _offset = 0;
private int _radius;
private int _fillColor;
private double _currentRotation = 0.0;
private double _lastTouchAngle = 0.0;
private boolean _rotating = false;
private String[] _labels;
/** Text inset from outer disc edge */
private static final int INSET = 30;
private DiscField() {
}
public DiscField(String[] labels, int radius, int fillColor) {
super(Field.FOCUSABLE);
_labels = labels;
_radius = radius;
_fillColor = fillColor;
}
protected void layout(int width, int height) {
setExtent(Math.min(width, getPreferredWidth()), Math.min(height, getPreferredHeight()));
}
private void drawFilledCircle(Graphics g, int x, int y, int r) {
// http://stackoverflow.com/a/1186851/119114
g.fillEllipse(x, y, x + r, y, x, y + r, 0, 360);
}
private void drawCircle(Graphics g, int x, int y, int r) {
g.drawEllipse(x, y, x + r, y, x, y + r, 0, 360);
}
protected void paint(Graphics graphics) {
int oldColor = graphics.getColor();
graphics.setColor(_fillColor);
drawFilledCircle(graphics, _radius, _radius, _radius);
graphics.setColor(Color.WHITE);
drawCircle(graphics, _radius, _radius, _radius);
// plot the text around the circle, inset by some 'padding' value
int textColor = (_fillColor == Color.WHITE) ? Color.BLACK : Color.WHITE;
graphics.setColor(textColor);
// equally space the labels around the disc
double interval = (2.0 * Math.PI / _labels.length);
for (int i = 0; i < _labels.length; i++) {
// account for font size when plotting text
int fontOffsetX = getFont().getAdvance(_labels[i]) / 2;
int fontOffsetY = getFont().getHeight() / 2;
int x = _radius + (int) ((_radius - INSET) * Math.cos(i * interval - _currentRotation)) - fontOffsetX;
int y = _radius - (int) ((_radius - INSET) * Math.sin(i * interval - _currentRotation)) - fontOffsetY;
graphics.drawText(_labels[i], x, y);
}
graphics.setColor(oldColor);
}
protected void drawFocus(Graphics graphics, boolean on) {
if (on) {
int oldColor = graphics.getColor();
int oldAlpha = graphics.getGlobalAlpha();
// just draw a white shine to indicate focus
graphics.setColor(Color.WHITE);
graphics.setGlobalAlpha(80);
drawFilledCircle(graphics, _radius, _radius, _radius);
// reset graphics context
graphics.setColor(oldColor);
graphics.setGlobalAlpha(oldAlpha);
}
}
protected void onUnfocus() {
super.onUnfocus();
_rotating = false;
}
protected boolean touchEvent(TouchEvent event) {
switch (event.getEvent()) {
case TouchEvent.MOVE: {
setFocus();
// Get the touch location, within this Field
int x = event.getX(1) - _offset - _radius;
int y = event.getY(1) - _offset - _radius;
if (x * x + y * y <= _radius * _radius) {
double angle = MathUtilities.atan2(y, x);
if (_rotating) {
// _lastTouchAngle only valid if _rotating
_currentRotation += angle - _lastTouchAngle;
// force a redraw (paint) with the new rotation angle
invalidate();
} else {
_rotating = true;
}
_lastTouchAngle = angle;
return true;
}
}
case TouchEvent.UNCLICK:
case TouchEvent.UP: {
_rotating = false;
return true;
}
case TouchEvent.DOWN: {
setFocus();
int x = event.getX(1) - _offset - _radius;
int y = event.getY(1) - _offset - _radius;
if (x * x + y * y <= _radius * _radius) {
_lastTouchAngle = MathUtilities.atan2(y, x);
_rotating = true;
return true;
}
}
default:
break;
}
return super.touchEvent(event);
}
protected boolean trackwheelRoll(int arg0, int arg1, int arg2) {
return super.trackwheelRoll(arg0, arg1, arg2);
// TODO!
}
public int getPreferredHeight() {
return getPreferredWidth();
}
public int getPreferredWidth() {
return 2 * _radius;
}
public String[] getLabels() {
return _labels;
}
public void setLabels(String[] labels) {
this._labels = labels;
}
public int getRadius() {
return _radius;
}
public void setRadius(int radius) {
this._radius = radius;
}
public double getCurrentAngle() {
return _currentRotation;
}
public void setCurrentAngle(double angle) {
this._currentRotation = angle;
}
public int getOffset() {
return _offset;
}
public void setOffset(int offset) {
this._offset = offset;
}
}
Containing all the DiscField objects is the DiscManager. It aligns the child DiscFields in sublayout(), and handles proper delegation of touch events ... since the fields overlap, and a touch within a DiscFields extent that does not also fall within its radius (i.e. the corners) should be handled by a larger disc.
/**
* A DiscManager is a container for DiscFields and manages proper delegation
* of touch event handling.
*/
private class DiscManager extends Manager {
private int _maxRadius = 0;
public DiscManager(long style){
super(style);
DiscField outerDisc = new DiscField(new String[] { "1", "2", "3", "4", "5", "6" },
180, Color.BLUE);
_maxRadius = outerDisc.getRadius();
DiscField middleDisc = new DiscField(new String[] { "1", "2", "3", "4", "5" },
120, Color.GRAY);
middleDisc.setOffset(_maxRadius - middleDisc.getRadius());
DiscField innerDisc = new DiscField(new String[] { "1", "2", "3", "4" },
60, Color.RED);
innerDisc.setOffset(_maxRadius - innerDisc.getRadius());
// order matters here:
add(outerDisc);
add(middleDisc);
add(innerDisc);
}
protected void sublayout(int width, int height) {
setExtent(2 * _maxRadius, 2 * _maxRadius);
// each disc needs to have the same x,y center to be concentric
for (int i = 0; i < getFieldCount(); i++) {
if (getField(i) instanceof DiscField) {
DiscField disc = (DiscField) getField(i);
int xCenter = _maxRadius - disc.getRadius();
int yCenter = _maxRadius - disc.getRadius();
setPositionChild(disc, xCenter, yCenter);
layoutChild(disc, 2 * _maxRadius, 2 * _maxRadius);
}
}
}
protected boolean touchEvent(TouchEvent event) {
int eventCode = event.getEvent();
// Get the touch location, within this Manager
int x = event.getX(1);
int y = event.getY(1);
if ((x >= 0) && (y >= 0) && (x < getWidth()) && (y < getHeight())) {
int field = getFieldAtLocation(x, y);
if (field >= 0) {
DiscField df = null;
for (int i = 0; i < getFieldCount(); i++) {
if (getField(field) instanceof DiscField) {
int r = ((DiscField)getField(field)).getRadius();
// (_maxRadius, _maxRadius) is the center of all discs
if ((x - _maxRadius) * (x - _maxRadius) + (y - _maxRadius) * (y - _maxRadius) <= r * r) {
df = (DiscField)getField(field);
} else {
// touch was not within this disc's radius, so the one slightly bigger
// should be passed this touch event
break;
}
}
}
// Let event propagate to child field
return (df != null) ? df.touchEvent(event) : super.touchEvent(event);
} else {
if (eventCode == TouchEvent.DOWN) {
setFocus();
}
// Consume the event
return true;
}
}
// Event wasn't for us, let superclass handle in default manner
return super.touchEvent(event);
}
}
Finally, a screen to use them:
public class DiscScreen extends MainScreen {
public DiscScreen() {
super(MainScreen.VERTICAL_SCROLL | MainScreen.VERTICAL_SCROLLBAR);
add(new DiscManager(Field.USE_ALL_WIDTH));
}
}
Results

Point of contact of 2 OBBs?

I'm working on the physics for my GTA2-like game so I can learn more about game physics.
The collision detection and resolution are working great.
I'm now just unsure how to compute the point of contact when I hit a wall.
Here is my OBB class:
public class OBB2D
{
private Vector2D projVec = new Vector2D();
private static Vector2D projAVec = new Vector2D();
private static Vector2D projBVec = new Vector2D();
private static Vector2D tempNormal = new Vector2D();
private Vector2D deltaVec = new Vector2D();
// Corners of the box, where 0 is the lower left.
private Vector2D corner[] = new Vector2D[4];
private Vector2D center = new Vector2D();
private Vector2D extents = new Vector2D();
private RectF boundingRect = new RectF();
private float angle;
//Two edges of the box extended away from corner[0].
private Vector2D axis[] = new Vector2D[2];
private double origin[] = new double[2];
public OBB2D(float centerx, float centery, float w, float h, float angle)
{
for(int i = 0; i < corner.length; ++i)
{
corner[i] = new Vector2D();
}
for(int i = 0; i < axis.length; ++i)
{
axis[i] = new Vector2D();
}
set(centerx,centery,w,h,angle);
}
public OBB2D(float left, float top, float width, float height)
{
for(int i = 0; i < corner.length; ++i)
{
corner[i] = new Vector2D();
}
for(int i = 0; i < axis.length; ++i)
{
axis[i] = new Vector2D();
}
set(left + (width / 2), top + (height / 2),width,height,0.0f);
}
public void set(float centerx,float centery,float w, float h,float angle)
{
float vxx = (float)Math.cos(angle);
float vxy = (float)Math.sin(angle);
float vyx = (float)-Math.sin(angle);
float vyy = (float)Math.cos(angle);
vxx *= w / 2;
vxy *= (w / 2);
vyx *= (h / 2);
vyy *= (h / 2);
corner[0].x = centerx - vxx - vyx;
corner[0].y = centery - vxy - vyy;
corner[1].x = centerx + vxx - vyx;
corner[1].y = centery + vxy - vyy;
corner[2].x = centerx + vxx + vyx;
corner[2].y = centery + vxy + vyy;
corner[3].x = centerx - vxx + vyx;
corner[3].y = centery - vxy + vyy;
this.center.x = centerx;
this.center.y = centery;
this.angle = angle;
computeAxes();
extents.x = w / 2;
extents.y = h / 2;
computeBoundingRect();
}
//Updates the axes after the corners move. Assumes the
//corners actually form a rectangle.
private void computeAxes()
{
axis[0].x = corner[1].x - corner[0].x;
axis[0].y = corner[1].y - corner[0].y;
axis[1].x = corner[3].x - corner[0].x;
axis[1].y = corner[3].y - corner[0].y;
// Make the length of each axis 1/edge length so we know any
// dot product must be less than 1 to fall within the edge.
for (int a = 0; a < axis.length; ++a)
{
float l = axis[a].length();
float ll = l * l;
axis[a].x = axis[a].x / ll;
axis[a].y = axis[a].y / ll;
origin[a] = corner[0].dot(axis[a]);
}
}
public void computeBoundingRect()
{
boundingRect.left = JMath.min(JMath.min(corner[0].x, corner[3].x), JMath.min(corner[1].x, corner[2].x));
boundingRect.top = JMath.min(JMath.min(corner[0].y, corner[1].y),JMath.min(corner[2].y, corner[3].y));
boundingRect.right = JMath.max(JMath.max(corner[1].x, corner[2].x), JMath.max(corner[0].x, corner[3].x));
boundingRect.bottom = JMath.max(JMath.max(corner[2].y, corner[3].y),JMath.max(corner[0].y, corner[1].y));
}
public void set(RectF rect)
{
set(rect.centerX(),rect.centerY(),rect.width(),rect.height(),0.0f);
}
// Returns true if other overlaps one dimension of this.
private boolean overlaps1Way(OBB2D other)
{
for (int a = 0; a < axis.length; ++a) {
double t = other.corner[0].dot(axis[a]);
// Find the extent of box 2 on axis a
double tMin = t;
double tMax = t;
for (int c = 1; c < corner.length; ++c) {
t = other.corner[c].dot(axis[a]);
if (t < tMin) {
tMin = t;
} else if (t > tMax) {
tMax = t;
}
}
// We have to subtract off the origin
// See if [tMin, tMax] intersects [0, 1]
if ((tMin > 1 + origin[a]) || (tMax < origin[a])) {
// There was no intersection along this dimension;
// the boxes cannot possibly overlap.
return false;
}
}
// There was no dimension along which there is no intersection.
// Therefore the boxes overlap.
return true;
}
public void moveTo(float centerx, float centery)
{
float cx,cy;
cx = center.x;
cy = center.y;
deltaVec.x = centerx - cx;
deltaVec.y = centery - cy;
for (int c = 0; c < 4; ++c)
{
corner[c].x += deltaVec.x;
corner[c].y += deltaVec.y;
}
boundingRect.left += deltaVec.x;
boundingRect.top += deltaVec.y;
boundingRect.right += deltaVec.x;
boundingRect.bottom += deltaVec.y;
this.center.x = centerx;
this.center.y = centery;
computeAxes();
}
// Returns true if the intersection of the boxes is non-empty.
public boolean overlaps(OBB2D other)
{
if(right() < other.left())
{
return false;
}
if(bottom() < other.top())
{
return false;
}
if(left() > other.right())
{
return false;
}
if(top() > other.bottom())
{
return false;
}
if(other.getAngle() == 0.0f && getAngle() == 0.0f)
{
return true;
}
return overlaps1Way(other) && other.overlaps1Way(this);
}
public Vector2D getCenter()
{
return center;
}
public float getWidth()
{
return extents.x * 2;
}
public float getHeight()
{
return extents.y * 2;
}
public void setAngle(float angle)
{
set(center.x,center.y,getWidth(),getHeight(),angle);
}
public float getAngle()
{
return angle;
}
public void setSize(float w,float h)
{
set(center.x,center.y,w,h,angle);
}
public float left()
{
return boundingRect.left;
}
public float right()
{
return boundingRect.right;
}
public float bottom()
{
return boundingRect.bottom;
}
public float top()
{
return boundingRect.top;
}
public RectF getBoundingRect()
{
return boundingRect;
}
public boolean overlaps(float left, float top, float right, float bottom)
{
if(right() < left)
{
return false;
}
if(bottom() < top)
{
return false;
}
if(left() > right)
{
return false;
}
if(top() > bottom)
{
return false;
}
return true;
}
public static float distance(float ax, float ay,float bx, float by)
{
if (ax < bx)
return bx - ay;
else
return ax - by;
}
public Vector2D project(float ax, float ay)
{
projVec.x = Float.MAX_VALUE;
projVec.y = Float.MIN_VALUE;
for (int i = 0; i < corner.length; ++i)
{
float dot = Vector2D.dot(corner[i].x,corner[i].y,ax,ay);
projVec.x = JMath.min(dot, projVec.x);
projVec.y = JMath.max(dot, projVec.y);
}
return projVec;
}
public Vector2D getCorner(int c)
{
return corner[c];
}
public int getNumCorners()
{
return corner.length;
}
public static float collisionResponse(OBB2D a, OBB2D b, Vector2D outNormal)
{
float depth = Float.MAX_VALUE;
for (int i = 0; i < a.getNumCorners() + b.getNumCorners(); ++i)
{
Vector2D edgeA;
Vector2D edgeB;
if(i >= a.getNumCorners())
{
edgeA = b.getCorner((i + b.getNumCorners() - 1) % b.getNumCorners());
edgeB = b.getCorner(i % b.getNumCorners());
}
else
{
edgeA = a.getCorner((i + a.getNumCorners() - 1) % a.getNumCorners());
edgeB = a.getCorner(i % a.getNumCorners());
}
tempNormal.x = edgeB.x -edgeA.x;
tempNormal.y = edgeB.y - edgeA.y;
tempNormal.normalize();
projAVec.equals(a.project(tempNormal.x,tempNormal.y));
projBVec.equals(b.project(tempNormal.x,tempNormal.y));
float distance = OBB2D.distance(projAVec.x, projAVec.y,projBVec.x,projBVec.y);
if (distance > 0.0f)
{
return 0.0f;
}
else
{
float d = Math.abs(distance);
if (d < depth)
{
depth = d;
outNormal.equals(tempNormal);
}
}
}
float dx,dy;
dx = b.getCenter().x - a.getCenter().x;
dy = b.getCenter().y - a.getCenter().y;
float dot = Vector2D.dot(dx,dy,outNormal.x,outNormal.y);
if(dot > 0)
{
outNormal.x = -outNormal.x;
outNormal.y = -outNormal.y;
}
return depth;
}
public Vector2D getMoveDeltaVec()
{
return deltaVec;
}
};
I'm now just unsure how to compute the point of contact when I hit a
wall.
You can represent a wall with a simple plane.
The OBB-vs-plane intersection test is the simplest separating axis test of them all:
If two convex objects don't intersect, then there is a plane where
the projection of these two objects will not intersect.
A box intersects plane only if the plane normal forms a separating axis. Compute the projection of the box center and the projected radius (4 dot products and a few adds) and you're good to go (you also get penetration depth for free).
The condition looks as follows:
|d| <= a1|n*A1| + a2|n*A2| + a3|n*A3|
Here:
d distance from the center of the box to the plane.
a1...a3 the extents of the box from the center.
n normal of the plane
A1...A3 the x,y,z-axis of the box
Some pseudocode:
//Test if OBB b intersects plane p
int TestOBBPlane(OBB b, Plane p)
{
// Compute the projection interval radius of b onto L(t) = b.c + t * p.n
float r = b.e[0]*Abs(Dot(p.n, b.u[0])) +
b.e[1]*Abs(Dot(p.n, b.u[1])) +
b.e[2]*Abs(Dot(p.n, b.u[2]));
// Compute distance of box center from plane
float s = Dot(p.n, b.c) – p.d;
// Intersection occurs when distance s falls within [-r,+r] interval
return Abs(s) <= r;
}
The OBB-vs-OBB intersection test is more complicated.
Let us refer to this great tutorial:
In this case we no longer have corresponding separating lines that are
perpendicular to the separating axes. Instead, we have separating
planes that separate the bounding volumes (and they are perpendicular
to their corresponding separating axes).
In 3D space, each OBB only has 3 unique planes extended by its faces,
and the separating planes are parallel to these faces. We are
interested in the separating planes parallel to the faces, but in 3D
space, the faces are not the only concern. We are also interested in
the edges. The separating planes of interest are parallel to the faces
of the boxes, and the separating axes of interest are perpendicular to
the separating planes. Hence the separating axes of interest are
perpendicular to the 3 unique faces of each box. Notice these 6
separating axes of interest correspond to the 6 local (XYZ) axes of
the two boxes.
So there are 9 separating axes to consider for edges collision in
addition to the 6 separating axes we already have found for the faces
collision. This makes the total number of possible separating axes to
consider at 15.
Here are the 15 possible separating axes (L) you will need to test:
CASE 1: L = Ax
CASE 2: L = Ay
CASE 3: L = Az
CASE 4: L = Bx
CASE 5: L = By
CASE 6: L = Bz
CASE 7: L = Ax x Bx
CASE 8: L = Ax x By
CASE 9: L = Ax x Bz
CASE 10: L = Ay x Bx
CASE 11: L = Ay x By
CASE 12: L = Ay x Bz
CASE 13: L = Az x Bx
CASE 14: L = Az x By
CASE 15: L = Az x Bz
Here:
Ax unit vector representing the x-axis of A
Ay unit vector representing the y-axis of A
Az unit vector representing the z-axis of A
Bx unit vector representing the x-axis of B
By unit vector representing the y-axis of B
Bz unit vector representing the z-axis of B
Now you can see the algorithm behind the OBB-OBB intersection test.
Let's jump to the source code:
2D OBB-OBB: http://www.flipcode.com/archives/2D_OBB_Intersection.shtml
3D OBB-OBB: http://www.geometrictools.com/LibMathematics/Intersection/Intersection.html
P.S: This link http://www.realtimerendering.com/intersections.html will be useful to those, who wish to go beyond planes and boxes.

Fastest way to get the set of convex polygons formed by Voronoi line segments

I used Fortune's Algorithm to find the Voronoi diagram of a set of points.
What I get back is a list of line segments, but I need to know which segments form closed polygons, and put them together in an object hashed by the original point they surround.
What might be the fastest way to find these??
Should I save some crucial information from from the algorithm? If so what?
Here is my implementation of fortune's algorithm in Java ported from a C++ implementation here
class Voronoi {
// The set of points that control the centers of the cells
private LinkedList<Point> pts;
// A list of line segments that defines where the cells are divided
private LinkedList<Edge> output;
// The sites that have not yet been processed, in acending order of X coordinate
private PriorityQueue sites;
// Possible upcoming cirlce events in acending order of X coordinate
private PriorityQueue events;
// The root of the binary search tree of the parabolic wave front
private Arc root;
void runFortune(LinkedList pts) {
sites.clear();
events.clear();
output.clear();
root = null;
Point p;
ListIterator i = pts.listIterator(0);
while (i.hasNext()) {
sites.offer(i.next());
}
// Process the queues; select the top element with smaller x coordinate.
while (sites.size() > 0) {
if ((events.size() > 0) && ((((CircleEvent) events.peek()).xpos) <= (((Point) sites.peek()).x))) {
processCircleEvent((CircleEvent) events.poll());
} else {
//process a site event by adding a curve to the parabolic front
frontInsert((Point) sites.poll());
}
}
// After all points are processed, do the remaining circle events.
while (events.size() > 0) {
processCircleEvent((CircleEvent) events.poll());
}
// Clean up dangling edges.
finishEdges();
}
private void processCircleEvent(CircleEvent event) {
if (event.valid) {
//start a new edge
Edge edgy = new Edge(event.p);
// Remove the associated arc from the front.
Arc parc = event.a;
if (parc.prev != null) {
parc.prev.next = parc.next;
parc.prev.edge1 = edgy;
}
if (parc.next != null) {
parc.next.prev = parc.prev;
parc.next.edge0 = edgy;
}
// Finish the edges before and after this arc.
if (parc.edge0 != null) {
parc.edge0.finish(event.p);
}
if (parc.edge1 != null) {
parc.edge1.finish(event.p);
}
// Recheck circle events on either side of p:
if (parc.prev != null) {
checkCircleEvent(parc.prev, event.xpos);
}
if (parc.next != null) {
checkCircleEvent(parc.next, event.xpos);
}
}
}
void frontInsert(Point focus) {
if (root == null) {
root = new Arc(focus);
return;
}
Arc parc = root;
while (parc != null) {
CircleResultPack rez = intersect(focus, parc);
if (rez.valid) {
// New parabola intersects parc. If necessary, duplicate parc.
if (parc.next != null) {
CircleResultPack rezz = intersect(focus, parc.next);
if (!rezz.valid){
Arc bla = new Arc(parc.focus);
bla.prev = parc;
bla.next = parc.next;
parc.next.prev = bla;
parc.next = bla;
}
} else {
parc.next = new Arc(parc.focus);
parc.next.prev = parc;
}
parc.next.edge1 = parc.edge1;
// Add new arc between parc and parc.next.
Arc bla = new Arc(focus);
bla.prev = parc;
bla.next = parc.next;
parc.next.prev = bla;
parc.next = bla;
parc = parc.next; // Now parc points to the new arc.
// Add new half-edges connected to parc's endpoints.
parc.edge0 = new Edge(rez.center);
parc.prev.edge1 = parc.edge0;
parc.edge1 = new Edge(rez.center);
parc.next.edge0 = parc.edge1;
// Check for new circle events around the new arc:
checkCircleEvent(parc, focus.x);
checkCircleEvent(parc.prev, focus.x);
checkCircleEvent(parc.next, focus.x);
return;
}
//proceed to next arc
parc = parc.next;
}
// Special case: If p never intersects an arc, append it to the list.
parc = root;
while (parc.next != null) {
parc = parc.next; // Find the last node.
}
parc.next = new Arc(focus);
parc.next.prev = parc;
Point start = new Point(0, (parc.next.focus.y + parc.focus.y) / 2);
parc.next.edge0 = new Edge(start);
parc.edge1 = parc.next.edge0;
}
void checkCircleEvent(Arc parc, double xpos) {
// Invalidate any old event.
if ((parc.event != null) && (parc.event.xpos != xpos)) {
parc.event.valid = false;
}
parc.event = null;
if ((parc.prev == null) || (parc.next == null)) {
return;
}
CircleResultPack result = circle(parc.prev.focus, parc.focus, parc.next.focus);
if (result.valid && result.rightmostX > xpos) {
// Create new event.
parc.event = new CircleEvent(result.rightmostX, result.center, parc);
events.offer(parc.event);
}
}
// Find the rightmost point on the circle through a,b,c.
CircleResultPack circle(Point a, Point b, Point c) {
CircleResultPack result = new CircleResultPack();
// Check that bc is a "right turn" from ab.
if ((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y) > 0) {
result.valid = false;
return result;
}
// Algorithm from O'Rourke 2ed p. 189.
double A = b.x - a.x;
double B = b.y - a.y;
double C = c.x - a.x;
double D = c.y - a.y;
double E = A * (a.x + b.x) + B * (a.y + b.y);
double F = C * (a.x + c.x) + D * (a.y + c.y);
double G = 2 * (A * (c.y - b.y) - B * (c.x - b.x));
if (G == 0) { // Points are co-linear.
result.valid = false;
return result;
}
// centerpoint of the circle.
Point o = new Point((D * E - B * F) / G, (A * F - C * E) / G);
result.center = o;
// o.x plus radius equals max x coordinate.
result.rightmostX = o.x + Math.sqrt(Math.pow(a.x - o.x, 2.0) + Math.pow(a.y - o.y, 2.0));
result.valid = true;
return result;
}
// Will a new parabola at point p intersect with arc i?
CircleResultPack intersect(Point p, Arc i) {
CircleResultPack res = new CircleResultPack();
res.valid = false;
if (i.focus.x == p.x) {
return res;
}
double a = 0.0;
double b = 0.0;
if (i.prev != null) // Get the intersection of i->prev, i.
{
a = intersection(i.prev.focus, i.focus, p.x).y;
}
if (i.next != null) // Get the intersection of i->next, i.
{
b = intersection(i.focus, i.next.focus, p.x).y;
}
if ((i.prev == null || a <= p.y) && (i.next == null || p.y <= b)) {
res.center = new Point(0, p.y);
// Plug it back into the parabola equation to get the x coordinate
res.center.x = (i.focus.x * i.focus.x + (i.focus.y - res.center.y) * (i.focus.y - res.center.y) - p.x * p.x) / (2 * i.focus.x - 2 * p.x);
res.valid = true;
return res;
}
return res;
}
// Where do two parabolas intersect?
Point intersection(Point p0, Point p1, double l) {
Point res = new Point(0, 0);
Point p = p0;
if (p0.x == p1.x) {
res.y = (p0.y + p1.y) / 2;
} else if (p1.x == l) {
res.y = p1.y;
} else if (p0.x == l) {
res.y = p0.y;
p = p1;
} else {
// Use the quadratic formula.
double z0 = 2 * (p0.x - l);
double z1 = 2 * (p1.x - l);
double a = 1 / z0 - 1 / z1;
double b = -2 * (p0.y / z0 - p1.y / z1);
double c = (p0.y * p0.y + p0.x * p0.x - l * l) / z0 - (p1.y * p1.y + p1.x * p1.x - l * l) / z1;
res.y = (-b - Math.sqrt((b * b - 4 * a * c))) / (2 * a);
}
// Plug back into one of the parabola equations.
res.x = (p.x * p.x + (p.y - res.y) * (p.y - res.y) - l * l) / (2 * p.x - 2 * l);
return res;
}
void finishEdges() {
// Advance the sweep line so no parabolas can cross the bounding box.
double l = gfx.width * 2 + gfx.height;
// Extend each remaining segment to the new parabola intersections.
Arc i = root;
while (i != null) {
if (i.edge1 != null) {
i.edge1.finish(intersection(i.focus, i.next.focus, l * 2));
}
i = i.next;
}
}
class Point implements Comparable<Point> {
public double x, y;
//public Point goal;
public Point(double X, double Y) {
x = X;
y = Y;
}
public int compareTo(Point foo) {
return ((Double) this.x).compareTo((Double) foo.x);
}
}
class CircleEvent implements Comparable<CircleEvent> {
public double xpos;
public Point p;
public Arc a;
public boolean valid;
public CircleEvent(double X, Point P, Arc A) {
xpos = X;
a = A;
p = P;
valid = true;
}
public int compareTo(CircleEvent foo) {
return ((Double) this.xpos).compareTo((Double) foo.xpos);
}
}
class Edge {
public Point start, end;
public boolean done;
public Edge(Point p) {
start = p;
end = new Point(0, 0);
done = false;
output.add(this);
}
public void finish(Point p) {
if (done) {
return;
}
end = p;
done = true;
}
}
class Arc {
//parabolic arc is the set of points eqadistant from a focus point and the beach line
public Point focus;
//these object exsit in a linked list
public Arc next, prev;
//
public CircleEvent event;
//
public Edge edge0, edge1;
public Arc(Point p) {
focus = p;
next = null;
prev = null;
event = null;
edge0 = null;
edge1 = null;
}
}
class CircleResultPack {
// stupid Java doesnt let me return multiple variables without doing this
public boolean valid;
public Point center;
public double rightmostX;
}
}
(I know it wont compile, the data structures need to be initialized, and its missing imports)
What I want is this:
LinkedList<Poly> polys;
//contains all polygons created by Voronoi edges
class Poly {
//defines a single polygon
public Point locus;
public LinkedList<Points> verts;
}
The most immediate brute force way I can think of to do this is to create an undirected graph of the points in the diagram (the endpoints of the edges), with a single entry for each point, and a single connection for each edge between a point (no duplicates) then go find all the loops in this graph, then for each set of loops that share 3 or more points, throw away everything but the shortest loop. However this would be way too slow.
The Voronoi diagram's dual is the Delaunay triangulation. That means each vertex on the Voroni diagram is connected to three edges - meaning each vertex belongs to three regions.
My algorithm to use this would be:
for each vertex in Voronoi Diagram
for each segment next to this point
"walk around the perimeter" (just keep going counter-clockwise)
until you get back to the starting vertex
That should be O(N) as there are only 3 segments for each vertex. You also have to do some bookkeeping to make sure you don't do the same region twice (a simple way is to just keep a bool for each outgoing edge, and as you walk, mark it off), and keeping in mind the point at infinity, but the idea should be enough.

Categories