/// <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; }
public AddPatchAction(FlaxEngine.Terrain terrain, ref Int2 patchCoord) { if (terrain == null) { throw new ArgumentException(nameof(terrain)); } _terrainId = terrain.ID; _patchCoord = patchCoord; }
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(); } }
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(); }
/// <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(); }
/// <summary> /// Called to end terrain painting. /// </summary> private void PaintEnd() { // Skip if nothing was painted if (!IsPainting) { return; } _paintTerrain = null; PaintEnded?.Invoke(); }
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); }
/// <inheritdoc /> public override void Update(float deltaTime) { if (_isDone) { _terrain = null; _isDone = false; Close(DialogResult.OK); return; } base.Update(deltaTime); }
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; }
/// <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); }
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(); }
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); } } }
/// <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) { }
/// <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)) { }
/// <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); } }