
function min( a, b ){ return if( a < b, a, b ); }
function max( a, b ){ return if( a > b, a, b ); }
function clamp( v, vmin, vmax ){ return if( v < vmin, vmin, if( v > vmax, vmax, v ) ); }
function lerp( a, b, t ){ return a * (1.0-t) + b * t; }
function sign( x ){ return if( x == 0, 0, if( x < 0, -1, 1 ) ); }
function normalize_angle( x ){ x = x % ( M_PI * 2 ); return if( x < 0, x + M_PI*2, x ); }
function saturate( x ){ return if( x < 0, 0, if( x > 1, 1, x ) ); }
function smoothstep( x ){ return x * x * ( 3 - 2 * x ); }
function smoothlerp_oneway( t, a, b ){ if( b == a ) return 1.0; return smoothstep( saturate( ( t - a ) / ( b - a ) ) ); }
function smoothlerp_range( t, a, b, c, d ){ return smoothlerp_oneway( t, a, b ) * smoothlerp_oneway( t, d, c ); }

//
// PRIMITIVE RENDERING
//

function SS_InitDrawFunctions()
{
	global g_RB = SS_CreateRenderBuffer();
	global g_VD_P2CC4 = SS_MakeVertexFormat( "pf2cb4" );
	global g_VD_P2T2 = SS_MakeVertexFormat( "pf2tf2" );
	global g_VD_P2T2CC4 = SS_MakeVertexFormat( "pf2tf2cb4" );
}

function SS_DrawColorRect( x1, y1, x2, y2, r, g, b, a )
{
	g_RB.begin()
		.f( x1, y1 )
		.f( x2, y1 )
		.f( x2, y2 )
		.f( x1, y2 )
		.c4b( r, g, b, a ).interlace( 4, 8, 8 )
		.draw( null, g_VD_P2CC4, 0, 4, SS_PT_TRIANGLE_FAN );
}
function SS_DrawColorRect_WH( x, y, w, h, r, g, b, a ){ SS_DrawColorRect( x, y, x+w, y+h, r, g, b, a ); }

function SS_DrawColor2VRect( x1, y1, x2, y2, r1, g1, b1, a1, r2, g2, b2, a2 )
{
	g_RB.begin()
		.f( x1, y1 ).c4b( r1, g1, b1, a1 )
		.f( x2, y1 ).c4b( r1, g1, b1, a1 )
		.f( x2, y2 ).c4b( r2, g2, b2, a2 )
		.f( x1, y2 ).c4b( r2, g2, b2, a2 )
		.draw( null, g_VD_P2CC4, 0, 4, SS_PT_TRIANGLE_FAN );
}
function SS_DrawColor2VRect_WH( x, y, w, h, r1, g1, b1, a1, r2, g2, b2, a2 ){ SS_DrawColor2VRect( x, y, x+w, y+h, r1, g1, b1, a1, r2, g2, b2, a2 ); }

function SS_DrawTexRect( x1, y1, x2, y2, tex )
{
	g_RB.begin().f(
		x1, y1, 0, 0,
		x2, y1, 1, 0,
		x2, y2, 1, 1,
		x1, y2, 0, 1
	).draw( tex, g_VD_P2T2, 0, 4, SS_PT_TRIANGLE_FAN );
}
function SS_DrawTexRect_WH( x, y, w, h, tex ){ SS_DrawTexRect( x, y, x+w, y+h, tex ); }
function SS_DrawTexBox( x, y, w, h, tex ){ w /= 2; h /= 2; SS_DrawTexRect( x-w, y-h, x+w, y+h, tex ); }
function SS_DrawTexBox_Rotated( x, y, w, h, angle, tex )
{
	w *= 0.5;
	h *= 0.5;
	
	c = cos( angle );
	s = sin( angle );
	
	ext1x = ( c - s ) * w;
	ext1y = ( c + s ) * h;
	ext2x = ( -c -s ) * w;
	ext2y = ( c - s ) * h;
	
	g_RB.begin().f(
		x - ext1x, y - ext1y, 0, 0, // pos - ext1
		x - ext2x, y - ext2y, 1, 0, // pos - ext2
		x + ext1x, y + ext1y, 1, 1, // pos + ext1
		x + ext2x, y + ext2y, 0, 1 // pos + ext2
	).draw( tex, g_VD_P2T2, 0, 4, SS_PT_TRIANGLE_FAN );
}

