Skip to content

Conversation

@mstr2
Copy link
Collaborator

@mstr2 mstr2 commented Nov 30, 2025

When a Scene or SubScene has its width or height set to 0, coordinate transformations stop working because the transformation matrix contains NaNs. This is a consequence of divisions by zero in both parallel and perspective projections.

Even though a 0x0 scene is mathematically degenerate, it is still desirable for coordinate transformation APIs to remain numerically well-behaved and return finite results whenever possible, rather than being poisoned by NaN/Infinity.

Here is an application that demonstrates the failing coordinate transformations. Click on the buttons to resize the SubScene and observe the console output. After applying the patch in this PR, you should observe that localToScreen() no longer returns NaN.

public class ZeroSizeSubScene extends Application {

    @Override
    public void start(Stage stage) {
        var rect = new Rectangle(50, 50, Color.RED);
        var subScene = new SubScene(new Group(rect), 100, 100);
        subScene.setFill(Color.GRAY);

        // Also try a perspective camera:
        //
        // var camera = new PerspectiveCamera();
        // camera.setRotate(45);
        // camera.setTranslateX(-20);
        // camera.setRotationAxis(new Point3D(0, 1, 0));
        // subScene.setCamera(camera);

        class MyButton extends Button {
            public MyButton(String text, double width, double height) {
                super(text);
                setOnAction(_ -> {
                    var timeline = new Timeline(
                        new KeyFrame(Duration.seconds(1), new KeyValue(subScene.widthProperty(), width)),
                        new KeyFrame(Duration.seconds(1), new KeyValue(subScene.heightProperty(), height)));

                    timeline.setOnFinished(_ -> {
                        Point2D p0 = rect.localToScreen(0, 0);
                        System.out.println("rect.localToScreen(0, 0) = " + p0);
                    });

                    timeline.play();
                });
            }
        }

        VBox.setMargin(subScene, new Insets(0, 0, 20, 0));

        var root = new VBox(5,
            subScene,
            new CheckBox("Rotate SubScene") {{ setOnAction(_ -> subScene.setRotate(isSelected() ? 45 : 0)); }},
            new MyButton("Size: 100x100", 100, 100),
            new MyButton("Size: 10x10", 10, 10),
            new MyButton("Size: 100x0", 100, 0),
            new MyButton("Size: 0x100", 0, 100),
            new MyButton("Size: 0x0", 0, 0)
        );

        Scene scene = new Scene(root, 300, 300);
        stage.setScene(scene);
        stage.show();
    }
}

Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8372761: Prevent degenerate transforms for zero-size Scene/SubScene (Bug - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1992/head:pull/1992
$ git checkout pull/1992

Update a local copy of the PR:
$ git checkout pull/1992
$ git pull https://git.openjdk.org/jfx.git pull/1992/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1992

View PR using the GUI difftool:
$ git pr show -t 1992

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1992.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 30, 2025

👋 Welcome back mstrauss! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Nov 30, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Ready for review label Nov 30, 2025
@mstr2
Copy link
Collaborator Author

mstr2 commented Nov 30, 2025

/reviewers 2

@openjdk
Copy link

openjdk bot commented Nov 30, 2025

@mstr2
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@mlbridge
Copy link

mlbridge bot commented Nov 30, 2025

Webrevs

@kevinrushforth
Copy link
Member

I took a quick look and it seems reasonable. I'll let others formally review it, though.

Reviewers: @arapte @lukostyra

/reviewers 2

@openjdk
Copy link

openjdk bot commented Dec 1, 2025

@kevinrushforth
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

assertEquals(minX, b.getMinX(), 0.0005);
assertEquals(minY, b.getMinY(), 0.0005);
assertEquals(width, b.getWidth(), 0.0005);
assertEquals(height, b.getHeight(), 0.0005);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could declare a static constant EPSILON and use it at least in the modified tests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there are many different epsilons used in this test class, I'd have to call it something specific like EPSILON_0005, and at that point, there's little difference between a numeric literal and a constant field. It's a bit like saying "ONE" instead of "1".

// transform pipeline from being poisoned by NaN or Infinity. For this purpose, we clamp the view width
// and height to ulp(1), guaranteeing non-zero view dimensions while keeping the clamp very small.
//
this.viewWidth = Math.max(Math.ulp(1.0), width);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor suggestion: create a private static function (safeSize ?) with the comment explaining why.

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good on macOS.

@andy-goryachev-oracle
Copy link
Contributor

@lukostyra could you please take a look at this on Windows with fractional scale?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rfr Ready for review

Development

Successfully merging this pull request may close these issues.

3 participants