1
\$\begingroup\$

As a project I have written a graphics library for educational use to allow kids at my high school to make Java games without directly dealing with to the Java graphics API. I'm in the final stages, tweaking mainly for performance and readability, and I would like some input on how I handle transformations.

The idea of the origin mode is to improve usabilty, making the drawn objects transform (and be drawn) about the most logical point, that being the specified origin point.

Demo

My main concern is that every time the origin modification runs, it creates a new AffineTransformation object, which means it quickly eats an unnecessary amount of memory, as it runs every time an object is drawn. Other than that, if you see any issues, or have any stylistic suggestions, they would be appreciated.

The relevant code to the transformation modifications, and the drawing pipeline is as follows. For sanity's sake I have omitted the set-up code and any setters and getters.

private Rectangle2D bounds = new Rectangle2D.Double();
private AffineTransform postTransform = new AffineTransform();

private boolean correct; // these are all user-set in provided setters
private int origin;
protected int plane; // protected because mouse detection needs access
private AffineTransform transform;

private void applyOrigin(Shape shape) {
    applyOrigin(shape.getBounds2D()); 
}

private void applyOrigin(RectangularShape shape) {
    bounds.setRect(shape.getX(), shape.getY(), shape.getWidth(), shape.getHeight());
    applyOrigin();
}

private void applyOrigin() {
    double x = -bounds.getX();
    double y = -bounds.getY();
    double p = (plane == DCanvas.PLANE_DEFAULT)? bounds.getY(): getHeight()-bounds.getY(); // getHeight = get height of drawing surface

    switch (origin) {
    case ORIGIN_LEFT:
    case ORIGIN_CENTER:
    case ORIGIN_RIGHT:
        y -= bounds.getHeight()/2;
        break;
    case ORIGIN_BOTTOM_LEFT:
    case ORIGIN_BOTTOM:
    case ORIGIN_BOTTOM_RIGHT:
        y -= bounds.getHeight();
        break;
    }

    switch (origin) {
    case ORIGIN_TOP:
    case ORIGIN_CENTER:
    case ORIGIN_BOTTOM:
        x -= bounds.getWidth()/2;
        break;
    case ORIGIN_TOP_RIGHT:
    case ORIGIN_RIGHT:
    case ORIGIN_BOTTOM_RIGHT:
        x -= bounds.getWidth();
        break;
    }

    if (plane == DCanvas.PLANE_CARTESIAN) switch (origin) {
    case ORIGIN_TOP_LEFT:
    case ORIGIN_TOP:
    case ORIGIN_TOP_RIGHT:
        y -= bounds.getHeight();
        break;
    case ORIGIN_BOTTOM_LEFT:
    case ORIGIN_BOTTOM:
    case ORIGIN_BOTTOM_RIGHT:
        y += bounds.getHeight();
        break;
    }

    if (correct) {
        x += 0.5;
        y += 0.5;
        if (plane == DCanvas.PLANE_CARTESIAN) y -= 1;
    }

    postTransform.setToTranslation(x, y);
    postTransform.preConcatenate(transform);
    postTransform.preConcatenate(AffineTransform.getTranslateInstance(bounds.getX(), p)); // this is the line I am most concerened with
    graphics.setTransform(postTransform); // graphics object to draw on surface
}

public synchronized void draw(Shape shape) {
    applyOrigin(shape);
    graphics.draw(shape); // graphics object to draw on surface
}
\$\endgroup\$
1
  • \$\begingroup\$ For sanity's sake I have trouble understanding the code because I don't know what values to start from \$\endgroup\$
    – Pimgd
    Commented Mar 23, 2016 at 9:15

1 Answer 1

1
\$\begingroup\$
private int origin;
...
switch (origin) {
case ORIGIN_LEFT:
case ORIGIN_CENTER:
case ORIGIN_RIGHT:
    y -= bounds.getHeight()/2;
    break;
case ORIGIN_BOTTOM_LEFT:
case ORIGIN_BOTTOM:
case ORIGIN_BOTTOM_RIGHT:
    y -= bounds.getHeight();
    break;
}

You should turn origin into an enum for compile time type safety.

double x = -bounds.getX();
double y = -bounds.getY();

switch (origin) {
case ORIGIN_LEFT:
case ORIGIN_CENTER:
case ORIGIN_RIGHT:
    y -= bounds.getHeight()/2;
    break;
case ORIGIN_BOTTOM_LEFT:
case ORIGIN_BOTTOM:
case ORIGIN_BOTTOM_RIGHT:
    y -= bounds.getHeight();
    break;
}

switch (origin) {
case ORIGIN_TOP:
case ORIGIN_CENTER:
case ORIGIN_BOTTOM:
    x -= bounds.getWidth()/2;
    break;
case ORIGIN_TOP_RIGHT:
case ORIGIN_RIGHT:
case ORIGIN_BOTTOM_RIGHT:
    x -= bounds.getWidth();
    break;
}

This entire section of code looks like you could get rid of it if you made proper use of enums.

You could store an offset multiplier in them; Origin.LEFT would have a -0.5 multiplier to height and a 0 multiplier to width, whereas Origin.BOTTOM_RIGHT would have -1 and -1 respectively.

Then you could just write a function applyOffset, which would work like this:

public Point getOriginPoint(Rectangle2D shape){
    double x = (offsetMultiplierX * shape.getWidth()) - shape.getX();
    double y = (offsetMultiplierY * shape.getHeight()) - shape.getY();
    return new Point(x, y);
}

Although maybe it'd be better to just calculate the offset; in that case you could invert y to get the cartesian view.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.