function SS_DrawTexColorRect( x1, y1, x2, y2, tex, r, g, b, a )
{
	g_RB.begin()
		.f( x1, y1, 0, 0 )
		.f( x2, y1, 1, 0 )
		.f( x2, y2, 1, 1 )
		.f( x1, y2, 0, 1 )
		.c4b( r, g, b, a ).interlace( 4, 16, 16 )
		.draw( tex, g_VD_P2T2CC4, 0, 4, SS_PT_TRIANGLE_FAN );
}
function SS_DrawTexColorRect_WH( x, y, w, h, tex, r, g, b, a ){ SS_DrawTexColorRect( x, y, x+w, y+h, tex, r, g, b, a ); }
function SS_DrawTexColorBox( x, y, w, h, tex, r, g, b, a ){ w /= 2; h /= 2; SS_DrawTexColorRect( x-w, y-h, x+w, y+h, tex, r, g, b, a ); }
function SS_DrawTexColorBox_Rotated( x, y, w, h, angle, tex, r, g, b, a )
{
	w *= 0.5;
	h *= 0.5;
	
	c = cos( angle );
	s = sin( angle );
	
	// down vector = s;-c
	// right vector = c;s
	// ext1 = down + right
	// ext2 = down - right
	ext1x = +s*h +c*w;
	ext1y = -c*h +s*w;
	ext2x = +s*h -c*w;
	ext2y = -c*h -s*w;
	
	rb = g_RB;
	rb.begin();
	if( tex )
	{
		rb.f(
			x - ext1x, y - ext1y, 0, 0, // pos - ext1
			x - ext2x, y - ext2y, 1, 0, // pos - ext2
			x + ext1x, y + ext1y, 1, 1, // pos + ext1
			x + ext2x, y + ext2y, 0, 1  // pos + ext2
		);
		rb.c4b( r, g, b, a ).interlace( 4, 16, 16 );
	}
	else
	{
		rb.f(
			x - ext1x, y - ext1y, // pos - ext1
			x - ext2x, y - ext2y, // pos - ext2
			x + ext1x, y + ext1y, // pos + ext1
			x + ext2x, y + ext2y  // pos + ext2
		);
		rb.c4b( r, g, b, a ).interlace( 4, 8, 8 );
	}
	rb.draw( tex, if( tex, g_VD_P2T2CC4, g_VD_P2CC4 ), 0, 4, SS_PT_TRIANGLE_FAN );
}

function SS_DrawColorPolyOutlined( vertices, r, g, b, a, or, og, ob, oa )
{
	if( vertices.size < 3 )
		return;
	
	g_RB.begin();
	foreach( v : vertices )
		g_RB.f( v.x, v.y );
	g_RB.c4b( r, g, b, a ).interlace( 4, 8, 8 );
	g_RB.draw( null, g_VD_P2CC4, 0, vertices.size, SS_PT_TRIANGLE_FAN );
	
	g_RB.begin();
	foreach( v : vertices )
		g_RB.f( v.x, v.y );
	v = vertices.first;
	g_RB.f( v.x, v.y );
	g_RB.c4b( or, og, ob, oa ).interlace( 4, 8, 8 );
	g_RB.draw( null, g_VD_P2CC4, 0, vertices.size + 1, SS_PT_LINE_STRIP );
}

function SS_DrawColorLine( x1, y1, x2, y2, r, g, b, a )
{
	g_RB.begin()
		.f( x1, y1 ).c4b( r, g, b, a )
		.f( x2, y2 ).c4b( r, g, b, a )
		.draw( null, g_VD_P2CC4, 0, 2, SS_PT_LINES );
}

