/*
 * Copyright 2011-2013 Blender Foundation
 *
 * 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
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

#include "render/camera.h"
#include "render/scene.h"

#include "blender/blender_sync.h"
#include "blender/blender_util.h"

#include "util/util_logging.h"

CCL_NAMESPACE_BEGIN

/* Blender Camera Intermediate: we first convert both the offline and 3d view
 * render camera to this, and from there convert to our native camera format. */

struct BlenderCamera {
	float nearclip;
	float farclip;

	CameraType type;
	float ortho_scale;

	float lens;
	float shuttertime;
	Camera::MotionPosition motion_position;
	array<float> shutter_curve;

	Camera::RollingShutterType rolling_shutter_type;
	float rolling_shutter_duration;

	float aperturesize;
	uint apertureblades;
	float aperturerotation;
	float focaldistance;

	float2 shift;
	float2 offset;
	float zoom;

	float2 pixelaspect;

	float aperture_ratio;

	PanoramaType panorama_type;
	float fisheye_fov;
	float fisheye_lens;
	float latitude_min;
	float latitude_max;
	float longitude_min;
	float longitude_max;
	bool use_spherical_stereo;
	float interocular_distance;
	float convergence_distance;
	bool use_pole_merge;
	float pole_merge_angle_from;
	float pole_merge_angle_to;

	enum { AUTO, HORIZONTAL, VERTICAL } sensor_fit;
	float sensor_width;
	float sensor_height;

	int full_width;
	int full_height;

	BoundBox2D border;
	BoundBox2D pano_viewplane;
	BoundBox2D viewport_camera_border;

	Transform matrix;
};

static void blender_camera_init(BlenderCamera *bcam,
                                BL::RenderSettings& b_render)
{
	memset(bcam, 0, sizeof(BlenderCamera));

	bcam->type = CAMERA_PERSPECTIVE;
	bcam->zoom = 1.0f;
	bcam->pixelaspect = make_float2(1.0f, 1.0f);
	bcam->sensor_width = 32.0f;
	bcam->sensor_height = 18.0f;
	bcam->sensor_fit = BlenderCamera::AUTO;
	bcam->shuttertime = 1.0f;
	bcam->motion_position = Camera::MOTION_POSITION_CENTER;
	bcam->rolling_shutter_type = Camera::ROLLING_SHUTTER_NONE;
	bcam->rolling_shutter_duration = 0.1f;
	bcam->border.right = 1.0f;
	bcam->border.top = 1.0f;
	bcam->pano_viewplane.right = 1.0f;
	bcam->pano_viewplane.top = 1.0f;
	bcam->viewport_camera_border.right = 1.0f;
	bcam->viewport_camera_border.top = 1.0f;

	/* render resolution */
	bcam->full_width = render_resolution_x(b_render);
	bcam->full_height = render_resolution_y(b_render);
}

static float blender_camera_focal_distance(BL::RenderEngine& b_engine,
                                           BL::Object& b_ob,
                                           BL::Camera& b_camera,
                                           BlenderCamera *bcam)
{
	BL::Object b_dof_object = b_camera.dof_object();

	if(!b_dof_object)
		return b_camera.dof_distance();
	
	/* for dof object, return distance along camera Z direction */
	BL::Array<float, 16> b_ob_matrix;
	b_engine.camera_model_matrix(b_ob, bcam->use_spherical_stereo, b_ob_matrix);
	Transform obmat = transform_clear_scale(get_transform(b_ob_matrix));
	Transform dofmat = get_transform(b_dof_object.matrix_world());
	float3 view_dir = normalize(transform_get_column(&obmat, 2));
	float3 dof_dir = transform_get_column(&obmat, 3) - transform_get_column(&dofmat, 3);
	return fabsf(dot(view_dir, dof_dir));
}

