raytester = dgeomray.new(10000)
raycollresult = dcollresult.new()
function raymousetest(geom,dist)
	local x,y,z = l3dcamera.default():pos()
	local dx,dy,dz = l3dcamera.default():toworld(mouse.x,mouse.y,1)
		
	dx,dy,dz = dx-x,dy-y,dz-z
	raytester:set(x,y,z,dx,dy,dz)
	raytester:length(dist or 10000)
	--local count,pos,normal,depth,geoms = raytester:test(geom)
	local count = raycollresult:test(raytester,geom)
	
	local hits = {}
	for i = 0,count-1 do 
		local x,y,z = raycollresult:pos(i)
		local nx,ny,nz,d =raycollresult:normal(i)
		local g1,g2 = raycollresult:geoms(i)
		
		local data = {
			x = x, y = y, z = z,
			nx = nx, ny = ny, nz = nz,
			d = d, geom = (g1 == raytester and g2 or g1),
		}
--		_print(data.d,luxinia.dataID(data.geom),luxinia.dataID(raytester))
		table.insert(hits,data)
	end
	
	return hits
end

function raytest(geom,x,y,z,dx,dy,dz,len)
	raytester:set(x,y,z,dx,dy,dz)
	raytester:length(len or 10000)
	local count = raycollresult:test(raytester,geom)
	local pos = {}
	local normal = {}
	local depth = {}
	local geoms = {}
	
	for i = 0,count-1 do 
		local x,y,z = raycollresult:pos(i)
		local nx,ny,nz,d =raycollresult:normal(i)
		local g1,g2 = raycollresult:geoms(i)
		
		table.insert(pos,x)
		table.insert(pos,y)
		table.insert(pos,z)
		
		table.insert(normal,nx)
		table.insert(normal,ny)
		table.insert(normal,nz)
		
		table.insert(depth,d)
		
		table.insert(geoms,g1)
		table.insert(geoms,g2)
	end
	
	return count,pos,normal,depth,geoms
end

specialmapobjects = {}
function specialmapobjects.laterne (self,x,y,z) 
	x,y,z = x or 0,y or 0, z or 30
	local psys = particlesys.load("laternenfall.prt")
	local em = l3dpemitter.new("laterne",lset:layer(topviewoffset),psys)
	local s3d = scenenode.new("",true)
	s3d:parent(self.s3d)
	--s3d = self.s3d
	em:linkinterface(s3d)
	em:uselocal(true)
	em:localpos(x,y,z)
	em:localrotdeg(-90,0,0)
	if (gameconfig.weather>=2) then em:start(0) end
	self.emitters = self.emitters or {}
	table.insert(self.emitters,em)
	
	self.l3d:rfLitSun(false)
	--self.l3d:rfNodepthmask(true)
	self.l3d:rfLitFX(false)
	--self.l3d:rfNodepthmask(true)
	self.l3d:setlayer(lset:layer(topviewoffset+2))

	if (gameconfig.lightning > 1) then
		local l = l3dlight.new("",lset:layer(1))	
		l:activate(0,200,0)
		l:ambient(0,0,0,0)
		l:diffuse(40,40,14,0)
		l:attenuateconst(1)
		l:attenuatequadratic(0.03)
		l:attenuatelinear(0.07)
		table.insert(self.lights,l)
		l:linkinterface(self.s3d)
		l:uselocal(true)
		l:localpos(x,y,z)
	end
end
specialmapobjects.laterne2 = specialmapobjects.laterne

function specialmapobjects.house1 (self)
	specialmapobjects.laterne(self,10,55,30)
	self.l3d:setlayer(lset:layer(1))
	self.l3d:rfLitSun(true)
