From: Svjatoslav Agejenko Date: Mon, 11 Jul 2022 19:14:30 +0000 (+0300) Subject: Code refactoring. X-Git-Url: http://www2.svjatoslav.eu/gitweb/?p=sixth-3d-demos.git;a=commitdiff_plain;h=08719db537fae3645ca86f9ee6f8deba4dadf4f4 Code refactoring. --- diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java new file mode 100644 index 0000000..2b3b643 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java @@ -0,0 +1,120 @@ +/* + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * This project is released under Creative Commons Zero (CC0) license. + * +*/ + +package eu.svjatoslav.sixth.e3d.examples.launcher; + +import eu.svjatoslav.sixth.e3d.examples.*; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +class ApplicationListPanel extends JPanel { + private static final long serialVersionUID = 2012721856427052560L; + + ApplicationListPanel() { + final GroupLayout groupLayout = new GroupLayout(this); + GroupLayout.SequentialGroup sequentialGroup = groupLayout.createSequentialGroup(); + sequentialGroup.addComponent(new JLabel("Choose an example to launch:")); + sequentialGroup.addComponent(new JButton(new ShowOctree())); + sequentialGroup.addComponent(new JButton(new ShowMathGraphs())); + sequentialGroup.addComponent(new JButton(new ShowPointCloud())); + sequentialGroup.addComponent(new JButton(new ShowRain())); + sequentialGroup.addComponent(new JButton(new ShowTextEditors())); + sequentialGroup.addComponent(new JButton(new ShowTextEditors2())); + sequentialGroup.addComponent(new JButton(new ShowGameOfLife())); + sequentialGroup.addComponent(new JButton(new ShowRandomPolygons())); + } + + private static class ShowTextEditors extends AbstractAction { + ShowTextEditors() { + putValue(NAME, "Text editors"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + TextEditorDemo.main(null); + } + } + + private static class ShowTextEditors2 extends AbstractAction { + ShowTextEditors2() { + putValue(NAME, "Text editors city"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + TextEditorDemo2.main(null); + } + } + + + private static class ShowRain extends AbstractAction { + ShowRain() { + putValue(NAME, "Raining numbers"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + RainingNumbersDemo.main(null); + } + } + + private static class ShowPointCloud extends AbstractAction { + ShowPointCloud() { + putValue(NAME, "Point cloud galaxy"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + PointCloudDemo.main(null); + } + } + + private static class ShowMathGraphs extends AbstractAction { + ShowMathGraphs() { + putValue(NAME, "Mathematical graphs"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + GraphDemo.main(null); + } + } + + private static class ShowRandomPolygons extends AbstractAction { + ShowRandomPolygons() { + putValue(NAME, "Random polygons"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + RandomPolygonsDemo.main(null); + } + } + + private static class ShowOctree extends AbstractAction { + ShowOctree() { + putValue(NAME, "Volumetric Octree"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + OctreeDemo.main(null); + } + } + + private static class ShowGameOfLife extends AbstractAction { + ShowGameOfLife() { + putValue(NAME, "Game of Life"); + } + + @Override + public void actionPerformed(final ActionEvent e) { + eu.svjatoslav.sixth.e3d.examples.life_demo.Main.main(null); + } + } + +} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/Main.java index 4d090f8..07e5c39 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/Main.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/Main.java @@ -9,29 +9,24 @@ package eu.svjatoslav.sixth.e3d.examples.launcher; import javax.swing.*; import java.awt.*; -class Main extends javax.swing.JFrame { - - private static final long serialVersionUID = -3679656169594556137L; - - private Main() { - super(); - initGUI(); - } +class Main { public static void main(final String[] args) { - SwingUtilities.invokeLater(() -> { - final Main inst = new Main(); - final BorderLayout instLayout = new BorderLayout(); - inst.setLocationRelativeTo(null); - inst.setVisible(true); - inst.getContentPane().setLayout(instLayout); - }); + buildAndShowGuiWindow(); } - private void initGUI() { - getContentPane().add(new MenuPanel()); - pack(); - setSize(390, 300); + private static void buildAndShowGuiWindow() { + JFrame frame = new JFrame("Sixth 3D engine demos"); + + // Keep application running until last frame is closed. + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(new ApplicationListPanel(), BorderLayout.CENTER); + frame.setSize(400, 300); + + frame.setLocationRelativeTo(null); // center frame on screen + frame.setVisible(true); } } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/MenuPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/MenuPanel.java deleted file mode 100644 index e620649..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/MenuPanel.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Sixth 3D engine demos. Author: Svjatoslav Agejenko. - * This project is released under Creative Commons Zero (CC0) license. - * -*/ - -package eu.svjatoslav.sixth.e3d.examples.launcher; - -import eu.svjatoslav.sixth.e3d.examples.*; - -import javax.swing.*; -import java.awt.event.ActionEvent; - -class MenuPanel extends JPanel { - private static final long serialVersionUID = 2012721856427052560L; - - MenuPanel() { - final GroupLayout groupLayout = new GroupLayout(this); - GroupLayout.SequentialGroup sequentialGroup = groupLayout.createSequentialGroup(); - sequentialGroup.addComponent(new JLabel("Choose an example to launch:")); - sequentialGroup.addComponent(new JButton(new ShowOctree())); - sequentialGroup.addComponent(new JButton(new ShowMathGraphs())); - sequentialGroup.addComponent(new JButton(new ShowPointCloud())); - sequentialGroup.addComponent(new JButton(new ShowRain())); - sequentialGroup.addComponent(new JButton(new ShowTextEditors())); - sequentialGroup.addComponent(new JButton(new ShowTextEditors2())); - sequentialGroup.addComponent(new JButton(new ShowGameOfLife())); - sequentialGroup.addComponent(new JButton(new ShowRandomPolygons())); - } - - private static class ShowTextEditors extends AbstractAction { - ShowTextEditors() { - putValue(NAME, "Text editors"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - TextEditorDemo.main(null); - } - } - - private static class ShowTextEditors2 extends AbstractAction { - ShowTextEditors2() { - putValue(NAME, "Text editors city"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - TextEditorDemo2.main(null); - } - } - - - private static class ShowRain extends AbstractAction { - ShowRain() { - putValue(NAME, "Raining numbers"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - RainingNumbersDemo.main(null); - } - } - - private static class ShowPointCloud extends AbstractAction { - ShowPointCloud() { - putValue(NAME, "Pointcloud galaxy"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - PointCloudDemo.main(null); - } - } - - private static class ShowMathGraphs extends AbstractAction { - ShowMathGraphs() { - putValue(NAME, "Mathematical graphs"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - GraphDemo.main(null); - } - } - - private static class ShowRandomPolygons extends AbstractAction { - ShowRandomPolygons() { - putValue(NAME, "Random polygons"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - RandomPolygonsDemo.main(null); - } - } - - private static class ShowOctree extends AbstractAction { - ShowOctree() { - putValue(NAME, "Volumetric Octree"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - OctreeDemo.main(null); - } - } - - private static class ShowGameOfLife extends AbstractAction { - ShowGameOfLife() { - putValue(NAME, "Game of Life"); - } - - @Override - public void actionPerformed(final ActionEvent e) { - eu.svjatoslav.sixth.e3d.examples.life.Main.main(null); - } - } - -} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Cell.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Cell.java deleted file mode 100755 index fb78e4b..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Cell.java +++ /dev/null @@ -1,131 +0,0 @@ -package eu.svjatoslav.sixth.e3d.examples.life; - -import eu.svjatoslav.sixth.e3d.geometry.Point3D; -import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController; -import eu.svjatoslav.sixth.e3d.renderer.raster.Color; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape; - -/** - * This class corresponds to a single cell within matrix. - */ -class Cell extends AbstractCompositeShape implements - MouseInteractionController { - - /** - * cell visual size - */ - static final int SIZE = 20; - /** - * Color of the active cell (R, G, B, A) - */ - private static final Color ACTIVE_COLOR = new Color("A8FF"); - /** - * Color of the active cell (R, G, B, A) while mouse is over it. - */ - private static final Color ACTIVE_COLOR_MOUSE_OVER = new Color("F9FF"); - /** - * Color of the inactive cell (R, G, B, A) - */ - private static final Color INACTIVE_COLOR = new Color("55F8"); - /** - * Color of the inactive cell (R, G, B, A) while mouse is over it. - */ - private static final Color INACTIVE_COLOR_MOUSE_OVER = new Color("77F8"); - /** - * A placeholder variable to help in next generation computation. Indicates - * whether cell is going to survive within next generation. - */ - public boolean survives; - /** - * Indicates whether cell is currently active - */ - private boolean active; - /** - * Indicates whether mouse pointer is currently over this cell. - */ - private boolean isMouseOver = false; - - public Cell(final Point3D center) { - super(center); - - createCellShape(); - - // enable receiving of mouse events - setMouseInteractionController(this); - } - - private void createCellShape() { - final double halfSize = SIZE / 2f; - - // define 4 points corresponding to cell borders - final Point3D p1 = new Point3D(-halfSize, 0, -halfSize); - - final Point3D p2 = new Point3D(+halfSize, 0, -halfSize); - - final Point3D p3 = new Point3D(+halfSize, 0, +halfSize); - - final Point3D p4 = new Point3D(-halfSize, 0, +halfSize); - - // connect 4 points with 2 polygons - addShape(new SolidPolygon(p1, p2, p3, computeCellColor())); - addShape(new SolidPolygon(p1, p4, p3, computeCellColor())); - } - - /** - * Compute cell color depending if cell is active and if mouse is over the - * cell. - */ - private Color computeCellColor() { - if (active) - if (isMouseOver) - return ACTIVE_COLOR_MOUSE_OVER; - else - return ACTIVE_COLOR; - else if (isMouseOver) - return INACTIVE_COLOR_MOUSE_OVER; - else - return INACTIVE_COLOR; - } - - public boolean isActive() { - return active; - } - - public void setActive(final boolean active) { - this.active = active; - updateColor(); - } - - @Override - public boolean mouseClicked() { - setActive(!isActive()); - return true; - } - - @Override - public boolean mouseEntered() { - setMouseOver(true); - return true; - } - - @Override - public boolean mouseExited() { - setMouseOver(false); - return true; - } - - private void setMouseOver(final boolean isMouseOver) { - this.isMouseOver = isMouseOver; - updateColor(); - } - - /** - * This method is called when cell status is changed to update its color - * too. - */ - private void updateColor() { - setColor(computeCellColor()); - } - -} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Main.java deleted file mode 100644 index afc0e1c..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Main.java +++ /dev/null @@ -1,108 +0,0 @@ -package eu.svjatoslav.sixth.e3d.examples.life; - -import eu.svjatoslav.sixth.e3d.geometry.Point3D; -import eu.svjatoslav.sixth.e3d.geometry.Rectangle; -import eu.svjatoslav.sixth.e3d.gui.Avatar; -import eu.svjatoslav.sixth.e3d.gui.ViewFrame; -import eu.svjatoslav.sixth.e3d.gui.ViewPanel; -import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker; -import eu.svjatoslav.sixth.e3d.math.Transform; -import eu.svjatoslav.sixth.e3d.renderer.raster.Color; -import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D; - -import java.awt.event.KeyEvent; - - -public class Main extends WorldNavigationUserInputTracker { - - private static final Matrix MATRIX = new Matrix( - new Point3D() // position matrix in the center of the scene - ); - - public static void main(final String[] args) { - new Main().run(); - } - - /** - * Handle keyboard input. - */ - @Override - public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) { - switch (event.getKeyChar()) { - case ' ': // space key - MATRIX.evolve(false); - break; - case 10: // ENTER - MATRIX.evolve(true); - break; - case 'c': // reset matrix - MATRIX.clear(); - break; - default: - return super.keyPressed(event, viewPanel); - } - return true; - } - - private void run() { - - // create application frame visible to the user - final ViewFrame viewFrame = new ViewFrame(); - - final ShapeCollection shapeCollection = viewFrame.getViewPanel() - .getRootShapeCollection(); - - // add matrix - shapeCollection.addShape(MATRIX); - - // add wire-frame grid (optional) - shapeCollection.addShape(createGrid()); - - final ViewPanel viewPanel = viewFrame.getViewPanel(); - - setAvatarOrientation(viewPanel.getAvatar()); - - // enable receiving of keyboard events - viewPanel.getKeyboardFocusStack().pushFocusOwner(this); - - // Done! World is built. So ensure screen is updated too. - viewPanel.repaintDuringNextViewUpdate(); - } - - /** - * Create pink wire-frame grid below (for decorative purposes). - */ - private Grid2D createGrid() { - return new Grid2D( - new Transform( - new Point3D( // Grid positioning: - 0, // center - 100, // below the main scene - 0), // center - - // Grid orientation: - 0, // no rotation along XZ axis - Math.PI / 2), // face down - - new Rectangle(800), // large enough, square grid - - 5, 5, // grid will be divided to 5x5 segments - - new LineAppearance(3, // line thickness - new Color("FF000050") // red and quite transparent - ) - ); - } - - /** - * Set Avatar/Camera initial position in the world. - */ - private void setAvatarOrientation(final Avatar avatar) { - avatar.setLocation(new Point3D(100, -50, -200)); - avatar.setAngleXZ(0.2f); - avatar.setAngleYZ(-0.7f); - } - -} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Matrix.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Matrix.java deleted file mode 100644 index ee7b0aa..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Matrix.java +++ /dev/null @@ -1,156 +0,0 @@ -package eu.svjatoslav.sixth.e3d.examples.life; - -import eu.svjatoslav.sixth.e3d.geometry.Point3D; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.SubShape; - -/** - * This is our 2D game of life world. It contains 2D array of {@link Cell}'s. - */ -class Matrix extends AbstractCompositeShape { - - /** - * Empty space between cells. - */ - private static final int BORDER = 5; - - /** - * Matrix size X and Y. (Amount of cells.) - */ - private static final int SIZE = 30; - - /** - * Object marker in 3D scene. Allows to locate all stars from the scene. - */ - private static final String GROUP_STARS = "stars"; - - /** - * Object marker in 3D scene. Allows to locate surface on the scene. - */ - private static final String GROUP_SURFACE = "surface"; - - /** - * 2 dimensional matrix of cells - */ - private final Cell[][] cells = new Cell[SIZE][]; - - public Matrix(final Point3D location) { - super(location); - - - for (int x = 0; x < SIZE; x++) { - cells[x] = new Cell[SIZE]; - - // init Y row - for (int z = 0; z < SIZE; z++) { - // create cell and register it - final Cell cell = new Cell(getCellLocation(x, z)); - cells[x][z] = cell; - addShape(cell); - } - } - - setGroupForUngrouped(GROUP_SURFACE); - } - - /** - * Clear matrix. - */ - public void clear() { - - // mark every cell as inactive - for (int x = 0; x < SIZE; x++) - for (int y = 0; y < SIZE; y++) - cells[x][y].setActive(false); - - // remove history stars - removeGroup(GROUP_STARS); - } - - /** - * Compute survived cells based on the rules of Conway's Game of Life. - */ - private void computeSurvivedCells() { - - for (int y = 0; y < SIZE; y++) - for (int x = 0; x < SIZE; x++) - processCell(x, y); - - for (int y = 0; y < SIZE; y++) - for (int x = 0; x < SIZE; x++) - cells[x][y].setActive(cells[x][y].survives); - - } - - private void processCell(int x, int y) { - int aliveNeighbours = countNeighbours(x, y); - - if (cells[x][y].isActive()) { - cells[x][y].survives = ((aliveNeighbours == 2) || (aliveNeighbours == 3)); - } else { - cells[x][y].survives = aliveNeighbours == 3; - } - } - - private int countNeighbours(int x, int y) { - int result = 0; - for (int ny = y - 1; ny <= y + 1; ny++) - for (int nx = x - 1; nx <= x + 1; nx++) - if (isCellAlive(nx, ny)) result++; - - if (isCellAlive(x, y)) result--; - - return result; - } - - private boolean isCellAlive(int x, int y) { - if (x < 0) return false; - if (x >= SIZE) return false; - if (y < 0) return false; - if (y >= SIZE) return false; - return cells[x][y].isActive(); - } - - /** - * Evolve matrix for one iteration - */ - public void evolve(final boolean preserveHistory) { - if (preserveHistory) - markActiveCells(); - - shiftStarsUp(); - - computeSurvivedCells(); - } - - private Point3D getCellLocation(final int x, final int z) { - final int shift = -((SIZE / 2) * (Cell.SIZE + BORDER)); - - return new Point3D( - (x * (Cell.SIZE + BORDER)) + shift, - 0, - (z * (Cell.SIZE + BORDER)) + shift); - } - - /** - * Leave trail of active cells as stars above matrix. - */ - private void markActiveCells() { - // mark survived cells - for (int x = 0; x < SIZE; x++) - for (int y = 0; y < SIZE; y++) - if (cells[x][y].isActive()) - addShape(new Star(getCellLocation(x, y))); - - setGroupForUngrouped(GROUP_STARS); - } - - /** - * Find all history tracking stars and shift them up. - */ - private void shiftStarsUp() { - - for (final SubShape subShape : getGroup(GROUP_STARS)) - ((Star) subShape.getShape()).getLocation().translateY(-10); - } -} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Star.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Star.java deleted file mode 100644 index ad242d0..0000000 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life/Star.java +++ /dev/null @@ -1,41 +0,0 @@ -package eu.svjatoslav.sixth.e3d.examples.life; - -import eu.svjatoslav.sixth.e3d.geometry.Point3D; -import eu.svjatoslav.sixth.e3d.renderer.raster.Color; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint; - -import java.util.ArrayList; -import java.util.List; - -class Star extends GlowingPoint { - public static final int STAR_SIZE = 10; - - public static final int UNIQUE_STARS_COUNT = 30; - - private static final List uniqueStarColors = new ArrayList<>(); - - /** - * A little hack to save RAM. We are going to have potentially lot of stars. - * Instead of creating new individual texture for each star, Sixth 3D engine - * uses internal optimization and reuses existing star textures, if star with - * identical color already exists. To take advantage ot such optimization - * we create here limited set of precomputed star colors and later reuse them. - */ - static { - for (int i = 0; i < UNIQUE_STARS_COUNT; i++) - uniqueStarColors.add( - new Color( - Math.random() + 0.5, - Math.random() + 0.5, - Math.random() + 0.5, - 255)); - } - - public Star(Point3D location) { - super(location, - STAR_SIZE, - uniqueStarColors.get((int) (Math.random() * uniqueStarColors.size())) // pick random pre-generated color - ); - } - -} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java new file mode 100755 index 0000000..6b1ee8e --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java @@ -0,0 +1,131 @@ +package eu.svjatoslav.sixth.e3d.examples.life_demo; + +import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController; +import eu.svjatoslav.sixth.e3d.renderer.raster.Color; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape; + +/** + * This class corresponds to a single cell within matrix. + */ +class Cell extends AbstractCompositeShape implements + MouseInteractionController { + + /** + * cell visual size + */ + static final int SIZE = 20; + /** + * Color of the active cell (R, G, B, A) + */ + private static final Color ACTIVE_COLOR = new Color("A8FF"); + /** + * Color of the active cell (R, G, B, A) while mouse is over it. + */ + private static final Color ACTIVE_COLOR_MOUSE_OVER = new Color("F9FF"); + /** + * Color of the inactive cell (R, G, B, A) + */ + private static final Color INACTIVE_COLOR = new Color("55F8"); + /** + * Color of the inactive cell (R, G, B, A) while mouse is over it. + */ + private static final Color INACTIVE_COLOR_MOUSE_OVER = new Color("77F8"); + /** + * A placeholder variable to help in next generation computation. Indicates + * whether cell is going to survive within next generation. + */ + public boolean survives; + /** + * Indicates whether cell is currently active + */ + private boolean active; + /** + * Indicates whether mouse pointer is currently over this cell. + */ + private boolean isMouseOver = false; + + public Cell(final Point3D center) { + super(center); + + createCellShape(); + + // enable receiving of mouse events + setMouseInteractionController(this); + } + + private void createCellShape() { + final double halfSize = SIZE / 2f; + + // define 4 points corresponding to cell borders + final Point3D p1 = new Point3D(-halfSize, 0, -halfSize); + + final Point3D p2 = new Point3D(+halfSize, 0, -halfSize); + + final Point3D p3 = new Point3D(+halfSize, 0, +halfSize); + + final Point3D p4 = new Point3D(-halfSize, 0, +halfSize); + + // connect 4 points with 2 polygons + addShape(new SolidPolygon(p1, p2, p3, computeCellColor())); + addShape(new SolidPolygon(p1, p4, p3, computeCellColor())); + } + + /** + * Compute cell color depending if cell is active and if mouse is over the + * cell. + */ + private Color computeCellColor() { + if (active) + if (isMouseOver) + return ACTIVE_COLOR_MOUSE_OVER; + else + return ACTIVE_COLOR; + else if (isMouseOver) + return INACTIVE_COLOR_MOUSE_OVER; + else + return INACTIVE_COLOR; + } + + public boolean isActive() { + return active; + } + + public void setActive(final boolean active) { + this.active = active; + updateColor(); + } + + @Override + public boolean mouseClicked() { + setActive(!isActive()); + return true; + } + + @Override + public boolean mouseEntered() { + setMouseOver(true); + return true; + } + + @Override + public boolean mouseExited() { + setMouseOver(false); + return true; + } + + private void setMouseOver(final boolean isMouseOver) { + this.isMouseOver = isMouseOver; + updateColor(); + } + + /** + * This method is called when cell status is changed to update its color + * too. + */ + private void updateColor() { + setColor(computeCellColor()); + } + +} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java new file mode 100644 index 0000000..cf47e7f --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java @@ -0,0 +1,108 @@ +package eu.svjatoslav.sixth.e3d.examples.life_demo; + +import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.geometry.Rectangle; +import eu.svjatoslav.sixth.e3d.gui.Avatar; +import eu.svjatoslav.sixth.e3d.gui.ViewFrame; +import eu.svjatoslav.sixth.e3d.gui.ViewPanel; +import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker; +import eu.svjatoslav.sixth.e3d.math.Transform; +import eu.svjatoslav.sixth.e3d.renderer.raster.Color; +import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D; + +import java.awt.event.KeyEvent; + + +public class Main extends WorldNavigationUserInputTracker { + + private static final Matrix MATRIX = new Matrix( + new Point3D() // position matrix in the center of the scene + ); + + public static void main(final String[] args) { + new Main().run(); + } + + /** + * Handle keyboard input. + */ + @Override + public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) { + switch (event.getKeyChar()) { + case ' ': // space key + MATRIX.evolve(false); + break; + case 10: // ENTER + MATRIX.evolve(true); + break; + case 'c': // reset matrix + MATRIX.clear(); + break; + default: + return super.keyPressed(event, viewPanel); + } + return true; + } + + private void run() { + + // create application frame visible to the user + final ViewFrame viewFrame = new ViewFrame(); + + final ShapeCollection shapeCollection = viewFrame.getViewPanel() + .getRootShapeCollection(); + + // add matrix + shapeCollection.addShape(MATRIX); + + // add wire-frame grid (optional) + shapeCollection.addShape(createGrid()); + + final ViewPanel viewPanel = viewFrame.getViewPanel(); + + setAvatarOrientation(viewPanel.getAvatar()); + + // enable receiving of keyboard events + viewPanel.getKeyboardFocusStack().pushFocusOwner(this); + + // Done! World is built. So ensure screen is updated too. + viewPanel.repaintDuringNextViewUpdate(); + } + + /** + * Create pink wire-frame grid below (for decorative purposes). + */ + private Grid2D createGrid() { + return new Grid2D( + new Transform( + new Point3D( // Grid positioning: + 0, // center + 100, // below the main scene + 0), // center + + // Grid orientation: + 0, // no rotation along XZ axis + Math.PI / 2), // face down + + new Rectangle(800), // large enough, square grid + + 5, 5, // grid will be divided to 5x5 segments + + new LineAppearance(3, // line thickness + new Color("FF000050") // red and quite transparent + ) + ); + } + + /** + * Set Avatar/Camera initial position in the world. + */ + private void setAvatarOrientation(final Avatar avatar) { + avatar.setLocation(new Point3D(100, -50, -200)); + avatar.setAngleXZ(0.2f); + avatar.setAngleYZ(-0.7f); + } + +} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java new file mode 100644 index 0000000..8736b96 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java @@ -0,0 +1,156 @@ +package eu.svjatoslav.sixth.e3d.examples.life_demo; + +import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.SubShape; + +/** + * This is our 2D game of life world. It contains 2D array of {@link Cell}'s. + */ +class Matrix extends AbstractCompositeShape { + + /** + * Empty space between cells. + */ + private static final int BORDER = 5; + + /** + * Matrix size X and Y. (Amount of cells.) + */ + private static final int SIZE = 30; + + /** + * Object marker in 3D scene. Allows to locate all stars from the scene. + */ + private static final String GROUP_STARS = "stars"; + + /** + * Object marker in 3D scene. Allows to locate surface on the scene. + */ + private static final String GROUP_SURFACE = "surface"; + + /** + * 2 dimensional matrix of cells + */ + private final Cell[][] cells = new Cell[SIZE][]; + + public Matrix(final Point3D location) { + super(location); + + + for (int x = 0; x < SIZE; x++) { + cells[x] = new Cell[SIZE]; + + // init Y row + for (int z = 0; z < SIZE; z++) { + // create cell and register it + final Cell cell = new Cell(getCellLocation(x, z)); + cells[x][z] = cell; + addShape(cell); + } + } + + setGroupForUngrouped(GROUP_SURFACE); + } + + /** + * Clear matrix. + */ + public void clear() { + + // mark every cell as inactive + for (int x = 0; x < SIZE; x++) + for (int y = 0; y < SIZE; y++) + cells[x][y].setActive(false); + + // remove history stars + removeGroup(GROUP_STARS); + } + + /** + * Compute survived cells based on the rules of Conway's Game of Life. + */ + private void computeSurvivedCells() { + + for (int y = 0; y < SIZE; y++) + for (int x = 0; x < SIZE; x++) + processCell(x, y); + + for (int y = 0; y < SIZE; y++) + for (int x = 0; x < SIZE; x++) + cells[x][y].setActive(cells[x][y].survives); + + } + + private void processCell(int x, int y) { + int aliveNeighbours = countNeighbours(x, y); + + if (cells[x][y].isActive()) { + cells[x][y].survives = ((aliveNeighbours == 2) || (aliveNeighbours == 3)); + } else { + cells[x][y].survives = aliveNeighbours == 3; + } + } + + private int countNeighbours(int x, int y) { + int result = 0; + for (int ny = y - 1; ny <= y + 1; ny++) + for (int nx = x - 1; nx <= x + 1; nx++) + if (isCellAlive(nx, ny)) result++; + + if (isCellAlive(x, y)) result--; + + return result; + } + + private boolean isCellAlive(int x, int y) { + if (x < 0) return false; + if (x >= SIZE) return false; + if (y < 0) return false; + if (y >= SIZE) return false; + return cells[x][y].isActive(); + } + + /** + * Evolve matrix for one iteration + */ + public void evolve(final boolean preserveHistory) { + if (preserveHistory) + markActiveCells(); + + shiftStarsUp(); + + computeSurvivedCells(); + } + + private Point3D getCellLocation(final int x, final int z) { + final int shift = -((SIZE / 2) * (Cell.SIZE + BORDER)); + + return new Point3D( + (x * (Cell.SIZE + BORDER)) + shift, + 0, + (z * (Cell.SIZE + BORDER)) + shift); + } + + /** + * Leave trail of active cells as stars above matrix. + */ + private void markActiveCells() { + // mark survived cells + for (int x = 0; x < SIZE; x++) + for (int y = 0; y < SIZE; y++) + if (cells[x][y].isActive()) + addShape(new Star(getCellLocation(x, y))); + + setGroupForUngrouped(GROUP_STARS); + } + + /** + * Find all history tracking stars and shift them up. + */ + private void shiftStarsUp() { + + for (final SubShape subShape : getGroup(GROUP_STARS)) + ((Star) subShape.getShape()).getLocation().translateY(-10); + } +} diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Star.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Star.java new file mode 100644 index 0000000..6f5edaa --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Star.java @@ -0,0 +1,41 @@ +package eu.svjatoslav.sixth.e3d.examples.life_demo; + +import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.renderer.raster.Color; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint; + +import java.util.ArrayList; +import java.util.List; + +class Star extends GlowingPoint { + public static final int STAR_SIZE = 10; + + public static final int UNIQUE_STARS_COUNT = 30; + + private static final List uniqueStarColors = new ArrayList<>(); + + /* + * A little hack to save RAM. We are going to have potentially lot of stars. + * Instead of creating new individual texture for each star, Sixth 3D engine + * uses internal optimization and reuses existing star textures, if star with + * identical color already exists. To take advantage ot such optimization + * we create here limited set of precomputed star colors and later reuse them. + */ + static { + for (int i = 0; i < UNIQUE_STARS_COUNT; i++) + uniqueStarColors.add( + new Color( + Math.random() + 0.5, + Math.random() + 0.5, + Math.random() + 0.5, + 255)); + } + + public Star(Point3D location) { + super(location, + STAR_SIZE, + uniqueStarColors.get((int) (Math.random() * uniqueStarColors.size())) // pick random pre-generated color + ); + } + +}