Skip to content

Instantly share code, notes, and snippets.

@CapsAdmin
Created July 11, 2013 03:34
Show Gist options
  • Save CapsAdmin/5972340 to your computer and use it in GitHub Desktop.
Save CapsAdmin/5972340 to your computer and use it in GitHub Desktop.
some sort of 3d world
screens = screens or {}
screens.active_screens = screens.active_screens or {}
screens.lock_player = false
screens.mouse_delta = Vector(0, 0, 0)
screens.focused_screen = NULL
do -- meta
local SCREEN = {}
SCREEN.__index = SCREEN
function SCREEN:Remove()
self:KillFocus()
for _, ent in pairs(self.entities) do
SafeRemoveEntity(ent)
end
function self:IsValid()
return false
end
screens.active_screens[self.screen_id] = nil
setmetatable(self, getmetatable(NULL))
end
function SCREEN:IsValid()
return true
end
AccessorFunc(SCREEN, "terrain_mat", "TerrainMaterial")
AccessorFunc(SCREEN, "rt_tex", "RTTexture")
AccessorFunc(SCREEN, "fps", "FPS", FORCE_NUMBER)
AccessorFunc(SCREEN, "width", "Width")
AccessorFunc(SCREEN, "height", "Height")
AccessorFunc(SCREEN, "cam_pos", "CameraPos")
AccessorFunc(SCREEN, "cam_ang", "CameraAngles")
AccessorFunc(SCREEN, "cam_fov", "CameraFOV")
function SCREEN:IsPaused()
return self.paused
end
function SCREEN:Pause()
self.paused = true
end
function SCREEN:Resume()
self.paused = false
end
do -- input
function SCREEN:RequestFocus()
screens.EnableKeyMonitor(true)
screens.focused_screen = self
end
function SCREEN:KillFocus()
if screens.focused_screen == self then
screens.EnableKeyMonitor(false)
screens.focused_screen = NULL
end
end
function SCREEN:IsFocused()
return screens.focused_screen == self
end
function SCREEN:GetMouseDelta()
return screens.mouse_delta
end
function SCREEN:OnInput(key, press)
end
end
do -- entities
function SCREEN:CreateEntity(mdl)
local ent = ClientsideModel(mdl)
ent:SetNoDraw(true)
table.insert(self.entities, ent)
return ent
end
function SCREEN:DrawEntities()
render.SuppressEngineLighting(true)
render.SetBlend(1)
render.SetColorModulation(1,1,1)
render.SetAmbientLight(0,0,0)
render.ResetModelLighting(0.2,0.2,0.2)
render.SetModelLighting(3, 1,1,1) -- global_light.r / 255, global_light.g / 255, global_light.b / 255)
for _, ent in pairs(self.entities) do
ent:DrawModel()
end
render.SuppressEngineLighting(false)
end
end
if pac then
function SCREEN:GetTextureFromURL(url, size, callback)
pac.urltex.GetMaterialFromURL(url, function(mat, tex)
callback(mat, tex)
end, false, nil, size, false)
end
end
do -- terrain
local W, H = ScrW(), ScrH()
local function start_height(tex)
local mat = CreateMaterial("screens_heightmap_" .. os.clock(), "UnlitGeneric", {["$basetexture"] = "dev/hemisphere_height.vtf"})
if tex then
mat:SetTexture("$basetexture", tex)
end
cam.Start2D()
surface.SetMaterial(mat)
surface.SetDrawColor(255, 255, 255, 255)
-- this kinda sucks but using a heightmap resolution higher than screen resolution will cause undesired results..
surface.DrawTexturedRect(0, 0, W, H)
render.CapturePixels()
end
local function get_height(x, y)
local r,g,b = render.ReadPixel(math.ceil(x*W), math.ceil(y*H))
return ((r+g+b) / 3) / 255
end
local function end_height()
cam.End2D()
end
function SCREEN:CreateTerrainFromHeightmap(tex, size, res, height)
local mesh = Mesh()
start_height(tex)
size = size or 32768
res = res or 64
height = height or 8192
local data = {}
local _size = size / res
for y = 0, res do
for x = 0, res do
local z1 = get_height(x/res, (y+1)/res) * height -- bottom left
local z2 = get_height(x/res, y/res) * height -- top left
local z3 = get_height((x+1)/res, y/res) * height -- top right
local z4 = get_height((x+1)/res, (y+1)/res) * height -- bottom right
table.insert(data, {pos = Vector(x * _size + _size, y * _size, z3)})
table.insert(data, {pos = Vector(x * _size, y * _size, z2)})
table.insert(data, {pos = Vector(x * _size, y * _size + _size, z1)})
table.insert(data, {pos = Vector(x * _size, y * _size + _size, z1)})
table.insert(data, {pos = Vector(x * _size + _size, y * _size + _size, z4)})
table.insert(data, {pos = Vector(x * _size + _size, y * _size, z3)})
end
end
end_height()
local height_cache = {}
for _, vertex in pairs(data) do
-- vertex.normal = VectorRand()
vertex.v = vertex.pos.x / size
vertex.u = vertex.pos.y / size
end
start_height(tex)
for x = 0, W do
for y = 0, H do
local r,g,b = render.ReadPixel(x, y)
local z = ((r+g+b) / 3) / 255
height_cache[x] = height_cache[x] or {}
height_cache[x][y] = height_cache[x][y] or z * height
end
end
end_height()
self.height_cache = height_cache
mesh:BuildFromTriangles(data)
SafeRemoveEntity(self.terrain_ent)
local ent = self:CreateEntity("error.mdl")
self.terrain_mat = Material("models/wireframe")
self.terrain_size = size
ent.RenderOverride = function(ent)
render.MaterialOverride(self.terrain_mat)
ent:SetModelScale(0, 0)
ent:DrawModel()
local mat = Matrix()
mat:SetAngles(ent:GetAngles())
mat:SetTranslation(ent:GetPos())
cam.PushModelMatrix(mat)
mesh:Draw()
cam.PopModelMatrix()
render.MaterialOverride()
end
end
function SCREEN:GetTerrainHeight(pos)
if self.height_cache then
local x = math.floor((pos.x / self.terrain_size) * W)
local y = math.floor((pos.y / self.terrain_size) * H)
return self.height_cache[x] and self.height_cache[x][y] or 0
end
return 0
end
end
function SCREEN:SetFog(start, _end, max_density, r,g,b)
if start and _end and max_density and r and g and b then
self.fog_params =
{
start = start,
_end = _end,
max_density = max_density,
color = {r,g,b},
}
else
self.fog_params = nil
end
end
function SCREEN:CreateWorldScreen()
SafeRemoveEntity(self.world_screen)
local ent = ClientsideModel("models/hunter/plates/plate1x1.mdl")
local mat = Matrix()
mat:Scale(Vector(self.height / self.width, 1, 1))
ent:EnableMatrix("RenderMultiply", mat)
ent.RenderOverride = function(s)
if not self:IsValid() then
timer.Simple(0, function()
s:Remove()
end)
end
render.SetBlend(1)
render.SetColorModulation(1, 1, 1)
render.PushFilterMag(TEXFILTER.POINT)
render.PushFilterMin(TEXFILTER.POINT)
render.MaterialOverride(self.screen_mat)
s:DrawModel()
render.MaterialOverride()
render.PopFilterMag()
render.PopFilterMin()
end
self.world_screen = ent
return ent
end
-- events
function SCREEN:UpdateFreeroamCamera()
local speed = 100
local delta = screens.mouse_delta * 0.1
local cam_pos = self.cam_pos
local cam_ang = self.cam_ang
cam_ang.p = cam_ang.p + delta.y
cam_ang.y = cam_ang.y - delta.x
cam_ang.p = math.clamp(cam_ang.p, -90, 90)
if input.IsKeyDown(KEY_LSHIFT) then
speed = speed * 4
elseif input.IsKeyDown(KEY_LCONTROL) then
speed = speed / 4
end
if input.IsKeyDown(KEY_SPACE) then
cam_pos = cam_pos - Vector(0, 0, -speed)
end
local offset = cam_ang:Forward() * speed
if input.IsKeyDown(KEY_W) then
cam_pos = cam_pos + offset
elseif input.IsKeyDown(KEY_S) then
cam_pos = cam_pos - offset
end
offset = cam_ang:Right() * speed
offset.z = -offset.z
if input.IsKeyDown(KEY_D) then
cam_pos = cam_pos + offset
elseif input.IsKeyDown(KEY_A) then
cam_pos = cam_pos - offset
end
self.cam_pos = cam_pos
self.cam_ang = cam_ang
end
function SCREEN:UpdateWalkCamera()
self.cam_vel = self.cam_vel or Vector(0,0,0)
local speed = 10
local delta = screens.mouse_delta * 0.1
local cam_pos = self.cam_pos
local cam_ang = self.cam_ang
cam_ang.p = cam_ang.p + delta.y
cam_ang.y = cam_ang.y - delta.x
cam_ang.p = math.clamp(cam_ang.p, -90, 90)
if input.IsKeyDown(KEY_LSHIFT) then
speed = speed * 4
elseif input.IsKeyDown(KEY_LCONTROL) then
speed = speed / 4
end
if input.IsKeyDown(KEY_SPACE) then
if not self.jumped then
self.cam_vel.z = self.cam_vel.z + 500
self.jumped = true
end
else
self.jumped = false
end
local offset = cam_ang:Forward() * speed
if input.IsKeyDown(KEY_W) then
cam_pos = cam_pos + offset
elseif input.IsKeyDown(KEY_S) then
cam_pos = cam_pos - offset
end
offset = cam_ang:Right() * speed
offset.z = -offset.z
if input.IsKeyDown(KEY_D) then
cam_pos = cam_pos + offset
elseif input.IsKeyDown(KEY_A) then
cam_pos = cam_pos - offset
end
cam_pos.z = math.max(cam_pos.z - 100, self:GetTerrainHeight(cam_pos) + 72)
self.cam_vel = (self.cam_vel + (cam_pos - self.cam_pos)) * 0.35
self.cam_pos = cam_pos + self.cam_vel
self.cam_ang = cam_ang
end
function SCREEN:UpdateCamera()
if not self:IsFocused() then return end
self:UpdateWalkCamera()
end
function SCREEN:Think()
self:UpdateCamera()
end
local skybox_ent = ClientsideModel("models/XQM/Rails/gumball_1.mdl")
skybox_ent:SetMaterial("models/debug/debugwhite")
function SCREEN:DrawSkyBox()
render.SetColorModulation(0.4,0.7,1)
render.SetBlend(1)
render.SuppressEngineLighting(true)
skybox_ent:SetPos(self.cam_pos)
skybox_ent:SetModelScale(-1, 0)
skybox_ent:DrawModel()
render.SuppressEngineLighting(false)
render.ClearDepth()
end
local water_mat = CreateMaterial("screens_water_"..RealTime(), "Refract",
{
["$model"] = 1,
["$nocull"] = 1,
["$translucent"] = 1,
["$bluramount"] = 50,
["$refractamount"] = 0.1,
["$refracttint"] = "0.5 0.5 1",
["$dudvmap"] = "water/dx80_tfwater001_dudv",
["$normalmap"] = "water/tfwater001_normal",
Proxies =
{
AnimatedTexture =
{
animatedtexturevar = "$normalmap",
animatedtextureframenumvar = "$bumpframe",
animatedtextureframerate = 30,
},
},
})
local water_mesh = Mesh()
local data = {}
local size = 32768 / 128*4
for y = 0, 128 do
for x = 0, 128 do
table.insert(data, {pos = Vector(x * size + size, y * size)})
table.insert(data, {pos = Vector(x * size, y * size)})
table.insert(data, {pos = Vector(x * size, y * size + size)})
table.insert(data, {pos = Vector(x * size, y * size + size)})
table.insert(data, {pos = Vector(x * size + size, y * size + size)})
table.insert(data, {pos = Vector(x * size + size, y * size)})
end
end
local up = Vector(0,0,1)
for k,v in pairs(data) do
v.normal = up
v.v = v.pos.x / size
v.u = v.pos.y / size
end
water_mesh:BuildFromTriangles(data)
function SCREEN:DrawWater()
render.UpdateRefractTexture()
render.SetBlend(1)
render.SuppressEngineLighting(true)
local mat = Matrix()
mat:Translate(Vector(-32768*0.5,-32768*0.5,1000))
cam.PushModelMatrix(mat)
render.SetMaterial(water_mat)
water_mesh:Draw()
cam.PopModelMatrix()
render.SuppressEngineLighting(false)
end
function SCREEN:Draw3D()
self:DrawEntities()
end
function SCREEN:Draw2D()
end
function SCREEN:DrawPostProcess()
if self.cam_pos.z < 1000 then
surface.SetDrawColor(20,50,100,100)
surface.DrawRect(0,0,self.width,self.height)
end
end
function SCREEN:Update()
self:Think()
local old_rt, old_w, old_h = render.GetRenderTarget(), ScrW(), ScrH()
render.SetRenderTarget(self.rt_tex)
render.SetViewPort(0, 0, self.width, self.height)
render.Clear(0,0,0,0, true)
cam.Start3D(self.cam_pos, self.cam_ang, self.cam_fov, 0, 0, self.width, self.height)
self:DrawSkyBox()
local params = self.fog_params
if params then
render.FogMode(1)
render.FogStart(params.start)
render.FogEnd(params._end)
render.FogMaxDensity(params.max_density)
render.FogColor(unpack(params.color))
end
self:Draw3D()
self:DrawWater()
cam.End3D()
cam.Start2D()
self:Draw2D()
self:DrawPostProcess()
cam.End2D()
render.SetRenderTarget(old_rt)
render.SetViewPort(0, 0, old_w, old_h)
end
screens.ScreenMeta = SCREEN
end
function screens.GetAll()
return screens.active_screens
end
function screens.Remove(id)
local self = screens.active_screens[id] or NULL
if self:IsValid() then
self:Remove()
end
end
function screens.Create(id, w, h, fps)
w = w or 320
h = h or 240
fps = fps or 10
screens.Remove(id)
local self = setmetatable({}, screens.ScreenMeta)
self.screen_id = id
self.width = w
self.height = h
self.fps = fps
self.cam_pos = Vector(0, 0, 0)
self.cam_ang = Angle(0, 0, 0)
self.cam_fov = 90
self.rt_tex = GetRenderTarget(id, w, h, true)
self.screen_mat = CreateMaterial(id, "UnlitGeneric")
self.screen_mat:SetTexture("$basetexture", self.rt_tex)
self.entities = {}
screens.active_screens[id] = self
return self
end
function screens.EnableKeyMonitor(b)
if b then
screens.lock_player = LocalPlayer():EyeAngles()
else
screens.lock_player = false
end
end
do -- hooks
hook.Add("Think", "screens_update", function()
local time = RealTime()
for _, screen in pairs(screens.active_screens) do
if not screen.paused and (not screen.last_update or screen.last_update < time) then
screen:Update()
screen.last_update = time + (1/screen.fps)
end
end
end)
hook.Add("CreateMove", "screens_camera", function(ucmd)
if screens.lock_player then
ucmd:SetForwardMove(0)
ucmd:SetSideMove(0)
ucmd:SetViewAngles(screens.lock_player)
ucmd:SetButtons(0)
screens.mouse_delta.x = ucmd:GetMouseX()
screens.mouse_delta.y = ucmd:GetMouseY()
end
end)
hook.Add("KeyPress", "screens", function(ply, key)
local screen = screens.focused_screen
if screen:IsValid() then
screen:OnInput(key, true)
end
end)
hook.Add("KeyRelease", "screens", function(ply, key)
local screen = screens.focused_screen
if screen:IsValid() then
screen:OnInput(key, false)
end
end)
end
do -- test
local screen = screens.Create("forest")
local ent = screen:CreateWorldScreen()
ent:SetPos(LocalPlayer():EyePos() + LocalPlayer():GetAimVector() * 30)
ent:SetAngles(LocalPlayer():EyeAngles() + Angle(90,0,0))
screen:SetFog(0, 18000, 0.15, 80, 170, 255)
screen:RequestFocus()
local trees = {
"models/props_foliage/tree_pine01.mdl",
"models/props_foliage/tree_pine01_8cluster.mdl",
"models/props_foliage/tree_pine01_4cluster.mdl",
"models/props_foliage/tree_pine_huge.mdl",
"models/props_foliage/tree_pine_small.mdl",
}
screen:GetTextureFromURL("http://dl.dropboxusercontent.com/u/244444/heightmap1.jpg", 2048, function(mat, tex)
screen:CreateTerrainFromHeightmap(tex)
for i = 1, 1000 do
local pos = Vector(math.random(), math.random(), 0) * 32768
pos.z = screen:GetTerrainHeight(pos)
if pos.z > 1000 and pos.z < 3000 then
local ent = screen:CreateEntity(table.Random(trees))
ent:SetModelScale(math.Rand(0.5, 2), 0)
ent:SetPos(pos)
end
end
end)
screen:GetTextureFromURL("http://dl.dropboxusercontent.com/u/244444/HeightMap_1BaseTexture.jpg", 2048, function(mat)
screen:SetTerrainMaterial(mat)
end)
function screen:Draw2D()
local t = RealTime()
local w, h = screen:GetWidth(), screen:GetHeight()
surface.SetFont("DermaDefault")
surface.SetTextColor(255, 255, 255, 255)
local pos = self:GetCameraPos()
surface.SetTextPos(5, 5)
surface.DrawText(tostring(pos))
local height = self:GetTerrainHeight(pos)
surface.SetTextPos(5, 20)
surface.DrawText(height)
end
LOL_SCREEN = screen
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment