JDK-8180363 : Applying same transform to a Group holding Shape & Camera can render incorrectly
  • Type: Bug
  • Component: javafx
  • Sub-Component: scenegraph
  • Affected Version: 8u40,9
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2017-05-13
  • Updated: 2018-09-05
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
Java version: 1.8.0_121

ADDITIONAL OS VERSION INFORMATION :
Windows 10.0.1.15063 Build 15063

A DESCRIPTION OF THE PROBLEM :
I create a Sphere and put it in a Group and add a Perspective camera. I give the sphere a small Z displacement.  I place the Group in a SubScene. I expect the sphere to be in the centre of the view.  It is only if integers are used for the SubScene size.  If I make the scene 400 x 400 it works OK.  If I make the scene 400.5 x 400,  the sphere is no longer at the centre. Instead it tracks some fraction of the camera movement.

I calculate the needed scene size so I get non-integer values. My fix is to use Math.floor to force integers..

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
The attached test program creates side-by-side subscenes, one is 400 x 400, the other is 400+ some fraction. I use .5. 

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The attached test program creates side-by-side subscenes, one is 400 x 400, the other is 400+ some fraction. I use .5.  I then animate the camera-sphere group rotating in X-Y.  There is a stationary box.  The correct view uses 400x400.
The view of the stationary box rotates correctly because the camera is rotating.  The sphere is stationary because it is the same group as the moving camera. 

In the right-hand subScene the red sphere is incorrectly moving.  The only difference is the subScene size.
ACTUAL -
See above

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Not applicable

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------



import java.util.logging.Logger;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.value.ObservableDoubleValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Point3D;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;



/**
 * TestCameraFovWithSubScene - No problems!
 * 
 * @author Robin
 *
 */
public class TestCameraFovWithSubScene extends Application{
	static final Material redmat = ofColor(Color.RED); 
	final static Color BACKGROUND_3D = Color.rgb(200, 200, 250); // 20,20,50 = dark navy
	static final String SidePanelStyle = "-fx-background-color: DAE6F3;-fx-padding:5;";
	static final String BoxStyle = "-fx-background-color: DAE6F3;-fx-padding:5;";
	private final Logger log = Logger.getLogger( getClass().getSimpleName() );
	Transformer transformer = new Transformer();
	Translate translate = new Translate(0,0,-10); // Camera at z=0
	Rotate rotate = new Rotate();

	/**
	 * 
	 * @param args
	 */
	public static void main(String[] args)		{
		Application.launch(args);
	}



	
	

	@Override
	public void start(Stage stage){

		VBox sidePanel = new VBox();
		Label label = new Label("Side panel");
		label.setStyle(BoxStyle);
		sidePanel.getChildren().add(label);
		sidePanel.setStyle(SidePanelStyle);
		HBox sceneRoot = new HBox(2,sidePanel, transformer.createSubScene(0), transformer.createSubScene(.5) );


		// Create a Scene with depth buffer enabled
		Scene scene = new Scene( sceneRoot, 1000, 1000, true);

		// Add the Scene to the Stage
		stage.setScene(scene);
		// Set the Title of the Stage
		stage.setTitle("An Example with Predefined 3D Shapes");
		// Display the Stage
		stage.show();
	}
	
	static public PhongMaterial ofColor(Color c){
		PhongMaterial mat = new PhongMaterial(Color.RED );
		mat.setDiffuseColor(c);
		mat.setSpecularColor(Color.BLACK);
		return mat;
	}
	
	// ====================================================================
	
	/**
	 * Create a Y-up camera.
	 * @author Robin
	 */
	static public class DynamicCamera extends Group{
		static final double D2R = Math.PI/180.;
		public final static double FIELD_OF_VIEW = 60; /* Nominal in degrees*/
		public final static double NEAR_CLIP = 0.0003;
		public final static double FAR_CLIP = 100.;
		String tag;
		private final Logger log = Logger.getLogger( getClass().getSimpleName() );
		PerspectiveCamera rawCamera;
		double sc = 1.0;
		Scale flipXy =  new Scale(-sc, -sc, sc); /* Scale Y by -1 so camera Y is up.
		Also need to flip X for right-handed scheme. After, x=right, y=up z=fwd*/
		Group yUpCamera = this;


		/**
		 * To get the camera into the right coordinate system, we first build a raw camera,
		 * then flip x and y to put it in Heliocentric equatorial coordinates. We then
		 * put it into a group (this this of this class). Then 'this' camera gets the various
		 * transform applied to it derived from orbits and user controls. 
		 * 
		 * @param id 
		 * @param cameraControls for this camera
		 */
		public DynamicCamera( double fieldOfViewAngle, boolean isMain){
			rawCamera = new PerspectiveCamera(true); /* true = eye position fixed at 0,0,0 
			in local coord. of the camera node. In non-fixed eye case, the camera position 
			is (w/2, h/2, h/2/tan) in local coord. of the camera. */
			tag = isMain ? "Main" : "Side";
			rawCamera.setNearClip(NEAR_CLIP); // Making this too small gives artifacts
			rawCamera.setFarClip(FAR_CLIP);
			rawCamera.setFieldOfView(fieldOfViewAngle); /* Specifies the field of view angle
			of the camera's projection plane, measured in degrees.*/
			rawCamera.getTransforms().add(flipXy); // Note flip is added to raw camera
			yUpCamera.getChildren().add(rawCamera); 
		}