static void blender_camera_from_object(BlenderCamera *bcam,
                                       BL::RenderEngine& b_engine,
                                       BL::Object& b_ob,
                                       bool skip_panorama = false)
{
	BL::ID b_ob_data = b_ob.data();

	if(b_ob_data.is_a(&RNA_Camera)) {
		BL::Camera b_camera(b_ob_data);
		PointerRNA ccamera = RNA_pointer_get(&b_camera.ptr, "cycles");

		bcam->nearclip = b_camera.clip_start();
		bcam->farclip = b_camera.clip_end();

		switch(b_camera.type())
		{
			case BL::Camera::type_ORTHO:
				bcam->type = CAMERA_ORTHOGRAPHIC;
				break;
			case BL::Camera::type_PANO:
				if(!skip_panorama)
					bcam->type = CAMERA_PANORAMA;
				else
					bcam->type = CAMERA_PERSPECTIVE;
				break;
			case BL::Camera::type_PERSP:
			default:
				bcam->type = CAMERA_PERSPECTIVE;
				break;
		}

		bcam->panorama_type = (PanoramaType)get_enum(ccamera,
		                                             "panorama_type",
		                                             PANORAMA_NUM_TYPES,
		                                             PANORAMA_EQUIRECTANGULAR);

		bcam->fisheye_fov = RNA_float_get(&ccamera, "fisheye_fov");
		bcam->fisheye_lens = RNA_float_get(&ccamera, "fisheye_lens");
		bcam->latitude_min = RNA_float_get(&ccamera, "latitude_min");
		bcam->latitude_max = RNA_float_get(&ccamera, "latitude_max");
		bcam->longitude_min = RNA_float_get(&ccamera, "longitude_min");
		bcam->longitude_max = RNA_float_get(&ccamera, "longitude_max");

		bcam->interocular_distance = b_camera.stereo().interocular_distance();
		if(b_camera.stereo().convergence_mode() == BL::CameraStereoData::convergence_mode_PARALLEL) {
			bcam->convergence_distance = FLT_MAX;
		}
		else {
			bcam->convergence_distance = b_camera.stereo().convergence_distance();
		}
		bcam->use_spherical_stereo = b_engine.use_spherical_stereo(b_ob);

		bcam->use_pole_merge = b_camera.stereo().use_pole_merge();
		bcam->pole_merge_angle_from = b_camera.stereo().pole_merge_angle_from();
		bcam->pole_merge_angle_to = b_camera.stereo().pole_merge_angle_to();

		bcam->ortho_scale = b_camera.ortho_scale();

		bcam->lens = b_camera.lens();

		/* allow f/stop number to change aperture_size but still
		 * give manual control over aperture radius */
		int aperture_type = get_enum(ccamera, "aperture_type");

		if(aperture_type == 1) {
			float fstop = RNA_float_get(&ccamera, "aperture_fstop");
			fstop = max(fstop, 1e-5f);

			if(bcam->type == CAMERA_ORTHOGRAPHIC)
				bcam->aperturesize = 1.0f/(2.0f*fstop);
			else
				bcam->aperturesize = (bcam->lens*1e-3f)/(2.0f*fstop);
		}
		else
			bcam->aperturesize = RNA_float_get(&ccamera, "aperture_size");

		bcam->apertureblades = RNA_int_get(&ccamera, "aperture_blades");
		bcam->aperturerotation = RNA_float_get(&ccamera, "aperture_rotation");
		bcam->focaldistance = blender_camera_focal_distance(b_engine, b_ob, b_camera, bcam);
		bcam->aperture_ratio = RNA_float_get(&ccamera, "aperture_ratio");

		bcam->shift.x = b_engine.camera_shift_x(b_ob, bcam->use_spherical_stereo);
		bcam->shift.y = b_camera.shift_y();

		bcam->sensor_width = b_camera.sensor_width();
		bcam->sensor_height = b_camera.sensor_height();

		if(b_camera.sensor_fit() == BL::Camera::sensor_fit_AUTO)
			bcam->sensor_fit = BlenderCamera::AUTO;
		else if(b_camera.sensor_fit() == BL::Camera::sensor_fit_HORIZONTAL)
			bcam->sensor_fit = BlenderCamera::HORIZONTAL;
		else
			bcam->sensor_fit = BlenderCamera::VERTICAL;
	}
	else {
		/* from lamp not implemented yet */
	}
}

