2011-04-27 11:58:34 +00:00
|
|
|
/*
|
2013-08-18 14:16:15 +00:00
|
|
|
* Copyright 2011-2013 Blender Foundation
|
2011-04-27 11:58:34 +00:00
|
|
|
*
|
2013-08-18 14:16:15 +00:00
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
2011-04-27 11:58:34 +00:00
|
|
|
*
|
2013-08-18 14:16:15 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2011-04-27 11:58:34 +00:00
|
|
|
*
|
2013-08-18 14:16:15 +00:00
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License
|
2011-04-27 11:58:34 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "camera.h"
|
2014-09-16 17:49:59 +00:00
|
|
|
#include "mesh.h"
|
|
|
|
#include "object.h"
|
2011-04-27 11:58:34 +00:00
|
|
|
#include "scene.h"
|
|
|
|
|
2012-10-15 21:12:58 +00:00
|
|
|
#include "device.h"
|
|
|
|
|
2014-09-16 17:49:59 +00:00
|
|
|
#include "util_foreach.h"
|
2011-04-27 11:58:34 +00:00
|
|
|
#include "util_vector.h"
|
|
|
|
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
Camera::Camera()
|
|
|
|
{
|
2012-04-30 12:49:26 +00:00
|
|
|
shuttertime = 1.0f;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2011-09-16 13:14:02 +00:00
|
|
|
aperturesize = 0.0f;
|
2011-04-27 11:58:34 +00:00
|
|
|
focaldistance = 10.0f;
|
2011-09-16 13:14:02 +00:00
|
|
|
blades = 0;
|
|
|
|
bladesrotation = 0.0f;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
matrix = transform_identity();
|
|
|
|
|
2012-04-30 12:49:26 +00:00
|
|
|
motion.pre = transform_identity();
|
|
|
|
motion.post = transform_identity();
|
|
|
|
use_motion = false;
|
|
|
|
|
2014-08-27 08:51:50 +00:00
|
|
|
aperture_ratio = 1.0f;
|
|
|
|
|
2012-02-28 16:44:54 +00:00
|
|
|
type = CAMERA_PERSPECTIVE;
|
2012-05-04 16:20:51 +00:00
|
|
|
panorama_type = PANORAMA_EQUIRECTANGULAR;
|
|
|
|
fisheye_fov = M_PI_F;
|
|
|
|
fisheye_lens = 10.5f;
|
2013-05-12 14:13:29 +00:00
|
|
|
fov = M_PI_4_F;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2014-05-02 21:22:14 +00:00
|
|
|
sensorwidth = 0.036f;
|
|
|
|
sensorheight = 0.024f;
|
2012-05-04 16:20:51 +00:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
nearclip = 1e-5f;
|
|
|
|
farclip = 1e5f;
|
|
|
|
|
|
|
|
width = 1024;
|
|
|
|
height = 512;
|
2013-09-03 22:39:21 +00:00
|
|
|
resolution = 1;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2012-11-10 22:31:29 +00:00
|
|
|
viewplane.left = -((float)width/(float)height);
|
|
|
|
viewplane.right = (float)width/(float)height;
|
|
|
|
viewplane.bottom = -1.0f;
|
|
|
|
viewplane.top = 1.0f;
|
2012-06-06 23:27:43 +00:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
screentoworld = transform_identity();
|
|
|
|
rastertoworld = transform_identity();
|
|
|
|
ndctoworld = transform_identity();
|
|
|
|
rastertocamera = transform_identity();
|
|
|
|
cameratoworld = transform_identity();
|
|
|
|
worldtoraster = transform_identity();
|
|
|
|
|
|
|
|
dx = make_float3(0.0f, 0.0f, 0.0f);
|
|
|
|
dy = make_float3(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
need_update = true;
|
|
|
|
need_device_update = true;
|
2012-09-04 13:29:07 +00:00
|
|
|
previous_need_motion = -1;
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Camera::~Camera()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2014-02-14 17:40:31 +00:00
|
|
|
void Camera::compute_auto_viewplane()
|
|
|
|
{
|
|
|
|
float aspect = (float)width/(float)height;
|
|
|
|
|
|
|
|
if(width >= height) {
|
|
|
|
viewplane.left = -aspect;
|
|
|
|
viewplane.right = aspect;
|
|
|
|
viewplane.bottom = -1.0f;
|
|
|
|
viewplane.top = 1.0f;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
viewplane.left = -1.0f;
|
|
|
|
viewplane.right = 1.0f;
|
|
|
|
viewplane.bottom = -1.0f/aspect;
|
|
|
|
viewplane.top = 1.0f/aspect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
void Camera::update()
|
|
|
|
{
|
|
|
|
if(!need_update)
|
|
|
|
return;
|
|
|
|
|
2011-12-20 12:25:37 +00:00
|
|
|
/* ndc to raster */
|
2011-04-27 11:58:34 +00:00
|
|
|
Transform screentocamera;
|
2011-12-20 12:25:37 +00:00
|
|
|
Transform ndctoraster = transform_scale(width, height, 1.0f);
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
/* raster to screen */
|
2012-11-21 01:00:03 +00:00
|
|
|
Transform screentondc =
|
2012-11-10 22:31:29 +00:00
|
|
|
transform_scale(1.0f/(viewplane.right - viewplane.left),
|
|
|
|
1.0f/(viewplane.top - viewplane.bottom), 1.0f) *
|
|
|
|
transform_translate(-viewplane.left, -viewplane.bottom, 0.0f);
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2012-11-21 01:00:03 +00:00
|
|
|
Transform screentoraster = ndctoraster * screentondc;
|
2011-04-27 11:58:34 +00:00
|
|
|
Transform rastertoscreen = transform_inverse(screentoraster);
|
|
|
|
|
|
|
|
/* screen to camera */
|
2012-02-28 16:44:54 +00:00
|
|
|
if(type == CAMERA_PERSPECTIVE)
|
|
|
|
screentocamera = transform_inverse(transform_perspective(fov, nearclip, farclip));
|
|
|
|
else if(type == CAMERA_ORTHOGRAPHIC)
|
2011-04-27 11:58:34 +00:00
|
|
|
screentocamera = transform_inverse(transform_orthographic(nearclip, farclip));
|
|
|
|
else
|
2012-02-28 16:44:54 +00:00
|
|
|
screentocamera = transform_identity();
|
2012-11-21 01:00:03 +00:00
|
|
|
|
|
|
|
Transform cameratoscreen = transform_inverse(screentocamera);
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
rastertocamera = screentocamera * rastertoscreen;
|
2012-11-21 01:00:03 +00:00
|
|
|
cameratoraster = screentoraster * cameratoscreen;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
cameratoworld = matrix;
|
|
|
|
screentoworld = cameratoworld * screentocamera;
|
|
|
|
rastertoworld = cameratoworld * rastertocamera;
|
|
|
|
ndctoworld = rastertoworld * ndctoraster;
|
2012-11-21 01:00:03 +00:00
|
|
|
|
|
|
|
/* note we recompose matrices instead of taking inverses of the above, this
|
|
|
|
* is needed to avoid inverting near degenerate matrices that happen due to
|
|
|
|
* precision issues with large scenes */
|
|
|
|
worldtocamera = transform_inverse(matrix);
|
|
|
|
worldtoscreen = cameratoscreen * worldtocamera;
|
|
|
|
worldtondc = screentondc * worldtoscreen;
|
|
|
|
worldtoraster = ndctoraster * worldtondc;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
/* differentials */
|
2012-02-28 16:44:54 +00:00
|
|
|
if(type == CAMERA_ORTHOGRAPHIC) {
|
2011-04-27 11:58:34 +00:00
|
|
|
dx = transform_direction(&rastertocamera, make_float3(1, 0, 0));
|
|
|
|
dy = transform_direction(&rastertocamera, make_float3(0, 1, 0));
|
|
|
|
}
|
2012-02-28 16:44:54 +00:00
|
|
|
else if(type == CAMERA_PERSPECTIVE) {
|
2012-04-16 08:35:21 +00:00
|
|
|
dx = transform_perspective(&rastertocamera, make_float3(1, 0, 0)) -
|
|
|
|
transform_perspective(&rastertocamera, make_float3(0, 0, 0));
|
|
|
|
dy = transform_perspective(&rastertocamera, make_float3(0, 1, 0)) -
|
|
|
|
transform_perspective(&rastertocamera, make_float3(0, 0, 0));
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
2012-02-28 16:44:54 +00:00
|
|
|
else {
|
|
|
|
dx = make_float3(0, 0, 0);
|
|
|
|
dy = make_float3(0, 0, 0);
|
|
|
|
}
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
dx = transform_direction(&cameratoworld, dx);
|
|
|
|
dy = transform_direction(&cameratoworld, dy);
|
|
|
|
|
|
|
|
need_update = false;
|
|
|
|
need_device_update = true;
|
|
|
|
}
|
|
|
|
|
2012-04-30 12:49:26 +00:00
|
|
|
void Camera::device_update(Device *device, DeviceScene *dscene, Scene *scene)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2012-10-15 21:12:58 +00:00
|
|
|
Scene::MotionType need_motion = scene->need_motion(device->info.advanced_shading);
|
2012-09-04 13:29:07 +00:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
update();
|
|
|
|
|
2012-09-04 13:29:07 +00:00
|
|
|
if (previous_need_motion != need_motion) {
|
|
|
|
/* scene's motion model could have been changed since previous device
|
|
|
|
* camera update this could happen for example in case when one render
|
|
|
|
* layer has got motion pass and another not */
|
|
|
|
need_device_update = true;
|
|
|
|
}
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
if(!need_device_update)
|
|
|
|
return;
|
|
|
|
|
|
|
|
KernelCamera *kcam = &dscene->data.cam;
|
|
|
|
|
|
|
|
/* store matrices */
|
|
|
|
kcam->screentoworld = screentoworld;
|
|
|
|
kcam->rastertoworld = rastertoworld;
|
|
|
|
kcam->rastertocamera = rastertocamera;
|
|
|
|
kcam->cameratoworld = cameratoworld;
|
2012-11-21 01:00:03 +00:00
|
|
|
kcam->worldtocamera = worldtocamera;
|
|
|
|
kcam->worldtoscreen = worldtoscreen;
|
2012-04-30 12:49:26 +00:00
|
|
|
kcam->worldtoraster = worldtoraster;
|
2012-11-21 01:00:03 +00:00
|
|
|
kcam->worldtondc = worldtondc;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2012-04-30 12:49:26 +00:00
|
|
|
/* camera motion */
|
2012-05-02 09:33:45 +00:00
|
|
|
kcam->have_motion = 0;
|
2012-04-30 12:49:26 +00:00
|
|
|
|
|
|
|
if(need_motion == Scene::MOTION_PASS) {
|
2012-05-07 10:53:09 +00:00
|
|
|
if(type == CAMERA_PANORAMA) {
|
|
|
|
if(use_motion) {
|
|
|
|
kcam->motion.pre = transform_inverse(motion.pre);
|
|
|
|
kcam->motion.post = transform_inverse(motion.post);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
kcam->motion.pre = kcam->worldtocamera;
|
|
|
|
kcam->motion.post = kcam->worldtocamera;
|
|
|
|
}
|
2012-04-30 12:49:26 +00:00
|
|
|
}
|
|
|
|
else {
|
2012-05-07 10:53:09 +00:00
|
|
|
if(use_motion) {
|
2012-11-21 01:00:03 +00:00
|
|
|
kcam->motion.pre = cameratoraster * transform_inverse(motion.pre);
|
|
|
|
kcam->motion.post = cameratoraster * transform_inverse(motion.post);
|
2012-05-07 10:53:09 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
kcam->motion.pre = worldtoraster;
|
|
|
|
kcam->motion.post = worldtoraster;
|
|
|
|
}
|
2012-04-30 12:49:26 +00:00
|
|
|
}
|
|
|
|
}
|
2012-10-09 18:37:14 +00:00
|
|
|
#ifdef __CAMERA_MOTION__
|
2012-04-30 12:49:26 +00:00
|
|
|
else if(need_motion == Scene::MOTION_BLUR) {
|
2012-05-02 09:33:45 +00:00
|
|
|
if(use_motion) {
|
2012-11-29 00:43:50 +00:00
|
|
|
transform_motion_decompose((DecompMotionTransform*)&kcam->motion, &motion, &matrix);
|
2012-05-02 09:33:45 +00:00
|
|
|
kcam->have_motion = 1;
|
|
|
|
}
|
2012-04-30 12:49:26 +00:00
|
|
|
}
|
2012-10-09 18:37:14 +00:00
|
|
|
#endif
|
2012-04-30 12:49:26 +00:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* depth of field */
|
2011-09-16 13:14:02 +00:00
|
|
|
kcam->aperturesize = aperturesize;
|
2011-04-27 11:58:34 +00:00
|
|
|
kcam->focaldistance = focaldistance;
|
2011-09-16 13:14:02 +00:00
|
|
|
kcam->blades = (blades < 3)? 0.0f: blades;
|
|
|
|
kcam->bladesrotation = bladesrotation;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
/* motion blur */
|
2012-10-09 18:37:14 +00:00
|
|
|
#ifdef __CAMERA_MOTION__
|
2013-01-23 16:56:02 +00:00
|
|
|
kcam->shuttertime = (need_motion == Scene::MOTION_BLUR) ? shuttertime: -1.0f;
|
2012-10-09 18:37:14 +00:00
|
|
|
#else
|
2013-01-23 16:56:02 +00:00
|
|
|
kcam->shuttertime = -1.0f;
|
2012-10-09 18:37:14 +00:00
|
|
|
#endif
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
/* type */
|
2012-02-28 16:44:54 +00:00
|
|
|
kcam->type = type;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2014-08-27 08:51:50 +00:00
|
|
|
/* anamorphic lens bokeh */
|
|
|
|
kcam->inv_aperture_ratio = 1.0f / aperture_ratio;
|
|
|
|
|
2012-05-04 16:20:51 +00:00
|
|
|
/* panorama */
|
|
|
|
kcam->panorama_type = panorama_type;
|
|
|
|
kcam->fisheye_fov = fisheye_fov;
|
|
|
|
kcam->fisheye_lens = fisheye_lens;
|
|
|
|
|
|
|
|
/* sensor size */
|
|
|
|
kcam->sensorwidth = sensorwidth;
|
|
|
|
kcam->sensorheight = sensorheight;
|
|
|
|
|
2012-05-07 10:53:09 +00:00
|
|
|
/* render size */
|
|
|
|
kcam->width = width;
|
|
|
|
kcam->height = height;
|
2013-04-15 21:38:31 +00:00
|
|
|
kcam->resolution = resolution;
|
2012-05-07 10:53:09 +00:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* store differentials */
|
2011-12-20 12:25:45 +00:00
|
|
|
kcam->dx = float3_to_float4(dx);
|
|
|
|
kcam->dy = float3_to_float4(dy);
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
/* clipping */
|
|
|
|
kcam->nearclip = nearclip;
|
|
|
|
kcam->cliplength = (farclip == FLT_MAX)? FLT_MAX: farclip - nearclip;
|
|
|
|
|
|
|
|
need_device_update = false;
|
2012-09-04 13:29:07 +00:00
|
|
|
previous_need_motion = need_motion;
|
2014-09-16 17:49:59 +00:00
|
|
|
|
|
|
|
/* Camera in volume. */
|
|
|
|
kcam->is_inside_volume = 0;
|
2014-10-02 21:46:31 +00:00
|
|
|
BoundBox viewplane_boundbox = viewplane_bounds_get();
|
|
|
|
for(size_t i = 0; i < scene->objects.size(); ++i) {
|
|
|
|
Object *object = scene->objects[i];
|
|
|
|
if(object->mesh->has_volume &&
|
|
|
|
viewplane_boundbox.intersects(object->bounds))
|
|
|
|
{
|
|
|
|
/* TODO(sergey): Consider adding more grained check. */
|
|
|
|
kcam->is_inside_volume = 1;
|
|
|
|
break;
|
2014-09-16 17:49:59 +00:00
|
|
|
}
|
|
|
|
}
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Camera::device_free(Device *device, DeviceScene *dscene)
|
|
|
|
{
|
|
|
|
/* nothing to free, only writing to constant memory */
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Camera::modified(const Camera& cam)
|
|
|
|
{
|
2012-09-20 12:29:28 +00:00
|
|
|
return !((shuttertime == cam.shuttertime) &&
|
2011-09-16 13:14:02 +00:00
|
|
|
(aperturesize == cam.aperturesize) &&
|
|
|
|
(blades == cam.blades) &&
|
|
|
|
(bladesrotation == cam.bladesrotation) &&
|
2011-04-27 11:58:34 +00:00
|
|
|
(focaldistance == cam.focaldistance) &&
|
2012-02-28 16:44:54 +00:00
|
|
|
(type == cam.type) &&
|
2011-04-27 11:58:34 +00:00
|
|
|
(fov == cam.fov) &&
|
|
|
|
(nearclip == cam.nearclip) &&
|
|
|
|
(farclip == cam.farclip) &&
|
2012-05-04 16:20:51 +00:00
|
|
|
(sensorwidth == cam.sensorwidth) &&
|
|
|
|
(sensorheight == cam.sensorheight) &&
|
2011-04-27 11:58:34 +00:00
|
|
|
// modified for progressive render
|
|
|
|
// (width == cam.width) &&
|
|
|
|
// (height == cam.height) &&
|
2012-11-10 22:31:29 +00:00
|
|
|
(viewplane == cam.viewplane) &&
|
|
|
|
(border == cam.border) &&
|
2012-04-30 12:49:26 +00:00
|
|
|
(matrix == cam.matrix) &&
|
2014-08-27 08:51:50 +00:00
|
|
|
(aperture_ratio == cam.aperture_ratio) &&
|
2012-05-04 16:20:51 +00:00
|
|
|
(panorama_type == cam.panorama_type) &&
|
|
|
|
(fisheye_fov == cam.fisheye_fov) &&
|
|
|
|
(fisheye_lens == cam.fisheye_lens));
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
2012-10-15 21:12:58 +00:00
|
|
|
bool Camera::motion_modified(const Camera& cam)
|
|
|
|
{
|
|
|
|
return !((motion == cam.motion) &&
|
|
|
|
(use_motion == cam.use_motion));
|
|
|
|
}
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
void Camera::tag_update()
|
|
|
|
{
|
|
|
|
need_update = true;
|
|
|
|
}
|
|
|
|
|
2014-09-16 17:49:59 +00:00
|
|
|
float3 Camera::transform_raster_to_world(float raster_x, float raster_y)
|
|
|
|
{
|
|
|
|
float3 D, P;
|
|
|
|
if(type == CAMERA_PERSPECTIVE) {
|
|
|
|
D = transform_perspective(&rastertocamera,
|
|
|
|
make_float3(raster_x, raster_y, 0.0f));
|
|
|
|
P = make_float3(0.0f, 0.0f, 0.0f);
|
|
|
|
/* TODO(sergey): Aperture support? */
|
|
|
|
P = transform_point(&cameratoworld, P);
|
|
|
|
D = normalize(transform_direction(&cameratoworld, D));
|
|
|
|
/* TODO(sergey): Clipping is conditional in kernel, and hence it could
|
|
|
|
* be mistakes in here, currently leading to wrong camera-in-volume
|
|
|
|
* detection.
|
|
|
|
*/
|
|
|
|
P += nearclip * D;
|
|
|
|
}
|
|
|
|
else if (type == CAMERA_ORTHOGRAPHIC) {
|
|
|
|
D = make_float3(0.0f, 0.0f, 1.0f);
|
|
|
|
/* TODO(sergey): Aperture support? */
|
|
|
|
P = transform_perspective(&rastertocamera,
|
|
|
|
make_float3(raster_x, raster_y, 0.0f));
|
|
|
|
P = transform_point(&cameratoworld, P);
|
|
|
|
D = normalize(transform_direction(&cameratoworld, D));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
assert(!"unsupported camera type");
|
|
|
|
}
|
|
|
|
return P;
|
|
|
|
}
|
|
|
|
|
|
|
|
BoundBox Camera::viewplane_bounds_get()
|
|
|
|
{
|
|
|
|
/* TODO(sergey): This is all rather stupid, but is there a way to perform
|
|
|
|
* checks we need in a more clear and smart fasion?
|
|
|
|
*/
|
|
|
|
BoundBox bounds = BoundBox::empty;
|
2014-10-02 18:37:05 +00:00
|
|
|
|
|
|
|
if(type == CAMERA_PANORAMA) {
|
|
|
|
bounds.grow(make_float3(cameratoworld.w.x,
|
|
|
|
cameratoworld.w.y,
|
|
|
|
cameratoworld.w.z));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
bounds.grow(transform_raster_to_world(0.0f, 0.0f));
|
|
|
|
bounds.grow(transform_raster_to_world(0.0f, (float)height));
|
|
|
|
bounds.grow(transform_raster_to_world((float)width, (float)height));
|
|
|
|
bounds.grow(transform_raster_to_world((float)width, 0.0f));
|
|
|
|
if(type == CAMERA_PERSPECTIVE) {
|
|
|
|
/* Center point has the most distancei in local Z axis,
|
|
|
|
* use it to construct bounding box/
|
|
|
|
*/
|
|
|
|
bounds.grow(transform_raster_to_world(0.5f*width, 0.5f*height));
|
|
|
|
}
|
2014-09-16 17:49:59 +00:00
|
|
|
}
|
|
|
|
return bounds;
|
|
|
|
}
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
CCL_NAMESPACE_END
|
|
|
|
|