javabuttonjavafxobservablelist

Why doesn't the location of 3D objects change when you click on the button?


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


Solution

  • 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;
        }
    }