static Transform blender_camera_matrix(const Transform& tfm,
                                       const CameraType type,
                                       const PanoramaType panorama_type)
{
	Transform result;

	if(type == CAMERA_PANORAMA) {
		if(panorama_type == PANORAMA_MIRRORBALL) {
			/* Mirror ball camera is looking into the negative Y direction
			 * which matches texture mirror ball mapping.
			 */
			result = tfm *
				make_transform(1.0f, 0.0f, 0.0f, 0.0f,
				               0.0f, 0.0f, 1.0f, 0.0f,
				               0.0f, 1.0f, 0.0f, 0.0f,
				               0.0f, 0.0f, 0.0f, 1.0f);
		}
		else {
			/* Make it so environment camera needs to be pointed in the direction
			 * of the positive x-axis to match an environment texture, this way
			 * it is looking at the center of the texture
			 */
			result = tfm *
				make_transform( 0.0f, -1.0f, 0.0f, 0.0f,
				                0.0f,  0.0f, 1.0f, 0.0f,
				               -1.0f,  0.0f, 0.0f, 0.0f,
				                0.0f,  0.0f, 0.0f, 1.0f);
		}
	}
	else {
		/* note the blender camera points along the negative z-axis */
		result = tfm * transform_scale(1.0f, 1.0f, -1.0f);
	}

	return transform_clear_scale(result);
}

static void blender_camera_viewplane(BlenderCamera *bcam,
                                     int width, int height,
                                     BoundBox2D *viewplane,
                                     float *aspectratio,
                                     float *sensor_size)
{
	/* dimensions */
	float xratio = (float)width*bcam->pixelaspect.x;
	float yratio = (float)height*bcam->pixelaspect.y;

	/* compute x/y aspect and ratio */
	float xaspect, yaspect;
	bool horizontal_fit;

	/* sensor fitting */
	if(bcam->sensor_fit == BlenderCamera::AUTO) {
		horizontal_fit = (xratio > yratio);
		if(sensor_size != NULL) {
			*sensor_size = bcam->sensor_width;
		}
	}
	else if(bcam->sensor_fit == BlenderCamera::HORIZONTAL) {
		horizontal_fit = true;
		if(sensor_size != NULL) {
			*sensor_size = bcam->sensor_width;
		}
	}
	else {
		horizontal_fit = false;
		if(sensor_size != NULL) {
			*sensor_size = bcam->sensor_height;
		}
	}

	if(horizontal_fit) {
		if(aspectratio != NULL) {
			*aspectratio = xratio/yratio;
		}
		xaspect = *aspectratio;
		yaspect = 1.0f;
	}
	else {
		if(aspectratio != NULL) {
			*aspectratio = yratio/xratio;
		}
		xaspect = 1.0f;
		yaspect = *aspectratio;
	}

	/* modify aspect for orthographic scale */
	if(bcam->type == CAMERA_ORTHOGRAPHIC) {
		xaspect = xaspect*bcam->ortho_scale/(*aspectratio*2.0f);
		yaspect = yaspect*bcam->ortho_scale/(*aspectratio*2.0f);
		if(aspectratio != NULL) {
			*aspectratio = bcam->ortho_scale/2.0f;
		}
	}

	if(bcam->type == CAMERA_PANORAMA) {
		/* set viewplane */
		if(viewplane != NULL) {
			*viewplane = bcam->pano_viewplane;
		}
	}
	else {
		/* set viewplane */
		if(viewplane != NULL) {
			viewplane->left = -xaspect;
			viewplane->right = xaspect;
			viewplane->bottom = -yaspect;
			viewplane->top = yaspect;

			/* zoom for 3d camera view */
			*viewplane = (*viewplane) * bcam->zoom;

			/* modify viewplane with camera shift and 3d camera view offset */
			float dx = 2.0f*(*aspectratio*bcam->shift.x + bcam->offset.x*xaspect*2.0f);
			float dy = 2.0f*(*aspectratio*bcam->shift.y + bcam->offset.y*yaspect*2.0f);

			viewplane->left += dx;
			viewplane->right += dx;
			viewplane->bottom += dy;
			viewplane->top += dy;
		}
	}
}