function SS_DrawColorCircle( x, y, radius, numsides, r, g, b, a )
{
	if( numsides < 1 || !numsides )
		numsides = ceil( radius * 8 / M_PI );
	if( numsides < 3 )
		return;
	
	g_RB.begin();
	
	ang = M_PI;
	ad = M_PI * 2 / numsides;
	for( i = 0; i < numsides; ++i )
	{
		g_RB.f( sin( ang ) * radius + x, cos( ang ) * radius + y );
		ang += ad;
	}
	
	g_RB.c4b( r, g, b, a ).interlace( 4, 8, 8 );
	g_RB.draw( null, g_VD_P2CC4, 0, numsides, SS_PT_TRIANGLE_FAN );
}

function SS_DrawButton( tex, color, x0, y0, x1, y1, off, texoff )
{
	off ||= 8;
	texoff ||= off;
	
	xA = x0 + off;
	xB = x1 - off;
	yA = y0 + off;
	yB = y1 - off;
	
	tox = texoff / tex.width;
	toy = texoff / tex.height;
	itx = 1 - tox;
	ity = 1 - toy;
	
	g_RB.begin();
	
	g_RB.f( x0, y0, 0, 0,  xA, y0, tox, 0,  xA, yA, tox, toy,  x0, yA, 0, toy )
		.f( xA, y0, tox, 0,  xB, y0, itx, 0,  xB, yA, itx, toy,  xA, yA, tox, toy )
		.f( xB, y0, itx, 0,  x1, y0, 1, 0,  x1, yA, 1, toy,  xB, yA, itx, toy );
	g_RB.f( x0, yA, 0, toy,  xA, yA, tox, toy,  xA, yB, tox, ity,  x0, yB, 0, ity )
		.f( xA, yA, tox, toy,  xB, yA, itx, toy,  xB, yB, itx, ity,  xA, yB, tox, ity )
		.f( xB, yA, itx, toy,  x1, yA, 1, toy,  x1, yB, 1, ity,  xB, yB, itx, ity );
	g_RB.f( x0, yB, 0, ity,  xA, yB, tox, ity,  xA, y1, tox, 1,  x0, y1, 0, 1 )
		.f( xA, yB, tox, ity,  xB, yB, itx, ity,  xB, y1, itx, 1,  xA, y1, tox, 1 )
		.f( xB, yB, itx, ity,  x1, yB, 1, ity,  x1, y1, 1, 1,  xB, y1, itx, 1 );
	
	g_RB.c4b( color.r, color.g, color.b, color.a ).interlace( 4, 16, 16 );
	g_RB.quadsToTris( 20 );
	g_RB.draw( tex, g_VD_P2T2CC4, 0, 54, SS_PT_TRIANGLES );
}

function SS_DrawFitted( tex, left, top, right, bottom, xq, yq )
{
	xq = 0.5;
	yq = 0.5;
	
	iw = tex.width;
	ih = tex.height;
	rw = right - left;
	rh = bottom - top;
	
	if( iw > rw || ih > rh )
	{
		ia = iw / ih;
		ra = rw / rh;
		if( ia > ra ) // width-limited
		{
			ih *= rw / iw;
			iw = rw;
		}
		else // height-limited
		{
			iw *= rh / ih;
			ih = rh;
		}
	}
	SS_DrawTexRect_WH( ( rw - iw ) * xq + left, ( rh - ih ) * yq + top, iw, ih, tex );
}



//
// MATRICES
//

function SS_GetCamera2DMatrix( x, y, size, aspect, angle )
{
	size = 1.0/size;
	x *= size/aspect;
	y *= size;
	return mat4
	(
		size/aspect, 0, 0, 0,
		0, -size, 0, 0,
		0, 0, size, 0,
		-x, y, 0.5*size, 1
	);
}
function SS_SetCamera2D( x, y, size, aspect, angle )
{
	SS_SetCamera( SS_GetCamera2DMatrix( x, y, size, aspect, angle ) );
}

