[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [17715] trunk/blender/source/gameengine: VideoTexture: new ImageMirror class for easy mirror (and portal) creation

Benoit Bolsee benoit.bolsee at online.be
Thu Dec 4 17:07:46 CET 2008


Revision: 17715
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=17715
Author:   ben2610
Date:     2008-12-04 17:07:46 +0100 (Thu, 04 Dec 2008)

Log Message:
-----------
VideoTexture: new ImageMirror class for easy mirror (and portal) creation

The new class VideoTexture.ImageMirror() is available to perform
automatic mirror rendering.

Constructor:

  VideoTexture.ImageMirror(scene,observer,mirror,material)
    scene:    reference to the scene that will be rendered.
              Both observer and mirror must be part of that scene.
    observer: reference to a game object used as view point for
              mirror rendering: the scene will be rendered through
              the mirror as if the active camera was at the observer 
              location. Usually the observer is the active camera
              but you can use any game obejct.
    mirror:   reference to the mesh object holding the mirror.
    material: material ID of the mirror texture as returned by 
              VideoTexture.materialID(). The mirror is formed by 
              the polygons mapped to that material.

There are no specific methods or attributes. ImageMirror inherits 
all methods and attributes from ImageRender. You must refresh the
parent VideoTexture.Texture object regularly to update the mirror 
rendering.

Guidelines on how to create a working mirror:
- Use a texture that is specific to the mirror so that the mirror 
  rendering only appears on the mirror.
- The mirror must be planar; the algorithm works well only for planar
  or quasi planar mirror. For spherical mirror, you will get better
  results with ImageRender and a camera at the center of the mirror. 
  ImageMirror automatically computes the mirror orientation and 
  position. The mirror doesn't need to be rectangular, it can be 
  circular or take any form provided it is planar.
- The mirror up direction must be along the Z axis in local mesh
  coordinates. If the mirror is not vertical, ImageMirror will 
  compute the up direction as being the projection of the Z axis
  on the mirror plane.
- UV mapping must be set right to get correct mirror rendering:
  - make a planar projection of the mirror polygons (Unwrap or projection from view)
  - eventually rotate the projection so that UV up direction corresponds to the mesh Z axis
  - scale the projection so that the extreme points touch the border of the texture
  - flip the UV projection horizontally (scale -1 on X axis). This is needed
    because the mirror texture is rendered from the back of the mirror and
    thus is reversed from the view point of the observer. Horizontal flip 
    in the UV map restores the correct orientation.

Besides these simple rules, the mirror rendering is completely automatic. 
In particular, you don't need to allocate a camera for the rendering, 
ImageMirror creates dynamically a camera for that. The reflection is correct
even on large angles. The mirror can be a dynamic and moving object, the 
algorithm always computes the correct camera position based on observer 
relative position. You don't have to worry about mirror position in the scene: 
the algorithm automatically computes the camera frustum so that any object 
behind the mirror is not rendered.

Warnings:
- observer and mirror are references to game objects. ImageMirror keeps
  a pointer to them but does not increment the reference count. You must ensure 
  that these game objects are not deleted as long as you refresh() the ImageMirror
  object. You must release the ImageMirror object before you delete the game
  objects. To release the ImageMirror object (normally stored in GameLogic),
  just assign it to None.
- Mirror rendering is automatically skipped when the observer is behind the mirror
  but it is not disabled when the mirror is out of sight of the observer.
  You should only refresh the mirror when you know that the observer is likely to see it.
  For example, no need to refresh a car inner mirror when the player is not in the car.

Example:

  contr = GameLogic.getCurrentController()
  # object holding the mirror
  mirror = contr.getOwner()
  scene = GameLogic.getCurrentScene()
  # observer will be the active camere
  camera = scene.getObjectList()['OBCamera']
  matID = VideoTexture.materialID(mirror, 'IMmirror.png')
  GameLogic.mirror = VideoTexture.Texture(mirror, matID)
  GameLogic.mirror.source = VideoTexture.ImageMirror(scene,camera,mirror,matID)
  # to render the mirror, just call GameLogic.mirror.refresh(True) on each frame.

You can download a demo game (with a video file) here:

  http://home.scarlet.be/~tsi46445/blender/VideoTextureDemo.zip

For those who have already downloaded the demo, you can just update the blend file:

  http://home.scarlet.be/~tsi46445/blender/MirrorTextureDemo.blend

Modified Paths:
--------------
    trunk/blender/source/gameengine/Ketsji/KX_Camera.cpp
    trunk/blender/source/gameengine/VideoTexture/Exception.cpp
    trunk/blender/source/gameengine/VideoTexture/Exception.h
    trunk/blender/source/gameengine/VideoTexture/ImageRender.cpp
    trunk/blender/source/gameengine/VideoTexture/ImageRender.h
    trunk/blender/source/gameengine/VideoTexture/Texture.h
    trunk/blender/source/gameengine/VideoTexture/blendVideoTex.cpp

Modified: trunk/blender/source/gameengine/Ketsji/KX_Camera.cpp
===================================================================
--- trunk/blender/source/gameengine/Ketsji/KX_Camera.cpp	2008-12-04 10:57:02 UTC (rev 17714)
+++ trunk/blender/source/gameengine/Ketsji/KX_Camera.cpp	2008-12-04 16:07:46 UTC (rev 17715)
@@ -259,10 +259,75 @@
 	if (m_set_frustum_center)
 		return;
 
+    // compute sphere for the general case and not only symmetric frustum:
+    // the mirror code in ImageRender can use very asymmetric frustum.
+    // We will put the sphere center on the line that goes from origin to the center of the far clipping plane
+    // This is the optimal position if the frustum is symmetric or very asymmetric and probably close
+    // to optimal for the general case. The sphere center position is computed so that the distance to 
+    // the near and far extreme frustum points are equal.
+
+    // get the transformation matrix from device coordinate to camera coordinate
+	MT_Matrix4x4 clip_camcs_matrix = m_projection_matrix;
+	clip_camcs_matrix.invert();
+
+    // detect which of the corner of the far clipping plane is the farthest to the origin
+	MT_Vector4 nfar;    // far point in device normalized coordinate
+    MT_Point3 farpoint; // most extreme far point in camera coordinate
+    MT_Point3 nearpoint;// most extreme near point in camera coordinate
+    MT_Point3 farcenter(0.,0.,0.);// center of far cliping plane in camera coordinate
+    MT_Scalar F=1.0, N; // square distance of far and near point to origin
+    MT_Scalar f, n;     // distance of far and near point to z axis. f is always > 0 but n can be < 0
+    MT_Scalar e, s;     // far and near clipping distance (<0)
+    MT_Scalar c;        // slope of center line = distance of far clipping center to z axis / far clipping distance
+    MT_Scalar z;        // projection of sphere center on z axis (<0)
+    // tmp value
+    MT_Vector4 npoint(1., 1., 1., 1.);
+    MT_Vector4 hpoint;
+    MT_Point3 point;
+    MT_Scalar len;
+    for (int i=0; i<4; i++)
+    {
+    	hpoint = clip_camcs_matrix*npoint;
+        point.setValue(hpoint[0]/hpoint[3], hpoint[1]/hpoint[3], hpoint[2]/hpoint[3]);
+        len = point.dot(point);
+        if (len > F)
+        {
+            nfar = npoint;
+            farpoint = point;
+            F = len;
+        }
+        // rotate by 90 degree along the z axis to walk through the 4 extreme points of the far clipping plane
+        len = npoint[0];
+        npoint[0] = -npoint[1];
+        npoint[1] = len;
+        farcenter += point;
+    }
+    // the far center is the average of the far clipping points
+    farcenter *= 0.25;
+    // the extreme near point is the opposite point on the near clipping plane
+    nfar.setValue(-nfar[0], -nfar[1], -1., 1.);
+   	nfar = clip_camcs_matrix*nfar;
+    nearpoint.setValue(nfar[0]/nfar[3], nfar[1]/nfar[3], nfar[2]/nfar[3]);
+    N = nearpoint.dot(nearpoint);
+    e = farpoint[2];
+    s = nearpoint[2];
+    // projection on XY plane for distance to axis computation
+    MT_Point2 farxy(farpoint[0], farpoint[1]);
+    // f is forced positive by construction
+    f = farxy.length();
+    // get corresponding point on the near plane
+    farxy *= s/e;
+    // this formula preserve the sign of n
+    n = f*s/e - MT_Point2(nearpoint[0]-farxy[0], nearpoint[1]-farxy[1]).length();
+    c = MT_Point2(farcenter[0], farcenter[1]).length()/e;
+    // the big formula, it simplifies to (F-N)/(2(e-s)) for the symmetric case
+    z = (F-N)/(2.0*(e-s+c*(f-n)));
+	m_frustum_center = MT_Point3(farcenter[0]*z/e, farcenter[1]*z/e, z);
+	m_frustum_radius = m_frustum_center.distance(farpoint);
+
+#if 0
 	// The most extreme points on the near and far plane. (normalized device coords)
 	MT_Vector4 hnear(1., 1., 0., 1.), hfar(1., 1., 1., 1.);
-	MT_Matrix4x4 clip_camcs_matrix = m_projection_matrix;
-	clip_camcs_matrix.invert();
 	
 	// Transform to hom camera local space
 	hnear = clip_camcs_matrix*hnear;
@@ -273,10 +338,12 @@
 	MT_Point3 farpoint(hfar[0]/hfar[3], hfar[1]/hfar[3], hfar[2]/hfar[3]);
 	
 	// Compute center
+    // don't use camera data in case the user specifies the matrix directly
 	m_frustum_center = MT_Point3(0., 0.,
-		(nearpoint.dot(nearpoint) - farpoint.dot(farpoint))/(2.0*(m_camdata.m_clipend - m_camdata.m_clipstart)));
+		(nearpoint.dot(nearpoint) - farpoint.dot(farpoint))/(2.0*(nearpoint[2]-farpoint[2] /*m_camdata.m_clipend - m_camdata.m_clipstart*/)));
 	m_frustum_radius = m_frustum_center.distance(farpoint);