--	self.l3d:rfNodepthmask(true)
	self.l3d:rfLitFX(true)
	local em = l3dpemitter.new("rauch",lset:layer(3),mapobject.chimneypsys)
	local s3d = scenenode.new("",true)
	s3d:parent(self.s3d)
	--s3d = self.s3d
	em:linkinterface(s3d)
	em:uselocal(true)
	em:localpos(-20,-45,90)
	em:localrotdeg(90,0,0)
	em:start(0)
	self.emitters = self.emitters or {}
	table.insert(self.emitters,em)
end

mapobject = {name="mapobject", 
	chimneypsys = particlesys.load("chimney.prt"),
	chimneyhappy = particlesys.load("chimneyhappy.prt") }
	
mapobject.chimneypsys:drawlayer(lset:layer(3))
mapobject.chimneyhappy:drawlayer(lset:layer(3))

mapobject.unlitlist = { baum =true, baum1 = true }
function mapobject.new (map,mdl,x,y,z,rx,ry,rz)
	local self = {}
	setmetatable(self,{__index = mapobject})
	self.orig = {x,y,z,rx,ry,rz}
	self.x,self.y,self.z = math.floor(x/config.quadsize+0.5),math.floor(y/config.quadsize+0.5),0
	self.rx,self.ry,self.rz = rx,ry,rz
	self.lx,self.ly,self.lz = x-self.x,y-self.y,z
	self.s3d = scenenode.new("",true)
	self.s3d:localrotdeg(rx/math.pi*180,ry/math.pi*180,rz/math.pi*180)
	self.l3d = l3dmodel.new("",lset:layer(1),mdl.mdl,false,false)
	self.l3d:linkinterface(self.s3d)
	if (mapobject.unlitlist[mdl.short]~=true) then
		self.l3d:rfLitSun(true)
	end
	self.l3d:rfLitFX(gameconfig.lightning>=3)
	self.l3d:projectorflag(1,true)
	self.lights = {}
	self.map = map
	self.mdl = mdl
	self.geom = dgeomtrimesh.new(mdl.colmesh.data)
	self.map.geomlut[luxinia.dataID(self.geom)] = {self,mdl.colmesh}
	self.geom:pos(x,y,z)
	self.geom:rotrad(rx,ry,rz)
	map.space:add(self.geom)
	if (mdl.hitmeshes) then
		self.hitgeoms = {}
		self.emitters = {}
		self.hudpointlist = {}
		for i,v in ipairs(mdl.hitmeshes) do
			map.hitzones = (map.hitzones or 0) + 1
			local x,y,z = unpack(v.center)
			local em = l3dpemitter.new("hitzone1",lset:layer(3),self.chimneypsys)
			local s3d = scenenode.new("",true)
			s3d:parent(self.s3d)
			--s3d = self.s3d
			em:linkinterface(s3d)
			em:uselocal(true)
			em:localpos(x,y,z)
			em:localrotdeg(90,0,0)
			if (gameconfig.weather>=2) then em:start(0) end
			table.insert(self.emitters,em)
			local em = l3dpemitter.new("happyzone1",lset:layer(3),self.chimneyhappy)
			em:linkinterface(s3d)
			em:uselocal(true)
			em:localpos(x,y,z)
			em:localrotdeg(90,0,0)
			
			local point = gamehud.addpoint(self.s3d:transform(x,y,0))
			table.insert(self.hudpointlist,{point,x,y})
			
			local geom = dgeomtrimesh.new(v.data)
			table.insert(self.hitgeoms,geom)
			geom:pos(x,y,z)
			geom:rotrad(rx,ry,rz)
			self.map.geomlut[luxinia.dataID(geom)] = {self,v}
			self.map.hitgeomlut[luxinia.dataID(geom)] = {self,v,em,point}
			map.space:add(geom)
		end
	end
	

	local node = self:autopos()
	if (specialmapobjects[self.mdl.short]) then specialmapobjects[self.mdl.short](self) end
	if (node) then
		node.objects[self] = true
		return self
	else
		self:delete()
	end
end

