Example #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="EditTerrainMapAction"/> class.
        /// </summary>
        /// <remarks>Use <see cref="AddPatch"/> to mark new patches to record and <see cref="OnEditingEnd"/> to finalize patches data after editing action.</remarks>
        /// <param name="terrain">The terrain.</param>
        /// <param name="stride">The data stride (eg. sizeof(float)).</param>
        protected EditTerrainMapAction(FlaxEngine.Terrain terrain, int stride)
        {
            _terrain = terrain._internalId;
            _patches = new List <PatchData>(4);
            var chunkSize     = terrain.ChunkSize;
            var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;

            _heightmapLength   = heightmapSize * heightmapSize;
            _heightmapDataSize = _heightmapLength * stride;
        }
Example #2
0
            public AddPatchAction(FlaxEngine.Terrain terrain, ref Int2 patchCoord)
            {
                if (terrain == null)
                {
                    throw new ArgumentException(nameof(terrain));
                }

                _terrainId  = terrain.ID;
                _patchCoord = patchCoord;
            }
Example #3
0
 private void OnSelectionChanged()
 {
     var terrainNode = Editor.SceneEditing.SelectionCount > 0 ? Editor.SceneEditing.Selection[0] as TerrainNode : null;
     var terrain = terrainNode?.Actor as FlaxEngine.Terrain;
     if (terrain != SelectedTerrain)
     {
         SelectedTerrain = terrain;
         SelectedTerrainChanged?.Invoke();
     }
 }
Example #4
0
        private void OnCreate()
        {
            if (_isWorking)
            {
                return;
            }

            var scene = Level.GetScene(0);

            if (scene == null)
            {
                throw new InvalidOperationException("No scene found to add terrain to it!");
            }

            // Create terrain object and setup some options
            var terrain = new FlaxEngine.Terrain();

            terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
            terrain.Transform        = new Transform(_options.Position, _options.Orientation, _options.Scale);
            terrain.Material         = _options.Material;
            terrain.PhysicalMaterial = _options.PhysicalMaterial;
            terrain.CollisionLOD     = _options.CollisionLOD;
            if (_options.Heightmap)
            {
                terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0);
            }

            // Add to scene (even if generation fails user gets a terrain in the scene)
            terrain.Parent = scene;
            Editor.Instance.Scene.MarkSceneEdited(scene);

            // Show loading label
            var label = new Label
            {
                AnchorPreset    = AnchorPresets.StretchAll,
                Offsets         = Margin.Zero,
                Text            = "Generating terrain...",
                BackgroundColor = Style.Current.ForegroundDisabled,
                Parent          = this,
            };

            // Lock UI
            _editor.Panel.Enabled = false;
            _isWorking            = true;
            _isDone = false;

            // Start async work
            _terrain = terrain;
            var thread = new System.Threading.Thread(Generate)
            {
                Name = "Terrain Generator"
            };

            thread.Start();
        }
Example #5
0
        /// <summary>
        /// Called to start terrain painting
        /// </summary>
        /// <param name="terrain">The terrain.</param>
        private void PaintStart(FlaxEngine.Terrain terrain)
        {
            // Skip if already is painting
            if (IsPainting)
            {
                return;
            }

            _paintTerrain = terrain;
            PaintStarted?.Invoke();
        }
Example #6
0
        /// <summary>
        /// Called to end terrain painting.
        /// </summary>
        private void PaintEnd()
        {
            // Skip if nothing was painted
            if (!IsPainting)
            {
                return;
            }

            _paintTerrain = null;
            PaintEnded?.Invoke();
        }
Example #7
0
            public DeletePatchAction(FlaxEngine.Terrain terrain, ref Int2 patchCoord)
            {
                if (terrain == null)
                {
                    throw new ArgumentException(nameof(terrain));
                }

                _terrainId  = terrain.ID;
                _patchCoord = patchCoord;
                _data       = TerrainTools.SerializePatch(terrain, ref patchCoord);
            }
Example #8
0
        /// <inheritdoc />
        public override void Update(float deltaTime)
        {
            if (_isDone)
            {
                _terrain = null;
                _isDone  = false;
                Close(DialogResult.OK);
                return;
            }

            base.Update(deltaTime);
        }
