├── README.md ├── lua ├── autorun │ ├── !terrain_detour.lua │ ├── terrain_chunkrequest.lua │ ├── terrain_core.lua │ ├── terrain_menu.lua │ ├── terrain_models.lua │ └── terrain_water.lua ├── entities │ └── terrain_chunk.lua └── simplex.lua ├── materials └── procedural_terrain │ ├── foliage │ ├── arbre01.vmt │ ├── arbre01.vtf │ ├── coastrock02.vmt │ └── coastrock02.vtf │ └── water │ ├── dx80_tfwater001_dudv.vtf │ ├── dx80_tfwater001_normal.vtf │ ├── tfwater001_normal.vtf │ └── water_warp.vmt └── models └── procedural_terrain └── foliage ├── Tree_pine04.dx90.vtx ├── Tree_pine04.phy ├── Tree_pine05.dx90.vtx ├── Tree_pine05.phy ├── Tree_pine06.dx90.vtx ├── Tree_pine06.phy ├── Tree_pine_Large.dx90.vtx ├── Tree_pine_Large.phy ├── rock_coast02a.dx90.vtx ├── rock_coast02a.mdl ├── rock_coast02a.phy ├── rock_coast02a.vvd ├── tree_pine04.mdl ├── tree_pine04.vvd ├── tree_pine05.mdl ├── tree_pine05.vvd ├── tree_pine06.mdl ├── tree_pine06.vvd ├── tree_pine_large.mdl └── tree_pine_large.vvd /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to my procedural terrain addon [![made with - mee++](https://img.shields.io/badge/made_with-mee%2B%2B-2ea44f)](https://) 2 | 3 | So this is a project ive been working on for about 5 months now, I am pretty proud of its release however it will likely not be 100% perfect, please report any bugs you find. 4 | 5 | If you are experiencing crashing, before reporting it, please try and switch to the 64x branch of gmod as it is more stable. Source physics isn't exactly happy when generating map wide objects, this addon is quite literally pushing source to its limits. 6 | 7 | ### How to use: 8 | - Go to gm_flatgrass, this mod replaces it with the custom terrain! 9 | - Access the custom menu by typing terrain_menu in console, or by going into the utilities tab and clicking the button. 10 | - You must be superadmin to use the menu! 11 | - There are plenty of options in the menu, I will just let you figure out how to use its features, it should be pretty simple. 12 | - The "submit button" actually changes the terrain, this is important, do not mix it up with the "test changes" button, which just alters the visual for your viewing. 13 | - As of now you cannot save your changes for later use, I will hopefully implement a save / load feature in the future. 14 | 15 | ### Custom materials that can be used with blendmap: 16 | - gm_construct/construct_sand 17 | - ground/snow01 (may need ep2, not sure) 18 | - hunter/myplastic 19 | - phoenix_storms/ps_grass 20 | - most materials from material tool 21 | - most .vmt files on your client 22 | 23 | ### Features 24 | - Better performance than other displacement forest maps such as gm_fork 25 | - Imported models, no dependeces required! 26 | - All chunk, blending, foliage, and tree shading complete in under 10 seconds (with my specs) 27 | - Lightmap calculations complete in under a minute (with my specs) 28 | - Lakes / Water System 29 | - Custom entity that acts as a displacement 30 | - Custom lightmap with generated shading 31 | - Customizable blendmap between 2 textures 32 | - Custom Lighting 33 | - No uv/texture stretching 34 | - Trees & grass foliage 35 | - Lots of options in the menu including a optional user-created LUA heightmap function 36 | - Mutliplayer Support 37 | 38 | ### Bugs I am aware of: 39 | - Tree and Rock collision can be wonky with high ping 40 | - Having a weak computer and being on the 32 bit branch of gmod may cause crashes, easiest solution is to switch to 64 bit if you haven't already 41 | edit: I implemented a possible working quickfix for this issue by simply not generating client physics if you're on 32 bit. (but either way, please switch to 64 bit) 42 | 43 | Special Thanks: 44 | DefaultOS - LOTS of help from this guy, helped with implementation of a lightmap & blend texture, as well as optimizations reguarding to trees, thx a lot man 45 | Impulse - helped import models / water material 46 | 47 | extra stuff: 48 | discord: https://discord.gg/cmQvg2AHgP 49 | patreon: https://www.patreon.com/meegmod 50 | pls donate i need a new ssd, current one is failing :( 51 | 52 | -------------------------------------------------------------------------------- /lua/autorun/!terrain_detour.lua: -------------------------------------------------------------------------------- 1 | // guess why this file is named !terrain_detour instead of terrain_detour 2 | // its because all the addon files initialize in alphabetical order! 3 | // we need the traceline detour to run before any other addon 4 | // its extremely important that the detour runs first to avoid addon conflictions 5 | 6 | if game.GetMap() != "gm_flatgrass" then return end 7 | 8 | AddCSLuaFile() 9 | 10 | Terrain = Terrain or {} 11 | 12 | Terrain.TraceLine = Terrain.TraceLine or util.TraceLine 13 | local lookup = Terrain.TraceLine // faster lookup, dont wanna bog frames 14 | function util.TraceLine(t) 15 | local tr = lookup(t) 16 | local e = tr.Entity 17 | if e and e:IsValid() and e:GetClass() == "terrain_chunk" then 18 | tr.Entity = game.GetWorld() 19 | end 20 | return tr 21 | end -------------------------------------------------------------------------------- /lua/autorun/terrain_chunkrequest.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | if game.GetMap() != "gm_flatgrass" then return end 4 | 5 | local terrain_speed = 0.1 6 | if SERVER then 7 | util.AddNetworkString("TERRAIN_SEND_DATA") // superadmin only! 8 | Terrain = Terrain or {} 9 | 10 | local function genChunks() 11 | for y = Terrain.Resolution - 1, -Terrain.Resolution, -1 do 12 | for x = Terrain.Resolution - 1, -Terrain.Resolution, -1 do 13 | if Terrain.Variables.cave then 14 | local chunk = ents.Create("terrain_chunk") 15 | chunk:SetPos(Vector(x * Terrain.ChunkResScale, y * Terrain.ChunkResScale, Terrain.ZOffset)) 16 | chunk:SetChunkX(x) 17 | chunk:SetChunkY(y) 18 | chunk:SetFlipped(true) 19 | chunk:Spawn() 20 | table.insert(Terrain.Chunks, chunk) 21 | end 22 | 23 | local chunk = ents.Create("terrain_chunk") 24 | chunk:SetPos(Vector(x * Terrain.ChunkResScale, y * Terrain.ChunkResScale, Terrain.ZOffset)) 25 | chunk:SetChunkX(x) 26 | chunk:SetChunkY(y) 27 | chunk:Spawn() 28 | table.insert(Terrain.Chunks, chunk) 29 | 30 | 31 | coroutine.wait(terrain_speed) 32 | end 33 | end 34 | end 35 | 36 | function Terrain.GenerateAll() 37 | for k, v in ipairs(Terrain.Chunks or {}) do // hotreloading 38 | SafeRemoveEntity(v) 39 | end 40 | 41 | local co = coroutine.create(genChunks) 42 | hook.Add("Think", "terrain_init", function() 43 | if coroutine.status(co) != "dead" then 44 | coroutine.resume(co) 45 | else 46 | hook.Remove("Think", "terrain_init") 47 | end 48 | end) 49 | end 50 | 51 | timer.Simple(1, Terrain.GenerateAll) //generate all chunks 52 | 53 | net.Receive("TERRAIN_SEND_DATA", function(len, ply) 54 | if len == 0 then 55 | net.Start("TERRAIN_SEND_DATA") 56 | net.WriteTable(Terrain.Variables) 57 | net.Send(ply) 58 | return 59 | end 60 | 61 | if !ply or !ply:IsValid() or !ply:IsSuperAdmin() then return end // only superadmins can edit terrain 62 | 63 | local t = net.ReadTable() 64 | Terrain.MathFunc = Terrain.BuildMathFunc(t) 65 | net.Start("TERRAIN_SEND_DATA") 66 | net.WriteTable(t) 67 | net.Broadcast() 68 | 69 | Terrain.Variables = t 70 | timer.Simple(1, Terrain.GenerateAll) // give clients a second to receive the data 71 | end) 72 | else 73 | // client needs the data to build the math function 74 | Terrain = Terrain or {} 75 | Terrain.ClientLoaded = false 76 | 77 | local done = 0 78 | local sizex = ScrW() * 0.5 79 | local sizey = ScrH() * 0.02 80 | local function loadhud() 81 | surface.SetDrawColor(Color(0, 0, 0, 255)) 82 | surface.DrawRect(sizex - 200, sizey - 12.5, 400, 50) 83 | draw.DrawText("Generating Server Chunks.. " .. math.Round(done * 100) .. "% done", "TargetID", sizex, sizey, color_white, TEXT_ALIGN_CENTER) 84 | end 85 | 86 | // clients can randomly forget chunk data during a lagspike, we rebuild it if that happens 87 | local co = coroutine.create(function() 88 | while true do 89 | local cave = (Terrain.Variables.cave and 2 or 1) 90 | coroutine.wait(1) 91 | if Terrain.ClientLoaded then 92 | local chunks = ents.FindByClass("terrain_chunk") 93 | if #chunks < ((Terrain.Resolution * 2)^2) * cave then 94 | hook.Add("HUDPaint", "terrain_load", loadhud) 95 | done = #chunks / (Terrain.Resolution * 2)^2 / cave 96 | continue 97 | else 98 | done = 1 99 | end 100 | for k, v in ipairs(chunks) do 101 | if v:IsValid() then 102 | if jit.arch != "x86" then 103 | if (!v:GetPhysicsObject() or !v:GetPhysicsObject():IsValid()) then 104 | print("Rebuilding Physics for Chunk " .. v:GetChunkX() .. "," .. v:GetChunkY()) 105 | v:OnRemove() 106 | v:ClientInitialize() 107 | end 108 | else 109 | if !v.TreeMatrices then 110 | print("Rebuilding Physics for Chunk " .. v:GetChunkX() .. "," .. v:GetChunkY()) 111 | v:OnRemove() 112 | v:ClientInitialize() 113 | end 114 | end 115 | end 116 | coroutine.wait(terrain_speed) 117 | end 118 | end 119 | end 120 | end) 121 | 122 | // request server to reload chunks 123 | hook.Add("Tick", "terrain_init", function() 124 | coroutine.resume(co) 125 | end) 126 | 127 | net.Receive("TERRAIN_SEND_DATA", function(len) 128 | local t = net.ReadTable() 129 | Terrain.Variables = t 130 | Terrain.MathFunc = Terrain.BuildMathFunc() 131 | Terrain.Material:SetTexture("$basetexture", t.material_2) 132 | Terrain.Material:SetTexture("$basetexture2", t.material_1) 133 | Terrain.WaterMaterial = Material(t.material_3) 134 | 135 | //if !Terrain.ClientLoaded then 136 | Terrain.ClientLoaded = true 137 | //local has_end = false 138 | //for k, v in ipairs(ents.FindByClass("terrain_chunk")) do 139 | // if v.Initialize then 140 | // v:Initialize() 141 | // if v.GetChunkX and v:GetChunkX() == -Terrain.Resolution and v:GetChunkY() == -Terrain.Resolution then 142 | // has_end = true 143 | // end 144 | // end 145 | //end 146 | 147 | //if has_end then 148 | //timer.Simple(10, function() 149 | // Terrain.GenerateLightmap(1024) 150 | //end) 151 | //end 152 | //end 153 | end) 154 | 155 | // clients request to server to get data for height function 156 | hook.Add("InitPostEntity", "terrain_init", function() 157 | net.Start("TERRAIN_SEND_DATA") 158 | net.SendToServer() 159 | end) 160 | end -------------------------------------------------------------------------------- /lua/autorun/terrain_core.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | if game.GetMap() != "gm_flatgrass" then return end 4 | 5 | if SERVER then resource.AddWorkshop("2830138108") end 6 | 7 | Terrain = Terrain or {} 8 | Terrain.ChunkSize = 256 9 | Terrain.Resolution = 6 10 | Terrain.ChunkResolution = 10 11 | Terrain.LightmapRes = 1024 12 | Terrain.ChunkResScale = Terrain.ChunkSize * Terrain.ChunkResolution 13 | Terrain.Chunks = Terrain.Chunks or {} 14 | Terrain.Simplex = include("simplex.lua") 15 | Terrain.LODDistance = 5000^2 16 | Terrain.ZOffset = -12770 17 | Terrain.SunDir = Vector(0.414519, 0.279596, 0.866025) //default flatgrass sun direction 18 | Terrain.Variables = { // variables that are networked 19 | height_1 = 50, // height multiplier 20 | noiseScale_1 = 20, 21 | height_2 = 5, // height multiplier 22 | noiseScale_2 = 2, 23 | 24 | offset = 0, // z offset 25 | seed = 21, 26 | treeHeight = 2, 27 | treeResolution = 5, 28 | treeThreshold = 0.5, 29 | cave = false, 30 | waterHeight = -12300, 31 | clampNoise = true, // noise 0-1 or -1-1 32 | customFunction = nil, // custom function to use, nil by default (not actually added to table, only here for visualization) 33 | spawnArea = true, // give the flatgrass building space or not 34 | generateGrass = true, 35 | grassSize = 25, 36 | treeColor = Vector(1, 1, 1), 37 | 38 | material_1 = "gm_construct/grass1", // terrain main material 39 | material_2 = "nature/rockfloor005a", // terrain secondary, rock material 40 | material_3 = "procedural_terrain/water/water_warp", // water material 41 | 42 | water_kill = false, 43 | water_ignite = false, 44 | water_viscosity = 1, 45 | water_buoyancy = 1, 46 | } 47 | 48 | local invMagicNumber = 1 / 2048 49 | Terrain.AllowedLibraries = { 50 | math = math, 51 | bit = bit, 52 | Simplex = Terrain.Simplex, 53 | } 54 | 55 | for k, v in pairs(math) do 56 | Terrain.AllowedLibraries[k] = v 57 | end 58 | 59 | // The main function that defines the height for a given point 60 | function Terrain.BuildMathFunc(values) 61 | values = values or Terrain.Variables 62 | if values.customFunction then 63 | local generatedFunction = setfenv(CompileString("local x, y = ...\n" .. values.customFunction, "Terrain Function"), Terrain.AllowedLibraries) 64 | return function(x, y, flip) 65 | local x = x * invMagicNumber 66 | local y = y * invMagicNumber 67 | 68 | local final = generatedFunction(x, y) or 0 69 | 70 | // finalize the value 71 | if values.spawnArea then final = ((math.abs(x) < 0.7 and math.abs(y) < 0.7) and 0.05 or final) end //spawn region gets space 72 | final = math.Clamp(final, 0, 100) 73 | return flip and (100 - final) * 256 or final * 256 74 | end 75 | end 76 | 77 | local randomNum = 4980.57 // random num for seed 78 | return function(x, y, flip) 79 | local x = x * invMagicNumber 80 | local y = y * invMagicNumber 81 | 82 | x = x + (values.seed * randomNum) 83 | 84 | local final = Terrain.Simplex.Noise2D(x / values.noiseScale_1, y / values.noiseScale_1) * values.height_1 85 | if values.clampNoise then final = math.Max(final, 0) end 86 | final = final + Terrain.Simplex.Noise2D(-x / values.noiseScale_2, -y / values.noiseScale_2) * values.height_2 87 | 88 | x = x - (values.seed * randomNum) 89 | 90 | final = final + values.offset 91 | 92 | // finalize the value 93 | if values.spawnArea then final = ((math.abs(x) < 0.7 and math.abs(y) < 0.7) and 0.05 or final) end //spawn region gets space 94 | final = math.Clamp(final, 0, 100) 95 | return flip and (100 - final) * 256 or final * 256 96 | end 97 | end 98 | 99 | Terrain.MathFunc = Terrain.BuildMathFunc() 100 | 101 | if SERVER then return end 102 | 103 | Terrain.Lightmap = GetRenderTarget("Terrain_Lightmap", Terrain.LightmapRes, Terrain.LightmapRes) 104 | render.ClearRenderTarget(Terrain.Lightmap, Color(127, 127, 127, 255)) 105 | 106 | // stolen tri intersection function lol 107 | local function intersectRayWithTriangle(rayOrigin, rayDir, tri1, tri2, tri3) 108 | local point1 = tri1 109 | local edge1 = tri2 - point1 110 | local edge2 = tri3 - point1 111 | local h = rayDir:Cross(edge2) 112 | local a = edge1:Dot(h) 113 | if a > 0 then 114 | return nil // This ray is parallel to this triangle. 115 | end 116 | 117 | local f = 1 / a 118 | local s = rayOrigin - point1 119 | local u = f * s:Dot(h) 120 | 121 | if u < 0 || u > 1 then 122 | return nil 123 | end 124 | 125 | local q = s:Cross(edge1) 126 | local v = f * rayDir:Dot(q) 127 | if v < 0 || u + v > 1 then 128 | return nil 129 | end 130 | 131 | // At this stage we can compute t to find out where the intersection point is on the line. 132 | local t = f * edge2:Dot(q) 133 | if (t > 0) then // ray intersection 134 | return rayOrigin + rayDir * t; 135 | end 136 | 137 | return nil 138 | end 139 | 140 | local function generateLightmap(res, heightFunction) 141 | local surface_SetDrawColor = surface.SetDrawColor // faster lookup 142 | local surface_DrawRect = surface.DrawRect 143 | local globalTerrainScale = Terrain.ChunkResScale * Terrain.Resolution 144 | local waterHeight = Terrain.Variables.waterHeight 145 | local util_TraceLine = util.TraceLine 146 | local lightmapMultiplier = Terrain.LightmapRes / res 147 | local math_floor = math.floor 148 | local math_ceil = math.ceil 149 | local math_Clamp = math.Clamp 150 | local function getHeight(x, y) 151 | return Vector(x, y, heightFunction(x, y) + Terrain.ZOffset) 152 | end 153 | local function traceFunc(e) return IsValid(e) and e:GetClass() == "terrain_chunk" end 154 | local res = res - 1 155 | 156 | local done = 0 157 | local sizex = ScrW() * 0.5 158 | local sizey = ScrH() * 0.02 159 | hook.Add("HUDPaint", "terrain_load", function() 160 | surface.SetDrawColor(Color(0, 0, 0, 255)) 161 | surface.DrawRect(sizex - 200, sizey - 12.5, 400, 50) 162 | draw.DrawText("Baking lighting.. " .. math.Round(done / res * 100) .. "% done", "TargetID", sizex, sizey, color_white, TEXT_ALIGN_CENTER) 163 | end) 164 | 165 | for y = 0, res do 166 | render.PushRenderTarget(Terrain.Lightmap) 167 | cam.Start2D() 168 | done = y 169 | for x = 0, res do 170 | local worldx = (x / res) * globalTerrainScale 171 | local worldy = (y / res) * globalTerrainScale 172 | worldx = worldx * 2 - globalTerrainScale 173 | worldy = worldy * 2 - globalTerrainScale 174 | 175 | // for smooth shading 176 | local worldx2 = (x / (res + 1)) * globalTerrainScale 177 | local worldy2 = (y / (res + 1)) * globalTerrainScale 178 | worldx2 = worldx2 * 2 - globalTerrainScale 179 | worldy2 = worldy2 * 2 - globalTerrainScale 180 | local triNorm = (getHeight(worldx, worldy) - getHeight(worldx2, worldy)):Cross(getHeight(worldx, worldy) - getHeight(worldx2, worldy2)):GetNormalized() 181 | local dotShading = triNorm:Dot(Terrain.SunDir) // smooth shading 182 | //local dotShading = (tri1[2] - tri1[1]):Cross(tri1[2] - tri1[3]):GetNormalized():Dot(sunDir) // flat shading (disabled) 183 | 184 | local math = math 185 | local lerpXBottom = math_floor(worldx / Terrain.ChunkSize - 0.001) * Terrain.ChunkSize 186 | local lerpYBottom = math_floor(worldy / Terrain.ChunkSize - 0.001) * Terrain.ChunkSize 187 | local lerpXTop = math_ceil(worldx / Terrain.ChunkSize) * Terrain.ChunkSize 188 | local lerpYTop = math_ceil(worldy / Terrain.ChunkSize) * Terrain.ChunkSize 189 | 190 | local corner1 = getHeight(lerpXTop, lerpYTop) 191 | local corner2 = getHeight(lerpXTop, lerpYBottom) 192 | local corner3 = getHeight(lerpXBottom, lerpYBottom) 193 | local corner4 = getHeight(lerpXBottom, lerpYTop) 194 | 195 | local v = Vector(worldx, worldy, 25601) 196 | local shadowPos = intersectRayWithTriangle(v, Vector(0, 0, -1), corner2, corner3, corner4) // 25601 = max height 197 | shadowPos = shadowPos or intersectRayWithTriangle(v, Vector(0, 0, -1), corner1, corner2, corner4) 198 | shadowPos = (shadowPos or Vector(0, 0, 0)) + Vector(0, 0, 1)// + triNorm * 10 199 | 200 | local shadowAmount = (dotShading + 1.5) * 64 201 | if dotShading > 0 then // if it faces toward the sun 202 | // we have mathmatical terrain, however it is not perfectly smooth 203 | // the shadow ray may intersect with its own triangle, which is not what we want 204 | // we need to do intersections with 2 triangles and find the real height 205 | 206 | // (we subtract 0.001 incase lerpxbottom and lerpxtop end up to be the same) 207 | 208 | // find rounded point heights 209 | 210 | // find where to cast the shadow ray 211 | if !util_TraceLine({start = shadowPos, endpos = shadowPos + Terrain.SunDir * 99999, filter = traceFunc}).HitSky then // if it hits rock 212 | shadowAmount = 50 213 | end 214 | else // sunlight does not hit it because it is angled away from the sun 215 | shadowAmount = 50 216 | end 217 | 218 | if waterHeight then 219 | local waterAmount = math_Clamp( (-shadowPos.z + waterHeight) * 0.2, 0, 50 ) 220 | if render.GetHDREnabled() then // quick fix for HDR, not sure why it brightens the scene by 80% 221 | shadowAmount = shadowAmount * 0.2 222 | waterAmount = waterAmount * 0.2 223 | end 224 | surface_SetDrawColor(shadowAmount - waterAmount * 0.5, shadowAmount - waterAmount*0.3, shadowAmount - waterAmount*0.1, 255) 225 | surface_DrawRect(x * lightmapMultiplier, y * lightmapMultiplier, lightmapMultiplier, lightmapMultiplier) 226 | else 227 | if render.GetHDREnabled() then shadowAmount = shadowAmount * 0.2 end 228 | 229 | surface_SetDrawColor(shadowAmount, shadowAmount, shadowAmount, 255) 230 | surface_DrawRect(x * lightmapMultiplier, y * lightmapMultiplier, lightmapMultiplier, lightmapMultiplier) 231 | end 232 | end 233 | cam.End2D() 234 | render.PopRenderTarget() 235 | coroutine.yield() 236 | end 237 | hook.Remove("HUDPaint", "terrain_load") 238 | end 239 | 240 | function Terrain.GenerateLightmap(res, heightFunction) 241 | local co = coroutine.create(function() generateLightmap(res, heightFunction or Terrain.MathFunc) end) 242 | 243 | // dont run it all at once, it destroys ur game 244 | hook.Add("Think", "terrain_lightmap_gen", function() 245 | if coroutine.status(co) != "dead" then 246 | coroutine.resume(co) 247 | else 248 | hook.Remove("Think", "terrain_lightmap_gen") 249 | end 250 | end) 251 | end 252 | 253 | -- initialize a menu option 254 | hook.Add("PopulateToolMenu", "terrain_menu", function() 255 | spawnmenu.AddToolMenuOption("Utilities", "Procedural Terrain", "Procedural_Terrain", "Terrain", "", "",function(panel) 256 | panel:ClearControls() 257 | panel:Button("Terrain Menu", "terrain_menu") 258 | panel:Help("\nfunny stuff") 259 | panel:Button("removes all textures", "pp_texturize", "0") 260 | panel:Button("puts them back", "pp_texturize", "") 261 | end) 262 | end) 263 | 264 | -------------------------------------------------------------------------------- /lua/autorun/terrain_menu.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | if SERVER then return end 3 | 4 | 5 | CreateClientConVar("terrain_loddistance", "5000", true, false, "Distance to chunk that defines wheather to render high or low definition", 0) 6 | cvars.AddChangeCallback("terrain_loddistance", function(_, _, val) Terrain.LODDistance = val^2 end) 7 | 8 | local options 9 | concommand.Add("terrain_menu", function() 10 | options = options or table.Copy(Terrain.Variables) 11 | local changedTerrain = false 12 | 13 | // start creating visual design 14 | local mainFrame = vgui.Create("DFrame") 15 | mainFrame:SetSize(800, 400) 16 | mainFrame:SetTitle("Terrain Menu") 17 | mainFrame:Center() 18 | mainFrame:MakePopup() 19 | function mainFrame:OnClose() 20 | if changedTerrain then 21 | for k, v in ipairs(ents.FindByClass("terrain_chunk")) do 22 | //v:BuildCollision() // FUNNY MEMORY LEAK HAHAHAH 23 | v:GenerateMesh() 24 | v:GenerateTrees() 25 | v:SetRenderBounds(v:OBBMins(), v:OBBMaxs() + Vector(0, 0, 1000)) 26 | end 27 | 28 | Terrain.Material:SetTexture("$basetexture", Terrain.Variables.material_2) 29 | Terrain.Material:SetTexture("$basetexture2", Terrain.Variables.material_1) 30 | Terrain.WaterMaterial = Material(Terrain.Variables.material_3) 31 | end 32 | Terrain.Variables.temp_waterHeight = nil 33 | end 34 | 35 | -- some helper functions to make our life easier, I hope 36 | local Panel = FindMetaTable("Panel") 37 | function Panel:meeSlider(text, min, max, option, decimals, dock) -- NOTE! 'option' is a string! 38 | local slider = vgui.Create("DNumSlider", self) 39 | slider:Dock(dock) 40 | slider:DockMargin(0, 5, 0, 5) 41 | slider:SetSize(410, 15) 42 | slider:SetText(text) 43 | slider:SetMinMax(min, max) 44 | slider:SetValue(options[option]) 45 | slider:SetDecimals(decimals) 46 | slider:SetDark(true) 47 | if decimals ~= 0 then 48 | function slider:OnValueChanged(val) 49 | options[option] = val 50 | end 51 | else 52 | function slider:OnValueChanged(val) 53 | options[option] = math.Round(val) -- enforce integers 54 | end 55 | end 56 | return slider 57 | end 58 | 59 | function Panel:meeCheckbox(text, option, dock) -- NOTE! 'option' is a string! 60 | local checkbox = vgui.Create("DCheckBoxLabel", self) 61 | checkbox:Dock(dock) 62 | checkbox:DockMargin(0, 5, 0, 0) 63 | checkbox:SetSize(16, 16) 64 | checkbox:SetText(text) 65 | checkbox:SetValue(options[option]) 66 | checkbox:SetTextColor(Color(0, 0, 0)) 67 | function checkbox:OnChange(val) 68 | options[option] = val 69 | end 70 | return checkbox 71 | end 72 | 73 | function Panel:meeColorMixer(text, option, scale, dock) 74 | local mixer = vgui.Create("DColorMixer", self) 75 | mixer:DockMargin(0, 5, 0, 0) 76 | mixer:Dock(dock) 77 | mixer:SetSize(410, 150) 78 | mixer:SetPalette(true) 79 | mixer:SetLabel(text) 80 | mixer:SetAlphaBar(false) 81 | mixer:SetWangs(true) 82 | mixer:SetVector(options[option] / scale) -- Set the default color 83 | local factor = scale / 255 84 | function mixer:ValueChanged(col) 85 | options[option] = Vector(col.r * factor, col.g * factor, col.b * factor) 86 | end 87 | return mixer 88 | end 89 | 90 | function Panel:fastDiv(x, y, dock) 91 | div = vgui.Create("DPanel", self) 92 | div:Dock(dock) 93 | div:SetSize(x, y) 94 | function div:Paint() end 95 | return div 96 | end 97 | 98 | 99 | // the tabs 100 | local tabsFrame = vgui.Create("DPanel", mainFrame) 101 | tabsFrame:SetSize(425, 365) 102 | tabsFrame:SetPos(370, 30) 103 | tabsFrame.Paint = nil 104 | 105 | // the mountain tab, contains the submit & test buttons & height modifiers 106 | local function mountainTab(tabs) 107 | local scrollPanel = vgui.Create("DScrollPanel", tabs) 108 | local scrollEditTab = tabs:AddSheet("Mountains", scrollPanel, "icon16/world_edit.png").Tab 109 | 110 | -- a div to hold our docked stuff, we gotta leave space for what's below 111 | local normalOptions = scrollPanel:fastDiv(410,220,TOP) 112 | 113 | // editable sliders 114 | normalOptions:meeSlider("Main Mountain Height", 0, 200, "height_1", 1, TOP) 115 | normalOptions:meeSlider("Main Mountain Size", 1, 50, "noiseScale_1", 1, TOP) 116 | normalOptions:meeSlider("Secondary Mountain Height", 0, 200, "height_2", 1, TOP) 117 | normalOptions:meeSlider("Secondary Moutain Size", 1, 50, "noiseScale_2", 1, TOP) 118 | 119 | normalOptions:meeCheckbox("Flipped Chunks (must submit changes to see effect)", "cave", TOP) 120 | normalOptions:meeCheckbox("Leave Space for Flatgrass Building", "spawnArea", TOP) 121 | normalOptions:meeCheckbox("Clamp Noise (0 to 1 instead of -1 to 1)", "clampNoise", TOP) 122 | normalOptions:meeSlider("Terrain Seed", 0, 2^32, "seed", 0, TOP) 123 | normalOptions:meeSlider("Terrain Z Offset", 0, 100, "offset", 1, TOP) 124 | 125 | local material_grass = vgui.Create("DTextEntry", scrollPanel) 126 | material_grass:SetPos(0, 225) 127 | material_grass:SetSize(200, 20) 128 | material_grass:SetValue(options.material_1 or "gm_construct/grass1") 129 | material_grass:SetPlaceholderText("gm_construct/grass1") 130 | material_grass:SetTextColor(Color(0, 0, 0)) 131 | material_grass:SetUpdateOnType(true) 132 | function material_grass:OnValueChange(val) 133 | if val == "" then val = material_grass:GetPlaceholderText() end 134 | options.material_1 = val 135 | end 136 | 137 | local material_rock = vgui.Create("DTextEntry", scrollPanel) 138 | material_rock:SetPos(0, 250) 139 | material_rock:SetSize(200, 20) 140 | material_rock:SetValue(options.material_2 or "nature/rockfloor005a") 141 | material_rock:SetPlaceholderText("nature/rockfloor005a") 142 | material_rock:SetTextColor(Color(0, 0, 0)) 143 | material_rock:SetUpdateOnType(true) 144 | function material_rock:OnValueChange(val) 145 | if val == "" then val = material_rock:GetPlaceholderText() end 146 | options.material_2 = val 147 | end 148 | 149 | local grassText = vgui.Create("DLabel", scrollPanel) 150 | grassText:SetPos(205, 220) 151 | grassText:SetSize(250, 50) 152 | grassText:SetColor(Color(0, 0, 0)) 153 | grassText:SetText("<- (Advanced) Textures used for grass\nand rock blending, used mainly for biomes\n(Must be a .vmt texture)\n(Examples in description of addon)") 154 | end 155 | 156 | // the foilage tab, contains settings for trees & grass 157 | local function treeTab(tabs) 158 | local scrollPanel = vgui.Create("DScrollPanel", tabs) 159 | local scrollEditTab = tabs:AddSheet("Foliage", scrollPanel, "icon16/arrow_up.png").Tab 160 | 161 | local container = scrollPanel:fastDiv(410,270,FILL) -- needed since everything here is docked 162 | 163 | // editable sliders 164 | container:meeSlider("Tree Size", 1, 10, "treeHeight", 1, TOP) 165 | container:meeSlider("Tree Density (x*x per chunk)", 0, 10, "treeResolution", 0, TOP) 166 | container:meeSlider("Tree Slope Threshold", 0, 1, "treeThreshold", 3, TOP) -- TODO: invert this slider 167 | 168 | container:meeSlider("Grass Size", 5, 100, "grassSize", 3, BOTTOM) 169 | container:meeCheckbox("Generate Grass?", "generateGrass", BOTTOM) 170 | container:meeColorMixer("Tree Color", "treeColor", 5, BOTTOM) 171 | end 172 | 173 | local function functionTab(tabs) 174 | local scrollPanel = vgui.Create("DScrollPanel", tabs) 175 | local scrollEditTab = tabs:AddSheet("Custom", scrollPanel, "icon16/application_xp_terminal.png").Tab 176 | 177 | local funcText = vgui.Create("DLabel", scrollPanel) 178 | funcText:SetPos(0, 30) 179 | funcText:SetSize(410, 16) 180 | funcText:SetColor(Color(0, 0, 0)) 181 | funcText:SetText("Optional Custom GLua Height Function (Must return a number in 0-100 Range!)") 182 | 183 | local funcText = vgui.Create("DLabel", scrollPanel) 184 | funcText:SetPos(0, 255) 185 | funcText:SetSize(400, 16) 186 | function funcText:Paint(w, h) 187 | surface.SetDrawColor(0, 0, 0) 188 | surface.DrawRect(0, 0, w, h) 189 | end 190 | 191 | local func = vgui.Create("DTextEntry", scrollPanel) 192 | func:SetPos(0, 50) 193 | func:SetSize(400, 200) 194 | func:SetMultiline(true) 195 | func:SetText(options.customFunction or "") 196 | function func:OnChange() 197 | options.customFunction = nil 198 | if !LocalPlayer():IsSuperAdmin() then 199 | funcText:SetText(" You must be Superadmin to use this!") 200 | funcText:SetColor(Color(255, 0, 0)) 201 | return 202 | end 203 | if func:GetValue() != "" then 204 | local compiledFunction = CompileString("local x, y, chunk = ...\n" .. func:GetValue(), "Terrain Function", false) 205 | if isstring(compiledFunction) then 206 | funcText:SetText(" Error: " .. compiledFunction) 207 | funcText:SetColor(Color(255, 100, 100)) 208 | else 209 | local generatedFunction = setfenv(compiledFunction, Terrain.AllowedLibraries) 210 | local suc, msg = pcall(function() 211 | local compiled = generatedFunction(0, 0) 212 | if !isnumber(compiled) then 213 | funcText:SetText(" Error: Return value must be a number") 214 | funcText:SetColor(Color(255, 100, 100)) 215 | return 216 | end 217 | funcText:SetText(" Success") 218 | funcText:SetColor(Color(100, 255, 100)) 219 | options.customFunction = func:GetValue() 220 | end) 221 | if !suc then 222 | funcText:SetText(" Error: " .. msg) 223 | funcText:SetColor(Color(255, 100, 100)) 224 | end 225 | end 226 | else 227 | funcText:SetText(" No Function Defined") 228 | funcText:SetColor(Color(255, 255, 255)) 229 | end 230 | end 231 | func:OnChange() 232 | 233 | local customChoices = vgui.Create("DComboBox", scrollPanel) 234 | customChoices:SetPos(0, 0) 235 | customChoices:SetSize(200, 20) 236 | customChoices:SetText("Terrain Function Examples") 237 | customChoices:AddChoice("Ripple", "return (sin(sqrt((x * 5)^2 + (y * 5)^2)) + 1) * 2") 238 | customChoices:AddChoice("Sine and Cosine Hills", "return (sin(x * 5) + cos(y * 5)) * 5") 239 | customChoices:AddChoice("Volcano", "return min(1 / (((x / 15)^2 + (y / 15)^2)), 50)") 240 | customChoices:AddChoice("Schuh Mountain", "return -(x^2+y^2)^0.5 * 8 + 60") 241 | customChoices:AddChoice("Plus Shaped Valley", "return x^2 * y^2") 242 | customChoices:AddChoice("Big Inverse Dome", "return 2^abs(x) + 2^abs(y)") 243 | customChoices:AddChoice("Dome Single", "return sqrt(1 - (x^2 + y^2)) * 10") 244 | customChoices:AddChoice("Domes Infinite", "local a = ((x * 0.5 + 0.5) % 1) * 2 - 1\nlocal b = ((y * 0.5 + 0.5) % 1) * 2 - 1\nreturn sqrt(1 - (a^2 + b^2)) * 10") 245 | customChoices:AddChoice("BlackHole", "return -30 / sqrt(x^2+y^2) + 100") 246 | customChoices:AddChoice("Spiral", "return (60/7.5) * sqrt(x^2 + y^2) + 0.18 * cos((80/7.5) * sqrt(x^2+y^2) + atan2(x,y)) * 60/7.5") 247 | customChoices:AddChoice("Checkerboard", "return (Round(x)%2 + Round(y)%2) % 2 * 10") 248 | customChoices:AddChoice("Basic Perlin Implementation", "return (Simplex.Noise2D(x / 10, y / 10) + 1) * 20 + Simplex.Noise2D(x, y)") 249 | customChoices:AddChoice("Avatar: The Last Airbender", "return Simplex.Noise2D(x / 3, y / 3) * 200") 250 | customChoices:AddChoice("Mee Graph", "local values = \n{1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,1,0,0,0,1,1,0,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,0,0}\nlocal localX = -floor(((x * 4) - 26) / 2)\nlocal localY = (floor((y + 5) * 2) * 26)\nif localX < 0 or localX > 26 then\nreturn 0\nend\nreturn (values[localX + localY] or 0) * 10") 251 | function customChoices:OnSelect(index, text, data) 252 | func:SetText(data) 253 | func:OnChange() 254 | end 255 | 256 | local spawnBox = vgui.Create("DCheckBoxLabel", scrollPanel) 257 | spawnBox:SetPos(207, 3) 258 | spawnBox:SetSize(16, 16) 259 | spawnBox:SetText("Leave Space for Flatgrass Building?") 260 | spawnBox:SetValue(options.spawnArea) 261 | spawnBox:SetTextColor(Color(0, 0, 0)) 262 | function spawnBox:OnChange(val) 263 | options.spawnArea = val 264 | end 265 | end 266 | 267 | -- the water tab, for water stuff. 268 | local function waterTab(tabs) 269 | local scrollPanel = vgui.Create("DScrollPanel", tabs) 270 | local scrollEditTab = tabs:AddSheet("Water", scrollPanel, "icon16/water.png").Tab 271 | 272 | local waterHeight 273 | local waterEnabled = scrollPanel:meeCheckbox("Enable Water?", "waterHeight", TOP) 274 | function waterEnabled:OnChange(val) 275 | if val then 276 | options.waterHeight = waterHeight:GetValue() 277 | Terrain.Variables.temp_waterHeight = waterHeight:GetValue() 278 | else 279 | Terrain.Variables.temp_waterHeight = -math.huge 280 | options.waterHeight = nil 281 | end 282 | end 283 | 284 | waterHeight = scrollPanel:meeSlider("Water Height", -12765, 12765, "waterHeight", 0, TOP) 285 | function waterHeight:OnValueChanged(val) -- special 286 | options.waterHeight = val 287 | Terrain.Variables.temp_waterHeight = val 288 | waterEnabled:SetValue(true) 289 | end 290 | 291 | local waterText = vgui.Create("DLabel", scrollPanel) 292 | waterText:SetPos(0, 150) 293 | waterText:SetSize(250, 20) 294 | waterText:SetColor(Color(0, 0, 0)) 295 | waterText:SetText("Water Material (better with transparent materials)") 296 | 297 | local material_water = vgui.Create("DTextEntry", scrollPanel) 298 | material_water:SetPos(0, 170) 299 | material_water:SetSize(300, 20) 300 | material_water:SetValue(options.material_3 or "procedural_terrain/water/water_warp") 301 | material_water:SetPlaceholderText("procedural_terrain/water/water_warp") 302 | material_water:SetTextColor(Color(0, 0, 0)) 303 | material_water:SetUpdateOnType(true) 304 | function material_water:OnValueChange(val) 305 | if val == "" then val = material_water:GetPlaceholderText() end 306 | options.material_3 = val 307 | end 308 | 309 | // instant kill water 310 | //scrollPanel:meeCheckbox("Water = Instant Death", "water_kill", TOP) 311 | local water_var = vgui.Create("DCheckBoxLabel", scrollPanel) 312 | water_var:SetPos(0, 100) 313 | water_var:SetSize(16, 16) 314 | water_var:SetText("Water = Instant Death") 315 | water_var:SetValue(options.water_kill) 316 | water_var:SetTextColor(Color(0, 0, 0)) 317 | function water_var:OnChange(val) 318 | options.water_kill = val 319 | end 320 | 321 | // ignite water 322 | local water_var = vgui.Create("DCheckBoxLabel", scrollPanel) 323 | water_var:SetPos(0, 125) 324 | water_var:SetSize(16, 16) 325 | water_var:SetText("Catch on fire if touch water? (good for lava)") 326 | water_var:SetValue(options.water_ignite) 327 | water_var:SetTextColor(Color(0, 0, 0)) 328 | function water_var:OnChange(val) 329 | options.water_ignite = val 330 | end 331 | 332 | scrollPanel:meeSlider("Water Viscocity", -10, 10, "water_viscosity", 2, TOP) 333 | scrollPanel:meeSlider("Buoyancy Multiplier", -100, 100, "water_buoyancy", 2, TOP) 334 | end 335 | 336 | // saving & loading done here 337 | local function saveTab(tabs) 338 | local scrollPanel = vgui.Create("DScrollPanel", tabs) 339 | local scrollEditTab = tabs:AddSheet("Saved Presets", scrollPanel, "icon16/disk.png").Tab 340 | 341 | local saved_presets = vgui.Create("DComboBox", scrollPanel) 342 | saved_presets:SetPos(0, 0) 343 | saved_presets:SetSize(240, 20) 344 | saved_presets:SetText("Saved Presets") 345 | 346 | // create directiory for the files 347 | file.CreateDir("terrain_presets/") 348 | 349 | // get files in directory and add them as choices 350 | local function refreshPresets() 351 | local files, directories = file.Find("terrain_presets/*", "DATA") 352 | for _, filename in ipairs(files) do 353 | local json_file = file.Read("terrain_presets/" .. filename, "DATA") 354 | saved_presets:AddChoice(string.Left(filename, #filename - 5), json_file) 355 | end 356 | end 357 | 358 | refreshPresets() 359 | function saved_presets:OnSelect(_, value, data) 360 | options = util.JSONToTable(data) 361 | end 362 | 363 | local saveButton = vgui.Create("DButton", scrollPanel) 364 | saveButton:SetPos(250, 10) 365 | saveButton:SetSize(150, 20) 366 | saveButton:SetText("Save Current Preset") 367 | function saveButton:DoClick() 368 | local ok = vgui.Create("DFrame") 369 | ok:SetTitle("Save File") 370 | ok:SetSize(200, 100) 371 | ok:Center() 372 | ok:MakePopup() 373 | ok:SetBackgroundBlur(true) 374 | 375 | local preset_name = vgui.Create("DTextEntry", ok) 376 | preset_name:SetPos(10, 50) 377 | preset_name:SetSize(110, 30) 378 | preset_name:SetText("presetname") 379 | 380 | local button = vgui.Create("DButton", ok) 381 | button:SetPos(120, 50) 382 | button:SetSize(70, 30) 383 | button:SetText("Submit") 384 | function button:DoClick() 385 | local filename = preset_name:GetValue() 386 | if filename == "" then filename = "presetname" end 387 | if system.IsLinux() then filename = string.lower(filename) end // linux only supports lowercase filenames 388 | local json_data = util.TableToJSON(Terrain.Variables) 389 | file.Write("terrain_presets/" .. filename .. ".json", json_data) 390 | saved_presets:AddChoice(filename, json_data) 391 | ok:Close() 392 | end 393 | end 394 | 395 | local deleteButton = vgui.Create("DButton", scrollPanel) 396 | deleteButton:SetPos(250, 40) 397 | deleteButton:SetSize(150, 20) 398 | deleteButton:SetText("Delete Selected Preset") 399 | function deleteButton:DoClick() 400 | local ok = vgui.Create("DFrame") 401 | ok:SetTitle("Are you sure?") 402 | ok:SetSize(200, 100) 403 | ok:Center() 404 | ok:MakePopup() 405 | ok:SetBackgroundBlur(true) 406 | 407 | local button = vgui.Create("DButton", ok) 408 | button:SetPos(10, 50) 409 | button:SetSize(70, 30) 410 | button:SetText("Yes") 411 | function button:DoClick() 412 | file.Delete("terrain_presets/" .. saved_presets:GetSelected() .. ".json") 413 | // no RemoveChoice() ? 414 | saved_presets:Clear() 415 | refreshPresets() 416 | ok:Close() 417 | end 418 | 419 | local button = vgui.Create("DButton", ok) 420 | button:SetPos(120, 50) 421 | button:SetSize(70, 30) 422 | button:SetText("No") 423 | function button:DoClick() 424 | ok:Close() 425 | end 426 | end 427 | end 428 | 429 | // minimap ortho view 430 | local zoomAmount = 1 431 | local renderBox = vgui.Create("DPanel", mainFrame) 432 | renderBox:SetSize(350, 360) 433 | renderBox:SetPos(10, 30) 434 | function renderBox:PaintOver(w, h) 435 | surface.SetDrawColor(100, 200, 100) 436 | surface.DrawOutlinedRect(0, 0, w, h, 5) 437 | surface.DrawOutlinedRect(35, 340, 280, 20, 1) 438 | surface.SetDrawColor(50, 100, 50) 439 | surface.DrawRect(36, 341, 278, 18) 440 | end 441 | function renderBox:Paint(w, h) 442 | local x = mainFrame:GetX() + renderBox:GetX() 443 | local y = mainFrame:GetY() + renderBox:GetY() 444 | local old = DisableClipping(true) 445 | local orthoScale = 2^14 / zoomAmount 446 | render.RenderView({ 447 | origin = Angle(45, CurTime() * 10, 0):Forward() * -2^14 + Vector(0, 0, 3000), 448 | angles = Angle(60, CurTime() * 10, 0), 449 | x = x, y = y, 450 | w = w, h = h, 451 | zfar = 2^16, 452 | ortho = { 453 | left = -orthoScale, 454 | right = orthoScale, 455 | top = -orthoScale, 456 | bottom = orthoScale, 457 | } 458 | }) 459 | DisableClipping(old) 460 | end 461 | // ortho zoom slider 462 | local renderZoom = vgui.Create("DNumSlider", mainFrame) 463 | renderZoom:SetPos(50, 374) 464 | renderZoom:SetSize(300, 10) 465 | renderZoom:SetText("Zoom") 466 | renderZoom:SetMinMax(1, 10) 467 | renderZoom:SetValue(1) 468 | renderZoom:SetDecimals(0) 469 | function renderZoom:OnValueChanged(val) 470 | zoomAmount = val 471 | end 472 | 473 | local tabs = vgui.Create("DPropertySheet", tabsFrame) 474 | tabs:Dock(FILL) 475 | 476 | mountainTab(tabs) 477 | functionTab(tabs) 478 | treeTab(tabs) 479 | waterTab(tabs) 480 | saveTab(tabs) 481 | 482 | // test & submit changes button 483 | if LocalPlayer():IsSuperAdmin() then 484 | local submitButton = vgui.Create("DButton", tabsFrame) 485 | submitButton:SetPos(250, 305) 486 | submitButton:SetSize(150, 50) 487 | submitButton:SetIcon("models/weapons/v_slam/new light1") 488 | submitButton:SetText(" Submit Changes") 489 | function submitButton:DoClick() 490 | net.Start("TERRAIN_SEND_DATA") 491 | net.WriteTable(options) // writetable since value types may change during development 492 | net.SendToServer() 493 | changedTerrain = false 494 | end 495 | end 496 | 497 | local testButton = vgui.Create("DButton", tabsFrame) 498 | testButton:SetPos(10, 305) 499 | testButton:SetSize(150, 50) 500 | testButton:SetIcon("models/weapons/v_slam/new light2") 501 | testButton:SetText("Test Changes") 502 | function testButton:DoClick() 503 | local newFunction = Terrain.BuildMathFunc(options) 504 | 505 | // reload all chunks with the new function 506 | for k, v in ipairs(ents.FindByClass("terrain_chunk")) do 507 | //v:BuildCollision(newFunction) // this shit crashes u 508 | v:GenerateMesh(newFunction) 509 | v:GenerateTrees(newFunction, options) 510 | v:SetRenderBounds(v:OBBMins() * Vector(1, 1, -1), v:OBBMaxs() + Vector(0, 0, 1000)) 511 | end 512 | 513 | Terrain.Material:SetTexture("$basetexture", options.material_2) 514 | Terrain.Material:SetTexture("$basetexture2", options.material_1) 515 | Terrain.WaterMaterial = Material(options.material_3) 516 | 517 | changedTerrain = true 518 | end 519 | end) -------------------------------------------------------------------------------- /lua/autorun/terrain_models.lua: -------------------------------------------------------------------------------- 1 | if game.GetMap() != "gm_flatgrass" then return end 2 | 3 | AddCSLuaFile() 4 | 5 | hook.Add("InitPostEntity", "terrain_modelinit", function() 6 | Terrain.TreeModels = { 7 | "models/procedural_terrain/foliage/tree_pine04.mdl", 8 | "models/procedural_terrain/foliage/tree_pine05.mdl", 9 | "models/procedural_terrain/foliage/tree_pine06.mdl", 10 | "models/procedural_terrain/foliage/tree_pine_large.mdl", 11 | "models/procedural_terrain/foliage/rock_coast02a.mdl", 12 | } 13 | 14 | for k, v in ipairs(Terrain.TreeModels) do 15 | util.PrecacheModel(v) 16 | end 17 | 18 | // client = visual meshes, server = physics meshes 19 | if CLIENT then 20 | local tree_material = Material("procedural_terrain/foliage/arbre01.vmt") 21 | Terrain.TreeMaterials = { 22 | tree_material, 23 | tree_material, 24 | tree_material, 25 | tree_material, 26 | Material("procedural_terrain/foliage/coastrock02.vmt"), 27 | } 28 | 29 | Terrain.TreeMeshes = {} 30 | Terrain.TreeMeshes_Low = {} 31 | 32 | // build and cache tree models 33 | for k, treeModel in ipairs(Terrain.TreeModels) do 34 | Terrain.TreeMeshes[k] = Mesh() 35 | local mesh1 = util.GetModelMeshes(treeModel) // model doesnt exist on client?! replace with a wood pole.. 36 | if !mesh1 then treeModel = "models/props_docks/dock02_pole02a.mdl" end 37 | Terrain.TreeMeshes[k]:BuildFromTriangles(util.GetModelMeshes(treeModel)[1].triangles) 38 | 39 | Terrain.TreeMeshes_Low[k] = Mesh() 40 | Terrain.TreeMeshes_Low[k]:BuildFromTriangles(util.GetModelMeshes(treeModel, 8)[1].triangles) 41 | end 42 | 43 | end 44 | 45 | // physmesh generation 46 | // there is no way to get the physmesh data of a model without creating a prop??? wtf??? 47 | Terrain.TreePhysMeshes = {} 48 | for k, v in ipairs(Terrain.TreeModels) do 49 | if SERVER then 50 | local tree = ents.Create("prop_physics") 51 | tree:SetModel(v) 52 | tree:Spawn() 53 | Terrain.TreePhysMeshes[k] = tree:GetPhysicsObject():GetMesh() 54 | SafeRemoveEntity(tree) 55 | end 56 | //else 57 | // if !util.GetModelMeshes(v) then v = "models/props_docks/dock02_pole02a.mdl" end 58 | // local tree = ents.CreateClientProp(v) 59 | // Terrain.TreePhysMeshes[k] = tree:GetPhysicsObject():GetMesh() 60 | // SafeRemoveEntity(tree) 61 | //end 62 | end 63 | end) -------------------------------------------------------------------------------- /lua/autorun/terrain_water.lua: -------------------------------------------------------------------------------- 1 | if game.GetMap() != "gm_flatgrass" then return end 2 | 3 | local gravity_convar = GetConVar("sv_gravity") 4 | 5 | 6 | // stolen tri intersection function lol 7 | local function intersectRayWithTriangle(rayOrigin, rayDir, tri1, tri2, tri3) 8 | local point1 = tri1 9 | local edge1 = tri2 - point1 10 | local edge2 = tri3 - point1 11 | local h = rayDir:Cross(edge2) 12 | local a = edge1:Dot(h) 13 | if a > 0 then 14 | return nil // This ray is parallel to this triangle. 15 | end 16 | 17 | local f = 1 / a 18 | local s = rayOrigin - point1 19 | local u = f * s:Dot(h) 20 | 21 | if u < 0 || u > 1 then 22 | return nil 23 | end 24 | 25 | local q = s:Cross(edge1) 26 | local v = f * rayDir:Dot(q) 27 | if v < 0 || u + v > 1 then 28 | return nil 29 | end 30 | 31 | // At this stage we can compute t to find out where the intersection point is on the line. 32 | local t = f * edge2:Dot(q) 33 | if (t > 0) then // ray intersection 34 | return rayOrigin + rayDir * t; 35 | end 36 | 37 | return nil 38 | end 39 | 40 | local trace_filter = function(e) return e:GetClass() == "terrain_chunk" end 41 | local function getHeight(x, y) 42 | return Vector(x, y, Terrain.MathFunc(x, y) + Terrain.ZOffset) 43 | end 44 | 45 | local function intersectTerrain(pos) 46 | local worldx = pos[1] 47 | local worldy = pos[2] 48 | local math = math 49 | local lerpXBottom = math.floor(worldx / Terrain.ChunkSize - 0.001) * Terrain.ChunkSize 50 | local lerpYBottom = math.floor(worldy / Terrain.ChunkSize - 0.001) * Terrain.ChunkSize 51 | local lerpXTop = math.ceil(worldx / Terrain.ChunkSize) * Terrain.ChunkSize 52 | local lerpYTop = math.ceil(worldy / Terrain.ChunkSize) * Terrain.ChunkSize 53 | 54 | // find where to cast the shadow ray 55 | local v = Vector(worldx, worldy, 25601) 56 | local shadowPos = intersectRayWithTriangle(v, Vector(0, 0, -1), getHeight(lerpXTop, lerpYBottom), getHeight(lerpXBottom, lerpYBottom), getHeight(lerpXBottom, lerpYTop)) // 25601 = max height 57 | shadowPos = shadowPos or intersectRayWithTriangle(v, Vector(0, 0, -1), getHeight(lerpXTop, lerpYTop), getHeight(lerpXTop, lerpYBottom), getHeight(lerpXBottom, lerpYTop)) 58 | 59 | return shadowPos[3] > pos[3] 60 | end 61 | 62 | local function inWater(pos) 63 | local waterHeight = Terrain.Variables.waterHeight 64 | if !waterHeight then return false end 65 | 66 | if intersectTerrain(pos) then return end 67 | return pos[3] < waterHeight 68 | end 69 | 70 | // water screenspace overlay 71 | local changedWater = false 72 | hook.Add("RenderScreenspaceEffects", "Terrain_PP", function() 73 | if inWater(EyePos()) then 74 | DrawMaterialOverlay(Terrain.Variables.material_3, 0.05) 75 | DrawMaterialOverlay("effects/water_warp01", 0.1) 76 | 77 | if !changedWater then 78 | changedWater = true 79 | LocalPlayer():EmitSound("Physics.WaterSplash") 80 | LocalPlayer():SetDSP(14, true) 81 | end 82 | elseif changedWater then 83 | changedWater = false 84 | LocalPlayer():EmitSound("Physics.WaterSplash") 85 | LocalPlayer():SetDSP(0, true) 86 | end 87 | end) 88 | 89 | // swim code yoinked from gwater, thanks again kodya 90 | // player animations 91 | hook.Add("CalcMainActivity", "Terrain_Swimming", function(ply) 92 | if !inWater(ply:GetPos()) or ply:IsOnGround() or ply:InVehicle() then return end 93 | return ACT_MP_SWIM, -1 94 | end) 95 | 96 | // main movement 97 | hook.Add("Move", "Terrain_Swimming", function(ply, move) 98 | if !inWater(ply:GetPos()) then return end 99 | if SERVER then 100 | if Terrain.Variables.water_kill and ply:Alive() then 101 | ply:Kill() 102 | return 103 | end 104 | local onfire = ply:IsOnFire() 105 | if ply:Alive() then 106 | if Terrain.Variables.water_ignite then 107 | if !onfire then 108 | ply:Ignite(1) 109 | end 110 | elseif onfire then 111 | ply:Extinguish() 112 | end 113 | end 114 | end 115 | 116 | local vel = move:GetVelocity() 117 | local ang = move:GetMoveAngles() 118 | 119 | local acel = 120 | (ang:Forward() * move:GetForwardSpeed()) + 121 | (ang:Right() * move:GetSideSpeed()) + 122 | (ang:Up() * move:GetUpSpeed()) 123 | 124 | local aceldir = acel:GetNormalized() 125 | local acelspeed = math.min(acel:Length(), ply:GetMaxSpeed()) 126 | acel = aceldir * acelspeed * 2 127 | 128 | if bit.band(move:GetButtons(), IN_JUMP) ~= 0 then 129 | acel.z = acel.z + ply:GetMaxSpeed() 130 | end 131 | 132 | vel = vel + acel * FrameTime() * (1 / (Terrain.Variables.water_viscosity * 0.5 + 0.5)) 133 | vel = vel * (1 - FrameTime() * 2) + Vector(0, 0, Terrain.Variables.water_buoyancy - 1) 134 | 135 | local pgrav = ply:GetGravity() == 0 and 1 or ply:GetGravity() 136 | local gravity = pgrav * gravity_convar:GetFloat() * 0.5 137 | vel.z = vel.z + FrameTime() * gravity 138 | 139 | move:SetVelocity(vel * 0.99) 140 | end) 141 | 142 | // secondary, final movement 143 | hook.Add("FinishMove", "Terrain_Swimming", function(ply, move) 144 | if !inWater(ply:GetPos()) then return end 145 | local vel = move:GetVelocity() 146 | local pgrav = ply:GetGravity() == 0 and 1 or ply:GetGravity() 147 | local gravity = pgrav * gravity_convar:GetFloat() * 0.5 148 | 149 | vel.z = vel.z + FrameTime() * gravity 150 | move:SetVelocity(vel) 151 | end) 152 | 153 | // serverside stuff now 154 | if CLIENT then 155 | Terrain.WaterMaterial = Material("procedural_terrain/water/water_warp") 156 | local waterMatrix = Matrix() 157 | waterMatrix:SetScale(Vector(Terrain.ChunkResScale * Terrain.Resolution, Terrain.ChunkResScale * Terrain.Resolution)) 158 | 159 | local uvscale = 100 160 | local waterMesh = Mesh() 161 | waterMesh:BuildFromTriangles({ 162 | {pos = Vector(-1, -1, 0), u = 0, v = uvscale}, 163 | {pos = Vector(1, 1, 0), u = uvscale, v = 0}, 164 | {pos = Vector(1, -1, 0), u = uvscale, v = uvscale}, 165 | 166 | {pos = Vector(-1, -1, 0), u = 0, v = uvscale}, 167 | {pos = Vector(-1, 1, 0), u = 0, v = 0}, 168 | {pos = Vector(1, 1, 0), u = uvscale, v = 0}, 169 | 170 | {pos = Vector(-1, -1, 0), u = 0, v = uvscale}, 171 | {pos = Vector(1, -1, 0), u = uvscale, v = uvscale}, 172 | {pos = Vector(1, 1, 0), u = uvscale, v = 0}, 173 | 174 | {pos = Vector(-1, -1, 0), u = 0, v = uvscale}, 175 | {pos = Vector(1, 1, 0), u = uvscale, v = 0}, 176 | {pos = Vector(-1, 1, 0), u = 0, v = 0}, 177 | }) 178 | 179 | hook.Add("PreDrawTranslucentRenderables", "Terrain_Water", function(_, sky) 180 | if sky or intersectTerrain(EyePos()) then return end 181 | local waterHeight = Terrain.Variables.temp_waterHeight or Terrain.Variables.waterHeight 182 | if waterHeight then 183 | waterMatrix:SetTranslation(Vector(0, 0, waterHeight)) 184 | render.SetMaterial(Terrain.WaterMaterial) 185 | cam.PushModelMatrix(waterMatrix) 186 | waterMesh:Draw() 187 | cam.PopModelMatrix() 188 | end 189 | end) 190 | return 191 | end 192 | 193 | hook.Add("PlayerFootstep", "Terrain_Water", function(ply, pos, foot, sound, volume, rf) 194 | if inWater(ply:GetPos()) then 195 | ply:EmitSound(foot == 0 and "Water.StepLeft" or "Water.StepRight", nil, nil, volume, CHAN_BODY) // volume doesnt work for some reason.. oh well 196 | return true 197 | end 198 | end ) 199 | 200 | // no fall damage in fake water 201 | hook.Add("GetFallDamage", "Terrain_Water", function(ply, speed) 202 | // for some reason player position isnt fully accurate when this is called 203 | local tr = util.TraceHull({ 204 | start = ply:GetPos(), 205 | endpos = ply:GetPos() + ply:GetVelocity(), 206 | maxs = ply:OBBMaxs(), 207 | mins = ply:OBBMins(), 208 | filter = ply 209 | }) 210 | if tr.Hit and inWater(tr.HitPos) then return 0 end 211 | end) 212 | 213 | 214 | if SERVER then 215 | local IsValid = IsValid 216 | local Clamp = math.Clamp 217 | local timer = timer 218 | local positions = {} 219 | local valid_materials = { 220 | ["floating_metal_barrel"] = true, 221 | ["wood"] = true, 222 | ["wood_crate"] = true, 223 | ["wood_furniture"] = true, 224 | ["rubbertire"] = true, 225 | ["wood_solid"] = true, 226 | ["plastic"] = true, 227 | ["watermelon"] = true, 228 | ["default"] = true, 229 | ["cardboard"] = true, 230 | ["paper"] = true, 231 | ["popcan"] = true, 232 | } 233 | hook.Add("Think", "terrain_buoyancy", function() 234 | local waterHeight = Terrain.Variables.waterHeight // add 5 so the objects dont stay all the way under water 235 | if !waterHeight then return end 236 | local entities = ents.FindByClass("prop_*") 237 | for _, prop in ipairs(entities) do 238 | local phys = prop:GetPhysicsObject() 239 | if !phys:IsValid() or phys:IsAsleep() then continue end 240 | 241 | local is_airboat = prop:GetClass() == "prop_vehicle_airboat" 242 | if valid_materials[phys:GetMaterial()] or is_airboat then 243 | local mins = prop:OBBMins() 244 | local maxs = prop:OBBMaxs() 245 | 246 | // do not calculate object, we know it is too far and not near the water 247 | local p = prop:GetPos()[3] - 1 248 | if p - math.abs(mins[3]) > waterHeight and p - math.abs(maxs[3]) > waterHeight then 249 | continue 250 | end 251 | 252 | // why is the airboat size fucked? 253 | if is_airboat then 254 | mins = mins * 0.5 255 | maxs = maxs * 0.5 256 | mins[3] = 0 257 | maxs[3] = 0 258 | end 259 | 260 | // so many points 261 | positions[1] = Vector(mins[1], mins[2], mins[3]) 262 | positions[2] = Vector(mins[1], mins[2], maxs[3]) 263 | positions[3] = Vector(mins[1], maxs[2], mins[3]) 264 | positions[4] = Vector(maxs[1], mins[2], mins[3]) 265 | positions[5] = Vector(mins[1], maxs[2], maxs[3]) 266 | positions[6] = Vector(maxs[1], maxs[2], mins[3]) 267 | positions[7] = Vector(maxs[1], mins[2], maxs[3]) 268 | positions[8] = Vector(maxs[1], maxs[2], maxs[3]) 269 | 270 | local prop_inwater = false 271 | local should_sleep = (phys:GetVelocity() + phys:GetAngleVelocity()):Length() < 1 and !prop:IsPlayerHolding() 272 | local viscosity = Terrain.Variables.water_viscosity 273 | local buoyancy = Terrain.Variables.water_buoyancy 274 | for _, pos in ipairs(positions) do 275 | local world_pos = prop:LocalToWorld(pos) 276 | if inWater(world_pos) then 277 | if is_airboat then 278 | phys:ApplyForceOffset(Vector(0, 0, phys:GetMass() * math.min(((waterHeight - world_pos[3]) * 0.75 * buoyancy), 2 * buoyancy)), world_pos) 279 | phys:ApplyForceCenter(phys:GetMass() * phys:GetVelocity() * viscosity * -0.001) //dampen very small bit for airboats 280 | else 281 | phys:ApplyForceOffset(Vector(0, 0, phys:GetMass() * (math.min(((waterHeight - world_pos[3]) * 0.1 * buoyancy), 3 * buoyancy))), world_pos) 282 | phys:ApplyForceCenter(phys:GetMass() * phys:GetVelocity() * viscosity * -0.003) //dampen a bit 283 | end 284 | phys:AddAngleVelocity(phys:GetAngleVelocity() * viscosity * -0.01) 285 | prop_inwater = true 286 | //debugoverlay.Sphere(world_pos, 10, 0.1) 287 | end 288 | end 289 | 290 | if prop_inwater and should_sleep then 291 | phys:Sleep() 292 | end 293 | end 294 | end 295 | end) 296 | end -------------------------------------------------------------------------------- /lua/entities/terrain_chunk.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | AddCSLuaFile() 4 | 5 | ENT.Type = "anim" 6 | ENT.Base = "base_gmodentity" 7 | 8 | ENT.Category = "Deform Test" 9 | ENT.PrintName = "Terrain Chunk" 10 | ENT.Author = "Mee" 11 | ENT.Purpose = "" 12 | ENT.Instructions = "" 13 | ENT.Spawnable = false 14 | 15 | if game.GetMap() != "gm_flatgrass" then return end 16 | 17 | function ENT:SetupDataTables() 18 | self:NetworkVar("Int", 0, "ChunkX") 19 | self:NetworkVar("Int", 1, "ChunkY") 20 | self:NetworkVar("Bool", 0, "Flipped") 21 | end 22 | 23 | Terrain = Terrain or {} 24 | 25 | if CLIENT then 26 | //Terrain.Material = Material("nature/blendrocksgrass006a") 27 | Terrain.Material = CreateMaterial("NatureblendTerrain01", "WorldVertexTransition", { 28 | ["$basetexture"] = "nature/rockfloor005a", 29 | ["$surfaceprop"] = "rock", 30 | ["$basetexture2"] = "gm_construct/grass1", 31 | ["$surfaceprop2"] = "dirt", 32 | ["$seamless_scale"] = 0.002, 33 | ["$nocull"] = 1, 34 | }) 35 | end 36 | 37 | function ENT:GetTreeData(pos, data, heightFunction) 38 | local heightFunction = heightFunction or Terrain.MathFunc 39 | 40 | // pos is local to chunk 41 | local x = pos[1] 42 | local y = pos[2] 43 | 44 | // chunk offset in world space 45 | local chunkoffsetx = self:GetChunkX() * Terrain.ChunkResScale 46 | local chunkoffsety = self:GetChunkY() * Terrain.ChunkResScale 47 | 48 | // no trees in spawn area (1000x1000 hu square) 49 | if data.spawnArea and math.abs(x + chunkoffsetx) < 1500 and math.abs(y + chunkoffsety) < 1500 then return nil end 50 | 51 | // the height of the vertex using the math function 52 | local vertexHeight = heightFunction(x + chunkoffsetx, y + chunkoffsety) 53 | local middleHeight = Vector(0, 0, vertexHeight) 54 | 55 | local finalPos = Vector(x + chunkoffsetx, y + chunkoffsety, vertexHeight + Terrain.ZOffset - 25.6 * data.treeHeight) // pushed down 25.6 units, (height of the base of the tree model) 56 | 57 | // no trees under water 58 | if data.waterHeight and finalPos[3] < data.waterHeight then return nil end 59 | 60 | // calculate the smoothed normal, if it is extreme, do not place a tree 61 | local smoothedNormal = Vector() 62 | for cornery = 0, 1 do 63 | for cornerx = 0, 1 do 64 | // get 4 corners in a for loop ranging from -1 to 1 65 | local cornerx = (cornerx - 0.5) * 2 66 | local cornery = (cornery - 0.5) * 2 67 | 68 | // get the height of the 0x triangle 69 | local cornerWorldy = (y + cornery) 70 | local cornerHeight = heightFunction(x + chunkoffsetx, cornerWorldy + chunkoffsety) 71 | local middleXPosition = Vector(0, cornery, cornerHeight) 72 | 73 | // get the height of the 0y triangle 74 | local cornerWorldx = (x + cornerx) 75 | local cornerHeight = heightFunction(cornerWorldx + chunkoffsetx, y + chunkoffsety) 76 | local middleYPosition = Vector(cornerx, 0, cornerHeight) 77 | 78 | // we now have 3 points, construct a triangle from this and add the normal to the average normal 79 | local triNormal = (middleYPosition - middleHeight):Cross(middleXPosition - middleHeight) * cornerx * cornery 80 | smoothedNormal = smoothedNormal + triNormal 81 | end 82 | end 83 | 84 | smoothedNormal = smoothedNormal:GetNormalized() 85 | if smoothedNormal[3] < data.treeThreshold then return nil end // remove trees on extreme slopes 86 | 87 | return finalPos, smoothedNormal 88 | end 89 | 90 | function ENT:GenerateTrees(heightFunction, data) 91 | local data = data or Terrain.Variables 92 | local heightFunction = heightFunction or Terrain.MathFunc 93 | local treeResolution = data.treeResolution or Terrain.Variables.treeResolution 94 | 95 | self.TreeMatrices = {} 96 | self.TreeModels = {} 97 | self.TreeShading = {} 98 | self.TreeColors = {} 99 | 100 | local treeMultiplier = Terrain.ChunkResolution / data.treeResolution * Terrain.ChunkSize 101 | local randomIndex = 0 102 | local chunkIndex = tostring(self:GetChunkX()) .. tostring(self:GetChunkY()) 103 | for y = 0, data.treeResolution - 1 do 104 | for x = 0, data.treeResolution - 1 do 105 | randomIndex = randomIndex + 1 106 | local m = Matrix() 107 | 108 | // generate seeded random position for tree 109 | local randseedx = util.SharedRandom("TerrainSeedX" .. chunkIndex, 0, 1, randomIndex) 110 | local randseedy = util.SharedRandom("TerrainSeedY" .. chunkIndex, 0, 1, randomIndex) 111 | local randPos = Vector(randseedx, randseedy) * data.treeResolution * treeMultiplier 112 | 113 | local finalPos, smoothedNormal = self:GetTreeData(randPos, data, heightFunction) 114 | if !finalPos then continue end 115 | 116 | m:SetTranslation(finalPos) 117 | m:SetAngles(Angle(0, randseedx * 3600, 0))//smoothedNormal:Angle() + Angle(90, 0, 0) Angle(0, randseedx * 3600, 0) 118 | m:SetScale(Vector(1, 1, 1) * data.treeHeight) 119 | finalPos[3] = finalPos[3] + 256 * data.treeHeight // add tree height 120 | table.insert(self.TreeMatrices, m) 121 | local modelIndex = math.floor(util.SharedRandom("TerrainModel" .. chunkIndex, 0, #Terrain.TreeModels - 0.9, randomIndex)) + 1 122 | table.insert(self.TreeModels, modelIndex) // 4.1 means 1/50 chance for a rock to generate instead of a tree 123 | local shading = util.TraceLine({start = finalPos, endpos = finalPos + Terrain.SunDir * 99999}).HitSky and 1.5 or 0.5 124 | table.insert(self.TreeShading, shading) 125 | table.insert(self.TreeColors, modelIndex != 5 and Vector(shading, shading, shading) * data.treeColor or Vector(shading, shading, shading)) 126 | end 127 | end 128 | end 129 | 130 | function ENT:GenerateMesh(heightFunction) 131 | local heightFunction = heightFunction or Terrain.MathFunc 132 | // generate a mesh for the chunk using the mesh library 133 | self.RenderMesh = Mesh(Terrain.Material) 134 | local mesh = mesh // local lookup is faster than global 135 | local err, msg 136 | local function smoothedNormal(chunkoffsetx, chunkoffsety, vertexPos) 137 | local unwrappedPos = vertexPos / Terrain.ChunkSize 138 | local smoothedNormal = Vector() 139 | for cornery = 0, 1 do 140 | for cornerx = 0, 1 do 141 | // get 4 corners in a for loop ranging from -1 to 1 142 | local cornerx = (cornerx - 0.5) * 2 143 | local cornery = (cornery - 0.5) * 2 144 | 145 | // get the height of the 0x triangle 146 | local cornerWorldx = vertexPos[1] 147 | local cornerWorldy = (unwrappedPos[2] + cornery) * Terrain.ChunkSize 148 | local cornerHeight = heightFunction(cornerWorldx + chunkoffsetx, cornerWorldy + chunkoffsety) 149 | local middleXPosition = Vector(0, Terrain.ChunkSize * cornery, cornerHeight) 150 | 151 | // get the height of the 0y triangle 152 | local cornerWorldx = (unwrappedPos[1] + cornerx) * Terrain.ChunkSize 153 | local cornerWorldy = vertexPos[2] 154 | local cornerHeight = heightFunction(cornerWorldx + chunkoffsetx, cornerWorldy + chunkoffsety) 155 | local middleYPosition = Vector(Terrain.ChunkSize * cornerx, 0, cornerHeight) 156 | 157 | // we now have 3 points, construct a triangle from this and add the normal to the average normal 158 | local triNormal = (middleYPosition - vertexPos):Cross(middleXPosition - vertexPos) * cornerx * cornery 159 | smoothedNormal = smoothedNormal + triNormal 160 | end 161 | end 162 | 163 | return smoothedNormal:GetNormalized() 164 | end 165 | 166 | mesh.Begin(self.RenderMesh, MATERIAL_TRIANGLES, Terrain.ChunkResolution^2 * 2) 167 | err, msg = pcall(function() 168 | for y = 0, Terrain.ChunkResolution - 1 do 169 | for x = 0, Terrain.ChunkResolution - 1 do 170 | // chunk offset in world space 171 | local chunkoffsetx = self:GetChunkX() * Terrain.ChunkResScale // Terrain.ChunkSize * Terrain.ChunkResolution 172 | local chunkoffsety = self:GetChunkY() * Terrain.ChunkResScale 173 | 174 | // vertex of the triangle in the chunks local space 175 | local worldx1 = x * Terrain.ChunkSize 176 | local worldy1 = y * Terrain.ChunkSize 177 | local worldx2 = (x + 1) * Terrain.ChunkSize 178 | local worldy2 = (y + 1) * Terrain.ChunkSize 179 | 180 | // the height of the vertex using the math function 181 | local flipped = self:GetFlipped() 182 | local vertexHeight1 = heightFunction(worldx1 + chunkoffsetx, worldy1 + chunkoffsety, flipped) 183 | local vertexHeight2 = heightFunction(worldx1 + chunkoffsetx, worldy2 + chunkoffsety, flipped) 184 | local vertexHeight3 = heightFunction(worldx2 + chunkoffsetx, worldy1 + chunkoffsety, flipped) 185 | local vertexHeight4 = heightFunction(worldx2 + chunkoffsetx, worldy2 + chunkoffsety, flipped) 186 | 187 | // vertex positions in local space 188 | local vertexPos1 = Vector(worldx1, worldy1, vertexHeight1) 189 | local vertexPos2 = Vector(worldx1, worldy2, vertexHeight2) 190 | local vertexPos3 = Vector(worldx2, worldy1, vertexHeight3) 191 | local vertexPos4 = Vector(worldx2, worldy2, vertexHeight4) 192 | 193 | // lightmap uv calculation, needs to spread over whole terrain or it looks weird 194 | // since chunks range into negative numbers we need to adhere to that 195 | local r = Terrain.Resolution 196 | local uvx1 = ((self:GetChunkX() + r) / r + (x / Terrain.ChunkResolution / r)) * 0.5 197 | local uvy1 = ((self:GetChunkY() + r) / r + (y / Terrain.ChunkResolution / r)) * 0.5 198 | local uvx2 = ((self:GetChunkX() + r) / r + ((x + 1) / Terrain.ChunkResolution / r)) * 0.5 199 | local uvy2 = ((self:GetChunkY() + r) / r + ((y + 1) / Terrain.ChunkResolution / r)) * 0.5 200 | 201 | local normal1 = -(vertexPos1 - vertexPos2):Cross(vertexPos1 - vertexPos3):GetNormalized() 202 | local normal2 = -(vertexPos4 - vertexPos3):Cross(vertexPos4 - vertexPos2):GetNormalized() 203 | 204 | local smoothedNormal1 = smoothedNormal(chunkoffsetx, chunkoffsety, vertexPos1) 205 | local smoothedNormal2 = smoothedNormal(chunkoffsetx, chunkoffsety, vertexPos2) 206 | local smoothedNormal3 = smoothedNormal(chunkoffsetx, chunkoffsety, vertexPos3) 207 | local smoothedNormal4 = smoothedNormal(chunkoffsetx, chunkoffsety, vertexPos4) 208 | 209 | local waterHeight = Terrain.Variables.waterHeight or -math.huge 210 | local rock1 = (vertexHeight1 + Terrain.ZOffset < waterHeight) and 0.3 or smoothedNormal1[3] 211 | local rock2 = (vertexHeight2 + Terrain.ZOffset < waterHeight) and 0.3 or smoothedNormal2[3] 212 | local rock3 = (vertexHeight3 + Terrain.ZOffset < waterHeight) and 0.3 or smoothedNormal3[3] 213 | local rock4 = (vertexHeight4 + Terrain.ZOffset < waterHeight) and 0.3 or smoothedNormal4[3] 214 | 215 | local color1 = math.Min(rock1 * 512, 255) 216 | local color2 = math.Min(rock2 * 512, 255) 217 | local color3 = math.Min(rock3 * 512, 255) 218 | local color4 = math.Min(rock4 * 512, 255) 219 | 220 | // first tri 221 | mesh.Position(vertexPos1) 222 | mesh.TexCoord(0, 0, 0) // texture UV 223 | mesh.TexCoord(1, uvx1, uvy1) // lightmap UV 224 | mesh.Color(255, 255, 255, color1) 225 | mesh.Normal(normal1) 226 | 227 | mesh.AdvanceVertex() 228 | mesh.Position(vertexPos2) 229 | mesh.TexCoord(0, 1, 0) 230 | mesh.TexCoord(1, uvx1, uvy2) 231 | mesh.Color(255, 255, 255, color2) 232 | mesh.Normal(normal1) 233 | mesh.AdvanceVertex() 234 | 235 | mesh.Position(vertexPos3) 236 | mesh.TexCoord(0, 0, 1) 237 | mesh.TexCoord(1, uvx2, uvy1) 238 | mesh.Color(255, 255, 255, color3) 239 | mesh.Normal(normal1) 240 | mesh.AdvanceVertex() 241 | 242 | // second tri 243 | mesh.Position(vertexPos3) 244 | mesh.TexCoord(0, 0, 1) 245 | mesh.TexCoord(1, uvx2, uvy1) 246 | mesh.Color(255, 255, 255, color3) 247 | mesh.Normal(normal2) 248 | mesh.AdvanceVertex() 249 | 250 | mesh.Position(vertexPos2) 251 | mesh.TexCoord(0, 1, 0) 252 | mesh.TexCoord(1, uvx1, uvy2) 253 | mesh.Color(255, 255, 255, color2) 254 | mesh.Normal(normal2) 255 | mesh.AdvanceVertex() 256 | 257 | mesh.Position(vertexPos4) 258 | mesh.TexCoord(0, 1, 1) 259 | mesh.TexCoord(1, uvx2, uvy2) 260 | mesh.Color(255, 255, 255, color4) 261 | mesh.Normal(normal2) 262 | mesh.AdvanceVertex() 263 | end 264 | end 265 | end) 266 | mesh.End() 267 | 268 | if !err then print(msg) end // if there is an error, catch it and throw it outside of mesh.begin since you crash if mesh.end is not called 269 | end 270 | 271 | local grassAmount = 104 272 | function ENT:GenerateGrass() 273 | self.GrassMesh = Mesh() 274 | if !Terrain.Variables.generateGrass then return end 275 | local grassSize = Terrain.Variables.grassSize 276 | 277 | local mesh = mesh 278 | local err, msg 279 | local chunkIndex = tostring(self:GetChunkX()) .. tostring(self:GetChunkY()) 280 | local randomIndex = 0 281 | mesh.Begin(self.GrassMesh, MATERIAL_TRIANGLES, grassAmount^2) 282 | err, msg = pcall(function() 283 | for y = 0, grassAmount - 1 do 284 | for x = 0, grassAmount - 1 do 285 | randomIndex = randomIndex + 1 286 | local mult = Terrain.ChunkResolution / grassAmount 287 | local x = x * mult 288 | local y = y * mult 289 | local randoffsetx = util.SharedRandom("TerrainGrassX" .. chunkIndex, 0, 1, randomIndex) * mult 290 | local randoffsety = util.SharedRandom("TerrainGrassY" .. chunkIndex, 0, 1, randomIndex) * mult 291 | 292 | // chunk offset in world space 293 | local chunkoffsetx = self:GetChunkX() * Terrain.ChunkResScale // Terrain.ChunkSize * Terrain.ChunkResolution 294 | local chunkoffsety = self:GetChunkY() * Terrain.ChunkResScale 295 | 296 | // vertex of the triangle in the chunks local space 297 | local worldx = (x + randoffsetx) * Terrain.ChunkSize 298 | local worldy = (y + randoffsety) * Terrain.ChunkSize 299 | 300 | // the height of the vertex using the math function 301 | local vertexHeight = Terrain.MathFunc(worldx + chunkoffsetx, worldy + chunkoffsety) 302 | local mainPos = Vector(chunkoffsetx + worldx, chunkoffsety + worldy, vertexHeight + Terrain.ZOffset) 303 | if Terrain.Variables.waterHeight and mainPos[3] < Terrain.Variables.waterHeight then continue end 304 | 305 | local randbrushx = math.floor(((randoffsetx * 9999) % 1) * 3) * 0.3 306 | local randbrushy = math.floor(((randoffsety * 9999) % 1) * 3) * 0.3 307 | local offsetx = randbrushx - 0.1 308 | local offsety = 0.5 - randbrushy 309 | local randdir = Angle(0, randoffsetx * 9999, 0) 310 | 311 | mesh.TexCoord(0, offsetx, 0.3 + offsety) 312 | mesh.Position(mainPos - randdir:Right() * grassSize) 313 | mesh.Color(200, 255, 200, 200) 314 | mesh.AdvanceVertex() 315 | 316 | mesh.TexCoord(0, 0.3 + offsetx, 0.3 + offsety) 317 | mesh.Position(mainPos + randdir:Right() * grassSize) 318 | mesh.Color(200, 255, 200, 255) 319 | mesh.AdvanceVertex() 320 | 321 | mesh.TexCoord(0, 0.3 + offsetx, offsety) 322 | mesh.Position(mainPos + (randdir:Right() + randdir:Up() * 2) * grassSize) 323 | mesh.Color(200, 255, 200, 255) 324 | mesh.AdvanceVertex() 325 | end 326 | end 327 | end) 328 | mesh.End() 329 | 330 | if !err then print(msg) end 331 | end 332 | 333 | // get the height of the terrain at a given point with given offset 334 | local function getChunkOffset(x, y, offsetx, offsety, flipped, heightFunction) 335 | local cs = Terrain.ChunkSize 336 | local ox, oy = x * cs, y * cs 337 | return Vector(ox, oy, heightFunction(ox + offsetx, oy + offsety, flipped)) 338 | end 339 | 340 | // create the collision mesh for the chunk, runs on server & client 341 | function ENT:BuildCollision(heightFunction) 342 | if CLIENT and jit.arch == "x86" then 343 | self:SetRenderBounds(Vector(0, 0, 0), Vector(1, 1, 1000) * Terrain.ChunkResScale + Vector(0, 0, 1000)) // add 1000 units for trees 344 | print("CLIENT IS ON 32 BIT, PREVENTING INCOMING CRASH BY NOT GENERATING PHYSICS OBJECT") 345 | return 346 | end // no collision for 32 bit because funny memory leak crashing hahaha 347 | 348 | local heightFunction = heightFunction or Terrain.MathFunc 349 | 350 | // main base terrain 351 | local finalMesh = {} 352 | for y = 1, Terrain.ChunkResolution do 353 | for x = 1, Terrain.ChunkResolution do 354 | local offsetx = self:GetChunkX() * Terrain.ChunkResScale 355 | local offsety = self:GetChunkY() * Terrain.ChunkResScale 356 | 357 | local flipped = self:GetFlipped() 358 | local p1 = getChunkOffset(x, y, offsetx, offsety, flipped, heightFunction) 359 | local p2 = getChunkOffset(x - 1, y, offsetx, offsety, flipped, heightFunction) 360 | local p3 = getChunkOffset(x, y - 1, offsetx, offsety, flipped, heightFunction) 361 | local p4 = getChunkOffset(x - 1, y - 1, offsetx, offsety, flipped, heightFunction) 362 | 363 | table.Add(finalMesh, { 364 | {pos = p1}, 365 | {pos = p2}, 366 | {pos = p3} 367 | }) 368 | 369 | table.Add(finalMesh, { 370 | {pos = p2}, 371 | {pos = p3}, 372 | {pos = p4} 373 | }) 374 | end 375 | end 376 | 377 | // tree collision 378 | // crashes if this is generated on client, guess trees & rocks will be buggy to interact with.. oh well 379 | if SERVER and !self:GetFlipped() then 380 | local data = Terrain.Variables 381 | data.treeMultiplier = Terrain.ChunkResolution / data.treeResolution * Terrain.ChunkSize 382 | local randomIndex = 0 383 | local chunkIndex = tostring(self:GetChunkX()) .. tostring(self:GetChunkY()) 384 | for y = 0, data.treeResolution - 1 do 385 | for x = 0, data.treeResolution - 1 do 386 | randomIndex = randomIndex + 1 387 | 388 | // generate seeded random position for tree 389 | local randseedx = util.SharedRandom("TerrainSeedX" .. chunkIndex, 0, 1, randomIndex) 390 | local randseedy = util.SharedRandom("TerrainSeedY" .. chunkIndex, 0, 1, randomIndex) 391 | local randPos = Vector(randseedx, randseedy) * data.treeResolution * data.treeMultiplier 392 | 393 | local finalPos = self:GetTreeData(randPos, data, heightFunction) 394 | if !finalPos then continue end 395 | 396 | local treeIndex = math.floor(util.SharedRandom("TerrainModel" .. chunkIndex, 0, #Terrain.TreeModels - 0.9, randomIndex)) + 1 397 | local treeMesh = {} 398 | for k, v in ipairs(Terrain.TreePhysMeshes[treeIndex]) do 399 | local rotatedPos = Vector(v.pos[1], v.pos[2], v.pos[3]) 400 | rotatedPos:Rotate(Angle(0, randseedx * 3600, 0)) 401 | treeMesh[k] = {pos = (rotatedPos * data.treeHeight + finalPos) - self:GetPos()} 402 | end 403 | 404 | table.Add(finalMesh, treeMesh) 405 | end 406 | end 407 | end 408 | 409 | self:PhysicsDestroy() 410 | self:PhysicsFromMesh(finalMesh) 411 | 412 | if CLIENT then 413 | self:SetRenderBounds(self:OBBMins(), self:OBBMaxs() + Vector(0, 0, 1000)) 414 | end 415 | end 416 | 417 | function ENT:Initialize() 418 | if CLIENT then return end 419 | 420 | self:SetModel("models/props_c17/FurnitureCouch002a.mdl") 421 | self:BuildCollision() 422 | self:SetSolid(SOLID_VPHYSICS) 423 | self:SetMoveType(MOVETYPE_NONE) 424 | self:EnableCustomCollisions(true) 425 | self:GetPhysicsObject():EnableMotion(false) 426 | self:GetPhysicsObject():SetMass(50000) // max weight, should help a bit with the physics solver 427 | self:DrawShadow(false) 428 | end 429 | 430 | function ENT:ClientInitialize() 431 | self:BuildCollision() 432 | if self:GetPhysicsObject():IsValid() then 433 | self:GetPhysicsObject():EnableMotion(false) 434 | self:GetPhysicsObject():SetMass(50000) // make sure to call these on client or else when you touch it, you will crash 435 | self:GetPhysicsObject():SetPos(self:GetPos()) 436 | end 437 | 438 | self:GenerateMesh() 439 | if !self:GetFlipped() then 440 | self:GenerateTrees() 441 | self:GenerateGrass() 442 | end 443 | 444 | // if its the last chunk, generate the lightmap 445 | if self:GetChunkX() == -Terrain.Resolution and self:GetChunkY() == -Terrain.Resolution then 446 | timer.Simple(2, function() 447 | Terrain.GenerateLightmap(1024) 448 | end) 449 | end 450 | end 451 | 452 | // it has to be transmitted to the client always because its like, the world 453 | function ENT:UpdateTransmitState() 454 | return TRANSMIT_ALWAYS 455 | end 456 | 457 | function ENT:CanProperty(ply, property) 458 | return false 459 | end 460 | 461 | hook.Add("CanDrive", "terrain_stopdrive", function(ply, ent) 462 | if ent:GetClass() == "terrain_chunk" then return false end 463 | end) 464 | 465 | // disable physgun pickup because that would be cancer 466 | hook.Add("PhysgunPickup", "Terrain_DisablePhysgun", function(ply, ent) 467 | if ent and ent:GetClass() == "terrain_chunk" then 468 | return false 469 | end 470 | end) 471 | 472 | function ENT:OnRemove() 473 | if self.RenderMesh and self.RenderMesh:IsValid() then 474 | self.RenderMesh:Destroy() 475 | end 476 | 477 | if self.GrassMesh and self.GrassMesh:IsValid() then 478 | self.GrassMesh:Destroy() 479 | end 480 | end 481 | 482 | // drawing, no server here 483 | if SERVER then return end 484 | 485 | local lm = Terrain.Lightmap 486 | local detailMaterial = Material("detail/detailsprites") // detail/detailsprites models/props_combine/combine_interface_disp 487 | 488 | // cache ALL of these for faster lookup 489 | local renderTable = {Material = Terrain.Material} 490 | local render_SetLightmapTexture = render.SetLightmapTexture 491 | local render_SetMaterial = render.SetMaterial 492 | local render_SetModelLighting = render.SetModelLighting 493 | local render_SetLocalModelLights = render.SetLocalModelLights 494 | local cam_PushModelMatrix = cam.PushModelMatrix 495 | local cam_PopModelMatrix = cam.PopModelMatrix 496 | local math_DistanceSqr = math.DistanceSqr 497 | local math_Distance = math.Distance 498 | 499 | // this MUST be optimized as much as possible, it is called multiple times every frame 500 | function ENT:GetRenderMesh() 501 | local self = self 502 | 503 | // set a lightmap texture to be used instead of the default one 504 | render_SetLightmapTexture(lm) 505 | 506 | if !self.TreeMatrices then 507 | renderTable.Mesh = self.RenderMesh 508 | return renderTable 509 | end 510 | 511 | // get local vars 512 | local lod = self.LOD 513 | local models = self.TreeModels 514 | local lighting = self.TreeShading 515 | local color = self.TreeColors 516 | local matrices = self.TreeMatrices 517 | local materials = Terrain.TreeMaterials 518 | local flashlightOn = LocalPlayer():FlashlightIsOn() 519 | 520 | // reset lighting 521 | render_SetLocalModelLights() 522 | render_SetModelLighting(1, 0.1, 0.1, 0.1) 523 | render_SetModelLighting(3, 0.1, 0.1, 0.1) 524 | render_SetModelLighting(5, 0.1, 0.1, 0.1) 525 | 526 | // render foliage 527 | if lod then // chunk is near us, render high quality foliage 528 | local lastlight 529 | local lastmat 530 | for i = 1, #matrices do 531 | local matrix = matrices[i] 532 | local modelID = models[i] 533 | if lastmat != modelID then 534 | if i == 1 or lastmat == 5 or modelID == 5 then 535 | render_SetMaterial(materials[modelID]) 536 | end 537 | lastmat = modelID 538 | end 539 | 540 | // give the tree its shading 541 | local tree_color = color[i] 542 | if tree_color != lastlight then 543 | local light = lighting[i] 544 | local light_2 = light * 0.45 545 | render_SetModelLighting(0, light_2, light_2, light_2) 546 | render_SetModelLighting(2, light, light, light) 547 | render_SetModelLighting(4, tree_color[1], tree_color[2], tree_color[3]) 548 | lastlight = tree_color 549 | end 550 | 551 | // push custom matrix generated earlier and render the tree 552 | cam_PushModelMatrix(matrix) 553 | Terrain.TreeMeshes[modelID]:Draw() 554 | if flashlightOn then // flashlight compatability 555 | render.PushFlashlightMode(true) 556 | Terrain.TreeMeshes[modelID]:Draw() 557 | render.PopFlashlightMode() 558 | end 559 | cam_PopModelMatrix() 560 | end 561 | else // chunk is far, render low definition 562 | local lastlight 563 | local lastmat 564 | for i = 1, #matrices do 565 | local matrix = matrices[i] 566 | local modelID = models[i] 567 | if lastmat != modelID then 568 | if i == 1 or lastmat == 5 or modelID == 5 then 569 | render_SetMaterial(materials[modelID]) 570 | end 571 | lastmat = modelID 572 | end 573 | 574 | // give the tree its shading 575 | local tree_color = color[i] 576 | if tree_color != lastlight then 577 | local light = lighting[i] 578 | local light_2 = light * 0.45 579 | render_SetModelLighting(0, light_2, light_2, light_2) 580 | render_SetModelLighting(2, light, light, light) 581 | render_SetModelLighting(4, tree_color[1], tree_color[2], tree_color[3]) 582 | lastlight = tree_color 583 | end 584 | 585 | // push custom matrix generated earlier and render the tree 586 | cam_PushModelMatrix(matrix) 587 | Terrain.TreeMeshes_Low[modelID]:Draw() 588 | cam_PopModelMatrix() 589 | end 590 | end 591 | 592 | // render the chunk mesh itself 593 | renderTable.Mesh = self.RenderMesh 594 | return renderTable 595 | end 596 | 597 | function ENT:Draw() 598 | local self = self 599 | local selfpos = (self:GetPos() + self:OBBCenter()) 600 | local eyepos = EyePos() 601 | self.LOD = math_DistanceSqr(selfpos[1], selfpos[2], eyepos[1], eyepos[2]) < Terrain.LODDistance 602 | self:DrawModel() 603 | if self.LOD and IsValid(self.GrassMesh) then 604 | render_SetMaterial(detailMaterial) 605 | self.GrassMesh:Draw() 606 | end 607 | end -------------------------------------------------------------------------------- /lua/simplex.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------- 2 | ---Simplex Noise 3 | -- Original Java Source: http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf 4 | -- Original Author: https://raw.githubusercontent.com/weswigham/simplex/master/lua/src/simplex.lua 5 | -- (most) Original comments included 6 | ----------------------------------------------- 7 | 8 | AddCSLuaFile() 9 | 10 | local math = math 11 | local table = table 12 | local tonumber = tonumber 13 | local ipairs = ipairs 14 | local error = error 15 | 16 | local simplex = {} 17 | 18 | simplex.DIR_X = 0 19 | simplex.DIR_Y = 1 20 | simplex.DIR_Z = 2 21 | simplex.DIR_W = 3 22 | simplex.internalCache = false 23 | 24 | 25 | local Gradients3D = {{1,1,0},{-1,1,0},{1,-1,0},{-1,-1,0}, 26 | {1,0,1},{-1,0,1},{1,0,-1},{-1,0,-1}, 27 | {0,1,1},{0,-1,1},{0,1,-1},{0,-1,-1}}; 28 | local Gradients4D = {{0,1,1,1}, {0,1,1,-1}, {0,1,-1,1}, {0,1,-1,-1}, 29 | {0,-1,1,1}, {0,-1,1,-1}, {0,-1,-1,1}, {0,-1,-1,-1}, 30 | {1,0,1,1}, {1,0,1,-1}, {1,0,-1,1}, {1,0,-1,-1}, 31 | {-1,0,1,1}, {-1,0,1,-1}, {-1,0,-1,1}, {-1,0,-1,-1}, 32 | {1,1,0,1}, {1,1,0,-1}, {1,-1,0,1}, {1,-1,0,-1}, 33 | {-1,1,0,1}, {-1,1,0,-1}, {-1,-1,0,1}, {-1,-1,0,-1}, 34 | {1,1,1,0}, {1,1,-1,0}, {1,-1,1,0}, {1,-1,-1,0}, 35 | {-1,1,1,0}, {-1,1,-1,0}, {-1,-1,1,0}, {-1,-1,-1,0}}; 36 | local p = {151,160,137,91,90,15, 37 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 38 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 39 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 40 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 41 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 42 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 43 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 44 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 45 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 46 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 47 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 48 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180}; 49 | 50 | -- To remove the need for index wrapping, double the permutation table length 51 | 52 | for i=1,#p do 53 | p[i-1] = p[i] 54 | p[i] = nil 55 | end 56 | 57 | for i=1,#Gradients3D do 58 | Gradients3D[i-1] = Gradients3D[i] 59 | Gradients3D[i] = nil 60 | end 61 | 62 | for i=1,#Gradients4D do 63 | Gradients4D[i-1] = Gradients4D[i] 64 | Gradients4D[i] = nil 65 | end 66 | 67 | local perm = {} 68 | 69 | for i=0,255 do 70 | perm[i] = p[i] 71 | perm[i+256] = p[i] 72 | end 73 | 74 | -- A lookup table to traverse the sim around a given point in 4D. 75 | -- Details can be found where this table is used, in the 4D noise method. 76 | 77 | local sim = { 78 | {0,1,2,3},{0,1,3,2},{0,0,0,0},{0,2,3,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,2,3,0}, 79 | {0,2,1,3},{0,0,0,0},{0,3,1,2},{0,3,2,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,3,2,0}, 80 | {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}, 81 | {1,2,0,3},{0,0,0,0},{1,3,0,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,3,0,1},{2,3,1,0}, 82 | {1,0,2,3},{1,0,3,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,0,3,1},{0,0,0,0},{2,1,3,0}, 83 | {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}, 84 | {2,0,1,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,0,1,2},{3,0,2,1},{0,0,0,0},{3,1,2,0}, 85 | {2,1,0,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,1,0,2},{0,0,0,0},{3,2,0,1},{3,2,1,0}}; 86 | 87 | local function Dot2D(tbl, x, y) 88 | return tbl[1]*x + tbl[2]*y; 89 | end 90 | 91 | local function Dot3D(tbl, x, y, z) 92 | return tbl[1]*x + tbl[2]*y + tbl[3]*z 93 | end 94 | 95 | local function Dot4D( tbl, x,y,z,w) 96 | return tbl[1]*x + tbl[2]*y + tbl[3]*z + tbl[3]*w; 97 | end 98 | 99 | local Prev2D = {} 100 | 101 | 102 | -- 2D simplex noise 103 | 104 | function simplex.Noise2D(xin, yin) 105 | if simplex.internalCache and Prev2D[xin] and Prev2D[xin][yin] then return Prev2D[xin][yin] end 106 | 107 | local n0, n1, n2; -- Noise contributions from the three corners 108 | -- Skew the input space to determine which simplex cell we're in 109 | local F2 = 0.5*(math.sqrt(3.0)-1.0); 110 | local s = (xin+yin)*F2; -- Hairy factor for 2D 111 | local i = math.floor(xin+s); 112 | local j = math.floor(yin+s); 113 | local G2 = (3.0-math.sqrt(3.0))/6.0; 114 | 115 | local t = (i+j)*G2; 116 | local X0 = i-t; -- Unskew the cell origin back to (x,y) space 117 | local Y0 = j-t; 118 | local x0 = xin-X0; -- The x,y distances from the cell origin 119 | local y0 = yin-Y0; 120 | 121 | -- For the 2D case, the simplex shape is an equilateral triangle. 122 | -- Determine which simplex we are in. 123 | local i1, j1; -- Offsets for second (middle) corner of simplex in (i,j) coords 124 | if(x0>y0) then 125 | i1=1 126 | j1=0 -- lower triangle, XY order: (0,0)->(1,0)->(1,1) 127 | else 128 | i1=0 129 | j1=1 -- upper triangle, YX order: (0,0)->(0,1)->(1,1) 130 | end 131 | 132 | -- A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 133 | -- a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 134 | -- c = (3-sqrt(3))/6 135 | 136 | local x1 = x0 - i1 + G2; -- Offsets for middle corner in (x,y) unskewed coords 137 | local y1 = y0 - j1 + G2; 138 | local x2 = x0 - 1.0 + 2.0 * G2; -- Offsets for last corner in (x,y) unskewed coords 139 | local y2 = y0 - 1.0 + 2.0 * G2; 140 | 141 | -- Work out the hashed gradient indices of the three simplex corners 142 | local ii = bit.band(i , 255) 143 | local jj = bit.band(j , 255) 144 | local gi0 = perm[ii+perm[jj]] % 12; 145 | local gi1 = perm[ii+i1+perm[jj+j1]] % 12; 146 | local gi2 = perm[ii+1+perm[jj+1]] % 12; 147 | 148 | -- Calculate the contribution from the three corners 149 | local t0 = 0.5 - x0*x0-y0*y0; 150 | if t0<0 then 151 | n0 = 0.0; 152 | else 153 | t0 = t0 * t0 154 | n0 = t0 * t0 * Dot2D(Gradients3D[gi0], x0, y0); -- (x,y) of Gradients3D used for 2D gradient 155 | end 156 | 157 | local t1 = 0.5 - x1*x1-y1*y1; 158 | if (t1<0) then 159 | n1 = 0.0; 160 | else 161 | t1 = t1*t1 162 | n1 = t1 * t1 * Dot2D(Gradients3D[gi1], x1, y1); 163 | end 164 | 165 | local t2 = 0.5 - x2*x2-y2*y2; 166 | if (t2<0) then 167 | n2 = 0.0; 168 | else 169 | t2 = t2*t2 170 | n2 = t2 * t2 * Dot2D(Gradients3D[gi2], x2, y2); 171 | end 172 | 173 | 174 | -- Add contributions from each corner to get the final noise value. 175 | -- The result is scaled to return values in the localerval [-1,1]. 176 | 177 | local retval = 70.0 * (n0 + n1 + n2) 178 | 179 | if simplex.internalCache then 180 | if not Prev2D[xin] then Prev2D[xin] = {} end 181 | Prev2D[xin][yin] = retval 182 | end 183 | 184 | return retval; 185 | end 186 | 187 | local Prev3D = {} 188 | 189 | -- 3D simplex noise 190 | function simplex.Noise3D(xin, yin, zin) 191 | 192 | if simplex.internalCache and Prev3D[xin] and Prev3D[xin][yin] and Prev3D[xin][yin][zin] then return Prev3D[xin][yin][zin] end 193 | 194 | local n0, n1, n2, n3; -- Noise contributions from the four corners 195 | 196 | -- Skew the input space to determine which simplex cell we're in 197 | local F3 = 1.0/3.0; 198 | local s = (xin+yin+zin)*F3; -- Very nice and simple skew factor for 3D 199 | local i = math.floor(xin+s); 200 | local j = math.floor(yin+s); 201 | local k = math.floor(zin+s); 202 | 203 | local G3 = 1.0/6.0; -- Very nice and simple unskew factor, too 204 | local t = (i+j+k)*G3; 205 | 206 | local X0 = i-t; -- Unskew the cell origin back to (x,y,z) space 207 | local Y0 = j-t; 208 | local Z0 = k-t; 209 | 210 | local x0 = xin-X0; -- The x,y,z distances from the cell origin 211 | local y0 = yin-Y0; 212 | local z0 = zin-Z0; 213 | 214 | -- For the 3D case, the simplex shape is a slightly irregular tetrahedron. 215 | -- Determine which simplex we are in. 216 | local i1, j1, k1; -- Offsets for second corner of simplex in (i,j,k) coords 217 | local i2, j2, k2; -- Offsets for third corner of simplex in (i,j,k) coords 218 | 219 | if (x0>=y0) then 220 | if (y0>=z0) then 221 | i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; -- X Y Z order 222 | elseif (x0>=z0) then 223 | i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; -- X Z Y order 224 | else 225 | i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; -- Z X Y order 226 | end 227 | else -- x0 y0) and 32 or 1; 350 | local c2 = (x0 > z0) and 16 or 1; 351 | local c3 = (y0 > z0) and 8 or 1; 352 | local c4 = (x0 > w0) and 4 or 1; 353 | local c5 = (y0 > w0) and 2 or 1; 354 | local c6 = (z0 > w0) and 1 or 1; 355 | local c = c1 + c2 + c3 + c4 + c5 + c6; 356 | local i1, j1, k1, l1; -- The localeger offsets for the second simplex corner 357 | local i2, j2, k2, l2; -- The localeger offsets for the third simplex corner 358 | local i3, j3, k3, l3; -- The localeger offsets for the fourth simplex corner 359 | 360 | -- sim[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. 361 | -- Many values of c will never occur, since e.g. x>y>z>w makes x=3 and 1 or 0; 367 | j1 = sim[c][2]>=3 and 1 or 0; 368 | k1 = sim[c][3]>=3 and 1 or 0; 369 | l1 = sim[c][4]>=3 and 1 or 0; 370 | -- The number 2 in the "sim" array is at the second largest coordinate. 371 | i2 = sim[c][1]>=2 and 1 or 0; 372 | j2 = sim[c][2]>=2 and 1 or 0; 373 | k2 = sim[c][3]>=2 and 1 or 0; 374 | l2 = sim[c][4]>=2 and 1 or 0; 375 | -- The number 1 in the "sim" array is at the second smallest coordinate. 376 | i3 = sim[c][1]>=1 and 1 or 0; 377 | j3 = sim[c][2]>=1 and 1 or 0; 378 | k3 = sim[c][3]>=1 and 1 or 0; 379 | l3 = sim[c][4]>=1 and 1 or 0; 380 | -- The fifth corner has all coordinate offsets = 1, so no need to look that up. 381 | local x1 = x0 - i1 + G4; -- Offsets for second corner in (x,y,z,w) coords 382 | local y1 = y0 - j1 + G4; 383 | local z1 = z0 - k1 + G4; 384 | local w1 = w0 - l1 + G4; 385 | local x2 = x0 - i2 + 2.0*G4; -- Offsets for third corner in (x,y,z,w) coords 386 | local y2 = y0 - j2 + 2.0*G4; 387 | local z2 = z0 - k2 + 2.0*G4; 388 | local w2 = w0 - l2 + 2.0*G4; 389 | local x3 = x0 - i3 + 3.0*G4; -- Offsets for fourth corner in (x,y,z,w) coords 390 | local y3 = y0 - j3 + 3.0*G4; 391 | local z3 = z0 - k3 + 3.0*G4; 392 | local w3 = w0 - l3 + 3.0*G4; 393 | local x4 = x0 - 1.0 + 4.0*G4; -- Offsets for last corner in (x,y,z,w) coords 394 | local y4 = y0 - 1.0 + 4.0*G4; 395 | local z4 = z0 - 1.0 + 4.0*G4; 396 | local w4 = w0 - 1.0 + 4.0*G4; 397 | 398 | -- Work out the hashed gradient indices of the five simplex corners 399 | local ii = bit.band(i , 255) 400 | local jj = bit.band(j , 255) 401 | local kk = bit.band(k , 255) 402 | local ll = bit.band(l , 255) 403 | local gi0 = perm[ii+perm[jj+perm[kk+perm[ll]]]] % 32; 404 | local gi1 = perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]] % 32; 405 | local gi2 = perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]] % 32; 406 | local gi3 = perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]] % 32; 407 | local gi4 = perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]] % 32; 408 | 409 | 410 | -- Calculate the contribution from the five corners 411 | local t0 = 0.5 - x0*x0 - y0*y0 - z0*z0 - w0*w0; 412 | if (t0<0) then 413 | n0 = 0.0; 414 | else 415 | t0 = t0*t0; 416 | n0 = t0 * t0 * Dot4D(Gradients4D[gi0], x0, y0, z0, w0); 417 | end 418 | 419 | local t1 = 0.5 - x1*x1 - y1*y1 - z1*z1 - w1*w1; 420 | if (t1<0) then 421 | n1 = 0.0; 422 | else 423 | t1 = t1*t1; 424 | n1 = t1 * t1 * Dot4D(Gradients4D[gi1], x1, y1, z1, w1); 425 | end 426 | 427 | local t2 = 0.5 - x2*x2 - y2*y2 - z2*z2 - w2*w2; 428 | if (t2<0) then 429 | n2 = 0.0; 430 | else 431 | t2 = t2*t2; 432 | n2 = t2 * t2 * Dot4D(Gradients4D[gi2], x2, y2, z2, w2); 433 | end 434 | 435 | local t3 = 0.5 - x3*x3 - y3*y3 - z3*z3 - w3*w3; 436 | if (t3<0) then 437 | n3 = 0.0; 438 | else 439 | t3 = t3*t3; 440 | n3 = t3 * t3 * Dot4D(Gradients4D[gi3], x3, y3, z3, w3); 441 | end 442 | 443 | local t4 = 0.5 - x4*x4 - y4*y4 - z4*z4 - w4*w4; 444 | if (t4<0) then 445 | n4 = 0.0; 446 | else 447 | t4 = t4*t4; 448 | n4 = t4 * t4 * Dot4D(Gradients4D[gi4], x4, y4, z4, w4); 449 | end 450 | 451 | -- Sum up and scale the result to cover the range [-1,1] 452 | 453 | local retval = 27.0 * (n0 + n1 + n2 + n3 + n4) 454 | 455 | if simplex.internalCache then 456 | if not Prev4D[x] then Prev4D[x] = {} end 457 | if not Prev4D[x][y] then Prev4D[x][y] = {} end 458 | if not Prev4D[x][y][z] then Prev4D[x][y][z] = {} end 459 | Prev4D[x][y][z][w] = retval 460 | end 461 | 462 | return retval; 463 | 464 | 465 | end 466 | 467 | local e = 2.71828182845904523536 468 | 469 | local PrevBlur2D = {} 470 | 471 | function simplex.GBlur2D(x,y,stdDev) 472 | if simplex.internalCache and PrevBlur2D[x] and PrevBlur2D[x][y] and PrevBlur2D[x][y][stdDev] then return PrevBlur2D[x][y][stdDev] end 473 | local pwr = ((x^2+y^2)/(2*(stdDev^2)))*-1 474 | local ret = (1/(2*math.pi*(stdDev^2)))*(e^pwr) 475 | 476 | if simplex.internalCache then 477 | if not PrevBlur2D[x] then PrevBlur2D[x] = {} end 478 | if not PrevBlur2D[x][y] then PrevBlur2D[x][y] = {} end 479 | PrevBlur2D[x][y][stdDev] = ret 480 | end 481 | return ret 482 | end 483 | 484 | local PrevBlur1D = {} 485 | 486 | function simplex.GBlur1D(x,stdDev) 487 | if simplex.internalCache and PrevBlur1D[x] and PrevBlur1D[x][stdDev] then return PrevBlur1D[x][stdDev] end 488 | local pwr = (x^2/(2*stdDev^2))*-1 489 | local ret = (1/(math.sqrt(2*math.pi)*stdDev))*(e^pwr) 490 | 491 | if simplex.internalCache then 492 | if not PrevBlur1D[x] then PrevBlur1D[x] = {} end 493 | PrevBlur1D[x][stdDev] = ret 494 | end 495 | return ret 496 | end 497 | 498 | function simplex.FractalSum(func, iter, ...) 499 | local ret = func(...) 500 | for i=1,iter do 501 | local power = 2^iter 502 | local s = power/i 503 | 504 | local scaled = {} 505 | for elem in ipairs({...}) do 506 | table.insert(scaled, elem*s) 507 | end 508 | ret = ret + (i/power)*(func(unpack(scaled))) 509 | end 510 | return ret 511 | end 512 | 513 | function simplex.FractalSumAbs(func, iter, ...) 514 | local ret = math.abs(func(...)) 515 | for i=1,iter do 516 | local power = 2^iter 517 | local s = power/i 518 | 519 | local scaled = {} 520 | for elem in ipairs({...}) do 521 | table.insert(scaled, elem*s) 522 | end 523 | ret = ret + (i/power)*(math.abs(func(unpack(scaled)))) 524 | end 525 | return ret 526 | end 527 | 528 | function simplex.Turbulence(func, direction, iter, ...) 529 | local ret = math.abs(func(...)) 530 | for i=1,iter do 531 | local power = 2^iter 532 | local s = power/i 533 | 534 | local scaled = {} 535 | for elem in ipairs({...}) do 536 | table.insert(scaled, elem*s) 537 | end 538 | ret = ret + (i/power)*(math.abs(func(unpack(scaled)))) 539 | end 540 | local args = {...} 541 | local dir_component = args[direction+1] 542 | return math.sin(dir_component+ret) 543 | end 544 | 545 | return simplex 546 | 547 | 548 | 549 | 550 | 551 | -------------------------------------------------------------------------------- /materials/procedural_terrain/foliage/arbre01.vmt: -------------------------------------------------------------------------------- 1 | "VertexLitGeneric" 2 | { 3 | "$baseTexture" "procedural_terrain\foliage\arbre01" 4 | 5 | "$AlphaTest" "1" 6 | "$AlphaTestReference" "0.5" 7 | 8 | "$nocull" 1 9 | "$model" 1 10 | 11 | "$FlashlightNoLambert" "1" 12 | } 13 | -------------------------------------------------------------------------------- /materials/procedural_terrain/foliage/arbre01.vtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/materials/procedural_terrain/foliage/arbre01.vtf -------------------------------------------------------------------------------- /materials/procedural_terrain/foliage/coastrock02.vmt: -------------------------------------------------------------------------------- 1 | "VertexLitGeneric" 2 | { 3 | 4 | "$basetexture" "procedural_terrain\foliage\coastrock02" 5 | "$surfaceprop" "rock" 6 | "$detail" "Detail/rock_detail_01" 7 | "$detailscale" "7" 8 | "$detailblendfactor" .9 9 | "$detailblendmode" 0 10 | } 11 | 12 | -------------------------------------------------------------------------------- /materials/procedural_terrain/foliage/coastrock02.vtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/materials/procedural_terrain/foliage/coastrock02.vtf -------------------------------------------------------------------------------- /materials/procedural_terrain/water/dx80_tfwater001_dudv.vtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/materials/procedural_terrain/water/dx80_tfwater001_dudv.vtf -------------------------------------------------------------------------------- /materials/procedural_terrain/water/dx80_tfwater001_normal.vtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/materials/procedural_terrain/water/dx80_tfwater001_normal.vtf -------------------------------------------------------------------------------- /materials/procedural_terrain/water/tfwater001_normal.vtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/materials/procedural_terrain/water/tfwater001_normal.vtf -------------------------------------------------------------------------------- /materials/procedural_terrain/water/water_warp.vmt: -------------------------------------------------------------------------------- 1 | "Refract" 2 | { 3 | "$refractamount" ".05" 4 | "$refracttint" "{205 225 255}" 5 | "$refractblur" "1" 6 | 7 | "$model" "1" 8 | 9 | "$scale" "[1 1]" 10 | 11 | "$bumpmap" "dev/water_dudv" 12 | "$normalmap" "procedural_terrain/water/tfwater001_normal" 13 | "$bumpframe" "0" 14 | 15 | 16 | "Proxies" 17 | { 18 | "AnimatedTexture" 19 | { 20 | "animatedtexturevar" "$normalmap" 21 | "animatedtextureframenumvar" "$bumpframe" 22 | "animatedtextureframerate" 30.00 23 | } 24 | "TextureScroll" 25 | { 26 | "texturescrollvar" "$bumptransform" 27 | "texturescrollrate" .1 28 | "texturescrollangle" 45.00 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine04.dx90.vtx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine04.dx90.vtx -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine04.phy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine04.phy -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine05.dx90.vtx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine05.dx90.vtx -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine05.phy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine05.phy -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine06.dx90.vtx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine06.dx90.vtx -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine06.phy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine06.phy -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine_Large.dx90.vtx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine_Large.dx90.vtx -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/Tree_pine_Large.phy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/Tree_pine_Large.phy -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/rock_coast02a.dx90.vtx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/rock_coast02a.dx90.vtx -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/rock_coast02a.mdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/rock_coast02a.mdl -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/rock_coast02a.phy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/rock_coast02a.phy -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/rock_coast02a.vvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/rock_coast02a.vvd -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine04.mdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine04.mdl -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine04.vvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine04.vvd -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine05.mdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine05.mdl -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine05.vvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine05.vvd -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine06.mdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine06.mdl -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine06.vvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine06.vvd -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine_large.mdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine_large.mdl -------------------------------------------------------------------------------- /models/procedural_terrain/foliage/tree_pine_large.vvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meetric1/GMod-Procedural-Terrain/9f03d832bb6f31469f5bf427f412b71edd208045/models/procedural_terrain/foliage/tree_pine_large.vvd --------------------------------------------------------------------------------