All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pepsoft.worldpainter.operations.Pencil Maven / Gradle / Ivy

There is a newer version: 2.23.2
Show newest version
/*
 * 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