Я столкнулся с некоторым странным поведением после добавления 3D-контента в мое приложение JavaFX, которое содержит панель инструментов.
Я создал небольшой проект, который повторяет эту проблему. В основном приложении я использую SubScene, но поведение абсолютно одинаковое.
Вот как должно выглядеть приложение (без панели инструментов)
После добавления этой единственной строки
ToolBar toolBar = new ToolBar();
Содержание становится таким
Вот код
import java.util.*;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.control.ToolBar;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Line;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Pair;
import org.apache.commons.math3.analysis.interpolation.PiecewiseBicubicSplineInterpolatingFunction;
public class Main extends Application {
double[] xMetricData = {1 * 1000, 10 * 1000, 20 * 1000, 30 * 1000, 40 * 1000};
double[] yMetricData = {1 * 1000, 10 * 1000, 20 * 1000, 30 * 1000, 40 * 1000, 50 * 1000};
double[][] fMetricData = {{25, 5, 10, 15, 30, 30}, {15, -5, 5, 15, 25, 25}, {35, 5, 15, 10, 15, 15}, {15, 25, 15, 25, 35, 35}, {20, 30, 25, 30, 40, 40}};
int size = 50;
int cubeSize = 100;
// variables for mouse interaction
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(20, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(-45, Rotate.Y_AXIS);
@Override
public void start(Stage primaryStage) {
// create axis walls
Group cube = createCube(cubeSize);
// initial cube rotation
cube.getTransforms().addAll(rotateX, rotateY);
//ToolBar toolBar = new ToolBar();
// add objects to scene
StackPane root = new StackPane();
root.getChildren().add(cube);
TriangleMesh mesh = new TriangleMesh();
int xLength = 0, zLength = 0;
for (int x = (int)xMetricData[0]; x < xMetricData[xMetricData.length - 1]; x+= 1000)
{
zLength = 0;
for (int z = (int) yMetricData[0]; z < yMetricData[yMetricData.length - 1]; z+= 1000)
{
float y = (float) apacheInterpolation(x, z);
//System.out.println(x +"," + z + ", " + y);
mesh.getPoints().addAll(x / 1000.0f, y, z / 1000.0f);
zLength += 1;
}
xLength += 1;
}
for (float x = 0; x < xLength - 1; x++)
{
for (float y = 0; y < zLength - 1; y++)
{
float x0 = x / xLength;
float y0 = y / zLength;
float x1 = (x + 1) / xLength;
float y1 = (y + 1) / zLength;
mesh.getTexCoords().addAll( //
x0, y0, // 0, top-left
x0, y1, // 1, bottom-left
x1, y1, // 2, top-right
x1, y1 // 3, bottom-right
);
}
}
// faces
for (int x = 0; x < xLength - 1; x++)
{
for (int z = 0; z < zLength - 1; z++)
{
int tl = x * zLength + z; // top-left
int bl = x * zLength + z + 1; // bottom-left
int tr = (x + 1) * zLength + z; // top-right
int br = (x + 1) * zLength + z + 1; // bottom-right
int offset = (x * (zLength - 1) + z ) * 8 / 2; // div 2 because we have u AND v in the list
// working
mesh.getFaces().addAll(bl, offset + 1, tl, offset + 0, tr, offset + 2);
mesh.getFaces().addAll(tr, offset + 2, br, offset + 3, bl, offset + 1);
System.out.println(tl +", " + tr);
}
}
System.out.println(mesh.getPoints().size());
PhongMaterial material = new PhongMaterial();
//material.setDiffuseMap(diffuseMap);
material.setSpecularColor(Color.BLACK);
MeshView meshView = new MeshView(mesh);
// meshView.setTranslateX(-0.5 * size);
// meshView.setTranslateZ(-0.5 * size);
meshView.setMaterial(material);
meshView.setCullFace(CullFace.BACK);
meshView.setDrawMode(DrawMode.FILL);
meshView.setDepthTest(DepthTest.ENABLE);
cube.getChildren().addAll(meshView);
// scene
Scene scene = new Scene(root, 800, 700, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
scene.setOnMousePressed(me -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged(me -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
makeZoomable(root);
primaryStage.setResizable(true);
primaryStage.setScene(scene);
primaryStage.show();
}
public static double getFirmwareOffset(List<int[]> matrix, int x, int y)
{
Comparator<Pair<Integer, Long>> idComparator = (p1, p2) -> (int) (p2.getKey() - p1.getValue());
Queue<Pair<Integer, Long>> integerPriorityQueue = new PriorityQueue<>(5, idComparator);
long minDistance = -1;
long sumDistance = 0;
for (int i = 0; i < matrix.size(); ++i)
{
long difX = (long)Math.abs(matrix.get(i)[0] - x);
long difY = (long)Math.abs(matrix.get(i)[1] - y);
// in case accuracy is not enough we coudl go back to the traditional formula
//long distance = sqrt(difX * difX + difY * difY);
long distance = difX + difY;
if (distance == 0)
return matrix.get(i)[2];
if (integerPriorityQueue.size() < 15)
{
integerPriorityQueue.add(new Pair<>(i, distance));
sumDistance += distance;
}
else
{
Pair<Integer, Long> top = integerPriorityQueue.peek();
if (top.getValue() > distance)
{
integerPriorityQueue.poll();
integerPriorityQueue.add(new Pair<>(i, distance));
sumDistance -= top.getValue();
sumDistance += distance;
}
}
if (minDistance == -1 || distance < minDistance)
minDistance = distance;
}
double sumRatios = 0;
List<Pair<Integer, Double>> ratios = new ArrayList<>();
while(integerPriorityQueue.size() > 0)
{
Pair<Integer, Long> entry = integerPriorityQueue.poll();
double ratio = (double)sumDistance / entry.getValue();
ratios.add(new Pair<>(entry.getKey(), ratio));
sumRatios += ratio;
}
if (sumRatios == 0)
return 0;
double offset = 0;
for (int i = 0; i < ratios.size(); ++i)
{
Pair<Integer, Double> entry = ratios.get(i);
offset += (matrix.get(entry.getKey())[2] * entry.getValue() / sumRatios);
}
return offset;
}
/**
* Axis wall
*/
public static class Axis extends Pane {
Rectangle wall;
public Axis(double size) {
// wall
// first the wall, then the lines => overlapping of lines over walls
// works
wall = new Rectangle(size, size);
getChildren().add(wall);
// grid
double zTranslate = 0;
double lineWidth = 1.0;
Color gridColor = Color.WHITE;
for (int y = 0; y <= size; y += size / 10) {
Line line = new Line(0, 0, size, 0);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateY(y);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
for (int x = 0; x <= size; x += size / 10) {
Line line = new Line(0, 0, 0, size);
line.setStroke(gridColor);
line.setFill(gridColor);
line.setTranslateX(x);
line.setTranslateZ(zTranslate);
line.setStrokeWidth(lineWidth);
getChildren().addAll(line);
}
// labels
// TODO: for some reason the text makes the wall have an offset
// for( int y=0; y <= size; y+=size/10) {
//
// Text text = new Text( ""+y);
// text.setTranslateX(size + 10);
//
// text.setTranslateY(y);
// text.setTranslateZ(zTranslate);
//
// getChildren().addAll(text);
//
// }
}
public void setFill(Paint paint) {
wall.setFill(paint);
}
}
public void makeZoomable(StackPane control) {
final double MAX_SCALE = 20.0;
final double MIN_SCALE = 4;
control.setScaleX(MIN_SCALE);
control.setScaleY(MIN_SCALE);
control.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent event) {
double delta = 1.2;
double scale = control.getScaleX();
if (event.getDeltaY() < 0) {
scale /= delta;
} else {
scale *= delta;
}
scale = clamp(scale, MIN_SCALE, MAX_SCALE);
control.setScaleX(scale);
control.setScaleY(scale);
event.consume();
}
});
}
/**
* Create axis walls
* @param size
* @return
*/
private Group createCube(int size) {
Group cube = new Group();
// size of the cube
Color color = Color.DARKCYAN;
List<Axis> cubeFaces = new ArrayList<>();
Axis r;
// back face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.5 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(0.5 * size);
cubeFaces.add(r);
// bottom face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.4 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(0);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// right face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.3 * 1), 1.0));
r.setTranslateX(-1 * size);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// left face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.2 * 1), 1.0));
r.setTranslateX(0);
r.setTranslateY(-0.5 * size);
r.setRotationAxis(Rotate.Y_AXIS);
r.setRotate(90);
cubeFaces.add(r);
// top face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-1 * size);
r.setRotationAxis(Rotate.X_AXIS);
r.setRotate(90);
// cubeFaces.add( r);
// front face
r = new Axis(size);
r.setFill(color.deriveColor(0.0, 1.0, (1 - 0.1 * 1), 1.0));
r.setTranslateX(-0.5 * size);
r.setTranslateY(-0.5 * size);
r.setTranslateZ(-0.5 * size);
// cubeFaces.add( r);
cube.getChildren().addAll(cubeFaces);
return cube;
}
public static double normalizeValue(double value, double min, double max, double newMin, double newMax) {
return (value - min) * (newMax - newMin) / (max - min) + newMin;
}
public static double clamp(double value, double min, double max) {
if (Double.compare(value, min) < 0)
return min;
if (Double.compare(value, max) > 0)
return max;
return value;
}
public static void main(String[] args) {
launch(args);
}
public double apacheInterpolation(float px, float py)
{
PiecewiseBicubicSplineInterpolatingFunction t = new PiecewiseBicubicSplineInterpolatingFunction(xMetricData, yMetricData, fMetricData);
return t.value(px, py);
}
}
UPD: после небольшого расследования любой контроль приводит к такому поведению.