static void blender_camera_sync(Camera *cam, BlenderCamera *bcam, int width, int height, const char *viewname)
{
	/* copy camera to compare later */
	Camera prevcam = *cam;
	float aspectratio, sensor_size;

	/* viewplane */
	blender_camera_viewplane(bcam, width, height,
		&cam->viewplane, &aspectratio, &sensor_size);

	cam->width = bcam->full_width;
	cam->height = bcam->full_height;

	cam->full_width = width;
	cam->full_height = height;

	/* panorama sensor */
	if(bcam->type == CAMERA_PANORAMA && bcam->panorama_type == PANORAMA_FISHEYE_EQUISOLID) {
		float fit_xratio = (float)bcam->full_width*bcam->pixelaspect.x;
		float fit_yratio = (float)bcam->full_height*bcam->pixelaspect.y;
		bool horizontal_fit;
		float sensor_size;

		if(bcam->sensor_fit == BlenderCamera::AUTO) {
			horizontal_fit = (fit_xratio > fit_yratio);
			sensor_size = bcam->sensor_width;
		}
		else if(bcam->sensor_fit == BlenderCamera::HORIZONTAL) {
			horizontal_fit = true;
			sensor_size = bcam->sensor_width;
		}
		else { /* vertical */
			horizontal_fit = false;
			sensor_size = bcam->sensor_height;
		}

		if(horizontal_fit) {
			cam->sensorwidth = sensor_size;
			cam->sensorheight = sensor_size * fit_yratio / fit_xratio;
		}
		else {
			cam->sensorwidth = sensor_size * fit_xratio / fit_yratio;
			cam->sensorheight = sensor_size;
		}
	}

	/* clipping distances */
	cam->nearclip = bcam->nearclip;
	cam->farclip = bcam->farclip;

	/* type */
	cam->type = bcam->type;

	/* panorama */
	cam->panorama_type = bcam->panorama_type;
	cam->fisheye_fov = bcam->fisheye_fov;
	cam->fisheye_lens = bcam->fisheye_lens;
	cam->latitude_min = bcam->latitude_min;
	cam->latitude_max = bcam->latitude_max;

	cam->longitude_min = bcam->longitude_min;
	cam->longitude_max = bcam->longitude_max;

	/* panorama stereo */
	cam->interocular_distance = bcam->interocular_distance;
	cam->convergence_distance = bcam->convergence_distance;
	cam->use_spherical_stereo = bcam->use_spherical_stereo;

	if(cam->use_spherical_stereo) {
		if(strcmp(viewname, "left") == 0)
			cam->stereo_eye = Camera::STEREO_LEFT;
		else if(strcmp(viewname, "right") == 0)
			cam->stereo_eye = Camera::STEREO_RIGHT;
		else
			cam->stereo_eye = Camera::STEREO_NONE;
	}

	cam->use_pole_merge = bcam->use_pole_merge;
	cam->pole_merge_angle_from = bcam->pole_merge_angle_from;
	cam->pole_merge_angle_to = bcam->pole_merge_angle_to;

	/* anamorphic lens bokeh */
	cam->aperture_ratio = bcam->aperture_ratio;

	/* perspective */
	cam->fov = 2.0f * atanf((0.5f * sensor_size) / bcam->lens / aspectratio);
	cam->focaldistance = bcam->focaldistance;
	cam->aperturesize = bcam->aperturesize;
	cam->blades = bcam->apertureblades;
	cam->bladesrotation = bcam->aperturerotation;

	/* transform */
	cam->matrix = blender_camera_matrix(bcam->matrix,
	                                    bcam->type,
	                                    bcam->panorama_type);
	cam->motion.pre = cam->matrix;
	cam->motion.post = cam->matrix;
	cam->use_motion = false;
	cam->use_perspective_motion = false;
	cam->shuttertime = bcam->shuttertime;
	cam->fov_pre = cam->fov;
	cam->fov_post = cam->fov;
	cam->motion_position = bcam->motion_position;

	cam->rolling_shutter_type = bcam->rolling_shutter_type;
	cam->rolling_shutter_duration = bcam->rolling_shutter_duration;

	cam->shutter_curve = bcam->shutter_curve;

	/* border */
	cam->border = bcam->border;
	cam->viewport_camera_border = bcam->viewport_camera_border;

	/* set update flag */
	if(cam->modified(prevcam))
		cam->tag_update();
}

/* Sync Render Camera */

