I am a student and I am studying physical modeling of processes. I'm new to GUI. I have two processes. The first process randomly scatters the balls onto the surface of the cylinder. Here's the code for it
public class Generation {
private static final Color[] COLORS = new Color[] { RED, YELLOW, GREEN, BROWN, BLUE, PINK, BLACK };
private final Tube tube;
private final int numberOfParticle;
private final double radius;
public Generation(Tube tube, int numberOfParticle) {
this.tube = tube;
this.numberOfParticle = numberOfParticle;
radius = Math.sqrt(tube.getRadius()*tube.getHeight()/(2*numberOfParticle));
}
private Particle RandomParticle (Color color) {
double phi = 2*Math.PI*Math.random();
double z = GetRandomNumberUsingNextDouble(-tube.getHeight()/2,tube.getHeight()/2);
Particle particle = new Particle(phi,tube.getRadius(),z,radius,color);
return particle;
}
private static double GetRandomNumberUsingNextDouble(double min, double max){
Random r = new Random();
return min + (max - min) * r.nextDouble();
}
public ObservableList<Particle> ParticlesGeneration() {
ObservableList<Particle> particlesList = FXCollections.observableArrayList()
for (int i = 0; i < numberOfParticle; i++) {
Particle particle = RandomParticle(COLORS[i % COLORS.length]);
particlesList.add(particle);
}
return particlesList;
}
}
Particle class
public class Particle {
private final double radius;
private double phi, rho, z;
private final Sphere particle;
public Particle(double phi, double rho, double z, double radius, Color color) {
this.particle = new Sphere(radius);
this.radius = radius;
this.phi = phi;
this.rho = rho;
this.z = z;
particle.setMaterial(new PhongMaterial(color));
particle.setTranslateX(rho * Math.cos(phi));
particle.setTranslateY(z);
particle.setTranslateZ(rho * Math.sin(phi));
}
private static double distance(double x1, double y1, double x2, double y2) {
x2 -= x1;
y2 -= y1;
return Math.sqrt(x2 * x2 + y2 * y2);
}
public double distance(Particle p) { return distance(getPhi()*getRho(), getZ(), p.getPhi()*p.getRho(),p.getZ());}
public double getRadius() { return radius; }
public double getPhi() { return phi; }
public void setPhi(double phi) { this.phi = phi; }
public double getRho() { return rho; }
public void setRho(double rho) { this.rho = rho; }
public double getZ() { return z; }
public void setZ(double z) { this.z = z; }
public Sphere getParticle() { return particle; }
}
Cylinder class
public class Tube {
private double radius, height;
private final Cylinder tube;
private final Color color;
public Tube(double radius, double height, Color color) {
this.height = height;
this.color = color;
this.radius = radius;
this.tube = new Cylinder(radius,height);
tube.setMaterial(new PhongMaterial(color));
}
public Color getColor() { return color; }
public double getRadius() { return radius; }
public double getHeight() { return height; }
public Cylinder getTube() { return tube; }
public void setRadius(double radius) { this.radius = radius; }
public void setHeight(double height) { this.height = height; }
}
The second process is to create a dense packing of randomly scattered particles
public class Minimization {
private double COEFFICIENT_FOR_ANGLE = 1;
private double COEFFICIENT_FOR_Z = 1;
@SuppressWarnings("FieldCanBeLocal")
private final double ACCEPTABLE_COEFFICIENT_VALUE_K_ANGEL = 0.0001;
@SuppressWarnings("FieldCanBeLocal")
private final double ACCEPTABLE_COEFFICIENT_VALUE_K_Z = 0.0001;
@SuppressWarnings("FieldCanBeLocal")
private final double ACCEPTABLE_VALUE_OF_ENERGY_DIFFERENCE = 0.0001;
@SuppressWarnings("FieldCanBeLocal")
public static final double MAX_VALUE = 1.7976931348623157E308;
private final ObservableList<Particle> poissonDiskCoordinatesParticles;
private final int numberOfParticle, degree;
private final double heightTube;
public Minimization(ObservableList<Particle> poissonDiskCoordinatesParticles, int degree, Tube tube) {
this.poissonDiskCoordinatesParticles = poissonDiskCoordinatesParticles;
this.degree = degree;
numberOfParticle = poissonDiskCoordinatesParticles.size();
heightTube = tube.getHeight();
}
public ObservableList<Particle> minimization () {
ObservableList<Particle> list = poissonDiskCoordinatesParticles;
double energyOld = MAX_VALUE;
double energyNew = energyOfSystem(list);
while (COEFFICIENT_FOR_ANGLE > ACCEPTABLE_COEFFICIENT_VALUE_K_ANGEL && COEFFICIENT_FOR_Z > ACCEPTABLE_COEFFICIENT_VALUE_K_Z && energyOld > energyNew) {
if (energyOld - energyNew < ACCEPTABLE_VALUE_OF_ENERGY_DIFFERENCE) {
COEFFICIENT_FOR_Z = COEFFICIENT_FOR_Z/2;
COEFFICIENT_FOR_ANGLE = COEFFICIENT_FOR_ANGLE/2;
}
energyOld = energyNew;
stepOfMinimization(list);
energyNew = energyOfSystem(list);
}
return list;
}
private void stepOfMinimization (ObservableList<Particle> list) {
for (int i = 0; i < numberOfParticle; i++) {
Particle particle = newParticle(list, i);
if (energyOfSystem(list) < energyOfSystem(list,particle,i)) {
list.set(i, particle);
}
}
}
private Particle newParticle(ObservableList<Particle> coordinates, int i) {
Particle particle = coordinates.get(i);
double ForcePhi = 0.0;
double ForceZ = 0.0;
for (int j = 0; j < numberOfParticle; j++) {
Particle jParticle = coordinates.get(j);
if (i != j) {
ForcePhi += (numberOfParticle * particle.getRho() *
(particle.getPhi() - jParticle.getPhi())) / pow(particle.distance(jParticle), degree + 2);
ForceZ += (numberOfParticle *
(particle.getZ() - jParticle.getZ())) / pow(particle.distance(jParticle), degree + 2);
}
}
ForceZ += degree / pow(particle.getZ() - heightTube / 2, degree + 1) + degree / pow(particle.getZ() + heightTube / 2, degree + 1);
particle.setPhi(particle.getPhi() + COEFFICIENT_FOR_ANGLE * ForcePhi);
particle.setZ(particle.getZ() + COEFFICIENT_FOR_Z * ForceZ);
return particle;
}
private double energyOfSystem (ObservableList<Particle> coordinates) {
double Energy = 0;
for (int i = 0; i < numberOfParticle; i++) {
for (int j = 0; j < numberOfParticle; j++) {
if (i != j) {
Energy += 1/pow(coordinates.get(i).distance(coordinates.get(j)),degree);
}
}
Energy += degree / pow(coordinates.get(i).getZ() - heightTube / 2, degree) + degree / pow(coordinates.get(i).getZ() + heightTube / 2, degree);
}
return Energy;
}
private double energyOfSystem (ObservableList<Particle> coordinates, Particle particle, int i) {
coordinates.set(i,particle);
return energyOfSystem(coordinates);
}
}
Class drawing particles
public class Mapping {
private final ObservableList<Particle> list;
private final int numberOfParticle;
private final Group group;
private final Tube tube;
public Mapping(int numberOfParticle, Group group, Tube tube, ObservableList<Particle> list) {
this.numberOfParticle = numberOfParticle;
this.group = group;
this.list = list;
this.tube = tube;
}
public void MappingParticle() {
group.getChildren().clear();
group.getChildren().add(tube.getTube());
for (int i = 0; i < numberOfParticle; i++) {
group.getChildren().add(list.get(i).getParticle());
}
}
}
And finally the interface
public class NanoTube extends Application {
private double anchorX, anchorY;
@SuppressWarnings("FieldCanBeLocal")
private final int WIDTH = 800;
@SuppressWarnings("FieldCanBeLocal")
private final int HEIGHT = 670;
private final int RADIUS = 100;
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(0, Rotate.Y_AXIS);
@Override
public void start(Stage stage) {
var buttonEnter = new Button("Enter");
var buttonEnergyMinimization = new Button("Minimization");
var buttonEnergyMinimizationStress = new Button("Minimization Stress");
var labelRadius = new Label("Cylinder's radius");
var labelHeight = new Label("Cylinder's height");
var labelNumber = new Label("Number Particle");
var textFieldRadius = new TextField();
var textFieldHeight = new TextField();
var textNumber = new TextField();
GridPane Top = new GridPane();
for (int i : new int[]{120, 100, 70, 90, 60, 140, 150, 65}) {
Top.getColumnConstraints().add(new ColumnConstraints(i));
}
for (int i = 0; i < 3; i++) {
Top.getRowConstraints().add(new RowConstraints(30));
}
Top.getRowConstraints().add(new RowConstraints(560));
Arrays.asList(labelRadius, labelHeight, labelNumber).forEach(label -> {
GridPane.setHalignment(label, HPos.CENTER);
GridPane.setValignment(label, VPos.CENTER);
});
Arrays.asList(buttonEnter, buttonEnergyMinimization).forEach(button -> {
GridPane.setHalignment(button, HPos.CENTER);
GridPane.setValignment(button, VPos.CENTER);
});
Top.add(labelRadius, 0, 0);
Top.add(labelHeight, 0, 1);
Top.add(labelNumber, 0, 2);
Top.add(textFieldRadius, 1, 0);
Top.add(textFieldHeight, 1, 1);
Top.add(textNumber, 1, 2);
Top.add(buttonEnter,2,0,1,3);
Top.add(buttonEnergyMinimization,3,0,1,3);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setFieldOfView(20);
camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -500));
Tube tube = new Tube(80,80,Color.rgb(225,225,0));
Group group = new Group(tube.getTube());
SubScene subScene = new SubScene(group, 750, 550, true, SceneAntialiasing.BALANCED);
subScene.setFill (Color.rgb (129, 129, 129));
subScene.setCamera(camera);
var root3d = new Group(subScene);
initMouseControl(subScene);
Top.add(root3d,0,3,8,1);
GridPane.setHalignment(root3d, HPos.CENTER);
GridPane.setValignment(root3d, VPos.CENTER);
buttonEnter.setOnAction(e -> {
int n = Integer.parseInt(textNumber.getText());
tube.setHeight(Double.parseDouble(textFieldHeight.getText()));
tube.setRadius(Double.parseDouble(textFieldRadius.getText()));
ObservableList<Particle> particles = new Generation(tube, n).ParticlesGeneration();
new Mapping(n,group,tube,particles).MappingParticle();
buttonEnergyMinimization.setOnAction(actionEvent -> {
ObservableList<Particle> list = new Minimization(particles,2,tube).minimization();
new Mapping(n,group,tube,list).MappingParticle();
});
});
var scene = new Scene(Top, WIDTH,HEIGHT);
stage.setScene(scene);
stage.show();
stage.setTitle("NanoTube Student Project");
}
public static void main(String[] args) {launch();}
private void initMouseControl(SubScene scene) {
scene.setOnMousePressed(event -> {
anchorX = event.getSceneX();
anchorY = event.getSceneY();
});
scene.setOnMouseDragged(event -> {
double dx = (anchorX - event.getSceneX());
double dy = (anchorY - event.getSceneY());
if (event.isPrimaryButtonDown()) {
rotateX.setAngle(rotateX.getAngle() -
(dy /RADIUS * 360) * (Math.PI / 180));
rotateY.setAngle(rotateY.getAngle() -
(dx /RADIUS * -360) * (Math.PI / 180));
}
anchorX = event.getSceneX();
anchorY = event.getSceneY();
});
}
}
If you click on the "Enter" button, the particles are drawn, but if you then click on the "Minimization" button, the image does not change. I don't understand why this is happening.
I was advised to write as brief an example of a non-working area as possible. But the examples work, but my program doesn't. I don't understand why the second button ignores drawing particles when using the "Minimization" class. In the end, individually everything works fine. I use javafx but I don't use fxml. I will be grateful if you find the time to help me.
UPD: About the minimization class. As I said earlier, I randomly placed the particles on the surface of the cylinder. I need to minimize the energy of the particle system by the "gradient descent" method. In order not to count in a three-dimensional coordinate system, I use UV-mapping of the side of the cylinder. That is, the particles are located on a rectangle with sides "cylinder height" and "cylinder radius * angle (in radians)", where the angle takes values from 0 to 2pi
Nothing in your code ever updates the UI.
Your Tube
class contains a reference to a UI object (a Cylinder
). When you create a Tube
instance, you set the height and radius of the Cylinder
based on the height
and radius
of the Tube
.
However, if you later change the height or radius of the Tube
via the setHeight(...)
or setRadius(...)
methods, you don't update the properties of the Cylinder
. So the UI never changes.
You need:
public class Tube {
private final Cylinder tube;
// ...
public void setRadius(double radius) {
this.radius = radius;
tube.setRadius(radius);
}
public void setHeight(double height) {
this.height = height;
tube.setHeight(height);
}
}
Similarly for the Particle
class, if the properties of the Particle
are changed, you need to update the properties of the UI object (the Sphere
):
public class Particle {
private final Sphere particle;
// ...
public void setPhi(double phi) {
this.phi = phi;
particle.setTranslateX(rho * Math.cos(phi));
particle.setTranslateZ(rho * Math.sin(phi));
}
public void setRho(double rho) {
this.rho = rho;
particle.setTranslateX(rho * Math.cos(phi));
particle.setTranslateZ(rho * Math.sin(phi));
}
public void setZ(double z) {
this.z = z;
particle.setTranslateY(z);
}
}
(Unchanged code omitted in both classes.)
A more "JavaFX" approach would be to separate the data class from the view of the data, and use JavaFX properties to keep everything in sync. To give you a brief flavor of this, the implementation of the Tube
would look something like:
public class Tube {
private final DoubleProperty height = new SimpleDoubleProperty();
public DoubleProperty heightProperty() {
return height ;
}
public final double getHeight() {
return heightProperty().get();
}
public final void setHeight(double height) {
heightProperty().set(height);
}
private final DoubleProperty radius = new SimpleDoubleProperty();
public DoubleProperty radiusProperty() {
return radius;
}
public final double getRadius() {
return radiusProperty().get();
}
public final void setRadius(double radius) {
radiusProperty().set(radius);
}
public Tube(double radius, double height) {
setRadius(radius);
setHeight(height);
}
}
public class TubeView {
private final Tube tube ;
private final Cylinder view ;
private Color color;
public TubeView(Tube tube, Color color) {
this.tube = tube;
this.view = new Cylinder();
this.color = color;
view.setMaterial(new PhongMaterial(color));
view.heightProperty().bind(tube.heightProperty());
view.radiusProperty().bind(tube.radiusProperty());
}
public Node asNode() {
return view;
}
}