Blob Blame Raw

#include <iostream>

#include "perspectivesandboxview.h"


PerspectiveSandBoxView::PerspectiveSandBoxView():
	persp_p0  (new View::Point( Vector2(-1, -1) )),
	persp_px  (new View::Point( Vector2( 1, -1) )),
	persp_py  (new View::Point( Vector2(-1,  1) )),
	persp_p1  (new View::Point( Vector2( 1,  1) )),
	bounds_p0 (new View::Point( Vector2(-2, -2) )),
	bounds_p1 (new View::Point( Vector2( 2,  2) ))
{
	transform.scale(50, 50);
	points.push_back(persp_p0);
	points.push_back(persp_px);
	points.push_back(persp_py);
	points.push_back(persp_p1);
	points.push_back(bounds_p0);
	points.push_back(bounds_p1);
}

void
PerspectiveSandBoxView::draw_grid(
	const Cairo::RefPtr<Cairo::Context> &context,
	const Matrix &matrix,
	const Color &color )
{
	const int count = 100;
	const int subcount = 10;

	const Real ps = get_pixel_size();

	for(int i = -count; i < count; ++i) {
		for(int j = -count; j < count; ++j) {
			Vector4 src(i/(Real)subcount, j/(Real)subcount, 0, 1);
			Vector4 dst = matrix*src;
			Real w = dst.w;
			if (w > real_precision) {
				Vector2 pos(dst.x/w, dst.y/w);
				
				Real ka = clamp(1/w, 0, 1);
				
				Real a = color.a*ka;
				Real r = 4*ps;
				if (i) r *= 0.5;
				if (j) r *= 0.5;
				if (i % 10) r *= 0.75;
				if (j % 10) r *= 0.75;
				context->set_source_rgba(color.r, color.g, color.b, a);
				context->arc(pos.x, pos.y, r, 0, 2.0*M_PI);
				context->fill();
			}
		}
	}
}

void
PerspectiveSandBoxView::draw_line(
	const Cairo::RefPtr<Cairo::Context> &context,
	const Pair2 &bounds,
	Real a,
	Real b,
	Real c,
	const Color &color )
{
	// equatation of line is a*x + b*y = c

	int count = 0;
	Vector2 points[4];
	
	if (fabs(a) > real_precision) {
		Real x0 = (c - bounds.p0.y*b)/a;
		if ( x0 + real_precision >= bounds.p0.x
		  && x0 - real_precision <= bounds.p1.x )
			points[count++] = Vector2(x0, bounds.p0.y);
		
		Real x1 = (c - bounds.p1.y*b)/a;
		if ( x1 + real_precision >= bounds.p0.x
		  && x1 - real_precision <= bounds.p1.x )
			points[count++] = Vector2(x1, bounds.p1.y);
	}

	if (fabs(b) > real_precision) {
		Real y0 = (c - bounds.p0.x*a)/b;
		if ( y0 + real_precision >= bounds.p0.y
		  && y0 - real_precision <= bounds.p1.y )
			points[count++] = Vector2(bounds.p0.x, y0);
		
		Real y1 = (c - bounds.p1.x*a)/b;
		if ( y1 + real_precision >= bounds.p0.y
		  && y1 - real_precision <= bounds.p1.y )
			points[count++] = Vector2(bounds.p1.x, y1);
	}

	if (count >= 2) {
		context->set_source_rgba(color.r, color.g, color.b, color.a);
		context->move_to(points[0].x, points[0].y);
		context->line_to(points[1].x, points[1].y);
		context->stroke();
	}
}

void
PerspectiveSandBoxView::draw_subdivisions(
	const Cairo::RefPtr<Cairo::Context> &context,
	const Matrix &matrix,
	const Pair2 &bounds,
	const Color &color )
{
	const Real ps = get_pixel_size();
	
	Vector4 a = matrix.row_x();
	Vector4 b = matrix.row_y();
	Vector4 c = matrix.row_w();

	// calc coefficient for equatation of "horizontal" line: A*x + B*y = C + D/w
	//                     equatation of line of horizon is: A*x + B*y = C
	Real A = a.y*b.w - a.w*b.y;
	Real B = a.w*b.x - a.x*b.w;
	Real C = a.y*b.x - a.x*b.y;
	Real D = a.w*(b.x*c.y - b.y*c.x) + b.w*(a.y*c.x - a.x*c.y) - C*c.w;
	
	if (D < 0) {
		A = -A;
		B = -B;
		C = -C;
		D = -D;
	}

	Real hd = ps*sqrt(A*A + B*B);
	if (hd <= real_precision)
		return; // orthogonal projection - no prespective - no subdiviosions
	hd = D/hd;
	Real horizonw  = hd;
	Real horizonw2 = hd/4;

	Real maxw = -INFINITY, minw = INFINITY;
	Vector2 corners[] = {
		Vector2(bounds.p0.x, bounds.p0.y),
		Vector2(bounds.p1.x, bounds.p0.y),
		Vector2(bounds.p1.x, bounds.p1.y),
		Vector2(bounds.p0.x, bounds.p1.y) };
	for(int i = 0; i < 4; ++i) {
		Real w = A*corners[i].x + B*corners[i].y - C;
		if (fabs(w) > real_precision) {
			w = D/w;
			if (w < 0 || w > horizonw) w = horizonw;
			if (minw > w) minw = w;
			if (maxw < w) maxw = w;
		}
	}

	if (minw >= maxw - real_precision)
		return; // all bounds too thin
	Real maxw2 = maxw < horizonw2 ? maxw : horizonw2;

	// draw limits
	Color limits_color = color;
	limits_color.a *= 0.5;
	draw_line(context, get_bounds(), A, B, C + D/minw, limits_color);
	draw_line(context, get_bounds(), A, B, C + D/maxw, limits_color);

	// split
	int minlog = (int)ceil (log2(minw ) + real_precision);
	int maxlog = (int)floor(log2(maxw2) - real_precision);
	for(int i = minlog; i <= maxlog; ++i)
		draw_line(context, bounds, A, B, C + D/exp2(i), color);
}

void
PerspectiveSandBoxView::on_draw_view(const Cairo::RefPtr<Cairo::Context> &context) {
	context->save();

	const Real ps = get_pixel_size();
	context->set_line_width(ps);

	Vector2 p0 = persp_p0->position;
	Vector2 px = persp_px->position;
	Vector2 py = persp_py->position;
	Vector2 p1 = persp_p1->position;
	
	Pair2 bounds(bounds_p0->position, bounds_p1->position);
	
	
	// perspective matrix
	Matrix matrix;
	{
		Vector2 A = px - p1;
		Vector2 B = py - p1;
		Vector2 C = p0 + p1 - px - py;
		
		Real cw = A.y*B.x - A.x*B.y;
		Real aw = B.x*C.y - B.y*C.x;
		Real bw = A.y*C.x - A.x*C.y;
		
		if (cw < 0) {
			aw = -aw;
			bw = -bw;
			cw = -cw;
		}
		
		Vector2 c = p0*cw;
		Vector2 a = px*(cw + aw) - c;
		Vector2 b = py*(cw + bw) - c;

		matrix.row_x() = Vector4(a, 0, aw);
		matrix.row_y() = Vector4(b, 0, bw);
		matrix.row_w() = Vector4(c, 0, cw);
	}

	draw_grid(context, matrix, Color(0, 1, 0, 1));
	draw_subdivisions(context, matrix, bounds, Color(0, 0, 1, 1));

	// draw frames
	context->set_source_rgba(0, 0, 1, 0.75);
	context->move_to(p0.x, p0.y);
	context->line_to(px.x, px.y);
	context->line_to(p1.x, p1.y);
	context->line_to(py.x, py.y);
	context->close_path();
	context->stroke();

	context->move_to(bounds.p0.x, bounds.p0.y);
	context->line_to(bounds.p1.x, bounds.p0.y);
	context->line_to(bounds.p1.x, bounds.p1.y);
	context->line_to(bounds.p0.x, bounds.p1.y);
	context->close_path();
	context->stroke();
	
	context->restore();
}