void BlenderSync::sync_camera(BL::RenderSettings& b_render,
                              BL::Object& b_override,
                              int width, int height,
                              const char *viewname)
{
	BlenderCamera bcam;
	blender_camera_init(&bcam, b_render);

	/* pixel aspect */
	bcam.pixelaspect.x = b_render.pixel_aspect_x();
	bcam.pixelaspect.y = b_render.pixel_aspect_y();
	bcam.shuttertime = b_render.motion_blur_shutter();

	BL::CurveMapping b_shutter_curve(b_render.motion_blur_shutter_curve());
	curvemapping_to_array(b_shutter_curve, bcam.shutter_curve, RAMP_TABLE_SIZE);

	PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
	bcam.motion_position =
	        (Camera::MotionPosition)get_enum(cscene,
	                                         "motion_blur_position",
	                                         Camera::MOTION_NUM_POSITIONS,
	                                         Camera::MOTION_POSITION_CENTER);
	bcam.rolling_shutter_type =
		(Camera::RollingShutterType)get_enum(cscene,
		                                     "rolling_shutter_type",
		                                     Camera::ROLLING_SHUTTER_NUM_TYPES,
		                                     Camera::ROLLING_SHUTTER_NONE);
	bcam.rolling_shutter_duration = RNA_float_get(&cscene, "rolling_shutter_duration");

	/* border */
	if(b_render.use_border()) {
		bcam.border.left = b_render.border_min_x();
		bcam.border.right = b_render.border_max_x();
		bcam.border.bottom = b_render.border_min_y();
		bcam.border.top = b_render.border_max_y();
	}

	/* camera object */
	BL::Object b_ob = b_scene.camera();

	if(b_override)
		b_ob = b_override;

	if(b_ob) {
		BL::Array<float, 16> b_ob_matrix;
		blender_camera_from_object(&bcam, b_engine, b_ob);
		b_engine.camera_model_matrix(b_ob, bcam.use_spherical_stereo, b_ob_matrix);
		bcam.matrix = get_transform(b_ob_matrix);
	}

	/* sync */
	Camera *cam = scene->camera;
	blender_camera_sync(cam, &bcam, width, height, viewname);
}

void BlenderSync::sync_camera_motion(BL::RenderSettings& b_render,
                                     BL::Object& b_ob,
                                     int width, int height,
                                     float motion_time)
{
	if(!b_ob)
		return;

	Camera *cam = scene->camera;
	BL::Array<float, 16> b_ob_matrix;
	b_engine.camera_model_matrix(b_ob, cam->use_spherical_stereo, b_ob_matrix);
	Transform tfm = get_transform(b_ob_matrix);
	tfm = blender_camera_matrix(tfm, cam->type, cam->panorama_type);

	if(tfm != cam->matrix) {
		VLOG(1) << "Camera " << b_ob.name() << " motion detected.";
		if(motion_time == 0.0f) {
			/* When motion blur is not centered in frame, cam->matrix gets reset. */
			cam->matrix = tfm;
		}
		else if(motion_time == -1.0f) {
			cam->motion.pre = tfm;
			cam->use_motion = true;
		}
		else if(motion_time == 1.0f) {
			cam->motion.post = tfm;
			cam->use_motion = true;
		}
	}

	if(cam->type == CAMERA_PERSPECTIVE) {
		BlenderCamera bcam;
		float aspectratio, sensor_size;
		blender_camera_init(&bcam, b_render);

		/* TODO(sergey): Consider making it a part of blender_camera_init(). */
		bcam.pixelaspect.x = b_render.pixel_aspect_x();
		bcam.pixelaspect.y = b_render.pixel_aspect_y();

		blender_camera_from_object(&bcam, b_engine, b_ob);
		blender_camera_viewplane(&bcam,
		                         width, height,
		                         NULL,
		                         &aspectratio,
		                         &sensor_size);
		/* TODO(sergey): De-duplicate calculation with camera sync. */
		float fov = 2.0f * atanf((0.5f * sensor_size) / bcam.lens / aspectratio);
		if(fov != cam->fov) {
			VLOG(1) << "Camera " << b_ob.name() << " FOV change detected.";
			if(motion_time == 0.0f) {
				cam->fov = fov;
			}
			else if(motion_time == -1.0f) {
				cam->fov_pre = fov;
				cam->use_perspective_motion = true;
			}
			else if(motion_time == 1.0f) {
				cam->fov_post = fov;
				cam->use_perspective_motion = true;
			}
		}
	}
}

/* Sync 3D View Camera */

static void blender_camera_view_subset(BL::RenderEngine& b_engine,
                                       BL::RenderSettings& b_render,
                                       BL::Scene& b_scene,
                                       BL::Object& b_ob,
                                       BL::SpaceView3D& b_v3d,
                                       BL::RegionView3D& b_rv3d,
                                       int width, int height,
                                       BoundBox2D *view_box,
                                       BoundBox2D *cam_box);

static void blender_camera_from_view(BlenderCamera *bcam,
                                     BL::RenderEngine& b_engine,
                                     BL::Scene& b_scene,
                                     BL::SpaceView3D& b_v3d,
                                     BL::RegionView3D& b_rv3d,
                                     int width, int height,
                                     bool skip_panorama = false)
{
	/* 3d view parameters */
	bcam->nearclip = b_v3d.clip_start();
	bcam->farclip = b_v3d.clip_end();
	bcam->lens = b_v3d.lens();
	bcam->shuttertime = b_scene.render().motion_blur_shutter();

	BL::CurveMapping b_shutter_curve(b_scene.render().motion_blur_shutter_curve());
	curvemapping_to_array(b_shutter_curve, bcam->shutter_curve, RAMP_TABLE_SIZE);

	if(b_rv3d.view_perspective() == BL::RegionView3D::view_perspective_CAMERA) {
		/* camera view */
		BL::Object b_ob = (b_v3d.lock_camera_and_layers())? b_scene.camera(): b_v3d.camera();

		if(b_ob) {
			blender_camera_from_object(bcam, b_engine, b_ob, skip_panorama);

			if(!skip_panorama && bcam->type == CAMERA_PANORAMA) {
				/* in panorama camera view, we map viewplane to camera border */
				BoundBox2D view_box, cam_box;

				BL::RenderSettings b_render_settings(b_scene.render());
				blender_camera_view_subset(b_engine,
				                           b_render_settings,
				                           b_scene,
				                           b_ob,
				                           b_v3d,
				                           b_rv3d,
				                           width, height,
				                           &view_box,
				                           &cam_box);

				bcam->pano_viewplane = view_box.make_relative_to(cam_box);
			}
			else {
				/* magic zoom formula */
				bcam->zoom = (float)b_rv3d.view_camera_zoom();
				bcam->zoom = (1.41421f + bcam->zoom/50.0f);
				bcam->zoom *= bcam->zoom;
				bcam->zoom = 2.0f/bcam->zoom;

				/* offset */
				bcam->offset = get_float2(b_rv3d.view_camera_offset());
			}
		}
	}
	else if(b_rv3d.view_perspective() == BL::RegionView3D::view_perspective_ORTHO) {
		/* orthographic view */
		bcam->farclip *= 0.5f;
		bcam->nearclip = -bcam->farclip;

		float sensor_size;
		if(bcam->sensor_fit == BlenderCamera::VERTICAL)
			sensor_size = bcam->sensor_height;
		else
			sensor_size = bcam->sensor_width;

		bcam->type = CAMERA_ORTHOGRAPHIC;
		bcam->ortho_scale = b_rv3d.view_distance() * sensor_size / b_v3d.lens();
	}

	bcam->zoom *= 2.0f;

	/* 3d view transform */
	bcam->matrix = transform_inverse(get_transform(b_rv3d.view_matrix()));
}

static void blender_camera_view_subset(BL::RenderEngine& b_engine,
                                       BL::RenderSettings& b_render,
                                       BL::Scene& b_scene,
                                       BL::Object& b_ob,
                                       BL::SpaceView3D& b_v3d,
                                       BL::RegionView3D& b_rv3d,
                                       int width, int height,
                                       BoundBox2D *view_box,
                                       BoundBox2D *cam_box)
{
	BoundBox2D cam, view;
	float view_aspect, cam_aspect, sensor_size;

	/* get viewport viewplane */
	BlenderCamera view_bcam;
	blender_camera_init(&view_bcam, b_render);
	blender_camera_from_view(&view_bcam, b_engine, b_scene, b_v3d, b_rv3d, width, height, true);

	blender_camera_viewplane(&view_bcam, width, height,
		&view, &view_aspect, &sensor_size);

	/* get camera viewplane */
	BlenderCamera cam_bcam;
	blender_camera_init(&cam_bcam, b_render);
	blender_camera_from_object(&cam_bcam, b_engine, b_ob, true);

	blender_camera_viewplane(&cam_bcam, cam_bcam.full_width, cam_bcam.full_height,
		&cam, &cam_aspect, &sensor_size);
	
	/* return */
	*view_box = view * (1.0f/view_aspect);
	*cam_box = cam * (1.0f/cam_aspect);
}

static void blender_camera_border_subset(BL::RenderEngine& b_engine,
                                         BL::RenderSettings& b_render,
                                         BL::Scene& b_scene,
                                         BL::SpaceView3D& b_v3d,
                                         BL::RegionView3D& b_rv3d,
                                         BL::Object& b_ob,
                                         int width, int height,
                                         const BoundBox2D &border,
                                         BoundBox2D *result)
{
	/* Determine camera viewport subset. */
	BoundBox2D view_box, cam_box;
	blender_camera_view_subset(b_engine, b_render, b_scene, b_ob, b_v3d, b_rv3d, width, height,
	                           &view_box, &cam_box);

	/* Determine viewport subset matching given border. */
	cam_box = cam_box.make_relative_to(view_box);
	*result = cam_box.subset(border);
}

static void blender_camera_border(BlenderCamera *bcam,
                                  BL::RenderEngine& b_engine,
                                  BL::RenderSettings& b_render,
                                  BL::Scene& b_scene,
                                  BL::SpaceView3D& b_v3d,
                                  BL::RegionView3D& b_rv3d,
                                  int width, int height)
{
	bool is_camera_view;

	/* camera view? */
	is_camera_view = b_rv3d.view_perspective() == BL::RegionView3D::view_perspective_CAMERA;

	if(!is_camera_view) {
		/* for non-camera view check whether render border is enabled for viewport
		 * and if so use border from 3d viewport
		 * assume viewport has got correctly clamped border already
		 */
		if(b_v3d.use_render_border()) {
			bcam->border.left = b_v3d.render_border_min_x();
			bcam->border.right = b_v3d.render_border_max_x();
			bcam->border.bottom = b_v3d.render_border_min_y();
			bcam->border.top = b_v3d.render_border_max_y();
		}
		return;
	}

	BL::Object b_ob = (b_v3d.lock_camera_and_layers())? b_scene.camera(): b_v3d.camera();

	if(!b_ob)
		return;

	/* Determine camera border inside the viewport. */
	BoundBox2D full_border;
	blender_camera_border_subset(b_engine,
	                             b_render,
	                             b_scene,
	                             b_v3d,
	                             b_rv3d,
	                             b_ob,
	                             width, height,
	                             full_border,
	                             &bcam->viewport_camera_border);

	if(!b_render.use_border()) {
		return;
	}

	bcam->border.left = b_render.border_min_x();
	bcam->border.right = b_render.border_max_x();
	bcam->border.bottom = b_render.border_min_y();
	bcam->border.top = b_render.border_max_y();

	/* Determine viewport subset matching camera border. */
	blender_camera_border_subset(b_engine,
	                             b_render,
	                             b_scene,
	                             b_v3d,
	                             b_rv3d,
	                             b_ob,
	                             width, height,
	                             bcam->border,
	                             &bcam->border);
	bcam->border = bcam->border.clamp();
}

void BlenderSync::sync_view(BL::SpaceView3D& b_v3d,
                            BL::RegionView3D& b_rv3d,
                            int width, int height)
{
	BlenderCamera bcam;
	BL::RenderSettings b_render_settings(b_scene.render());
	blender_camera_init(&bcam, b_render_settings);
	blender_camera_from_view(&bcam,
	                         b_engine,
	                         b_scene,
	                         b_v3d,
	                         b_rv3d,
	                         width, height);
	blender_camera_border(&bcam,
	                      b_engine,
	                      b_render_settings,
	                      b_scene,
	                      b_v3d,
	                      b_rv3d,
	                      width, height);
	blender_camera_sync(scene->camera, &bcam, width, height, "");
}

BufferParams BlenderSync::get_buffer_params(BL::RenderSettings& b_render,
                                            BL::SpaceView3D& b_v3d,
                                            BL::RegionView3D& b_rv3d,
                                            Camera *cam,
                                            int width, int height)
{
	BufferParams params;
	bool use_border = false;

	params.full_width = width;
	params.full_height = height;

	if(b_v3d && b_rv3d && b_rv3d.view_perspective() != BL::RegionView3D::view_perspective_CAMERA)
		use_border = b_v3d.use_render_border();
	else
		use_border = b_render.use_border();

	if(use_border) {
		/* border render */
		/* the viewport may offset the border outside the view */
		BoundBox2D border = cam->border.clamp();
		params.full_x = (int)(border.left * (float)width);
		params.full_y = (int)(border.bottom * (float)height);
		params.width = (int)(border.right * (float)width) - params.full_x;
		params.height = (int)(border.top * (float)height) - params.full_y;

		/* survive in case border goes out of view or becomes too small */
		params.width = max(params.width, 1);
		params.height = max(params.height, 1);
	}
	else {
		params.width = width;
		params.height = height;
	}

	return params;
}

CCL_NAMESPACE_END

