[PATCH] fix Blender camera loader FoV/aspect

@Kaelthas the Blender importer camera loader doesn’t currently properly set the field of view or aspect of the camera - so the scene looks different than how it looks through the camera in Blender.



The camera “lens” SDNA field is in millimeters, it’s currently used as the aspect ratio which doesn’t make sense (e.g. the default value is 35mm).



This patch uses “lens” and “sensor_fit” along with “sensor_x” and “sensor_y” to compute the field of view in degrees. JME camera uses a vertical FoV, Blender uses horizontal by default (depending on “sensor_fit”). So to convert from horizontal to vertical, we need the actual camera viewport aspect ratio (“xsch” / “ysch”) which Blender stores in the scenes RenderData structure.



I wasn’t sure how to deal with “ortho_scale” for a parallel camera - I don’t think it’s currently being handled properly, and this patch doesn’t fix it.



[patch]diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java

index 0c8d7d7…43489ac 100644

— a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java

+++ b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java

@@ -106,7 +106,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;

public CameraNode toCamera(Structure structure) throws BlenderFileException {

CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);

if (cameraHelper.shouldBeLoaded(structure, blenderContext)) {

  •   	return cameraHelper.toCamera(structure);<br />
    
  •   	return cameraHelper.toCamera(structure, blenderContext);<br />
    

}

return null;

}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java

index 5b5c57e…979353e 100644

— a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java

+++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java

