/// <summary>
        /// Save block data
        /// </summary>
        private void SaveDataInWorldDictionary()
        {
            return;

            if (isTerrainModified && blocks.IsCreated)
            {
                // TODO: save parameters
                //NativeArray<BlockParameter> blockParameterKeys = blockParameters.GetKeyArray(Allocator.Temp);
                //NativeArray<short> blockParameterValues = blockParameters.GetValueArray(Allocator.Temp);
                ChunkSaveData data = new ChunkSaveData(blocks.ToArray());

                SerializableVector2Int serializableChunkPos = SerializableVector2Int.FromVector2Int(ChunkPosition);
                // add new key or update existing data
                if (World.WorldSave.savedChunks.ContainsKey(serializableChunkPos))
                {
                    World.WorldSave.savedChunks[serializableChunkPos] = data;
                }
                else
                {
                    World.WorldSave.savedChunks.Add(serializableChunkPos, data);
                }

                //blockParameterKeys.Dispose();
                //blockParameterValues.Dispose();
            }
        }
Example #2
0
        /// <summary>
        /// Get the voxeldata for a chunk location from file
        /// </summary>
        /// <returns>False if the chunk is empty</returns>
        static bool GetDataForChunkFromFile(Chunk.ID chunkId, string levelName, out ChunkSaveData chunkData)
        {
            chunkData = default;
            IFormatter formatter  = new BinaryFormatter();
            Stream     readStream = new FileStream(
                GetChunkDataFileName(chunkId, levelName),
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read
                )
            {
                Position = 0
            };
            var fileData = formatter.Deserialize(readStream);

            if (fileData is ChunkSaveData)
            {
                chunkData = (ChunkSaveData)fileData;
                readStream.Close();
                return(true);
            }

            readStream.Close();
            return(false);
        }
Example #3
0
    public Chunk(ChunkSaveData data)
    {
        Id = data.Id;
        if (current_id <= data.Id)
        {
            current_id = data.Id + 1;
        }
        X            = data.X;
        Z            = data.Z;
        Blocks       = new List <Block>();
        Block_Groups = new List <BlockGroup>();
        GameObject   = new GameObject(string.Format("Chunk_({0},{1})#{2}", X, Z, Id));
        GameObject.transform.parent = Map.Instance.Block_Container.transform;
        GameObject.SetActive(true);
        Average_Elevation = 0;

        foreach (BlockSaveData block_data in data.Blocks)
        {
            Block block = Block.Load(block_data, GameObject);
            block.Chunk = this;
            Blocks.Add(block);
        }

        foreach (BlockGroupSaveData group_data in data.Block_Groups)
        {
            BlockGroup group = BlockGroup.Load(group_data, this);
            Block_Groups.Add(group);
        }
    }
Example #4
0
        protected override IChunkData InitialiseChunkDataFromSaved(ChunkSaveData chunkSaveData, Vector3Int chunkId)
        {
            var data = chunkDataFactory.Create(chunkId, chunkManager.ChunkDimensions, chunkSaveData.voxels);

            data.SetRotationsFromArray(chunkSaveData.rotatedEntries);
            return(data);
        }
Example #5
0
    public void Save(ChunkSaveData chunkData)
    {
        Vector2Int position = chunkData.position;

        Debug.Log("Saving changes to chunk " + position);
        FileInfo fileInfo = new FileInfo(worldDirectory + "/" + GetFileName(position));

        WriteChunk(fileInfo, chunkData);
    }
Example #6
0
 public void Add(ChunkSaveData chunk)
 {
     if (data == null)
     {
         CustomLogger.Instance.Error("Start_Saving needs to be called before Add");
     }
     else
     {
         data.Chunks.Add(chunk);
     }
 }
        public bool Load(ChunkSaveData saveData)
        {
            if (saveData.densityMap.Length != nativeCollectionStash.densityMap.Length)
            {
                Debug.LogError($"Size of loaded chunk {key.origin} does not match the current chunk size");
                return(false);
            }

            nativeCollectionStash.densityMap.CopyFrom(saveData.densityMap);

            ScheduleRefreshDensitiesJob();
            return(true);
        }