		//public void setTransforms(ObservableList<Transform> transforms){
		//	yUpCamera.getTransforms().addAll(transforms ); 
		//}

		
		/**
		 * @return the raw perspective camera that should be added to the subScene
		 */
		public Camera getRawCamera(){
			return rawCamera;
		}


		/**
		 * Bind camera width of field of view to a supplied observable double. 
		 * @param observableFovAngle
		 */
		public void bindFov(ObservableDoubleValue observableFovAngle){
			rawCamera.fieldOfViewProperty().bind(
					observableFovAngle ); /*  */
		}
	}

	// =========================================

	static public class Transformer{
		double radius = 3;
		Translate translate = new Translate(0,0,-10); // Camera at z=0
		Rotate rotate = new Rotate();
		Timeline timeline;
		KeyFrame keyframe;
		Duration duration = Duration.millis( 30 );
		private final Logger log = Logger.getLogger( getClass().getSimpleName() );
		double angle;
		private boolean DEPTH_ON = true;
		Group subSceneRoot;


		public Transformer(){

		}
		
		public SubScene createSubScene(double fraction){
			subSceneRoot = new Group();	
			SubScene subScene = new SubScene(subSceneRoot, 400+fraction, 400 , DEPTH_ON, SceneAntialiasing.BALANCED);
			subScene.setFill( BACKGROUND_3D ); // Set BG color os scene
			DynamicCamera yUpCamera = new DynamicCamera(60,true);
			subScene.setCamera(yUpCamera.getRawCamera());
			Group viewPoint = new Group( yUpCamera, makeSphere() ); //, light
			viewPoint.getTransforms().addAll(buildTransforms() );
			subSceneRoot.getChildren().addAll( viewPoint, makeBox() );
			startTimeline();
			return subScene;
		}

		
		public Box makeBox(){
			// Create a Box
			double len = 2.;
			Box box = new Box(len,len,len);
			box.setTranslateX(0);
			box.setTranslateY(0);
			box.setTranslateZ(10); 
			return box;
		}
		
		
		public Sphere makeSphere(){
			// Create a Sphere
			Sphere sphere = new Sphere(.005);
			sphere.setMaterial(redmat);
			sphere.setDrawMode(DrawMode.LINE);
			sphere.setTranslateZ(.1);
			return sphere;
		}


		public ObservableList<Transform> buildTransforms(){
			ObservableList<Transform> transforms =  
					FXCollections.observableArrayList(translate);
			return transforms;
		}

		/**
		 * startTimeline & run indefinitely
		 */
		public void startTimeline(){
			log.info("Timeline starting ...");

			timeline = new Timeline();
			timeline.setCycleCount(Timeline.INDEFINITE);
			keyframe = new KeyFrame( duration,
					eh -> timeEvent() ); // forwards=true, reset=false
			timeline.getKeyFrames().clear();
			timeline.getKeyFrames().add(keyframe);
			log.info("Timeline playing at frame rate " + duration );
			timeline.play();
			log.info("... Timeline started");

		}

		private void timeEvent() {
			angle += 5.;
			if(angle >360)angle = 0;
			//log.info("Tick " + x);
			update(angle);
		}



		public void update(double angle){
			double rad = angle*Math.PI / 180.;
			double x = radius * Math.cos(rad);
			double y = radius * Math.sin(rad);
			translate.setX(x); // Problem is with x
			translate.setY(y);
			rotate.setAngle(angle);
			rotate.setAxis(new Point3D(0,0,1.) );
		}
	}

}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Use only integers for subScene size


Comments
Good find. The challenge then will be to fix this without reintroducing JDK-8094194 and without causing another regression.
27-09-2017

This is regression of JDK-8094194. The scaling of camera's ProjViewTx matrix in D3DContext seems to be suspicious. The scaling related to view width & height should be applied to Projection & View matrix, but not to Transformations matrix.
27-09-2017

Issue reproducible in both windows and Linux. Its a regression in windows introduced in 8u40. Windows 10 Pro(version 1511) Build 10586.318 ------------ 8 GA : Pass 8u33 : Pass 8u40 : Fail <-- Introduced here 8u60 : Fail 8u121 : Fail 8u131 : Fail 8u152 : Fail 9-ea+168 : Fail ------------ Ubuntu 16.04.2 LTS ------------ 8 GA - 9+ea167 : Fail ------------
15-05-2017