/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Copyright (c) 2007 The Zdeno Ash Miklas * * This source file is part of VideoTexture library * * Contributor(s): * * ***** END GPL LICENSE BLOCK ***** */ /** \file gameengine/VideoTexture/ImageRender.cpp * \ingroup bgevideotex */ // implementation #include "PyObjectPlus.h" #include #include #include #include "GL/glew.h" #include "KX_PythonInit.h" #include "DNA_scene_types.h" #include "RAS_CameraData.h" #include "RAS_MeshObject.h" #include "BLI_math.h" #include "ImageRender.h" #include "ImageBase.h" #include "BlendType.h" #include "Exception.h" #include "Texture.h" 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_owncamera(false), m_observer(NULL), m_mirror(NULL), m_clip(100.f), m_mirrorHalfWidth(0.f), m_mirrorHalfHeight(0.f) { // initialize background color setBackground(0, 0, 255, 255); // retrieve rendering objects m_engine = KX_GetActiveEngine(); m_rasterizer = m_engine->GetRasterizer(); m_canvas = m_engine->GetCanvas(); } // destructor ImageRender::~ImageRender (void) { if (m_owncamera) m_camera->Release(); } // set background color void ImageRender::setBackground (int red, int green, int blue, int alpha) { m_background[0] = (red < 0) ? 0.f : (red > 255) ? 1.f : float(red)/255.f; m_background[1] = (green < 0) ? 0.f : (green > 255) ? 1.f : float(green)/255.f; m_background[2] = (blue < 0) ? 0.f : (blue > 255) ? 1.f : float(blue)/255.f; m_background[3] = (alpha < 0) ? 0.f : (alpha > 255) ? 1.f : float(alpha)/255.f; } // capture image from viewport void ImageRender::calcImage (unsigned int texId, double ts) { if (m_rasterizer->GetDrawingMode() != RAS_IRasterizer::KX_TEXTURED || // no need for texture m_camera->GetViewport() || // camera must be inactive m_camera == m_scene->GetActiveCamera()) { // no need to compute texture in non texture rendering m_avail = false; return; } // render the scene from the camera Render(); // get image from viewport ImageViewport::calcImage(texId, ts); // restore OpenGL state m_canvas->EndFrame(); } 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.01) 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); m_camera->GetSGNode()->UpdateWorldData(0.0); // compute camera frustrum: // get position of mirror relative to camera: offset = mirrorPos-cameraPos MT_Vector3 mirrorOffset = mirrorWorldPos - cameraWorldPos; // convert to camera orientation mirrorOffset = mirrorOffset * cameraWorldOri; // scale mirror size to world scale: // get closest local axis for mirror Y and X axis and scale height and width by local axis scale MT_Scalar x, y; x = fabs(m_mirrorY[0]); y = fabs(m_mirrorY[1]); float height = (x > y) ? ((x > fabs(m_mirrorY[2])) ? mirrorObjWorldScale[0] : mirrorObjWorldScale[2]): ((y > fabs(m_mirrorY[2])) ? mirrorObjWorldScale[1] : mirrorObjWorldScale[2]); x = fabs(m_mirrorX[0]); y = fabs(m_mirrorX[1]); float width = (x > y) ? ((x > fabs(m_mirrorX[2])) ? mirrorObjWorldScale[0] : mirrorObjWorldScale[2]): ((y > fabs(m_mirrorX[2])) ? mirrorObjWorldScale[1] : mirrorObjWorldScale[2]); width *= m_mirrorHalfWidth; height *= m_mirrorHalfHeight; // left = offsetx-width // right = offsetx+width // top = offsety+height // bottom = offsety-height // near = -offsetz // far = near+100 frustrum.x1 = mirrorOffset[0]-width; frustrum.x2 = mirrorOffset[0]+width; frustrum.y1 = mirrorOffset[1]-height; frustrum.y2 = mirrorOffset[1]+height; frustrum.camnear = -mirrorOffset[2]; frustrum.camfar = -mirrorOffset[2]+m_clip; } // Store settings to be restored later const RAS_IRasterizer::StereoMode stereomode = m_rasterizer->GetStereoMode(); RAS_Rect area = m_canvas->GetWindowArea(); // The screen area that ImageViewport will copy is also the rendering zone m_canvas->SetViewPort(m_position[0], m_position[1], m_position[0]+m_capSize[0]-1, m_position[1]+m_capSize[1]-1); m_canvas->ClearColor(m_background[0], m_background[1], m_background[2], m_background[3]); m_canvas->ClearBuffer(RAS_ICanvas::COLOR_BUFFER|RAS_ICanvas::DEPTH_BUFFER); m_rasterizer->BeginFrame(RAS_IRasterizer::KX_TEXTURED,m_engine->GetClockTime()); m_engine->SetWorldSettings(m_scene->GetWorldInfo()); m_rasterizer->SetAuxilaryClientInfo(m_scene); m_rasterizer->DisplayFog(); // matrix calculation, don't apply any of the stereo mode m_rasterizer->SetStereoMode(RAS_IRasterizer::RAS_STEREO_NOSTEREO); if (m_mirror) { // frustrum was computed above // get frustrum matrix and set projection matrix MT_Matrix4x4 projmat = m_rasterizer->GetFrustumMatrix( frustrum.x1, frustrum.x2, frustrum.y1, frustrum.y2, frustrum.camnear, frustrum.camfar); m_camera->SetProjectionMatrix(projmat); } else if (m_camera->hasValidProjectionMatrix()) { m_rasterizer->SetProjectionMatrix(m_camera->GetProjectionMatrix()); } else { float lens = m_camera->GetLens(); float sensor_x = m_camera->GetSensorWidth(); float sensor_y = m_camera->GetSensorHeight(); bool orthographic = !m_camera->GetCameraData()->m_perspective; float nearfrust = m_camera->GetCameraNear(); float farfrust = m_camera->GetCameraFar(); float aspect_ratio = 1.0f; Scene *blenderScene = m_scene->GetBlenderScene(); MT_Matrix4x4 projmat; // compute the aspect ratio from frame blender scene settings so that render to texture // works the same in Blender and in Blender player if (blenderScene->r.ysch != 0) aspect_ratio = float(blenderScene->r.xsch*blenderScene->r.xasp) / float(blenderScene->r.ysch*blenderScene->r.yasp); if (orthographic) { RAS_FramingManager::ComputeDefaultOrtho( nearfrust, farfrust, m_camera->GetScale(), aspect_ratio, m_camera->GetSensorFit(), frustrum ); projmat = m_rasterizer->GetOrthoMatrix( frustrum.x1, frustrum.x2, frustrum.y1, frustrum.y2, frustrum.camnear, frustrum.camfar); } else { RAS_FramingManager::ComputeDefaultFrustum( nearfrust, farfrust, lens, sensor_x, sensor_y, RAS_SENSORFIT_AUTO, aspect_ratio, frustrum); projmat = m_rasterizer->GetFrustumMatrix( frustrum.x1, frustrum.x2, frustrum.y1, frustrum.y2, frustrum.camnear, frustrum.camfar); } m_camera->SetProjectionMatrix(projmat); } MT_Transform camtrans(m_camera->GetWorldToCamera()); MT_Matrix4x4 viewmat(camtrans); m_rasterizer->SetViewMatrix(viewmat, m_camera->NodeGetWorldOrientation(), m_camera->NodeGetWorldPosition(), m_camera->GetCameraData()->m_perspective); m_camera->SetModelviewMatrix(viewmat); // restore the stereo mode now that the matrix is computed m_rasterizer->SetStereoMode(stereomode); m_scene->CalculateVisibleMeshes(m_rasterizer,m_camera); m_scene->RenderBuckets(camtrans, m_rasterizer); m_scene->RenderFonts(); // restore the canvas area now that the render is completed m_canvas->GetWindowArea() = area; } // cast Image pointer to ImageRender inline ImageRender * getImageRender (PyImage *self) { return static_cast(self->m_image); } // python methods // Blender Scene type static BlendType sceneType ("KX_Scene"); // Blender Camera type static BlendType cameraType ("KX_Camera"); // object initialization static int ImageRender_init(PyObject *pySelf, PyObject *args, PyObject *kwds) { // parameters - scene object PyObject *scene; // camera object PyObject *camera; // parameter keywords static const char *kwlist[] = {"sceneObj", "cameraObj", NULL}; // get parameters if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", const_cast(kwlist), &scene, &camera)) return -1; try { // get scene pointer KX_Scene * scenePtr (NULL); if (scene != NULL) scenePtr = sceneType.checkType(scene); // throw exception if scene is not available if (scenePtr == NULL) THRWEXCP(SceneInvalid, S_OK); // get camera pointer KX_Camera * cameraPtr (NULL); if (camera != NULL) cameraPtr = cameraType.checkType(camera); // throw exception if camera is not available if (cameraPtr == NULL) THRWEXCP(CameraInvalid, S_OK); // get pointer to image structure PyImage *self = reinterpret_cast(pySelf); // create source object if (self->m_image != NULL) delete self->m_image; self->m_image = new ImageRender(scenePtr, cameraPtr); } catch (Exception & exp) { exp.report(); return -1; } // initialization succeded return 0; } // get background color static PyObject *getBackground (PyImage *self, void *closure) { return Py_BuildValue("[BBBB]", getImageRender(self)->getBackground(0), getImageRender(self)->getBackground(1), getImageRender(self)->getBackground(2), getImageRender(self)->getBackground(3)); } // set color static int setBackground(PyImage *self, PyObject *value, void *closure) { // check validity of parameter if (value == NULL || !PySequence_Check(value) || PySequence_Size(value) != 4 || !PyLong_Check(PySequence_Fast_GET_ITEM(value, 0)) || !PyLong_Check(PySequence_Fast_GET_ITEM(value, 1)) || !PyLong_Check(PySequence_Fast_GET_ITEM(value, 2)) || !PyLong_Check(PySequence_Fast_GET_ITEM(value, 3))) { PyErr_SetString(PyExc_TypeError, "The value must be a sequence of 4 integer between 0 and 255"); return -1; } // set background color getImageRender(self)->setBackground( (unsigned char)(PyLong_AsLong(PySequence_Fast_GET_ITEM(value, 0))), (unsigned char)(PyLong_AsLong(PySequence_Fast_GET_ITEM(value, 1))), (unsigned char)(PyLong_AsLong(PySequence_Fast_GET_ITEM(value, 2))), (unsigned char)(PyLong_AsLong(PySequence_Fast_GET_ITEM(value, 3)))); // success return 0; } // methods structure static PyMethodDef imageRenderMethods[] = { // methods from ImageBase class {"refresh", (PyCFunction)Image_refresh, METH_NOARGS, "Refresh image - invalidate its current content"}, {NULL} }; // attributes structure static PyGetSetDef imageRenderGetSets[] = { {(char*)"background", (getter)getBackground, (setter)setBackground, (char*)"background color", NULL}, // attribute from ImageViewport {(char*)"capsize", (getter)ImageViewport_getCaptureSize, (setter)ImageViewport_setCaptureSize, (char*)"size of render area", NULL}, {(char*)"alpha", (getter)ImageViewport_getAlpha, (setter)ImageViewport_setAlpha, (char*)"use alpha in texture", NULL}, {(char*)"whole", (getter)ImageViewport_getWhole, (setter)ImageViewport_setWhole, (char*)"use whole viewport to render", NULL}, // attributes from ImageBase class {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL}, {(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL}, {(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL}, {(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbor)", NULL}, {(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL}, {(char*)"zbuff", (getter)Image_getZbuff, (setter)Image_setZbuff, (char*)"use depth buffer as texture", NULL}, {(char*)"depth", (getter)Image_getDepth, (setter)Image_setDepth, (char*)"get depth information from z-buffer using unsigned int precision", NULL}, {(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL}, {NULL} }; // define python type PyTypeObject ImageRenderType = { PyVarObject_HEAD_INIT(NULL, 0) "VideoTexture.ImageRender", /*tp_name*/ sizeof(PyImage), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Image_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ &imageBufferProcs, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "Image source from render", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ imageRenderMethods, /* tp_methods */ 0, /* tp_members */ imageRenderGetSets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)ImageRender_init, /* tp_init */ 0, /* tp_alloc */ Image_allocNew, /* tp_new */ }; // object initialization static int ImageMirror_init(PyObject *pySelf, PyObject *args, PyObject *kwds) { // parameters - scene object PyObject *scene; // reference object for mirror PyObject *observer; // object holding the mirror PyObject *mirror; // material of the mirror short materialID = 0; // parameter keywords static const char *kwlist[] = {"scene", "observer", "mirror", "material", NULL}; // get parameters if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOO|h", const_cast(kwlist), &scene, &observer, &mirror, &materialID)) return -1; try { // get scene pointer KX_Scene * scenePtr (NULL); if (scene != NULL && PyObject_TypeCheck(scene, &KX_Scene::Type)) scenePtr = static_castBGE_PROXY_REF(scene); else THRWEXCP(SceneInvalid, S_OK); if (scenePtr==NULL) /* in case the python proxy reference is invalid */ THRWEXCP(SceneInvalid, S_OK); // get observer pointer KX_GameObject * observerPtr (NULL); if (observer != NULL && PyObject_TypeCheck(observer, &KX_GameObject::Type)) observerPtr = static_castBGE_PROXY_REF(observer); else if (observer != NULL && PyObject_TypeCheck(observer, &KX_Camera::Type)) observerPtr = static_castBGE_PROXY_REF(observer); else THRWEXCP(ObserverInvalid, S_OK); if (observerPtr==NULL) /* in case the python proxy reference is invalid */ THRWEXCP(ObserverInvalid, S_OK); // get mirror pointer KX_GameObject * mirrorPtr (NULL); if (mirror != NULL && PyObject_TypeCheck(mirror, &KX_GameObject::Type)) mirrorPtr = static_castBGE_PROXY_REF(mirror); else THRWEXCP(MirrorInvalid, S_OK); if (mirrorPtr==NULL) /* in case the python proxy reference is invalid */ THRWEXCP(MirrorInvalid, S_OK); // locate the material in the mirror RAS_IPolyMaterial * material = getMaterial(mirror, materialID); if (material == NULL) THRWEXCP(MaterialNotAvail, S_OK); // get pointer to image structure PyImage *self = reinterpret_cast(pySelf); // create source object if (self->m_image != NULL) { delete self->m_image; self->m_image = NULL; } self->m_image = new ImageRender(scenePtr, observerPtr, mirrorPtr, material); } catch (Exception & exp) { exp.report(); return -1; } // initialization succeded return 0; } // get background color static PyObject *getClip (PyImage *self, void *closure) { return PyFloat_FromDouble(getImageRender(self)->getClip()); } // set clip static int setClip(PyImage *self, PyObject *value, void *closure) { // check validity of parameter double clip; if (value == NULL || !PyFloat_Check(value) || (clip = PyFloat_AsDouble(value)) < 0.01 || clip > 5000.0) { PyErr_SetString(PyExc_TypeError, "The value must be an float between 0.01 and 5000"); return -1; } // set background color getImageRender(self)->setClip(float(clip)); // success return 0; } // attributes structure static PyGetSetDef imageMirrorGetSets[] = { {(char*)"clip", (getter)getClip, (setter)setClip, (char*)"clipping distance", NULL}, // attribute from ImageRender {(char*)"background", (getter)getBackground, (setter)setBackground, (char*)"background color", NULL}, // attribute from ImageViewport {(char*)"capsize", (getter)ImageViewport_getCaptureSize, (setter)ImageViewport_setCaptureSize, (char*)"size of render area", NULL}, {(char*)"alpha", (getter)ImageViewport_getAlpha, (setter)ImageViewport_setAlpha, (char*)"use alpha in texture", NULL}, {(char*)"whole", (getter)ImageViewport_getWhole, (setter)ImageViewport_setWhole, (char*)"use whole viewport to render", NULL}, // attributes from ImageBase class {(char*)"valid", (getter)Image_valid, NULL, (char*)"bool to tell if an image is available", NULL}, {(char*)"image", (getter)Image_getImage, NULL, (char*)"image data", NULL}, {(char*)"size", (getter)Image_getSize, NULL, (char*)"image size", NULL}, {(char*)"scale", (getter)Image_getScale, (setter)Image_setScale, (char*)"fast scale of image (near neighbor)", NULL}, {(char*)"flip", (getter)Image_getFlip, (setter)Image_setFlip, (char*)"flip image vertically", NULL}, {(char*)"zbuff", (getter)Image_getZbuff, (setter)Image_setZbuff, (char*)"use depth buffer as texture", NULL}, {(char*)"depth", (getter)Image_getDepth, (setter)Image_setDepth, (char*)"get depth information from z-buffer using unsigned int precision", NULL}, {(char*)"filter", (getter)Image_getFilter, (setter)Image_setFilter, (char*)"pixel filter", NULL}, {NULL} }; // constructor ImageRender::ImageRender (KX_Scene *scene, KX_GameObject *observer, KX_GameObject *mirror, RAS_IPolyMaterial *mat) : ImageViewport(), m_render(false), m_scene(scene), m_observer(observer), m_mirror(mirror), m_clip(100.f) { // this constructor is used for automatic planar mirror // create a camera, take all data by default, in any case we will recompute the frustrum on each frame RAS_CameraData camdata; vector mirrorVerts; vector::iterator it; float mirrorArea = 0.f; float mirrorNormal[3] = {0.f, 0.f, 0.f}; float mirrorUp[3]; float dist, vec[3], axis[3]; float zaxis[3] = {0.f, 0.f, 1.f}; float yaxis[3] = {0.f, 1.f, 0.f}; float mirrorMat[3][3]; float left, right, top, bottom, back; // make sure this camera will delete its node m_camera= new KX_Camera(scene, KX_Scene::m_callbacks, camdata, true, true); m_camera->SetName("__mirror__cam__"); // don't add the camera to the scene object list, it doesn't need to be accessible m_owncamera = true; // retrieve rendering objects m_engine = KX_GetActiveEngine(); m_rasterizer = m_engine->GetRasterizer(); m_canvas = m_engine->GetCanvas(); // locate the vertex assigned to mat and do following calculation in mesh coordinates for (int meshIndex = 0; meshIndex < mirror->GetMeshCount(); meshIndex++) { RAS_MeshObject* mesh = mirror->GetMesh(meshIndex); int numPolygons = mesh->NumPolygons(); for (int polygonIndex=0; polygonIndex < numPolygons; polygonIndex++) { RAS_Polygon* polygon = mesh->GetPolygon(polygonIndex); if (polygon->GetMaterial()->GetPolyMaterial() == mat) { RAS_TexVert *v1, *v2, *v3, *v4; float normal[3]; float area; // this polygon is part of the mirror, v1 = polygon->GetVertex(0); v2 = polygon->GetVertex(1); v3 = polygon->GetVertex(2); mirrorVerts.push_back(v1); mirrorVerts.push_back(v2); mirrorVerts.push_back(v3); if (polygon->VertexCount() == 4) { v4 = polygon->GetVertex(3); mirrorVerts.push_back(v4); area = normal_quad_v3(normal,(float*)v1->getXYZ(), (float*)v2->getXYZ(), (float*)v3->getXYZ(), (float*)v4->getXYZ()); } else { area = normal_tri_v3(normal,(float*)v1->getXYZ(), (float*)v2->getXYZ(), (float*)v3->getXYZ()); } area = fabs(area); mirrorArea += area; mul_v3_fl(normal, area); add_v3_v3v3(mirrorNormal, mirrorNormal, normal); } } } if (mirrorVerts.size() == 0 || mirrorArea < FLT_EPSILON) { // no vertex or zero size mirror THRWEXCP(MirrorSizeInvalid, S_OK); } // compute average normal of mirror faces mul_v3_fl(mirrorNormal, 1.0f/mirrorArea); if (normalize_v3(mirrorNormal) == 0.f) { // no normal THRWEXCP(MirrorNormalInvalid, S_OK); } // the mirror plane has an equation of the type ax+by+cz = d where (a,b,c) is the normal vector // if the mirror is more vertical then horizontal, the Z axis is the up direction. // otherwise the Y axis is the up direction. // If the mirror is not perfectly vertical(horizontal), the Z(Y) axis projection on the mirror // plan by the normal will be the up direction. if (fabsf(mirrorNormal[2]) > fabsf(mirrorNormal[1]) && fabsf(mirrorNormal[2]) > fabsf(mirrorNormal[0])) { // the mirror is more horizontal than vertical copy_v3_v3(axis, yaxis); } else { // the mirror is more vertical than horizontal copy_v3_v3(axis, zaxis); } dist = dot_v3v3(mirrorNormal, axis); if (fabsf(dist) < FLT_EPSILON) { // the mirror is already fully aligned with up axis copy_v3_v3(mirrorUp, axis); } else { // projection of axis to mirror plane through normal copy_v3_v3(vec, mirrorNormal); mul_v3_fl(vec, dist); sub_v3_v3v3(mirrorUp, axis, vec); if (normalize_v3(mirrorUp) == 0.f) { // should not happen THRWEXCP(MirrorHorizontal, S_OK); return; } } // compute rotation matrix between local coord and mirror coord // to match camera orientation, we select mirror z = -normal, y = up, x = y x z negate_v3_v3(mirrorMat[2], mirrorNormal); copy_v3_v3(mirrorMat[1], mirrorUp); cross_v3_v3v3(mirrorMat[0], mirrorMat[1], mirrorMat[2]); // transpose to make it a orientation matrix from local space to mirror space transpose_m3(mirrorMat); // transform all vertex to plane coordinates and determine mirror position left = FLT_MAX; right = -FLT_MAX; bottom = FLT_MAX; top = -FLT_MAX; back = -FLT_MAX; // most backward vertex (=highest Z coord in mirror space) for (it = mirrorVerts.begin(); it != mirrorVerts.end(); it++) { copy_v3_v3(vec, (float*)(*it)->getXYZ()); mul_m3_v3(mirrorMat, vec); if (vec[0] < left) left = vec[0]; if (vec[0] > right) right = vec[0]; if (vec[1] < bottom) bottom = vec[1]; if (vec[1] > top) top = vec[1]; if (vec[2] > back) back = vec[2]; } // now store this information in the object for later rendering m_mirrorHalfWidth = (right-left)*0.5f; m_mirrorHalfHeight = (top-bottom)*0.5f; if (m_mirrorHalfWidth < 0.01f || m_mirrorHalfHeight < 0.01f) { // mirror too small THRWEXCP(MirrorTooSmall, S_OK); } // mirror position in mirror coord vec[0] = (left+right)*0.5f; vec[1] = (top+bottom)*0.5f; vec[2] = back; // convert it in local space: transpose again the matrix to get back to mirror to local transform transpose_m3(mirrorMat); mul_m3_v3(mirrorMat, vec); // mirror position in local space m_mirrorPos.setValue(vec[0], vec[1], vec[2]); // mirror normal vector (pointed towards the back of the mirror) in local space m_mirrorZ.setValue(-mirrorNormal[0], -mirrorNormal[1], -mirrorNormal[2]); m_mirrorY.setValue(mirrorUp[0], mirrorUp[1], mirrorUp[2]); m_mirrorX = m_mirrorY.cross(m_mirrorZ); m_render = true; setBackground(0, 0, 255, 255); } // define python type PyTypeObject ImageMirrorType = { PyVarObject_HEAD_INIT(NULL, 0) "VideoTexture.ImageMirror", /*tp_name*/ sizeof(PyImage), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Image_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ &imageBufferProcs, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "Image source from mirror", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ imageRenderMethods, /* tp_methods */ 0, /* tp_members */ imageMirrorGetSets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)ImageMirror_init, /* tp_init */ 0, /* tp_alloc */ Image_allocNew, /* tp_new */ };