Example #8
0
 private void ReadChunk(FileInfo file, ChunkSaveData saveData)
 {
     //while (busy) System.Threading.Thread.Sleep(4);
     busy = true;
     byte[] buffer = new byte[4];
     using (FileStream stream = new FileStream(file.FullName, FileMode.Open))
     {
         while (stream.Position < stream.Length)
         {
             stream.Read(buffer, 0, 4);
             saveData.changes.Add(new ChunkSaveData.C(buffer[0], buffer[1], buffer[2], buffer[3]));
         }
     }
     busy = false;
 }
Example #9
0
    public ChunkSaveData Load(Vector2Int position)
    {
        ChunkSaveData saveData = new ChunkSaveData(position);
        FileInfo      fileInfo = new FileInfo(worldDirectory + "/" + GetFileName(position));

        if (fileInfo.Exists)
        {
            ReadChunk(fileInfo, saveData);
            //Debug.Log("SaveManager loaded changes to chunk " + saveData.position);
        }
        else
        {
        }
        return(saveData);
    }
Example #10
0
    public void Save()
    {
        ChunkSaveData data = new ChunkSaveData();

        data.Id     = Id;
        data.X      = X;
        data.Z      = Z;
        data.Blocks = new List <BlockSaveData>();
        foreach (Block block in Blocks)
        {
            data.Blocks.Add(block.Save_Data);
        }
        data.Block_Groups = new List <BlockGroupSaveData>();
        foreach (BlockGroup group in Block_Groups)
        {
            data.Block_Groups.Add(group.Save_Data);
        }

        SaveManager.Instance.Add(data);
    }
    private void SaveChunkData(ChunkSaveData data)
    {
        foreach (ChunkData chunkData in data.ChunkData)
        {
            string     fileName = "C:/MinecraftUnity/Data/chunks" + "/" + chunkData.Name + ".mindata";
            FileStream file;
            if (File.Exists(fileName))
            {
                file = File.OpenWrite(fileName);
            }
            else
            {
                file = File.Create(fileName);
            }
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(file, chunkData);

            file.Close();
        }
    }
Example #12
0
    private void WriteChunk(FileInfo file, ChunkSaveData saveData)
    {
        //while (busy) System.Threading.Thread.Sleep(4);
        busy = true;
        byte[] buffer = new byte[4];
        using (FileStream stream = new FileStream(file.FullName, FileMode.Create))
        {
            for (int i = 0; i < saveData.changes.Count; ++i)
            {
                buffer[0] = saveData.changes[i].x;
                buffer[1] = saveData.changes[i].y;
                buffer[2] = saveData.changes[i].z;
                buffer[3] = saveData.changes[i].b;
                stream.Write(buffer, 0, 4);
            }
        }
        Debug.Log("SaveManager saved changes to chunk " + saveData.position);

        busy = false;
    }
    /*  private void Update()
     * {
     *     if(Input.GetKeyDown(KeyCode.F5))
     *     {
     *         SaveFile();
     *     }
     *     if(Input.GetKeyDown(KeyCode.F6))
     *     {
     *         LoadFile();
     *     }
     * }*/



    private void SaveFile()
    {
        string     fileName = "C:/MinecraftUnity/Data" + "/ save.mindata";
        FileStream file;

        if (File.Exists(fileName))
        {
            file = File.OpenWrite(fileName);
        }
        else
        {
            file = File.Create(fileName);
        }

        GameData        data      = new GameData(GetPlayerPosition());
        BinaryFormatter formatter = new BinaryFormatter();

        formatter.Serialize(file, data);

        file.Close();
        ChunkSaveData chunksData = new ChunkSaveData();

        SaveChunkData(chunksData);
    }
Example #14
0
 /// <summary>
 /// Only to be used by jobs
 /// Save a chunk to file
 /// </summary>
 /// <param name="legalLevelSaveName">The level name, only consisting of legal characters. See: IllegalCharactersForFileName</param>
 static public void SaveChunkDataToFile(Coordinate chunkId, string legalLevelSaveName, ChunkSaveData chunkData)
 {
     /*IFormatter formatter = new BinaryFormatter();
      * CheckForSaveDirectory(levelName);
      * Stream stream = new FileStream(GetChunkDataFileName(chunkId, legalLevelSaveName), FileMode.Create, FileAccess.Write, FileShare.None);
      * formatter.Serialize(stream, chunkData);
      * stream.Close();*/
 }
 protected abstract IChunkData InitialiseChunkDataFromSaved(ChunkSaveData chunkSaveData, Vector3Int chunkId);