function mapobject.delete(self)
	if(self.deleted) then return end
	self.deleted = true
	self.s3d:delete(true)
	self.l3d:delete()
	self.map.geomlut[luxinia.dataID(self.geom)] = nil
	self.geom:delete()

	if (self.hitgeoms) then
		for i,v in ipairs(self.hitgeoms) do
			v:delete()
			self.map.hitzones = (self.map.hitzones or 0) - 1
		end
		for i,v in ipairs(self.hudpointlist)
			do v[1]:delete() end
	end
	for a,b in pairs(self.lights) do 
		b:delete() 
	end
	if (self.emitters) then
		for a,b in pairs(self.emitters) do
			b:delete()
		end
	end
end	

function mapobject.autopos(self)
	if(self.deleted) then return end
	local node = self.map:get(self.x,self.y)
	if (not node) then
		self.s3d:localpos(1200102,12001021,12001201)
		return false
	else
		local cnt,pos = raytest(node.geom, self.x+self.lx,self.y+self.ly,1000, 0, 0,-1)
		if (cnt>0) then
			self.s3d:localpos(pos[1],pos[2],pos[3]+self.lz)
			self.geom:pos(pos[1],pos[2],pos[3]+self.lz)
			if (self.hitgeoms) then
				for i,v in ipairs(self.hitgeoms) do
					v:pos(pos[1],pos[2],pos[3]+self.lz)
				end
				for i,v in ipairs(self.hudpointlist) do
					v[1].x,v[1].y = self.geom:local2world(v[2],v[3],0)
				end
			end
			return node
		end
		return false
		--self.s3d:lo
	end
end

mapnode = {name="mapnode"}
function mapnode.new(level,id,x,y,z)
	if (not levelnodes.nodeids[id]) then return end
	local self = {}
	self.x,self.y,self.z = x,y,z
	self.id = id
	self.level = level
	self.objects = {}
	self.lnode = levelnodes.nodeids[id]
	self.s3d,self.l3d = self.lnode:create(x,y,z)
	self.geom = dgeomtrimesh.new(self.lnode.colmesh)
	level.geomlut[luxinia.dataID(self.geom)] = {self}
	--self.geom = dgeombox.new(config.quadsize,config.quadsize,10)
	self.geom:pos(x*config.quadsize,y*config.quadsize,z*config.heightstep)
	setmetatable(self,{__index = mapnode})
	return self
end

function mapnode:forallquads()
	return levelnode.forallquads(self.lnode)
end

function mapnode:delete()
	self.level.geomlut[luxinia.dataID(self.geom)] = nil
	self.l3d:delete()
	self.s3d:delete(true)
	self.geom:delete()
	self.lnode = nil
	self.l3d,self.s3d = nil,nil
	for a,b in pairs(self.objects) do 
		a:delete()
	end
end

levelmap = {}
function levelmap.new ()
	local self = {}
	self.nodes = autotable()
	self.geomlut = {}
	self.hitgeomlut = {}
	self.space = dspacehash.new()
	self.space:collidetest(false)
	setmetatable(self,{__index=levelmap})
	return self
end

function levelmap.getgeomowner (self, geom) 
	return self.geomlut[luxinia.dataID(geom)]
end

function levelmap.gethitgeomowner (self, geom)
	return self.hitgeomlut[luxinia.dataID(geom)]
end

function levelmap.get(self,x,y)
	if (self.nodes[x] and self.nodes[x][y]) then
		return self.nodes[x][y]
	end
end

function levelmap.getz(self,x,y)
	if (self.nodes[x] and self.nodes[x][y]) then
		return self.nodes[x][y].z
	end
	local n,s,w,e = self:get(x,y+1),self:get(x,y-1),self:get(x-1,y),self:get(x+1,y)
	if (not s and not w and not e and not n) then return nil end
	n,s,w,e = n and n.z,s and s.z,w and w.z,e and e.z
	local any = n or s or e or w
	return math.min(math.min(n or s or any,s or n or any), math.min(w or e or any,e or w or any))
end

function levelmap.clear(self,x,y)
	local node = self:get(x,y)
	if (not node) then return end
	for lx,ly in node:forallquads() do
		self.nodes[x+lx][y+ly] = nil
	end
	node:delete()
end

function levelmap.set(self,id,x,y,z)
	local old = self:get(x,y,z)
	local objects = {}
	if (old) then
		objects,old.objects = old.objects,objects
	end
	self:clear(x,y)
	local node = mapnode.new(self,id,x,y,z)
	if (not node) then return end
	self.space:add(node.geom)
	for lx,ly in node.lnode:forallquads() do 
	
		self.nodes[x+lx][y+ly] = node
	end
	node.objects = objects
	for a,b in pairs(objects) do
		a:autopos()
	end
end

function levelmap.add(self,obj,x,y,z,rx,ry,rz)
	local o = mapobject.new(self,obj,x,y,z,rx,ry,rz)
end

function levelmap.test(self,x,y,z,dx,dy,dz,len)
	return raytest(self.space,x,y,z,dx,dy,dz,len)
end

function levelmap.getfits (self,x,y)
	local north,east,south,west,all = 
		self:get(x,y+1),self:get(x+1,y),self:get(x,y-1),self:get(x-1,y),levelnodes.nodeids
	north = north and levelnodes.north[north.lnode.south]
	south = south and levelnodes.south[south.lnode.north]
	east = east and levelnodes.east[east.lnode.west]
	west = west and levelnodes.west[west.lnode.east]
	--assert(north and south and east and west and all)
	return table.keyintersection(north,east,south,west,all)
end

function levelmap:cleanup()
	local list = {}
	for x,ls in pairs(self.nodes) do for y,_ in pairs(ls) do table.insert(list,{x,y}) end end
	for i,v in ipairs(list) do
		self:clear(unpack(v))
	end
	if (self.batchbuffer) then
		self.batchbuffer:delete()
	end
	self.space:delete()
end


function levelmap.save (self,path)
	local fp = io.open(projectpath.."/levels/"..path,"w")
	function write(...)
		--for i,v in arg do 
		--_print(unpack(arg))
		fp:write(...,"\n")
	end
	write("local nodes = {")
	for x,b in pairs(self.nodes) do 
		for y,node in pairs(b) do
			local str = {}
			for obj,p in pairs(node.objects) do
				if (not obj.deleted) then
					local orig = obj.orig
					local s = string.format("\t\t{%q,{%.3f,%.3f,%.3f, %.3f,%.3f, %.3f}},",obj.mdl.short,orig[1],orig[2],orig[3],orig[4],orig[5],orig[6])
					table.insert(str,s)
				end
			end
			write("\t{"..tostring(x)..","..tostring(y)..","..tostring(node.z)..","..string.format("%q",levelnodes.nodeids[node.id].name)..",{\n"..table.concat(str,"\n").."\n\t}},")
		end
	end
	write ("}")
	write ("return nodes")
	fp:close(fp)
end
	
function levelmap.load (path)
	collectgarbage"collect"
	local lvl = dofile(projectpath.."/levels/"..path)
	local level = levelmap.new()
	for _,node in pairs(lvl) do
		if (levelnodes.nodes[node[4]]) then
			level:set(levelnodes.nodes[node[4]].id,node[1],node[2],node[3])
			if (node[5]) then
				for _,obj in pairs(node[5]) do 
					if (mapobjects[obj[1]]) then
						level:add(mapobjects[obj[1]],unpack(obj[2]))
					end
				end
			end
		end
	end
	
	if (level.batchbuffer) then
		level.batchbuffer:delete()
	end
	--level.batchbuffer = l3dbatchbuffer.new(l3dset.getnormal(),scenenode.getroot())
	return level
end