function SS_GetCameraUIMatrix( x1, x2, y1, y2 )
{
	w = x2 - x1;
	h = y2 - y1;
	return mat4
	(
		2.0/w, 0, 0, 0,
		0, -2.0/h, 0, 0,
		0, 0, 1, 0,
		-1-x1/w*2.0, 1+y1/w*2.0, 0, 1
	);
}
function SS_SetCameraUI( x1, x2, y1, y2 )
{
	SS_SetCamera( SS_GetCameraUIMatrix( x1, x2, y1, y2 ) );
}

function SS_MultiplyPointMatrix( mtx, x, y )
{
	return vec2( mtx[0]*x + mtx[1]*y + mtx[3], mtx[4]*x + mtx[5]*y + mtx[7] );
}


function SS_MatrixTranspose( m )
{
	return mat4
	(
		m[0], m[4], m[8], m[12],
		m[1], m[5], m[9], m[13],
		m[2], m[6], m[10], m[14],
		m[3], m[7], m[11], m[15]
	);
}

function SS_GetCameraLookAt( pos, dir, up )
{
	zaxis = dir.normalized;
	xaxis = vec3_cross( up, zaxis ).normalized;
	yaxis = vec3_cross( zaxis, xaxis );
	
	return mat4
	(
		xaxis[0], yaxis[0], zaxis[0], 0,
		xaxis[1], yaxis[1], zaxis[1], 0,
		xaxis[2], yaxis[2], zaxis[2], 0,
		-vec3_dot( xaxis, pos ),
		-vec3_dot( yaxis, pos ),
		-vec3_dot( zaxis, pos ),
		1
	);
}
function SS_GetCameraPerspective( angle, aspect, znear, zfar )
{
	yscale = 1 / tan( angle / 2 );
	xscale = yscale / aspect;
	return mat4
	(
		-xscale, 0, 0, 0,
		0, yscale, 0, 0,
		0, 0, zfar/(zfar-znear), 1,
		0, 0, -znear*zfar/(zfar-znear), 0
	);
}
function SS_SetCamera3D( pos, dir, up, angle, aspect, znear, zfar )
{
	SS_SetCamera
	(
		SS_GetCameraLookAt( pos, dir, up )
		,SS_GetCameraPerspective( angle, aspect, znear, zfar )
	);
}
function SS_GetCamera3D( pos, dir, up, angle, aspect, znear, zfar )
{
	return
	[
		SS_GetCameraLookAt( pos, dir, up )
		,SS_GetCameraPerspective( angle, aspect, znear, zfar )
	];
}



//
// TEXT RENDERING
//

global
	DT_LEFT    = 0b00000001,
	DT_CENTER  = 0b00000010,
	DT_RIGHT   = 0b00000100,
	DT_JUSTIFY = 0b00001000,
	DT_TOP     = 0b00010000,
	DT_VCENTER = 0b00100000,
	DT_BOTTOM  = 0b01000000;


