function exists(name)
    if type(name)~="string" then return false end
    return os.rename(name,name) and true or false
end

function isFile(name)
    if type(name)~="string" then return false end
    if not exists(name) then return false end
    local f = io.open(name)
    if f then
        f:close()
        return true
    end
    return false
end

function isDir(name)
    return (exists(name) and not isFile(name))
end

fonts = {
	inconsolata = {sizes={}, file="fonts/inconsolata.ttf"},
	mplus = {sizes={}, file="fonts/inconsolata.ttf"}
}
mouse = {}
keyboard 	= {}
developer = true
function print_info(levels)
	local levels = levels or 1
	for i=1,levels,1 do 
		local info = debug.getinfo(i+1, "Sl")
	  print(string.format("%s:%d",info.short_src, info.currentline))
	end
end

if love then 
	LOVE = true
	lfs 	 				= love.filesystem
	gfx 	 				= lume.clone(love.graphics)
	sfx 	 				= love.sound and lume.clone(love.sound)
	afx 	 				= love.audio and lume.clone(love.audio)
	mth 					= lume.clone(love.math)
	limage 				= love.image
	lthread 			= love.thread
	ltimer        = lume.clone(love.timer)
	lmouse  			= love.mouse
	lkeyboard     = love.keyboard
	lwindow       = love.window
	ljoystick     = love.joystick
	local gfx = gfx
	print(inspect(love.graphics.getSystemLimits( )))
	ffi.cdef [[
	  //typedef void (*GLFWglproc)(void);
	  void* SDL_GL_GetProcAddress(const char* proc)
  ]]
	getproc = function (name) return ffi.C.SDL_GL_GetProcAddress(name) end
	gl   					= require("source/libs/opengl")

	gl.Initialize(getproc)
	gle = setmetatable({}, {__index = function(_, key)
		return gl.e["GL_"..key]
	end})
	gl.LineWidth(4)
	
	function lfs.exists  (dir)
		return lfs.getInfo(dir)
	end

	function lfs.isFile  (dir)
		return lfs.getInfo(dir).type~="directory"
	end

	function lfs.isDir  (dir)
		return lfs.getInfo(dir).type=="directory"
	end

	function love.keypressed( k)		keyboard.pressed (k) end
	function love.keyreleased( k)	keyboard.released( k) end

	function love.mousepressed( x, y, button)		mouse.pressed ( x, y, button) end
	function love.mousereleased( x, y, button)	mouse.released( x, y, button) end
	function love.wheelmoved(x, y)							mouse.wheelmoved(x, y) end
	function love.mousemoved(x, y, dx, dy)			mouse.mousemoved(x, y, dx, dy) end




	
	mat4 = cpml.mat4
	vec3 = cpml.vec3 
	vec2 = cpml.vec2
	quat = cpml.quat
	bound3 = cpml.bound3
	utils = cpml.utils

	axes = {
		x=vec3(1,0,0),
		y=vec3(0,1,0),
		z=vec3(0,0,1)
	}

	blankVec = vec3(0)
	blankMat = mat4()
	function makeProper(mat)
		local m = mat4.new()
		return mat4.transpose(m, mat)
	end

	love.setDeprecationOutput( false )
	setVSync = function(v) lwindow.setVSync(v and 1 or 0) end 
	

	local function error_printer(msg, layer)
		print((debug.traceback("Error: " .. tostring(msg), 1+(layer or 1)):gsub("\n[^\n]+$", "")))
	end
	 
	function love.errorhandler(msg)
		msg = tostring(msg)
		error_printer(msg, 2)
	end

	love.load   = engine.load
	love.quit   = engine.quit
	love.update = engine.update
	love.draw   = engine.draw
	love.resize = engine.resize
	love.filedropped = engine.filedropped
	love.directorydropped = engine.directorydropped
	love.focus = engine.focus

	function gfx.setCamera(camera)
		gfx.camera = camera
	end

	function gfx.draw3D(mesh)
		gfx.draw(mesh)
	end

	function gfx.start3D()

	end
	function gfx.end3D()

	end

	function gfx.setTexture(mesh,texture)
		if mesh then 
			mesh:setTexture(texture)
		end
	end

	function gfx.isCanvas(canvas)
		return type(canvas)=="userdata" and canvas:type()=="Canvas"
	end

	function gfx.isTexture(canvas)
		return type(canvas)=="userdata" and (canvas:type()=="Canvas" or canvas:type()=="Image")
	end

	function gfx.getTexture (mesh)
		if mesh then 
			return mesh:getTexture()
		end
	end
	
	function gfx.quad (w,h)
		return gfx.newMesh(zformat6,{
			{0,0,0,0, 0,0, 1,1,1,1},
			{w,0,0,0, 1,0, 1,1,1,1},
			{w,h,0,0, 1,1, 1,1,1,1},
			
			{0,0,0,0, 0,0, 1,1,1,1},
			{w,h,0,0, 1,1, 1,1,1,1},
			{0,h,0,0, 0,1, 1,1,1,1},
		})
	end

	function gfx.setDefaultFilter ()
		love.graphics.setDefaultFilter("nearest","nearest")
	end

	function gfx.setFilter  (tex,filterU,filterD)
		tex:setFilter(filterU,filterD or filterU)
		return true
	end

	function gfx.fromTextureData (data,map)
		if map then 
			data:mapPixel(map)
		end
		local img =gfx.newImage( data )
		local canv = gfx.newCanvas(img:getDimensions())
		gfx.push("all")
			gfx.setCanvas(canv)
			gfx.draw(img)
			img:release()
		gfx.pop()
		return canv
	end

	local fontCount = 0
	local maxSize 	= 128
	local step 		= 5
	local gfx_setFont = love.graphics.setFont
	function gfx.setFont(name,size)
		if not name then print("invalid font") return end
		if gfx.customFont then name = bezierFont end
		if name == gfx.currentFontName and mf(size) == mf(gfx.currentFontSize) then 
			return gfx.currentFont 
		end

		local size = mf(size or gfx.currentFontSize)
		local font 
		name.sizes = name.sizes or {}
		if name.sizes[size] == nil then 
			font = gfx.newFont(name.file , size)
			print("new font",size,font)
			--font:setFallbacks(gfx.newFont( size))
			name.sizes[size] = font 
			if LOVE then font:setFilter( "nearest", "nearest", 0 ) end
			gfx_setFont(font)
		elseif gfx.getFont() ~= name.sizes[size] then
			font = name.sizes[size]
			gfx_setFont(font)
		end

		gfx.currentFont  	= font or gfx.currentFont
		if gfx.currentFont then 
			gfx.currentFontHeight = gfx.currentFont:getHeight()
			gfx.currentFontWidth  = gfx.currentFont:getWidth("w")
		end
		gfx.currentFontName = name
		gfx.currentFontSize = size
		return gfx.currentFont
	end

	function gfx.fontSize(n)
		gfx.setFont(gfx.currentFontName,n)
	end



	loSpec = select(4,gfx.getRendererInfo()):sub(1,5)=="Intel"
	hiSpec = not loSpec

	defaultCursor = lmouse.getSystemCursor( "arrow" )
	resizeCursor	= lmouse.getSystemCursor( "sizewe" )
	hand 					= lmouse.getSystemCursor( "hand" )
	crossCursor 	= lmouse.getSystemCursor( "crosshair" )
	lkeyboard.setKeyRepeat 	( false )

	local bz1,bz2,bz3 = vec2(),vec2(),vec2()
	gfx.setNoGamma=function() end

	path1 = tostring(lfs.getSource().."/"):gsub("%s+", "\\ ")
	path2 = tostring(lfs.getAppdataDirectory().."/".."LOVE".."/"..lfs.getIdentity().."/"):gsub("%s+", "\\ ")

	engine.run = nothing
else 













