org.pepsoft.worldpainter.operations.Pencil Maven / Gradle / Ivy
/*
* WorldPainter, a graphical and interactive map generator for Minecraft.
* Copyright © 2011-2015 pepsoft.org, The Netherlands
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.pepsoft.worldpainter.operations;
import org.pepsoft.worldpainter.Dimension;
import org.pepsoft.worldpainter.MapDragControl;
import org.pepsoft.worldpainter.RadiusControl;
import org.pepsoft.worldpainter.WorldPainterView;
import org.pepsoft.worldpainter.painting.DimensionPainter;
import org.pepsoft.worldpainter.painting.Paint;
import javax.swing.*;
import java.awt.*;
/**
* Draw dots and straight or freehand lines with any terrain or layer.
*
* Created by pepijn on 15-05-15.
*/
public class Pencil extends AbstractPaintOperation {
public Pencil(WorldPainterView view, RadiusControl radiusControl, MapDragControl mapDragControl) {
super("Pencil", "Draw dots and straight or freehand lines with any terrain or layer", view, radiusControl, mapDragControl, 100, "operation.pencil");
}
@Override
protected void tick(int centreX, int centreY, boolean undo, boolean first, float dynamicLevel) {
final Dimension dimension = getDimension();
if (dimension == null) {
// Probably some kind of race condition
return;
}
dimension.setEventsInhibited(true);
try {
painter.setUndo(undo);
if (first) {
// Either a single click, or the start of a drag
lockedX = centreX;
lockedY = centreY;
lockedAxis = null;
if (isShiftDown()) {
// Shift was pressed: draw a line from the last location
if ((previousX != Integer.MIN_VALUE) && (previousY != Integer.MIN_VALUE)) {
if (isCtrlDown()) {
// Ctrl was also pressed: snap the line to 45 degree
// angles
int[] snappedCoords = snapCoords(previousX, previousY, centreX, centreY, getAxis(previousX, previousY, centreX, centreY));
centreX = snappedCoords[0];
centreY = snappedCoords[1];
}
painter.drawLine(dimension, previousX, previousY, centreX, centreY);
}
inhibitDrag = true;
} else {
// Shift was not pressed: just draw a single dot
painter.drawPoint(dimension, centreX, centreY);
inhibitDrag = false;
}
previousX = centreX;
previousY = centreY;
} else if (! inhibitDrag) {
// Continuation of a drag
if (isCtrlDown()) {
// Ctrl was pressed: snap the line to 45 degree angles
// relative to the point where the drag was started
if (lockedAxis == null) {
lockedAxis = getAxis(lockedX, lockedY, centreX, centreY);
}
int[] snappedCoords = snapCoords(lockedX, lockedY, centreX, centreY, lockedAxis);
centreX = snappedCoords[0];
centreY = snappedCoords[1];
}
if ((centreX != previousX) || (centreY != previousY)) {
if ((Math.abs(centreX - previousX) <= 1) && (Math.abs(centreY - previousY) <= 1)) {
painter.drawPoint(dimension, centreX, centreY);
} else {
painter.drawLine(dimension, previousX, previousY, centreX, centreY);
}
previousX = centreX;
previousY = centreY;
}
}
} finally {
dimension.setEventsInhibited(false);
}
}
@Override
protected void paintChanged(Paint newPaint) {
newPaint.setDither(false);
painter.setPaint(getPaint());
}
private Axis getAxis(int x1, int y1, int x2, int y2) {
if ((x1 == x2) && (y1 == y2)) {
return null;
}
double angle = Math.atan((double) (y2 - y1) / (x2 - x1));
if (x2 < x1) {
angle += Math.PI;
} else if (angle < 0) {
angle += Math.PI * 2;
}
switch ((int) Math.round(angle * 4 / Math.PI)) {
case 0:
case 4:
case 8:
return Axis.W_E;
case 1:
case 5:
return Axis.NW_SE;
case 2:
case 6:
return Axis.N_S;
case 3:
case 7:
return Axis.NE_SW;
default:
throw new InternalError();
}
}
/**
* Snap a point to the closest point on a line defined by another point and an axis.
*
* @param x1 The X coordinate of the point defining the line.
* @param y1 THe Y coordinate of the point defining the line.
* @param x2 The X coordinate of the point to snap to the line.
* @param y2 The Y coordinate of the point to snap to the line.
* @param axis The axis through {@code x1,y2} defining the line to snap to.
* @return An array containing the coordinates of the closest point on the specified line to {@code x2,y2}.
* Index 0 contains the x coordinate and index 1 the y coordinate.
*/
private int[] snapCoords(int x1, int y1, int x2, int y2, Axis axis) {
if (axis == null) {
return new int[] {x2, y2};
}
switch (axis) {
case W_E:
return new int[] {x2, y1};
case NW_SE:
Point closestPoint = closestPoint(new Point(x1, y1), new Point(x1 + 1000, y1 + 1000), new Point(x2, y2));
return new int[] {closestPoint.x, closestPoint.y};
case N_S:
return new int[] {x1, y2};
case NE_SW:
closestPoint = closestPoint(new Point(x1, y1), new Point(x1 + 1000, y1 - 1000), new Point(x2, y2));
return new int[] {closestPoint.x, closestPoint.y};
default:
throw new IllegalArgumentException();
}
}
/**
* Returns the closest point on the infinite line through p1 and p2 to
* p3.
*
* @param p1 First point of the line
* @param p2 Second point of the line
* @param p3 Point to which we want to find the closest point on
* the line defined by p1,p2
* @return The closest point on the line through p1 and p2 to p3
*/
private Point closestPoint(Point p1, Point p2, Point p3) {
final double xDelta = p2.getX() - p1.getX();
final double yDelta = p2.getY() - p1.getY();
if ((xDelta == 0) && (yDelta == 0)) {
throw new IllegalArgumentException("p1 and p2 cannot be the same point");
}
final double u = ((p3.getX() - p1.getX()) * xDelta + (p3.getY() - p1.getY()) * yDelta) / (xDelta * xDelta + yDelta * yDelta);
return new Point((int) Math.round(p1.getX() + u * xDelta), (int) Math.round(p1.getY() + u * yDelta));
}
@Override
public JPanel getOptionsPanel() {
return OPTIONS_PANEL;
}
private final DimensionPainter painter = new DimensionPainter();
private int previousX = Integer.MIN_VALUE, previousY = Integer.MIN_VALUE, lockedX = Integer.MIN_VALUE, lockedY = Integer.MIN_VALUE;
private Axis lockedAxis;
private boolean inhibitDrag;
enum Axis {W_E, NW_SE, N_S, NE_SW}
private static final JPanel OPTIONS_PANEL = new StandardOptionsPanel("Pencil", "With the left mouse button do the following to apply the currently selected paint:\n" +
"
- Drag for freeform lines\n" +
"
- Click for dots\n" +
"
- Shift+click for straight lines from last dot or end of last line\n" +
"
- Hold Ctrl to constrain to 45 degree angles
\n" +
"Use the right mouse button instead, to:\n" +
"
- With a Layer selected: remove the layer\n" +
"
- With a Terrain selected: reset to the current theme\n" +
"
- With a Biome selected: reset to Auto Biome" +
"
");
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy