/*
 * Polygon_RayTrace
 * Copylight (C) 2013 mocchi
 * mocchi_2003@yahoo.co.jp
 * License: Boost ver.1
 */

#include "opennurbs.h"
#include "ximage.h"
#include "ximapng.h"

template <typename ThreadCalculators, typename CommonInfo>
void RenderingTest(ThreadCalculators &tcs, CommonInfo &ci){
	ON_3dPoint target(0, 0, 25);
	int divides = 20;
	ON_Interval animation(0,divides);

	// 解像度
	int py = 400, px = 400;
	double hrange_y = 16, hrange_x = 16;

	double cone_cos = std::cos(30.0 * ON_DEGREES_TO_RADIANS);

	// レンダリング
	std::fprintf(stderr, "rendering\n");
	for (int l = 0; l <= divides; ++l){
		ON_ClassArray<ON_Polyline> traces;
		struct Camera{
			ON_3dPoint origin;
			ON_3dVector eye, vert, horz;
			ON_Plane pln;
		}camera;
		double t = animation.NormalizedParameterAt(l);
		camera.origin.x = std::cos(t * 2.0 * ON_PI) * 40;
		camera.origin.y = std::sin(t * 2.0 * ON_PI) * 40;
		camera.origin.z = std::abs(ON_Interval(40, -40).ParameterAt(t));
		camera.eye = target - camera.origin;
		camera.eye.Unitize();
		camera.horz = ON_CrossProduct(camera.eye, ON_3dVector(0,0,1));
		camera.vert = ON_CrossProduct(camera.horz, camera.eye);
		camera.horz.Unitize(), camera.vert.Unitize();
		camera.pln.CreateFromFrame(camera.origin, camera.horz, camera.vert);

		CxImage img;
		img.Create(px, py, 24);
		img.Clear();

		std::fprintf(stderr, " calculate floor\n");

		// 視線方向から光線追跡する
		// 床(z=0)に光線を当てる
		ON_Plane flr(ON_3dPoint(0,0,0), ON_3dVector(0,0,1));
		struct Thread : public pt::thread{
			Thread() : pt::thread(0){}
			int yofset, px, py;
			double hrange_x, hrange_y;
			MeshRayIntersection *mri;
			Camera *camera;
			CommonInfo *ci;
			CxImage *img;
			ON_Plane *flr;
			void execute(){
				RefractParams rp;
				int block_seed = 15;
				for (int iy = yofset; iy < py; iy += THREADS){
					std::printf("   y:%d\n", iy);
					for (int ix = 0; ix < px; ++ix){
						double u = (static_cast<double>(ix) * 2.0 / static_cast<double>(px) - 1) * hrange_x;
						double v = (static_cast<double>(iy) * 2.0 / static_cast<double>(py) - 1) * hrange_y;
						ON_3dRay ray, ray_o;
						ray.m_P = camera->pln.PointAt(u, v);
						ray.m_V = camera->eye;
//						ON_Polyline *trace = 0;
//						if (ix % 10 == 0 && iy % 10 == 0) trace = &traces.AppendNew();
						double flux_o;
						RayTrace(ray, 1.0, 555, *mri, rp, ci, false, block_seed, ray_o, flux_o, 0);
						double t;
						double sky = ON_DotProduct(ray_o.m_V, ON_3dVector(0,0,1));
						if (sky > 0){
							img->SetPixelColor(ix, iy, CxImage::RGBtoRGBQUAD(RGB(0,128+sky*127,255)));
							continue;
						}
						ON_Line lin(ray_o.m_P, ray_o.m_P + ray_o.m_V);
						ON_Intersect(lin, flr->plane_equation, &t);
						if (t < 0) continue;
						ON_3dPoint flr_pt = lin.PointAt(t);

						// 床は1辺 50mm の白黒の市松タイルとして乱反射させる
						int bw = (static_cast<int>(flr_pt.x / 50) + static_cast<int>(flr_pt.y / 50)) & 1;
						if (flr_pt.x * flr_pt.y < 0) bw ^= 1;
						uint8_t gry = (120 + bw * 60);
						if (gry > 255) gry = 255;
						img->SetPixelColor(ix, iy, CxImage::RGBtoRGBQUAD(RGB(gry,gry,gry)));
					}
				}
			}
		};
		ON_ClassArray<Thread> threads;
		for (int i = 0; i < THREADS; ++i){
			Thread &th = threads.AppendNew();
			th.mri = &tcs[i].mri;
			th.yofset = i;
			th.px = px, th.py = py;
			th.hrange_x = hrange_x, th.hrange_y = hrange_y;
			th.camera = &camera;
			th.ci = &ci;
			th.img = &img;
			th.flr = &flr;
			th.start();
		}
		for (int i = 0; i < THREADS; ++i){
			threads[i].waitfor();
		}

		std::fprintf(stderr, " calculate ray\n");
		// 反射・屈折した光線のカメラへの直射光を拾う
		COLORREF rgbs[2] = {
			RGB(255,255,255),
			RGB(255,255,0)
		};
		for (int k = 0; k < ci.raies_last.Count(); ++k){
			COLORREF rgb = rgbs[k % 2];
			ON_ClassArray<ON_3dRay> &ray_last = ci.raies_last[k];
			for (int j = 0; j < ray_last.Count(); ++j){	
				if (cone_cos > -ON_DotProduct(ray_last[j].m_V, camera.eye)) continue;

				double u, v;
				ON_3dPoint &lit_pt = ray_last[j].m_P;
				camera.pln.ClosestPointTo(lit_pt, &u, &v);
				if (std::abs(u) > hrange_x || std::abs(v) > hrange_y) continue;
				int ix = static_cast<int>(std::floor((u + hrange_x) * px / (hrange_x * 2)));
				if (ix < 0 || ix > px) continue;
				int iy = static_cast<int>(std::floor((v + hrange_y) * py / (hrange_y * 2)));
				if (iy < 0 || iy > py) continue;
				img.SetPixelColor(ix, iy, CxImage::RGBtoRGBQUAD(rgb));
			}
		}

		{
			CxMemFile mf;
			mf.Open();
			img.Encode(&mf, CXIMAGE_FORMAT_PNG);
			mf.Seek(0, SEEK_SET);
			ON_String fn; fn.Format("image_%d.png", l);
			FILE *fp = std::fopen(fn, "wb");
			std::fwrite(mf.GetBuffer(), mf.Size(), 1, fp);
			std::fclose(fp);
			mf.Close();
		}
	}
}