SS_CalcTextLayout_LineArray = [].reserve(3*32);
function SS_CalcTextLayout( text, font, width, height ) use( SS_CalcTextLayout_LineArray )
{
	lines = SS_CalcTextLayout_LineArray.clear();
	lineheight = font.size;
	
	SP = 0x20; // SPACE
	NL = 0x0A; // LINE FEED
	
	line_start = 0;
	end_of_last_word = 0;
	cur_line_width = 0;
	cur_word_width = 0;
	num_words = 1;
	
	prev_chr_pos = null;
	prev_chr_val = null;
	
	IT = string_utf8_iterator( text );
	if( !iter_advance( IT ) )
		return lines;
	for(;;)
	{
		(chr_pos, chr_val) = iter_getdata( IT, true );
		
		char_width = font.getAdvance( prev_chr_val, chr_val );
		
		if( chr_val == NL )
		{
			cur_line_width += cur_word_width;
			if( prev_chr_val == SP )
				num_words--;
			
		//	lines.push({ from = line_start, to = chr_pos, width = cur_line_width, numwords = num_words });
			lines.push( line_start, chr_pos, cur_line_width );
			
			cur_line_width = 0;
			cur_word_width = 0;
			num_words = 1;
			if( ( lines.size / 3 + 1 ) * lineheight > height )
				break;
			
			// goto next line after all subsequent spaces
			prev_chr_pos = null;
			prev_chr_val = null;
			while( ( lastadv = iter_advance( IT ) ) && iter_getdata( IT ) == SP );
			if( !lastadv )
				break;
			line_start = iter_getdata( IT, true, false );
			continue;
		}
		if( chr_val == SP )
		{
			cur_line_width += cur_word_width;
			if( prev_chr_val !== null && prev_chr_val != SP )
				num_words++;
			end_of_last_word = chr_pos;
			cur_word_width = 0;
		}
		
		if( cur_line_width + cur_word_width + char_width < width )
		{
			// still within line
			cur_word_width += char_width;
			prev_chr_pos = chr_pos;
			prev_chr_val = chr_val;
			if( !iter_advance( IT ) )
				break;
		}
		else
		{
			// over the limit
			if( cur_line_width )
			{
				// if not first word, commit line and restart the word
				cur_word_width = 0;
				num_words--;
				chr_pos = end_of_last_word;
				
				IT.offset = end_of_last_word;
			}
			cur_line_width += cur_word_width;
			
		//	lines.push({ from = line_start, to = i, width = cur_line_width, numwords = num_words });
			lines.push( line_start, chr_pos, cur_line_width );
			
			cur_line_width = 0;
			cur_word_width = 0;
			num_words = 1;
			if( ( lines.size / 3 + 1 ) * lineheight > height )
				break;
			
			// goto next line after all subsequent spaces
			prev_chr_pos = null;
			prev_chr_val = null;
			while( ( lastadv = iter_advance( IT ) ) && iter_getdata( IT ) == SP );
			if( !lastadv )
				break;
			line_start = iter_getdata( IT, true, false );
		}
	}
	
	cur_line_width += cur_word_width;
	if( cur_line_width )
	{
	//	lines.push({ from = line_start, to = text_ucp.size, width = cur_line_width, numwords = num_words });
		lines.push( line_start, text.length, cur_line_width );
	}
	
	return lines;
}

function SS_DrawTextRect( text, font, color, placement, left, right, top, bottom )
{
	// validation
	if( !SS_IsFont( font ) )
		return WARNING( "invalid value for 'font'" );
	
	hfc = !!( placement & DT_LEFT ) + !!( placement & DT_CENTER )
		+ !!( placement & DT_RIGHT ) + !!( placement & DT_JUSTIFY );
	vfc = !!( placement & DT_TOP ) + !!( placement & DT_VCENTER )
		+ !!( placement & DT_BOTTOM );
	if( hfc != 1 || vfc != 1 )
		return WARNING( "invalid placement flag combo" );
	
	// sizing
	width = right - left;
	height = bottom - top;
	
	lineheight = font.size;
	if( height < lineheight )
		return true;
	
	lines = SS_CalcTextLayout( text, font, width, height );
	
	vspace = height - lines.size / 3 * lineheight;
	y = top;
	if( placement & DT_VCENTER ) y += vspace / 2;
	else if( placement & DT_BOTTOM ) y += vspace;
	
//	foreach( lnum, line : lines )
	for( clo = 0; clo < lines.size; clo += 3 )
	{
		hspace = width - lines[clo+2];//line.width;
		x = left;
		if( placement & DT_CENTER ) x += hspace / 2;
		else if( placement & DT_RIGHT ) x += hspace;
		
		textpart = string_part( text, lines[clo], lines[clo+1] - lines[clo] );
		
		SS_DrawTextLine_VN( textpart, font, x, y, color );
		y += lineheight;
	}
}