@@ -74,6 +74,8 @@ public class BlenderContext {

private BlenderKey blenderKey;

/** The header of the file block. */

private DnaBlockData dnaBlockData;

  • /** The scene structure. */
  • private Structure sceneStructure;

    /** The input stream of the blend file. /

    private BlenderInputStream inputStream;

    /
    * The asset manager. */

    @@ -174,6 +176,24 @@ public class BlenderContext {

    public DnaBlockData getDnaBlockData() {

    return dnaBlockData;

    }
  • /**
    • This method sets the scene structure data.
  • *
    • @param sceneStructure
    •        the scene structure data<br />
      
  • */
  • public void setSceneStructure(Structure sceneStructure) {
  •   this.sceneStructure = sceneStructure;<br />
    
  • }

    +
  • /**
    • This method returns the scene structure data.
  • *
    • @return the scene structure data
  • */
  • public Structure getSceneStructure() {
  •   return sceneStructure;<br />
    
  • }



    /**
  • This method returns the asset manager.

    diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java

    index afad7ff…7829116 100644

    — a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java

    +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java

    @@ -215,15 +215,19 @@ public class BlenderLoader extends AbstractBlenderLoader {

    if (!fileBlock.isDnaBlock()) {

    blocks.add(fileBlock);

    // save the scene’s file block
  •   		if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00 &amp;&amp; blenderKey.getLayersToLoad() &lt; 0) {<br />
    
  •   		if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) {<br />
    

sceneFileBlock = fileBlock;

}

}

} while (!fileBlock.isLastBlock());

// VERIFY LAYERS TO BE LOADED BEFORE LOADING FEATURES

if (sceneFileBlock != null) {

  •   	int lay = ((Number) sceneFileBlock.getStructure(blenderContext).getFieldValue(&quot;lay&quot;)).intValue();<br />
    
  •   	blenderContext.getBlenderKey().setLayersToLoad(lay);// load only current layer<br />
    
  •   	Structure sceneStructure = sceneFileBlock.getStructure(blenderContext);<br />
    
  •   	if (blenderKey.getLayersToLoad() &lt; 0) {<br />
    
  •   		int lay = ((Number) sceneStructure.getFieldValue(&quot;lay&quot;)).intValue();<br />
    
  •   		blenderContext.getBlenderKey().setLayersToLoad(lay);// load only current layer<br />
    
  •                    }<br />
    
  •                    blenderContext.setSceneStructure(sceneStructure);<br />
    

}

}

}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java

index ae028ee…5aeb74d 100644

— a/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java

+++ b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java

@@ -1,6 +1,7 @@

package com.jme3.scene.plugins.blender.cameras;



import com.jme3.asset.BlenderKey.FeaturesToLoad;

+import com.jme3.math.FastMath;

import com.jme3.renderer.Camera;

import com.jme3.scene.CameraNode;

import com.jme3.scene.plugins.blender.AbstractBlenderHelper;

@@ -42,9 +43,9 @@ public class CameraHelper extends AbstractBlenderHelper {

  •         an exception is thrown when there are problems with the<br />
    
  •         blender file<br />
    

*/

  • public CameraNode toCamera(Structure structure) throws BlenderFileException {
  • public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {

    if (blenderVersion >= 250) {
  •        return this.toCamera250(structure);<br />
    
  •        return this.toCamera250(structure, blenderContext.getSceneStructure());<br />
    

} else {

return this.toCamera249(structure);

}

@@ -55,13 +56,22 @@ public class CameraHelper extends AbstractBlenderHelper {

*

  • @param structure
  •        camera structure<br />
    
    • @param sceneStructure
    •        scene structure<br />
      
  • @return jme camera object
  • @throws BlenderFileException
  •         an exception is thrown when there are problems with the<br />
    
  •         blender file<br />
    

*/

  • private CameraNode toCamera250(Structure structure) throws BlenderFileException {
  •    Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);<br />
    
  • private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
  •    int width = DEFAULT_CAM_WIDTH;<br />
    
  •    int height = DEFAULT_CAM_HEIGHT;<br />
    
  •    if (sceneStructure != null) {<br />
    
  •        Structure renderData = (Structure)sceneStructure.getFieldValue(&quot;r&quot;);<br />
    
  •        width = ((Number)renderData.getFieldValue(&quot;xsch&quot;)).shortValue();<br />
    
  •        height = ((Number)renderData.getFieldValue(&quot;ysch&quot;)).shortValue();<br />
    
  •    }<br />
    
  •    Camera camera = new Camera(width, height);<br />
    

int type = ((Number) structure.getFieldValue("type")).intValue();

if (type != 0 && type != 1) {

LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);

@@ -69,15 +79,31 @@ public class CameraHelper extends AbstractBlenderHelper {

}

//type==0 - perspective; type==1 - orthographic; perspective is used as default

camera.setParallelProjection(type == 1);

  •    float aspect = 0;<br />
    
  •    float aspect = width / (float)height;<br />
    
  •    float fovY; // Vertical field of view in degrees<br />
    

float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();

float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();

if (type == 0) {

  •        aspect = ((Number) structure.getFieldValue(&quot;lens&quot;)).floatValue();<br />
    
  •        // Convert lens MM to vertical degrees in fovY, see Blender rna_Camera_angle_get()<br />
    
  •        // If sensor_fit is vert (2), then sensor_y is used<br />
    
  •        boolean sensorVertical = ((Byte)structure.getFieldValue(&quot;sensor_fit&quot;)).byteValue() == 2;<br />
    
  •        String sensorName = &quot;sensor_x&quot;;<br />
    
  •        if (sensorVertical) {<br />
    
  •            sensorName = &quot;sensor_y&quot;;<br />
    
  •        }<br />
    
  •        float focalLength = ((Number) structure.getFieldValue(&quot;lens&quot;)).floatValue();<br />
    
  •        float sensor = ((Number) structure.getFieldValue(sensorName)).floatValue();<br />
    
  •        float fov = 2.0f * FastMath.atan((sensor / 2.0f) / focalLength);<br />
    
  •        if (sensorVertical) {<br />
    
  •            fovY = fov * FastMath.RAD_TO_DEG;<br />
    
  •        } else {<br />
    
  •            // Convert fov from horizontal to vertical<br />
    
  •            fovY = 2.0f * FastMath.atan(FastMath.tan(fov / 2.0f) / aspect) * FastMath.RAD_TO_DEG;<br />
    
  •        }<br />
    

} else {

  •        aspect = ((Number) structure.getFieldValue(&quot;ortho_scale&quot;)).floatValue();<br />
    
  •        fovY = ((Number) structure.getFieldValue(&quot;ortho_scale&quot;)).floatValue();//XXX<br />
    

}

  •    camera.setFrustumPerspective(45, aspect, clipsta, clipend);<br />
    
  •    camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);<br />
    

return new CameraNode(null, camera);

}



diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

index c0058eb…e430d03 100644

— a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

+++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@@ -206,7 +206,7 @@ public class ObjectHelper extends AbstractBlenderHelper {

if(pCamera.isNotNull()) {

CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);

List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());

  •   				CameraNode camera = cameraHelper.toCamera(camerasArray.get(0));<br />
    
  •   				CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext);<br />
    

camera.setName(name);

camera.setLocalTransform(t);

result = camera;

[/patch]

1 Like

The problem with this solution is here:



[java]

boolean sensorVertical = ((Byte)structure.getFieldValue("sensor_fit")).byteValue() == 2;

String sensorName = "sensor_x";

if (sensorVertical) {

sensorName = "sensor_y";

}

[/java]



Before Blender 2.60 there were no such fields in camera object as: sensor_fit, sensor_x or sensor_y.

Your model was made in blender 2.58. Even if I save the model in blender 2.63 I do not get these fields.

I only found them when I created the model in 2.63 from scratch.



But even if I create a new file (with one light, camera and a cube) I only see black screen when I set this patch. So soemthing is still wrong here.

But I think these changes are a good start for further investigation :wink:

1 Like
@Kaelthas said:
Your model was made in blender 2.58. Even if I save the model in blender 2.63 I do not get these fields.
I only found them when I created the model in 2.63 from scratch.

Ugh, you're right. I checked Blender 2.53 and 2.58 sources and they use the function lens_to_angle to convert "lens" using the formula 2.0f * atan(16.0f/lens) - so I guess sensor was hardcoded to 32.0 (32/2=16) prior to adding sensor_fit/sensor_x/sensor_y.


But even if I create a new file (with one light, camera and a cube) I only see black screen when I set this patch. So soemthing is still wrong here.
But I think these changes are a good start for further investigation ;)


You see black because the blender camera is facing backwards to what JME expects, you need the patch to fix camera orientation to correct that (and that patch currently only works if you load the model with fixUpAxis=false).

Here is an updated patch that uses the default 32.0 sensor size if sensor_fit is missing.

[patch]diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
index 0c8d7d7..43489ac 100644
--- a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java
@@ -106,7 +106,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
public CameraNode toCamera(Structure structure) throws BlenderFileException {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
if (cameraHelper.shouldBeLoaded(structure, blenderContext)) {
- return cameraHelper.toCamera(structure);
+ return cameraHelper.toCamera(structure, blenderContext);
}
return null;
}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
index 5b5c57e..979353e 100644
--- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
@@ -74,6 +74,8 @@ public class BlenderContext {
private BlenderKey blenderKey;
/** The header of the file block. */
private DnaBlockData dnaBlockData;
+ /** The scene structure. */
+ private Structure sceneStructure;
/** The input stream of the blend file. */
private BlenderInputStream inputStream;
/** The asset manager. */
@@ -174,6 +176,24 @@ public class BlenderContext {
public DnaBlockData getDnaBlockData() {
return dnaBlockData;
}
+ /**
+ * This method sets the scene structure data.
+ *
+ * @param sceneStructure
+ * the scene structure data
+ */
+ public void setSceneStructure(Structure sceneStructure) {
+ this.sceneStructure = sceneStructure;
+ }
+
+ /**
+ * This method returns the scene structure data.
+ *
+ * @return the scene structure data
+ */
+ public Structure getSceneStructure() {
+ return sceneStructure;
+ }

/**
* This method returns the asset manager.
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
index afad7ff..7829116 100644
--- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java
@@ -215,15 +215,19 @@ public class BlenderLoader extends AbstractBlenderLoader {
if (!fileBlock.isDnaBlock()) {
blocks.add(fileBlock);
// save the scene's file block
- if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00 && blenderKey.getLayersToLoad() < 0) {
+ if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) {
sceneFileBlock = fileBlock;
}
}
} while (!fileBlock.isLastBlock());
// VERIFY LAYERS TO BE LOADED BEFORE LOADING FEATURES
if (sceneFileBlock != null) {
- int lay = ((Number) sceneFileBlock.getStructure(blenderContext).getFieldValue("lay")).intValue();
- blenderContext.getBlenderKey().setLayersToLoad(lay);// load only current layer
+ Structure sceneStructure = sceneFileBlock.getStructure(blenderContext);
+ if (blenderKey.getLayersToLoad() < 0) {
+ int lay = ((Number) sceneStructure.getFieldValue("lay")).intValue();
+ blenderContext.getBlenderKey().setLayersToLoad(lay);// load only current layer
+ }
+ blenderContext.setSceneStructure(sceneStructure);
}
}
}
diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
index ae028ee..ebbbb87 100644
--- a/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
@@ -1,6 +1,7 @@
package com.jme3.scene.plugins.blender.cameras;

import com.jme3.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.math.FastMath;
import com.jme3.renderer.Camera;
import com.jme3.scene.CameraNode;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
@@ -42,9 +43,9 @@ public class CameraHelper extends AbstractBlenderHelper {
* an exception is thrown when there are problems with the
* blender file
*/
- public CameraNode toCamera(Structure structure) throws BlenderFileException {
+ public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
if (blenderVersion >= 250) {
- return this.toCamera250(structure);
+ return this.toCamera250(structure, blenderContext.getSceneStructure());
} else {
return this.toCamera249(structure);
}
@@ -55,13 +56,22 @@ public class CameraHelper extends AbstractBlenderHelper {
*
* @param structure
* camera structure
+ * @param sceneStructure
+ * scene structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
- private CameraNode toCamera250(Structure structure) throws BlenderFileException {
- Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
+ private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
+ int width = DEFAULT_CAM_WIDTH;
+ int height = DEFAULT_CAM_HEIGHT;
+ if (sceneStructure != null) {
+ Structure renderData = (Structure)sceneStructure.getFieldValue("r");
+ width = ((Number)renderData.getFieldValue("xsch")).shortValue();
+ height = ((Number)renderData.getFieldValue("ysch")).shortValue();
+ }
+ Camera camera = new Camera(width, height);
int type = ((Number) structure.getFieldValue("type")).intValue();
if (type != 0 && type != 1) {
LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);
@@ -69,15 +79,37 @@ public class CameraHelper extends AbstractBlenderHelper {
}
//type==0 - perspective; type==1 - orthographic; perspective is used as default
camera.setParallelProjection(type == 1);
- float aspect = 0;
+ float aspect = width / (float)height;
+ float fovY; // Vertical field of view in degrees
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();
if (type == 0) {
- aspect = ((Number) structure.getFieldValue("lens")).floatValue();
+ // Convert lens MM to vertical degrees in fovY, see Blender rna_Camera_angle_get()
+ // Default sensor size prior to 2.60 was 32.
+ float sensor = 32.0f;
+ boolean sensorVertical = false;
+ Byte sensorFit = (Byte)structure.getFieldValue("sensor_fit");
+ if (sensorFit != null) {
+ // If sensor_fit is vert (2), then sensor_y is used
+ sensorVertical = sensorFit.byteValue() == 2;
+ String sensorName = "sensor_x";
+ if (sensorVertical) {
+ sensorName = "sensor_y";
+ }
+ sensor = ((Number) structure.getFieldValue(sensorName)).floatValue();
+ }
+ float focalLength = ((Number) structure.getFieldValue("lens")).floatValue();
+ float fov = 2.0f * FastMath.atan((sensor / 2.0f) / focalLength);
+ if (sensorVertical) {
+ fovY = fov * FastMath.RAD_TO_DEG;
+ } else {
+ // Convert fov from horizontal to vertical
+ fovY = 2.0f * FastMath.atan(FastMath.tan(fov / 2.0f) / aspect) * FastMath.RAD_TO_DEG;
+ }
} else {
- aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
+ fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue();//XXX
}
- camera.setFrustumPerspective(45, aspect, clipsta, clipend);
+ camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);
return new CameraNode(null, camera);
}

diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
index c0058eb..e430d03 100644
--- a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
+++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
@@ -206,7 +206,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
if(pCamera.isNotNull()) {
CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
List<Structure> camerasArray = pCamera.fetchData(blenderContext.getInputStream());
- CameraNode camera = cameraHelper.toCamera(camerasArray.get(0));
+ CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
camera.setName(name);
camera.setLocalTransform(t);
result = camera;
[/patch]
2 Likes

@erlend_sh: Can we make rectalogic a committer?

@Momoko_Fan said:
@erlend_sh: Can we make rectalogic a committer?

Darn, these badges keep confusing developers :P Having commit access and receiving a "Contributor" badge are two separate things. I'll have to clarify this in the upcoming rewrite of our contributor guidelines.

To answer your question, yes he can have commit access, just ask him what e-mail he'd like to use on GoogleCode and you can add him.

I think it’s a cool idea :slight_smile:

@rectalogic definitely has a talent of finding and fixing bugs :slight_smile:



And by the way @rectalogic instead of doing this



[java]

Byte sensorFit = (Byte)structure.getFieldValue(“sensor_fit”);

[/java]



do something like this:



[java]

byte sensorFit = ((Number)structure.getFieldValue(“sensor_fit”)).byteValue();

[/java]



It is possible that the particular field in blender might change its numerical type in future releases.

I had lots of crashes because of that before when I expected Byte and got Short instead. So cast what you get on Number and then get the proper primitive.

This will cause the code less complicated when loading different blender file types :wink:



And when you belive you’re done with camera fixes - commit it. I’ll check it out when I update :slight_smile:

I finally got around to committing this.

1 Like