Example #9
0
            public EditChunkMaterialAction(FlaxEngine.Terrain terrain, ref Int2 patchCoord, ref Int2 chunkCoord, MaterialBase toSet)
            {
                if (terrain == null)
                {
                    throw new ArgumentException(nameof(terrain));
                }

                _terrainId      = terrain.ID;
                _patchCoord     = patchCoord;
                _chunkCoord     = chunkCoord;
                _beforeMaterial = terrain.GetChunkOverrideMaterial(ref patchCoord, ref chunkCoord)?.ID ?? Guid.Empty;
                _afterMaterial  = toSet?.ID ?? Guid.Empty;
            }
Example #10
0
        /// <inheritdoc />
        public override void Update(float deltaTime)
        {
            if (_isDone)
            {
                Editor.Instance.SceneEditing.Select(_terrain);

                _terrain = null;
                _isDone  = false;
                Close(DialogResult.OK);
                return;
            }

            base.Update(deltaTime);
        }
Example #11
0
        private void OnCreate()
        {
            if (_isWorking)
            {
                return;
            }

            var scene = SceneManager.GetScene(0);

            if (scene == null)
            {
                throw new InvalidOperationException("No scene found to add terrain to it!");
            }

            // Create terrain object and setup some options
            var terrain = FlaxEngine.Terrain.New();

            terrain.Setup(_options.LODCount, (int)_options.ChunkSize);
            terrain.Transform        = new Transform(_options.Position, _options.Orientation, _options.Scale);
            terrain.Material         = _options.Material;
            terrain.PhysicalMaterial = _options.PhysicalMaterial;
            terrain.CollisionLOD     = _options.CollisionLOD;

            // Add to scene (even if generation fails user gets a terrain in the scene)
            terrain.Parent = scene;
            Editor.Instance.Scene.MarkSceneEdited(scene);

            // Show loading label
            var label = new Label
            {
                DockStyle       = DockStyle.Fill,
                Text            = "Generating terrain...",
                BackgroundColor = Color.Black * 0.6f,
                Parent          = this,
            };

            // Lock UI
            _editor.Panel.Enabled = false;
            _isWorking            = true;
            _isDone = false;

            // Start async work
            _terrain = terrain;
            var thread = new System.Threading.Thread(Generate);

            thread.Name = "Terrain Generator";
            thread.Start();
        }