Example #16
0
    public void LoadTerrain()     //also loads structures INFO
    {
        blocks = new byte[16, 256, 16];
        Vector2Int worldPos = position * 16;

        for (int z = 0; z < 16; ++z)
        {
            for (int x = 0; x < 16; ++x)
            {
                if (worldInfo.type == WorldInfo.Type.Flat)
                {
                    for (int y = 4; y < 256; ++y)
                    {
                        blocks[x, y, z] = BlockTypes.AIR;
                    }
                    blocks[x, 3, z] = BlockTypes.GRASS;
                    blocks[x, 2, z] = BlockTypes.DIRT;
                    blocks[x, 1, z] = BlockTypes.DIRT;
                    blocks[x, 0, z] = BlockTypes.BEDROCK;
                    continue;
                }

                worldX = position.x * 16 + x;
                worldZ = position.y * 16 + z;
                int   bottomHeight = 0;
                float hills        = noise.GetPerlin(worldX * 4f + 500, worldZ * 4f) * 0.5f + .5f;

                if (worldInfo.type == WorldInfo.Type.FloatingIslands)
                {
                    float distanceToSpawn = Vector2.Distance(new Vector2(worldX, worldZ), Vector2.zero);
                    float bigIsland       = Mathf.Clamp01((250f - distanceToSpawn) / 250f);
                    float i1     = noise.GetPerlin(worldX * .5f, worldZ * .5f);
                    float i2     = noise.GetPerlin(worldX * 1f, worldZ * 1f);
                    float i3     = noise.GetPerlin(worldX * 5f, worldZ * 5f);
                    float height = Mathf.Min(i1, i2) + bigIsland + (i3 * 0.02f);
                    height = Mathf.Clamp01(height - 0.1f) / 0.9f;
                    height = Mathf.Pow(height, 1f / 2);
                    if (height == 0)
                    {
                        for (int y = 0; y < 256; ++y)
                        {
                            blocks[x, y, z] = BlockTypes.AIR;
                        }
                        continue;
                    }
                    hills       *= height;               //smooth edge
                    bottomHeight = (int)(SURFACE_HEIGHT - (height * 80));
                }

                int   hillHeight    = (int)(SURFACE_HEIGHT + (hills * 16));
                float bedrock       = noise.GetPerlin(worldX * 64f, worldZ * 64f) * 0.5f + 0.5f;
                int   bedrockHeight = (int)(1 + bedrock * 4);



                for (int y = 0; y < 256; ++y)
                {
                    if (y > hillHeight || y < bottomHeight)
                    {
                        blocks[x, y, z] = BlockTypes.AIR;
                        continue;
                    }
                    if (y < bedrockHeight)
                    {
                        blocks[x, y, z] = BlockTypes.BEDROCK;
                        continue;
                    }

                    if (y > hillHeight - 4)
                    {
                        if (GenerateCaves(x, y, z, 0.2f))
                        {
                            continue;
                        }
                        if (y == hillHeight)
                        {
                            blocks[x, y, z] = BlockTypes.GRASS;
                            //blocks[x, y, z] = BlockTypes.AIR; //TEMP
                            continue;
                        }
                        blocks[x, y, z] = BlockTypes.DIRT;
                        //blocks[x, y, z] = BlockTypes.AIR; //TEMP
                        continue;
                    }
                    else
                    {
                        if (GenerateCaves(x, y, z, 0f))
                        {
                            continue;
                        }
                        if (GenerateOres(x, y, z))
                        {
                            continue;
                        }
                        blocks[x, y, z] = BlockTypes.STONE;
                        //blocks[x, y, z] = BlockTypes.AIR; //TEMP
                        continue;
                    }
                }
            }
        }

        string hash           = World.activeWorld.info.seed.ToString() + position.x.ToString() + position.y.ToString();
        int    structuresSeed = hash.GetHashCode();

        System.Random rnd = new System.Random(structuresSeed);
        structures         = new List <StructureInfo>();
        bool[,] spotsTaken = new bool[16, 16];

        if (worldInfo.type != WorldInfo.Type.Flat)
        {
            //cave entrances
            if (rnd.Next() < STRUCTURE_CHANCE_CAVE_ENTRANCE)
            {
                int h = 255;
                while (h > 0)
                {
                    if (blocks[8, h, 8] != BlockTypes.AIR)
                    {
                        structures.Add(new StructureInfo(new Vector3Int(0, h + 6, 0), Structure.Type.CAVE_ENTRANCE, rnd.Next()));
                        break;
                    }
                    h--;
                }
            }

            //trees
            for (int y = 2; y < 14; ++y)
            {
                for (int x = 2; x < 14; ++x)
                {
                    if (rnd.Next() < STRUCTURE_CHANCE_TREE)
                    {
                        if (IsSpotFree(spotsTaken, new Vector2Int(x, y), 2))
                        {
                            spotsTaken[x, y] = true;
                            int height = 255;
                            while (height > 0)
                            {
                                if (blocks[x, height, y] == BlockTypes.GRASS)
                                {
                                    structures.Add(new StructureInfo(new Vector3Int(x, height + 1, y), Structure.Type.OAK_TREE, rnd.Next()));
                                    break;
                                }
                                height--;
                            }
                        }
                    }
                }
            }
        }

        if (rnd.Next() < STRUCTURE_CHANCE_WELL)
        {
            if (IsSpotFree(spotsTaken, new Vector2Int(7, 7), 3))
            {
                //Debug.Log("Spot is free");

                int  minH     = 255;
                int  maxH     = 0;
                bool canPlace = true;
                for (int y = 5; y < 11; ++y)
                {
                    for (int x = 5; x < 11; ++x)
                    {
                        for (int h = 255; h > -1; h--)
                        {
                            byte b = blocks[x, h, y];
                            if (b != BlockTypes.AIR)
                            {
                                //Debug.Log(b);
                                canPlace &= (b == BlockTypes.GRASS);
                                minH      = Mathf.Min(minH, h);
                                maxH      = Mathf.Max(maxH, h);
                                break;
                            }
                        }
                    }
                }
                canPlace &= Mathf.Abs(minH - maxH) < 2;
                if (canPlace)
                {
                    Debug.Log("spawning well structure");
                    for (int y = 5; y < 11; ++y)
                    {
                        for (int x = 5; x < 11; ++x)
                        {
                            spotsTaken[x, y] = true;
                        }
                    }
                    int h = 255;
                    while (h > 0)
                    {
                        if (blocks[7, h, 7] != BlockTypes.AIR)
                        {
                            structures.Add(new StructureInfo(new Vector3Int(7, h + 1, 7), Structure.Type.WELL, rnd.Next()));
                            break;
                        }
                        h--;
                    }
                }
            }
        }

        //already load changes from disk here (apply later)
        saveData = SaveDataManager.instance.Load(position);

        terrainReady = true;
        //Debug.Log($"Chunk {position} terrain ready");
    }
        private void Update()
        {
            // Exit Sample
            if (Input.GetKey(KeyCode.Escape))
            {
                foreach (var disposable in TestWorld.disposeList)
                {
                    disposable.Dispose();
                }

                Application.Quit();
#if UNITY_EDITOR
                UnityEditor.EditorApplication.isPlaying = false;
#endif
            }

            // Exit Sample and destroy saves
            if (Input.GetKey(KeyCode.RightBracket) && Input.GetKey(KeyCode.Escape))
            {
                foreach (var disposable in TestWorld.disposeList)
                {
                    disposable.Dispose();
                }

                ChunkSaveData.DeleteAll();

                Application.Quit();
#if UNITY_EDITOR
                UnityEditor.EditorApplication.isPlaying = false;
#endif
            }

            // Hide and lock cursor when right mouse button pressed
            if (Input.GetMouseButtonDown(1))
            {
                Cursor.lockState = CursorLockMode.Locked;
            }

            // Unlock and show cursor when right mouse button released
            if (Input.GetMouseButtonUp(1))
            {
                Cursor.visible   = true;
                Cursor.lockState = CursorLockMode.None;
            }

            // Rotation
            if (Input.GetMouseButton(1))
            {
                var mouseMovement = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y") * (invertY ? 1 : -1));

                var mouseSensitivityFactor = mouseSensitivityCurve.Evaluate(mouseMovement.magnitude);

                m_TargetCameraState.yaw   += mouseMovement.x * mouseSensitivityFactor;
                m_TargetCameraState.pitch += mouseMovement.y * mouseSensitivityFactor;
            }

            // Translation
            var translation = GetInputTranslationDirection() * Time.deltaTime;

            // Speed up movement when shift key held
            if (Input.GetKey(KeyCode.LeftShift))
            {
                translation *= 10.0f;
            }

            // Modify movement by a boost factor (defined in Inspector and modified in play mode through the mouse scroll wheel)
            boost       += Input.mouseScrollDelta.y * 0.2f;
            translation *= Mathf.Pow(2.0f, boost);

            m_TargetCameraState.Translate(translation);

            // Framerate-independent interpolation
            // Calculate the lerp amount, such that we get 99% of the way to our target in the specified time
            var positionLerpPct = 1f - Mathf.Exp(Mathf.Log(1f - 0.99f) / positionLerpTime * Time.deltaTime);
            var rotationLerpPct = 1f - Mathf.Exp(Mathf.Log(1f - 0.99f) / rotationLerpTime * Time.deltaTime);
            m_InterpolatingCameraState.LerpTowards(m_TargetCameraState, positionLerpPct, rotationLerpPct);

            m_InterpolatingCameraState.UpdateTransform(transform);
        }
Example #18
0
    public void LoadTerrain()     //also loads structures INFO
    {
        blocks = new byte[16, 256, 16];
        light  = new byte[16, 256, 16];
        Vector2Int worldPos = position * 16;

        for (int z = 0; z < 16; ++z)
        {
            for (int x = 0; x < 16; ++x)
            {
                int   noiseX = worldPos.x + x;
                int   noiseZ = worldPos.y + z;
                float height = SimplexNoise.Noise.CalcPixel2D(noiseX, noiseZ + 50000, 0.01f);
                height = height * 16 + 64;
                int heightInt = (int)height;

                float bedrock = SimplexNoise.Noise.CalcPixel2D(noiseX, noiseZ + 50000, 1f);
                bedrock = bedrock * 3 + 1;
                int bedrockInt = (int)bedrock;

                for (int y = 0; y < 256; ++y)
                {
                    //bedrock
                    if (y < bedrockInt)
                    {
                        blocks[x, y, z] = BlockTypes.BEDROCK;
                        continue;
                    }

                    //air
                    if (y > heightInt)
                    {
                        blocks[x, y, z] = BlockTypes.AIR;
                        continue;
                    }

                    //ores
                    float o1 = SimplexNoise.Noise.CalcPixel3D(noiseX + 50000, y, noiseZ, 0.1f);
                    float o2 = SimplexNoise.Noise.CalcPixel3D(noiseX + 40000, y, noiseZ, 0.1f);
                    float o3 = SimplexNoise.Noise.CalcPixel3D(noiseX + 30000, y, noiseZ, 0.04f);
                    float o4 = SimplexNoise.Noise.CalcPixel3D(noiseX + 60000, y, noiseZ, 0.1f);
                    float o5 = SimplexNoise.Noise.CalcPixel3D(noiseX + 70000, y, noiseZ, 0.1f);
                    float o6 = SimplexNoise.Noise.CalcPixel3D(noiseX + 80000, y, noiseZ, 0.03f);

                    float heightGradient = Mathf.Pow(Mathf.Clamp01(y / 128f), 2f);

                    //caves
                    float c1 = SimplexNoise.Noise.CalcPixel3D(noiseX, y, noiseZ, 0.1f);
                    float c2 = SimplexNoise.Noise.CalcPixel3D(noiseX, y, noiseZ, 0.04f);
                    float c3 = SimplexNoise.Noise.CalcPixel3D(noiseX, y, noiseZ, 0.02f);
                    float c4 = SimplexNoise.Noise.CalcPixel3D(noiseX, y, noiseZ, 0.01f);

                    c1 += (heightGradient);
                    if (c1 < .5 && c2 < .5 && c3 < .5 && c4 < .5)
                    {
                        blocks[x, y, z] = BlockTypes.AIR;
                        continue;
                    }

                    //grass level
                    if (y == heightInt)
                    {
                        blocks[x, y, z] = BlockTypes.GRASS;
                        continue;
                    }

                    //dirt
                    if (y >= heightInt - 4)
                    {
                        blocks[x, y, z] = BlockTypes.DIRT;
                        continue;
                    }



                    o5 += (heightGradient);
                    if (y < 64 && o5 < .04)
                    {
                        blocks[x, y, z] = BlockTypes.GOLD;
                        continue;
                    }

                    if (y < 16 && Mathf.Pow(o2, 4f) > .7 && o3 < .1)
                    {
                        blocks[x, y, z] = BlockTypes.DIAMOND;
                        continue;
                    }

                    if (o4 < .1 && o6 > .8)
                    {
                        blocks[x, y, z] = BlockTypes.IRON;
                        continue;
                    }

                    if (o1 < .08)
                    {
                        blocks[x, y, z] = BlockTypes.COAL;
                        continue;
                    }

                    //remaining is stone
                    blocks[x, y, z] = BlockTypes.STONE;
                    continue;
                }
            }
        }

        string hash           = World.activeWorld.info.seed.ToString() + position.x.ToString() + position.y.ToString();
        int    structuresSeed = hash.GetHashCode();

        //Debug.Log("Chunk structures seed is " + structuresSeed);
        System.Random rnd = new System.Random(structuresSeed);
        structures         = new List <StructureInfo>();
        bool[,] spotsTaken = new bool[16, 16];

        //cave entrances
        if (rnd.Next() < STRUCTURE_CHANCE_CAVE_ENTRANCE)
        {
            int h = 255;
            while (h > 0)
            {
                if (blocks[8, h, 8] != BlockTypes.AIR)
                {
                    structures.Add(new StructureInfo(new Vector3Int(0, h + 6, 0), Structure.Type.CAVE_ENTRANCE, rnd.Next()));
                    //Debug.Log($"Adding cave entrance at {position.x} {position.y}");
                    break;
                }
                h--;
            }
        }

        //trees
        for (int y = 2; y < 14; ++y)
        {
            for (int x = 2; x < 14; ++x)
            {
                if (rnd.Next() < STRUCTURE_CHANCE_TREE)
                {
                    if (IsSpotFree(spotsTaken, new Vector2Int(x, y), 2))
                    {
                        spotsTaken[x, y] = true;
                        int height = 255;
                        while (height > 0)
                        {
                            if (blocks[x, height, y] == BlockTypes.GRASS)
                            {
                                structures.Add(new StructureInfo(new Vector3Int(x, height + 1, y), Structure.Type.OAK_TREE, rnd.Next()));
                                break;
                            }
                            height--;
                        }
                    }
                }
            }
        }

        if (rnd.Next() < STRUCTURE_CHANCE_WELL)
        {
            if (IsSpotFree(spotsTaken, new Vector2Int(7, 7), 3))
            {
                //Debug.Log("Spot is free");

                int  minH     = 255;
                int  maxH     = 0;
                bool canPlace = true;
                for (int y = 5; y < 11; ++y)
                {
                    for (int x = 5; x < 11; ++x)
                    {
                        for (int h = 255; h > -1; h--)
                        {
                            byte b = blocks[x, h, y];
                            if (b != BlockTypes.AIR)
                            {
                                //Debug.Log(b);
                                canPlace &= (b == BlockTypes.GRASS);
                                minH      = Mathf.Min(minH, h);
                                maxH      = Mathf.Max(maxH, h);
                                break;
                            }
                        }
                    }
                }
                canPlace &= Mathf.Abs(minH - maxH) < 2;
                if (canPlace)
                {
                    Debug.Log("spawning well structure");
                    for (int y = 5; y < 11; ++y)
                    {
                        for (int x = 5; x < 11; ++x)
                        {
                            spotsTaken[x, y] = true;
                        }
                    }
                    int h = 255;
                    while (h > 0)
                    {
                        if (blocks[7, h, 7] != BlockTypes.AIR)
                        {
                            structures.Add(new StructureInfo(new Vector3Int(7, h + 1, 7), Structure.Type.WELL, rnd.Next()));
                            break;
                        }
                        h--;
                    }
                }
            }
        }



        //already load changes from disk here (apply later)
        saveData = SaveDataManager.instance.Load(position);

        terrainReady = true;
        //Debug.Log($"Chunk {position} terrain ready");
    }
        public void Save(ChunkSaveData saveData)
        {
            saveData.densityMap = new float[nativeCollectionStash.densityMap.Length];

            nativeCollectionStash.densityMap.CopyToFast(saveData.densityMap);
        }