├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── aftman.toml ├── default.project.json ├── selene.toml ├── sourcemap.json └── src ├── ReplicatedStorage └── Shared │ ├── ChunkSettings.luau │ ├── ChunksUtil.luau │ ├── ItemsData.luau │ ├── ItemsIDs.luau │ └── init.meta.json └── StarterPlayer ├── ParallelScripts ├── BiomeLoaderParallel.client.luau ├── TerrainLoaderParallel.client.luau └── init.meta.json └── StarterPlayerScripts ├── Main.client.luau ├── Modules ├── Break.luau ├── Chunks │ ├── ChunksManager.luau │ ├── Data │ │ ├── ChunkGenerationStages.luau │ │ ├── ChunksData.luau │ │ └── init.meta.json │ ├── Loading │ │ ├── Caves │ │ │ ├── CheeseCavesGenerator.luau │ │ │ ├── SpaghettiCavesGenerator.luau │ │ │ └── init.meta.json │ │ ├── Terrain │ │ │ ├── BedrockGenerator.luau │ │ │ ├── Biomes │ │ │ │ ├── BiomeSelector.luau │ │ │ │ ├── BiomesData.luau │ │ │ │ ├── Desert │ │ │ │ │ ├── DesertGenerator.luau │ │ │ │ │ └── init.meta.json │ │ │ │ ├── Forest │ │ │ │ │ ├── Decorations │ │ │ │ │ │ ├── DecorationsGenerator.luau │ │ │ │ │ │ ├── Trees │ │ │ │ │ │ │ ├── Tree.luau │ │ │ │ │ │ │ ├── TreesGenerator.luau │ │ │ │ │ │ │ └── init.meta.json │ │ │ │ │ │ ├── Unused │ │ │ │ │ │ │ ├── DandelionGenerator.luau │ │ │ │ │ │ │ ├── GrassGenerator.luau │ │ │ │ │ │ │ ├── OxeyeDaisyGenerator.luau │ │ │ │ │ │ │ ├── PoppyGenerator.luau │ │ │ │ │ │ │ └── init.meta.json │ │ │ │ │ │ └── init.meta.json │ │ │ │ │ ├── ForestGenerator.luau │ │ │ │ │ ├── GrassLayerGenerator.luau │ │ │ │ │ └── init.meta.json │ │ │ │ ├── Jungle │ │ │ │ │ ├── JungleGenerator.luau │ │ │ │ │ └── init.meta.json │ │ │ │ ├── Tundra │ │ │ │ │ ├── TundraGenerator.luau │ │ │ │ │ └── init.meta.json │ │ │ │ └── init.meta.json │ │ │ ├── SplineMaps.luau │ │ │ ├── TerrainShapeGenerator.luau │ │ │ └── init.meta.json │ │ └── init.meta.json │ ├── Rendering │ │ ├── ChunkRenderer.luau │ │ ├── Data │ │ │ ├── AtlasTextureData.luau │ │ │ ├── BlockMeshData.luau │ │ │ ├── PlantMeshData.luau │ │ │ └── init.meta.json │ │ └── init.meta.json │ └── init.meta.json ├── CollisionsManager.luau ├── Lighting.luau └── init.meta.json └── ParallelScripts ├── BiomeLoaderParallel.client.luau ├── TerrainLoaderParallel.client.luau └── init.meta.json /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Roblox's only Voxels Engine 2 | 3 | A project of this scale would benefit massively from your help. 4 | 5 | Feel free to implement the [planned features](README.md). 6 | Any optimizations and/or improvements to the codebase are appreciated. 7 | 8 | ## Reporting bugs 9 | 10 | Please file an issue containing the following information: 11 | * The error message(s) (if applicable). 12 | * The actions performed to recreate bug. 13 | 14 | ## Feature requests 15 | 16 | The project does accept any "game feature" requests. The aim is to create a voxels engine, not a fully fledged game. That's up to you to create using this engine! 17 | 18 | All requests to add mobs, inventory, PvP, etc will be rejected. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mustafa Al-Khafaji 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Voxels Engine 2 | A highly extensible voxel engine written in Luau for Roblox. 3 | 4 | [Inspired by Minecraft and Terraria] 5 | 6 | ## Features 7 | 8 | * Rendering 9 | * Render a chunk of terrain procedurally using EditableMeshes and EditableImages 10 | * Cull hidden faces 11 | * Generation 12 | * Terrain shape using fractal noise and spline maps 13 | * Multithreading by giving chunks generation stages 14 | * Storing data into u8 buffers 15 | 16 | ## Getting Started 17 | 18 | 1. Download the latest .rbxl file from [releases](https://github.com/mustafakhafaji/Mineblox/releases). 19 | 2. Import file into a new place in Roblox Studio. 20 | 21 | ## Planned Features 22 | 23 | * Biomes 24 | * Desert 25 | * Forest 26 | * Jungle 27 | * Tundra 28 | * Caves 29 | * Spaghetti caves 30 | * Cheese caves 31 | 32 | ## Contributing 33 | 34 | Check out the [contribution guide](CONTRIBUTING.md) for instructions. -------------------------------------------------------------------------------- /aftman.toml: -------------------------------------------------------------------------------- 1 | # This file lists tools managed by Aftman, a cross-platform toolchain manager. 2 | # For more information, see https://github.com/LPGhatguy/aftman 3 | 4 | # To add a new tool, add an entry to this table. 5 | [tools] 6 | rojo = "rojo-rbx/rojo@7.4.0-rc3" -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mineblox", 3 | "tree": { 4 | "$className": "DataModel", 5 | 6 | "Lighting": { 7 | "$className": "Lighting", 8 | 9 | "$properties": { 10 | "Ambient": {"Color3": [0.2745098039, 0.2745098039, 0.2745098039]}, 11 | "OutdoorAmbient": {"Color3": [0.2745098039, 0.2745098039, 0.2745098039]}, 12 | "Brightness": {"Float32": 3}, 13 | "EnvironmentDiffuseScale": {"Float32": 1}, 14 | "EnvironmentSpecularScale": {"Float32": 1}, 15 | "GlobalShadows": {"Bool": true}, 16 | "Technology": {"Enum": 3}, 17 | "ShadowSoftness": {"Float32": 0.2} 18 | } 19 | }, 20 | 21 | "Workspace": { 22 | "$className": "Workspace", 23 | 24 | "CollisionParts": { 25 | "$className": "Folder" 26 | } 27 | }, 28 | 29 | "ReplicatedFirst": { 30 | "$className": "ReplicatedFirst", 31 | "$path": "src/ReplicatedFirst", 32 | "$ignoreUnknownInstances": true 33 | }, 34 | 35 | "ReplicatedStorage": { 36 | "$className": "ReplicatedStorage", 37 | "$path": "src/ReplicatedStorage", 38 | "$ignoreUnknownInstances": true, 39 | 40 | "ServerSettings": { 41 | "$className": "Configuration" 42 | } 43 | }, 44 | 45 | "ServerScriptService": { 46 | "$className": "ServerScriptService", 47 | "$path": "src/ServerScriptService", 48 | "$ignoreUnknownInstances": true 49 | }, 50 | 51 | "StarterPlayer": { 52 | "$className": "StarterPlayer", 53 | "$ignoreUnknownInstances": true, 54 | 55 | "StarterPlayerScripts": { 56 | "$className": "StarterPlayerScripts", 57 | "$path": "src/StarterPlayer/StarterPlayerScripts", 58 | "$ignoreUnknownInstances": true, 59 | 60 | "Events": { 61 | "$className": "Folder", 62 | 63 | "ChunkLoadedBiome": { 64 | "$className": "BindableEvent" 65 | }, 66 | 67 | "ChunkLoadedTerrain": { 68 | "$className": "BindableEvent" 69 | } 70 | }, 71 | 72 | "ClientSettings": { 73 | "$className": "Configuration", 74 | 75 | "$properties": { 76 | "Attributes": { 77 | "ChunkLoadingActors": {"Float64": 16} 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | -------------------------------------------------------------------------------- /sourcemap.json: -------------------------------------------------------------------------------- 1 | {"name":"Mineblox","className":"DataModel","filePaths":["default.project.json"],"children":[{"name":"ReplicatedFirst","className":"ReplicatedFirst"},{"name":"ReplicatedStorage","className":"ReplicatedStorage","children":[{"name":"Shared","className":"Folder","filePaths":["src/ReplicatedStorage\\Shared\\init.meta.json"],"children":[{"name":"ChunkSettings","className":"ModuleScript","filePaths":["src/ReplicatedStorage\\Shared\\ChunkSettings.luau"]},{"name":"ChunksUtil","className":"ModuleScript","filePaths":["src/ReplicatedStorage\\Shared\\ChunksUtil.luau"]},{"name":"ItemsData","className":"ModuleScript","filePaths":["src/ReplicatedStorage\\Shared\\ItemsData.luau"]},{"name":"ItemsIDs","className":"ModuleScript","filePaths":["src/ReplicatedStorage\\Shared\\ItemsIDs.luau"]}]},{"name":"ServerSettings","className":"Configuration"}]},{"name":"ServerScriptService","className":"ServerScriptService"},{"name":"StarterPlayer","className":"StarterPlayer","children":[{"name":"StarterPlayerScripts","className":"StarterPlayerScripts","children":[{"name":"Main","className":"LocalScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Main.client.luau"]},{"name":"Modules","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\init.meta.json"],"children":[{"name":"Break","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Break.luau"]},{"name":"Chunks","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\init.meta.json"],"children":[{"name":"ChunksManager","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\ChunksManager.luau"]},{"name":"Data","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Data\\init.meta.json"],"children":[{"name":"ChunkGenerationStages","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Data\\ChunkGenerationStages.luau"]},{"name":"ChunksData","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Data\\ChunksData.luau"]}]},{"name":"Loading","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\init.meta.json"],"children":[{"name":"Caves","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Caves\\init.meta.json"],"children":[{"name":"CheeseCavesGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Caves\\CheeseCavesGenerator.luau"]},{"name":"SpaghettiCavesGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Caves\\SpaghettiCavesGenerator.luau"]}]},{"name":"Terrain","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\init.meta.json"],"children":[{"name":"BedrockGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\BedrockGenerator.luau"]},{"name":"Biomes","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\init.meta.json"],"children":[{"name":"BiomeSelector","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\BiomeSelector.luau"]},{"name":"BiomesData","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\BiomesData.luau"]},{"name":"Desert","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Desert\\init.meta.json"],"children":[{"name":"DesertGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Desert\\DesertGenerator.luau"]}]},{"name":"Forest","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\init.meta.json"],"children":[{"name":"Decorations","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\init.meta.json"],"children":[{"name":"DecorationsGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\DecorationsGenerator.luau"]},{"name":"Trees","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Trees\\init.meta.json"],"children":[{"name":"Tree","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Trees\\Tree.luau"]},{"name":"TreesGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Trees\\TreesGenerator.luau"]}]},{"name":"Unused","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Unused\\init.meta.json"],"children":[{"name":"DandelionGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Unused\\DandelionGenerator.luau"]},{"name":"GrassGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Unused\\GrassGenerator.luau"]},{"name":"OxeyeDaisyGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Unused\\OxeyeDaisyGenerator.luau"]},{"name":"PoppyGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\Decorations\\Unused\\PoppyGenerator.luau"]}]}]},{"name":"ForestGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\ForestGenerator.luau"]},{"name":"GrassLayerGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Forest\\GrassLayerGenerator.luau"]}]},{"name":"Jungle","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Jungle\\init.meta.json"],"children":[{"name":"JungleGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Jungle\\JungleGenerator.luau"]}]},{"name":"Tundra","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Tundra\\init.meta.json"],"children":[{"name":"TundraGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\Biomes\\Tundra\\TundraGenerator.luau"]}]}]},{"name":"SplineMaps","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\SplineMaps.luau"]},{"name":"TerrainShapeGenerator","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Loading\\Terrain\\TerrainShapeGenerator.luau"]}]}]},{"name":"Rendering","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Rendering\\init.meta.json"],"children":[{"name":"ChunkRenderer","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Rendering\\ChunkRenderer.luau"]},{"name":"Data","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Rendering\\Data\\init.meta.json"],"children":[{"name":"AtlasTextureData","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Rendering\\Data\\AtlasTextureData.luau"]},{"name":"BlockMeshData","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Rendering\\Data\\BlockMeshData.luau"]},{"name":"PlantMeshData","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Chunks\\Rendering\\Data\\PlantMeshData.luau"]}]}]}]},{"name":"CollisionsManager","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\CollisionsManager.luau"]},{"name":"Lighting","className":"ModuleScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\Modules\\Lighting.luau"]}]},{"name":"ParallelScripts","className":"Folder","filePaths":["src/StarterPlayer/StarterPlayerScripts\\ParallelScripts\\init.meta.json"],"children":[{"name":"BiomeLoaderParallel","className":"LocalScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\ParallelScripts\\BiomeLoaderParallel.client.luau"]},{"name":"TerrainLoaderParallel","className":"LocalScript","filePaths":["src/StarterPlayer/StarterPlayerScripts\\ParallelScripts\\TerrainLoaderParallel.client.luau"]}]},{"name":"Events","className":"Folder","children":[{"name":"ChunkLoadedBiome","className":"BindableEvent"},{"name":"ChunkLoadedTerrain","className":"BindableEvent"}]},{"name":"ClientSettings","className":"Configuration"}]}]},{"name":"Workspace","className":"Workspace","children":[{"name":"CollisionParts","className":"Folder"}]},{"name":"Lighting","className":"Lighting"}]} -------------------------------------------------------------------------------- /src/ReplicatedStorage/Shared/ChunkSettings.luau: -------------------------------------------------------------------------------- 1 | return { 2 | CHUNK_SIZE = 8, 3 | CHUNK_DISTANCE = 10, 4 | MAX_CHUNK_DISTANCE = 1000, 5 | 6 | MAX_CHUNK_LOADING_ACTORS = 24, 7 | 8 | BLOCK_SIZE = 3, 9 | } 10 | -------------------------------------------------------------------------------- /src/ReplicatedStorage/Shared/ChunksUtil.luau: -------------------------------------------------------------------------------- 1 | local ChunksUtil = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | 5 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 6 | 7 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 8 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 9 | 10 | -- PUBLIC 11 | 12 | -- Converts [chunk_x][chunk_z][x][z][y] position to a Vector3(x, y, z) 13 | function ChunksUtil.chunkToWorldPosition(chunkX: number, chunkZ: number, chunkY: number, x: number, z: number, y: number): Vector3 14 | local worldX = (chunkX * CHUNK_SIZE + x) * BLOCK_SIZE 15 | local worldZ = (chunkZ * CHUNK_SIZE + z) * BLOCK_SIZE 16 | local worldY = (chunkY * CHUNK_SIZE + y) * BLOCK_SIZE 17 | return Vector3.new(worldX, worldY, worldZ) 18 | end 19 | 20 | 21 | -- Returns the buffer position of blockId from relative chunk position 22 | function ChunksUtil.chunkToBufferPosition(x: number, z: number, y: number): number 23 | return (x - 1) * CHUNK_SIZE + (z - 1) * CHUNK_SIZE * CHUNK_SIZE + (y - 1) 24 | end 25 | 26 | 27 | -- Converts Vector3(x, y, z) position to a [chunk_x][chunk_z][x][z][y] 28 | function ChunksUtil.worldToChunkPosition(coordinate : Vector3): {number} 29 | local chunkX = ((coordinate.X - BLOCK_SIZE) / BLOCK_SIZE) // CHUNK_SIZE 30 | local chunkZ = ((coordinate.Z - BLOCK_SIZE) / BLOCK_SIZE) // CHUNK_SIZE 31 | local chunkY = ((coordinate.Y - BLOCK_SIZE) / BLOCK_SIZE) // CHUNK_SIZE 32 | local x = coordinate.X / BLOCK_SIZE - (chunkX * CHUNK_SIZE) 33 | local z = coordinate.Z / BLOCK_SIZE - (chunkZ * CHUNK_SIZE) 34 | local y = coordinate.Y / BLOCK_SIZE - (chunkY * CHUNK_SIZE) 35 | return {chunkX, chunkZ, chunkY, x, z, y} 36 | end 37 | 38 | 39 | -- Returns (-1, 1) 40 | function ChunksUtil.simpleNoise(x: number, y: number, seed: number): number 41 | return math.noise(x / 20, y / 20, seed) 42 | end 43 | 44 | --[[ 45 | Octaves = 2, 46 | Lacunarity = 5, -- > 1 47 | Persistence = .2, -- > 0, < 1 48 | Scale = 40, 49 | ]] 50 | function ChunksUtil.continentalnessNoise(x: number, y: number, seed: number): number 51 | local value = 0 52 | local amplitude = 1 53 | local x1 = x 54 | local y1 = y 55 | 56 | for i = 1, 2, 1 do 57 | value += math.noise(x1 / 160, y1 / 160, seed) * amplitude 58 | 59 | y1 *= 5 60 | x1 *= 5 61 | 62 | amplitude *= .2 63 | end 64 | return value 65 | end 66 | 67 | 68 | --[[ 69 | Octaves = 2, 70 | Lacunarity = 4, -- > 1 71 | Persistence = .2, -- > 0, < 1 72 | Scale = 50, 73 | ]] 74 | function ChunksUtil.erosionNoise(x: number, y: number, seed: number): number 75 | local value = 0 76 | local amplitude = 1 77 | local x1 = x 78 | local y1 = y 79 | 80 | for i = 1, 2, 1 do 81 | value += math.noise(x1 / 200, y1 / 200, seed) * amplitude 82 | 83 | y1 *= 4 84 | x1 *= 4 85 | 86 | amplitude *= .2 87 | end 88 | return value 89 | end 90 | 91 | 92 | function ChunksUtil.peaksAndValleysNoise(x: number, y: number, seed: number): number 93 | local value = 0 94 | local amplitude = 1 95 | local x1 = x 96 | local y1 = y 97 | 98 | for i = 1, 2, 1 do 99 | value += math.noise(x1 / 100, y1 / 100, seed) * amplitude 100 | 101 | y1 *= 4 102 | x1 *= 4 103 | 104 | amplitude *= .2 105 | end 106 | return value 107 | end 108 | 109 | 110 | -- Fills in table contents with chunkxz + xyz indices 111 | function ChunksUtil.fillChunksTable(tbl: {}, ...): () 112 | local data = {...} 113 | 114 | while #data > 0 do 115 | if not tbl[data[1]] then 116 | tbl[data[1]] = {} 117 | end 118 | tbl = tbl[data[1]] 119 | table.remove(data, 1) 120 | end 121 | end 122 | 123 | 124 | return ChunksUtil -------------------------------------------------------------------------------- /src/ReplicatedStorage/Shared/ItemsData.luau: -------------------------------------------------------------------------------- 1 | local itemsData = { 2 | Air = { 3 | isTransparent = true, 4 | }, 5 | 6 | Water = { 7 | isTransparent = true, 8 | }, 9 | 10 | Stone = { 11 | Hardness = 20, 12 | PreferredTool = 'Pickaxe', 13 | DropItem = 'Cobblestone', 14 | Type = 'Block', 15 | }, 16 | 17 | Cobblestone = { 18 | Hardness = 20, 19 | PreferredTool = 'Pickaxe', 20 | Type = 'Block', 21 | }, 22 | 23 | ['Grass Block'] = { 24 | Hardness = 6, 25 | PreferredTool = 'Shovel', 26 | DropItem = 'Dirt', 27 | Type = 'Block', 28 | }, 29 | 30 | Dirt = { 31 | Hardness = 5, 32 | PreferredTool = 'Shovel', 33 | Type = 'Block', 34 | }, 35 | 36 | ['Oak Wood Planks'] = { 37 | Hardness = 20, 38 | PreferredTool = 'Axe', 39 | Type = 'Block', 40 | }, 41 | 42 | Bedrock = {}, 43 | 44 | Sand = { 45 | Hardness = 8, 46 | PreferredTool = 'Shovel', 47 | Type = 'Block', 48 | }, 49 | 50 | ['Oak Log'] = { 51 | Hardness = 20, 52 | PreferredTool = 'Axe', 53 | Type = 'Block', 54 | }, 55 | 56 | ['Oak Leaves'] = { 57 | Hardness = 2, 58 | Type = 'Block', 59 | isTransparent = true, 60 | }, 61 | 62 | Glass = { 63 | isTransparent = true, 64 | }, 65 | 66 | ['Coal Ore'] = { 67 | Hardness = 30, 68 | PreferredTool = 'Pickaxe', 69 | DropItem = 'Coal', 70 | Type = 'Block', 71 | }, 72 | 73 | ['Gold Ore'] = { 74 | Hardness = 30, 75 | PreferredTool = 'Pickaxe', 76 | DropItem = 'Raw Gold', 77 | Type = 'Block', 78 | }, 79 | 80 | ['Iron Ore'] = { 81 | Hardness = 30, 82 | PreferredTool = 'Pickaxe', 83 | DropItem = 'Raw Iron', 84 | Type = 'Block', 85 | }, 86 | 87 | ['Diamond Ore'] = { 88 | Hardness = 30, 89 | PreferredTool = 'Pickaxe', 90 | DropItem = 'Diamond', 91 | Type = 'Block', 92 | }, 93 | 94 | ['Wooden Pickaxe'] = { 95 | Type = 'Pickaxe', 96 | }, 97 | 98 | Dandelion = { 99 | Hardness = 0, 100 | Type = 'Plant', 101 | }, 102 | 103 | Grass = { 104 | Hardness = 0, 105 | Type = 'Plant', 106 | }, 107 | 108 | ['Oxeye Daisy'] = { 109 | Hardness = 0, 110 | Type = 'Plant', 111 | }, 112 | 113 | Poppy = { 114 | Hardness = 0, 115 | Type = 'Plant', 116 | }, 117 | 118 | Clay = { 119 | Hardness = 6, 120 | PreferredTool = 'Shovel', 121 | Type = 'Block', 122 | }, 123 | 124 | Gravel = { 125 | Hardness = 6, 126 | PreferredTool = 'Shovel', 127 | Type = 'Block', 128 | }, 129 | 130 | ['Raw Iron'] = { 131 | Type = 'Ore' 132 | }, 133 | 134 | ['Diamond'] = { 135 | Type = 'Ore' 136 | }, 137 | 138 | ['Coal'] = { 139 | Type = 'Ore' 140 | }, 141 | 142 | ['Iron Ingot'] = { 143 | Type = 'Ore' 144 | }, 145 | 146 | ['Wooden Sword'] = { 147 | Type = 'Sword', 148 | }, 149 | 150 | ['Stone Sword'] = { 151 | Type = 'Sword', 152 | }, 153 | 154 | ['Iron Sword'] = { 155 | Type = 'Sword', 156 | }, 157 | 158 | ['Diamond Sword'] = { 159 | Type = 'Sword', 160 | }, 161 | 162 | ['Stone Pickaxe'] = { 163 | Type = 'Pickaxe', 164 | }, 165 | 166 | ['Iron Pickaxe'] = { 167 | Type = 'Pickaxe', 168 | }, 169 | 170 | ['Diamond Pickaxe'] = { 171 | Type = 'Pickaxe', 172 | }, 173 | 174 | ['Wooden Axe'] = { 175 | Type = 'Axe', 176 | }, 177 | 178 | ['Stone Axe'] = { 179 | Type = 'Axe', 180 | }, 181 | 182 | ['Iron Axe'] = { 183 | Type = 'Axe', 184 | }, 185 | 186 | ['Diamond Axe'] = { 187 | Type = 'Axe', 188 | }, 189 | 190 | ['Wooden Shovel'] = { 191 | Type = 'Shovel', 192 | }, 193 | 194 | ['Stone Shovel'] = { 195 | Type = 'Shovel', 196 | }, 197 | 198 | ['Iron Shovel'] = { 199 | Type = 'Shovel', 200 | }, 201 | 202 | ['Diamond Shovel'] = { 203 | Type = 'Shovel', 204 | }, 205 | } 206 | 207 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 208 | 209 | local ItemsIDs = require(ReplicatedStorage.Shared.ItemsIDs) 210 | 211 | function _init() 212 | 213 | for itemID, itemName in ipairs(ItemsIDs) do 214 | 215 | if not itemsData[itemName] then 216 | itemsData[itemName] = {} 217 | end 218 | 219 | itemsData[itemName]['ID'] = itemID 220 | end 221 | end 222 | 223 | -- Init 224 | _init() 225 | 226 | return itemsData -------------------------------------------------------------------------------- /src/ReplicatedStorage/Shared/ItemsIDs.luau: -------------------------------------------------------------------------------- 1 | return { 2 | 3 | 'Air', 4 | 'Water', 5 | 6 | -- Everyday blocks 7 | 'Dirt', 8 | 'Grass Block', 9 | 'Stone', 10 | 'Cobblestone', 11 | 'Oak Wood Planks', 12 | 'Oak Leaves', 13 | 'Oak Log', 14 | 'Bedrock', 15 | 'Sand', 16 | 'Glass', 17 | 18 | -- Ore blocks 19 | 'Coal Ore', 20 | 'Gold Ore', 21 | 'Iron Ore', 22 | 'Diamond Ore', 23 | 24 | -- Tools 25 | 'Wooden Pickaxe', 26 | 'Wooden Axe', 27 | 'Wooden Shovel', 28 | 'Wooden Sword', 29 | 30 | 'Stone Pickaxe', 31 | 'Stone Axe', 32 | 'Stone Shovel', 33 | 'Stone Sword', 34 | 35 | 'Iron Pickaxe', 36 | 'Iron Axe', 37 | 'Iron Shovel', 38 | 'Iron Sword', 39 | 40 | 'Diamond Pickaxe', 41 | 'Diamond Axe', 42 | 'Diamond Shovel', 43 | 'Diamond Sword', 44 | 45 | 'Coal', 46 | 'Gold Ingot', 47 | 'Raw Iron', 48 | 'Iron Ingot', 49 | 'Diamond', 50 | 51 | -- Flowers 52 | 'Grass', 53 | 'Dandelion', 54 | 'Poppy', 55 | 'Oxeye Daisy', 56 | 57 | -- Misc 58 | 'Oak Sapling', 59 | 'Apple' 60 | } -------------------------------------------------------------------------------- /src/ReplicatedStorage/Shared/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/ParallelScripts/BiomeLoaderParallel.client.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local actor: Actor = script:GetActor() 3 | 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | local StarterPlayer = game:GetService('StarterPlayer') 6 | 7 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 8 | local BiomeSelector = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.Biomes.BiomeSelector) 9 | 10 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 11 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 12 | local CHUNK_DISTANCE: number = ChunkSettings['CHUNK_DISTANCE'] 13 | 14 | local ChunkLoadedBiome: BindableEvent = StarterPlayer.StarterPlayerScripts.Events.ChunkLoadedBiome 15 | 16 | -- FUNCTIONS 17 | 18 | function loadChunk(chunkX: number, chunkZ: number, chunkY: number, chunkBlocks: buffer, neighbouringChunks: {}, SEED: number): () 19 | local neighbouringChunksModifications = BiomeSelector.generateBiome(chunkX, chunkZ, chunkY, chunkBlocks, neighbouringChunks, SEED) 20 | 21 | ChunkLoadedBiome:Fire(chunkX, chunkZ, chunkY, chunkBlocks, neighbouringChunks) 22 | task.synchronize() 23 | actor:Destroy() 24 | script:Destroy() 25 | end 26 | 27 | 28 | actor:BindToMessageParallel('beginLoading', loadChunk) -------------------------------------------------------------------------------- /src/StarterPlayer/ParallelScripts/TerrainLoaderParallel.client.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local actor: Actor = script:GetActor() 3 | 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | local StarterPlayer = game:GetService('StarterPlayer') 6 | 7 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 8 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 9 | local TerrainShapeGenerator = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.TerrainShapeGenerator) 10 | 11 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 12 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 13 | local CHUNK_DISTANCE: number = ChunkSettings['CHUNK_DISTANCE'] 14 | 15 | local AIR_ID: number = ItemsData['Air']['ID'] 16 | 17 | local ChunkLoadedTerrain: BindableEvent = StarterPlayer.StarterPlayerScripts.Events.ChunkLoadedTerrain 18 | 19 | -- FUNCTIONS 20 | 21 | function createAirBuffer(): buffer 22 | local chunkBlocks = buffer.create(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE + 2) -- Offset of 2 when checking for neighbours 23 | buffer.fill(chunkBlocks, 0, AIR_ID) 24 | return chunkBlocks 25 | end 26 | 27 | 28 | function loadChunk(chunkX: number, chunkZ: number, chunkY: number, SEED: number): () 29 | local chunkBlocks = createAirBuffer() 30 | TerrainShapeGenerator.generate(chunkX, chunkZ, chunkY, chunkBlocks, SEED) 31 | 32 | ChunkLoadedTerrain:Fire(chunkX, chunkZ, chunkY, chunkBlocks) 33 | task.synchronize() 34 | actor:Destroy() 35 | script:Destroy() 36 | end 37 | 38 | 39 | actor:BindToMessageParallel('beginLoading', loadChunk) -------------------------------------------------------------------------------- /src/StarterPlayer/ParallelScripts/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Main.client.luau: -------------------------------------------------------------------------------- 1 | local Players = game:GetService('Players') 2 | local Modules = Players.LocalPlayer.PlayerScripts:WaitForChild('Modules') 3 | 4 | require(Modules.Chunks.ChunksManager) 5 | --require(Modules.Lighting) 6 | 7 | task.delay(.1, function() 8 | require(Modules.Break) 9 | require(Modules.CollisionsManager) 10 | end) -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Break.luau: -------------------------------------------------------------------------------- 1 | local Break = {} 2 | 3 | local Players = game:GetService('Players') 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | local UserInputService = game:GetService('UserInputService') 6 | 7 | local Player: Player = Players.LocalPlayer 8 | local PlayerScripts = Player.PlayerScripts 9 | 10 | local Modules = PlayerScripts:WaitForChild('Modules') 11 | 12 | local ChunksData = require(Modules.Chunks.Data.ChunksData) 13 | local ChunkRenderer = require(Modules.Chunks.Rendering.ChunkRenderer) 14 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 15 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 16 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 17 | 18 | local mouse: Mouse = Player:GetMouse() 19 | local camera: Camera = workspace.CurrentCamera 20 | 21 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 22 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 23 | 24 | local AIR_ID: number = ItemsData['Air']['ID'] 25 | 26 | -- PRIVATE 27 | 28 | local function roundToNearest(value: number, snap: number): number 29 | return math.round(value / snap) * snap 30 | end 31 | 32 | 33 | local function handleInputBegan(input: InputObject, typing: boolean): () 34 | if 35 | typing 36 | or input.UserInputType ~= Enum.UserInputType.MouseButton1 37 | or not Player.Character 38 | then 39 | return 40 | end 41 | 42 | local rayOrigin = camera.CFrame.Position 43 | local rayDirection = mouse.UnitRay.Direction 44 | 45 | local lastRoundedPosition = nil 46 | 47 | -- Keep raycasting with increments 48 | for i = 1, 150 do 49 | local location = rayOrigin + rayDirection * (i / 10) 50 | local roundedPosition = Vector3.new( 51 | roundToNearest(location.X, BLOCK_SIZE), 52 | roundToNearest(location.Y, BLOCK_SIZE), 53 | roundToNearest(location.Z, BLOCK_SIZE) 54 | ) 55 | 56 | if roundedPosition == lastRoundedPosition then 57 | continue 58 | end 59 | 60 | local chunkPosition = ChunksUtil.worldToChunkPosition(roundedPosition) 61 | local chunkX = chunkPosition[1] 62 | local chunkZ = chunkPosition[2] 63 | local chunkY = chunkPosition[3] 64 | local x = chunkPosition[4] 65 | local z = chunkPosition[5] 66 | local y = math.clamp(chunkPosition[6], 0, CHUNK_SIZE) 67 | 68 | local bufferPosition = ChunksUtil.chunkToBufferPosition(x, z, y) 69 | 70 | local blockId = buffer.readu8(ChunksData[chunkX][chunkZ][chunkY]['buffer'], bufferPosition) 71 | 72 | if blockId ~= AIR_ID then 73 | buffer.writeu8(ChunksData[chunkX][chunkZ][chunkY]['buffer'], bufferPosition, AIR_ID) 74 | local toRerender = {{chunkX, chunkZ, chunkY}} 75 | 76 | if x == 1 then 77 | table.insert(toRerender, {chunkX - 1, chunkZ, chunkY}) 78 | elseif x == CHUNK_SIZE then 79 | table.insert(toRerender, {chunkX + 1, chunkZ, chunkY}) 80 | end 81 | if z == 1 then 82 | table.insert(toRerender, {chunkX, chunkZ - 1, chunkY}) 83 | elseif z == CHUNK_SIZE then 84 | table.insert(toRerender, {chunkX, chunkZ + 1, chunkY}) 85 | end 86 | if y == 1 then 87 | table.insert(toRerender, {chunkX, chunkZ, chunkY - 1}) 88 | elseif y == CHUNK_SIZE then 89 | table.insert(toRerender, {chunkX, chunkZ, chunkY + 1}) 90 | end 91 | 92 | for _, chunk in toRerender do 93 | ChunkRenderer.unrenderChunk(chunk[1], chunk[2], chunk[3]) 94 | ChunkRenderer.renderChunk(chunk[1], chunk[2], chunk[3]) 95 | end 96 | return 97 | end 98 | 99 | lastRoundedPosition = roundedPosition 100 | end 101 | end 102 | 103 | -- EVENTS 104 | 105 | UserInputService.InputBegan:Connect(handleInputBegan) 106 | 107 | return Break -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/ChunksManager.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local ChunkLoading = {} 3 | 4 | local Players = game:GetService('Players') 5 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 6 | local RunService = game:GetService('RunService') 7 | 8 | local Player: Player = Players.LocalPlayer 9 | local Character: Model = Player.Character or Player.CharacterAdded:Wait() 10 | local HumanoidRootPart: Part = Character:WaitForChild('HumanoidRootPart') :: Part 11 | local PlayerScripts = Player.PlayerScripts 12 | 13 | local Modules = PlayerScripts:WaitForChild('Modules') 14 | local Events = PlayerScripts:WaitForChild('Events') 15 | 16 | local ChunkRenderer = require(Modules.Chunks.Rendering.ChunkRenderer) 17 | local ChunksData = require(Modules.Chunks.Data.ChunksData) 18 | local ChunkGenerationStages = require(Modules.Chunks.Data.ChunkGenerationStages) 19 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 20 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 21 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 22 | 23 | local ParallelScripts = PlayerScripts:WaitForChild('ParallelScripts') 24 | 25 | local BiomeLoaderParallel: LocalScript = ParallelScripts.BiomeLoaderParallel 26 | local TerrainLoaderParallel: LocalScript = ParallelScripts.TerrainLoaderParallel 27 | 28 | local ClientSettings = PlayerScripts:WaitForChild('ClientSettings') 29 | 30 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 31 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 32 | local CHUNK_DISTANCE: number = ChunkSettings['CHUNK_DISTANCE'] 33 | local MAX_CHUNK_DISTANCE: number = ChunkSettings['MAX_CHUNK_DISTANCE'] 34 | local MAX_CHUNK_LOADING_ACTORS: number = ChunkSettings['MAX_CHUNK_LOADING_ACTORS'] 35 | 36 | local LOAD_OFFSET: number = 2 37 | 38 | local CHUNKS_TO_RENDER_PER_UPDATE: number = 2 39 | local CHUNKS_TO_LOAD_TERRAIN_PER_UPDATE: number = 7 40 | 41 | local FRAMES_BETWEEN_RENDERS: number = 1 42 | local FRAMES_BETWEEN_LOADS: number = 4 43 | 44 | local AIR_ID: number = ItemsData['Air']['ID'] 45 | local SEED: number = 500 46 | 47 | local framesSinceLastRender: number = 0 48 | local framesSinceLastLoad: number = 0 49 | 50 | local chunkLoadingActors: number = ClientSettings:GetAttribute('ChunkLoadingActors') 51 | local chunkLoadingActorsHalf: number = chunkLoadingActors // 2 -- Amount of biome and terrain actors seperately 52 | 53 | local terrainActors = table.create(chunkLoadingActorsHalf) 54 | local biomeActors = table.create(chunkLoadingActorsHalf) 55 | 56 | local ActorsFolder = Instance.new('Folder') 57 | ActorsFolder.Name = 'Actors' 58 | ActorsFolder.Parent = ParallelScripts 59 | 60 | -- Create actors for loadChunk() 61 | for i = 1, MAX_CHUNK_LOADING_ACTORS // 2 do 62 | local terrainActor = Instance.new('Actor') 63 | terrainActor.Name = 'TerrainActor' .. i 64 | local terrainScript = TerrainLoaderParallel:Clone() 65 | terrainScript.Parent = terrainActor 66 | terrainActor.Parent = ActorsFolder 67 | 68 | local biomeActor = Instance.new('Actor') 69 | biomeActor.Name = 'BiomeActor' .. i 70 | local biomeScript = BiomeLoaderParallel:Clone() 71 | biomeScript.Parent = biomeActor 72 | biomeActor.Parent = ActorsFolder 73 | 74 | table.insert(terrainActors, terrainActor) 75 | table.insert(biomeActors, biomeActor) 76 | end 77 | 78 | -- PRIVATE 79 | 80 | local function onChunkLoadingActorsChanged(): () 81 | local newValue = ClientSettings:GetAttribute('ChunkLoadingActors') 82 | ClientSettings:SetAttribute('ChunkLoadingActors', math.clamp(math.floor(newValue), 2, MAX_CHUNK_LOADING_ACTORS)) 83 | newValue = ClientSettings:GetAttribute('ChunkLoadingActors') 84 | 85 | chunkLoadingActors = newValue 86 | chunkLoadingActorsHalf = newValue // 2 87 | end 88 | 89 | local function renderChunks(toRender: {}): () 90 | local chunksRendered: number = 0 91 | for _, chunk in toRender do 92 | local chunkX = chunk[1] 93 | local chunkZ = chunk[2] 94 | local chunkY = chunk[3] 95 | 96 | ChunksData[chunkX][chunkZ][chunkY]['isRendered'] = true 97 | ChunkRenderer.renderChunk(chunkX, chunkZ, chunkY) 98 | 99 | chunksRendered += 1 100 | if chunksRendered > CHUNKS_TO_RENDER_PER_UPDATE then 101 | return 102 | end 103 | end 104 | end 105 | 106 | 107 | local function unrenderChunks(toUnrender: {}): () 108 | local chunksUnrendered: number = 0 109 | for i = #toUnrender, 1, -1 do 110 | local chunk = toUnrender[i] 111 | local chunkX = chunk[1] 112 | local chunkZ = chunk[2] 113 | local chunkY = chunk[3] 114 | 115 | ChunkRenderer.unrenderChunk(chunkX, chunkZ, chunkY) 116 | ChunksData[chunkX][chunkZ][chunkY]['isRendered'] = false 117 | 118 | chunksUnrendered += 1 119 | if chunksUnrendered > CHUNKS_TO_RENDER_PER_UPDATE then 120 | return 121 | end 122 | end 123 | end 124 | 125 | 126 | -- Calls loadChunk() on every chunk to load 127 | local function loadChunks(toLoad: {}): () 128 | local chunksTerrainLoaded: number = 0 129 | 130 | local chunksToLoadTerrain = {} 131 | local chunksToLoadBiome = {} 132 | 133 | for _, chunk in toLoad do 134 | local chunkX: number = chunk[1] 135 | local chunkZ: number = chunk[2] 136 | local chunkY: number = chunk[3] 137 | 138 | if ChunksData[chunkX][chunkZ][chunkY]['stage'] == ChunkGenerationStages['none'] then 139 | if chunksTerrainLoaded > CHUNKS_TO_LOAD_TERRAIN_PER_UPDATE then 140 | continue 141 | end 142 | ChunksData[chunkX][chunkZ][chunkY]['isLoading'] = true 143 | 144 | table.insert(chunksToLoadTerrain, chunk) 145 | 146 | chunksTerrainLoaded += 1 147 | 148 | elseif ChunksData[chunkX][chunkZ][chunkY]['stage'] == ChunkGenerationStages['terrain'] then 149 | if -- Neighbours' terrain must be loaded 150 | not ChunksData[chunkX][chunkZ][chunkY - 1] 151 | or ChunksData[chunkX][chunkZ][chunkY - 1]['stage'] == ChunkGenerationStages['none'] 152 | then 153 | continue 154 | end 155 | ChunksData[chunkX][chunkZ][chunkY]['isLoading'] = true 156 | 157 | table.insert(chunksToLoadBiome, chunk) 158 | end 159 | end 160 | 161 | task.defer(function() 162 | for _, chunk in chunksToLoadTerrain do 163 | local chunkX: number = chunk[1] 164 | local chunkZ: number = chunk[2] 165 | local chunkY: number = chunk[3] 166 | 167 | terrainActors[math.random(1, chunkLoadingActorsHalf)]:SendMessage('beginLoading', chunkX, chunkZ, chunkY, SEED) 168 | end 169 | 170 | for _, chunk in chunksToLoadBiome do 171 | local chunkX: number = chunk[1] 172 | local chunkZ: number = chunk[2] 173 | local chunkY: number = chunk[3] 174 | 175 | local chunkBlocks: buffer = ChunksData[chunkX][chunkZ][chunkY]['buffer'] 176 | local neighbouringChunks = { 177 | [chunkX .. 'x' .. chunkZ .. 'x' .. chunkY - 1] = ChunksData[chunkX][chunkZ][chunkY - 1]['buffer'] 178 | } 179 | 180 | biomeActors[math.random(1, chunkLoadingActorsHalf)]:SendMessage('beginLoading', chunkX, chunkZ, chunkY, chunkBlocks, neighbouringChunks, SEED) 181 | end 182 | end) 183 | end 184 | 185 | -- Returns chunks to render 186 | local function findChunksToRender(): {} 187 | local characterPosition: Vector3 = HumanoidRootPart.Position 188 | local characterPositionChunkX: number = characterPosition.X // (CHUNK_SIZE * BLOCK_SIZE) 189 | local characterPositionChunkZ: number = characterPosition.Z // (CHUNK_SIZE * BLOCK_SIZE) 190 | local characterPositionChunkY: number = characterPosition.Y // (CHUNK_SIZE * BLOCK_SIZE) 191 | 192 | local toRender = {} 193 | for chunkX = characterPositionChunkX - CHUNK_DISTANCE, characterPositionChunkX + CHUNK_DISTANCE do 194 | local chunkXDistance = (chunkX - characterPositionChunkX) ^ 2 195 | for chunkZ = characterPositionChunkZ - CHUNK_DISTANCE, characterPositionChunkZ + CHUNK_DISTANCE do 196 | local chunkZDistance = (chunkZ - characterPositionChunkZ) ^ 2 197 | for chunkY = characterPositionChunkY - CHUNK_DISTANCE, characterPositionChunkY + CHUNK_DISTANCE do 198 | -- All neighbours must be fully loaded 199 | local distance = chunkXDistance + chunkZDistance + (chunkY - characterPositionChunkY) ^ 2 200 | if 201 | distance > CHUNK_DISTANCE * CHUNK_DISTANCE 202 | or not ChunksData[chunkX] 203 | or not ChunksData[chunkX][chunkZ] 204 | or not ChunksData[chunkX][chunkZ][chunkY] 205 | or ChunksData[chunkX][chunkZ][chunkY]['isRendered'] == true 206 | or ChunksData[chunkX][chunkZ][chunkY]['stage'] ~= ChunkGenerationStages['done'] 207 | or not ChunksData[chunkX + 1] 208 | or not ChunksData[chunkX + 1][chunkZ] 209 | or not ChunksData[chunkX + 1][chunkZ][chunkY] 210 | or ChunksData[chunkX + 1][chunkZ][chunkY]['stage'] ~= ChunkGenerationStages['done'] 211 | or not ChunksData[chunkX - 1] 212 | or not ChunksData[chunkX - 1][chunkZ] 213 | or not ChunksData[chunkX - 1][chunkZ][chunkY] 214 | or ChunksData[chunkX - 1][chunkZ][chunkY]['stage'] ~= ChunkGenerationStages['done'] 215 | or not ChunksData[chunkX][chunkZ + 1] 216 | or not ChunksData[chunkX][chunkZ + 1][chunkY] 217 | or ChunksData[chunkX][chunkZ + 1][chunkY]['stage'] ~= ChunkGenerationStages['done'] 218 | or not ChunksData[chunkX][chunkZ - 1] 219 | or not ChunksData[chunkX][chunkZ - 1][chunkY] 220 | or ChunksData[chunkX][chunkZ - 1][chunkY]['stage'] ~= ChunkGenerationStages['done'] 221 | or not ChunksData[chunkX][chunkZ][chunkY + 1] 222 | or ChunksData[chunkX][chunkZ][chunkY + 1]['stage'] ~= ChunkGenerationStages['done'] 223 | or not ChunksData[chunkX][chunkZ][chunkY - 1] 224 | or ChunksData[chunkX][chunkZ][chunkY - 1]['stage'] ~= ChunkGenerationStages['done'] 225 | then 226 | continue 227 | end 228 | local isAllAir: boolean = true 229 | local chunkBlocks: buffer = ChunksData[chunkX][chunkZ][chunkY]['buffer'] 230 | for i = 1, CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE do 231 | if buffer.readu8(chunkBlocks, i) ~= AIR_ID then 232 | isAllAir = false 233 | break 234 | end 235 | end 236 | if isAllAir then 237 | continue 238 | end 239 | 240 | table.insert(toRender, {chunkX, chunkZ, chunkY}) 241 | end 242 | end 243 | end 244 | return toRender 245 | end 246 | 247 | 248 | -- Returns chunks to unrender 249 | local function findChunksToUnrender(): {} --TODO not good has issues 250 | local characterPosition: Vector3 = HumanoidRootPart.Position 251 | local characterPositionChunkX: number = characterPosition.X // (CHUNK_SIZE * BLOCK_SIZE) 252 | local characterPositionChunkZ: number = characterPosition.Z // (CHUNK_SIZE * BLOCK_SIZE) 253 | local characterPositionChunkY: number = characterPosition.Y // (CHUNK_SIZE * BLOCK_SIZE) 254 | 255 | local safeChunks = {} 256 | 257 | local radius = CHUNK_DISTANCE + 1 258 | 259 | for chunkX = characterPositionChunkX - radius, characterPositionChunkX + radius do 260 | safeChunks[chunkX] = {} 261 | local chunkXDistance = (chunkX - characterPositionChunkX) ^ 2 262 | 263 | for chunkZ = characterPositionChunkZ - radius, characterPositionChunkZ + radius do 264 | safeChunks[chunkX][chunkZ] = {} 265 | local chunkZDistance = (chunkZ - characterPositionChunkZ) ^ 2 266 | 267 | for chunkY = characterPositionChunkY - radius, characterPositionChunkY + radius do 268 | local distance = chunkXDistance + chunkZDistance + (chunkY - characterPositionChunkY) ^ 2 269 | if distance <= radius * radius then 270 | safeChunks[chunkX][chunkZ][chunkY] = true 271 | end 272 | end 273 | end 274 | end 275 | 276 | local toUnrender = {} 277 | -- Find what loaded chunks aren't in shouldBeRendered 278 | for chunkX in ChunksData do 279 | for chunkZ in ChunksData[chunkX] do 280 | for chunkY in ChunksData[chunkX][chunkZ] do 281 | if not ChunksData[chunkX][chunkZ][chunkY]['isRendered'] then 282 | continue 283 | end 284 | if 285 | not safeChunks[chunkX] 286 | or not safeChunks[chunkX][chunkZ] 287 | or not safeChunks[chunkX][chunkZ][chunkY] 288 | then 289 | table.insert(toUnrender, {chunkX, chunkZ, chunkY}) 290 | end 291 | end 292 | end 293 | end 294 | return toUnrender 295 | end 296 | 297 | 298 | -- Returns the closest chunks to the character 299 | local function findChunksToLoad(): {} 300 | local characterPosition: Vector3 = HumanoidRootPart.Position 301 | local characterPositionChunkX: number = characterPosition.X // (CHUNK_SIZE * BLOCK_SIZE) 302 | local characterPositionChunkZ: number = characterPosition.Z // (CHUNK_SIZE * BLOCK_SIZE) 303 | local characterPositionChunkY: number = characterPosition.Y // (CHUNK_SIZE * BLOCK_SIZE) 304 | 305 | local toLoad = {} 306 | local radius = CHUNK_DISTANCE + LOAD_OFFSET 307 | 308 | for chunkX = characterPositionChunkX - radius, characterPositionChunkX + radius do 309 | local chunkXDistance = (chunkX - characterPositionChunkX) ^ 2 310 | for chunkZ = characterPositionChunkZ - radius, characterPositionChunkZ + radius do 311 | local chunkZDistance = (chunkZ - characterPositionChunkZ) ^ 2 312 | for chunkY = characterPositionChunkY - radius, characterPositionChunkY + radius do 313 | 314 | local distance = chunkXDistance + chunkZDistance + (chunkY - characterPositionChunkY) ^ 2 315 | if 316 | ChunksData[chunkX] 317 | and ChunksData[chunkX][chunkZ] 318 | and ChunksData[chunkX][chunkZ][chunkY] 319 | then 320 | local chunkData = ChunksData[chunkX][chunkZ][chunkY] 321 | if 322 | chunkData['stage'] == ChunkGenerationStages['done'] 323 | or chunkData['isLoading'] 324 | or (chunkData['stage'] == ChunkGenerationStages['terrain'] 325 | and distance > (radius - 1) * (radius - 1)) 326 | then 327 | continue 328 | end 329 | end 330 | 331 | if 332 | math.abs(chunkX) > MAX_CHUNK_DISTANCE 333 | or math.abs(chunkY) > MAX_CHUNK_DISTANCE 334 | or math.abs(chunkZ) > MAX_CHUNK_DISTANCE 335 | or distance > radius * radius 336 | then 337 | continue 338 | end 339 | 340 | table.insert(toLoad, {chunkX, chunkZ, chunkY}) 341 | if 342 | not ChunksData[chunkX] 343 | or not ChunksData[chunkX][chunkZ] 344 | or not ChunksData[chunkX][chunkZ][chunkY] 345 | then 346 | ChunksUtil.fillChunksTable(ChunksData, chunkX, chunkZ, chunkY) 347 | ChunksData[chunkX][chunkZ][chunkY]['stage'] = ChunkGenerationStages['none'] 348 | end 349 | end 350 | end 351 | end 352 | return toLoad 353 | end 354 | 355 | 356 | -- Returns chunks not within character's range 357 | local function unloadChunks(chunksToLoad: {}): () 358 | local characterPosition: Vector3 = HumanoidRootPart.Position 359 | local characterPositionChunkX: number = characterPosition.X // (CHUNK_SIZE * BLOCK_SIZE) 360 | local characterPositionChunkZ: number = characterPosition.Z // (CHUNK_SIZE * BLOCK_SIZE) 361 | local characterPositionChunkY: number = characterPosition.Y // (CHUNK_SIZE * BLOCK_SIZE) 362 | 363 | local shouldBeLoaded = {} 364 | 365 | -- Find what chunks should be loaded 366 | local radius = CHUNK_DISTANCE + LOAD_OFFSET + 1 367 | 368 | for chunkX = characterPositionChunkX - radius, characterPositionChunkX + radius do 369 | shouldBeLoaded[chunkX] = {} 370 | local chunkXDistance = (chunkX - characterPositionChunkX) ^ 2 371 | 372 | for chunkZ = characterPositionChunkZ - radius, characterPositionChunkZ + radius do 373 | shouldBeLoaded[chunkX][chunkZ] = {} 374 | local chunkZDistance = (chunkZ - characterPositionChunkZ) ^ 2 375 | 376 | for chunkY = characterPositionChunkY - radius, characterPositionChunkY + radius do 377 | local distance = chunkXDistance + chunkZDistance + (chunkY - characterPositionChunkY) ^ 2 378 | if distance <= radius * radius then 379 | shouldBeLoaded[chunkX][chunkZ][chunkY] = true 380 | end 381 | end 382 | end 383 | end 384 | 385 | -- Find what loaded chunks aren't in shouldBeLoaded 386 | for chunkX in ChunksData do 387 | for chunkZ in ChunksData[chunkX] do 388 | for chunkY in ChunksData[chunkX][chunkZ] do 389 | if 390 | not shouldBeLoaded[chunkX] 391 | or not shouldBeLoaded[chunkX][chunkZ] 392 | or not shouldBeLoaded[chunkX][chunkZ][chunkY] 393 | then 394 | ChunksData[chunkX][chunkZ][chunkY] = nil 395 | end 396 | end 397 | end 398 | end 399 | end 400 | 401 | 402 | -- Reorders table of chunks based on distance from player 403 | local function reorderChunkPriority(chunks: {}): {} 404 | local characterPosition: Vector3 = HumanoidRootPart.Position 405 | local characterPositionChunkX: number = characterPosition.X // (CHUNK_SIZE * BLOCK_SIZE) 406 | local characterPositionChunkZ: number = characterPosition.Z // (CHUNK_SIZE * BLOCK_SIZE) 407 | local characterPositionChunkY: number = characterPosition.Y // (CHUNK_SIZE * BLOCK_SIZE) 408 | 409 | table.sort(chunks, function(a, b) 410 | local aDistance = (a[1] - characterPositionChunkX) ^ 2 + (a[2] - characterPositionChunkZ) ^ 2 + (a[3] - characterPositionChunkY) ^ 2 411 | local bDistance = (b[1] - characterPositionChunkX) ^ 2 + (b[2] - characterPositionChunkZ) ^ 2 + (b[3] - characterPositionChunkY) ^ 2 412 | return aDistance < bDistance 413 | end) 414 | return chunks 415 | end 416 | 417 | 418 | local function handleFrame(): () 419 | framesSinceLastLoad += 1 420 | framesSinceLastRender += 1 421 | if not HumanoidRootPart then 422 | return 423 | end 424 | 425 | if framesSinceLastLoad >= FRAMES_BETWEEN_LOADS then 426 | local chunksToLoad = findChunksToLoad() 427 | reorderChunkPriority(chunksToLoad) 428 | --print(chunksToLoad) 429 | --[[local chunk = chunksToLoad[1] 430 | local x = chunk[1] 431 | local z = chunk[2] 432 | local y = chunk[3] 433 | 434 | print(x, z, y, ChunksData[x][z][y])]] 435 | loadChunks(chunksToLoad) 436 | 437 | unloadChunks() 438 | framesSinceLastLoad = 0 439 | return 440 | end 441 | 442 | if framesSinceLastRender >= FRAMES_BETWEEN_RENDERS then 443 | local chunksToRender = findChunksToRender() 444 | reorderChunkPriority(chunksToRender) 445 | renderChunks(chunksToRender) 446 | 447 | local ChunksToUnrender = findChunksToUnrender() 448 | reorderChunkPriority(ChunksToUnrender) 449 | unrenderChunks(ChunksToUnrender) 450 | framesSinceLastRender = 0 451 | end 452 | end 453 | 454 | local function handleChunkLoadedTerrain(chunkX: number, chunkZ: number, chunkY: number, chunkBlocks: buffer) 455 | ChunksData[chunkX][chunkZ][chunkY]['buffer'] = chunkBlocks 456 | ChunksData[chunkX][chunkZ][chunkY]['stage'] = ChunkGenerationStages['terrain'] 457 | ChunksData[chunkX][chunkZ][chunkY]['isLoading'] = false 458 | end 459 | 460 | local function handleChunkLoadedBiome(chunkX: number, chunkZ: number, chunkY: number, chunkBlocks: buffer, neighbouringChunks: {}) 461 | ChunksData[chunkX][chunkZ][chunkY]['buffer'] = chunkBlocks 462 | ChunksData[chunkX][chunkZ][chunkY]['stage'] = ChunkGenerationStages['done'] 463 | ChunksData[chunkX][chunkZ][chunkY]['isLoading'] = false 464 | 465 | for neighbouringChunkString, neighbouringChunkNewBuffer in neighbouringChunks do 466 | local neighbouringChunkPosition = string.split(neighbouringChunkString, 'x') 467 | local neighbouringChunkX = tonumber(neighbouringChunkPosition[1]) 468 | local neighbouringChunkZ = tonumber(neighbouringChunkPosition[2]) 469 | local neighbouringChunkY = tonumber(neighbouringChunkPosition[3]) 470 | 471 | local neighbouringChunkOldBuffer = ChunksData[neighbouringChunkX][neighbouringChunkZ][neighbouringChunkY]['buffer'] 472 | 473 | for x = 1, CHUNK_SIZE do 474 | local bufferPositionX: number = (x - 1) * CHUNK_SIZE 475 | for z = 1, CHUNK_SIZE do 476 | local bufferPositionZ: number = (z - 1) * CHUNK_SIZE * CHUNK_SIZE 477 | for y = 1, CHUNK_SIZE do 478 | local oldBlockId: number = buffer.readu8(neighbouringChunkOldBuffer, bufferPositionX + bufferPositionZ + (y - 1)) 479 | local newBlockId: number = buffer.readu8(neighbouringChunkNewBuffer, bufferPositionX + bufferPositionZ + (y - 1)) 480 | if oldBlockId == newBlockId then 481 | continue 482 | end 483 | buffer.writeu8(neighbouringChunkOldBuffer, bufferPositionX + bufferPositionZ + (y - 1), newBlockId) 484 | end 485 | end 486 | end 487 | end 488 | end 489 | 490 | -- EVENTS 491 | 492 | RunService.Heartbeat:Connect(handleFrame) 493 | 494 | Events.ChunkLoadedTerrain.Event:Connect(handleChunkLoadedTerrain) 495 | Events.ChunkLoadedBiome.Event:Connect(handleChunkLoadedBiome) 496 | 497 | ClientSettings:GetAttributeChangedSignal('ChunkLoadingActors'):Connect(onChunkLoadingActorsChanged) 498 | 499 | return ChunkLoading 500 | -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Data/ChunkGenerationStages.luau: -------------------------------------------------------------------------------- 1 | -- Represents what stage has been completed by a chunk 2 | return { 3 | ['none'] = 0, 4 | ['terrain'] = 1, 5 | ['biome'] = 2, 6 | ['done'] = 3 7 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Data/ChunksData.luau: -------------------------------------------------------------------------------- 1 | return {} -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Data/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Caves/CheeseCavesGenerator.luau: -------------------------------------------------------------------------------- 1 | local CheeseCavesGenerator = {} 2 | 3 | return CheeseCavesGenerator 4 | -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Caves/SpaghettiCavesGenerator.luau: -------------------------------------------------------------------------------- 1 | local SpaghettiCavesGenerator = {} 2 | 3 | return SpaghettiCavesGenerator 4 | -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Caves/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/BedrockGenerator.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local BedrockGeneration = {} 3 | 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | 6 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 7 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 8 | 9 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 10 | local MAX_CHUNK_DISTANCE: number = ChunkSettings['MAX_CHUNK_DISTANCE'] 11 | 12 | local BEDROCK_ID: number = ItemsData['Bedrock']['ID'] 13 | 14 | -- PUBLIC 15 | 16 | function BedrockGeneration.generate(chunkBlocks: {}, chunkY: number, random: Random): ({}, Random) 17 | if chunkY ~= -MAX_CHUNK_DISTANCE then 18 | return 19 | end 20 | for x = 1, CHUNK_SIZE do 21 | for z = 1, CHUNK_SIZE do 22 | if random:NextInteger(1, 2) == 2 then 23 | chunkBlocks[x][z][CHUNK_SIZE - 1] = BEDROCK_ID 24 | else 25 | chunkBlocks[x][z][CHUNK_SIZE] = BEDROCK_ID 26 | end 27 | end 28 | end 29 | return chunkBlocks, random 30 | end 31 | 32 | return BedrockGeneration -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/BiomeSelector.luau: -------------------------------------------------------------------------------- 1 | local BiomeSelector = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | local StarterPlayer = game:GetService('StarterPlayer') 5 | 6 | local SplineMaps = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.SplineMaps) 7 | local BiomesData = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.Biomes.BiomesData) 8 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 9 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 10 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 11 | 12 | local CHUNK_SIZE = ChunkSettings['CHUNK_SIZE'] 13 | 14 | -- PRIVATE 15 | 16 | function getBiomeGenerator(worldPositionX: number, worldPositionZ: number, chunkY: number, SEED: number): ModuleScript 17 | local temperature: number = math.clamp(ChunksUtil.simpleNoise(worldPositionX, worldPositionZ, SEED) * 2, -2, 2) 18 | local humidity: number = math.clamp(ChunksUtil.simpleNoise(worldPositionX, worldPositionZ, SEED + 1) * 2, -2, 2) 19 | 20 | return BiomesData[1].biomeGenerator 21 | -- Get surface level biome 22 | --[[for _, biomeData in BiomesData do 23 | if 24 | temperature < biomeData.temperature.min 25 | or temperature > biomeData.temperature.max 26 | or humidity < biomeData.humidity.min 27 | or humidity > biomeData.humidity.max 28 | then 29 | continue 30 | end 31 | return biomeData.biomeGenerator 32 | end]] 33 | end 34 | 35 | -- PUBLIC 36 | 37 | function BiomeSelector.generateBiome(chunkX: number, chunkZ: number, chunkY: number, chunkBlocks: buffer, neighbouringChunks: {}, SEED: number): () 38 | for x = 1, CHUNK_SIZE do 39 | local worldPositionX: number = chunkX * CHUNK_SIZE + x 40 | 41 | for z = 1, CHUNK_SIZE do 42 | local worldPositionZ: number = chunkZ * CHUNK_SIZE + z 43 | 44 | local biomeGenerator: ModuleScript = getBiomeGenerator(worldPositionX, worldPositionZ, chunkY, SEED) 45 | local neighbouringChunksModifications = biomeGenerator.generate(chunkBlocks, neighbouringChunks, chunkX, chunkZ, chunkY, x, z) 46 | end 47 | end 48 | end 49 | 50 | return BiomeSelector -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/BiomesData.luau: -------------------------------------------------------------------------------- 1 | local Players = game:GetService('Players') 2 | 3 | local Player: Player = Players.LocalPlayer 4 | local PlayerScripts = Player.PlayerScripts 5 | 6 | local Modules = PlayerScripts:WaitForChild('Modules') 7 | 8 | local ForestGenerator = require(Modules.Chunks.Loading.Terrain.Biomes.Forest.ForestGenerator) 9 | local DesertGenerator = require(Modules.Chunks.Loading.Terrain.Biomes.Desert.DesertGenerator) 10 | local JungleGenerator = require(Modules.Chunks.Loading.Terrain.Biomes.Jungle.JungleGenerator) 11 | local TundraGenerator = require(Modules.Chunks.Loading.Terrain.Biomes.Tundra.TundraGenerator) 12 | 13 | return { 14 | { 15 | temperature = { 16 | min = -1, 17 | max = 1 18 | }, 19 | humidity = { 20 | min = -2, 21 | max = 0 22 | }, 23 | biomeGenerator = ForestGenerator 24 | }, 25 | { 26 | temperature = { 27 | min = 1, 28 | max = 2 29 | }, 30 | humidity = { 31 | min = -2, 32 | max = 2 33 | }, 34 | biomeGenerator = DesertGenerator 35 | }, 36 | { 37 | temperature = { 38 | min = -1, 39 | max = 1 40 | }, 41 | humidity = { 42 | min = 0, 43 | max = 2 44 | }, 45 | biomeGenerator = JungleGenerator 46 | }, 47 | { 48 | temperature = { 49 | min = -2, 50 | max = -1 51 | }, 52 | humidity = { 53 | min = -2, 54 | max = 2 55 | }, 56 | biomeGenerator = TundraGenerator 57 | }, 58 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Desert/DesertGenerator.luau: -------------------------------------------------------------------------------- 1 | local DesertGenerator = {} 2 | 3 | function DesertGenerator.generate(chunkBlocks: buffer) 4 | 5 | end 6 | 7 | return DesertGenerator -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Desert/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/DecorationsGenerator.luau: -------------------------------------------------------------------------------- 1 | local DecorationsGenerator = {} 2 | 3 | local StarterPlayer = game:GetService("StarterPlayer") 4 | 5 | local TreesGenerator = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.Decorations.Trees.TreesGenerator) 6 | --local DandelionGenerator = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.Decorations.DandelionGenerator) 7 | --local GrassGenerator = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.Decorations.GrassGenerator) 8 | --local PoppyGenerator = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.Decorations.PoppyGenerator) 9 | --local OxeyeDaisyGenerator = require(StarterPlayer.StarterPlayerScripts.Modules.Chunks.Loading.Terrain.Decorations.OxeyeDaisyGenerator) 10 | 11 | -- PUBLIC 12 | 13 | function DecorationsGenerator.generate(chunkBlocks: buffer, surfaceY: {}, SEED: number, chunkX: number, chunkZ: number): ({}, {}) 14 | --local chunkKey = .5 * (chunkX + chunkZ) * (chunkX + chunkZ + 1) + chunkZ 15 | 16 | TreesGenerator.generate(chunkBlocks, surfaceY, chunkX, chunkZ) 17 | --GrassGenerator.generate(chunkBlocks, surfaceY, SEED, random, chunkX, chunkZ) 18 | --DandelionGenerator.generate(chunkBlocks, surfaceY, SEED, random, chunkX, chunkZ) 19 | --PoppyGenerator.generate(chunkBlocks, surfaceY, SEED, random, chunkX, chunkZ) 20 | --OxeyeDaisyGenerator.generate(chunkBlocks, surfaceY, SEED, random, chunkX, chunkZ) 21 | 22 | return chunkBlocks 23 | end 24 | 25 | return DecorationsGenerator -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Trees/Tree.luau: -------------------------------------------------------------------------------- 1 | local Tree = {} 2 | 3 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 4 | 5 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 6 | 7 | local OAK_LOG_ID = ItemsData['Oak Log']['ID'] 8 | local OAK_LEAVES_ID = ItemsData['Oak Leaves']['ID'] 9 | 10 | local MIN_LOGS = 3 11 | local MAX_LOGS = 5 12 | 13 | local LOGS_INSIDE_TREE = 2 14 | 15 | -- PRIVATE 16 | 17 | -- Stores oak logs positions in table 18 | function generateLogs(height: number): {} 19 | local tree = {} 20 | for y = 1, height do 21 | table.insert(tree, {0, 0, y, OAK_LOG_ID}) 22 | end 23 | return tree 24 | end 25 | 26 | 27 | -- Stores oak leaves positions in table 28 | function generateLeavesLayers(tree: {}, random: Random, height: number): {} 29 | -- Store first layer 30 | local layer = 1 31 | for x = -2, 2 do 32 | for z = -2, 2 do 33 | -- Skip center 34 | if x == 0 and z == 0 then 35 | continue 36 | end 37 | if math.abs(x) == 2 and math.abs(z) == 2 then 38 | if random:NextInteger(1, 4) <= 1 then 39 | continue 40 | end 41 | end 42 | table.insert(tree, {x, z, layer + height - LOGS_INSIDE_TREE, OAK_LEAVES_ID}) 43 | end 44 | end 45 | layer += 1 46 | 47 | -- Store second layer 48 | for x = -2, 2 do 49 | for z = -2, 2 do 50 | if x == 0 and z == 0 then -- Skip center 51 | continue 52 | end 53 | if math.abs(x) == 2 and math.abs(z) == 2 then 54 | if random:NextInteger(1, 4) <= 1 then 55 | continue 56 | end 57 | end 58 | table.insert(tree, {x, z, layer + height - LOGS_INSIDE_TREE, OAK_LEAVES_ID}) 59 | end 60 | end 61 | layer += 1 62 | 63 | -- Store third layer 64 | for x = -1, 1 do 65 | for z = -1, 1 do 66 | table.insert(tree, {x, z, layer + height - LOGS_INSIDE_TREE, OAK_LEAVES_ID}) 67 | end 68 | end 69 | layer += 1 70 | 71 | -- Store fourth layer 72 | for x = -1, 1 do 73 | for z = -1, 1 do 74 | if math.abs(x) == 1 and math.abs(z) == 1 then 75 | continue 76 | end 77 | table.insert(tree, {x, z, layer + height - LOGS_INSIDE_TREE, OAK_LEAVES_ID}) 78 | end 79 | end 80 | 81 | return tree 82 | end 83 | 84 | 85 | -- PUBLIC 86 | 87 | function Tree.getRandomTree(random: Random): {} 88 | local height = random:NextInteger(MIN_LOGS, MAX_LOGS) 89 | return generateLeavesLayers(generateLogs(height), random, height) 90 | end 91 | 92 | 93 | return Tree -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Trees/TreesGenerator.luau: -------------------------------------------------------------------------------- 1 | local TreeGeneration = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | local StarterPlayer = game:GetService("StarterPlayer") 5 | 6 | local Modules = StarterPlayer.StarterPlayerScripts.Modules 7 | 8 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 9 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 10 | local Tree = require(Modules.Chunks.Loading.Terrain.Decorations.Trees.Tree) 11 | 12 | local BLOCK_SIZE = ChunkSettings['BLOCK_SIZE'] 13 | local CHUNK_SIZE = ChunkSettings['CHUNK_SIZE'] 14 | 15 | local PERCENT_CHANCE_FOR_TREE = 2 16 | 17 | -- PRIVATE 18 | 19 | function createEmptyPreloadedBlocks(preloadedBlocks: {}, chunkX: number, chunkZ: number): ({number: {number: {number: {number: number}}}}) 20 | 21 | if not preloadedBlocks[chunkX] then 22 | preloadedBlocks[chunkX] = {} 23 | end 24 | if not preloadedBlocks[chunkX][chunkZ] then 25 | preloadedBlocks[chunkX][chunkZ] = {} 26 | end 27 | 28 | for x = 1, CHUNK_SIZE do 29 | preloadedBlocks[chunkX][chunkZ][x] = {} 30 | 31 | for z = 1, CHUNK_SIZE do 32 | preloadedBlocks[chunkX][chunkZ][x][z] = {} 33 | end 34 | end 35 | 36 | return preloadedBlocks 37 | end 38 | 39 | -- PUBLIC 40 | 41 | function TreeGeneration.generate(chunkBlocks: buffer, surfaceY: {}, random: Random, chunkX: number, chunkZ: number): ({}, {}) 42 | for x = 1, CHUNK_SIZE do 43 | for z = 1, CHUNK_SIZE do 44 | for _, y in surfaceY[x][z] do 45 | 46 | local currentValue = random:NextInteger(0, 200) 47 | if currentValue > PERCENT_CHANCE_FOR_TREE then 48 | continue 49 | end 50 | 51 | local treeBlocks = Tree.getRandomTree(random) 52 | 53 | for _, blockData in treeBlocks do 54 | 55 | local relativeTreeX = blockData[1] 56 | local relativeTreeZ = blockData[2] 57 | local relativeTreeY = blockData[3] 58 | local blockID = blockData[4] 59 | 60 | local blockX = relativeTreeX + x 61 | local blockZ = relativeTreeZ + z 62 | local blockY = y + relativeTreeY 63 | 64 | local worldPosition = Vector3.new( 65 | (chunkX * CHUNK_SIZE + blockX) * BLOCK_SIZE, 66 | blockY * BLOCK_SIZE, 67 | (chunkZ * CHUNK_SIZE + blockZ) * BLOCK_SIZE 68 | ) 69 | 70 | local chunkPosition = ChunksUtil.worldToChunkPosition(worldPosition) 71 | 72 | local leavesChunkX = chunkPosition[1] 73 | local leavesChunkZ = chunkPosition[2] 74 | local leavesX = chunkPosition[3] 75 | local leavesZ = chunkPosition[4] 76 | local leavesY = chunkPosition[5] 77 | 78 | if -- Block is NOT out of bounds 79 | leavesChunkX == chunkX 80 | and leavesChunkZ == chunkZ 81 | then 82 | chunkBlocks[blockX][blockZ][blockY] = blockID 83 | continue 84 | end 85 | 86 | -- Block IS out of bounds 87 | 88 | -- If chunk not in preloaded -> add chunk to preloaded 89 | --[[if 90 | not preloadedBlocks[leavesChunkX] 91 | or not preloadedBlocks[leavesChunkX][leavesChunkZ] 92 | then 93 | preloadedBlocks = createEmptyPreloadedBlocks(preloadedBlocks, leavesChunkX, leavesChunkZ) 94 | end 95 | 96 | preloadedBlocks[leavesChunkX][leavesChunkZ][leavesX][leavesZ][leavesY] = blockID]] 97 | end 98 | end 99 | end 100 | end 101 | 102 | return chunkBlocks 103 | end 104 | 105 | return TreeGeneration -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Trees/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Unused/DandelionGenerator.luau: -------------------------------------------------------------------------------- 1 | local DandelionGeneration = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | 5 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 6 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 7 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 8 | 9 | local CHUNK_SIZE = ChunkSettings['CHUNK_SIZE'] 10 | 11 | local AIR_ID = ItemsData['Air']['ID'] 12 | local DANDELION_ID = ItemsData['Dandelion']['ID'] 13 | 14 | -- PUBLIC 15 | 16 | -- Returns chunk blocks table including dandelions 17 | function DandelionGeneration.generate(chunkBlocks: {}, surfaceY: {}, SEED: number, random: Random, chunkX: number, chunkZ: number): ({}) 18 | 19 | SEED += 1 20 | 21 | for x = 1, CHUNK_SIZE do 22 | for z = 1, CHUNK_SIZE do 23 | 24 | for _, y in surfaceY[x][z] do 25 | 26 | if chunkBlocks[x][z][y + 1] ~= AIR_ID then 27 | continue 28 | end 29 | 30 | local xPos = chunkX * CHUNK_SIZE + x 31 | local zPos = chunkZ * CHUNK_SIZE + z 32 | 33 | local currentChance = ChunksUtil.simpleNoise(xPos, zPos, SEED) -- Returns [-1, 1] 34 | local currentValue = random:NextInteger(-45, 1) -- Controls frequency 35 | 36 | if currentChance < currentValue then 37 | chunkBlocks[x][z][y + 1] = DANDELION_ID 38 | end 39 | end 40 | end 41 | end 42 | 43 | return chunkBlocks 44 | end 45 | 46 | return DandelionGeneration -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Unused/GrassGenerator.luau: -------------------------------------------------------------------------------- 1 | local GrassGeneration = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | 5 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 6 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 7 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 8 | 9 | local CHUNK_SIZE = ChunkSettings['CHUNK_SIZE'] 10 | 11 | local AIR_ID = ItemsData['Air']['ID'] 12 | local GRASS_ID = ItemsData['Grass']['ID'] 13 | 14 | -- PUBLIC 15 | 16 | function GrassGeneration.generate(chunkBlocks: {}, surfaceY: {}, SEED: number, random: Random, chunkX: number, chunkZ: number): ({}) 17 | 18 | SEED += 2 19 | 20 | for x = 1, CHUNK_SIZE do 21 | for z = 1, CHUNK_SIZE do 22 | 23 | for _, y in surfaceY[x][z] do 24 | 25 | if chunkBlocks[x][z][y + 1] ~= AIR_ID then 26 | continue 27 | end 28 | 29 | local xPos = chunkX * CHUNK_SIZE + x 30 | local ZPos = chunkX * CHUNK_SIZE + z 31 | 32 | local currentChance = ChunksUtil.simpleNoise(xPos, ZPos, SEED) -- Returns [-1, 1] 33 | local currentValue = random:NextInteger(-8, 1) -- Controls frequency 34 | 35 | if currentChance < currentValue then 36 | chunkBlocks[x][z][y + 1] = GRASS_ID 37 | end 38 | end 39 | end 40 | end 41 | 42 | return chunkBlocks 43 | end 44 | 45 | return GrassGeneration -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Unused/OxeyeDaisyGenerator.luau: -------------------------------------------------------------------------------- 1 | local OxeyeGeneration = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | 5 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 6 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 7 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 8 | 9 | local CHUNK_SIZE = ChunkSettings['CHUNK_SIZE'] 10 | 11 | local AIR_ID = ItemsData['Air']['ID'] 12 | local OXEYE_DAISY_ID = ItemsData['Oxeye Daisy']['ID'] 13 | 14 | -- PUBLIC 15 | 16 | -- Returns chunk blocks table including oxeye daisies 17 | function OxeyeGeneration.generate(chunkBlocks: {}, surfaceY: {}, SEED: number, random: Random, chunkX: number, chunkZ: number): ({}) 18 | 19 | SEED += 3 20 | 21 | for x = 1, CHUNK_SIZE do 22 | for z = 1, CHUNK_SIZE do 23 | 24 | for _, y in surfaceY[x][z] do 25 | 26 | if chunkBlocks[x][z][y + 1] ~= AIR_ID then 27 | continue 28 | end 29 | 30 | local xPos = chunkX * CHUNK_SIZE + x 31 | local zPos = chunkZ * CHUNK_SIZE + z 32 | 33 | local currentChance = ChunksUtil.simpleNoise(xPos, zPos, SEED) -- Returns [-1, 1] 34 | local currentValue = random:NextInteger(-40, 1) -- Controls frequency 35 | 36 | if currentChance < currentValue then 37 | chunkBlocks[x][z][y + 1] = OXEYE_DAISY_ID 38 | end 39 | end 40 | end 41 | end 42 | 43 | return chunkBlocks 44 | end 45 | 46 | return OxeyeGeneration -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Unused/PoppyGenerator.luau: -------------------------------------------------------------------------------- 1 | local PoppyGeneration = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | 5 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 6 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 7 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 8 | 9 | local CHUNK_SIZE = ChunkSettings['CHUNK_SIZE'] 10 | 11 | local AIR_ID = ItemsData['Air']['ID'] 12 | local POPPY_ID = ItemsData['Poppy']['ID'] 13 | 14 | -- PUBLIC 15 | 16 | -- Returns chunk blocks table including poppies 17 | function PoppyGeneration.generate(chunkBlocks: {}, surfaceY: {}, SEED: number, random: Random, chunkX: number, chunkZ: number): ({}) 18 | 19 | SEED += 4 20 | 21 | for x = 1, CHUNK_SIZE do 22 | for z = 1, CHUNK_SIZE do 23 | 24 | for _, y in surfaceY[x][z] do 25 | 26 | if chunkBlocks[x][z][y + 1] ~= AIR_ID then 27 | continue 28 | end 29 | 30 | local xPos = chunkX * CHUNK_SIZE + x 31 | local zPos = chunkZ * CHUNK_SIZE + z 32 | 33 | local currentChance = ChunksUtil.simpleNoise(xPos, zPos, SEED) -- Returns [-1, 1] 34 | local currentValue = random:NextInteger(-55, 1) -- Controls frequency 35 | 36 | if currentChance < currentValue then 37 | chunkBlocks[x][z][y + 1] = POPPY_ID 38 | end 39 | end 40 | end 41 | end 42 | 43 | return chunkBlocks 44 | end 45 | 46 | return PoppyGeneration -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/Unused/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/Decorations/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/ForestGenerator.luau: -------------------------------------------------------------------------------- 1 | local ForestGenerator = {} 2 | 3 | local Players = game:GetService('Players') 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | 6 | local Player: Player = Players.LocalPlayer 7 | local PlayerScripts = Player.PlayerScripts 8 | 9 | local Modules = PlayerScripts:WaitForChild('Modules') 10 | 11 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 12 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 13 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 14 | local GrassLayerGenerator = require(Modules.Chunks.Loading.Terrain.Biomes.Forest.GrassLayerGenerator) 15 | --local TreesGenerator = require(Modules.Chunks.Loading.Terrain.Biomes.Forest.Decorations.Trees.TreesGenerator) 16 | 17 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 18 | 19 | local AIR_ID: number = ItemsData['Air']['ID'] 20 | local GRASS_BLOCK_ID: number = ItemsData['Grass Block']['ID'] 21 | local DIRT_ID: number = ItemsData['Dirt']['ID'] 22 | 23 | -- PUBLIC 24 | 25 | function ForestGenerator.generate(chunkBlocks: buffer, neighbouringChunks: {}, chunkX: number, chunkZ: number, chunkY: number, x: number, z: number): {} 26 | GrassLayerGenerator.generate(chunkBlocks, neighbouringChunks, chunkX, chunkZ, chunkY, x, z) 27 | --TreesGenerator.generate(chunkBlocks, neighbouringChunks, neighbouringChunksModifications) 28 | return neighbouringChunks 29 | --[[if buffer.readu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y)) == AIR_ID then 30 | return 31 | end 32 | 33 | if y == CHUNK_SIZE then 34 | local aboveChunk: buffer = neighbouringChunks[chunkX .. chunkZ .. chunkY + 1] 35 | if buffer.readu8(aboveChunk, ChunksUtil.chunkToBufferPosition(x, z, 1)) == AIR_ID then 36 | buffer.writeu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y), GRASS_BLOCK_ID) 37 | end 38 | return 39 | end 40 | 41 | if buffer.readu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y + 1)) == AIR_ID then 42 | buffer.writeu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y), GRASS_BLOCK_ID) 43 | 44 | elseif buffer.readu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y + 1)) == GRASS_BLOCK_ID then 45 | buffer.writeu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y), DIRT_ID) 46 | end]] 47 | end 48 | 49 | return ForestGenerator -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/GrassLayerGenerator.luau: -------------------------------------------------------------------------------- 1 | local GrassLayerGenerator = {} 2 | 3 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 4 | 5 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 6 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 7 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 8 | 9 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 10 | 11 | local AIR_ID: number = ItemsData['Air']['ID'] 12 | local GRASS_BLOCK_ID: number = ItemsData['Grass Block']['ID'] 13 | local DIRT_ID: number = ItemsData['Dirt']['ID'] 14 | local STONE_ID: number = ItemsData['Stone']['ID'] 15 | 16 | local MAX_DIRT: number = 3 17 | 18 | -- PUBLIC 19 | 20 | function GrassLayerGenerator.generate(chunkBlocks: buffer, neighbouringChunks: {}, chunkX: number, chunkZ: number, chunkY: number, x: number, z: number): () 21 | -- Skip air and dirt blocks. 22 | local didPlaceGrassBlock: boolean = false 23 | local dirtsPlaced: number = 0 24 | 25 | for y = 1, CHUNK_SIZE - 1 do 26 | if buffer.readu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y)) ~= STONE_ID then 27 | didPlaceGrassBlock = false 28 | dirtsPlaced = 0 29 | continue 30 | end 31 | 32 | if didPlaceGrassBlock then 33 | dirtsPlaced += 1 34 | buffer.writeu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y), DIRT_ID) 35 | else 36 | if buffer.readu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y + 1)) == AIR_ID then 37 | didPlaceGrassBlock = true 38 | buffer.writeu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, y), GRASS_BLOCK_ID) 39 | end 40 | end 41 | 42 | if dirtsPlaced >= MAX_DIRT then 43 | didPlaceGrassBlock = false 44 | dirtsPlaced = 0 45 | end 46 | end 47 | 48 | --local chunkBelowBlocks: buffer = neighbouringChunks[chunkX .. 'x' .. chunkZ .. 'x' .. chunkY - 1] 49 | --buffer.writeu8(chunkBelowBlocks, ChunksUtil.chunkToBufferPosition(x, z, 5), DIRT_ID) 50 | 51 | -- If bottom blockId == Air -> add grass + dirt sequence in below chunk 52 | if buffer.readu8(chunkBlocks, ChunksUtil.chunkToBufferPosition(x, z, 1)) == AIR_ID then 53 | local chunkBelowBlocks: buffer = neighbouringChunks[chunkX .. 'x' .. chunkZ .. 'x' .. chunkY - 1] 54 | if buffer.readu8(chunkBelowBlocks, ChunksUtil.chunkToBufferPosition(x, z, CHUNK_SIZE), AIR_ID) then 55 | return 56 | end 57 | buffer.writeu8(chunkBelowBlocks, ChunksUtil.chunkToBufferPosition(x, z, CHUNK_SIZE), GRASS_BLOCK_ID) 58 | 59 | for y = 1, MAX_DIRT do 60 | buffer.writeu8(chunkBelowBlocks, ChunksUtil.chunkToBufferPosition(x, z, CHUNK_SIZE - y), DIRT_ID) 61 | end 62 | end 63 | end 64 | 65 | return GrassLayerGenerator -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Forest/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Jungle/JungleGenerator.luau: -------------------------------------------------------------------------------- 1 | local JungleGenerator = {} 2 | 3 | function JungleGenerator.generate(chunkBlocks: buffer) 4 | 5 | end 6 | 7 | return JungleGenerator -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Jungle/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Tundra/TundraGenerator.luau: -------------------------------------------------------------------------------- 1 | local TundraGenerator = {} 2 | 3 | function TundraGenerator.generate(chunkBlocks: buffer) 4 | 5 | end 6 | 7 | return TundraGenerator -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/Tundra/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/Biomes/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/SplineMaps.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local SplineMaps = {} 3 | 4 | local continentalnessBaseHeight: number = 15 5 | local erosionBaseHeight: number = 15 6 | local peaksAndValleysBaseHeight: number = 25 7 | 8 | local continentalnessTerrainHeightOffsetSpline = { 9 | {-1, continentalnessBaseHeight * 0}, 10 | {-0.8, continentalnessBaseHeight * .1}, 11 | {-0.5, continentalnessBaseHeight * .2}, 12 | {-.3, continentalnessBaseHeight * .32}, 13 | {-.1, continentalnessBaseHeight * .5}, 14 | {0, continentalnessBaseHeight * .85}, 15 | {.15, continentalnessBaseHeight * 1}, 16 | {1, continentalnessBaseHeight * 1}, 17 | } 18 | 19 | local continentalnessSquashFactorSpline = { 20 | {}, 21 | {} 22 | } 23 | 24 | local erosionTerrainHeightOffsetSpline = { 25 | {-1, erosionBaseHeight * 0}, 26 | {-.6, erosionBaseHeight * .2}, 27 | {-.3, erosionBaseHeight * .4}, 28 | {0, erosionBaseHeight * .65}, 29 | {0.2, erosionBaseHeight * .8}, 30 | {.3, erosionBaseHeight * 1}, 31 | {1, erosionBaseHeight * 1}, 32 | } 33 | 34 | local erosionSquashFactorSpline = { 35 | {0, 1}, 36 | {.1, .75}, 37 | {.275, .6}, 38 | {.3, .675}, 39 | {.475, .15}, 40 | {.6, .13}, 41 | {.7, .13}, 42 | {.72, .3}, 43 | {.8, .3}, 44 | {.82, .13}, 45 | {1, .05} 46 | } 47 | 48 | local peaksAndValleysTerrainHeightOffsetSpline = { 49 | {-1, .4}, 50 | {-0.3, peaksAndValleysBaseHeight * .25}, 51 | {-0.1, 0}, 52 | {0, peaksAndValleysBaseHeight * .25}, 53 | {.35, peaksAndValleysBaseHeight}, 54 | {.45, peaksAndValleysBaseHeight * .6}, 55 | {.5, peaksAndValleysBaseHeight * .3}, 56 | {1, peaksAndValleysBaseHeight} 57 | } 58 | 59 | local peaksAndValleysSquashFactorSpline = { 60 | {0, 0}, 61 | {.1, .1}, 62 | {.33, .33}, 63 | {.4, .35}, 64 | {.6, .85}, 65 | {1, 1} 66 | } 67 | 68 | 69 | -- PRIVATE 70 | 71 | local function evaluateSpline(sequence: {}, x: number): (number) 72 | if x <= -1 then 73 | return sequence[1][2] 74 | elseif x >= 1 then 75 | return sequence[#sequence][2] 76 | end 77 | 78 | for i = 1, #sequence - 1 do 79 | local currentKeypoint = sequence[i] 80 | local nextKeypoint = sequence[i + 1] 81 | 82 | if x >= currentKeypoint[1] and x < nextKeypoint[1] then 83 | local t = (x - currentKeypoint[1]) / (nextKeypoint[1] - currentKeypoint[1]) 84 | return (1 - t) * currentKeypoint[2] + t * nextKeypoint[2] 85 | end 86 | end 87 | end 88 | 89 | -- PUBLIC 90 | 91 | function SplineMaps.getContinentalnessTerrainHeightOffset(x: number): (number) 92 | return evaluateSpline(continentalnessTerrainHeightOffsetSpline, x) 93 | end 94 | 95 | 96 | function SplineMaps.getErosionTerrainHeightOffset(x: number): (number) 97 | return evaluateSpline(erosionTerrainHeightOffsetSpline, x) 98 | end 99 | 100 | 101 | function SplineMaps.getPeaksAndValleysTerrainHeightOffset(x: number): (number) 102 | return evaluateSpline(peaksAndValleysTerrainHeightOffsetSpline, x) 103 | end 104 | 105 | 106 | function SplineMaps.getContinentalnessSquashFactor(x: number): (number) 107 | return evaluateSpline(continentalnessSquashFactorSpline, x) 108 | end 109 | 110 | 111 | function SplineMaps.getErosionSquashFactor(x: number): (number) 112 | return evaluateSpline(erosionSquashFactorSpline, x) 113 | end 114 | 115 | 116 | function SplineMaps.getPeaksAndValleysSquashFactor(x: number): (number) 117 | return evaluateSpline(peaksAndValleysSquashFactorSpline, x) 118 | end 119 | 120 | 121 | return SplineMaps 122 | -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/TerrainShapeGenerator.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local TerrainShapeGenerator = {} 3 | 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | local Players = game:GetService('Players') 6 | 7 | local Player: Player = Players.LocalPlayer 8 | local PlayerScripts = Player.PlayerScripts 9 | 10 | local Modules = PlayerScripts:WaitForChild('Modules') 11 | 12 | local SplineMaps = require(Modules.Chunks.Loading.Terrain.SplineMaps) 13 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 14 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 15 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 16 | 17 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 18 | 19 | local STONE_ID: number = ItemsData['Stone']['ID'] 20 | 21 | local squishFactor: number = 20 22 | 23 | -- PUBLIC 24 | 25 | function TerrainShapeGenerator.generate(chunkX: number, chunkZ: number, chunkY: number, chunkBlocks: buffer, SEED: number): (buffer) 26 | local baseHeights = {} 27 | for x = 1, CHUNK_SIZE do 28 | local worldPositionX: number = chunkX * CHUNK_SIZE + x 29 | local bufferPositionX: number = (x - 1) * CHUNK_SIZE 30 | baseHeights[x] = {} 31 | 32 | for z = 1, CHUNK_SIZE do 33 | local worldPositionZ: number = chunkZ * CHUNK_SIZE + z 34 | local bufferPositionZ: number = (z - 1) * CHUNK_SIZE * CHUNK_SIZE 35 | 36 | -- maybe use the more complex noise function with diff parameters for each noise 37 | local continentalnessPerlin: number = ChunksUtil.continentalnessNoise(worldPositionX, worldPositionZ, SEED - 1) 38 | local erosionPerlin: number = ChunksUtil.erosionNoise(worldPositionX, worldPositionZ, SEED) 39 | local peaksAndValleysPerlin: number = ChunksUtil.peaksAndValleysNoise(worldPositionX, worldPositionZ, SEED + 1) 40 | 41 | local continentalnessHeightOffset: number = SplineMaps.getContinentalnessTerrainHeightOffset(continentalnessPerlin) 42 | local erosionHeightOffset: number = SplineMaps.getErosionTerrainHeightOffset(erosionPerlin) 43 | local peaksAndValleysHeightOffset: number = SplineMaps.getPeaksAndValleysTerrainHeightOffset(peaksAndValleysPerlin) 44 | 45 | --local continentalnessSquashFactor = SplineMaps.getContinentalnessSquashFactor(continentalnessPerlin) 46 | --local erosionSquashFactor = SplineMaps.getErosionSquashFactor(erosionPerlin) 47 | --local peaksAndValleysSquashFactor = SplineMaps.getPeaksAndValleysSquashFactor(peaksAndValleysPerlin) 48 | 49 | local baseHeight: number = continentalnessHeightOffset - erosionHeightOffset + peaksAndValleysHeightOffset 50 | baseHeights[x][z] = baseHeight 51 | 52 | for y = CHUNK_SIZE, 1, -1 do 53 | local worldPositionY: number = chunkY * CHUNK_SIZE + y 54 | 55 | local density = math.noise(worldPositionX / 20, worldPositionZ / 20, worldPositionY / 20) 56 | local densityModifier: number = (baseHeight - worldPositionY) / squishFactor 57 | 58 | if density + densityModifier > 0 then 59 | buffer.writeu8(chunkBlocks, bufferPositionX + bufferPositionZ + (y - 1), STONE_ID) 60 | end 61 | end 62 | end 63 | end 64 | return chunkBlocks 65 | end 66 | 67 | return TerrainShapeGenerator -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/Terrain/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Loading/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Rendering/ChunkRenderer.luau: -------------------------------------------------------------------------------- 1 | local ChunkRenderer = {} 2 | 3 | -- 3333 max blocks, if face culling implemented 4 | -- = 8 * 8 * 52 = 3328 blocks 5 | --local buffer = buffer.create(12288) 6 | 7 | local AssetService = game:GetService("AssetService") 8 | local Atlas = AssetService:CreateEditableImageAsync("rbxassetid://17400483416") 9 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 10 | local Players = game:GetService('Players') 11 | 12 | local Player: Player = Players.LocalPlayer 13 | local PlayerScripts = Player.PlayerScripts 14 | 15 | local Modules = PlayerScripts:WaitForChild('Modules') 16 | 17 | local ChunksData = require(Modules.Chunks.Data.ChunksData) 18 | local BlockMeshData = require(Modules.Chunks.Rendering.Data.BlockMeshData) 19 | local PlantMeshData = require(Modules.Chunks.Rendering.Data.PlantMeshData) 20 | local AtlasTextureData = require(Modules.Chunks.Rendering.Data.AtlasTextureData) 21 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 22 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 23 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 24 | 25 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 26 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 27 | 28 | local AIR_ID: number = ItemsData['Air']['ID'] 29 | 30 | local ATLAS_SIZE: number = 8 31 | 32 | local faceOrder = { 33 | {BlockMeshData.frontVertices, BlockMeshData.frontIndices}, 34 | {BlockMeshData.backVertices, BlockMeshData.backIndices}, 35 | {BlockMeshData.rightVertices, BlockMeshData.rightIndices}, 36 | {BlockMeshData.leftVertices, BlockMeshData.leftIndices}, 37 | {BlockMeshData.topVertices, BlockMeshData.topIndices}, 38 | {BlockMeshData.bottomVertices, BlockMeshData.bottomIndices} 39 | } 40 | 41 | local BLOCK_NEIGHBOUR_VECTORS = 42 | { 43 | Vector3.new(0, 0, BLOCK_SIZE), 44 | Vector3.new(0, 0, -BLOCK_SIZE), 45 | Vector3.new(BLOCK_SIZE, 0, 0), 46 | Vector3.new(-BLOCK_SIZE, 0, 0), 47 | Vector3.new(0, BLOCK_SIZE, 0), 48 | Vector3.new(0, -BLOCK_SIZE, 0), 49 | } 50 | 51 | -- PRIVATE 52 | 53 | local function createFace(EditableMesh: EditableMesh, faceVertices, faceIndices, blockId, faceId, x, z, y) 54 | local vertices = {} 55 | for _, vertexOffset in faceVertices do 56 | local vertexPosition = Vector3.new(x + vertexOffset.X, y + vertexOffset.Y, z + vertexOffset.Z) 57 | local vertexId = EditableMesh:AddVertex(vertexPosition) 58 | table.insert(vertices, vertexId) 59 | end 60 | for i = 1, 4, 3 do 61 | local vertexId0Position = faceIndices[i] 62 | local vertexId1Position = faceIndices[i + 1] 63 | local vertexId2Position = faceIndices[i + 2] 64 | EditableMesh:AddTriangle(vertices[vertexId0Position], vertices[vertexId1Position], vertices[vertexId2Position]) 65 | end 66 | 67 | -- Find face's texture in atlas -> set vertice's UVs to texture's corner 68 | local texturePosition = AtlasTextureData[blockId][faceId] 69 | local x = texturePosition % ATLAS_SIZE - 1 70 | local y = texturePosition // ATLAS_SIZE 71 | EditableMesh:SetUV(vertices[1], Vector2.new(x / ATLAS_SIZE, y / ATLAS_SIZE)) -- Top left of image 72 | EditableMesh:SetUV(vertices[2], Vector2.new((x + 1) / ATLAS_SIZE, y / ATLAS_SIZE)) -- Top right of image 73 | EditableMesh:SetUV(vertices[3], Vector2.new(x / ATLAS_SIZE, (y + 1) / ATLAS_SIZE)) -- Bottom left of image 74 | EditableMesh:SetUV(vertices[4], Vector2.new((x + 1) / ATLAS_SIZE, (y + 1) / ATLAS_SIZE) ) -- Bottom right of image 75 | end 76 | 77 | 78 | local function getShouldSidesRender(chunkX: number, chunkZ: number, chunkY: number, x: number, z: number, y: number): {} 79 | local shouldSidesRender = {false, false, false, false, false, false} 80 | local worldPosition = ChunksUtil.chunkToWorldPosition(chunkX, chunkZ, chunkY, x, z, y) 81 | 82 | for i, offsetVector in BLOCK_NEIGHBOUR_VECTORS do 83 | local neighborWorldPosition = Vector3.new( 84 | worldPosition.X + offsetVector.X, 85 | worldPosition.Y + offsetVector.Y, 86 | worldPosition.Z + offsetVector.Z 87 | ) 88 | 89 | local neighborChunkPosition = ChunksUtil.worldToChunkPosition(neighborWorldPosition) 90 | local neighborChunkX = neighborChunkPosition[1] 91 | local neighborChunkZ = neighborChunkPosition[2] 92 | local neighborChunkY = neighborChunkPosition[3] 93 | local neighborX = neighborChunkPosition[4] 94 | local neighborZ = neighborChunkPosition[5] 95 | local neighborY = neighborChunkPosition[6] 96 | 97 | local bufferPosition = ChunksUtil.chunkToBufferPosition(neighborX, neighborZ, neighborY) 98 | 99 | shouldSidesRender[i] = buffer.readu8(ChunksData[neighborChunkX][neighborChunkZ][neighborChunkY]['buffer'], bufferPosition) == AIR_ID 100 | end 101 | return shouldSidesRender 102 | end 103 | 104 | -- PUBLIC 105 | 106 | function ChunkRenderer.renderChunk(chunkX: number, chunkZ: number, chunkY: number): () 107 | local MeshPart = Instance.new('MeshPart') 108 | MeshPart.Name = `{chunkX}x{chunkZ}x{chunkY}` 109 | MeshPart.Size = Vector3.new(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) 110 | MeshPart.CanCollide = false 111 | MeshPart.Anchored = true 112 | 113 | local EditableMesh = Instance.new('EditableMesh') 114 | local EditableImage = Atlas:Clone() 115 | 116 | local loadedChunkBlocks: buffer = ChunksData[chunkX][chunkZ][chunkY]['buffer'] 117 | 118 | for x = 1, CHUNK_SIZE do 119 | local bufferPositionX = (x - 1) * CHUNK_SIZE 120 | 121 | for z = 1, CHUNK_SIZE do 122 | local bufferPositionZ = (z - 1) * CHUNK_SIZE * CHUNK_SIZE 123 | 124 | for y = 1, CHUNK_SIZE do 125 | local blockId = buffer.readu8(loadedChunkBlocks, bufferPositionX + bufferPositionZ + (y - 1)) 126 | if 127 | not blockId 128 | or blockId == AIR_ID 129 | or not AtlasTextureData[blockId] 130 | then 131 | continue 132 | end 133 | 134 | local shouldSidesRender: {} = getShouldSidesRender(chunkX, chunkZ, chunkY, x, z, y) 135 | for faceId, shouldRender in shouldSidesRender do 136 | if not shouldRender then 137 | continue 138 | end 139 | 140 | local faceVertices = faceOrder[faceId][1] 141 | local faceIndices = faceOrder[faceId][2] 142 | 143 | createFace(EditableMesh, faceVertices, faceIndices, blockId, faceId, x, z, y) 144 | end 145 | end 146 | end 147 | end 148 | 149 | EditableMesh.Parent = MeshPart 150 | EditableImage.Parent = MeshPart 151 | MeshPart.Position = Vector3.new( 152 | chunkX * CHUNK_SIZE * BLOCK_SIZE, 153 | chunkY * CHUNK_SIZE * BLOCK_SIZE, 154 | chunkZ * CHUNK_SIZE * BLOCK_SIZE 155 | ) 156 | MeshPart.Parent = workspace 157 | end 158 | 159 | 160 | function ChunkRenderer.unrenderChunk(chunkX: number, chunkZ: number, chunkY: number): () 161 | local chunk = workspace:FindFirstChild(`{chunkX}x{chunkZ}x{chunkY}`) 162 | if chunk then 163 | chunk:Destroy() 164 | end 165 | end 166 | 167 | 168 | return ChunkRenderer -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Rendering/Data/AtlasTextureData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | [1] = {}, 3 | [2] = {}, 4 | [3] = {2, 2, 2, 2, 2, 2}, 5 | [4] = {3, 3, 3, 3, 1, 2}, 6 | [5] = {9, 9, 9, 9, 9, 9}, 7 | [6] = {17, 17, 17, 17, 17, 17}, 8 | [7] = {4, 4, 4, 4, 5, 5}, 9 | [8] = {54, 54, 54, 54, 54, 54}, 10 | [9] = {21, 21, 21, 21, 22, 22}, 11 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Rendering/Data/BlockMeshData.luau: -------------------------------------------------------------------------------- 1 | local BlockMeshData = {} 2 | 3 | BlockMeshData.rightVertices = { 4 | Vector3.new(.5, .5, .5), 5 | Vector3.new(.5, .5, -.5), 6 | Vector3.new(.5, -.5, .5), 7 | Vector3.new(.5, -.5, -.5) 8 | } 9 | BlockMeshData.rightIndices = {3, 4, 2, 2, 1, 3} 10 | 11 | BlockMeshData.leftVertices = { 12 | Vector3.new(-.5, .5, -.5), 13 | Vector3.new(-.5, .5, .5), 14 | Vector3.new(-.5, -.5, -.5), 15 | Vector3.new(-.5, -.5, .5) 16 | } 17 | BlockMeshData.leftIndices = {3, 4, 2, 2, 1, 3} 18 | 19 | BlockMeshData.topVertices = { 20 | Vector3.new(-.5, .5, -.5), 21 | Vector3.new(.5, .5, -.5), 22 | Vector3.new(-.5, .5, .5), 23 | Vector3.new(.5, .5, .5) 24 | } 25 | BlockMeshData.topIndices = {3, 4, 2, 2, 1, 3} 26 | 27 | BlockMeshData.bottomVertices = { 28 | Vector3.new(-.5, -.5, -.5), 29 | Vector3.new(.5, -.5, -.5), 30 | Vector3.new(-.5, -.5, .5), 31 | Vector3.new(.5, -.5, .5) 32 | } 33 | BlockMeshData.bottomIndices = {2, 4, 3, 3, 1, 2} 34 | 35 | BlockMeshData.frontVertices = { 36 | Vector3.new(-.5, .5, .5), 37 | Vector3.new(.5, .5, .5), 38 | Vector3.new(-.5, -.5, .5), 39 | Vector3.new(.5, -.5, .5), 40 | } 41 | BlockMeshData.frontIndices = {3, 4, 2, 2, 1, 3} 42 | 43 | BlockMeshData.backVertices = { 44 | Vector3.new(-.5, .5, -.5), 45 | Vector3.new(.5, .5, -.5), 46 | Vector3.new(-.5, -.5, -.5), 47 | Vector3.new(.5, -.5, -.5), 48 | } 49 | BlockMeshData.backIndices = {2, 4, 3, 3, 1, 2} 50 | 51 | return BlockMeshData -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Rendering/Data/PlantMeshData.luau: -------------------------------------------------------------------------------- 1 | local PlantMeshData = {} 2 | 3 | PlantMeshData.leftVertices = { 4 | Vector3.new(-.5, .5, -.5), 5 | Vector3.new(.5, .5, .5), 6 | Vector3.new(-.5, -.5, -.5), 7 | Vector3.new(.5, -.5, .5), 8 | } 9 | PlantMeshData.leftIndices = {3, 4, 2, 2, 1, 3} 10 | 11 | PlantMeshData.rightVertices = { 12 | Vector3.new(-.5, .5, .5), 13 | Vector3.new(.5, .5, -.5), 14 | Vector3.new(-.5, -.5, .5), 15 | Vector3.new(.5, -.5, -.5), 16 | } 17 | PlantMeshData.rightIndices = {3, 4, 2, 2, 1, 3} 18 | 19 | return PlantMeshData -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Rendering/Data/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/Rendering/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Chunks/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/CollisionsManager.luau: -------------------------------------------------------------------------------- 1 | local CollisionsManager = {} 2 | 3 | local Players = game:GetService('Players') 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | local RunService = game:GetService('RunService') 6 | 7 | local Player: Player = Players.LocalPlayer 8 | local Character: Model = Player.Character or Player.CharacterAdded:Wait() 9 | local HumanoidRootPart: Part = Character:WaitForChild('HumanoidRootPart') :: Part 10 | local PlayerScripts = Player.PlayerScripts 11 | 12 | local Modules = PlayerScripts:WaitForChild('Modules') 13 | 14 | local ChunksData = require(Modules.Chunks.Data.ChunksData) 15 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 16 | local ChunksUtil = require(ReplicatedStorage.Shared.ChunksUtil) 17 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 18 | 19 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 20 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 21 | 22 | local AIR_ID: number = ItemsData['Air']['ID'] 23 | 24 | local CollisionParts: Folder = workspace.CollisionParts 25 | local collisionParts = {} 26 | 27 | local IS_DEBUGGING: boolean = false 28 | 29 | for i = 1, 10 do 30 | local collisionPart: BasePart = Instance.new('Part') 31 | collisionPart.Transparency = 1 32 | collisionPart.Material = Enum.Material.Neon 33 | collisionPart.Color = Color3.fromRGB(52, 255, 0) 34 | collisionPart.Anchored = true 35 | collisionPart.CanCollide = true 36 | collisionPart.Size = Vector3.new(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) 37 | if IS_DEBUGGING then 38 | collisionPart.Transparency = .9 39 | collisionPart.Size = Vector3.new(BLOCK_SIZE + .01, BLOCK_SIZE + .01, BLOCK_SIZE + .01) 40 | end 41 | collisionPart.Parent = CollisionParts 42 | table.insert(collisionParts, collisionPart) 43 | end 44 | 45 | 46 | local function roundToNearest(value: number, snap: number): number 47 | return math.round(value / snap) * snap 48 | end 49 | 50 | 51 | local function updateCollisionParts(): () 52 | if not HumanoidRootPart then 53 | return 54 | end 55 | 56 | local characterPosition: Vector3 = HumanoidRootPart.Position 57 | local roundedPosition: Vector3 = Vector3.new( 58 | roundToNearest(characterPosition.X, BLOCK_SIZE), 59 | roundToNearest(characterPosition.Y - 1, BLOCK_SIZE), 60 | roundToNearest(characterPosition.Z, BLOCK_SIZE) 61 | ) 62 | 63 | local surroundingBlockPositions: {Vector3} = {} 64 | 65 | for worldX = roundedPosition.X - BLOCK_SIZE, roundedPosition.X + BLOCK_SIZE, BLOCK_SIZE do 66 | for worldZ = roundedPosition.Z - BLOCK_SIZE, roundedPosition.Z + BLOCK_SIZE, BLOCK_SIZE do 67 | for worldY = roundedPosition.Y - BLOCK_SIZE, roundedPosition.Y + BLOCK_SIZE, BLOCK_SIZE do 68 | local chunkPosition = ChunksUtil.worldToChunkPosition(Vector3.new(worldX, worldY, worldZ)) 69 | local chunkX = chunkPosition[1] 70 | local chunkZ = chunkPosition[2] 71 | local chunkY = chunkPosition[3] 72 | local x = chunkPosition[4] 73 | local z = chunkPosition[5] 74 | local y = chunkPosition[6] 75 | 76 | if 77 | not ChunksData[chunkX] 78 | or not ChunksData[chunkX][chunkZ] 79 | or not ChunksData[chunkX][chunkZ][chunkY] 80 | then 81 | continue 82 | end 83 | local chunkBlocks: buffer = ChunksData[chunkX][chunkZ][chunkY]['buffer'] 84 | 85 | local bufferPosition = ChunksUtil.chunkToBufferPosition(x, z, y) 86 | if buffer.readu8(chunkBlocks, bufferPosition) == AIR_ID then 87 | continue 88 | end 89 | table.insert(surroundingBlockPositions, Vector3.new(worldX, worldY, worldZ)) 90 | end 91 | end 92 | end 93 | 94 | table.sort(surroundingBlockPositions, function(a, b) 95 | return 96 | (a.X - roundedPosition.X) ^ 2 + (a.Y - roundedPosition.Y) ^ 2 + (a.Z - roundedPosition.Z) ^ 2 97 | < 98 | (b.X - roundedPosition.X) ^ 2 + (b.Y - roundedPosition.Y) ^ 2 + (b.Z - roundedPosition.Z) ^ 2 99 | end) 100 | 101 | for i = 1, #collisionParts do 102 | if not surroundingBlockPositions[i] then 103 | collisionParts[i].Position = Vector3.new(0, -1000, 0) 104 | continue 105 | end 106 | collisionParts[i].Position = surroundingBlockPositions[i] 107 | end 108 | end 109 | 110 | RunService.PreRender:Connect(updateCollisionParts) 111 | 112 | return CollisionsManager -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/Lighting.luau: -------------------------------------------------------------------------------- 1 | local LightingController = {} 2 | 3 | local Lighting = game:GetService('Lighting') 4 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 5 | 6 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 7 | 8 | local CHUNK_DISTANCE: number = ChunkSettings['CHUNK_DISTANCE'] 9 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 10 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 11 | 12 | Lighting.FogEnd = CHUNK_DISTANCE * CHUNK_SIZE * BLOCK_SIZE 13 | Lighting.FogStart = (CHUNK_DISTANCE - 1) * CHUNK_SIZE * BLOCK_SIZE 14 | 15 | return LightingController 16 | -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/Modules/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/ParallelScripts/BiomeLoaderParallel.client.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local actor: Actor = script:GetActor() 3 | 4 | if not actor then 5 | return 6 | end 7 | 8 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 9 | local Players = game:GetService('Players') 10 | 11 | local Player: Player = Players.LocalPlayer 12 | local PlayerScripts = Player.PlayerScripts 13 | 14 | local Modules = PlayerScripts:WaitForChild('Modules') 15 | local Events = PlayerScripts:WaitForChild('Events') 16 | 17 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 18 | local BiomeSelector = require(Modules.Chunks.Loading.Terrain.Biomes.BiomeSelector) 19 | 20 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 21 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 22 | local CHUNK_DISTANCE: number = ChunkSettings['CHUNK_DISTANCE'] 23 | 24 | local ChunkLoadedBiome: BindableEvent = Events.ChunkLoadedBiome 25 | 26 | -- FUNCTIONS 27 | 28 | local function loadChunk(chunkX: number, chunkZ: number, chunkY: number, chunkBlocks: buffer, neighbouringChunks: {}, SEED: number): () 29 | local neighbouringChunksModifications = BiomeSelector.generateBiome(chunkX, chunkZ, chunkY, chunkBlocks, neighbouringChunks, SEED) 30 | 31 | ChunkLoadedBiome:Fire(chunkX, chunkZ, chunkY, chunkBlocks, neighbouringChunks) 32 | end 33 | 34 | 35 | actor:BindToMessageParallel('beginLoading', loadChunk) -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/ParallelScripts/TerrainLoaderParallel.client.luau: -------------------------------------------------------------------------------- 1 | --!native 2 | local actor: Actor = script:GetActor() 3 | 4 | if not actor then 5 | return 6 | end 7 | 8 | local ReplicatedStorage = game:GetService('ReplicatedStorage') 9 | local Players = game:GetService('Players') 10 | 11 | local Player: Player = Players.LocalPlayer 12 | local PlayerScripts = Player.PlayerScripts 13 | 14 | local Modules = PlayerScripts:WaitForChild('Modules') 15 | local Events = PlayerScripts:WaitForChild('Events') 16 | 17 | local ChunkSettings = require(ReplicatedStorage.Shared.ChunkSettings) 18 | local ItemsData = require(ReplicatedStorage.Shared.ItemsData) 19 | local TerrainShapeGenerator = require(Modules.Chunks.Loading.Terrain.TerrainShapeGenerator) 20 | 21 | local BLOCK_SIZE: number = ChunkSettings['BLOCK_SIZE'] 22 | local CHUNK_SIZE: number = ChunkSettings['CHUNK_SIZE'] 23 | local CHUNK_DISTANCE: number = ChunkSettings['CHUNK_DISTANCE'] 24 | 25 | local AIR_ID: number = ItemsData['Air']['ID'] 26 | 27 | local ChunkLoadedTerrain: BindableEvent = Events.ChunkLoadedTerrain 28 | 29 | -- FUNCTIONS 30 | 31 | local function createAirBuffer(): buffer 32 | local chunkBlocks = buffer.create(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE + 2) -- Offset of 2 when checking for neighbours 33 | buffer.fill(chunkBlocks, 0, AIR_ID) 34 | return chunkBlocks 35 | end 36 | 37 | 38 | local function loadChunk(chunkX: number, chunkZ: number, chunkY: number, SEED: number): () 39 | local chunkBlocks = createAirBuffer() 40 | TerrainShapeGenerator.generate(chunkX, chunkZ, chunkY, chunkBlocks, SEED) 41 | 42 | ChunkLoadedTerrain:Fire(chunkX, chunkZ, chunkY, chunkBlocks) 43 | end 44 | 45 | 46 | actor:BindToMessageParallel('beginLoading', loadChunk) -------------------------------------------------------------------------------- /src/StarterPlayer/StarterPlayerScripts/ParallelScripts/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreUnknownInstances": true 3 | } --------------------------------------------------------------------------------