├── jar_manifest.mf ├── LICENSE.txt ├── src └── main │ └── java │ └── thebombzen │ └── emfieldhockey │ ├── object │ ├── ElectricFieldContributor.java │ ├── MagneticFieldContributor.java │ ├── ElectricFieldReceiver.java │ ├── MagneticFieldReceiver.java │ ├── PositionedObject.java │ ├── MovingObject.java │ ├── RenderPassComparator.java │ ├── RenderableObject.java │ ├── pointcharge │ │ ├── StationaryPointCharge.java │ │ ├── PointCharge.java │ │ └── MovingPointCharge.java │ └── other │ │ ├── ConstantMagneticField.java │ │ ├── PlacementSelector.java │ │ └── ElectricFieldRenderer.java │ ├── Constants.java │ ├── TickExecutor.java │ ├── CanvasMouseListener.java │ ├── ElectromagneticFieldHockey.java │ ├── Vector.java │ └── World.java ├── INSTALL.txt ├── make.bat ├── README.md └── Makefile /jar_manifest.mf: -------------------------------------------------------------------------------- 1 | Main-Class: thebombzen.emfieldhockey.ElectromagneticFieldHockey 2 | 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Electromagnetic Field Hockey is released in the Public Domain. You may use it for any reason, 2 | for any purpose (including commerical purposes), without even asking, 3 | to the full extent permitted by law in your jurisdiction. 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/ElectricFieldContributor.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import thebombzen.emfieldhockey.Vector; 4 | 5 | public interface ElectricFieldContributor { 6 | public Vector getElectricField(Vector position); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/MagneticFieldContributor.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import thebombzen.emfieldhockey.Vector; 4 | 5 | public interface MagneticFieldContributor { 6 | public double getMagneticField(Vector position); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/ElectricFieldReceiver.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import thebombzen.emfieldhockey.Vector; 4 | 5 | public interface ElectricFieldReceiver extends PositionedObject { 6 | public Vector getElectricForce(Vector electricField); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/MagneticFieldReceiver.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import thebombzen.emfieldhockey.Vector; 4 | 5 | public interface MagneticFieldReceiver extends PositionedObject { 6 | public Vector getMagneticForce(double magneticField); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/PositionedObject.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import thebombzen.emfieldhockey.Vector; 4 | 5 | public interface PositionedObject extends RenderableObject { 6 | public Vector getPosition(); 7 | 8 | public double getRadius(); 9 | 10 | public void setPosition(Vector vector); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | LINUX / OSX / SOLARIS / *nix 2 | =========================== 3 | Navigate to the root directory and run 4 | make 5 | That's it! To clean the compiled files run 6 | make clean 7 | 8 | WINDOWS 9 | =========== 10 | Navigate to the root directory and run 11 | make.bat 12 | That's it! To clean the compiled files run 13 | make.bat clean 14 | 15 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | RMDIR /S /Q build 2 | DEL /S /Q ElectromagneticFieldHockey.jar 3 | IF %1==clean EXIT 4 | MKDIR build 5 | javac -source 1.6 -target 1.6 -deprecation -sourcepath src\main\java -d build -implicit:class -g:none src\main\java\thebombzen\emfieldhockey\ElectromagneticFieldHockey.java 6 | jar -cmf jar_manifest.mf ElectromagneticFieldHockey.jar -C build thebombzen 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Electromagnetic Field Hockey 2 | ============================ 3 | 4 | Electromagnetic Field Hockey is based of "Electric Field Hockey" on phet.colorado.edu, but with magnetism programmed in. 5 | 6 | Note that the magnetic field effects are orders of magnitude smaller than electric field effects, 7 | so this uses large magnetic fields to make the effects visisble. 8 | 9 | This is developed by Leo Izen (thebombzen). 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/MovingObject.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import thebombzen.emfieldhockey.Vector; 4 | 5 | public interface MovingObject extends PositionedObject { 6 | 7 | public void collide(PositionedObject object); 8 | 9 | public double getMass(); 10 | 11 | public Vector getVelocity(); 12 | 13 | public void setMass(double mass); 14 | 15 | public void setVelocity(Vector velocity); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: ElectromagneticFieldHockey.jar 2 | 3 | ElectromagneticFieldHockey.jar: build/thebombzen/emfieldhockey/ElectromagneticFieldHockey.class 4 | jar -cmf jar_manifest.mf ElectromagneticFieldHockey.jar -C build/ thebombzen/ 5 | 6 | build/thebombzen/emfieldhockey/ElectromagneticFieldHockey.class: 7 | mkdir -p build/ 8 | javac -source 1.6 -target 1.6 -deprecation -sourcepath src/main/java -d build/ -implicit:class -g:none src/main/java/thebombzen/emfieldhockey/ElectromagneticFieldHockey.java 9 | 10 | clean: 11 | rm -rf build/ 12 | rm -f ElectromagneticFieldHockey.jar 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/RenderPassComparator.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import java.util.Comparator; 4 | 5 | public final class RenderPassComparator implements Comparator { 6 | 7 | public static final RenderPassComparator renderPassComparator = new RenderPassComparator(); 8 | 9 | private RenderPassComparator() { 10 | 11 | } 12 | 13 | @Override 14 | public int compare(RenderableObject o1, RenderableObject o2) { 15 | return Integer.valueOf(o1.getRenderPass()) 16 | .compareTo(o2.getRenderPass()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/RenderableObject.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object; 2 | 3 | import java.awt.Graphics2D; 4 | import java.awt.event.MouseEvent; 5 | 6 | import thebombzen.emfieldhockey.Vector; 7 | 8 | public interface RenderableObject { 9 | 10 | public int getRenderPass(); 11 | 12 | public boolean isPositionInsideObject(Vector position); 13 | 14 | /** 15 | * Returns true if the action was successful and final. 16 | * 17 | * @return success 18 | */ 19 | public boolean onClick(MouseEvent event); 20 | 21 | public void render(Graphics2D g2); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/pointcharge/StationaryPointCharge.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object.pointcharge; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | 6 | import thebombzen.emfieldhockey.Vector; 7 | 8 | public class StationaryPointCharge extends PointCharge { 9 | 10 | public StationaryPointCharge(Vector position, double charge) { 11 | super(position, charge); 12 | } 13 | 14 | @Override 15 | public void render(Graphics2D g2) { 16 | super.render(g2); 17 | g2.setColor(Color.BLACK); 18 | g2.fillOval(getPosition().getIntegerX() - 5, getPosition() 19 | .getIntegerY() - 5, 10, 10); 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "StationaryPointCharge [getPosition()=" + getPosition() 25 | + ", getCharge()=" + getCharge() + "]"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/Constants.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey; 2 | 3 | import java.awt.GraphicsEnvironment; 4 | 5 | public final class Constants { 6 | 7 | /** 8 | * Pixels per meter 9 | */ 10 | private static final double PPM = 100D; 11 | 12 | /** 13 | * Physics area width, in pixels 14 | */ 15 | public static final int WIDTH = 720; 16 | 17 | /** 18 | * Physics area height, in pixels 19 | */ 20 | public static final int HEIGHT = 540; 21 | 22 | /** 23 | * K, in N m^2 / C^2 /. m->p 24 | */ 25 | public static final double K = 299792.458D * 29979.2458D * PPM * PPM * PPM; 26 | 27 | /** 28 | * E0, in F / m /. m->p 29 | */ 30 | public static final double E0 = 1D / (4D * Math.PI * K); 31 | 32 | /** 33 | * MU0, in H / m /. m->p 34 | */ 35 | public static final double MU0 = 4E-7D * Math.PI * PPM; 36 | 37 | /** 38 | * framerate 39 | */ 40 | 41 | public static final int REFRESH_RATE = GraphicsEnvironment 42 | .getLocalGraphicsEnvironment().getDefaultScreenDevice() 43 | .getDisplayMode().getRefreshRate(); 44 | 45 | public static final double TICK_TIME_STEP = 1D / REFRESH_RATE; 46 | public static final long TICK_TIME_STEP_NANOS = (long) (1E9D * TICK_TIME_STEP); 47 | 48 | private Constants() { 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/other/ConstantMagneticField.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object.other; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.event.MouseEvent; 6 | 7 | import thebombzen.emfieldhockey.Constants; 8 | import thebombzen.emfieldhockey.Vector; 9 | import thebombzen.emfieldhockey.object.MagneticFieldContributor; 10 | import thebombzen.emfieldhockey.object.RenderableObject; 11 | 12 | public class ConstantMagneticField implements RenderableObject, 13 | MagneticFieldContributor { 14 | 15 | private static boolean enabled = false; 16 | 17 | public static boolean isEnabled() { 18 | return enabled; 19 | } 20 | 21 | public static void setEnabled(boolean enabled) { 22 | ConstantMagneticField.enabled = enabled; 23 | } 24 | 25 | @Override 26 | public double getMagneticField(Vector position) { 27 | return enabled ? 0.5E4D : 0D; 28 | } 29 | 30 | @Override 31 | public int getRenderPass() { 32 | return -1; 33 | } 34 | 35 | @Override 36 | public boolean isPositionInsideObject(Vector position) { 37 | return false; 38 | } 39 | 40 | @Override 41 | public boolean onClick(MouseEvent event) { 42 | return false; 43 | } 44 | 45 | @Override 46 | public void render(Graphics2D g2) { 47 | if (!enabled) { 48 | return; 49 | } 50 | g2.setColor(new Color(0x008000)); 51 | for (int x = 0; x <= Constants.WIDTH; x += 20) { 52 | for (int y = 0; y <= Constants.HEIGHT; y += 20) { 53 | g2.drawLine(x - 2, y - 2, x + 2, y + 2); 54 | g2.drawLine(x - 2, y + 2, x + 2, y - 2); 55 | } 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/pointcharge/PointCharge.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object.pointcharge; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.event.MouseEvent; 6 | 7 | import thebombzen.emfieldhockey.Constants; 8 | import thebombzen.emfieldhockey.Vector; 9 | import thebombzen.emfieldhockey.World; 10 | import thebombzen.emfieldhockey.object.ElectricFieldContributor; 11 | import thebombzen.emfieldhockey.object.PositionedObject; 12 | 13 | public class PointCharge implements PositionedObject, ElectricFieldContributor { 14 | 15 | private double charge; 16 | private Vector position; 17 | 18 | public static final PointCharge DUMMY = new PointCharge(null, 0D); 19 | 20 | protected PointCharge(Vector position, double charge) { 21 | this.position = position; 22 | this.charge = charge; 23 | } 24 | 25 | public double getCharge() { 26 | return charge; 27 | } 28 | 29 | @Override 30 | public Vector getElectricField(Vector position) { 31 | Vector r = position.subtract(getPosition()); 32 | return r.multiply(Constants.K * getCharge() / r.getNormCubed()); 33 | } 34 | 35 | @Override 36 | public Vector getPosition() { 37 | return position; 38 | } 39 | 40 | @Override 41 | public double getRadius() { 42 | return 10D; 43 | } 44 | 45 | @Override 46 | public int getRenderPass() { 47 | return 0; 48 | } 49 | 50 | @Override 51 | public boolean isPositionInsideObject(Vector position) { 52 | return position.subtract(getPosition()).getNormSquared() <= getRadius() 53 | * getRadius(); 54 | } 55 | 56 | @Override 57 | public boolean onClick(MouseEvent event) { 58 | if (event.getButton() == MouseEvent.BUTTON2) { 59 | World.getInstance().removeObject(this); 60 | return true; 61 | } else { 62 | return false; 63 | } 64 | } 65 | 66 | @Override 67 | public void render(Graphics2D g2) { 68 | if (charge >= 0) { 69 | g2.setColor(Color.RED); 70 | } else { 71 | g2.setColor(Color.BLUE); 72 | } 73 | g2.fillOval(getPosition().getIntegerX() - 10, getPosition() 74 | .getIntegerY() - 10, 20, 20); 75 | } 76 | 77 | public void setCharge(double charge) { 78 | this.charge = charge; 79 | } 80 | 81 | @Override 82 | public void setPosition(Vector position) { 83 | this.position = position; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/other/PlacementSelector.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object.other; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.event.MouseEvent; 6 | 7 | import thebombzen.emfieldhockey.Vector; 8 | import thebombzen.emfieldhockey.object.RenderableObject; 9 | 10 | public class PlacementSelector implements RenderableObject { 11 | 12 | private int selected = 0; 13 | 14 | private static final PlacementSelector placementSelector = new PlacementSelector(); 15 | 16 | public static PlacementSelector getInstance() { 17 | return placementSelector; 18 | } 19 | 20 | private PlacementSelector() { 21 | 22 | } 23 | 24 | @Override 25 | public int getRenderPass() { 26 | return 1; 27 | } 28 | 29 | public int getSelected() { 30 | return selected; 31 | } 32 | 33 | @Override 34 | public boolean isPositionInsideObject(Vector position) { 35 | if (position.getIntegerX() < 150 && position.getIntegerY() < 50) { 36 | return true; 37 | } else { 38 | return false; 39 | } 40 | } 41 | 42 | @Override 43 | public boolean onClick(MouseEvent event) { 44 | if (event.getX() < 100) { 45 | setSelected(event.getX() / 50); 46 | } else if (event.getX() < 150) { 47 | ConstantMagneticField 48 | .setEnabled(!ConstantMagneticField.isEnabled()); 49 | } 50 | return true; 51 | } 52 | 53 | @Override 54 | public void render(Graphics2D g2) { 55 | g2.setColor(Color.WHITE); 56 | g2.fillRect(0, 0, 150, 50); 57 | 58 | g2.setColor(Color.BLACK); 59 | for (int i = 0; i < 3; i++) { 60 | g2.drawRect(50 * i, 0, 50, 50); 61 | } 62 | 63 | g2.setColor(Color.ORANGE); 64 | g2.drawRect(50 * getSelected() + 1, 1, 48, 48); 65 | g2.setColor(Color.BLACK); 66 | g2.drawRect(50 * getSelected() + 2, 2, 46, 46); 67 | 68 | g2.setColor(Color.RED); 69 | g2.fillOval(15, 15, 20, 20); 70 | g2.fillOval(65, 15, 20, 20); 71 | g2.setColor(Color.BLACK); 72 | g2.fillOval(70, 20, 10, 10); 73 | 74 | g2.setColor(new Color(0x008000)); 75 | g2.drawLine(110, 10, 140, 40); 76 | g2.drawLine(140, 10, 110, 40); 77 | 78 | if (ConstantMagneticField.isEnabled()) { 79 | g2.setColor(Color.ORANGE); 80 | g2.drawRect(101, 1, 48, 48); 81 | g2.setColor(Color.BLACK); 82 | g2.drawRect(102, 2, 46, 46); 83 | } 84 | 85 | } 86 | 87 | public void setSelected(int selected) { 88 | this.selected = selected; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/TickExecutor.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey; 2 | 3 | import java.awt.Canvas; 4 | import java.awt.Color; 5 | import java.awt.Graphics; 6 | import java.awt.Graphics2D; 7 | import java.awt.RenderingHints; 8 | import java.awt.image.BufferedImage; 9 | 10 | public class TickExecutor implements Runnable { 11 | 12 | private static final TickExecutor tickExecutor = new TickExecutor(); 13 | 14 | public static TickExecutor getInstance() { 15 | return tickExecutor; 16 | } 17 | 18 | private long excess = 0L; 19 | 20 | private TickExecutor() { 21 | 22 | } 23 | 24 | private void render(World world) { 25 | Canvas canvas = ElectromagneticFieldHockey.getInstance().getCanvas(); 26 | BufferedImage image = new BufferedImage(Constants.WIDTH, 27 | Constants.HEIGHT, BufferedImage.TYPE_INT_ARGB); 28 | Graphics2D g2 = image.createGraphics(); 29 | g2.setColor(Color.WHITE); 30 | g2.fillRect(0, 0, Constants.WIDTH, Constants.HEIGHT); 31 | g2.setColor(Color.BLACK); 32 | g2.drawRect(0, 0, Constants.WIDTH - 1, Constants.HEIGHT - 1); 33 | 34 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 35 | RenderingHints.VALUE_ANTIALIAS_ON); 36 | world.renderObjects(g2); 37 | 38 | g2.dispose(); 39 | Graphics canvasGraphics = canvas.getGraphics(); 40 | canvasGraphics.drawImage(image, 0, 0, null); 41 | canvasGraphics.dispose(); 42 | } 43 | 44 | @Override 45 | public void run() { 46 | 47 | long beforeTime = System.nanoTime(); 48 | 49 | World world = World.getInstance(); 50 | 51 | try { 52 | world.updateObjects(Constants.TICK_TIME_STEP * 0.75D); 53 | render(world); 54 | } catch (RuntimeException e) { 55 | e.printStackTrace(); 56 | throw new RuntimeException(e); 57 | } catch (Error e) { 58 | e.printStackTrace(); 59 | throw new Error(e); 60 | } 61 | 62 | long afterTime = System.nanoTime(); 63 | 64 | long should = Constants.TICK_TIME_STEP_NANOS; 65 | long did = afterTime - beforeTime; 66 | if (did >= should) { 67 | excess += did - should; 68 | if (excess > Constants.TICK_TIME_STEP_NANOS) { 69 | excess -= Constants.TICK_TIME_STEP_NANOS; 70 | world.updateObjects(Constants.TICK_TIME_STEP); 71 | } 72 | } else { 73 | long millis = (should - did) / 1000000L; 74 | int nanos = (int) ((should - did) % 1000000L); 75 | try { 76 | Thread.sleep(millis, nanos); 77 | } catch (InterruptedException ie) { 78 | ie.printStackTrace(); 79 | } 80 | } 81 | 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/CanvasMouseListener.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey; 2 | 3 | import java.awt.event.MouseAdapter; 4 | import java.awt.event.MouseEvent; 5 | 6 | import thebombzen.emfieldhockey.object.PositionedObject; 7 | import thebombzen.emfieldhockey.object.RenderableObject; 8 | import thebombzen.emfieldhockey.object.other.PlacementSelector; 9 | import thebombzen.emfieldhockey.object.pointcharge.MovingPointCharge; 10 | import thebombzen.emfieldhockey.object.pointcharge.PointCharge; 11 | import thebombzen.emfieldhockey.object.pointcharge.StationaryPointCharge; 12 | 13 | public class CanvasMouseListener extends MouseAdapter { 14 | 15 | private Vector pressedLocation = null; 16 | 17 | @Override 18 | public void mousePressed(MouseEvent event) { 19 | 20 | boolean success = false; 21 | 22 | World world = World.getInstance(); 23 | Vector location = new Vector(event.getX(), event.getY()); 24 | for (RenderableObject renderableObject : world 25 | .getAllRenderableObjects()) { 26 | if (renderableObject.isPositionInsideObject(location)) { 27 | success |= renderableObject.onClick(event); 28 | } 29 | } 30 | 31 | if (!success) { 32 | pressedLocation = new Vector(event.getX(), event.getY()); 33 | } 34 | 35 | } 36 | 37 | @Override 38 | public void mouseReleased(MouseEvent event) { 39 | if (pressedLocation == null) { 40 | return; 41 | } 42 | 43 | World world = World.getInstance(); 44 | Vector location = new Vector(event.getX(), event.getY()); 45 | 46 | if (world.isPositionInsideRenderableObjects(location)) { 47 | return; 48 | } 49 | 50 | for (PositionedObject object : world.getAllPositionedObjects()) { 51 | double radius = object.getRadius() + PointCharge.DUMMY.getRadius(); 52 | if (object.getPosition().subtract(location).getNormSquared() <= radius 53 | * radius) { 54 | return; 55 | } 56 | } 57 | 58 | Vector r = location.subtract(pressedLocation); 59 | Vector v = r.getNormSquared() >= 2500 ? r.setLength(200D) : Vector.ZERO; 60 | 61 | if (event.getButton() == MouseEvent.BUTTON1 62 | || event.getButton() == MouseEvent.BUTTON3) { 63 | switch (PlacementSelector.getInstance().getSelected()) { 64 | case 0: 65 | world.addObject(new MovingPointCharge(location, 2E-6D * (event 66 | .getButton() == MouseEvent.BUTTON1 ? 1 : -1), v, 1E-2D)); 67 | break; 68 | case 1: 69 | world.addObject(new StationaryPointCharge(location, 70 | 2E-6D * (event.getButton() == MouseEvent.BUTTON1 ? 1 71 | : -1))); 72 | break; 73 | } 74 | } 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/ElectromagneticFieldHockey.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Canvas; 5 | import java.awt.Color; 6 | import java.awt.Dimension; 7 | import java.awt.Toolkit; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import javax.swing.JFrame; 13 | import javax.swing.JPanel; 14 | 15 | import thebombzen.emfieldhockey.object.other.ConstantMagneticField; 16 | import thebombzen.emfieldhockey.object.other.ElectricFieldRenderer; 17 | import thebombzen.emfieldhockey.object.other.PlacementSelector; 18 | 19 | public class ElectromagneticFieldHockey extends JPanel { 20 | 21 | private static final ElectromagneticFieldHockey instance = new ElectromagneticFieldHockey(); 22 | 23 | private static final long serialVersionUID = -5566156782717789895L; 24 | 25 | public static ElectromagneticFieldHockey getInstance() { 26 | return instance; 27 | } 28 | 29 | public static void main(String[] args) { 30 | JFrame frame = new JFrame(); 31 | frame.setBackground(Color.WHITE); 32 | 33 | frame.add(getInstance()); 34 | 35 | frame.pack(); 36 | frame.setTitle("Electromagnetic Field Hockey"); 37 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 38 | frame.setResizable(false); 39 | 40 | Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); 41 | 42 | frame.setLocation((size.width - frame.getWidth()) / 2, 43 | (size.height - frame.getHeight()) / 2); 44 | frame.setVisible(true); 45 | 46 | getInstance().start(); 47 | 48 | } 49 | 50 | private CanvasMouseListener listener = new CanvasMouseListener(); 51 | private Canvas canvas = new Canvas(); 52 | private ScheduledExecutorService service = Executors 53 | .newSingleThreadScheduledExecutor(); 54 | private boolean started = false; 55 | 56 | private ElectromagneticFieldHockey() { 57 | this.setLayout(new BorderLayout()); 58 | this.setBackground(Color.WHITE); 59 | canvas.setSize(Constants.WIDTH, Constants.HEIGHT); 60 | canvas.setBackground(Color.WHITE); 61 | canvas.addMouseListener(listener); 62 | this.add(canvas); 63 | 64 | World.getInstance().addObject(PlacementSelector.getInstance()); 65 | World.getInstance().addObject(new ConstantMagneticField()); 66 | World.getInstance().addObject(new ElectricFieldRenderer()); 67 | 68 | } 69 | 70 | public Canvas getCanvas() { 71 | return canvas; 72 | } 73 | 74 | private void start() { 75 | if (!started) { 76 | started = true; 77 | service.scheduleAtFixedRate(TickExecutor.getInstance(), 0, 78 | Constants.TICK_TIME_STEP_NANOS, TimeUnit.NANOSECONDS); 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/other/ElectricFieldRenderer.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object.other; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.event.MouseEvent; 6 | 7 | import thebombzen.emfieldhockey.Constants; 8 | import thebombzen.emfieldhockey.Vector; 9 | import thebombzen.emfieldhockey.World; 10 | import thebombzen.emfieldhockey.object.ElectricFieldContributor; 11 | import thebombzen.emfieldhockey.object.RenderableObject; 12 | 13 | public class ElectricFieldRenderer implements RenderableObject { 14 | 15 | @Override 16 | public int getRenderPass() { 17 | return -1; 18 | } 19 | 20 | @Override 21 | public boolean isPositionInsideObject(Vector position) { 22 | return false; 23 | } 24 | 25 | @Override 26 | public boolean onClick(MouseEvent event) { 27 | return false; 28 | } 29 | 30 | @Override 31 | public void render(Graphics2D g2) { 32 | for (int x = 10; x <= Constants.WIDTH; x += 20) { 33 | for (int y = 10; y <= Constants.HEIGHT; y += 20) { 34 | 35 | Vector electricField = Vector.ZERO; 36 | for (ElectricFieldContributor contributor : World.getInstance() 37 | .getElectricFieldContributors()) { 38 | electricField = electricField.add(contributor 39 | .getElectricField(new Vector(x, y))); 40 | } 41 | 42 | double logNorm = Math.log(electricField.getNormSquared()) - 23D; 43 | 44 | if (Double.isInfinite(logNorm)) { 45 | continue; 46 | } 47 | 48 | double max = 10D; 49 | 50 | if (logNorm < 0D) { 51 | continue; 52 | } 53 | 54 | if (logNorm > max) { 55 | logNorm = max; 56 | } 57 | 58 | Vector direction = electricField.setLength(8D); 59 | 60 | double angle = Math.atan2(direction.getY(), direction.getX()); 61 | 62 | Vector arm1 = new Vector(Math.cos(angle + Math.PI / 6), 63 | Math.sin(angle + Math.PI / 6)).multiply(6D); // already 64 | // normalized 65 | Vector arm2 = new Vector(Math.cos(angle - Math.PI / 6), 66 | Math.sin(angle - Math.PI / 6)).multiply(6D); // already 67 | // normalized 68 | 69 | g2.setColor(new Color(Color.HSBtoRGB(0.1F, 70 | (float) (logNorm / max), 1F))); 71 | g2.drawLine(x - (int) (direction.getX()), 72 | y - (int) (direction.getY()), 73 | x + (int) (direction.getX()), 74 | y + (int) (direction.getY())); 75 | g2.drawLine(x + (int) (direction.getX()), 76 | y + (int) (direction.getY()), 77 | x + (int) (direction.getX() - arm1.getX()), y 78 | + (int) (direction.getY() - arm1.getY())); 79 | g2.drawLine(x + (int) (direction.getX()), 80 | y + (int) (direction.getY()), 81 | x + (int) (direction.getX() - arm2.getX()), y 82 | + (int) (direction.getY() - arm2.getY())); 83 | 84 | } 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/object/pointcharge/MovingPointCharge.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey.object.pointcharge; 2 | 3 | import thebombzen.emfieldhockey.Vector; 4 | import thebombzen.emfieldhockey.object.ElectricFieldReceiver; 5 | import thebombzen.emfieldhockey.object.MagneticFieldReceiver; 6 | import thebombzen.emfieldhockey.object.MovingObject; 7 | import thebombzen.emfieldhockey.object.PositionedObject; 8 | 9 | public class MovingPointCharge extends PointCharge implements MovingObject, 10 | ElectricFieldReceiver, MagneticFieldReceiver { 11 | 12 | private double mass; 13 | private Vector velocity; 14 | 15 | public MovingPointCharge(Vector position, double charge, Vector velocity, 16 | double mass) { 17 | super(position, charge); 18 | this.velocity = velocity; 19 | this.mass = mass; 20 | } 21 | 22 | @Override 23 | public void collide(PositionedObject object) { 24 | if (object instanceof MovingObject) { 25 | collideWithMoving((MovingObject) object); 26 | } else { 27 | collideWithStationary(object); 28 | } 29 | } 30 | 31 | private void collideWithMoving(MovingObject object) { 32 | 33 | Vector collisionDirection = object.getPosition() 34 | .subtract(getPosition()); 35 | Vector collisionV1 = getVelocity().getComponent(collisionDirection); 36 | Vector collisionV2 = object.getVelocity().getComponent( 37 | collisionDirection); 38 | Vector keptV1 = getVelocity().subtract(collisionV1); 39 | Vector keptV2 = object.getVelocity().subtract(collisionV2); 40 | Vector outV1 = collisionV1.multiply(getMass() - object.getMass()) 41 | .add(collisionV2.multiply(2D * object.getMass())) 42 | .divide(getMass() + object.getMass()); 43 | Vector outV2 = collisionV2.multiply(object.getMass() - getMass()) 44 | .add(collisionV1.multiply(2D * getMass())) 45 | .divide(getMass() + object.getMass()); 46 | object.setVelocity(outV2.add(keptV2)); 47 | setVelocity(outV1.add(keptV1)); 48 | 49 | } 50 | 51 | private void collideWithStationary(PositionedObject object) { 52 | Vector collisionDirection = object.getPosition() 53 | .subtract(getPosition()); 54 | Vector collisionV = getVelocity().getComponent(collisionDirection); 55 | setVelocity(getVelocity().subtract(collisionV.multiply(2D))); 56 | 57 | } 58 | 59 | @Override 60 | public Vector getElectricForce(Vector electricField) { 61 | return electricField.multiply(getCharge()); 62 | } 63 | 64 | @Override 65 | public Vector getMagneticForce(double magneticField) { 66 | return getVelocity().multiply(getCharge()).cross(magneticField); 67 | } 68 | 69 | @Override 70 | public double getMass() { 71 | return mass; 72 | } 73 | 74 | @Override 75 | public Vector getVelocity() { 76 | return velocity; 77 | } 78 | 79 | @Override 80 | public void setMass(double mass) { 81 | this.mass = mass; 82 | } 83 | 84 | @Override 85 | public void setVelocity(Vector velocity) { 86 | this.velocity = velocity; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return "MovingPointCharge [getVelocity()=" + getVelocity() 92 | + ", getMass()=" + getMass() + ", getPosition()=" 93 | + getPosition() + ", getCharge()=" + getCharge() + "]"; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/Vector.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey; 2 | 3 | public class Vector { 4 | 5 | public static final Vector I = new Vector(1, 0); 6 | public static final Vector J = new Vector(0, 1); 7 | public static final Vector ZERO = new Vector(0, 0); 8 | 9 | public static double fastInverseSquareRoot(double x) { 10 | long raw = Double.doubleToRawLongBits(x); 11 | raw = 0x5fe6eb50c7b537a9L - (raw >>> 1); 12 | double y = Double.longBitsToDouble(raw); 13 | y *= 1.5F - 0.5F * x * y * y; 14 | return y; 15 | } 16 | 17 | private double x; 18 | private double y; 19 | 20 | public Vector(double x, double y) { 21 | this.x = x; 22 | this.y = y; 23 | } 24 | 25 | public Vector add(Vector addend) { 26 | return new Vector(x + addend.getX(), y + addend.getY()); 27 | } 28 | 29 | public Vector cross(double z) { 30 | return new Vector(y, -x).multiply(z); 31 | } 32 | 33 | public double cross(Vector vector) { 34 | return getX() * vector.getY() - getY() * vector.getX(); 35 | } 36 | 37 | public Vector divide(double divisor) { 38 | return new Vector(x / divisor, y / divisor); 39 | } 40 | 41 | public double dot(Vector vector) { 42 | return getX() * vector.getX() + getY() * vector.getY(); 43 | } 44 | 45 | @Override 46 | public boolean equals(Object obj) { 47 | if (this == obj) 48 | return true; 49 | if (obj == null) 50 | return false; 51 | if (getClass() != obj.getClass()) 52 | return false; 53 | Vector other = (Vector) obj; 54 | if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) 55 | return false; 56 | if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) 57 | return false; 58 | return true; 59 | } 60 | 61 | public Vector getComponent(Vector direction) { 62 | return direction.multiply(dot(direction) / direction.getNormSquared()); 63 | } 64 | 65 | public int getIntegerX() { 66 | return (int) x; 67 | } 68 | 69 | public int getIntegerY() { 70 | return (int) y; 71 | } 72 | 73 | public double getInverseNorm() { 74 | return Vector.fastInverseSquareRoot(getNormSquared()); 75 | } 76 | 77 | public double getNorm() { 78 | return 1D / getInverseNorm(); 79 | } 80 | 81 | public double getNormCubed() { 82 | double n = getNormSquared(); 83 | return n * n * Vector.fastInverseSquareRoot(n); 84 | } 85 | 86 | public double getNormSquared() { 87 | return getX() * getX() + getY() * getY(); 88 | } 89 | 90 | public double getX() { 91 | return x; 92 | } 93 | 94 | public double getY() { 95 | return y; 96 | } 97 | 98 | @Override 99 | public int hashCode() { 100 | final int prime = 31; 101 | int result = 1; 102 | long temp; 103 | temp = Double.doubleToLongBits(x); 104 | result = prime * result + (int) (temp ^ (temp >>> 32)); 105 | temp = Double.doubleToLongBits(y); 106 | result = prime * result + (int) (temp ^ (temp >>> 32)); 107 | return result; 108 | } 109 | 110 | public Vector multiply(double multiplicand) { 111 | return new Vector(x * multiplicand, y * multiplicand); 112 | } 113 | 114 | public Vector negate() { 115 | return new Vector(-x, -y); 116 | } 117 | 118 | public Vector normalize() { 119 | return multiply(getInverseNorm()); 120 | } 121 | 122 | public Vector setLength(double length) { 123 | return multiply(getInverseNorm() * length); 124 | } 125 | 126 | public Vector subtract(Vector subtrahend) { 127 | return new Vector(x - subtrahend.getX(), y - subtrahend.getY()); 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | return "Vector [x=" + x + ", y=" + y + "]"; 133 | } 134 | 135 | } 136 | 137 | -------------------------------------------------------------------------------- /src/main/java/thebombzen/emfieldhockey/World.java: -------------------------------------------------------------------------------- 1 | package thebombzen.emfieldhockey; 2 | 3 | import java.awt.Graphics2D; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.Random; 8 | 9 | import thebombzen.emfieldhockey.object.ElectricFieldContributor; 10 | import thebombzen.emfieldhockey.object.ElectricFieldReceiver; 11 | import thebombzen.emfieldhockey.object.MagneticFieldContributor; 12 | import thebombzen.emfieldhockey.object.MagneticFieldReceiver; 13 | import thebombzen.emfieldhockey.object.MovingObject; 14 | import thebombzen.emfieldhockey.object.PositionedObject; 15 | import thebombzen.emfieldhockey.object.RenderPassComparator; 16 | import thebombzen.emfieldhockey.object.RenderableObject; 17 | 18 | public class World { 19 | 20 | private static final World world = new World(); 21 | 22 | public static final Random random = new Random(); 23 | 24 | public static World getInstance() { 25 | return world; 26 | } 27 | 28 | private List renderableObjects = new ArrayList(); 29 | private List positionedObjects = new ArrayList(); 30 | private List movingObjects = new ArrayList(); 31 | private List electricFieldContributors = new ArrayList(); 32 | private List electricFieldReceivers = new ArrayList(); 33 | private List magneticFieldContributors = new ArrayList(); 34 | private List magneticFieldReceivers = new ArrayList(); 35 | 36 | private World() { 37 | 38 | } 39 | 40 | public synchronized void addObject(Object object) { 41 | if (object instanceof RenderableObject) { 42 | renderableObjects.add((RenderableObject) object); 43 | } 44 | if (object instanceof PositionedObject) { 45 | positionedObjects.add((PositionedObject) object); 46 | } 47 | if (object instanceof MovingObject) { 48 | movingObjects.add((MovingObject) object); 49 | } 50 | if (object instanceof ElectricFieldContributor) { 51 | electricFieldContributors.add((ElectricFieldContributor) object); 52 | } 53 | if (object instanceof ElectricFieldReceiver) { 54 | electricFieldReceivers.add((ElectricFieldReceiver) object); 55 | } 56 | if (object instanceof MagneticFieldContributor) { 57 | magneticFieldContributors.add((MagneticFieldContributor) object); 58 | } 59 | if (object instanceof MagneticFieldReceiver) { 60 | magneticFieldReceivers.add((MagneticFieldReceiver) object); 61 | } 62 | 63 | } 64 | 65 | public synchronized MovingObject[] getAllMovingObjects() { 66 | return movingObjects.toArray(new MovingObject[movingObjects.size()]); 67 | } 68 | 69 | public synchronized PositionedObject[] getAllPositionedObjects() { 70 | return positionedObjects.toArray(new PositionedObject[positionedObjects 71 | .size()]); 72 | } 73 | 74 | public synchronized RenderableObject[] getAllRenderableObjects() { 75 | return renderableObjects.toArray(new RenderableObject[renderableObjects 76 | .size()]); 77 | } 78 | 79 | public synchronized ElectricFieldContributor[] getElectricFieldContributors() { 80 | return electricFieldContributors 81 | .toArray(new ElectricFieldContributor[electricFieldContributors 82 | .size()]); 83 | } 84 | 85 | private Vector getForce(MovingObject movingObject) { 86 | Vector force = Vector.ZERO; 87 | 88 | if (movingObject instanceof ElectricFieldReceiver) { 89 | Vector electricField = Vector.ZERO; 90 | for (ElectricFieldContributor contributor : electricFieldContributors) { 91 | if (contributor instanceof PositionedObject) { 92 | double radius = ((PositionedObject) contributor) 93 | .getRadius() + movingObject.getRadius(); 94 | if (((PositionedObject) contributor).getPosition() 95 | .subtract(movingObject.getPosition()) 96 | .getNormSquared() <= radius * radius) { 97 | continue; 98 | } 99 | } 100 | electricField = electricField.add(contributor 101 | .getElectricField(movingObject.getPosition())); 102 | } 103 | force = force.add(((ElectricFieldReceiver) movingObject) 104 | .getElectricForce(electricField)); 105 | } 106 | 107 | if (movingObject instanceof MagneticFieldReceiver) { 108 | double magneticField = 0D; 109 | for (MagneticFieldContributor contributor : magneticFieldContributors) { 110 | if (contributor instanceof PositionedObject) { 111 | double radius = ((PositionedObject) contributor) 112 | .getRadius() + movingObject.getRadius(); 113 | if (((PositionedObject) contributor).getPosition() 114 | .subtract(movingObject.getPosition()) 115 | .getNormSquared() <= radius * radius) { 116 | continue; 117 | } 118 | } 119 | magneticField += contributor.getMagneticField(movingObject 120 | .getPosition()); 121 | } 122 | force = force.add(((MagneticFieldReceiver) movingObject) 123 | .getMagneticForce(magneticField)); 124 | } 125 | 126 | return force; 127 | 128 | } 129 | 130 | public synchronized boolean isPositionInsideRenderableObjects( 131 | Vector position) { 132 | for (RenderableObject renderableObject : renderableObjects) { 133 | if (renderableObject.isPositionInsideObject(position)) { 134 | return true; 135 | } 136 | } 137 | return false; 138 | } 139 | 140 | public synchronized void removeObject(Object object) { 141 | if (object instanceof RenderableObject) { 142 | renderableObjects.remove(object); 143 | } 144 | if (object instanceof PositionedObject) { 145 | positionedObjects.remove(object); 146 | } 147 | if (object instanceof MovingObject) { 148 | movingObjects.remove(object); 149 | } 150 | if (object instanceof ElectricFieldContributor) { 151 | electricFieldContributors.remove(object); 152 | } 153 | if (object instanceof ElectricFieldReceiver) { 154 | electricFieldReceivers.remove(object); 155 | } 156 | if (object instanceof MagneticFieldContributor) { 157 | magneticFieldContributors.remove(object); 158 | } 159 | if (object instanceof MagneticFieldReceiver) { 160 | magneticFieldReceivers.remove(object); 161 | } 162 | } 163 | 164 | public synchronized void renderObjects(Graphics2D g2) { 165 | Collections.sort(renderableObjects, 166 | RenderPassComparator.renderPassComparator); 167 | for (RenderableObject o : renderableObjects) { 168 | o.render(g2); 169 | } 170 | } 171 | 172 | public synchronized void updateObjects(double timestep) { 173 | 174 | for (int i = 0; i < movingObjects.size(); i++) { 175 | MovingObject o1 = movingObjects.get(i); 176 | for (int j = i + 1; j < movingObjects.size(); j++) { 177 | 178 | MovingObject o2 = movingObjects.get(j); 179 | 180 | double radius = o1.getRadius() + o2.getRadius(); 181 | 182 | if (o1.getPosition().subtract(o2.getPosition()) 183 | .getNormSquared() <= radius * radius) { 184 | o1.collide(o2); 185 | } 186 | 187 | } 188 | for (int j = 0; j < positionedObjects.size(); j++) { 189 | PositionedObject o2 = positionedObjects.get(j); 190 | if (o2 instanceof MovingObject) { 191 | continue; 192 | } 193 | 194 | double radius = o1.getRadius() + o2.getRadius(); 195 | if (o1.getPosition().subtract(o2.getPosition()) 196 | .getNormSquared() <= radius * radius) { 197 | o1.collide(o2); 198 | } 199 | } 200 | } 201 | 202 | MovingObject[] movings = getAllMovingObjects(); 203 | 204 | Vector[] positions = new Vector[movings.length]; 205 | Vector[][] velocities = new Vector[movings.length][4]; 206 | Vector[][] accelerations = new Vector[movings.length][4]; 207 | 208 | // set starting conditions and k1: y0 is position and velocity and k1 is 209 | // the derivative 210 | for (int i = 0; i < movings.length; i++) { 211 | positions[i] = movings[i].getPosition(); 212 | velocities[i][0] = movings[i].getVelocity(); 213 | accelerations[i][0] = getForce(movings[i]).divide( 214 | movings[i].getMass()); 215 | } 216 | 217 | // get k2 218 | for (int i = 0; i < movings.length; i++) { 219 | Vector velocity = velocities[i][0]; 220 | Vector acceleration = accelerations[i][0]; 221 | 222 | movings[i].setPosition(positions[i].add(velocity 223 | .multiply(timestep * 0.5D))); 224 | movings[i].setVelocity(velocities[i][0].add(acceleration 225 | .multiply(timestep * 0.5D))); 226 | } 227 | for (int i = 0; i < movings.length; i++) { 228 | velocities[i][1] = movings[i].getVelocity(); 229 | accelerations[i][1] = getForce(movings[i]).divide( 230 | movings[i].getMass()); 231 | } 232 | 233 | // get k3 234 | for (int i = 0; i < movings.length; i++) { 235 | Vector velocity = velocities[i][1]; 236 | Vector acceleration = accelerations[i][1]; 237 | 238 | movings[i].setPosition(positions[i].add(velocity 239 | .multiply(timestep * 0.5D))); 240 | movings[i].setVelocity(velocities[i][0].add(acceleration 241 | .multiply(timestep * 0.5D))); 242 | } 243 | for (int i = 0; i < movings.length; i++) { 244 | velocities[i][2] = movings[i].getVelocity(); 245 | accelerations[i][2] = getForce(movings[i]).divide( 246 | movings[i].getMass()); 247 | } 248 | 249 | // get k4 250 | for (int i = 0; i < movings.length; i++) { 251 | Vector velocity = velocities[i][2]; 252 | Vector acceleration = accelerations[i][2]; 253 | 254 | movings[i].setPosition(positions[i].add(velocity 255 | .multiply(timestep * 0.5D))); 256 | movings[i].setVelocity(velocities[i][0].add(acceleration 257 | .multiply(timestep * 0.5D))); 258 | } 259 | for (int i = 0; i < movings.length; i++) { 260 | velocities[i][3] = movings[i].getVelocity(); 261 | accelerations[i][3] = getForce(movings[i]).divide( 262 | movings[i].getMass()); 263 | } 264 | 265 | // finalize 266 | for (int i = 0; i < movings.length; i++) { 267 | Vector position = positions[i].add(velocities[i][0] 268 | .add(velocities[i][1].add(velocities[i][2]).multiply(2D)) 269 | .add(velocities[i][3]).multiply(timestep / 6D)); 270 | Vector velocity = velocities[i][0].add(accelerations[i][0] 271 | .add(accelerations[i][1].add(accelerations[i][2]).multiply( 272 | 2D)).add(accelerations[i][3]) 273 | .multiply(timestep / 6D)); 274 | movings[i].setPosition(position); 275 | movings[i].setVelocity(velocity); 276 | } 277 | 278 | for (PositionedObject positionedObject : getAllPositionedObjects()) { 279 | if (positionedObject.getPosition().getX() < -50D 280 | || positionedObject.getPosition().getX() > 50D + Constants.WIDTH 281 | || positionedObject.getPosition().getY() < -50D 282 | || positionedObject.getPosition().getY() > 50D + Constants.HEIGHT) { 283 | removeObject(positionedObject); 284 | } 285 | } 286 | 287 | } 288 | 289 | } 290 | --------------------------------------------------------------------------------