Added more stuff.
This commit is contained in:
parent
3807e0bae3
commit
3f8677d889
1
pom.xml
1
pom.xml
|
@ -28,6 +28,7 @@
|
||||||
<version>0.0.4</version>
|
<version>0.0.4</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>3.8.1</release>
|
<release>3.8.1</release>
|
||||||
|
<jlinkImageName>ThreadRipper</jlinkImageName>
|
||||||
<launcher>launcher</launcher>
|
<launcher>launcher</launcher>
|
||||||
<mainClass>nl.andrewlalis.threadripper.ThreadRipperApplication</mainClass>
|
<mainClass>nl.andrewlalis.threadripper.ThreadRipperApplication</mainClass>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
package nl.andrewlalis.threadripper;
|
package nl.andrewlalis.threadripper;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.scene.Group;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import nl.andrewlalis.threadripper.engine.ParticleChamber;
|
import nl.andrewlalis.threadripper.engine.ParticleChamber;
|
||||||
import nl.andrewlalis.threadripper.engine.Vec2;
|
import nl.andrewlalis.threadripper.engine.Vec2;
|
||||||
import nl.andrewlalis.threadripper.particle.Particle;
|
|
||||||
import nl.andrewlalis.threadripper.particle.ParticleFactory;
|
import nl.andrewlalis.threadripper.particle.ParticleFactory;
|
||||||
|
import nl.andrewlalis.threadripper.render.ParticleChamberRenderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application starting point.
|
* Main application starting point.
|
||||||
|
@ -20,46 +23,34 @@ import nl.andrewlalis.threadripper.particle.ParticleFactory;
|
||||||
public class ThreadRipperApplication extends Application {
|
public class ThreadRipperApplication extends Application {
|
||||||
|
|
||||||
private final Canvas canvas;
|
private final Canvas canvas;
|
||||||
|
|
||||||
private final ParticleChamber chamber;
|
private final ParticleChamber chamber;
|
||||||
|
private final Thread chamberThread;
|
||||||
|
private final ParticleChamberRenderer renderer;
|
||||||
|
private final Thread renderThread;
|
||||||
|
|
||||||
public ThreadRipperApplication() {
|
public ThreadRipperApplication() {
|
||||||
this.canvas = new Canvas(1000, 1000);
|
this.canvas = new Canvas(800, 800);
|
||||||
this.chamber = new ParticleChamber(canvas);
|
this.chamber = new ParticleChamber();
|
||||||
|
this.renderer = new ParticleChamberRenderer(chamber, canvas);
|
||||||
|
|
||||||
|
|
||||||
ParticleFactory factory = new ParticleFactory(
|
ParticleFactory factory = new ParticleFactory(
|
||||||
10000.0,
|
0.1, 100000000000000.0,
|
||||||
10000000.0,
|
0, 0,
|
||||||
0,
|
0.5, 5,
|
||||||
0,
|
new Vec2(0, 0), new Vec2(800, 800),
|
||||||
new Vec2(350, 250),
|
new Vec2(-50, -50), new Vec2(50, 50)
|
||||||
new Vec2(450, 400),
|
|
||||||
new Vec2(10, -2),
|
|
||||||
new Vec2(25, 2)
|
|
||||||
);
|
);
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 50; i++) {
|
||||||
this.chamber.addParticle(factory.build());
|
this.chamber.addParticle(factory.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.chamber.addParticle(new Particle(new Vec2(400, 298), new Vec2(19, 0), 100000.0, 0.0));
|
this.chamberThread = new Thread(this.chamber);
|
||||||
// this.chamber.addParticle(new Particle(new Vec2(400, 300), new Vec2(20, 0), 10000000000.0, 0.0));
|
this.chamberThread.start();
|
||||||
this.chamber.addParticle(new Particle(new Vec2(400, 500), new Vec2(0, 0), 1000000000000000.0, 0.0));
|
|
||||||
|
|
||||||
// ParticleFactory factory1 = new ParticleFactory(
|
this.renderThread = new Thread(this.renderer);
|
||||||
// 1000000000.0,
|
this.renderThread.start();
|
||||||
// 10000000000000.0,
|
|
||||||
// 0,
|
|
||||||
// 0,
|
|
||||||
// new Vec2(700, 700),
|
|
||||||
// new Vec2(900, 900),
|
|
||||||
// new Vec2(0, 0),
|
|
||||||
// new Vec2(0, 0)
|
|
||||||
// );
|
|
||||||
// for (int i = 0; i < 500; i++) {
|
|
||||||
// this.chamber.addParticle(factory1.build());
|
|
||||||
// }
|
|
||||||
|
|
||||||
Thread particleChamberThread = new Thread(chamber);
|
|
||||||
particleChamberThread.setName("ParticleChamber");
|
|
||||||
particleChamberThread.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@ -71,22 +62,47 @@ public class ThreadRipperApplication extends Application {
|
||||||
public void start(Stage stage) throws Exception {
|
public void start(Stage stage) throws Exception {
|
||||||
stage.setTitle("ThreadRipper");
|
stage.setTitle("ThreadRipper");
|
||||||
|
|
||||||
GraphicsContext gc = this.canvas.getGraphicsContext2D();
|
BorderPane borderPane = new BorderPane();
|
||||||
|
borderPane.setCenter(this.canvas);
|
||||||
|
|
||||||
gc.setFill(Color.GREEN);
|
TextField simRateField = new TextField(Double.toString(this.chamber.getSimulationRate()));
|
||||||
gc.fillOval(10, 60, 30, 30);
|
Button simRateUpdate = new Button("Apply");
|
||||||
|
simRateUpdate.setOnMouseClicked(mouseEvent -> {
|
||||||
|
double simRate = this.chamber.getSimulationRate();
|
||||||
|
try {
|
||||||
|
simRate = Double.parseDouble(simRateField.getText().trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
simRateField.clear();
|
||||||
|
}
|
||||||
|
this.chamber.setSimulationRate(simRate);
|
||||||
|
});
|
||||||
|
HBox bottomPanel = new HBox(
|
||||||
|
new Label("Simulation Rate"),
|
||||||
|
simRateField,
|
||||||
|
simRateUpdate
|
||||||
|
);
|
||||||
|
borderPane.setBottom(bottomPanel);
|
||||||
|
|
||||||
|
Scene scene = new Scene(borderPane);
|
||||||
|
|
||||||
|
ChangeListener<Number> sceneSizeListener = (observable, oldValue, newValue) -> {
|
||||||
|
this.canvas.setWidth(scene.getWidth());
|
||||||
|
this.canvas.setHeight(scene.getHeight());
|
||||||
|
};
|
||||||
|
scene.widthProperty().addListener(sceneSizeListener);
|
||||||
|
scene.heightProperty().addListener(sceneSizeListener);
|
||||||
|
|
||||||
Group root = new Group();
|
|
||||||
root.getChildren().add(this.canvas);
|
|
||||||
Scene scene = new Scene(root);
|
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
|
stage.setMaximized(true);
|
||||||
stage.show();
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() throws Exception {
|
public void stop() throws Exception {
|
||||||
this.chamber.setRunning(false);
|
this.chamber.setRunning(false);
|
||||||
|
this.chamberThread.join();
|
||||||
|
this.renderer.setRunning(false);
|
||||||
|
this.renderThread.join();
|
||||||
super.stop();
|
super.stop();
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package nl.andrewlalis.threadripper.engine;
|
package nl.andrewlalis.threadripper.engine;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.canvas.Canvas;
|
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import nl.andrewlalis.threadripper.particle.Particle;
|
import nl.andrewlalis.threadripper.particle.Particle;
|
||||||
|
|
||||||
|
@ -13,28 +9,28 @@ import java.util.concurrent.*;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ParticleChamber implements Runnable {
|
public class ParticleChamber implements Runnable {
|
||||||
private static final int DEFAULT_THREAD_POOL = 100;
|
private static final int DEFAULT_THREAD_POOL = 100;
|
||||||
private static final double DEFAULT_UPDATE_FPS = 60;
|
private static final double DEFAULT_UPDATES_PER_SECOND = 60;
|
||||||
|
|
||||||
private final Set<Particle> particles;
|
private final Set<Particle> particles;
|
||||||
|
private double simulationRate = 1.0;
|
||||||
|
private boolean allowCollision = true;
|
||||||
|
|
||||||
private int threadCount;
|
private int threadCount;
|
||||||
private ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
private CompletionService<ParticleUpdate> particleUpdateService;
|
private final CompletionService<ParticleUpdate> particleUpdateService;
|
||||||
|
|
||||||
private boolean running;
|
private boolean running;
|
||||||
private double updateFps;
|
private double updateFps;
|
||||||
|
private double secondsSinceLastUpdate;
|
||||||
|
|
||||||
private final Canvas canvas;
|
public ParticleChamber() {
|
||||||
|
|
||||||
public ParticleChamber(Canvas canvas) {
|
|
||||||
this.particles = new HashSet<>();
|
this.particles = new HashSet<>();
|
||||||
|
|
||||||
this.threadCount = DEFAULT_THREAD_POOL;
|
this.threadCount = DEFAULT_THREAD_POOL;
|
||||||
this.executorService = Executors.newFixedThreadPool(this.threadCount);
|
this.executorService = Executors.newFixedThreadPool(this.threadCount);
|
||||||
this.particleUpdateService = new ExecutorCompletionService<>(this.executorService);
|
this.particleUpdateService = new ExecutorCompletionService<>(this.executorService);
|
||||||
|
|
||||||
this.updateFps = DEFAULT_UPDATE_FPS;
|
this.updateFps = DEFAULT_UPDATES_PER_SECOND;
|
||||||
|
|
||||||
this.canvas = canvas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +45,14 @@ public class ParticleChamber implements Runnable {
|
||||||
this.running = running;
|
this.running = running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void setSimulationRate(double simulationRate) {
|
||||||
|
this.simulationRate = simulationRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setAllowCollision(boolean allowCollision) {
|
||||||
|
this.allowCollision = allowCollision;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
@ -61,6 +65,7 @@ public class ParticleChamber implements Runnable {
|
||||||
final long elapsedMilliseconds = currentTimeMilliseconds - previousTimeMilliseconds;
|
final long elapsedMilliseconds = currentTimeMilliseconds - previousTimeMilliseconds;
|
||||||
|
|
||||||
millisecondsSinceLastUpdate += elapsedMilliseconds;
|
millisecondsSinceLastUpdate += elapsedMilliseconds;
|
||||||
|
this.secondsSinceLastUpdate = millisecondsSinceLastUpdate / 1000.0;
|
||||||
|
|
||||||
final double millisecondsPerFrame = 1000.0 / this.updateFps;
|
final double millisecondsPerFrame = 1000.0 / this.updateFps;
|
||||||
|
|
||||||
|
@ -68,12 +73,12 @@ public class ParticleChamber implements Runnable {
|
||||||
final double secondsSinceLastUpdate = millisecondsSinceLastUpdate / 1000.0;
|
final double secondsSinceLastUpdate = millisecondsSinceLastUpdate / 1000.0;
|
||||||
millisecondsSinceLastUpdate = 0L;
|
millisecondsSinceLastUpdate = 0L;
|
||||||
//log.info("Updating particles after {} seconds elapsed.", secondsSinceLastUpdate);
|
//log.info("Updating particles after {} seconds elapsed.", secondsSinceLastUpdate);
|
||||||
this.updateParticles(secondsSinceLastUpdate);
|
this.updateParticles(secondsSinceLastUpdate * this.simulationRate);
|
||||||
this.drawParticles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previousTimeMilliseconds = currentTimeMilliseconds;
|
previousTimeMilliseconds = currentTimeMilliseconds;
|
||||||
}
|
}
|
||||||
|
this.executorService.shutdown();
|
||||||
log.info("Particle chamber stopped.");
|
log.info("Particle chamber stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +89,7 @@ public class ParticleChamber implements Runnable {
|
||||||
private void updateParticles(double deltaTime) {
|
private void updateParticles(double deltaTime) {
|
||||||
// First submit a new callable task for each particle.
|
// First submit a new callable task for each particle.
|
||||||
for (Particle particle : this.particles) {
|
for (Particle particle : this.particles) {
|
||||||
this.particleUpdateService.submit(new ParticleUpdater(particle, this.particles));
|
this.particleUpdateService.submit(new ParticleUpdater(particle, this.particles, this.allowCollision));
|
||||||
}
|
}
|
||||||
|
|
||||||
int updatesReceived = 0;
|
int updatesReceived = 0;
|
||||||
|
@ -103,28 +108,41 @@ public class ParticleChamber implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<Particle> particlesToRemove = new HashSet<>(this.particles.size());
|
||||||
|
Set<Particle> particlesToAdd = new HashSet<>(this.particles.size());
|
||||||
// Implement the updates for each particle.
|
// Implement the updates for each particle.
|
||||||
for (ParticleUpdate update : updates) {
|
for (ParticleUpdate update : updates) {
|
||||||
update.getFocusParticle().updateVelocity(update.getAcceleration(), deltaTime);
|
update.getFocusParticle().updateVelocity(update.getAcceleration(), deltaTime);
|
||||||
update.getFocusParticle().updatePosition(deltaTime);
|
update.getFocusParticle().updatePosition(deltaTime);
|
||||||
//log.info("Particle updated: {}", update.getFocusParticle().toString());
|
if (!update.getCollidesWith().isEmpty() && !particlesToRemove.contains(update.getFocusParticle())) {
|
||||||
|
particlesToRemove.addAll(update.getCollidesWith());
|
||||||
|
particlesToRemove.add(update.getFocusParticle());
|
||||||
|
// Create a new particle as combination.
|
||||||
|
Particle p = update.getFocusParticle();
|
||||||
|
for (Particle collided : update.getCollidesWith()) {
|
||||||
|
p = p.combine(collided);
|
||||||
|
}
|
||||||
|
particlesToAdd.add(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.particles.removeAll(particlesToRemove);
|
||||||
|
this.particles.addAll(particlesToAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public double getSecondsSinceLastUpdate() {
|
||||||
* Draws all particles on the canvas.
|
return this.secondsSinceLastUpdate;
|
||||||
*/
|
}
|
||||||
private void drawParticles() {
|
|
||||||
Platform.runLater(() -> {
|
public double getSimulationRate() {
|
||||||
GraphicsContext gc = this.canvas.getGraphicsContext2D();
|
return simulationRate;
|
||||||
gc.setFill(Color.WHITE);
|
}
|
||||||
gc.clearRect(0, 0, this.canvas.getWidth(), this.canvas.getHeight());
|
|
||||||
gc.setFill(Color.BLACK);
|
public Set<Particle> getCopyOfParticles() {
|
||||||
gc.setStroke(Color.BLUE);
|
Set<Particle> set = new HashSet<>(this.particles.size());
|
||||||
for (Particle particle : this.particles) {
|
for (Particle p : this.particles) {
|
||||||
Vec2 pos = particle.getPosition();
|
set.add(p.getCopy());
|
||||||
gc.fillOval(pos.getX() - 3, pos.getY() - 3, 6, 6);
|
}
|
||||||
}
|
return set;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package nl.andrewlalis.threadripper.engine;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import nl.andrewlalis.threadripper.particle.Particle;
|
import nl.andrewlalis.threadripper.particle.Particle;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes an update to a particle's motion.
|
* Describes an update to a particle's motion.
|
||||||
*/
|
*/
|
||||||
|
@ -19,8 +21,14 @@ public class ParticleUpdate {
|
||||||
*/
|
*/
|
||||||
private final Vec2 acceleration;
|
private final Vec2 acceleration;
|
||||||
|
|
||||||
public ParticleUpdate(Particle focusParticle, Vec2 acceleration) {
|
/**
|
||||||
|
* A set of particles that this one has collided with.
|
||||||
|
*/
|
||||||
|
private final Set<Particle> collidesWith;
|
||||||
|
|
||||||
|
public ParticleUpdate(Particle focusParticle, Vec2 acceleration, Set<Particle> collidesWith) {
|
||||||
this.focusParticle = focusParticle;
|
this.focusParticle = focusParticle;
|
||||||
this.acceleration = acceleration;
|
this.acceleration = acceleration;
|
||||||
|
this.collidesWith = collidesWith;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewlalis.threadripper.engine;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import nl.andrewlalis.threadripper.particle.Particle;
|
import nl.andrewlalis.threadripper.particle.Particle;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
@ -14,24 +15,40 @@ import java.util.concurrent.Callable;
|
||||||
public class ParticleUpdater implements Callable<ParticleUpdate> {
|
public class ParticleUpdater implements Callable<ParticleUpdate> {
|
||||||
private final Particle focusParticle;
|
private final Particle focusParticle;
|
||||||
private final Set<Particle> particles;
|
private final Set<Particle> particles;
|
||||||
|
private final boolean allowCollision;
|
||||||
|
|
||||||
public ParticleUpdater(Particle focusParticle, Set<Particle> particles) {
|
public ParticleUpdater(
|
||||||
|
Particle focusParticle,
|
||||||
|
Set<Particle> particles,
|
||||||
|
boolean allowCollision
|
||||||
|
) {
|
||||||
this.focusParticle = focusParticle;
|
this.focusParticle = focusParticle;
|
||||||
this.particles = particles;
|
this.particles = particles;
|
||||||
|
this.allowCollision = allowCollision;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParticleUpdate call() throws Exception {
|
public ParticleUpdate call() throws Exception {
|
||||||
double accelerationX = 0L;
|
double accelerationX = 0L;
|
||||||
double accelerationY = 0L;
|
double accelerationY = 0L;
|
||||||
|
Set<Particle> collidesWith = new HashSet<>();
|
||||||
for (Particle particle : this.particles) {
|
for (Particle particle : this.particles) {
|
||||||
if (!particle.equals(this.focusParticle)) {
|
if (!particle.equals(this.focusParticle)) {
|
||||||
Vec2 partialAcceleration = this.computeAcceleration(particle);
|
Vec2 partialAcceleration = this.computeAcceleration(particle);
|
||||||
accelerationX += partialAcceleration.getX();
|
accelerationX += partialAcceleration.getX();
|
||||||
accelerationY += partialAcceleration.getY();
|
accelerationY += partialAcceleration.getY();
|
||||||
|
|
||||||
|
// Collision detection:
|
||||||
|
final double distance = this.focusParticle.getPosition().distance(particle.getPosition());
|
||||||
|
if (
|
||||||
|
Math.abs(this.focusParticle.getRadius() - particle.getRadius()) <= distance
|
||||||
|
&& distance <= (this.focusParticle.getRadius() + particle.getRadius())
|
||||||
|
) {
|
||||||
|
collidesWith.add(particle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ParticleUpdate(this.focusParticle, new Vec2(accelerationX, accelerationY));
|
return new ParticleUpdate(this.focusParticle, new Vec2(accelerationX, accelerationY), collidesWith);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,17 +68,13 @@ public class ParticleUpdater implements Callable<ParticleUpdate> {
|
||||||
final double gravityNewtons = Constants.G * (this.focusParticle.getMass() * other.getMass()) / Math.pow(radius, 2);
|
final double gravityNewtons = Constants.G * (this.focusParticle.getMass() * other.getMass()) / Math.pow(radius, 2);
|
||||||
final double gravityAcceleration = gravityNewtons / this.focusParticle.getMass();
|
final double gravityAcceleration = gravityNewtons / this.focusParticle.getMass();
|
||||||
final Vec2 gravityAccelerationVector = Vec2.fromPolar(gravityAcceleration, angle);
|
final Vec2 gravityAccelerationVector = Vec2.fromPolar(gravityAcceleration, angle);
|
||||||
// log.info(
|
|
||||||
// "Force of gravity between particles {} and {} is {} Newtons.\nCauses {} m/s^2 acceleration on particle {}.",
|
|
||||||
// this.focusParticle.getId(),
|
|
||||||
// other.getId(),
|
|
||||||
// gravityNewtons,
|
|
||||||
// gravityAcceleration,
|
|
||||||
// this.focusParticle.getId()
|
|
||||||
// );
|
|
||||||
|
|
||||||
final double emNewtons = Constants.Ke * (this.focusParticle.getCharge() * other.getCharge()) / Math.pow(radius, 2);
|
final double emNewtons = Constants.Ke * (this.focusParticle.getCharge() * other.getCharge()) / Math.pow(radius, 2);
|
||||||
final double emAcceleration = emNewtons / this.focusParticle.getMass();
|
final boolean isRepulsion = emNewtons < 0.0;
|
||||||
|
double emAcceleration = emNewtons / this.focusParticle.getMass();
|
||||||
|
if (isRepulsion) {
|
||||||
|
emAcceleration *= -1.0;
|
||||||
|
}
|
||||||
final Vec2 emAccelerationVector = Vec2.fromPolar(emAcceleration, angle);
|
final Vec2 emAccelerationVector = Vec2.fromPolar(emAcceleration, angle);
|
||||||
|
|
||||||
final Vec2 totalAcceleration = gravityAccelerationVector.add(emAccelerationVector);
|
final Vec2 totalAcceleration = gravityAccelerationVector.add(emAccelerationVector);
|
||||||
|
|
|
@ -50,4 +50,8 @@ public class Vec2 {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("[%f, %f]", this.getX(), this.getY());
|
return String.format("[%f, %f]", this.getX(), this.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vec2 getCopy() {
|
||||||
|
return new Vec2(this.getX(), this.getY());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ public class Particle {
|
||||||
/**
|
/**
|
||||||
* Unique id for this particle.
|
* Unique id for this particle.
|
||||||
*/
|
*/
|
||||||
private long id;
|
private final long id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The particle's position, in meters.
|
* The particle's position, in meters.
|
||||||
|
@ -37,15 +37,25 @@ public class Particle {
|
||||||
*/
|
*/
|
||||||
private double charge;
|
private double charge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The particle's radius, in meters.
|
||||||
|
*/
|
||||||
|
private double radius;
|
||||||
|
|
||||||
public Particle(Vec2 position) {
|
public Particle(Vec2 position) {
|
||||||
this(position, new Vec2(0, 0), 1, 0);
|
this(position, new Vec2(0, 0), 1, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Particle(Vec2 position, Vec2 velocity, double mass, double charge) {
|
public Particle(Vec2 position, double mass, double charge, double radius) {
|
||||||
|
this(position, new Vec2(0, 0), mass, charge, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Particle(Vec2 position, Vec2 velocity, double mass, double charge, double radius) {
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.velocity = velocity;
|
this.velocity = velocity;
|
||||||
this.mass = mass;
|
this.mass = mass;
|
||||||
this.charge = charge;
|
this.charge = charge;
|
||||||
|
this.radius = radius;
|
||||||
|
|
||||||
this.id = NEXT_PARTICLE_ID;
|
this.id = NEXT_PARTICLE_ID;
|
||||||
NEXT_PARTICLE_ID++;
|
NEXT_PARTICLE_ID++;
|
||||||
|
@ -95,4 +105,32 @@ public class Particle {
|
||||||
this.getVelocity()
|
this.getVelocity()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Particle getCopy() {
|
||||||
|
return new Particle(
|
||||||
|
this.getPosition().getCopy(),
|
||||||
|
this.getVelocity().getCopy(),
|
||||||
|
this.getMass(),
|
||||||
|
this.getCharge(),
|
||||||
|
this.getRadius()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Particle combine(Particle other) {
|
||||||
|
final Vec2 position = new Vec2(
|
||||||
|
(this.getPosition().getX() + other.getPosition().getX()) / 2.0,
|
||||||
|
(this.getPosition().getY() + other.getPosition().getY()) / 2.0
|
||||||
|
);
|
||||||
|
final Vec2 velocity = new Vec2(
|
||||||
|
(this.getVelocity().getX() + other.getVelocity().getX()) / 2.0,
|
||||||
|
(this.getVelocity().getY() + other.getVelocity().getY()) / 2.0
|
||||||
|
);
|
||||||
|
return new Particle(
|
||||||
|
position,
|
||||||
|
velocity,
|
||||||
|
this.getMass() + other.getMass(),
|
||||||
|
this.getCharge() + other.getCharge(),
|
||||||
|
this.getRadius() + other.getRadius()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ public class ParticleFactory {
|
||||||
private final double maxMass;
|
private final double maxMass;
|
||||||
private final double minCharge;
|
private final double minCharge;
|
||||||
private final double maxCharge;
|
private final double maxCharge;
|
||||||
|
private final double minRadius;
|
||||||
|
private final double maxRadius;
|
||||||
private final Vec2 minPosition;
|
private final Vec2 minPosition;
|
||||||
private final Vec2 maxPosition;
|
private final Vec2 maxPosition;
|
||||||
private final Vec2 minVelocity;
|
private final Vec2 minVelocity;
|
||||||
|
@ -19,6 +21,8 @@ public class ParticleFactory {
|
||||||
double maxMass,
|
double maxMass,
|
||||||
double minCharge,
|
double minCharge,
|
||||||
double maxCharge,
|
double maxCharge,
|
||||||
|
double minRadius,
|
||||||
|
double maxRadius,
|
||||||
Vec2 minPosition,
|
Vec2 minPosition,
|
||||||
Vec2 maxPosition,
|
Vec2 maxPosition,
|
||||||
Vec2 minVelocity,
|
Vec2 minVelocity,
|
||||||
|
@ -28,6 +32,8 @@ public class ParticleFactory {
|
||||||
this.maxMass = maxMass;
|
this.maxMass = maxMass;
|
||||||
this.minCharge = minCharge;
|
this.minCharge = minCharge;
|
||||||
this.maxCharge = maxCharge;
|
this.maxCharge = maxCharge;
|
||||||
|
this.minRadius = minRadius;
|
||||||
|
this.maxRadius = maxRadius;
|
||||||
this.minPosition = minPosition;
|
this.minPosition = minPosition;
|
||||||
this.maxPosition = maxPosition;
|
this.maxPosition = maxPosition;
|
||||||
this.minVelocity = minVelocity;
|
this.minVelocity = minVelocity;
|
||||||
|
@ -45,14 +51,20 @@ public class ParticleFactory {
|
||||||
random.nextDouble(this.minVelocity.getY(), this.maxVelocity.getY())
|
random.nextDouble(this.minVelocity.getY(), this.maxVelocity.getY())
|
||||||
);
|
);
|
||||||
final double mass = random.nextDouble(this.minMass, this.maxMass);
|
final double mass = random.nextDouble(this.minMass, this.maxMass);
|
||||||
// final double charge = random.nextDouble(this.minCharge, this.maxCharge);
|
final double charge;
|
||||||
|
if (this.minCharge == 0.0 && this.maxCharge == 0.0) {
|
||||||
|
charge = 0.0;
|
||||||
|
} else {
|
||||||
|
charge = random.nextDouble(this.minCharge, this.maxCharge);
|
||||||
|
}
|
||||||
|
final double radius = random.nextDouble(this.minRadius, this.maxRadius);
|
||||||
|
|
||||||
final double charge = 0;
|
|
||||||
return new Particle(
|
return new Particle(
|
||||||
position,
|
position,
|
||||||
velocity,
|
velocity,
|
||||||
mass,
|
mass,
|
||||||
charge
|
charge,
|
||||||
|
radius
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
package nl.andrewlalis.threadripper.render;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import nl.andrewlalis.threadripper.engine.ParticleChamber;
|
||||||
|
import nl.andrewlalis.threadripper.engine.Vec2;
|
||||||
|
import nl.andrewlalis.threadripper.particle.Particle;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The renderer is responsible for drawing a particle chamber's contents to
|
||||||
|
* a canvas.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ParticleChamberRenderer implements Runnable {
|
||||||
|
private static final double DEFAULT_FPS = 60.0;
|
||||||
|
private static final int FPS_READING_COUNT = 360;
|
||||||
|
|
||||||
|
private final ParticleChamber chamber;
|
||||||
|
private final Canvas canvas;
|
||||||
|
|
||||||
|
private double targetFps;
|
||||||
|
private boolean running;
|
||||||
|
|
||||||
|
private Vec2 offset;
|
||||||
|
private double scale;
|
||||||
|
|
||||||
|
private LinkedList<Double> recentFpsReadings;
|
||||||
|
|
||||||
|
public ParticleChamberRenderer(ParticleChamber chamber, Canvas canvas) {
|
||||||
|
this.chamber = chamber;
|
||||||
|
this.canvas = canvas;
|
||||||
|
|
||||||
|
this.targetFps = DEFAULT_FPS;
|
||||||
|
this.recentFpsReadings = new LinkedList<>();
|
||||||
|
|
||||||
|
this.offset = new Vec2(0, 0);
|
||||||
|
this.scale = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setRunning(boolean running) {
|
||||||
|
this.running = running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setTargetFps(double targetFps) {
|
||||||
|
if (targetFps > 0) {
|
||||||
|
this.targetFps = targetFps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setOffset(Vec2 offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setScale(double scale) {
|
||||||
|
if (scale > 0) {
|
||||||
|
this.scale = scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
this.running = true;
|
||||||
|
long previousTimeMilliseconds = System.currentTimeMillis();
|
||||||
|
long millisecondsSinceLastFrame = 0L;
|
||||||
|
|
||||||
|
log.info("Starting particle chamber renderer.");
|
||||||
|
while (this.running) {
|
||||||
|
final long currentTimeMilliseconds = System.currentTimeMillis();
|
||||||
|
final long elapsedMilliseconds = currentTimeMilliseconds - previousTimeMilliseconds;
|
||||||
|
|
||||||
|
millisecondsSinceLastFrame += elapsedMilliseconds;
|
||||||
|
|
||||||
|
final double millisecondsPerFrame = 1000.0 / this.targetFps;
|
||||||
|
|
||||||
|
if (millisecondsSinceLastFrame > millisecondsPerFrame) {
|
||||||
|
final double currentFps = 1000.0 / millisecondsSinceLastFrame;
|
||||||
|
this.updateFpsReading(currentFps);
|
||||||
|
this.draw();
|
||||||
|
millisecondsSinceLastFrame = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousTimeMilliseconds = currentTimeMilliseconds;
|
||||||
|
}
|
||||||
|
log.info("Particle chamber renderer stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void draw() {
|
||||||
|
final double secondsSinceLastUpdate = this.chamber.getSecondsSinceLastUpdate();
|
||||||
|
final Set<Particle> particles = this.chamber.getCopyOfParticles();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
GraphicsContext gc = this.canvas.getGraphicsContext2D();
|
||||||
|
gc.setFill(Color.WHITE);
|
||||||
|
gc.clearRect(0, 0, this.canvas.getWidth(), this.canvas.getHeight());
|
||||||
|
|
||||||
|
|
||||||
|
for (Particle particle : particles) {
|
||||||
|
particle.updatePosition(secondsSinceLastUpdate);
|
||||||
|
Vec2 pos = particle.getPosition().add(this.offset);
|
||||||
|
if (particle.getCharge() > 0) {
|
||||||
|
gc.setFill(Color.BLUE);
|
||||||
|
} else {
|
||||||
|
gc.setFill(Color.RED);
|
||||||
|
}
|
||||||
|
gc.fillOval(
|
||||||
|
pos.getX() - particle.getRadius(),
|
||||||
|
pos.getY() - particle.getRadius(),
|
||||||
|
particle.getRadius() * 2,
|
||||||
|
particle.getRadius() * 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.setFill(Color.BLACK);
|
||||||
|
gc.fillText(String.format("FPS: %.2f", this.getAverageFps()), 10, 10);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFpsReading(double recentFpsReading) {
|
||||||
|
this.recentFpsReadings.addFirst(recentFpsReading);
|
||||||
|
if (this.recentFpsReadings.size() > FPS_READING_COUNT) {
|
||||||
|
this.recentFpsReadings.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAverageFps() {
|
||||||
|
if (this.recentFpsReadings.size() == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
double sum = 0.0;
|
||||||
|
for (double reading : this.recentFpsReadings) {
|
||||||
|
sum += reading;
|
||||||
|
}
|
||||||
|
return sum / this.recentFpsReadings.size();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue