#include "sys_interaction.h"
#include "system.h"
#include "sys_vr.h"
#include "sys_vr_passthru.h"
#include "sys_render.h"
#include "sys_tilestore.h"
#include "sys_undo.h"

enum side {left,right,top,bottom};
enum side opposite[4] = { right, left, bottom, top};
#define HOR 1
#define VER 0
#define GROW_FACTOR (1.004f)
#define GROW_MIN (0.05f)
#define GROW_MAX (8.0f / UV_PER_METER)

static uint32_t tracked_id = 0xfffffffful;
static unsigned toolidx = 0;
static yrQuad* selected = NULL;
static enum side seledge;
static enum side posdir;
static yrQuad preview;
static int wasdown;
static int resizing;

static struct {
	vec4i base;
	vec4f e0d;
	vec4f e1d;
	vec4i noff;
	float uvbase[2];
	float dim[2];
	float hitmove[2];
	float maxdim;
} state;

static yrTexture tex_fill = NULL;
static yrTexture tex_edgeh = NULL;
static yrTexture tex_edgev = NULL;
static yrTexture tpadbut_arrows = NULL;
static yrTexture tpadbut_arrows_h = NULL;
static yrTexture tpadbut_arrows_v = NULL;
static yrTexture tpadbut_arrows_glow[5] = {NULL};

#define DIST_MIN 0.0f
#define DIST_MAX 64.0f

static void resize_shutdown(void);
static void resize_settrackedid(uint32_t,unsigned);
static void resize_tick(void);
static int  resize_canfreeze(void);
static void resize_forceidle(void);
static int	resize_getpokepos(vec4f* pos, vec4f* dir, float* len) {return 0;}

int tool_resize_init(func_tool_shutdown* st_sd,
					 func_tool_settrackedid* ft_sti,
					 func_tool_tick* ft_t,
					 func_tool_canfreeze* ft_cf,
					 func_tool_forceidle* ft_fi,
					 func_tool_getpokepos* ft_gpp,
					 func_tool_renderwindow* ft_rw,
					 func_tool_poke* ft_p)
{
	*st_sd	= resize_shutdown;
	*ft_sti	= resize_settrackedid;
	*ft_t	= resize_tick;
	*ft_cf	= resize_canfreeze;
	*ft_fi	= resize_forceidle;
	*ft_gpp = resize_getpokepos;
	*ft_rw	= nullfunc;
	*ft_p	= nullfunc;

	tracked_id = 0xfffffffful;
	toolidx = 0;
	selected = NULL;
	seledge = top;
	wasdown = 0;
	resizing = 0;
	tex_fill = yrTexture_loadfile("data/vr/empty.png"); if(!tex_fill) return -1;
	tex_edgeh = yrTexture_loadfile("data/vr/edgeh.png"); if(!tex_edgeh) return -1;
	tex_edgev = yrTexture_loadfile("data/vr/edgev.png"); if(!tex_edgev) return -1;
	tpadbut_arrows			= yrTexture_loadfile("data/vr/tpadbut_arrows.png");			if(!tpadbut_arrows) return -1;
	tpadbut_arrows_h		= yrTexture_loadfile("data/vr/tpadbut_arrows_h.png");		if(!tpadbut_arrows_h) return -1;
	tpadbut_arrows_v		= yrTexture_loadfile("data/vr/tpadbut_arrows_v.png");		if(!tpadbut_arrows_v) return -1;
	tpadbut_arrows_glow[0]	= yrTexture_loadfile("data/vr/tpadbut_arrows_center.png");	if(!tpadbut_arrows_glow[0]) return -1;
	tpadbut_arrows_glow[1]	= yrTexture_loadfile("data/vr/tpadbut_arrows_right.png");	if(!tpadbut_arrows_glow[1]) return -1;
	tpadbut_arrows_glow[2]	= yrTexture_loadfile("data/vr/tpadbut_arrows_up.png");		if(!tpadbut_arrows_glow[2]) return -1;
	tpadbut_arrows_glow[3]	= yrTexture_loadfile("data/vr/tpadbut_arrows_left.png");	if(!tpadbut_arrows_glow[3]) return -1;
	tpadbut_arrows_glow[4]	= yrTexture_loadfile("data/vr/tpadbut_arrows_down.png");	if(!tpadbut_arrows_glow[4]) return -1;
	return 0;
}

static void resize_shutdown(void)
{
	yrTexture_unload(tex_fill);
	yrTexture_unload(tex_edgeh);
	yrTexture_unload(tex_edgev);
	tex_fill = NULL;
	tex_edgeh = NULL;
	tex_edgev = NULL;
	yrTexture_unload(tpadbut_arrows_glow[0]);
	yrTexture_unload(tpadbut_arrows_glow[1]);
	yrTexture_unload(tpadbut_arrows_glow[2]);
	yrTexture_unload(tpadbut_arrows_glow[3]);
	yrTexture_unload(tpadbut_arrows);
	yrTexture_unload(tpadbut_arrows_h);
	yrTexture_unload(tpadbut_arrows_v);
	tpadbut_arrows_glow[0] = NULL;
	tpadbut_arrows_glow[1] = NULL;
	tpadbut_arrows_glow[2] = NULL;
	tpadbut_arrows_glow[3] = NULL;
	tpadbut_arrows_glow[4] = NULL;
	tpadbut_arrows = NULL;
	tpadbut_arrows_h = NULL;
	tpadbut_arrows_v = NULL;
}

static void resize_settrackedid(uint32_t tr_id, unsigned tool_idx)
{
	tracked_id = tr_id;
	toolidx = tool_idx;
}

static int resize_canfreeze(void)
{
	return selected == NULL;
}

static void	resize_forceidle(void)
{
	if(selected) {
		yrQuadStore_editlock(NULL);
		selected = NULL;
	}
}

static void pick_edge(const tracehit h, const mat4f mat, enum side* edge, enum side* posdir);
static void resize_do(enum side edge, enum side posdir, enum side pushdir);
static void resize_select(yrQuad* q, vec4f mat_c2);
static void resize_init(void);
static void resize_finalize(void);

static tracehit trace_singlequad(yrQuad* q, vec4i rayorigin, vec4f dir);
static void resize_tick(void)
{
	VRControllerState_t cbuttons = {0};
	ovrSystem_GetControllerState(tracked_id, &cbuttons);
	mat4f mat = yrVR_world_from_tracked(tracked_id);
	vec4i off = yrVR_get_coord_offset();

	float touchx = cbuttons.rAxis[EVRButtonId_k_EButton_SteamVR_Touchpad - EVRButtonId_k_EButton_Axis0].x;
	float touchy = cbuttons.rAxis[EVRButtonId_k_EButton_SteamVR_Touchpad - EVRButtonId_k_EButton_Axis0].y;
	int touchd = 0!=(cbuttons.ulButtonPressed & (1ull << EVRButtonId_k_EButton_SteamVR_Touchpad));
	int toucht = (touchx != 0.0f && touchy != 0.0f);

	int draw_preview = 0;
	//nothing selected, highlight edge on hover and select if pushdown
	if(!selected) {
		tracehit h = {0};
		if(toucht) {
			vec4i o = vec4i_add(off, vec4i_from_vec4f(vec4f_mul(YR_ACCURACY, mat.col[3])));
			h = yrInteraction_trace(o, vec4f_neg(mat.col[2]), DIST_MIN, DIST_MAX);
			vec4f n = h.q ? h.q->normal : mat.col[2];
			yrRender_beam(toolidx, mat.col[3], vec4f_neg(mat.col[2]), n, h.dist, 0xFF808040);
		}
		if(h.q) {
			preview = *h.q;
			state.dim[0] = preview.width;
			state.dim[1] = preview.height;
			draw_preview = 1;
			pick_edge(h, mat, &seledge, &posdir);
			if(touchd && !wasdown && !yrQuadStore_editlocked()) {
			   resize_select(h.q, mat.col[2]);
			}
		} else {
			draw_preview = 0;
		}
		//draw buttons
		yrRenderTrackedOverlay_clear(toolidx);
		if(touchd || toucht) {
			int glowidx;
			if((touchx*touchx + touchy*touchy) < 0.25f)	glowidx = 0;
			else if(fabsf(touchx) > fabsf(touchy))		glowidx = (touchx > 0.0f) ? 1:3;
			else										glowidx = (touchy > 0.0f) ? 2:4;
			yrRenderTrackedOverlay_draw(toolidx, tpadbut_arrows_glow[glowidx], -1.0f, -1.0f, 1.0f, 1.0f, touchd ? 0xFFFF8010 : 0x80FF8010);
		}
		yrRenderTrackedOverlay_draw(toolidx, tpadbut_arrows, -1.0f, -1.0f, 1.0f, 1.0f, 0xFFFFFFFF);
		yrRenderTrackedOverlay_render(toolidx);
	}
	//quad selected, resize on pushdown, finalize on center pushdown, highlight active edge
	else {
		draw_preview = 1;
		//beam and which button is pushed?
		enum side pushdir =
			(fabsf(touchx) > fabsf(touchy))
				? ((touchx > 0.0f)
					? right
					: left)
				: ((touchy > 0.0f)
					? top
					: bottom);
		vec4i o = vec4i_add(off, vec4i_from_vec4f(vec4f_mul(YR_ACCURACY, mat.col[3])));
		tracehit h = trace_singlequad(selected, o, vec4f_neg(mat.col[2]));
		vec4f n = h.q->normal;
		yrRender_beam(toolidx, mat.col[3], vec4f_neg(mat.col[2]), n, h.dist, 0xFFFFFF80);
		//in progress of resizing, don't change edge now
		if(touchd && resizing) {
			resize_do(seledge, posdir, pushdir);
			//draw buttons
			yrRenderTrackedOverlay_clear(toolidx);
			int glowidx;
			yrTexture buts;
			if(posdir == left || posdir == right) {
				buts = tpadbut_arrows_h;
				glowidx = (touchx > 0.0f) ? 1:3;
			} else {
				buts = tpadbut_arrows_v;
				glowidx = (touchy > 0.0f) ? 2:4;
			}
			yrRenderTrackedOverlay_draw(toolidx, tpadbut_arrows_glow[glowidx], -1.0f, -1.0f, 1.0f, 1.0f, 0xFFFF8010);
			yrRenderTrackedOverlay_draw(toolidx, buts, -1.0f, -1.0f, 1.0f, 1.0f, 0xFFFFFFFF);
			yrRenderTrackedOverlay_render(toolidx);
		}
		//pick an edge
		else {
			resizing = 0;
			//draw buttons
			yrRenderTrackedOverlay_clear(toolidx);
			if(touchd || toucht) {
				int glowidx;
				if((touchx*touchx + touchy*touchy) < 0.25f)	glowidx = 0;
				else if(fabsf(touchx) > fabsf(touchy))		glowidx = (touchx > 0.0f) ? 1:3;
				else										glowidx = (touchy > 0.0f) ? 2:4;
				yrRenderTrackedOverlay_draw(toolidx, tpadbut_arrows_glow[glowidx], -1.0f, -1.0f, 1.0f, 1.0f, touchd ? 0xFFFF8010 : 0x80FF8010);
			}
			yrRenderTrackedOverlay_draw(toolidx, tpadbut_arrows, -1.0f, -1.0f, 1.0f, 1.0f, 0xFFFFFFFF);
			yrRenderTrackedOverlay_render(toolidx);
			//adjust hitcoords and pick edge
			h.hitcoord[0] = (h.hitcoord[0] * selected->width  - state.hitmove[0]) / state.dim[0];
			h.hitcoord[1] = (h.hitcoord[1] * selected->height - state.hitmove[1]) / state.dim[1];
			pick_edge(h, mat, &seledge, &posdir);
			//starting a press
			if(touchd) {
				//center press, finalize
				if(!wasdown && touchx*touchx + touchy*touchy < 0.25f) {
					resize_finalize();
					selected = NULL;
					yrQuadStore_editlock(NULL);
				//side press, get resizing
				} else {
					resize_init();
					resizing = 1;
				}
			}
		}
	}
	wasdown = touchd;
	
	//draw preview quad with highlit edge
	if(draw_preview) {
		//clear overlay
		yrRenderOverlay_clear(OVERLAY_EDIT);
		yrRenderOverlay_draw(OVERLAY_EDIT, tex_fill, 0.0f, 0.0f, 8.0f, 8.0f, 0x80FF8040);
		//draw tile grid
		float offh = -(preview.uv_draw[0] - floorf(preview.uv_draw[0]));
		float offv = -(preview.uv_draw[1] - floorf(preview.uv_draw[1]));
		for(float g = offh; g <= state.dim[0] * UV_PER_METER; g += 1.0f)
			yrRenderOverlay_draw(OVERLAY_EDIT, tex_fill, g - 0.01f, 0.0f, g + 0.01f, 8.0f, 0x80000000);
		for(float g = offv; g <= state.dim[1] * UV_PER_METER; g += 1.0f)
			yrRenderOverlay_draw(OVERLAY_EDIT, tex_fill, 0.0f, g - 0.01f, 8.0f, g + 0.01f, 0x80000000);
		//draw edge
		float hhi = state.dim[0] * UV_PER_METER;
		float vhi = state.dim[1] * UV_PER_METER;
		float hce = hhi/2;
		float vce = vhi/2;
		switch(seledge) {
			case left:		yrRenderOverlay_draw(OVERLAY_EDIT, tex_edgeh, 0.0f, 0.0f, hce, vhi, 0x804080FF); break;
			case right:		yrRenderOverlay_draw(OVERLAY_EDIT, tex_edgeh, hhi, 0.0f, hce, vhi, 0x804080FF); break;
			case top:		yrRenderOverlay_draw(OVERLAY_EDIT, tex_edgev, 0.0f, vhi, hhi, vce, 0x804080FF); break;
			case bottom:	yrRenderOverlay_draw(OVERLAY_EDIT, tex_edgev, 0.0f, 0.0f, hhi, vce, 0x804080FF); break;
		}
		preview.width = state.dim[0];
		preview.height = state.dim[1];
		float remember[2] = {preview.uv_draw[0],preview.uv_draw[1]}; //hacky but works
		preview.uv_draw[0] = 0.0f;
		preview.uv_draw[1] = 0.0f;
		yrRenderOverlay_render(OVERLAY_EDIT, &preview , 0);
		preview.uv_draw[0] = remember[0];
		preview.uv_draw[1] = remember[1];
	}
}


static void pick_edge(const tracehit h, const mat4f mat, enum side* edge, enum side* posdir)
{
	YR_ASSERT(h.q);
	//calc edge
	*edge = (fabsf(h.hitcoord[0] - 0.5f) > fabsf(h.hitcoord[1] - 0.5f))
		? ((h.hitcoord[0] > 0.5f)
		   ? right
		   : left)
		: ((h.hitcoord[1] > 0.5f)
		   ? top
		   : bottom);

	//get edges and adjust for orientation
	*posdir = *edge;
	vec4f e0 = vec3f_normalized(vec4f_from_vec4i(vec4i_sub(h.q->v[1],h.q->v[0])));
	vec4f e1 = vec3f_normalized(vec4f_from_vec4i(vec4i_sub(h.q->v[2],h.q->v[0])));
	if(fabsf(e1.z) < fabsf(e0.z)) {
		vec4f swap = e0;
		e0 = e1;
		e1 = swap;
		switch(*posdir) {
			case left: *posdir = bottom; break;
			case right: *posdir = top; break;
			case top: *posdir = right; break;
			case bottom: *posdir = left; break;
		}
	}
	if(e1.z < 0.0f) {
		switch(*posdir) {
			case left: break;
			case right: break;
			case top: *posdir = bottom; break;
			case bottom: *posdir = top; break;
		}
	}
	if(vec3f_dot(mat.col[0], e0) < 0.0f) {
		switch(*posdir) {
			case left: *posdir = right; break;
			case right: *posdir = left; break;
			case top: break;
			case bottom: break;
		}
	}
}

static void resize_select(yrQuad* q, vec4f mat_c2)
{
	yrQuadStore_editlock(q); //select quad
	yrTileStore_important(q->ext->tile_max_idx, (TileID*) q->ext->tiles);
	selected = q;
	state.dim[0] = q->width;
	state.dim[1] = q->height;
	state.e0d = vec3f_normalized(vec4f_from_vec4i(vec4i_sub(q->v[1],q->v[0])));
	state.e1d = vec3f_normalized(vec4f_from_vec4i(vec4i_sub(q->v[2],q->v[0])));
	state.hitmove[0] = 0.0f;
	state.hitmove[1] = 0.0f;
	state.noff = vec4i_from_vec4f((vec3f_dot(mat_c2, q->normal) > 0.0f) ? q->normal : vec4f_neg(q->normal));
}

static void resize_init(void)
{
	YR_ASSERT(selected);
	switch(seledge) {
		case right:
			state.base = preview.v[0];
			state.uvbase[0] = preview.uv_draw[0];
			state.uvbase[1] = preview.uv_draw[1];
			state.maxdim = GROW_MAX - (state.uvbase[0] - floorf(state.uvbase[0]))/UV_PER_METER;
			break;
		case top:
			state.base = preview.v[0];
			state.uvbase[0] = preview.uv_draw[0];
			state.uvbase[1] = preview.uv_draw[1];
			state.maxdim = GROW_MAX - (state.uvbase[1] - floorf(state.uvbase[1]))/UV_PER_METER;
			break;
		case left:
			state.base = preview.v[1];
			state.uvbase[0] = preview.uv_draw[0] + state.dim[0] * UV_PER_METER;
			state.uvbase[1] = preview.uv_draw[1];
			state.maxdim = GROW_MAX + (state.uvbase[0] - ceilf(state.uvbase[0]))/UV_PER_METER;
			break;
		case bottom:
			state.base = preview.v[2];
			state.uvbase[0] = preview.uv_draw[0];
			state.uvbase[1] = preview.uv_draw[1] + state.dim[1] * UV_PER_METER;
			state.maxdim = GROW_MAX + (state.uvbase[1] - ceilf(state.uvbase[1]))/UV_PER_METER;
			break;
	}
}

static void resize_grow_left(void);
static void resize_grow_right(void);
static void resize_grow_top(void);
static void resize_grow_bottom(void);
static void resize_shrink_left(void);
static void resize_shrink_right(void);
static void resize_shrink_top(void);
static void resize_shrink_bottom(void);

static void resize_do(enum side edge, enum side posdir, enum side pushdir)
{
	if(pushdir == posdir) {
		switch(edge) {
			case left:		resize_grow_left(); break;
			case right:		resize_grow_right(); break;
			case top:		resize_grow_top(); break;
			case bottom:	resize_grow_bottom(); break;
		}
	}
	else if(pushdir == opposite[posdir]) {
		switch(edge) {
			case left:		resize_shrink_left(); break;
			case right:		resize_shrink_right(); break;
			case top:		resize_shrink_top(); break;
			case bottom:	resize_shrink_bottom(); break;
		}
	}
}

static void resize_grow_right(void)
{
	state.dim[0] *= GROW_FACTOR;
	if(state.dim[0] > state.maxdim) state.dim[0] = state.maxdim;

	preview.v[0] = vec4i_add(state.noff, state.base);
	preview.v[1] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[2] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.uv_draw[0] = state.uvbase[0];
	preview.uv_draw[1] = state.uvbase[1];
}

static void resize_shrink_right(void)
{
	state.dim[0] /= GROW_FACTOR;
	if(state.dim[0] < GROW_MIN) state.dim[0] = GROW_MIN;

	preview.v[0] = vec4i_add(state.noff, state.base);
	preview.v[1] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[2] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.uv_draw[0] = state.uvbase[0];
	preview.uv_draw[1] = state.uvbase[1];
}

static void resize_grow_top(void)
{
	state.dim[1] *= GROW_FACTOR;
	if(state.dim[1] > state.maxdim) state.dim[1] = state.maxdim;

	preview.v[0] = vec4i_add(state.noff, state.base);
	preview.v[1] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[2] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.uv_draw[0] = state.uvbase[0];
	preview.uv_draw[1] = state.uvbase[1];
}

static void resize_shrink_top(void)
{
	state.dim[1] /= GROW_FACTOR;
	if(state.dim[1] < GROW_MIN) state.dim[1] = GROW_MIN;

	preview.v[0] = vec4i_add(state.noff, state.base);
	preview.v[1] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[2] = vec4i_add(state.noff, vec4i_add(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.uv_draw[0] = state.uvbase[0];
	preview.uv_draw[1] = state.uvbase[1];
}

static void resize_grow_left(void)
{
	float newdim = state.dim[0] * GROW_FACTOR;
	if(newdim > state.maxdim) newdim = state.maxdim;
	state.hitmove[0] -= (newdim - state.dim[0]);
	state.dim[0] = newdim;

	preview.v[0] = vec4i_add(state.noff, vec4i_sub(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[1] = vec4i_add(state.noff, state.base);
	preview.v[2] = vec4i_add(state.noff, vec4i_add(preview.v[0], vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.uv_draw[0] = state.uvbase[0] - state.dim[0] * UV_PER_METER;
	preview.uv_draw[1] = state.uvbase[1];
}

static void resize_shrink_left(void)
{
	float newdim = state.dim[0] / GROW_FACTOR;
	if(newdim < GROW_MIN) newdim = GROW_MIN;
	state.hitmove[0] -= (newdim - state.dim[0]);
	state.dim[0] = newdim;
	
	preview.v[0] = vec4i_add(state.noff, vec4i_sub(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[1] = vec4i_add(state.noff, state.base);
	preview.v[2] = vec4i_add(state.noff, vec4i_add(preview.v[0], vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.uv_draw[0] = state.uvbase[0] - state.dim[0] * UV_PER_METER;
	preview.uv_draw[1] = state.uvbase[1];
}

static void resize_grow_bottom(void)
{
	float newdim = state.dim[1] * GROW_FACTOR;
	if(newdim > state.maxdim) newdim = state.maxdim;
	state.hitmove[1] -= (newdim - state.dim[1]);
	state.dim[1] = newdim;

	preview.v[0] = vec4i_add(state.noff, vec4i_sub(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.v[1] = vec4i_add(state.noff, vec4i_add(preview.v[0], vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[2] = vec4i_add(state.noff, state.base);
	preview.uv_draw[0] = state.uvbase[0];
	preview.uv_draw[1] = state.uvbase[1] - state.dim[1] * UV_PER_METER;
}

static void resize_shrink_bottom(void)
{
	float newdim = state.dim[1] / GROW_FACTOR;
	if(newdim < GROW_MIN) newdim = GROW_MIN;
	state.hitmove[1] -= (newdim - state.dim[1]);
	state.dim[1] = newdim;

	preview.v[0] = vec4i_add(state.noff, vec4i_sub(state.base, vec4i_from_vec4f(vec4f_mul(state.dim[1] * YR_ACCURACY, state.e1d))));
	preview.v[1] = vec4i_add(state.noff, vec4i_add(preview.v[0], vec4i_from_vec4f(vec4f_mul(state.dim[0] * YR_ACCURACY, state.e0d))));
	preview.v[2] = vec4i_add(state.noff, state.base);
	preview.uv_draw[0] = state.uvbase[0];
	preview.uv_draw[1] = state.uvbase[1] - state.dim[1] * UV_PER_METER;
}

static uint32_t tile_by_coord(int row, int col, yrQuad* q)
{
	if(row < 0) return 0xFFFFFFFFul;
	if(col < 0) return 0xFFFFFFFFul;	
	if(col >= (int) q->tile_width) return 0xFFFFFFFFul;
	if(row * q->tile_width >= (int) q->ext->tile_max_idx) return 0xFFFFFFFFul;

	return q->ext->tiles[row * q->tile_width + col];
}

static void resize_finalize(void)
{
	YR_ASSERT(selected);
	//remember old state for undo system
	yrQuad quad_restore = *selected;
	uint32_t tile_restore[64];
	memcpy(tile_restore, selected->ext->tiles, 64 * sizeof(uint32_t));
	//calc offset xy
	float lo_u = preview.uv_draw[0];
	float lo_v = preview.uv_draw[1];
	float hi_u = lo_u + state.dim[0] * UV_PER_METER;
	float hi_v = lo_v + state.dim[1] * UV_PER_METER;
	int shiftc = (int)floorf(lo_u);
	int shiftr = (int)floorf(lo_v);
	int cols = (int)(ceilf(hi_u) - floorf(lo_u));
	int rows = (int)(ceilf(hi_v) - floorf(lo_v));
	if(cols > 8) cols = 8;
	if(rows > 8) rows = 8;
	//remap tiles
	uint32_t tiles[64];
	memset(tiles, -1, sizeof(tiles));
	for(int r = 0; r < rows; ++r)
	for(int c = 0; c < cols; ++c)
	{
		tiles[r*cols + c] = tile_by_coord(r + shiftr, c + shiftc, selected);
	}
	//adjust background coordinates
	if(!(selected->flags & QUAD_BG_PIN))
	{
		float bg_per_meter[2];
		bg_per_meter[0] = (selected->uv_back[1][0] - selected->uv_back[0][0])/selected->width;
		bg_per_meter[1] = (selected->uv_back[1][1] - selected->uv_back[0][1])/selected->height;
		float bgdiff[2];
		bgdiff[0] = bg_per_meter[0] * (preview.uv_draw[0] - selected->uv_draw[0]) / UV_PER_METER;
		bgdiff[1] = bg_per_meter[1] * (preview.uv_draw[1] - selected->uv_draw[1]) / UV_PER_METER;
		selected->uv_back[0][0] = selected->uv_back[0][0] + bgdiff[0];
		selected->uv_back[0][1] = selected->uv_back[0][1] + bgdiff[1];
		selected->uv_back[1][0] = selected->uv_back[0][0] + state.dim[0] * bg_per_meter[0];
		selected->uv_back[1][1] = selected->uv_back[0][1] + state.dim[1] * bg_per_meter[1];
		while(selected->uv_back[0][0] < -1.0f) {
			selected->uv_back[0][0] += 1.0f;
			selected->uv_back[1][0] += 1.0f;
		}
		while(selected->uv_back[0][1] < -1.0f) {
			selected->uv_back[0][1] += 1.0f;
			selected->uv_back[1][1] += 1.0f;
		}
		while(selected->uv_back[0][0] > 1.0f) {
			selected->uv_back[0][0] -= 1.0f;
			selected->uv_back[1][0] -= 1.0f;
		}
		while(selected->uv_back[0][1] > 1.0f) {
			selected->uv_back[0][1] -= 1.0f;
			selected->uv_back[1][1] -= 1.0f;
		}
	}
	//resize quad
	selected->v[0] = vec4i_sub(preview.v[0], state.noff);
	selected->v[1] = vec4i_sub(preview.v[1], state.noff);
	selected->v[2] = vec4i_sub(preview.v[2], state.noff);
	selected->uv_draw[0] = lo_u - floorf(lo_u);
	selected->uv_draw[1] = lo_v - floorf(lo_v);
	memcpy(selected->ext->tiles, tiles, 64 * sizeof(uint32_t));	
	yrQuadStore_update(selected);
	selected->flags |= QUAD_CLEARCACHE;
	//wipe out of view tiles
	uint64_t edgemask = 0ull;
	for(int c = 0; c < cols; ++c) {edgemask |= 1; edgemask <<= 1; }
	for(int r = 1; r < rows -1 ; ++r) {edgemask |= 1; edgemask <<= (cols - 1); edgemask |= 1; edgemask <<= 1; }
	for(int c = 0; c < cols; ++c) {edgemask |= 1; edgemask <<= 1; }
	yrRenderOverlay_clear(OVERLAY_EDIT);
	yrRenderOverlay_draw(OVERLAY_EDIT, tex_fill, 0.0f, 0.0f, selected->uv_draw[0], 8.0f, 0xFF000000);
	yrRenderOverlay_draw(OVERLAY_EDIT, tex_fill, 0.0f, 0.0f, 8.0f, selected->uv_draw[1], 0xFF000000);
	yrRenderOverlay_draw(OVERLAY_EDIT, tex_fill, selected->uv_draw[0] + selected->width * UV_PER_METER, 0.0f, 8.0f, 8.0f, 0xFF000000);
	yrRenderOverlay_draw(OVERLAY_EDIT, tex_fill, 0.0f, selected->uv_draw[1] +selected->height * UV_PER_METER, 8.0f, 8.0f, 0xFF000000);
	yrRenderOverlay_apply(OVERLAY_EDIT, selected, edgemask, 1);
	yrRenderOverlay_clear(OVERLAY_EDIT);
	//register in undo system
	yrUndo_append(utBoth, &quad_restore, selected, tile_restore, selected->ext->tiles);
}

static tracehit trace_singlequad(yrQuad* q, vec4i rayorigin, vec4f dir)
{
	//transform to quad space
	vec4f foo = vec4f_mul(1.0f / YR_ACCURACY,
						  vec4f_from_vec4i(vec4i_sub(rayorigin, q->v[0])));
	vec4f QO = mat4f_apply(q->ext->invtransform, foo);
	vec4f QD = mat4f_apply(q->ext->invtransform, dir);

	//calculate distance 0 = oz + dist*dz
	float dist = -QO.z / QD.z;

	//coord calcs
	vec4f Qproj = vec4f_add(QO, vec4f_mul(dist, QD));
	Qproj.x /= q->width;
	Qproj.y /= q->height;

	vec4f proj = vec4f_add(foo, vec4f_mul(dist, dir));
	tracehit out = {0};
	out.pos = vec4i_add(q->v[0], vec4i_from_vec4f(vec4f_mul(YR_ACCURACY,proj)));
	out.dist = dist;
	out.q = q;
	out.hitcoord[0] = Qproj.x;
	out.hitcoord[1] = Qproj.y;
	return out;
}