/// <inheritdoc /> public override unsafe void Apply(ref ApplyParams p) { var strength = p.Strength * -10.0f; var brushPosition = p.Gizmo.CursorPosition; var tempBuffer = (byte *)p.TempBuffer; // Apply brush modification Profiler.BeginEvent("Apply Brush"); for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; var sourceMask = p.SourceHolesMask[zz * p.HeightmapSize + xx] != 0 ? 1.0f : 0.0f; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); samplePositionWorld.Y = brushPosition.Y; var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld); tempBuffer[z * p.ModifiedSize.X + x] = (byte)((sourceMask + paintAmount * strength) < 0.8f ? 0 : 255); } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifyHolesMask(p.Terrain, ref p.PatchCoord, tempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); }
/// <inheritdoc /> public override unsafe void Apply(ref ApplyParams p) { // Prepare var brushPosition = p.Gizmo.CursorPosition; var noise = new PerlinNoise(0, NoiseScale, p.Strength * NoiseAmount); var chunkSize = p.Terrain.ChunkSize; var patchSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount; var patchOffset = p.PatchCoord * patchSize; // Apply brush modification Profiler.BeginEvent("Apply Brush"); for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; var sourceHeight = p.SourceHeightMap[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, sourceHeight, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); var noiseSample = noise.Sample(xx + patchOffset.X, zz + patchOffset.Y); var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld); p.TempBuffer[z * p.ModifiedSize.X + x] = sourceHeight + noiseSample * paintAmount; } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifyHeightMap(p.Terrain, ref p.PatchCoord, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); }
/// <inheritdoc /> public override unsafe void Apply(ref ApplyParams p) { var strength = p.Strength * 1000.0f; var brushPosition = p.Gizmo.CursorPosition; // Apply brush modification Profiler.BeginEvent("Apply Brush"); for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; var sourceHeight = p.SourceHeightMap[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, sourceHeight, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld); p.TempBuffer[z * p.ModifiedSize.X + x] = sourceHeight + paintAmount * strength; } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifyHeightMap(p.Terrain, ref p.PatchCoord, new IntPtr(p.TempBuffer), ref p.ModifiedOffset, ref p.ModifiedSize); }
/// <inheritdoc /> public override unsafe void Apply(ref ApplyParams p) { // Prepare var brushPosition = p.Gizmo.CursorPosition; var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * p.Brush.Size), 2); var max = p.HeightmapSize - 1; var strength = Mathf.Saturate(p.Strength); // Apply brush modification Profiler.BeginEvent("Apply Brush"); for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; var sourceHeight = p.SourceHeightMap[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, sourceHeight, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; if (paintAmount > 0) { // Sum the nearby values float smoothValue = 0; int smoothValueSamples = 0; int minX = Math.Max(x - radius + p.ModifiedOffset.X, 0); int minZ = Math.Max(z - radius + p.ModifiedOffset.Y, 0); int maxX = Math.Min(x + radius + p.ModifiedOffset.X, max); int maxZ = Math.Min(z + radius + p.ModifiedOffset.Y, max); for (int dz = minZ; dz <= maxZ; dz++) { for (int dx = minX; dx <= maxX; dx++) { var height = p.SourceHeightMap[dz * p.HeightmapSize + dx]; smoothValue += height; smoothValueSamples++; } } // Normalize smoothValue /= smoothValueSamples; // Blend between the height and smooth value p.TempBuffer[z * p.ModifiedSize.X + x] = Mathf.Lerp(sourceHeight, smoothValue, paintAmount); } else { p.TempBuffer[z * p.ModifiedSize.X + x] = sourceHeight; } } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifyHeightMap(p.Terrain, ref p.PatchCoord, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); }
/// <inheritdoc /> public override unsafe void Apply(ref ApplyParams p) { var strength = p.Strength; var layer = (int)Layer; var brushPosition = p.Gizmo.CursorPosition; var layerComponent = layer % 4; // Apply brush modification Profiler.BeginEvent("Apply Brush"); for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; var src = p.SourceData[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; // Extract layer weight byte *srcPtr = &src.R; var srcWeight = *(srcPtr + layerComponent) / 255.0f; // Accumulate weight float dstWeight = srcWeight + paintAmount; // Check for solid layer case if (dstWeight >= 1.0f) { // Erase other layers // TODO: maybe erase only the higher layers? // TODO: need to erase also weights form the other splatmaps src = Color32.Transparent; // Use limit value dstWeight = 1.0f; } // Modify packed weight *(srcPtr + layerComponent) = (byte)(dstWeight * 255.0f); // Write back p.TempBuffer[z * p.ModifiedSize.X + x] = src; } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); }
/// <inheritdoc /> public override unsafe void Apply(ref ApplyParams p) { // If used with invert mode pick the target height level if (p.Options.Invert) { var center = p.ModifiedOffset + p.ModifiedSize / 2; TargetHeight = p.SourceHeightMap[center.Y * p.HeightmapSize + center.X]; return; } // Prepare var brushPosition = p.Gizmo.CursorPosition; var targetHeight = TargetHeight; var strength = Mathf.Saturate(p.Strength); // Apply brush modification Profiler.BeginEvent("Apply Brush"); for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; var sourceHeight = p.SourceHeightMap[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, sourceHeight, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; // Blend between the height and the target value p.TempBuffer[z * p.ModifiedSize.X + x] = Mathf.Lerp(sourceHeight, targetHeight, paintAmount); } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifyHeightMap(p.Terrain, ref p.PatchCoord, new IntPtr(p.TempBuffer), ref p.ModifiedOffset, ref p.ModifiedSize); }
/// <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> /// Applies the modification to the terrain. /// </summary> /// <param name="p">The parameters to use.</param> public abstract void Apply(ref ApplyParams p);
/// <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); } }