----------------------------------------------------------------------------------
	LOVR = true
	ffi.cdef [[
	  typedef void (*GLFWglproc)(void);
	  GLFWglproc glfwGetProcAddress(const char* procname);

	  void	glfwWindowHint (int hint, int value);
	  void* calloc(size_t nitems, size_t size);
	  void free(void *ptr);
	  void *memset(void *ptr, int x, size_t n);

	  typedef struct  {int width; int height; unsigned char * pixels;} GLFWimage;
	  typedef struct  GLFWcursor GLFWcursor;
	  GLFWcursor* glfwCreateCursor	(	const GLFWimage * 	image, int 	xhot, int 	yhot );
		
	]]


  rl.SetTraceLogLevel(7)
  rl.SetExitKey(0)
	rl.SetConfigFlags(bit.bor(rl.FLAG_VSYNC_HINT, rl.FLAG_WINDOW_RESIZABLE, rl.FLAG_MSAA_4X_HINT ))
	-- ffi.C.glfwInit()
	-- ffi.C.glfwWindowHint(0x0002100D, 8)
	rl.InitWindow(1024, 1024, "")

	lfs 	 				= {}
	gfx 	 				= {}
	sfx 	 				= {}
	afx 	 				= {}
	mth 					= {}
	limage 				= {}
	lthread 			= {}
	ltimer        = {}
	ljoystick     = {}
	lwindow       = {}
	lmouse  			= require('source/libs/lovr-mouse')
	lkeyboard 		= require('source/libs/lovr-keyboard')
	gl   					= require("source/libs/opengl")

	getproc = function (name) return ffi.C.glfwGetProcAddress(name) end
	gl.Initialize(getproc)
	gle = setmetatable({}, {__index = function(_, key)
		return gl.e["GL_"..key]
	end})

	gl.Enable(gle.MULTISAMPLE) 
	-- gl.Enable(GL_FRAMEBUFFER_SRGB)

	local function threadPop(self,name)
		return nil
	end
	local function threadPush(self,name)
		return nil
	end
	function lthread.getChannel()
		local channel= {
			pop=threadPop,
			push=threadPush,
		}
		return channel
	end



	-----------------------------------------------------------------------------

	function lmouse.setCursor(cursor)

	end

	local pixels=ffi.new("unsigned char[?]", 16 * 16 * 4)
	local image= ffi.new("GLFWimage",  16,  16,  pixels)
	local blankCursor = ffi.C.glfwCreateCursor(image, 0, 0);
	function lmouse.setVisible(visible)
		ffi.C.glfwSetCursor(ffi.C.glfwGetCurrentContext(), (not visible) and blankCursor or nil);
	end

	function lmouse.newCursor(data)

	end


	local fs   				= require("fs")
	for k,v in pairs(rl) do
		-- print(k)
	end

	function cchar(str)
		local chars = ffi.new("char[?]",ffi.sizeof("char")*#str)
		ffi.copy(chars, str)
		return ffi.new("const char*",chars)
	end

	-- WINDOW----------------------------------------------------------------------
	
	function lwindow.setMode(width,height,flags)	
		print("set size ",width,height)
		-- delayedCall(function()
			rl.SetWindowSize(width,height)
		-- end,2)
		
		if flags then 

		end
	end

	function lwindow.setPosition(x,y)
		rl.SetWindowPosition(x,y)
	end

	function lwindow.getPosition()
		return 0,0
	end

	function lwindow.setTitle(title)
		rl.SetWindowTitle(title)
	end

	function lwindow.setFullscreen(set)
		if not set == rl.IsWindowFullscreen() then
			-- rl.ToggleFullscreen()
		end
	end

	function lwindow.getFullscreen()
		return rl.IsWindowFullscreen()
	end

	function lwindow.setVSync(sync)
		lwindow.vsync = sync
	end

	setVSync=lwindow.setVSync



	-- MATH -------------------------------------------------------------------------
	local sin,cos = math.sin,math.cos

	mat4 = cpml.mat4 
	vec3 = cpml.vec3
	vec2 = cpml.vec2
	quat = cpml.quat
	utils = cpml.utils

	local Polygon           = require('source.libs.HC.polygon')
	function mth.triangulate(poly)
    local pol = Polygon(unpack(poly))
    local tris = pol:triangulate()
    return tris
  end

  local tmpBez1 = ffi.new(VECTOR_TYPE.."[10]")
	local tmpBez2 = ffi.new(VECTOR_TYPE.."[10]")

	local function evaluate(bezier,t)
		local curve = bezier.curve
	  tmpBez1[1],tmpBez1[2] = lerp(curve[1],curve[5],t),  lerp(curve[2],curve[6],t) -- E
	  tmpBez1[3],tmpBez1[4] = lerp(curve[5],curve[7],t),  lerp(curve[6],curve[8],t) -- F
	  tmpBez1[5],tmpBez1[6] = lerp(curve[7],curve[3],t),  lerp(curve[8],curve[4],t) -- G
	  tmpBez1[7],tmpBez1[8] = lerp(tmpBez1[3],tmpBez1[5],t),        lerp(tmpBez1[4],tmpBez1[6],t) -- J

	  tmpBez2[1],tmpBez2[2] = curve[1],curve[2] -- A
	  tmpBez2[5],tmpBez2[6] = tmpBez1[1],tmpBez1[2] -- E
	  tmpBez2[7],tmpBez2[8] = lerp(tmpBez1[1],tmpBez1[3],t),    lerp(tmpBez1[2],tmpBez1[4],t) -- H
	  return lerp(tmpBez2[7],tmpBez1[7],t),  lerp(tmpBez2[8],tmpBez1[8],t) -- K
	end
	local function setControlPoint (bezier,i,x,y)
		local curve = bezier.curve
		if 		 i==1 then 	curve[1],curve[2]= x,y
		elseif i==4 then	curve[3],curve[4]= x,y 
		elseif i==2 then	curve[5],curve[6]= x,y
		elseif i==3 then	curve[7],curve[8]= x,y end
	end
	local bezTable = {__index={
		evaluate = evaluate,
		setControlPoint = setControlPoint
	}}
  function mth.newBezierCurve(...)
    local pts = {...}
    local bez = ffi.new(VECTOR_TYPE.."[10]")
		bez[1],bez[2]= pts[1],pts[2]
		bez[3],bez[4]= pts[7],pts[8]
		bez[5],bez[6]= pts[3],pts[4]
		bez[7],bez[8]= pts[5],pts[6]
    local bezier = {curve = bez}
    return setmetatable(bezier,bezTable)
  end
	-- -- Vec2 		-------------------
	-- vec2,vec2_mt = {},{}
	-- local newVec2 = ffi.typeof("Vector2")
	-- function vec2.new(x,y)
	-- 	if type(x) == "number" then
	-- 		return newVec2(x, y or x)
	-- 	elseif type(x) == "table" then
	-- 		local xx, yy = x.x or x[1], x.y or x[2]
	-- 		return newVec2(xx, yy)
	-- 	else
	-- 		return newVec2(0,0)
	-- 	end
	-- end
	-- function vec2_mt.__call(_, x,y)
	-- 	return vec2.new(x,y)
	-- end
	-- ffi.metatype(newVec2, vec2_mt)
	-- setmetatable(vec2, vec2_mt)

	-- -- Vec3 		-------------------
	-- vec3,vec3_mt = {},{}
	-- local newVec3 = ffi.typeof("Vector3")
	-- local tmp1,tmp2,tmp3

	-- function vec3.new(x,y,z)
	-- 	if x and y and z then
	-- 		return newVec3(x, y, z)
	-- 	elseif type(x) == "table" then
	-- 		local xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]
	-- 		return newVec3(xx, yy, zz)
	-- 	elseif type(x) == "number" then
	-- 		return newVec3(x, x, x)
	-- 	else
	-- 		return newVec3(0,0,0)
	-- 	end
	-- end
	-- function vec3.clone(a)
	-- 	return newVec3(a.x, a.y, a.z)
	-- end
	-- function vec3.dot(a, b)
	-- 	return a.x * b.x + a.y * b.y + a.z * b.z
	-- end
	-- function vec3.normalize(a)
	-- 	return newVec3(rl.Vector3Normalize(a))
	-- end
	-- function vec3.rotate(a, phi, axis)
	-- 	if not vec3.is_vec3(axis) then
	-- 		return a
	-- 	end
	-- 	local u = axis:normalize()
	-- 	local c = cos(phi)
	-- 	local s = sin(phi)

	-- 	tmp1:set((c+u.x*u.x*(1-c)),(u.x*u.y*(1-c)-u.z*s),(u.x*u.z*(1-c)+u.y*s))
	-- 	tmp2:set((u.y*u.x*(1-c)+u.z*s),(c+u.y*u.y*(1-c)),(u.y*u.z*(1-c)-u.x*s))
	-- 	tmp3:set((u.z*u.x*(1-c)-u.y*s),(u.z*u.y*(1-c)+u.x*s),(c+u.z*u.z*(1-c)))

	-- 	return newVec3(
	-- 		a:dot(tmp1),
	-- 		a:dot(tmp2),
	-- 		a:dot(tmp3)
	-- 	)
	-- end
	-- function vec3.set(a, b, c, d)
	-- 	if c then
	-- 		a.x = b
	-- 		a.y = c
	-- 		a.z = d
	-- 	else  
	-- 		a.x = b.x
	-- 		a.y = b.y
	-- 		a.z = b.z
	-- 	end
	-- 	return a
	-- end
	-- function vec3.is_vec3(a)
	-- 	return 	type(a) == "cdata" and ffi.istype("Vector3", a)
	-- end
	-- function vec3_mt.__call(_,  x, y, z)
	-- 	return vec3.new( x, y, z)
	-- end
	-- vec3_mt.__index    = vec3
	-- ffi.metatype(newVec3, vec3_mt)
	-- setmetatable(vec3, vec3_mt)
	-- tmp1,tmp2,tmp3 = vec3(),vec3(),vec3()

	-- Quat 		-------------------
	-- quat,quat_mt = {},{}
	-- local newQuat = ffi.typeof("Quaternion")
	-- function quat.new(x,y,z,w)
	-- 	if x and y and z and w then
	-- 		return newQuat(x, y, z, w)
	-- 	elseif type(x) == "table" then
	-- 		local xx, yy, zz, ww = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]
	-- 		return newQuat(xx, yy, zz, ww)
	-- 	end
	-- 	return newQuat(0, 0, 0, 1)
	-- end
	-- function quat.rotate(angle,axis)
	-- 	return rl.QuaternionFromAxisAngle(axis,angle)
	-- end
	-- function quat.is_quat(a)
	-- 	return 	type(a) == "cdata" and ffi.istype("Quaternion", a)
	-- end
	-- function quat_mt.__mul(a, b)
	-- 	assert(quat.is_quat(a), "__mul: Wrong argument type for left hand operand. (<cpml.quat> expected)")
	-- 	assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.quat> or <cpml.vec3> or <number> expected)")

	-- 	if quat.is_quat(b) then
	-- 		return rl.QuaternionMultiply(a,b)
	-- 	end

	-- 	-- if type(b) == "number" then
	-- 	-- 	return a:scale(b)
	-- 	-- end

	-- 	return rl.Vector3RotateByQuaternion(b, a);
	-- end
	-- function quat_mt.__call(_, a)
	-- 	return quat.new()
	-- end
	-- ffi.metatype(newQuat, quat_mt)
	-- setmetatable(quat, quat_mt)

	-- Matrix 	-------------------
	-- mat4,mat4_mt = {},{}
	-- local newMat = ffi.typeof("Matrix")
	-- function mat4.new()
	-- 	return newMat():identity()
	-- end
	-- function mat4.set(a,b)
	-- 	a.m0,a.m1,a.m2,a.m3,a.m4,a.m5,a.m6,a.m7,a.m8,a.m9,a.m10,a.m11,a.m12,a.m13,a.m14,a.m15 = 
	-- 	b.m0,b.m1,b.m2,b.m3,b.m4,b.m5,b.m6,b.m7,b.m8,b.m9,b.m10,b.m11,b.m12,b.m13,b.m14,b.m15 
	-- 	return a
	-- end
	-- local identityMat = rl.MatrixIdentity()
	-- function mat4.identity(out)
	-- 	if out then 
	-- 		return out:set(identityMat)
	-- 	else
	-- 		mat4():set(identityMat)
	-- 	end
	-- end
	-- function mat4.mul(out, a, b)

	-- 	out.m0 = a.m0*b.m0 + a.m1*b.m4 + a.m2*b.m8 + a.m3*b.m12;
 --    out.m1 = a.m0*b.m1 + a.m1*b.m5 + a.m2*b.m9 + a.m3*b.m13;
 --    out.m2 = a.m0*b.m2 + a.m1*b.m6 + a.m2*b.m10 + a.m3*b.m14;
 --    out.m3 = a.m0*b.m3 + a.m1*b.m7 + a.m2*b.m11 + a.m3*b.m15;
 --    out.m4 = a.m4*b.m0 + a.m5*b.m4 + a.m6*b.m8 + a.m7*b.m12;
 --    out.m5 = a.m4*b.m1 + a.m5*b.m5 + a.m6*b.m9 + a.m7*b.m13;
 --    out.m6 = a.m4*b.m2 + a.m5*b.m6 + a.m6*b.m10 + a.m7*b.m14;
 --    out.m7 = a.m4*b.m3 + a.m5*b.m7 + a.m6*b.m11 + a.m7*b.m15;
 --    out.m8 = a.m8*b.m0 + a.m9*b.m4 + a.m10*b.m8 + a.m11*b.m12;
 --    out.m9 = a.m8*b.m1 + a.m9*b.m5 + a.m10*b.m9 + a.m11*b.m13;
 --    out.m10 = a.m8*b.m2 + a.m9*b.m6 + a.m10*b.m10 + a.m11*b.m14;
 --    out.m11 = a.m8*b.m3 + a.m9*b.m7 + a.m10*b.m11 + a.m11*b.m15;
 --    out.m12 = a.m12*b.m0 + a.m13*b.m4 + a.m14*b.m8 + a.m15*b.m12;
 --    out.m13 = a.m12*b.m1 + a.m13*b.m5 + a.m14*b.m9 + a.m15*b.m13;
 --    out.m14 = a.m12*b.m2 + a.m13*b.m6 + a.m14*b.m10 + a.m15*b.m14;
 --    out.m15 = a.m12*b.m3 + a.m13*b.m7 + a.m14*b.m11 + a.m15*b.m15;

	--   return out
	-- end
	-- local trans = rl.MatrixIdentity()
	-- function mat4.translate(out,a,vec)
	-- 	if not vec then 
	-- 		out,a,vec = mat4(),out,a 
	-- 	end
	-- 	trans.m3,trans.m7,trans.m11 = vec.x,vec.y,vec.z
	-- 	local b = trans 
	-- 	return out:set(rl.MatrixMultiply(a,rl.MatrixTranslate(vec.x,vec.y,vec.z)))
	-- end
	-- local tmpvec = vec3():cast()
	-- function mat4.from_angle_axis(angle,axis)
	-- 	local out = newMat()
	-- 	tmpvec.x,tmpvec.y,tmpvec.z = axis.x,axis.y,axis.z
	-- 	return out:set(rl.MatrixRotate(tmpvec,angle))
	-- end
	-- function mat4.rotate(out, a, angle,axis)
	-- 	tmpvec.x,tmpvec.y,tmpvec.z = axis.x,axis.y,axis.z
	-- 	return out:mul(a, rl.MatrixRotate(tmpvec,angle))
	-- end
	
	-- function mat4.scale(out,a,s)
	-- 	if not s then 
	-- 		a,s = out,a
	-- 	end
	-- 	if type(s)=="number" then 
	-- 		s = vec3(s,s,s)
	-- 	end
	-- 	mat4.set(out,a)
	-- 	return out:set(rl.MatrixMultiply(a,rl.MatrixScale(s.x,s.y,s.z)))
	-- end
	-- function mat4.from_quaternion(quat)
	-- 	return rl.QuaternionToMatrix(quat:cast())
	-- end
	-- function mat4.from_perspective(fovy, aspect, near, far)
	-- 	return rl.MatrixPerspective( fovy, aspect,  near,  far);
	-- end
	-- function mat4.invert(out, a)
	-- 	return out:set(rl.MatrixInvert(a or out))
	-- end
	-- function mat4.transpose(out,a)
	-- 	return out:set(rl.MatrixTranspose(a or out))
	-- end

	-- function mat4.clone(m)
	-- 	return newMat():set(m)
	-- end

	-- function mat4.look_at(out, a, eye, target, up)
	-- 	return out:set(rl.MatrixLookAt(eye:cast(),  target:cast(), up:cast()))
	-- end

	-- local position = vec3():cast()
	-- function mat4.unproject(win, view, projection, viewport)

	-- 	position.x = (position.x - viewport[1]) / viewport[3]
	-- 	position.y = (position.y - viewport[2]) / viewport[4]

	-- 	position.x = position.x * 2 - 1
	-- 	position.y = position.y * 2 - 1
	-- 	position.z = position.z * 2 - 1

	-- 	return vec3(rl.rlUnproject(position,  projection, view))
	-- end

	-- function mat4_mt.__mul(a, b)
	-- 	assert(mat4.is_mat4(a), "__mul: Wrong argument type for left hand operand. (<cpml.mat4> expected)")

	-- 	if vec3.is_vec3(b) then
	-- 		tmpvec.x,tmpvec.y,tmpvec.z = b.x,b.y,b.z
	-- 		return vec3(rl.Vector3Transform(tmpvec, a))
	-- 	end

	-- 	assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")

	-- 	if mat4.is_mat4(b) then
	-- 		return newMat():mul(a, b)
	-- 	end
	-- 	error()
	-- end

	-- function mat4.is_mat4(a)
	-- 	return 	type(a) == "cdata" and ffi.istype("Matrix", a)
	-- end
	

	-- function mat4_mt.__call(_)
	-- 	return mat4.new()
	-- end
	-- function mat4_mt.__index(t, k)
	-- 	return rawget(mat4, k)
	-- end
	-- ffi.metatype(newMat, mat4_mt)
	-- setmetatable(mat4,   mat4_mt)

	-- tmpMat = mat4()
	-- mat1 = mat4():scale(3)
	-- mat2 = mat4():scale(vec3(2,2,2)):scale(vec3(1,2,3))
	-- mat3 = mat4()

	-- mat3:mul(mat1,mat2)

	-- local m = mat2
	-- print(m.m0		,m.m1		,m.m2		,m.m3		)
	-- print(m.m4		,m.m5		,m.m6		,m.m7		)
	-- print(m.m8		,m.m9		,m.m10		,m.m11		)
	-- print(m.m12	,m.m13		,m.m14		,m.m15		)

	cpml.mat4.transpose=function(out,a) 
		for i=1, 16 do
			out[i] = a[i]
		end 
		return out
	end
	local tmpMat = ffi.new("Matrix")
	function setMat(b)
		local a = tmpMat 
		a.m0,a.m1,a.m2,a.m3,a.m4,a.m5,a.m6,a.m7,a.m8,a.m9,a.m10,a.m11,a.m12,a.m13,a.m14,a.m15 = 
		b[1],b[2],b[3],b[4],b[5],b[6],b[7],b[8],b[9],b[10],b[11],b[12],b[13],b[14],b[15],b[16]
		return tmpMat
	end
	axes = {
		x=vec3(1,0,0),
		y=vec3(0,1,0),
		z=vec3(0,0,1)
	}
	vec3.unit_x = axes.x
	vec3.unit_y = axes.y
	vec3.unit_z = axes.z

	blankVec = vec3(0)
	blankMat = mat4()

	function makeProper(m)
		return m
	end

	local tempModel = ffi.new("Model")
	local tempRay = ffi.new("Ray")
	tempBBOX = ffi.new("BoundingBox")
  tempModel.meshCount = 1
  function testTempBBOX(ray,bbox)
		tempBBOX.min.x,tempBBOX.min.y,tempBBOX.min.z = 
			bbox.min.x,bbox.min.y,bbox.min.z
		tempBBOX.max.x,tempBBOX.max.y,tempBBOX.max.z = 
			bbox.max.x,bbox.max.y,bbox.max.z
		tempRay.position.x,tempRay.position.y,tempRay.position.z = 
			ray.position.x,ray.position.y,ray.position.z
		tempRay.direction.x,tempRay.direction.y,tempRay.direction.z = 
			ray.direction.x,ray.direction.y,ray.direction.z
		return rl.CheckCollisionRayBox(tempRay,tempBBOX)
	end
	function testTempModel (ray,mesh,transform)
    tempModel.transform = setMat(transform)
    tempModel.meshes = ffi.cast("Mesh*",mesh.mesh)
    tempRay.position = ray.position:cast()
    tempRay.direction = ray.direction:cast()
    local info = rl.GetCollisionRayModel(tempRay, tempModel)
    return 
    	info.hit,
    	info.distance,
    	vec3(info.position.x,info.position.y,info.position.z),
    	vec3(info.normal.x,info.normal.y,info.normal.z)
	end
	-- IMAGE -------------------------------------------------------------------------
	gfx.defaultFilter = "linear"
	local function returnType(self) return self._type end
  local function getWidth(self) return self.width end  
  local function getHeight(self) return self.height end  
  local function getFormat(self) return self.format end
  local function getMSAA(self) return self.msaa end
	local function getFilter(self) return self.filter end  
	local function setFilter(self,filter) 
		self.filter=filter 
		local tex = self.isCanvas and self.texture.texture or self.texture
		if filter=="linear" then 
			gl.BindTexture(gle.TEXTURE_2D, tex.id);
			gl.TexParameteri(gle.TEXTURE_2D, gle.TEXTURE_MIN_FILTER, gle.LINEAR);
			gl.BindTexture(gle.TEXTURE_2D, 0);
		else
			rl.SetTextureFilter(tex,0)
		end
	end  
  local function releaseImageData(self) end
  local function releaseTexture(self) end
  local function releaseMesh(self) 
  	-- rl.rlUnloadMesh(self.mesh) lume.clear(self) 
  end
  local function tostringcanvas(self) return "Canvas" end

  function encode(self,format,name)
		rl.ExportImage(type(self)=="table" and self.data or self,name)
	end

  local imgColor = ffi.new("Color",0,0,0,1)
  local imageDataMT = {__index={
  	isImageData = true,
		_type   = "ImageData",
		release = releaseImageData,
		type   = returnType,
		getHeight=getHeight,
		getWidth=getWidth,
		encode = encode
		-- Data:getString
		-- ImageData:getDimensions
		-- ImageData:getFormat
		-- ImageData:getPixel
		-- ImageData:getString
		-- ImageData:mapPixel
		-- ImageData:paste
		-- ImageData:setPixel
		-- Object:release
		-- Object:type
		-- Object:typeOf
  }}
	function limage.newImageData(w,h)
		local imageData = {
  		width  = w, 
  		height = h,
		}
		if h then 
			imageData.data = rl.GenImageColor(w, h, imgColor)
		else
			imageData.data = rl.LoadImage(w)
		end
		return setmetatable(imageData,imageDataMT)
	end

	local function newImageDataFromImage(self) 
		local data = {
			data = rl.GetTextureData(self.texture) 
		}
		return setmetatable(data,imageDataMT)
	end
	local function newImageDataFromCanvas(self) 
		local data = {
			data = rl.GetTextureData(self.texture.texture) 
		}
		return setmetatable(data,imageDataMT)
	end
	-- GRAPHICS------------------------------------------------------------------------

	local imageMT = {__index={
		isImage = true,
		_type   = "Image",
		type   = returnType,
		getFilter =getFilter,
		setFilter =setFilter,
		release = releaseTexture,
		newImageData = newImageDataFromImage,
		getWidth  = getWidth,
		getHeight = getHeight,
	}}

	function gfx.newImage(fileOrData)
		local image = {
  		filter  = gfx.defaultFilter
		}
		if type(fileOrData)=="string" then 
			image.texture = rl.LoadTexture(fileOrData)
		else 
			image.texture = rl.LoadTextureFromImage(fileOrData)
		end
		image.width  = image.texture.width
		image.height = image.texture.height
		gfx.setFilter(image,image.filter)
		image.matmap  =ffi.new("MaterialMap",image.texture,ffi.new("Color",1,1,1,1))
		image.material=ffi.new("Material",gfx.shader.shader,ffi.cast("MaterialMap*",image.matmap))

		return setmetatable(image,imageMT)
	end
	local function renderTo(self,func) 
  	local prev = gfx.getCanvas()
  	gfx.setCanvas(self)
  	func() 
  	gfx.setCanvas(prev)
  end

  local canvasMT = {__index={
		isCanvas = true,
		type   = returnType,
		newImageData = newImageDataFromCanvas,
		getFilter =getFilter,
		setFilter =setFilter,
		getFormat = getFormat,
		getMSAA = getMSAA,
		release = releaseTexture,
		getWidth  = getWidth,
		getHeight = getHeight,
		renderTo = renderTo,
		_type   = "Canvas",
  },__tostring = tostringcanvas}

 --  local function copyFrameBufferTexture(int width, int height, int fboIn, int textureIn, int fboOut, int textureOut)
 --    -- Bind input FBO + texture to a color attachment
 --    gl.BindFramebuffer(gle.READ_FRAMEBUFFER, fboIn);
 --    gl.FramebufferTexture2D(gle.READ_FRAMEBUFFER, gle.COLOR_ATTACHMENT0, gle.TEXTURE_2D, textureIn, 0);
 --    gl.ReadBuffer(gle.COLOR_ATTACHMENT0);

 --    -- Bind destination FBO + texture to another color attachment
 --    gl.BindFramebuffer(gle.DRAW_FRAMEBUFFER, fboOut);
 --    gl.FramebufferTexture2D(gle.DRAW_FRAMEBUFFER, gle.COLOR_ATTACHMENT1, gle.TEXTURE_2D, textureOut, 0);
 --    gl.DrawBuffer(gle.COLOR_ATTACHMENT1);

 --    -- specify source, destination drawing (sub)rectangles.
 --    gl.BlitFramebuffer(0, 0, width, height,
 --                        0, 0, width, height,
 --                        gle.COLOR_BUFFER_BIT, gle.NEAREST);

 --    -- unbind the color attachments
 --    gl.FramebufferTexture2D(gle.FRAMEBUFFER, gle.COLOR_ATTACHMENT1, gle.TEXTURE_2D, 0, 0);
 --    gl.FramebufferTexture2D(gle.FRAMEBUFFER, gle.COLOR_ATTACHMENT0, gle.TEXTURE_2D, 0, 0);
	-- end

  local function msaaTexture(width, height, format, samples)
    
    gl.BindTexture(gle.TEXTURE_2D_MULTISAMPLE, 0);  

    local id = ffi.new("unsigned int[1]")

    -- gl.PixelStorei(gle.UNPACK_ALIGNMENT, 1);
    gl.GenTextures(1, id);              
    gl.BindTexture(gle.TEXTURE_2D_MULTISAMPLE, id[0]);

		-- local glInternalFormat, glFormat, glType = ffi.new("unsigned int[1]"),ffi.new("unsigned int[1]"),ffi.new("unsigned int[1]")
   	-- rl.rlGetGlTextureFormats(format, glInternalFormat, glFormat, glType);
    gl.TexImage2DMultisample(gle.TEXTURE_2D_MULTISAMPLE, samples, gle.RGBA, width, height, false)
    

    -- gl.TexParameteri(gle.TEXTURE_2D_MULTISAMPLE, gle.TEXTURE_MAG_FILTER, gle.LINEAR);  
    -- gl.TexParameteri(gle.TEXTURE_2D_MULTISAMPLE, gle.TEXTURE_MIN_FILTER, gle.LINEAR);  
    gl.BindTexture(gle.TEXTURE_2D_MULTISAMPLE, 0);

    return id[0];
  end


  local function  LoadRenderTexture(width,height,depth,msaa)

		local target = ffi.new("RenderTexture2D",0)
		-- target.depthTexture = true;

    local id = ffi.new("unsigned int[1]")
    gl.GenFramebuffers(1, id);
    target.id = id[0]
    
    gl.BindFramebuffer(gle.FRAMEBUFFER, target.id);

    target.texture.id = msaa>1 and msaaTexture(width,height,7,msaa) or rl.rlLoadTexture(nil, width, height, 7, 1);
    target.texture.width = width;
    target.texture.height = height;
    target.texture.format = 7;
    target.texture.mipmaps = 1;

    if (depth) then 
				-- type(depth)~="boolean" and depth.id or
        target.depth.id =  rl.rlLoadTextureDepth(width, height, false);
        target.depth.width = width;
        target.depth.height = height;
        target.depth.format = 19;       --DEPTH_COMPONENT_24BIT?
        target.depth.mipmaps = 1;
    end
    if msaa>1 then 
    	gl.BindFramebuffer(gle.FRAMEBUFFER, target.id);
	    gl.FramebufferTexture2D(gle.FRAMEBUFFER, gle.COLOR_ATTACHMENT0, gle.TEXTURE_2D_MULTISAMPLE, target.texture.id, 0);
	    gl.BindFramebuffer(gle.FRAMEBUFFER, 0);
			-- if depth then glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, id, 0);
   --      else glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, id);
	  else
	    rl.rlFramebufferAttach(target.id, target.texture.id, 0,100);    -- COLOR attachment
	    rl.rlFramebufferAttach(target.id, target.depth.id, 1,200);      -- DEPTH attachment
	  end
   
    
    if not rl.rlFramebufferComplete(target.id) then error("render buffer not complete "..tostring(msaa)) end

    gl.BindFramebuffer(gle.FRAMEBUFFER, 0);

    return target
  end
	function gfx.newCanvas(width,height,format)
		local msaa    = math.max(math.min(8,format and format.msaa or 0),1)
		-- print("new canvas",width,height,msaa)
		-- print_info(4)
  	local canvas = {
  		width  = width,
  		height = height,
  		format  = "normal",
  		filter  = "linear",
  		msaa = msaa,
  		texture = LoadRenderTexture(width, height,true,0)
  	}

  	if msaa>1 then 
  		canvas.multiSampled = LoadRenderTexture(width, height,false,msaa)
  		msaamade= true
  	else
  		
  	end

  -- 	print("texture",canvas.texture.texture.id)
		-- print("depth",canvas.texture.depth.id)

  	canvas.matmap  =ffi.new("MaterialMap",canvas.texture.texture,ffi.new("Color",1,1,1,1))
  	canvas.material=ffi.new("Material")
  	canvas.material.maps= ffi.cast("MaterialMap*",canvas.matmap)

  	return setmetatable(canvas,canvasMT)
  end

	

  function gfx.setFilter  (tex,filter)

  	if tex then 
			setFilter(tex,filter)
		else 
			error("invalid texture")
		end
	end

  local function getTexture(self)
		return self.texture
	end
	gfx.getTexture = getTexture
	local deftex = gfx.newCanvas(1,1)
	rl.BeginTextureMode(deftex.texture)
	rl.rlClearColor(255,255,255,255)
	rl.EndTextureMode()
	local function setTexture(mesh,texture)
		if not mesh then print() print_info(5) error("NO MESH") end
		local texture = texture  or defaultCanvas or deftex
		mesh.texture = texture
		mesh.material.maps[0].texture=texture.isCanvas and texture.texture.texture or texture.texture
	end
	gfx.setTexture = setTexture
	local function getVertexCount(self)
		return self.mesh.vertexCount
	end
	local function getVertex(self,i)
		local j = (i-1)*3
		return self.mesh.vertices[j],self.mesh.vertices[j+1],self.mesh.vertices[j+2]
	end


	local function valloc(typ,n,m)
		return ffi.cast(typ.."*",ffi.C.calloc(n*m,ffi.sizeof(typ)))
	end

	function setVertices(self,verts,start,num,clear)
		local fromT, fromC = type(verts)=="table",type(verts)=="cdata"
		local start = start and start-1 or 0
		local n,t = self.vertexCount,self.triangleCount
		local num = math.min(n,num or (fromT and #verts) or n)
		local set = tonumber(self.mesh.vaoId)~=0
		
		-- print("\n",set and "SET" or "NEW", " MESH", self.mode,n,t, "--------")

		if not set then self.mesh.vaoId = 0 end

		local x,y,z,w, u,v, r,g,b,a, nx,ny,nz = 0,0,0,0, 0,0, 0,0,0,0, 0,0,0
		local offset = 1
		
		for i,attribute in ipairs(self.format) do 

			if attribute[1] == "VertexPosition" then 
				x,y,z,w=offset,offset+1,offset+2,offset+3
				offset = offset+4
			end

			if attribute[1] == "VertexTexCoord" then 
				u,v=offset,offset+1
				offset = offset+2
			end

			if attribute[1] == "VertexColor" then 
				r,g,b,a=offset,offset+1,offset+2,offset+3
				offset = offset+4
			end

			if attribute[1] == "VertexNormal" then 
				nx,ny,nz=offset,offset+1,offset+2
				offset = offset+3
			end
		end
		
		if self.mode~= "triangles" then 
			self.mesh.indices   = set and self.mesh.indices or valloc("unsigned short",t,3)  
		end

		self.mesh.vertices  = set and self.mesh.vertices or valloc("float",n,3) 
		self.mesh.texcoords = set and self.mesh.texcoords or valloc("float",n,2) 

		if r>0  then 	self.mesh.colors    = set and self.mesh.colors  or  valloc("unsigned char",n,4) end
		if nx>0 then 	self.mesh.normals   = set and self.mesh.normals or  valloc("float",n,3) end

		local toset = set and  math.min(n,start+num) or n

		local index = 0
		for i=start,toset-1,1 do 
			if self.mode == "triangles" or i<=2 then 
				-- self.mesh.indices[index] = i
				-- index=index+1
			elseif self.mode == "strip" and i>1 then 
				self.mesh.indices[index] = i-2
				index=index+1
				self.mesh.indices[index] = i-1
				index=index+1
				self.mesh.indices[index] = i
				index=index+1
			elseif self.mode == "fan" and i>0 then 
				self.mesh.indices[index] = 0
				index=index+1
				self.mesh.indices[index] = i-1
				index=index+1
				self.mesh.indices[index] = i
				index=index+1
			end
		end

		if fromT or fromC then
			for i=start+1,toset,1 do 
				local j = i-1
				self.mesh.vertices[j*3  ]=fromC and verts[j].x or verts[i][x]
				self.mesh.vertices[j*3+1]=fromC and verts[j].y or verts[i][y]
				self.mesh.vertices[j*3+2]=fromC and verts[j].z or verts[i][z]
				if u>0 then 
					self.mesh.texcoords[j*2  ]=(fromC and verts[j].u or verts[i][u]) or 0
					self.mesh.texcoords[j*2+1]=(fromC and verts[j].v or verts[i][v]) or 0
				end

				if r>0 then 
					self.mesh.colors[j*4  ]=((fromC and verts[j].r or verts[i][r]) or 1)*255
					self.mesh.colors[j*4+1]=((fromC and verts[j].g or verts[i][g]) or 1)*255
					self.mesh.colors[j*4+2]=((fromC and verts[j].b or verts[i][b]) or 1)*255
					self.mesh.colors[j*4+3]=((fromC and verts[j].a or verts[i][a]) or 1)*255
				end

				if nx>0 then 
					self.mesh.normals[j*3  ]=(fromC and verts[j].nx or verts[i][nx]) or 1
					self.mesh.normals[j*3+1]=(fromC and verts[j].ny or verts[i][ny]) or 1
					self.mesh.normals[j*3+2]=(fromC and verts[j].nz or verts[i][nz]) or 1
				end
			end
		end

		if set then 
			if self.mode~= "triangles" then 
				rl.rlUpdateMeshAt(self.mesh, 6, t, 0)
			end
			rl.rlUpdateMeshAt(self.mesh, 0, n, 0)
			rl.rlUpdateMeshAt(self.mesh, 1, n, 0)
			if nx>0 then rl.rlUpdateMeshAt(self.mesh, 2, n, 0) end
			if r>0  then rl.rlUpdateMeshAt(self.mesh, 3, n, 0) end
		else
			-- print("newmesh")
			self.mesh.vboId = ffi.cast("unsigned int*",ffi.C.calloc(ffi.new("unsigned int ",7), ffi.sizeof("unsigned int")))
			rl.rlLoadMesh(self.mesh, true)
			-- print("vaoid",tonumber(self.mesh.vaoId))
		end
		-- print("MESH COMPLETE\n")
		return toset
	end
	function setDrawRange(self,first,last)

	end
	function setVertexMap(self)

	end
	local meshMT = {__index={
		isMesh 				= true,
		_type   			= "Mesh",

		type   				= returnType,
		release 			= releaseMesh,
		
		getVertexCount= getVertexCount,
		getVertex 		= getVertex,
		getTexture    = getTexture,

		setVertices 	= setVertices,
		setDrawRange 	= setDrawRange,
		setTexture 		= setTexture,
		setVertexMap  = setVertexMap,
	}}
	
	function gfx.newMesh(format,verts,mode)
		local mode = mode or "triangles"
		local n = type(verts)=="table" and #verts or verts
		local t = mode=="triangles" and n/3 or n-2

		local mesh = {
			mesh   				= ffi.gc(ffi.new("Mesh",n,t),rl.rlUnloadMesh),
			material			= rl.LoadMaterialDefault(),
			format 				= format or zformat,
			mode   				= mode,
			vertexCount   = n,
			triangleCount = t,
  		mapping       = {},
		}	

		setmetatable(mesh,meshMT)

		setVertices(mesh,verts)

		return mesh
	end

	function gfx.quad (w,h)
		return gfx.newMesh(zformat6,{
			{0,0,0,0, 0,0, 1,1,1,1},
			{w,0,0,0, 1,0, 1,1,1,1},
			{w,h,0,0, 1,1, 1,1,1,1},

			{0,0,0,0, 0,0, 1,1,1,1},
			{w,h,0,0, 1,1, 1,1,1,1},
			{0,h,0,0, 0,1, 1,1,1,1},
		})
	end
		
	local camera3D = ffi.new("Camera3D")
  camera3D.position = vec3 ({ 0.0, 0.0, -10.0 }):cast()
  camera3D.target = vec3 ({ 0.0, 0.0, 10 }):cast()
  camera3D.up = vec3 ({ 0.0, 1.0, 0.0 }):cast()

  camera3D.fovy = 40
  camera3D.type = rl.CAMERA_PERSPECTIVE;

  local camFactor = 0.001
  local camScale = vec3(camFactor,camFactor,camFactor)
  local viewTransform = mat4()
  local defaultTransform = mat4():scale(mat4(),camScale)
  local origin = ffi.new("Vector2",0,0)
  local srcR = ffi.new("Rectangle",0,0,0,0)
  local dstR = ffi.new("Rectangle",0,0,0,0)
  local campos =vec3()
  function gfx.setCamera(camera)
  	gfx.camera = camera
  end
 	 function setCam(camera)
  	if camera then 
  		campos:set(camera.pos:scale2(camFactor))
  		camera3D.target.x,camera3D.target.y,camera3D.target.z = camera.lookDirection:sub2(campos)
  		camera3D.position.x,camera3D.position.y,camera3D.position.z = -campos.x,-campos.y,-campos.z
  		camera3D.fovy = camera.fov
  	end
		return camera3D
  end
  local function BeginMode3D(camera)
  	rl.rlglDraw();                         

    rl.rlMatrixMode(gle.PROJECTION);        
    rl.rlPushMatrix();                     
    rl.rlLoadIdentity();                   

    local aspect = screenWidth/screenHeight
    local top = 0.01*math.tan(math.rad(camera.fovy*0.5));
    local right = top*aspect;

    rl.rlFrustum(-right, right, -top, top, 0.01,5000*camFactor);
    rl.rlMatrixMode(gle.MODELVIEW);         
    rl.rlLoadIdentity();                   

    local matView = rl.MatrixLookAt(camera.position, camera.target, camera.up);
    rl.rlMultMatrixf(ffi.cast("float*",rl.MatrixToFloatV(matView)));      

    rl.rlEnableBackfaceCulling()

    rl.rlEnableDepthTest()
  end
  
  function gfx.start3D()
  	gfx.drawing3D = true 
  	BeginMode3D(setCam(gfx.camera))
  end
  
  function gfx.end3D()
  	if gfx.drawing3D then  
  		rl.rlDisableBackfaceCulling()
  		rl.EndMode3D() 
  	end
  	gfx.drawing3D = false
  end

  function gfx.draw3D(thing,transform)
  	if not gfx.drawing3D then 
  		gfx.start3D() 
  	end
  	thing.material.shader = gfx.shader.shader
		thing.material.maps[0].color = gfx.color
  	rl.rlDrawMesh(thing.mesh, thing.material, setMat(transform and viewTransform:scale(transform,camScale) or defaultTransform))
  end

  local identityMatrix = rl.MatrixIdentity()
  local tmpQuad = {x=0,y=0,w=0,h=0}
  function gfx.draw(thing, x, y, a, sx, sy, ox, oy)
  	if not thing then 
  		print_info(3)
  		error("nil drawable") 
  	end
  	-- gfx.end3D()
  	if thing.isMesh then 
  		thing.material.shader = gfx.shader.shader
  		thing.material.maps[0].color = gfx.color
		  gl.FrontFace(gle.CW)
  		rl.rlglDraw()
  		-- rl.rlLoadIdentity()
  		rl.rlPushMatrix()
  		if type(x)=="number" then 
	  		rl.rlTranslatef(x or 0,y or 0,0)
	  	end
  		if type(sx)=="number" then 
  			rl.rlScalef(sx or 1,sy or 1,1)
  		end
  		-- gfx.shader:send("custom",1)
			rl.rlDrawMesh(thing.mesh, thing.material, identityMatrix)

			-- gfx.shader:send("custom",0)
			-- rl.rlglDraw()
			rl.rlPopMatrix()
			gl.FrontFace(gle.CCW)
		elseif thing.isCanvas or thing.isImage then 
			local quad = tmpQuad
			local dx,dy,da,dsx,dsy,dox,doy
			local w,h=thing.width,thing.height
			if type(x)=="table" then 
				quad.x,quad.y,quad.w,quad.h = x.w,x.y,x.w,x.h
				dx,dy,da,dsx,dsy = y or 0, a or 0,  sx or 0, sy or 1, ox or 1
			else
				quad.x,quad.y,quad.w,quad.h = 0,0,w,h
				dx,dy,da,dsx,dsy,dox,doy = x or 0, y or 0, a or 0,  sx or 1, sy or 1, ox or 0, oy or 0
			end
			origin.x,origin.y=dox or 0,doy or 0
			srcR.x, srcR.y, srcR.width, srcR.height = quad.x,quad.y,quad.w,(gfx.canvas or thing:type()=="Image") and quad.h or -quad.h
			dstR.x, dstR.y, dstR.width, dstR.height = dx,dy,w*dsx,h*dsy
			rl.DrawTexturePro(thing.isImage and thing.texture or thing.texture.texture, srcR, dstR, origin, da, gfx.color)
		end
	end

	local function getViewport (self)
		return self.x,self.y,self.w,self.h
	end

	local function getReferenceDimensions(self)
		return self.rw,self.rh
	end

	local quadMT = {__index={
		isQuad 				= true,
		_type   			= "Quad",

		type   				= returnType,
		getViewport   = getViewport,
		getTextureDimensions   = getReferenceDimensions,
	}}

	function gfx.newQuad(x,y,w,h,rw,rh)
		quad = {
			x=x,y=y,w=w,h=h,rw=rw,rh=rh,
		}
		setmetatable(quad,quadMT)
		return quad 
	end

	local rec = ffi.new("Rectangle",0,0,0,0)
	function gfx.rectangle(style,x,y,w,h,rx,ry,segs)
		rl.rlDisableDepthTest()
		rec.x, rec.y, rec.width, rec.height = x,y,w,h
		local round = rx and (rx/math.min(w,h))*1.7 or 0
		local segs = segs or 4
		if style=="fill" then 
			if round>0 then 
				rl.DrawRectangleRounded(rec, round, segs, gfx.color);               
			else
				rl.DrawRectangle(x,y,w,h, gfx.color)
			end
		else
			if round>0 then 
		    rl.DrawRectangleRoundedLines(rec, round, segs, gfx.lineWidth, gfx.color); 
		  else
				rl.DrawRectangleLinesEx(rec, gfx.lineWidth, gfx.color);   
			end
		end
	end

	function gfx.circle(style,x,y,r)
		if style == "fill" then 
			-- rl.DrawCircle(x,y,r,gfx.color)
			rl.DrawEllipse(x,y, r,r,  gfx.color); 
		else
	    rl.DrawCircleLines(x,y,r,gfx.color)
	  end
	end

	local arcCenter = ffi.new("Vector2")
	function gfx.arc(style,mode,x,y,r,a1,a2,s)
		arcCenter.x,arcCenter.y = x,y
		if style=="line" then 
	    rl.DrawCircleSectorLines(arcCenter, r,math.deg(a1), math.deg(a2), s, gfx.color)
	    if mode~="open"  then 
	    	--line
	    end
	  else
			rl.DrawCircleSector(arcCenter, r,a1, a2, s, gfx.color)
		end
	end

	function gfx.line(...)
		local line  = {...}
		local linev = ffi.new("Vector2[?]",#line/2)
		for i=0,#line/2-1,1 do 
			linev[i].x=line[i*2+1]
			linev[i].y=line[i*2+2]
		end

		if #line>4 then 
			rl.DrawLineStrip(linev, #line/2, gfx.color)
		else
			rl.DrawLineEx(linev[0], linev[1], gfx.lineWidth, gfx.color); 
		end
	end

	function gfx.polygon()
	end


	function gfx.getWidth()
		return rl.GetScreenWidth()
	end
	function gfx.getHeight()
		return rl.GetScreenHeight()
	end
	function gfx.isWireframe()
		return gfx.wireframe
	end
	function gfx.setWireframe(set)
		gfx.wireframe = set
		if set then 
		 rl.rlEnableWireMode()
		else
			rl.rlDisableWireMode()
		end
	end
	function gfx.translate(x,y,z)
		rl.rlTranslatef(x,y,z or 0)
	end
	function gfx.rotate(a)
		rl.rlRotatef(math.deg(a),0,0,1)
	end
	function gfx.scale(x,y,z)
		rl.rlScalef(x,y,z or 0)
	end
	function gfx.getCanvas()
		return gfx.canvas
	end
	function gfx.setCanvas(canvas)
		gfx.end3D()
		if gfx.canvas and gfx.canvas.texture and gfx.canvas.msaa>1 then 
			if game.state =="Edit" then 
				-- rl.BeginTextureMode(gfx.canvas.texture)
				-- gfx.clear(1,0,1,1)
				-- rl.BeginTextureMode(gfx.canvas.texture)
				-- gfx.clear(1,0,1,1)
			end
		end
		
		if gfx.canvas and gfx.canvas.texture and gfx.canvas.msaa>1 then 
			-- local err = gl.GetError() if err>0 then print_info(5) error(string.format("GL ERROR %i",err)) end
			rl.rlglDraw();       
	    rl.rlDisableFramebuffer();       
	    rl.EndTextureMode()
			-- encode(rl.GetTextureData(gfx.canvas.texture.texture),"tga","test.tga")
			gl.BindFramebuffer(gle.READ_FRAMEBUFFER, gfx.canvas.multiSampled.id);
			gl.BindFramebuffer(gle.DRAW_FRAMEBUFFER, gfx.canvas.texture.id );
			gl.BlitFramebuffer(
			    0, 0, gfx.canvas.width, gfx.canvas.height, 
			    0, 0, gfx.canvas.width, gfx.canvas.height, 
			    gle.COLOR_BUFFER_BIT, gle.LINEAR);
			-- print(gl.CheckFramebufferStatus(gle.READ_FRAMEBUFFER),gl.CheckFramebufferStatus(gle.DRAW_FRAMEBUFFER))
			gl.BindFramebuffer(gle.READ_FRAMEBUFFER, 0);       
			gl.BindFramebuffer(gle.DRAW_FRAMEBUFFER, 0);
			gl.BindFramebuffer(gle.FRAMEBUFFER, 0);

		else
			rl.EndTextureMode()
		end
		gfx.canvas = nil
		if canvas and canvas.texture then 
			if canvas.msaa>1 then 
				rl.BeginTextureMode(canvas.multiSampled)
				-- gl.Enable(gle.MULTISAMPLE)
			else
				rl.BeginTextureMode(canvas.texture)
			end
			gfx.canvas = canvas
		end
  end
  local clearCol = ffi.new("Color",0,0,0,255)
  function gfx.clear(r,g,b,a,s,z)
  	local cr,cg,cb,ca 
  	if type(r)=="table" then 
  		cr,cg,cb,ca =r[1] or 0,r[2] or 0,r[3] or 0,r[4] or 0
  	else
  		cr,cg,cb,ca = r or 0,g or 0,b or 0,a or 1
  	end
  	-- if gfx.getCanvas() then 
	  	if z then 
	  		rl.rlClearScreenBuffers()
	  	end
  		rl.rlClearColor(cr*255,cg*255,cb*255,ca*255)
	  -- else
	  	clearCol.r,clearCol.g,clearCol.b,clearCol.a = cr*255,cg*255,cb*255,ca*255
	  	rl.ClearBackground(clearCol)
	  -- end
  end
  
  function gfx.setDepthMode(mode,write)
  	if not mode then 
  		rl.rlDisableDepthTest()
  	else
	  	rl.rlEnableDepthTest()
	  end
  end
  function gfx.setDepthTest()

  end
 
  function gfx.isCanvas(canvas)
		return type(canvas)=="table" and canvas.isCanvas
	end

	function gfx.isTexture(tex)
		return type(tex)=="table" and (tex.isCanvas or tex.isImage)
	end

	gfx.pushed = 0
  gfx.stack={}
  function gfx.push()
   -- local info = debug.getinfo(2, "Sl")
   -- print(string.rep(".",gfx.pushed)..string.format("[%s]:%d",info.short_src, info.currentline))
    gfx.pushed = gfx.pushed+1
    -- print("push "..gfx.pushed)
    gfx.stack[gfx.pushed] = gfx.stack[gfx.pushed] or {} 
    gfx.stack[gfx.pushed].wireframe = gfx.isWireframe()
    -- gfx.stack[gfx.pushed].compareMode, gfx.stack[gfx.pushed].write = gfx.getDepthTest()
    gfx.stack[gfx.pushed].blendMode, gfx.stack[gfx.pushed].alphaMode = gfx.getBlendMode()
    gfx.stack[gfx.pushed].canvas = gfx.getCanvas()
    gfx.stack[gfx.pushed].shader = gfx.getShader()

    gfx.stack[gfx.pushed].r,
    gfx.stack[gfx.pushed].g,
    gfx.stack[gfx.pushed].b,
    gfx.stack[gfx.pushed].a 
    	= gfx.getColor()
    
    rl.rlPushMatrix()
  end

  function gfx.pop(...)
    -- if gfx.pushed>0 then 
    	if gfx.pushed==0 then 
    		print()
    		print(#gfx.stack,gfx.pushed)
    		print_info(4) 
    		print()
    		return
    	end
    	rl.rlPopMatrix()
      -- gfx.setDepthTest(gfx.stack[gfx.pushed].compareMode, gfx.stack[gfx.pushed].write)
      gfx.setCanvas(gfx.stack[gfx.pushed].canvas)
      gfx.setShader(gfx.stack[gfx.pushed].shader)
      gfx.setWireframe(gfx.stack[gfx.pushed].wireframe)
      gfx.setBlendMode(gfx.stack[gfx.pushed].blendMode, gfx.stack[gfx.pushed].alphaMode )
      gfx.setColor(
        gfx.stack[gfx.pushed].r,
        gfx.stack[gfx.pushed].g,
        gfx.stack[gfx.pushed].b,
        gfx.stack[gfx.pushed].a 
      )
      -- print("pop "..gfx.pushed)
      lume.clear(gfx.stack[gfx.pushed])
      gfx.stack[gfx.pushed] = nil
      gfx.pushed = gfx.pushed-1
      -- local info = debug.getinfo(2, "Sl")
   -- print(string.rep(".",gfx.pushed)..string.format("[%s]:%d",info.short_src, info.currentline))
      -- if game.state == "Edit" then error("d", 2) end
    -- end
  end

  function gfx.getStackDepth()
    return gfx.pushed
  end

	gfx.defaultVert=[[
    #version 330
		
		// Input vertex attributes
		in vec3 vertexPosition;
		in vec2 vertexTexCoord;
		in vec3 vertexNormal;
		in vec4 vertexColor;

		// Input uniform values
		uniform mat4 mvp;
		uniform mat4 MVP=mat4(1);
		uniform float custom = 0;

		// Output vertex attributes (to fragment shader)
		out vec2 fragTexCoord;
		out vec4 fragColor;
		out vec2 screenSpace;

		// NOTE: Add here your custom variables 

		void main()
		{
		    // Send vertex attributes to fragment shader
		    fragTexCoord = vertexTexCoord;
		    fragColor = vertexColor;
		    
		    // Calculate final vertex position
		    mat4 mat2use = custom>0.5 ? MVP : mvp;
		    vec4 clipSpace =  mat2use*vec4(vertexPosition, 1.0);
		    screenSpace = clipSpace.xy;
		    gl_Position = clipSpace;
		}
  ]]
  
  gfx.defaultFrag=[[
    #version 330

		// Input vertex attributes (from vertex shader)
		in vec2 fragTexCoord;
		in vec4 fragColor;


		// Input uniform values
		uniform sampler2D texture0;
		uniform vec2 screenSize;

		// Output fragment color
		out vec4 finalColor;

		// NOTE: Add here your custom variables

		void main()
		{
		    // Texel color fetching from texture sampler
		    vec4 texelColor = texture(texture0, fragTexCoord);
		    
		    // NOTE: Implement here your fragment shader code
		    vec4 frag = texelColor*fragColor;
		    finalColor = vec4(pow(frag.rgb, vec3(1)),frag.a);
		}
  ]]


	function gfx.setDefaultFilter (filter)
		gfx.defaultFilter = filter
	end


	ffi.cdef[[
		int printf(const char *fmt, ...);
	]]


	local uniformType = {0,rl.UNIFORM_VEC2,rl.UNIFORM_VEC3,rl.UNIFORM_VEC4}
	local intptr = ffi.new("int[4]")
	local floatptr = ffi.new("float[4]")
	local transposedMat = mat4()
	local function sendUniform(self,...)
    local args = {...}
  	local name,value,third = args[1],args[2],args[3]
  	if value=="column" then 
  		value = third
  	end
    -- print_info(3)
    local loc = rl.GetShaderLocation(self.shader, name)
    if loc>=0 then 
    	local typ = type(value)
    	if typ == "number" then 
    		floatptr[0] = value
    		rl.SetShaderValue(self.shader, loc, floatptr, rl.UNIFORM_FLOAT)
      elseif typ == "table" then 
		    -- print("SEND")
		    -- print(tostring(name),type(value)=="table" and #value or tostring(value))
      	if mat4.is_mat4(value) then 
	      	rl.SetShaderValueMatrix(self.shader, loc, setMat(value))
	      	-- print("sentmat")
      	elseif type(value[1])=="number" then 
        	-- print("set vec4",name,value)
      		floatptr[0]=value[1]
      		floatptr[1]=value[2]
      		floatptr[2]=value[3] or 0
      		floatptr[3]=value[4] or 0
        	rl.SetShaderValue(self.shader, loc, floatptr, uniformType[#value])
        elseif type(typ[1])=="table" then 
        	rl.SetShaderValueV(self.shader, loc, valptr, uniformType[#value[1]],#value)
        end
      elseif typ=="boolean" or typ=="nil" then 
      	intptr[0] = value and 1 or 0
				rl.SetShaderValue(self.shader, loc, intptr ,rl.UNIFORM_INT)

      elseif typ=="cdata" then 
      
      else

      end
    else 
      -- print("No Uniform \""..name.."\" found")
    end
  end



	function gfx.newShader(frag,vert,options)
		local loaded = rl.LoadShaderCode(vert,frag)
		if loaded.id~=rl.GetShaderDefault().id then 
	    return {
	      shader = loaded,
	      send  = sendUniform
	    }
	  else
	  	rl.SetTraceLogLevel(0)
	  	print()
	  	print("------------------------")
	  	print("    failed to compile "..(options and options.name or ""))
	  	print("------------------------")
	  	rl.LoadShaderCode(vert,frag)
	  	-- print(frag)
	  	-- print(vert)
	  	-- error("-")
	  	rl.SetTraceLogLevel(7)
	  end
  end



  function gfx.getShader()
  	return gfx.shader
  end

  local identity = mat4():identity()
  local screenMat = mat4():identity()
  local tmpMat   = ffi.new("Matrix")
  function gfx.setShader(shader)
    -- print("type",tostring(shader))
    -- print(inspect(shader))
  	if not shader and screenWidth then 
  		screenMat:identity()
  		screenMat:scale(screenMat,vec3(2/(screenWidth),-2/(screenHeight),1))
  		screenMat:translate(screenMat,vec3(-screenWidth/2,-screenHeight/2,0))
  		gfx.defaultShader:send("MVP",screenMat)
  	end
    shader = shader or gfx.defaultShader
    -- shader:send("screenSize",{screenWidth,screenHeight})
  	rl.EndShaderMode() 
  	rl.BeginShaderMode(shader.shader)
  	gfx.end3D()
  	gfx.shader = shader
  end

  print("default shader")
  gfx.defaultShader = gfx.newShader(gfx.defaultFrag,gfx.defaultVert,{name="default"})
  gfx.setShader()

  local defaultCanvas = gfx.newCanvas(1,1)
  rl.BeginTextureMode(defaultCanvas.texture)
  gfx.clear(1,1,1,1)
  rl.EndTextureMode()
	local matmap  =ffi.new("MaterialMap",defaultCanvas.texture.texture,ffi.new("Color",255,255,255,255))
	local material=ffi.new("Material",gfx.defaultShader.shader,matmap)

	gfx.color = ffi.new("Color",1,1,0,0.2)
	function gfx.setColor(r,g,b,a)
		local r,g,b,a = r,g,b,a 
		if type(r)=="table" then 
			r,g,b,a = r[1],r[2],r[3],r[4]
		end
		if not r then return end
		gfx.color.r = r*255
		gfx.color.g = g*255
		gfx.color.b = b*255
		gfx.color.a = a*255
	end
	

	function gfx.getColor()
		return gfx.color.r/255,gfx.color.g/255,gfx.color.b/255,gfx.color.a/255
	end

	function gfx.newFont(file,size)
		return rl.LoadFontEx(file,size or gfx.currentFontSize,ffi.cast("int*",ffi.new("int[1]")[0]),250)
	end

	local inconsolata = rl.LoadFontEx("fonts/inconsolata.ttf",12,ffi.cast("int*",ffi.new("int[1]")[0]),250)

	gfx.currentFontSize = 12

	local function getTextWidth(self,text)
		return rl.MeasureTextEx(self.sizes[gfx.currentFontSize],text, gfx.currentFontSize,0).x
	end

	local function getFontHeight(self)
		return gfx.currentFontSize
	end

	function gfx.setFont(font,size)
		local size = math.floor(size)
		if not font.sizes[size] then 
			font.sizes[size] = gfx.newFont(font.file,size)
			-- print(font.file,size)
		end
		gfx.currentFont=font
		gfx.currentFontSize = size
		font.getWidth = getTextWidth
		font.getHeight = getFontHeight
	end


	gfx.setFont(fonts.inconsolata,12)

	function gfx.getFont()
		return gfx.currentFont.sizes[gfx.currentFontSize]
	end

	function gfx.fontSize(size)
		if gfx.currentFontSize ~= size then 
			gfx.currentFontSize = size
			gfx.setFont(gfx.currentFont,size)
		end
	end

	local mf = math.floor
	local textPos = vec2(0,0):cast()
	local ffiStrings = {}
	local str = ffi.new("char[?]",1000)
	local sze = ffi.new("float[1]",10)
	local spc = ffi.new("float[1]",0)
	local char = ffi.sizeof("char")

	function gfx.print(text, x, y, angle, sx, sy)
		local text = tostring(text)
		textPos.x,textPos.y=mf(x or 0),mf(y or 0)
		ffi.fill(str,#text*char)
		ffi.copy(str, text)
		sze[0]=gfx.currentFontSize
		rl.DrawTextEx(gfx.currentFont.sizes[gfx.currentFontSize],text,textPos,sze[0],spc[0],gfx.color)
	end

	local textSize = ffi.new("Vector2")
	function gfx.printf(text,x,y,w,align)
		local text = tostring(text)
		textPos.x,textPos.y=mf(x or 0),mf(y or 0)
		ffi.fill(str,#text*char)
		ffi.copy(str, text)
		sze[0]=gfx.currentFontSize
		local font = gfx.currentFont.sizes[gfx.currentFontSize]
		if align == "center" then 
			textSize = rl.MeasureTextEx(font, text, sze[0], spc[0])
			textPos.x = textPos.x + (w-textSize.x)/2
		end
		rl.DrawTextEx(font,text,textPos,sze[0],spc[0],gfx.color)
	end

	function gfx.setLineJoin()
	end
	gfx.lineWidth = 1
	function gfx.setLineWidth(width)
		gfx.lineWidth = width
  end
  function gfx.setLineStyle()
  end

  local cullModes = {front=gle.FRONT,back=gle.BACK,none=gle.NONE}
  function gfx.setMeshCullMode(mode)
  	if not cullModes[mode] then error("invalid cull mode") end
  	gfx.meshCullMode = cullModes[mode]
  	if mode=="none" then 
			gl.Disable(gle.CULL_FACE)
  	else 
  		gl.Enable(gle.CULL_FACE)
	  	gl.CullFace(gfx.meshCullMode)
	  end
  end

  gfx.scissor = {}
  function gfx.setScissor(x,y,width,height)
  	if  x then 
  		gfx.scissor.x,gfx.scissor.y,gfx.scissor.width,gfx.scissor.height = x,y,width,height
			rl.BeginScissorMode(x,y,width,height)
  	else
  		rl.EndScissorMode()
  	end
  end

  function gfx.getScissor()
  	return gfx.scissor.x,gfx.scissor.y,gfx.scissor.width,gfx.scissor.height
  end

	function gfx.reset ()
		rl.rlLoadIdentity()
    gfx.setCanvas()
    gfx.setShader()
    gfx.setColor(1,1,1,1)
    rl.BeginBlendMode(0)
	  rl.EndBlendMode()
  end

  function gfx.setBlendMode(mode1,mode2)
  	local mode =0
  	if type(mode1)=="number" then 
  		mode = mode1	
  	elseif mode2=="alphamultiply" then 
  		mode = 2
  	elseif mode2=="premultiplied" then 
  		mode = 3
  	else
  		mode = 0
  	end
  	rl.BeginBlendMode(mode)
  end

  function gfx.getBlendMode()
  	
  end


  -- FILESYSTEM------------------------------------------------------------------
  local bytesRead = ffi.new "int [1]"

  function lfs.isFused()
  	
  end

  function lfs.getLastModified(dir)
  	return 0
  end

  function lfs.remove(dir)

  end

	function lfs.getUserDirectory()
		os.getenv ( "HOME" )
	end

	function lfs.getSaveDirectory()
		local ext = 
			MAC and "/Library/Application Support/Project Engine/" or  
			(WINDOWS and "" or 
			LINUX and "")
		return os.getenv ( "HOME" )..ext
	end

	function lfs.getSource()
		return tostring(rl.GetWorkingDirectory())
	end

	function lfs.write(dir,string)
		loader:writeFile(lfs.getSaveDirectory()..dir,string)
	end

	function lfs.createDirectory(dir)
		print("mkdir",lfs.getSaveDirectory()..dir)
		fs.mkdir(lfs.getSaveDirectory()..dir,true)
	end

	local function checkFile(func,dir,arg)
		if fs.is(lfs.getSaveDirectory()..dir) then 
			return 	func(lfs.getSaveDirectory()..dir,arg)
		elseif rl.FileExists(dir) then 
			return 	func(dir,arg)
		end
	end

	function lfs.getDirectoryItems(dir)
		local t = {}
		print("dir",lfs.getSaveDirectory()..dir)
		for name,d in fs.dir(lfs.getSaveDirectory()..dir) do 
			if not name then
		    print('error: ', d)
		    break
		  end
			table.insert(t,d:name())
		end
		print("dir items")
		print(inspect(t))
		return t 
	end

  function lfs.read(dir)
  	if fs.is(lfs.getSaveDirectory()..dir) then 
  		print("read file fs")
  		local file = io.open(lfs.getSaveDirectory()..dir)
			local str = file:read("*a") 
			file:close()
			return str
		else
			print("read file rl")
			return ffi.string(rl.LoadFileData(dir,bytesRead))
		end
  end

	function lfs.getSize(dir)
		return fs.attr(lfs.getSaveDirectory()..dir,"size") or 1
	end

	function lfs.exists  (dir)
		return fs.is(lfs.getSaveDirectory()..dir)
	end

	function lfs.isFile  (dir)
		return fs.is(lfs.getSaveDirectory()..dir,"file")
	end

	function lfs.isDir  (dir)
		return not fs.is(lfs.getSaveDirectory()..dir,"file")
	end

	function lfs.load(dir)
		return loadfile(lfs.getSaveDirectory()..dir)
	end

	if pcall(require,"release") then 
		developer = false
	end


	-- TIMER
	function ltimer.getTime()
		return rl.GetTime()
	end
	function ltimer.getFPS()
		return rl.GetFPS()
	end

	-- JOYSTICKS
	function ljoystick.getJoystickCount()
		return 0
	end

	function ljoystick.getJoysticks()

	end

	-------------------------------------------------------------
	path1 = "/path/to/ProjectName/engine/"
	path2 = fs.tmpdir()--"/path/to/Library/Application Support/LOVE/ProjectApp/"
	print("path2",path2)

	function maketestmesh()
		
	end


	function engine.render()
		

	    gfx.setCanvas()
	    gfx.setShader()

	    rl.ClearBackground(rl.BLUE)
	    -- gfx.rectangle("fill",0,0,screenWidth,screenHeight)
	    engine.draw()
	    -- gfx.setColor(1,0,0,1)
	    -- gfx.circle("line",100,100,10)

	end
	engine.run = function(args)

		engine.load(args)

		collectgarbage('restart')

		while not (rl.WindowShouldClose()) do    -- Detect window close button or ESC key
			if rl.IsWindowResized() then 
				engine.resize(rl.GetScreenWidth(),rl.GetScreenHeight())
			end

			engine.update(rl.GetFrameTime())
			
			if cmdDown and keypressed("q") then break end
			
			rl.BeginDrawing()
			engine.render()
			rl.EndDrawing()
		end
		engine.quit()
		rl.CloseWindow()   
	end
end

MVPmat = mat4()

lmouse.getPressure=lmouse.getPressure or function() return 1 end



function canRelease(thing)
	return type(thing) =="userdata" and (thing:type()=="Image" or thing:type()=="Mesh" or thing:type()=="Canvas")
end

	local function queue(v2,v3,v4,v5,v6,v7,v8,v9,v10)
		local t = drawFuncsUI[drawFuncsUI[0]]
		t[2],t[3],t[4],t[5],t[6],t[7],t[8],t[9],t[10]=
		v2,v3,v4,v5,v6,v7,v8,v9,v10
	end
	gfx_copy = gfx
	function pushPost ()

		if postingDraws then return end
		postingDraws = true
		gfx_copy = gfx
		local graphics = gfx_copy 
		-- table.insert(drawFuncsUI,{f=graphics.setBlendMode,a={"alpha"}})
		gfx= setmetatable({}, {
		  __index = function(mytable, key)
		  	local value = rawget(graphics,key)
		  	if type(value)=="function" then 
			  	-- print(key,value)
			  	-- local info = debug.getinfo(2, "Sl")
			  	drawFuncsUI[0] = drawFuncsUI[0] + 1
			  	drawFuncsUI[drawFuncsUI[0]] = drawFuncsUI[drawFuncsUI[0]]  or {}
			   	drawFuncsUI[drawFuncsUI[0]][1]=value --,s=info.short_src,l=info.currentline})
		    	return queue
		   	else 
		   		return value
		    end
		  end
		})
	end

	function popPost() 
		postingDraws = false
		gfx = gfx_copy 
	end



useData     = ffi and useFFI 
----------------------------------------------------------------------------------
gfx.currentFontSize = 12
gfx.currentFontName = fonts.inconsolata

print("path1",path1)
print("path2",path2)

_canvases={}
_texmem = 0
function newCanvas (w,h,s)
	-- print("new canvas")
	-- print_info(3)
	if s=="vector" then return end
	local tex,canv 

	if type(s)=="string"  then 
		s={format=s}
	end
	if LOVR then

	else 
		if type(s)=="table" then 
			s.format = (s.format=="normal" or s.format=="normal") and  "rgba16" or s.format
		else
			s = (s=="normal" or s=="normal") and  "rgba16" or s
		end
	end
	canv = gfx.newCanvas(w,h,s)
	-- table.insert(_canvases,canv)
	-- print("          ",canv)
	local sizes = {normal = 32, rgb565=16, rgba16f=64, depth16=16}
	local fmt = s and s.format or "normal"
	local size = sizes[fmt] or 32
	-- print("px",w,h,fmt,(w*h*size)/(1024^2).."mb")
	_texmem = _texmem +(w*h*size)/(1024^2)
	return canv
end

function file_exists(file)
	if love then return (lfs.isFile(file) ~= nil) end

	local f = io.open(file, "r")
	if f then f:close() end
	return f ~= nil
end


function q(str,strip)
	local str2 = str:gsub("//","/")
	if str2:sub(1,1) =='"' then 
		if strip then 
			return str2:sub(2,#str2-1)
		else
			return str2 
		end
	elseif not strip then  
    return '"'..str2..'"'
  else 
  	return str2 
  end
end

-- // Assumes the two textures are the same dimensions
-- void copyFrameBufferTexture(int width, int height, int fboIn, int textureIn, int fboOut, int textureOut)
-- {
--     // Bind input FBO + texture to a color attachment
--     glBindFramebuffer(GL_READ_FRAMEBUFFER, fboIn);
--     glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureIn, 0);
--     glReadBuffer(GL_COLOR_ATTACHMENT0);

--     // Bind destination FBO + texture to another color attachment
--     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboOut);
--     glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, textureOut, 0);
--     glDrawBuffer(GL_COLOR_ATTACHMENT1);

--     // specify source, destination drawing (sub)rectangles.
--     glBlitFramebuffer(0, 0, width, height,
--                         0, 0, width, height,
--                         GL_COLOR_BUFFER_BIT, GL_NEAREST);

--     // unbind the color attachments
--     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
--     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
-- }