/**
* Copyright 2014
* SMEdit https://github.com/StarMade/SMEdit
* SMTools https://github.com/StarMade/SMTools
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
**/
package jo.sm.ui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.Path2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import jo.sm.data.BlockTypes;
import jo.sm.data.RenderPoly;
import jo.sm.data.RenderSet;
import jo.sm.data.SparseMatrix;
import jo.sm.data.UndoBuffer;
import jo.sm.logic.RenderPolyLogic;
import jo.sm.logic.StarMadeLogic;
import jo.sm.ship.data.Block;
import jo.vecmath.Matrix4f;
import jo.vecmath.Point3f;
import jo.vecmath.Point3i;
import jo.vecmath.Vector3f;
import jo.vecmath.logic.Matrix4fLogic;
import jo.vecmath.logic.Point3iLogic;
@SuppressWarnings("serial")
public class AWTRenderPanel extends RenderPanel {
private static final float PIXEL_TO_RADIANS = (1f / 3.14159f / 16f);
private static final float ROLL_SCALE = 1.1f;
private static final int MOUSE_MODE_NULL = 0;
private static final int MOUSE_MODE_PIVOT = 1;
private static final int MOUSE_MODE_SELECT = 2;
private Point mMouseDownAt;
private int mMouseMode;
private boolean mPlainGraphics;
private boolean mAxis;
private boolean mDontDraw;
private UndoBuffer mUndoer;
private SparseMatrix<Block> mFilteredGrid;
private final RenderSet mTiles;
private final Matrix4f mTransform;
private final Vector3f mPreTranslate;
private float mScale;
private float mRotX;
private float mRotY;
Vector3f mPOVTranslate;
private final Vector3f mPostTranslate;
public AWTRenderPanel() {
mUndoer = new UndoBuffer();
mTiles = new RenderSet();
mTransform = new Matrix4f();
mPreTranslate = new Vector3f();
mPOVTranslate = new Vector3f();
mScale = 1f;
mRotX = (float) Math.PI;
mRotY = 0;
mPostTranslate = new Vector3f();
mPlainGraphics = false;
MouseAdapter ma = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent ev) {
if (ev.getButton() == MouseEvent.BUTTON1) {
doMouseDown(ev.getPoint(), ev.getModifiers());
}
}
@Override
public void mouseReleased(MouseEvent ev) {
if (ev.getButton() == MouseEvent.BUTTON1) {
doMouseUp(ev.getPoint(), ev.getModifiers());
}
}
@Override
public void mouseDragged(MouseEvent ev) {
if (mMouseDownAt != null) {
doMouseMove(ev.getPoint(), ev.getModifiers());
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
doMouseWheel(e.getWheelRotation());
}
};
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(
new RenderPanelKeyEventDispatcher(this));
addMouseListener(ma);
addMouseMotionListener(ma);
addMouseWheelListener(ma);
StarMadeLogic.getInstance().addPropertyChangeListener("model", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
doNewGrid();
}
});
}
@Override
public synchronized void updateTransform() {
Dimension s = getSize();
mPostTranslate.x = s.width / 2;
mPostTranslate.y = s.height / 2;
mTransform.setIdentity();
Matrix4fLogic.translate(mTransform, mPreTranslate);
Matrix4fLogic.rotX(mTransform, mRotX);
Matrix4fLogic.rotY(mTransform, mRotY);
Matrix4fLogic.translate(mTransform, mPOVTranslate);
Matrix4fLogic.scale(mTransform, mScale);
Matrix4fLogic.translate(mTransform, mPostTranslate);
RenderPolyLogic.transformAndSort(mTiles, mTransform);
repaint();
}
private void doMouseDown(Point p, int modifiers) {
mMouseDownAt = p;
//System.out.println("MouseMod="+Integer.toHexString(modifiers));
if ((modifiers & MouseEvent.SHIFT_MASK) != 0) {
RenderPoly tile = getTileAt(p.x, p.y);
if (tile == null) {
return;
}
mMouseMode = MOUSE_MODE_SELECT;
StarMadeLogic.getInstance().setSelectedLower(null);
StarMadeLogic.getInstance().setSelectedUpper(null);
extendSelection(tile);
} else {
mMouseMode = MOUSE_MODE_PIVOT;
}
}
private void doMouseMove(Point p, int modifiers) {
if (mMouseMode == MOUSE_MODE_PIVOT) {
int dx = p.x - mMouseDownAt.x;
int dy = p.y - mMouseDownAt.y;
mMouseDownAt = p;
if (StarMadeLogic.isProperty(StarMadeLogic.INVERT_Y_AXIS)) {
mRotX -= dy * PIXEL_TO_RADIANS;
} else {
mRotX += dy * PIXEL_TO_RADIANS;
}
if (StarMadeLogic.isProperty(StarMadeLogic.INVERT_X_AXIS)) {
mRotY -= dx * PIXEL_TO_RADIANS;
} else {
mRotY += dx * PIXEL_TO_RADIANS;
}
updateTransform();
} else if (mMouseMode == MOUSE_MODE_SELECT) {
RenderPoly tile = getTileAt(p.x, p.y);
if (tile != null) {
extendSelection(tile);
}
}
}
private void doMouseUp(Point p, int modifiers) {
if (mMouseMode == MOUSE_MODE_PIVOT) {
doMouseMove(p, modifiers);
mMouseDownAt = null;
} else if (mMouseMode == MOUSE_MODE_PIVOT) {
doMouseMove(p, modifiers);
}
mMouseMode = MOUSE_MODE_NULL;
}
private void doMouseWheel(int roll) {
if (roll > 0) {
while (roll-- > 0) {
mScale /= ROLL_SCALE;
}
} else if (roll < 0) {
while (roll++ < 0) {
mScale *= ROLL_SCALE;
}
}
updateTransform();
}
private void extendSelection(RenderPoly tile) {
Point3i lowest = new Point3i();
Point3i highest = new Point3i();
RenderPolyLogic.getBounds(tile, lowest, highest);
if (highest.x > lowest.x) {
highest.x--;
}
if (highest.y > lowest.y) {
highest.y--;
}
if (highest.z > lowest.z) {
highest.z--;
}
Point3i lower = StarMadeLogic.getInstance().getSelectedLower();
lower = Point3iLogic.min(lower, lowest);
StarMadeLogic.getInstance().setSelectedLower(lower);
Point3i upper = StarMadeLogic.getInstance().getSelectedUpper();
upper = Point3iLogic.max(upper, highest);
StarMadeLogic.getInstance().setSelectedUpper(upper);
updateTiles();
}
@Override
public void paint(Graphics g) {
if (mTiles == null) {
return;
}
Dimension s = getSize();
g.setColor(Color.black);
g.fillRect(0, 0, s.width, s.height);
Graphics2D g2 = (Graphics2D) g;
RenderPolyLogic.draw(g2, mTiles, !mPlainGraphics);
}
private void doNewGrid() {
Point3i lower = new Point3i();
Point3i upper = new Point3i();
StarMadeLogic.getModel().getBounds(lower, upper);
mPreTranslate.x = -(lower.x + upper.x) / 2;
mPreTranslate.y = -(lower.y + upper.y) / 2;
mPreTranslate.z = -(lower.z + upper.z) / 2;
float maxModel = Math.max(Math.max(upper.x - lower.x, upper.y - lower.y), upper.z - lower.z);
Dimension s = getSize();
float maxScreen = Math.max(s.width, s.height);
mScale = maxScreen / maxModel / 2f;
//System.out.println("Scale="+mScale+", preTrans="+mPreTranslate);
//mTransform.setTranslation(new Vector3f(s.width/2f, s.height/2f, 0));
updateTiles();
}
@Override
public void updateTiles() {
if (mDontDraw) {
mFilteredGrid = new SparseMatrix<>();
} else {
if (StarMadeLogic.getInstance().getViewFilter() == null) {
mFilteredGrid = StarMadeLogic.getModel();
} else {
mFilteredGrid = StarMadeLogic.getInstance().getViewFilter().modify(StarMadeLogic.getModel(), null, StarMadeLogic.getInstance(), null);
}
}
RenderPolyLogic.fillPolys(mFilteredGrid, mTiles);
Point3i lower = StarMadeLogic.getInstance().getSelectedLower();
Point3i upper = StarMadeLogic.getInstance().getSelectedUpper();
if ((lower != null) && (upper != null)) {
addBox(lower, upper, new short[]{BlockTypes.SPECIAL_SELECT_XP, BlockTypes.SPECIAL_SELECT_XM,
BlockTypes.SPECIAL_SELECT_YP, BlockTypes.SPECIAL_SELECT_YM,
BlockTypes.SPECIAL_SELECT_ZP, BlockTypes.SPECIAL_SELECT_ZM,});
}
if (mAxis) {
lower = new Point3i();
upper = new Point3i();
mFilteredGrid.getBounds(lower, upper);
int r = Math.max(256, Math.abs(lower.x));
r = Math.max(r, Math.abs(lower.y));
r = Math.max(r, Math.abs(lower.z));
r = Math.max(r, Math.abs(upper.x));
r = Math.max(r, Math.abs(upper.y));
r = Math.max(r, Math.abs(upper.z));
r += 16;
addBox(new Point3i(9, 8, 8), new Point3i(r + 8, 8, 8), new short[]{BlockTypes.SPECIAL_SELECT_XP});
addBox(new Point3i(8 - r, 8, 8), new Point3i(7, 8, 8), new short[]{BlockTypes.SPECIAL_SELECT_XM});
addBox(new Point3i(8, 9, 8), new Point3i(8, r + 8, 8), new short[]{BlockTypes.SPECIAL_SELECT_YP});
addBox(new Point3i(8, 8 - r, 8), new Point3i(8, 7, 8), new short[]{BlockTypes.SPECIAL_SELECT_YM});
addBox(new Point3i(8, 8, 9), new Point3i(8, 8, r + 8), new short[]{BlockTypes.SPECIAL_SELECT_ZP});
addBox(new Point3i(8, 8, 8 - r), new Point3i(8, 8, 7), new short[]{BlockTypes.SPECIAL_SELECT_ZM});
}
updateTransform();
}
private void addBox(Point3i lower, Point3i upper, short[] colors) {
if ((lower == null) || (upper == null)) {
return;
}
upper = new Point3i(upper.x + 1, upper.y + 1, upper.z + 1); // only place where bounds are at +1
addSelectFace(upper.x, lower.y, lower.z, upper.x, upper.y, upper.z,
RenderPoly.XP, colors[0 % colors.length]);
addSelectFace(lower.x, lower.y, lower.z, lower.x, upper.y, upper.z,
RenderPoly.XM, colors[1 % colors.length]);
addSelectFace(lower.x, upper.y, lower.z, upper.x, upper.y, upper.z,
RenderPoly.YP, colors[2 % colors.length]);
addSelectFace(lower.x, lower.y, lower.z, upper.x, lower.y, upper.z,
RenderPoly.YM, colors[3 % colors.length]);
addSelectFace(lower.x, lower.y, upper.z, upper.x, upper.y, upper.z,
RenderPoly.ZP, colors[4 % colors.length]);
addSelectFace(lower.x, lower.y, lower.z, upper.x, upper.y, lower.z,
RenderPoly.ZM, colors[5 % colors.length]);
}
private void addSelectFace(int x1, int y1, int z1, int x2, int y2, int z2,
int face, short type) {
if (x1 == x2) {
for (int y = y1; y < y2; y++) {
for (int z = z1; z < z2; z++) {
addSelectTile(x1, y, z, x2, y + 1, z + 1, face, type);
}
}
} else if (y1 == y2) {
for (int x = x1; x < x2; x++) {
for (int z = z1; z < z2; z++) {
addSelectTile(x, y1, z, x + 1, y2, z + 1, face, type);
}
}
} else if (z1 == z2) {
for (int x = x1; x < x2; x++) {
for (int y = y1; y < y2; y++) {
addSelectTile(x, y, z1, x + 1, y + 1, z2, face, type);
}
}
}
}
private void addSelectTile(int x1, int y1, int z1, int x2, int y2, int z2,
int face, short type) {
RenderPoly tile = new RenderPoly();
if (x1 == x2) {
tile.setModelPoints(new Point3i[]{
new Point3i(x1, y1, z1),
new Point3i(x1, y1, z2),
new Point3i(x1, y2, z2),
new Point3i(x1, y2, z1),});
} else if (y1 == y2) {
tile.setModelPoints(new Point3i[]{
new Point3i(x1, y1, z1),
new Point3i(x1, y1, z2),
new Point3i(x2, y1, z2),
new Point3i(x2, y1, z1),});
} else if (z1 == z2) {
tile.setModelPoints(new Point3i[]{
new Point3i(x1, y1, z1),
new Point3i(x1, y2, z1),
new Point3i(x2, y2, z1),
new Point3i(x2, y1, z1),});
}
tile.setNormal(face);
tile.setType(RenderPoly.SQUARE);
tile.setBlock(new Block(type));
mTiles.getAllPolys().add(tile);
}
@Override
public RenderPoly getTileAt(double x, double y) {
for (int i = mTiles.getVisiblePolys().size() - 1; i >= 0; i--) {
RenderPoly tile = mTiles.getVisiblePolys().get(i);
Point3f[] corners = RenderPolyLogic.getCorners(tile, mTiles);
Path2D p = new Path2D.Float();
p.moveTo(corners[0].x, corners[0].y);
p.lineTo(corners[1].x, corners[1].y);
p.lineTo(corners[2].x, corners[2].y);
p.lineTo(corners[3].x, corners[3].y);
p.lineTo(corners[0].x, corners[0].y);
if (p.contains(x, y)) {
return tile;
}
}
return null;
}
@Override
public Block getBlockAt(double x, double y) {
RenderPoly tile = getTileAt(x, y);
if (tile != null) {
return tile.getBlock();
}
return null;
}
@Override
public boolean isPlainGraphics() {
return mPlainGraphics;
}
@Override
public void setPlainGraphics(boolean plainGraphics) {
mPlainGraphics = plainGraphics;
repaint();
}
@Override
public boolean isAxis() {
return mAxis;
}
@Override
public void setAxis(boolean axis) {
mAxis = axis;
updateTiles();
}
@Override
public UndoBuffer getUndoer() {
return mUndoer;
}
@Override
public void setUndoer(UndoBuffer undoer) {
mUndoer = undoer;
}
@Override
public void undo() {
SparseMatrix<Block> grid = mUndoer.undo();
if (grid != null) {
StarMadeLogic.setModel(grid);
}
}
@Override
public void redo() {
SparseMatrix<Block> grid = mUndoer.redo();
if (grid != null) {
StarMadeLogic.setModel(grid);
}
}
@Override
public void setCloseRequested(boolean pleaseClose) {
// ignore
}
@Override
public boolean isDontDraw() {
return mDontDraw;
}
@Override
public void setDontDraw(boolean dontDraw) {
mDontDraw = dontDraw;
updateTiles();
}
}