-	
+#endif
+
 	// Transform to world space.
 	m_frustum_center = GetCameraToWorld()(m_frustum_center);
 	m_frustum_radius /= fabs(NodeGetWorldScaling()[NodeGetWorldScaling().closestAxis()]);

Modified: trunk/blender/source/gameengine/VideoTexture/Exception.cpp
===================================================================
--- trunk/blender/source/gameengine/VideoTexture/Exception.cpp	2008-12-04 10:57:02 UTC (rev 17714)
+++ trunk/blender/source/gameengine/VideoTexture/Exception.cpp	2008-12-04 16:07:46 UTC (rev 17715)
@@ -204,6 +204,12 @@
     ImageSizesNotMatchDesc.registerDesc();
     SceneInvalidDesc.registerDesc();
     CameraInvalidDesc.registerDesc();
+    ObserverInvalidDesc.registerDesc();
+    MirrorInvalidDesc.registerDesc();
+    MirrorSizeInvalidDesc.registerDesc();
+    MirrorNormalInvalidDesc.registerDesc();
+    MirrorHorizontalDesc.registerDesc();
+    MirrorTooSmallDesc.registerDesc();
     SourceVideoEmptyDesc.registerDesc();
     SourceVideoCreationDesc.registerDesc();
 }

Modified: trunk/blender/source/gameengine/VideoTexture/Exception.h
===================================================================
--- trunk/blender/source/gameengine/VideoTexture/Exception.h	2008-12-04 10:57:02 UTC (rev 17714)
+++ trunk/blender/source/gameengine/VideoTexture/Exception.h	2008-12-04 16:07:46 UTC (rev 17715)
@@ -202,6 +202,12 @@
 extern ExpDesc ImageSizesNotMatchDesc;
 extern ExpDesc SceneInvalidDesc;
 extern ExpDesc CameraInvalidDesc;
