Add erase tool, more improvements

Changes are now tracked in `tilesToChange`.
(No more need for `tilesPrevious` and `tilesChanged`.)
Applied to full tiles instead of just height or texture.
main
copygirl 2 months ago
parent 52cc3fa28d
commit 883e2af317
  1. 0
      addons/terrain-editing/icons/corner_tile.png
  2. 6
      addons/terrain-editing/icons/corner_tile.png.import
  3. BIN
      addons/terrain-editing/icons/erase.png
  4. 34
      addons/terrain-editing/icons/erase.png.import
  5. 23
      addons/terrain-editing/terrain_editing_controls.gd
  6. 9
      addons/terrain-editing/terrain_editing_controls.tscn
  7. 208
      terrain/Terrain+Editing.cs
  8. 11
      utility/CollectionExtensions.cs

Before

Width:  |  Height:  |  Size: 124 B

After

Width:  |  Height:  |  Size: 124 B

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dc0q2xn2cgcjw" uid="uid://dc0q2xn2cgcjw"
path="res://.godot/imported/corner_paint.png-c66b764e062a869b0cd32b525c1718b2.ctex" path="res://.godot/imported/corner_tile.png-0c44f85b2fc372771ac9f78ccd76ab93.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://addons/terrain-editing/icons/corner_paint.png" source_file="res://addons/terrain-editing/icons/corner_tile.png"
dest_files=["res://.godot/imported/corner_paint.png-c66b764e062a869b0cd32b525c1718b2.ctex"] dest_files=["res://.godot/imported/corner_tile.png-0c44f85b2fc372771ac9f78ccd76ab93.ctex"]
[params] [params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://7kstj7og5bsh"
path="res://.godot/imported/erase.png-ab9c7058d6cbc29ffa2b36ef2f531c01.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/terrain-editing/icons/erase.png"
dest_files=["res://.godot/imported/erase.png-ab9c7058d6cbc29ffa2b36ef2f531c01.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

@ -2,10 +2,10 @@
class_name TerrainEditingControls class_name TerrainEditingControls
extends VBoxContainer extends VBoxContainer
enum ToolMode { HEIGHT, FLATTEN, PAINT } enum ToolMode { HEIGHT, FLATTEN, PAINT, ERASE }
enum ToolShape { CORNER, CIRCLE, SQUARE } enum ToolShape { CORNER, CIRCLE, SQUARE }
@onready var tool_mode_buttons : Array[Button] = [ $Height, $Flatten, $Paint ] @onready var tool_mode_buttons : Array[Button] = [ $Height, $Flatten, $Paint, $Erase ]
@onready var tool_shape_buttons : Array[Button] = [ $Corner, $Circle, $Square ] @onready var tool_shape_buttons : Array[Button] = [ $Corner, $Circle, $Square ]
@onready var paint_texture_buttons : Array[Button] = [ $Grass, $Dirt, $Rock, $Sand ] @onready var paint_texture_buttons : Array[Button] = [ $Grass, $Dirt, $Rock, $Sand ]
@ -15,14 +15,14 @@ enum ToolShape { CORNER, CIRCLE, SQUARE }
@onready var connected_toggle : Button = $Connected @onready var connected_toggle : Button = $Connected
@onready var corner_texture_default : Texture2D = preload("icons/corner.png") @onready var corner_texture_default : Texture2D = preload("icons/corner.png")
@onready var corner_texture_paint : Texture2D = preload("icons/corner_paint.png") @onready var corner_texture_tile : Texture2D = preload("icons/corner_tile.png")
## Gets or sets the currently active tool mode (HEIGHT, FLATTEN, PAINT). ## Gets or sets the currently active tool mode.
var tool_mode : ToolMode: var tool_mode : ToolMode:
get: return index_of_pressed(tool_mode_buttons) get: return index_of_pressed(tool_mode_buttons)
set(value): set_pressed(tool_mode_buttons, value); tool_mode_changed() set(value): set_pressed(tool_mode_buttons, value); tool_mode_changed()
## Gets or sets the currently selected tool shape (CORNER, CIRCLE, SQUARE). ## Gets or sets the currently selected tool shape.
var tool_shape : ToolShape: var tool_shape : ToolShape:
get: return index_of_pressed(tool_shape_buttons) get: return index_of_pressed(tool_shape_buttons)
set(value): set_pressed(tool_shape_buttons, value); tool_shape_changed() set(value): set_pressed(tool_shape_buttons, value); tool_shape_changed()
@ -56,15 +56,22 @@ func _ready() -> void:
func tool_mode_changed() -> void: func tool_mode_changed() -> void:
var is_height := (tool_mode == ToolMode.HEIGHT) var is_height := (tool_mode == ToolMode.HEIGHT)
var is_flatten := (tool_mode == ToolMode.FLATTEN)
var is_paint := (tool_mode == ToolMode.PAINT) var is_paint := (tool_mode == ToolMode.PAINT)
var is_erase := (tool_mode == ToolMode.ERASE)
# In 'PAINT' mode, 'CORNER' affects a single tile regardless of 'draw_size'. # In 'PAINT' and 'ERASE' mode, 'CORNER' affects a single tile regardless of 'draw_size'.
# This changes the button's icon to a small square to communicate that. # This changes the button's icon to a small square to communicate that.
tool_shape_buttons[0].icon = corner_texture_paint if is_paint else corner_texture_default tool_shape_buttons[0].icon = corner_texture_tile if is_paint or is_erase else corner_texture_default
# Enable texture buttons only in 'PAINT' mode.
for button in paint_texture_buttons: button.disabled = !is_paint for button in paint_texture_buttons: button.disabled = !is_paint
# Enable raise/lower toggle only in 'HEIGHT' mode.
raise_lower_toggle.disabled = !is_height raise_lower_toggle.disabled = !is_height
connected_toggle.disabled = is_paint
# Enable connected toggle only in 'HEIGHT' or 'FLATTEN' mode.
connected_toggle.disabled = !(is_height or is_flatten)
func tool_shape_changed() -> void: func tool_shape_changed() -> void:
var is_corner := (tool_shape == ToolShape.CORNER) var is_corner := (tool_shape == ToolShape.CORNER)

@ -1,10 +1,11 @@
[gd_scene load_steps=17 format=3 uid="uid://bp0wulaxcrutd"] [gd_scene load_steps=18 format=3 uid="uid://bp0wulaxcrutd"]
[ext_resource type="Script" path="res://addons/terrain-editing/terrain_editing_controls.gd" id="1_4e1sk"] [ext_resource type="Script" path="res://addons/terrain-editing/terrain_editing_controls.gd" id="1_4e1sk"]
[ext_resource type="Script" path="res://addons/terrain-editing/modifier_toggle_button.gd" id="2_61bkl"] [ext_resource type="Script" path="res://addons/terrain-editing/modifier_toggle_button.gd" id="2_61bkl"]
[ext_resource type="Texture2D" uid="uid://dqbtbf8pe05qv" path="res://addons/terrain-editing/icons/height.png" id="3_mn5pg"] [ext_resource type="Texture2D" uid="uid://dqbtbf8pe05qv" path="res://addons/terrain-editing/icons/height.png" id="3_mn5pg"]
[ext_resource type="Texture2D" uid="uid://bcb8w33ns56go" path="res://addons/terrain-editing/icons/flatten.png" id="4_c1bhf"] [ext_resource type="Texture2D" uid="uid://bcb8w33ns56go" path="res://addons/terrain-editing/icons/flatten.png" id="4_c1bhf"]
[ext_resource type="Texture2D" uid="uid://btdpyu4n3pgkx" path="res://addons/terrain-editing/icons/paint.png" id="5_547tx"] [ext_resource type="Texture2D" uid="uid://btdpyu4n3pgkx" path="res://addons/terrain-editing/icons/paint.png" id="5_547tx"]
[ext_resource type="Texture2D" uid="uid://7kstj7og5bsh" path="res://addons/terrain-editing/icons/erase.png" id="5_i5hs4"]
[ext_resource type="Texture2D" uid="uid://btl3jsqeldix2" path="res://addons/terrain-editing/icons/corner.png" id="6_fcc6v"] [ext_resource type="Texture2D" uid="uid://btl3jsqeldix2" path="res://addons/terrain-editing/icons/corner.png" id="6_fcc6v"]
[ext_resource type="Texture2D" uid="uid://2u1ldmh0osbx" path="res://addons/terrain-editing/icons/circle.png" id="7_b1ydi"] [ext_resource type="Texture2D" uid="uid://2u1ldmh0osbx" path="res://addons/terrain-editing/icons/circle.png" id="7_b1ydi"]
[ext_resource type="Texture2D" uid="uid://btjd1704xtdjv" path="res://addons/terrain-editing/icons/square.png" id="8_w3t42"] [ext_resource type="Texture2D" uid="uid://btjd1704xtdjv" path="res://addons/terrain-editing/icons/square.png" id="8_w3t42"]
@ -50,6 +51,12 @@ toggle_mode = true
icon = ExtResource("5_547tx") icon = ExtResource("5_547tx")
flat = true flat = true
[node name="Erase" type="Button" parent="."]
layout_mode = 2
toggle_mode = true
icon = ExtResource("5_i5hs4")
flat = true
[node name="HSeparator" type="HSeparator" parent="."] [node name="HSeparator" type="HSeparator" parent="."]
layout_mode = 2 layout_mode = 2

@ -4,16 +4,13 @@ using System.Runtime.InteropServices;
public partial class Terrain public partial class Terrain
{ {
// These mirror the modes / shapes in 'terrain_editing_controls.gd'. // These mirror the modes / shapes in 'terrain_editing_controls.gd'.
enum ToolMode { Height, Flatten, Paint } enum ToolMode { Height, Flatten, Paint, Erase }
enum ToolShape { Corner, Circle, Square } enum ToolShape { Corner, Circle, Square }
// Set by the terrain editing plugin. // Set by the terrain editing plugin.
// Enables access to the in-editor undo/redo system. // Enables access to the in-editor undo/redo system.
public EditorUndoRedoManager EditorUndoRedo { get; set; } public EditorUndoRedoManager EditorUndoRedo { get; set; }
// Dummy value to satisfy the overly careful compiler.
static bool _dummy = false;
Material _editToolMaterial; Material _editToolMaterial;
public override void _EnterTree() public override void _EnterTree()
{ {
@ -78,16 +75,22 @@ public partial class Terrain
// TODO: Allow click-dragging which doesn't affect already changed tiles / corners. // TODO: Allow click-dragging which doesn't affect already changed tiles / corners.
// TODO: Use ArrayMesh instead of ImmediateMesh. // TODO: Use ArrayMesh instead of ImmediateMesh.
// TODO: Support texture blending somehow.
// TODO: Clear empty chunks.
// Holds onto all the tiles and which of their corners corners will be affected by this edit operation. // Data structure for holding tiles to be modified.
var tilesToChange = new Dictionary<TilePos, Corners<bool>>(); var tilesToChange = new Dictionary<TilePos, TileModification>();
// Don't look at this black magic. The Dictionary type should have this by default I swear! // Utility function to set 'Affected' for the specified position and corner.
// Basically, this returns a reference to an entry in the dictionary that can be modified directly. void SetCornerAffected((TilePos Position, Corner Corner) pair)
ref Corners<bool> Tile(TilePos position) => ref CollectionsMarshal.GetValueRefOrAddDefault(tilesToChange, position, out _dummy); => tilesToChange.GetOrAddNew(pair.Position).Affected[pair.Corner] = true;
if (toolMode == ToolMode.Paint) { // Utility function to get the height for the specified position and corner.
// In PAINT mode, only full tiles are ever affected. short GetCornerHeight((TilePos Position, Corner Corner) pair)
=> Data.GetTileOrDefault(pair.Position).Height[pair.Corner];
if (toolMode is ToolMode.Paint or ToolMode.Erase) {
// In PAINT or ERASE mode, only full tiles are ever affected.
// So this makes populating 'tilesToChange' very straight-forward. // So this makes populating 'tilesToChange' very straight-forward.
var tiles = toolShape switch { var tiles = toolShape switch {
@ -100,23 +103,21 @@ public partial class Terrain
}; };
foreach (var pos in tiles) foreach (var pos in tiles)
tilesToChange.Add(pos, new(true)); tilesToChange.Add(pos, new(){ Affected = new(true) });
} else if (toolShape == ToolShape.Corner) { } else if (toolShape == ToolShape.Corner) {
// With the CORNER shape, only a single corner is affected. // With the CORNER shape, only a single corner is affected.
// Modify selected corner itself. // Modify selected corner itself.
Tile(hover.Position)[hover.Corner] = true; SetCornerAffected(hover);
// If the 'connected_toggle' button is active, move "connected" corners. // If the 'connected_toggle' button is active, move "connected" corners.
// This is a simplified version of the code below that only affects the 3 neighboring corners. // This is a simplified version of the code below that only affects the 3 neighboring corners.
if (isConnected) { if (isConnected) {
var height = Data.GetTileOrDefault(hover.Position).Height[hover.Corner]; var height = GetCornerHeight(hover);
foreach (var neighbor in GetNeighbors(hover.Position, hover.Corner)) { foreach (var neighbor in GetNeighbors(hover.Position, hover.Corner))
var neighborHeight = Data.GetTileOrDefault(neighbor.Position).Height[neighbor.Corner]; if (GetCornerHeight(neighbor) == height)
if (neighborHeight != height) continue; SetCornerAffected(neighbor);
Tile(neighbor.Position)[neighbor.Corner] = true;
}
} }
} else { } else {
@ -129,7 +130,7 @@ public partial class Terrain
// Modify selected tiles themselves. // Modify selected tiles themselves.
foreach (var pos in tiles) foreach (var pos in tiles)
tilesToChange.Add(pos, new(true)); tilesToChange.Add(pos, new(){ Affected = new(true) });
// If the 'connected_toggle' button is active, move "connected" corners. // If the 'connected_toggle' button is active, move "connected" corners.
// Connected corners are the ones that are at the same height as ones already being moved. // Connected corners are the ones that are at the same height as ones already being moved.
@ -137,12 +138,10 @@ public partial class Terrain
var tile = Data.GetTileOrDefault(pos); var tile = Data.GetTileOrDefault(pos);
foreach (var corner in Enum.GetValues<Corner>()) { foreach (var corner in Enum.GetValues<Corner>()) {
var height = tile.Height[corner]; var height = tile.Height[corner];
foreach (var neighbor in GetNeighbors(pos, corner)) { foreach (var neighbor in GetNeighbors(pos, corner))
if (tiles.Contains(neighbor.Position)) continue; if (!tiles.Contains(neighbor.Position))
var neighborHeight = Data.GetTileOrDefault(neighbor.Position).Height[neighbor.Corner]; if (GetCornerHeight(neighbor) == height)
if (neighborHeight != height) continue; SetCornerAffected(neighbor);
Tile(neighbor.Position)[neighbor.Corner] = true;
}
} }
} }
@ -152,68 +151,52 @@ public partial class Terrain
if (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) { if (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) {
prevent_default = true; prevent_default = true;
string action; // Fill with current tile data.
StringName method; foreach (var (pos, modified) in tilesToChange)
Variant[] doArgs; modified.Original = modified.New = Data.GetTileOrDefault(pos);
Variant[] undoArgs;
string action;
if (toolMode == ToolMode.Paint) { if (toolMode == ToolMode.Paint) {
// TODO: Support blending somehow.
var tilesPrevious = new List<(TilePos, byte)>();
var tilesChanged = new List<(TilePos, byte)>();
foreach (var (pos, corners) in tilesToChange) {
var tile = Data.GetTileOrDefault(pos);
tilesPrevious.Add((pos, tile.TexturePrimary));
tilesChanged.Add((pos, texture));
}
action = "Paint terrain"; action = "Paint terrain";
method = nameof(DoModifyTerrainTexture); foreach (var (_, modified) in tilesToChange)
doArgs = [ PackTextureData(tilesChanged) ]; modified.New.TexturePrimary = texture;
undoArgs = [ PackTextureData(tilesPrevious) ]; } else if (toolMode == ToolMode.Erase) {
} else { action = "Erase terrain";
var tilesPrevious = new List<(TilePos, Corners<short>)>(); foreach (var (_, modified) in tilesToChange)
var tilesChanged = new List<(TilePos, Corners<short>)>(); modified.New = default;
} else if (isFlatten) {
var amount = isFlatten ? Data.GetTileOrDefault(hover.Position).Height[hover.Corner] action = "Flatten terrain";
: isRaise ? (short)+1 : (short)-1; var amount = GetCornerHeight(hover);
foreach (var (_, modified) in tilesToChange) {
foreach (var (pos, corners) in tilesToChange) { ref var height = ref modified.New.Height;
var tile = Data.GetTileOrDefault(pos); if (modified.Affected.TopLeft ) height.TopLeft = amount;
tilesPrevious.Add((pos, tile.Height)); if (modified.Affected.TopRight ) height.TopRight = amount;
if (modified.Affected.BottomRight) height.BottomRight = amount;
var newHeight = tile.Height; if (modified.Affected.BottomLeft ) height.BottomLeft = amount;
if (isFlatten) {
if (corners.TopLeft ) newHeight.TopLeft = amount;
if (corners.TopRight ) newHeight.TopRight = amount;
if (corners.BottomRight) newHeight.BottomRight = amount;
if (corners.BottomLeft ) newHeight.BottomLeft = amount;
} else {
if (corners.TopLeft ) newHeight.TopLeft += amount;
if (corners.TopRight ) newHeight.TopRight += amount;
if (corners.BottomRight) newHeight.BottomRight += amount;
if (corners.BottomLeft ) newHeight.BottomLeft += amount;
} }
tilesChanged.Add((pos, newHeight)); } else {
action = isRaise ? "Raise terrain" : "Lower terrain";
var amount = isRaise ? (short)+1 : (short)-1;
foreach (var (_, modified) in tilesToChange) {
ref var height = ref modified.New.Height;
if (modified.Affected.TopLeft ) height.TopLeft += amount;
if (modified.Affected.TopRight ) height.TopRight += amount;
if (modified.Affected.BottomRight) height.BottomRight += amount;
if (modified.Affected.BottomLeft ) height.BottomLeft += amount;
} }
action = isFlatten ? "Flatten terrain"
: isRaise ? "Raise terrain"
: "Lower Terrain";
method = nameof(DoModifyTerrainHeight);
doArgs = [ PackHeightData(tilesChanged) ];
undoArgs = [ PackHeightData(tilesPrevious) ];
} }
if (EditorUndoRedo is EditorUndoRedoManager undo) { if (EditorUndoRedo is EditorUndoRedoManager undo) {
undo.CreateAction(action, backwardUndoOps: false); undo.CreateAction(action, backwardUndoOps: false);
undo.AddDoMethod(this, method, doArgs); var doData = Pack(tilesToChange.Select(e => (e.Key, e.Value.New)));
var undoData = Pack(tilesToChange.Select(e => (e.Key, e.Value.Original)));
undo.AddDoMethod(this, nameof(DoModifyTerrain), doData);
undo.AddDoMethod(this, nameof(UpdateMeshAndShape)); undo.AddDoMethod(this, nameof(UpdateMeshAndShape));
undo.AddDoMethod(this, GodotObject.MethodName.NotifyPropertyListChanged); undo.AddDoMethod(this, GodotObject.MethodName.NotifyPropertyListChanged);
undo.AddUndoMethod(this, method, undoArgs); undo.AddUndoMethod(this, nameof(DoModifyTerrain), undoData);
undo.AddUndoMethod(this, nameof(UpdateMeshAndShape)); undo.AddUndoMethod(this, nameof(UpdateMeshAndShape));
undo.AddUndoMethod(this, GodotObject.MethodName.NotifyPropertyListChanged); undo.AddUndoMethod(this, GodotObject.MethodName.NotifyPropertyListChanged);
@ -221,27 +204,28 @@ public partial class Terrain
} }
} }
UpdateEditToolMesh(tilesToChange); UpdateEditToolMesh(tilesToChange.Select(e => (e.Key, e.Value.Affected)));
return prevent_default; return prevent_default;
} }
public void EditorUnfocus() class TileModification
=> ClearEditToolMesh();
public void DoModifyTerrainHeight(byte[] data)
{ {
foreach (var (pos, corners) in UnpackHeightData(data)) public Corners<bool> Affected;
Data[pos].Height = corners; public Tile Original;
public Tile New;
} }
public void DoModifyTerrainTexture(byte[] data) public void EditorUnfocus()
=> ClearEditToolMesh();
public void DoModifyTerrain(byte[] data)
{ {
foreach (var (pos, texture) in UnpackTextureData(data)) foreach (var (pos, tile) in Unpack(data))
Data[pos].TexturePrimary = texture; Data[pos] = tile;
} }
void UpdateEditToolMesh(Dictionary<TilePos, Corners<bool>> tiles) void UpdateEditToolMesh(IEnumerable<(TilePos, Corners<bool>)> tiles)
{ {
var mesh = GetOrCreateMesh("EditToolMesh"); var mesh = GetOrCreateMesh("EditToolMesh");
mesh.ClearSurfaces(); mesh.ClearSurfaces();
@ -297,56 +281,46 @@ public partial class Terrain
=> _offsetLookup[corner].Select(e => (new TilePos(pos.X + e.X, pos.Z + e.Z), e.Opposite)); => _offsetLookup[corner].Select(e => (new TilePos(pos.X + e.X, pos.Z + e.Z), e.Opposite));
static byte[] PackHeightData(IEnumerable<(TilePos Position, Corners<short> Corners)> data) static byte[] Pack(IEnumerable<(TilePos Position, Tile tile)> data)
{ {
using var stream = new MemoryStream(); using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream); using var writer = new BinaryWriter(stream);
foreach (var (pos, corners) in data) { foreach (var (pos, tile) in data) {
writer.Write(pos.X); writer.Write(pos.X);
writer.Write(pos.Z); writer.Write(pos.Z);
writer.Write(corners.TopLeft); writer.Write(tile.Height.TopLeft);
writer.Write(corners.TopRight); writer.Write(tile.Height.TopRight);
writer.Write(corners.BottomRight); writer.Write(tile.Height.BottomRight);
writer.Write(corners.BottomLeft); writer.Write(tile.Height.BottomLeft);
writer.Write(tile.TexturePrimary);
writer.Write(tile.TextureSecondary);
writer.Write(tile.TextureBlend);
} }
return stream.ToArray(); return stream.ToArray();
} }
static IEnumerable<(TilePos Position, Corners<short> Corners)> UnpackHeightData(byte[] data) static IEnumerable<(TilePos Position, Tile tile)> Unpack(byte[] data)
{ {
using var stream = new MemoryStream(data); using var stream = new MemoryStream(data);
using var reader = new BinaryReader(stream); using var reader = new BinaryReader(stream);
while (stream.Position < stream.Length) { while (stream.Position < stream.Length) {
var x = reader.ReadInt32(); var x = reader.ReadInt32();
var y = reader.ReadInt32(); var y = reader.ReadInt32();
var corners = new Corners<short>(
var height = new Corners<short>(
reader.ReadInt16(), reader.ReadInt16(), reader.ReadInt16(), reader.ReadInt16(),
reader.ReadInt16(), reader.ReadInt16()); reader.ReadInt16(), reader.ReadInt16());
yield return (new(x, y), corners);
}
}
static byte[] PackTextureData(IEnumerable<(TilePos Position, byte Texture)> data) var texPrimary = reader.ReadByte();
{ var texSecondary = reader.ReadByte();
using var stream = new MemoryStream(); var texBlend = reader.ReadByte();
using var writer = new BinaryWriter(stream);
foreach (var (pos, texture) in data) {
writer.Write(pos.X);
writer.Write(pos.Z);
writer.Write(texture);
}
return stream.ToArray();
}
static IEnumerable<(TilePos Position, byte Texture)> UnpackTextureData(byte[] data) yield return (new(x, y), new(){
{ Height = height,
using var stream = new MemoryStream(data); TexturePrimary = texPrimary,
using var reader = new BinaryReader(stream); TextureSecondary = texSecondary,
while (stream.Position < stream.Length) { TextureBlend = texBlend,
var x = reader.ReadInt32(); });
var y = reader.ReadInt32();
var texture = reader.ReadByte();
yield return (new(x, y), texture);
} }
} }
} }

@ -0,0 +1,11 @@
public static class CollectionExtensions
{
public static TValue GetOrAddNew<TKey, TValue>(
this Dictionary<TKey, TValue> dict, TKey key)
where TValue : new()
{
if (!dict.TryGetValue(key, out var value))
dict.Add(key, value = new());
return value;
}
}
Loading…
Cancel
Save