Example #12
0
            private void OnPatchEdit(FlaxEngine.Terrain terrain, ref BoundingBox patchBounds)
            {
                Editor.Instance.Scene.MarkSceneEdited(terrain.Scene);

                var  editorOptions = Editor.Instance.Options.Options;
                bool isPlayMode    = Editor.Instance.StateMachine.IsPlayMode;

                // Auto NavMesh rebuild
                if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
                {
                    if (terrain.Scene && (terrain.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
                    {
                        terrain.Scene.BuildNavMesh(patchBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
                    }
                }
            }
Example #13
0
        /// <summary>
        /// Applies the modification to the terrain.
        /// </summary>
        /// <param name="brush">The brush.</param>
        /// <param name="options">The options.</param>
        /// <param name="gizmo">The gizmo.</param>
        /// <param name="terrain">The terrain.</param>
        public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
        {
            // Combine final apply strength
            float strength = Strength * options.Strength * options.DeltaTime;

            if (strength <= 0.0f)
            {
                return;
            }
            if (options.Invert && SupportsNegativeApply)
            {
                strength *= -1;
            }

            // Prepare
            var         chunkSize         = terrain.ChunkSize;
            var         heightmapSize     = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
            var         heightmapLength   = heightmapSize * heightmapSize;
            var         patchSize         = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
            var         tempBuffer        = (float *)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
            var         unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
            ApplyParams p = new ApplyParams
            {
                Terrain       = terrain,
                Brush         = brush,
                Gizmo         = gizmo,
                Options       = options,
                Strength      = strength,
                HeightmapSize = heightmapSize,
                TempBuffer    = tempBuffer,
            };

            // Get brush bounds in terrain local space
            var brushBounds = gizmo.CursorBrushBounds;

            terrain.GetLocalToWorldMatrix(out p.TerrainWorld);
            terrain.GetWorldToLocalMatrix(out var terrainInvWorld);
            BoundingBox.Transform(ref brushBounds, ref terrainInvWorld, out var brushBoundsLocal);

            // TODO: try caching brush weights before apply to reduce complexity and batch brush sampling

            // Process all the patches under the cursor
            for (int patchIndex = 0; patchIndex < gizmo.PatchesUnderCursor.Count; patchIndex++)
            {
                var patch = gizmo.PatchesUnderCursor[patchIndex];
                var patchPositionLocal = new Vector3(patch.PatchCoord.X * patchSize, 0, patch.PatchCoord.Y * patchSize);

                // Transform brush bounds from local terrain space into local patch vertex space
                var brushBoundsPatchLocalMin = (brushBoundsLocal.Minimum - patchPositionLocal) * unitsPerVertexInv;
                var brushBoundsPatchLocalMax = (brushBoundsLocal.Maximum - patchPositionLocal) * unitsPerVertexInv;

                // Calculate patch heightmap area to modify by brush
                var brushPatchMin  = new Int2(Mathf.FloorToInt(brushBoundsPatchLocalMin.X), Mathf.FloorToInt(brushBoundsPatchLocalMin.Z));
                var brushPatchMax  = new Int2(Mathf.CeilToInt(brushBoundsPatchLocalMax.X), Mathf.FloorToInt(brushBoundsPatchLocalMax.Z));
                var modifiedOffset = brushPatchMin;
                var modifiedSize   = brushPatchMax - brushPatchMin;

                // Expand the modification area by one vertex in each direction to ensure normal vectors are updated for edge cases, also clamp to prevent overflows
                if (modifiedOffset.X < 0)
                {
                    modifiedSize.X  += modifiedOffset.X;
                    modifiedOffset.X = 0;
                }
                if (modifiedOffset.Y < 0)
                {
                    modifiedSize.Y  += modifiedOffset.Y;
                    modifiedOffset.Y = 0;
                }
                modifiedSize.X = Mathf.Min(modifiedSize.X + 2, heightmapSize - modifiedOffset.X);
                modifiedSize.Y = Mathf.Min(modifiedSize.Y + 2, heightmapSize - modifiedOffset.Y);

                // Skip patch won't be modified at all
                if (modifiedSize.X <= 0 || modifiedSize.Y <= 0)
                {
                    continue;
                }

                // Get the patch data (cached internally by the c++ core in editor)
                float *sourceHeights = EditHoles ? null : TerrainTools.GetHeightmapData(terrain, ref patch.PatchCoord);
                byte * sourceHoles   = EditHoles ? TerrainTools.GetHolesMaskData(terrain, ref patch.PatchCoord) : null;
                if (sourceHeights == null && sourceHoles == null)
                {
                    throw new FlaxException("Cannot modify terrain. Loading heightmap failed. See log for more info.");
                }

                // Record patch data before editing it
                if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
                {
                    gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord);
                }

                // Apply modification
                p.ModifiedOffset     = modifiedOffset;
                p.ModifiedSize       = modifiedSize;
                p.PatchCoord         = patch.PatchCoord;
                p.PatchPositionLocal = patchPositionLocal;
                p.SourceHeightMap    = sourceHeights;
                p.SourceHolesMask    = sourceHoles;
                Apply(ref p);
            }

            var  editorOptions = Editor.Instance.Options.Options;
            bool isPlayMode    = Editor.Instance.StateMachine.IsPlayMode;

            // Auto NavMesh rebuild
            if (!isPlayMode && editorOptions.General.AutoRebuildNavMesh)
            {
                if (terrain.Scene && (terrain.StaticFlags & StaticFlags.Navigation) == StaticFlags.Navigation)
                {
                    Navigation.BuildNavMesh(terrain.Scene, brushBounds, editorOptions.General.AutoRebuildNavMeshTimeoutMs);
                }
            }
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="EditTerrainHolesMapAction"/> class.
 /// </summary>
 /// <param name="terrain">The terrain.</param>
 public EditTerrainHolesMapAction(FlaxEngine.Terrain terrain)
     : base(terrain, sizeof(byte))
 {
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="EditTerrainSplatMapAction"/> class.
 /// </summary>
 /// <param name="terrain">The terrain.</param>
 public EditTerrainSplatMapAction(FlaxEngine.Terrain terrain)
     : base(terrain, Color32.SizeInBytes)
 {
 }
Example #16
0
 /// <summary>
 /// Initializes a new instance of the <see cref="EditTerrainHeightMapAction"/> class.
 /// </summary>
 /// <param name="terrain">The terrain.</param>
 public EditTerrainHeightMapAction(FlaxEngine.Terrain terrain)
     : base(terrain, sizeof(float))
 {
 }
Example #17
0
        /// <summary>
        /// Applies the modification to the terrain.
        /// </summary>
        /// <param name="brush">The brush.</param>
        /// <param name="options">The options.</param>
        /// <param name="gizmo">The gizmo.</param>
        /// <param name="terrain">The terrain.</param>
        public unsafe void Apply(Brush brush, ref Options options, PaintTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
        {
            // Combine final apply strength
            float strength = Strength * options.Strength * options.DeltaTime * 10.0f;

            if (strength <= 0.0f)
            {
                return;
            }
            if (options.Invert && SupportsNegativeApply)
            {
                strength *= -1;
            }

            // Prepare
            var         splatmapIndex     = ActiveSplatmapIndex;
            var         chunkSize         = terrain.ChunkSize;
            var         heightmapSize     = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1;
            var         heightmapLength   = heightmapSize * heightmapSize;
            var         patchSize         = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
            var         tempBuffer        = (Color32 *)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes).ToPointer();
            var         unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
            ApplyParams p = new ApplyParams
            {
                Terrain       = terrain,
                Brush         = brush,
                Gizmo         = gizmo,
                Options       = options,
                Strength      = strength,
                SplatmapIndex = splatmapIndex,
                HeightmapSize = heightmapSize,
                TempBuffer    = tempBuffer,
            };

            // Get brush bounds in terrain local space
            var brushBounds = gizmo.CursorBrushBounds;

            terrain.GetLocalToWorldMatrix(out p.TerrainWorld);
            terrain.GetWorldToLocalMatrix(out var terrainInvWorld);
            BoundingBox.Transform(ref brushBounds, ref terrainInvWorld, out var brushBoundsLocal);

            // TODO: try caching brush weights before apply to reduce complexity and batch brush sampling

            // Process all the patches under the cursor
            for (int patchIndex = 0; patchIndex < gizmo.PatchesUnderCursor.Count; patchIndex++)
            {
                var patch = gizmo.PatchesUnderCursor[patchIndex];
                var patchPositionLocal = new Vector3(patch.PatchCoord.X * patchSize, 0, patch.PatchCoord.Y * patchSize);

                // Transform brush bounds from local terrain space into local patch vertex space
                var brushBoundsPatchLocalMin = (brushBoundsLocal.Minimum - patchPositionLocal) * unitsPerVertexInv;
                var brushBoundsPatchLocalMax = (brushBoundsLocal.Maximum - patchPositionLocal) * unitsPerVertexInv;

                // Calculate patch heightmap area to modify by brush
                var brushPatchMin  = new Int2(Mathf.FloorToInt(brushBoundsPatchLocalMin.X), Mathf.FloorToInt(brushBoundsPatchLocalMin.Z));
                var brushPatchMax  = new Int2(Mathf.CeilToInt(brushBoundsPatchLocalMax.X), Mathf.FloorToInt(brushBoundsPatchLocalMax.Z));
                var modifiedOffset = brushPatchMin;
                var modifiedSize   = brushPatchMax - brushPatchMin;

                // Clamp to prevent overflows
                if (modifiedOffset.X < 0)
                {
                    modifiedSize.X  += modifiedOffset.X;
                    modifiedOffset.X = 0;
                }
                if (modifiedOffset.Y < 0)
                {
                    modifiedSize.Y  += modifiedOffset.Y;
                    modifiedOffset.Y = 0;
                }
                modifiedSize.X = Mathf.Min(modifiedSize.X, heightmapSize - modifiedOffset.X);
                modifiedSize.Y = Mathf.Min(modifiedSize.Y, heightmapSize - modifiedOffset.Y);

                // Skip patch won't be modified at all
                if (modifiedSize.X <= 0 || modifiedSize.Y <= 0)
                {
                    continue;
                }

                // Get the patch data (cached internally by the c++ core in editor)
                var sourceDataPtr = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndex);
                if (sourceDataPtr == IntPtr.Zero)
                {
                    throw new FlaxException("Cannot modify terrain. Loading splatmap failed. See log for more info.");
                }
                var sourceData = (Color32 *)sourceDataPtr.ToPointer();

                // Record patch data before editing it
                if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
                {
                    gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex);
                }

                // Apply modification
                p.ModifiedOffset     = modifiedOffset;
                p.ModifiedSize       = modifiedSize;
                p.PatchCoord         = patch.PatchCoord;
                p.PatchPositionLocal = patchPositionLocal;
                p.SourceData         = sourceData;
                Apply(ref p);
            }
        }