+extern ExpDesc ObserverInvalidDesc;
+extern ExpDesc MirrorInvalidDesc;
+extern ExpDesc MirrorSizeInvalidDesc;
+extern ExpDesc MirrorNormalInvalidDesc;
+extern ExpDesc MirrorHorizontalDesc;
+extern ExpDesc MirrorTooSmallDesc;
 extern ExpDesc SourceVideoEmptyDesc;
 extern ExpDesc SourceVideoCreationDesc;
 

Modified: trunk/blender/source/gameengine/VideoTexture/ImageRender.cpp
===================================================================
--- trunk/blender/source/gameengine/VideoTexture/ImageRender.cpp	2008-12-04 10:57:02 UTC (rev 17714)
+++ trunk/blender/source/gameengine/VideoTexture/ImageRender.cpp	2008-12-04 16:07:46 UTC (rev 17715)
@@ -24,26 +24,44 @@
 
 #include <PyObjectPlus.h>
 #include <structmember.h>
+#include <float.h>
+#include <math.h>
 
+
 #include <BIF_gl.h>
 
 #include "KX_PythonInit.h"
 #include "DNA_scene_types.h"
+#include "RAS_CameraData.h"
+#include "RAS_MeshObject.h"
+#include "BLI_arithb.h"
 
 #include "ImageRender.h"
 #include "ImageBase.h"
 #include "BlendType.h"
 #include "Exception.h"
+#include "Texture.h"
 
-ExceptionID SceneInvalid, CameraInvalid;
+ExceptionID SceneInvalid, CameraInvalid, ObserverInvalid;
+ExceptionID MirrorInvalid, MirrorSizeInvalid, MirrorNormalInvalid, MirrorHorizontal, MirrorTooSmall;
 ExpDesc SceneInvalidDesc (SceneInvalid, "Scene object is invalid");
 ExpDesc CameraInvalidDesc (CameraInvalid, "Camera object is invalid");
+ExpDesc ObserverInvalidDesc (ObserverInvalid, "Observer object is invalid");
+ExpDesc MirrorInvalidDesc (MirrorInvalid, "Mirror object is invalid");
+ExpDesc MirrorSizeInvalidDesc (MirrorSizeInvalid, "Mirror has no vertex or no size");
+ExpDesc MirrorNormalInvalidDesc (MirrorNormalInvalid, "Cannot determine mirror plane");
+ExpDesc MirrorHorizontalDesc (MirrorHorizontal, "Mirror is horizontal in local space");
+ExpDesc MirrorTooSmallDesc (MirrorTooSmall, "Mirror is too small");
 
 // constructor
 ImageRender::ImageRender (KX_Scene * scene, KX_Camera * camera) : 
     ImageViewport(),
+    m_render(true),
     m_scene(scene),
-    m_camera(camera)
+    m_camera(camera),
+    m_owncamera(false),
+    m_observer(NULL),
+    m_mirror(NULL)
 {
 	// initialize background colour
 	setBackground(0, 0, 255, 255);
@@ -57,6 +75,8 @@
 // destructor
 ImageRender::~ImageRender (void)
 {
+    if (m_owncamera)
+        m_camera->Release();
 }
 
 
@@ -91,6 +111,75 @@
 
 void ImageRender::Render()
 {
+	RAS_FrameFrustum frustrum;
+
+    if (!m_render)
+        return;
+
+    if (m_mirror)
+    {
+        // mirror mode, compute camera frustrum, position and orientation
+        // convert mirror position and normal in world space
+        const MT_Matrix3x3 & mirrorObjWorldOri = m_mirror->GetSGNode()->GetWorldOrientation();
+        const MT_Point3 & mirrorObjWorldPos = m_mirror->GetSGNode()->GetWorldPosition();
+        const MT_Vector3 & mirrorObjWorldScale = m_mirror->GetSGNode()->GetWorldScaling();
+        MT_Point3 mirrorWorldPos = 
+            mirrorObjWorldPos + mirrorObjWorldScale * (mirrorObjWorldOri * m_mirrorPos);
+        MT_Vector3 mirrorWorldZ = mirrorObjWorldOri * m_mirrorZ;
+        // get observer world position
+        const MT_Point3 & observerWorldPos = m_observer->GetSGNode()->GetWorldPosition();
+        // get plane D term = mirrorPos . normal
+        MT_Scalar mirrorPlaneDTerm = mirrorWorldPos.dot(mirrorWorldZ);
+        // compute distance of observer to mirror = D - observerPos . normal
+        MT_Scalar observerDistance = mirrorPlaneDTerm - observerWorldPos.dot(mirrorWorldZ);
+        // if distance < 0.01 => observer is on wrong side of mirror, don't render
+        if (observerDistance < 0.01f)
+            return;
+        // set camera world position = observerPos + normal * 2 * distance
+        MT_Point3 cameraWorldPos = observerWorldPos + (MT_Scalar(2.0)*observerDistance)*mirrorWorldZ;
+        m_camera->GetSGNode()->SetLocalPosition(cameraWorldPos);
+        // set camera orientation: z=normal, y=mirror_up in world space, x= y x z
+        MT_Vector3 mirrorWorldY = mirrorObjWorldOri * m_mirrorY;
+        MT_Vector3 mirrorWorldX = mirrorObjWorldOri * m_mirrorX;
+        MT_Matrix3x3 cameraWorldOri(
+            mirrorWorldX[0], mirrorWorldY[0], mirrorWorldZ[0],
+            mirrorWorldX[1], mirrorWorldY[1], mirrorWorldZ[1], 
+            mirrorWorldX[2], mirrorWorldY[2], mirrorWorldZ[2]);
+        m_camera->GetSGNode()->SetLocalOrientation(cameraWorldOri);

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list