@tool extends Node const HT_Painter = preload("./painter.gd") const HTerrain = preload("../../hterrain.gd") const HTerrainData = preload("../../hterrain_data.gd") const HT_Logger = preload("../../util/logger.gd") const HT_Brush = preload("./brush.gd") const HT_RaiseShader = preload("./shaders/raise.gdshader") const HT_SmoothShader = preload("./shaders/smooth.gdshader") const HT_LevelShader = preload("./shaders/level.gdshader") const HT_FlattenShader = preload("./shaders/flatten.gdshader") const HT_ErodeShader = preload("./shaders/erode.gdshader") const HT_Splat4Shader = preload("./shaders/splat4.gdshader") const HT_Splat16Shader = preload("./shaders/splat16.gdshader") const HT_SplatIndexedShader = preload("./shaders/splat_indexed.gdshader") const HT_ColorShader = preload("./shaders/color.gdshader") const HT_AlphaShader = preload("./shaders/alpha.gdshader") const MODE_RAISE = 0 const MODE_LOWER = 1 const MODE_SMOOTH = 2 const MODE_FLATTEN = 3 const MODE_SPLAT = 4 const MODE_COLOR = 5 const MODE_MASK = 6 const MODE_DETAIL = 7 const MODE_LEVEL = 8 const MODE_ERODE = 9 const MODE_COUNT = 10 class HT_ModifiedMap: var map_type := 0 var map_index := 0 var painter_index := 0 signal flatten_height_changed var _painters : Array[HT_Painter] = [] var _brush := HT_Brush.new() var _color := Color(1, 0, 0, 1) var _mask_flag := false var _mode := MODE_RAISE var _flatten_height := 0.0 var _detail_index := 0 var _detail_density := 1.0 var _texture_index := 0 var _slope_limit_low_angle := 0.0 var _slope_limit_high_angle := PI / 2.0 var _modified_maps := [] var _terrain : HTerrain var _logger = HT_Logger.get_for(self) func _init(): for i in 4: var p := HT_Painter.new() # The name is just for debugging p.set_name(str("Painter", i)) #p.set_brush_size(_brush_size) p.texture_region_changed.connect(_on_painter_texture_region_changed.bind(i)) add_child(p) _painters.append(p) func get_brush() -> HT_Brush: return _brush func get_brush_size() -> int: return _brush.get_size() func set_brush_size(s: int): _brush.set_size(s) # for p in _painters: # p.set_brush_size(_brush_size) func set_brush_texture(texture: Texture2D): _brush.set_shapes([texture]) # for p in _painters: # p.set_brush_texture(texture) func get_opacity() -> float: return _brush.get_opacity() func set_opacity(opacity: float): _brush.set_opacity(opacity) func set_flatten_height(h: float): if h == _flatten_height: return _flatten_height = h flatten_height_changed.emit() func get_flatten_height() -> float: return _flatten_height func set_color(c: Color): _color = c func get_color() -> Color: return _color func set_mask_flag(m: bool): _mask_flag = m func get_mask_flag() -> bool: return _mask_flag func set_detail_density(d: float): _detail_density = clampf(d, 0.0, 1.0) func get_detail_density() -> float: return _detail_density func set_detail_index(di: int): _detail_index = di func set_texture_index(i: int): _texture_index = i func get_texture_index() -> int: return _texture_index func get_slope_limit_low_angle() -> float: return _slope_limit_low_angle func get_slope_limit_high_angle() -> float: return _slope_limit_high_angle func set_slope_limit_angles(low: float, high: float): _slope_limit_low_angle = low _slope_limit_high_angle = high func is_operation_pending() -> bool: for p in _painters: if p.is_operation_pending(): return true return false func has_modified_chunks() -> bool: for p in _painters: if p.has_modified_chunks(): return true return false func get_undo_chunk_size() -> int: return HT_Painter.UNDO_CHUNK_SIZE func commit() -> Dictionary: assert(_terrain.get_data() != null) var terrain_data = _terrain.get_data() assert(not terrain_data.is_locked()) var changes := [] var chunk_positions : Array assert(len(_modified_maps) > 0) for mm in _modified_maps: #print("Flushing painter ", mm.painter_index) var painter : HT_Painter = _painters[mm.painter_index] var info := painter.commit() # Note, positions are always the same for each map chunk_positions = info.chunk_positions changes.append({ "map_type": mm.map_type, "map_index": mm.map_index, "chunk_initial_datas": info.chunk_initial_datas, "chunk_final_datas": info.chunk_final_datas }) var cs := get_undo_chunk_size() for pos in info.chunk_positions: var rect = Rect2(pos * cs, Vector2(cs, cs)) # This will update vertical bounds and notify normal map baker, # since the latter updates out of order for preview terrain_data.notify_region_change(rect, mm.map_type, mm.map_index, false, true) # for i in len(_painters): # var p = _painters[i] # if p.has_modified_chunks(): # print("Painter ", i, " has modified chunks") # `commit()` is supposed to consume these chunks, there should be none left assert(not has_modified_chunks()) return { "chunk_positions": chunk_positions, "maps": changes } func set_mode(mode: int): assert(mode >= 0 and mode < MODE_COUNT) _mode = mode func get_mode() -> int: return _mode func set_terrain(terrain: HTerrain): if terrain == _terrain: return _terrain = terrain # It's important to release resources here, # otherwise Godot keeps modified terrain maps in memory and "reloads" them like that # next time we reopen the scene, even if we didn't save it for p in _painters: p.set_image(null, null) p.clear_brush_shader_params() # This may be called from an `_input` callback. # Returns `true` if any change was performed. func paint_input(position: Vector2, pressure: float) -> bool: assert(_terrain.get_data() != null) var data := _terrain.get_data() assert(not data.is_locked()) if not _brush.configure_paint_input(_painters, position, pressure): # Sometimes painting may not happen due to frequency options return false _modified_maps.clear() match _mode: MODE_RAISE: _paint_height(data, position, 1.0) MODE_LOWER: _paint_height(data, position, -1.0) MODE_SMOOTH: _paint_smooth(data, position) MODE_FLATTEN: _paint_flatten(data, position) MODE_LEVEL: _paint_level(data, position) MODE_ERODE: _paint_erode(data, position) MODE_SPLAT: # TODO Properly support what happens when painting outside of supported index # var supported_slots_count := terrain.get_cached_ground_texture_slot_count() # if _texture_index >= supported_slots_count: # _logger.debug("Painting out of range of supported texture slots: {0}/{1}" \ # .format([_texture_index, supported_slots_count])) # return if _terrain.is_using_indexed_splatmap(): _paint_splat_indexed(data, position) else: var splatmap_count := _terrain.get_used_splatmaps_count() match splatmap_count: 1: _paint_splat4(data, position) 4: _paint_splat16(data, position) MODE_COLOR: _paint_color(data, position) MODE_MASK: _paint_mask(data, position) MODE_DETAIL: _paint_detail(data, position) _: _logger.error("Unknown mode {0}".format([_mode])) assert(len(_modified_maps) > 0) return true func _on_painter_texture_region_changed(rect: Rect2, painter_index: int): var data := _terrain.get_data() if data == null: return for mm in _modified_maps: if mm.painter_index == painter_index: # This will tell auto-baked maps to update (like normals). data.notify_region_change(rect, mm.map_type, mm.map_index, false, false) break func _paint_height(data: HTerrainData, position: Vector2, factor: float): var image := data.get_image(HTerrainData.CHANNEL_HEIGHT) var texture := data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0, true) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_HEIGHT mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] # When using sculpting tools, make it dependent on brush size var raise_strength := 10.0 + float(_brush.get_size()) var delta := factor * (2.0 / 60.0) * raise_strength var p : HT_Painter = _painters[0] p.set_brush_shader(HT_RaiseShader) p.set_brush_shader_param("u_factor", delta) p.set_image(image, texture) p.paint_input(position) func _paint_smooth(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_HEIGHT) var texture := data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0, true) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_HEIGHT mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] p.set_brush_shader(HT_SmoothShader) p.set_brush_shader_param("u_factor", 1.0) p.set_image(image, texture) p.paint_input(position) func _paint_flatten(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_HEIGHT) var texture := data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0, true) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_HEIGHT mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] p.set_brush_shader(HT_FlattenShader) p.set_brush_shader_param("u_flatten_value", _flatten_height) p.set_image(image, texture) p.paint_input(position) func _paint_level(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_HEIGHT) var texture := data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0, true) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_HEIGHT mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] p.set_brush_shader(HT_LevelShader) p.set_brush_shader_param("u_factor", (10.0 / 60.0)) p.set_image(image, texture) p.paint_input(position) func _paint_erode(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_HEIGHT) var texture := data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0, true) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_HEIGHT mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] p.set_brush_shader(HT_ErodeShader) p.set_image(image, texture) p.paint_input(position) func _paint_splat4(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_SPLAT) var texture := data.get_texture(HTerrainData.CHANNEL_SPLAT, 0, true) var heightmap_texture := data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_SPLAT mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] var splat := Color(0.0, 0.0, 0.0, 0.0) splat[_texture_index] = 1.0; p.set_brush_shader(HT_Splat4Shader) p.set_brush_shader_param("u_splat", splat) _set_slope_limit_shader_params(p, heightmap_texture) p.set_image(image, texture) p.paint_input(position) func _paint_splat_indexed(data: HTerrainData, position: Vector2): var map_types := [ HTerrainData.CHANNEL_SPLAT_INDEX, HTerrainData.CHANNEL_SPLAT_WEIGHT ] _modified_maps = [] var textures := [] for mode in 2: textures.append(data.get_texture(map_types[mode], 0, true)) for mode in 2: var image := data.get_image(map_types[mode]) var mm := HT_ModifiedMap.new() mm.map_type = map_types[mode] mm.map_index = 0 mm.painter_index = mode _modified_maps.append(mm) var p : HT_Painter = _painters[mode] p.set_brush_shader(HT_SplatIndexedShader) p.set_brush_shader_param("u_mode", mode) p.set_brush_shader_param("u_index_map", textures[0]) p.set_brush_shader_param("u_weight_map", textures[1]) p.set_brush_shader_param("u_texture_index", _texture_index) p.set_image(image, textures[mode]) p.paint_input(position) func _paint_splat16(data: HTerrainData, position: Vector2): # Make sure required maps are present while data.get_map_count(HTerrainData.CHANNEL_SPLAT) < 4: data._edit_add_map(HTerrainData.CHANNEL_SPLAT) var splats := [] for i in 4: splats.append(Color(0.0, 0.0, 0.0, 0.0)) splats[_texture_index / 4][_texture_index % 4] = 1.0 var textures := [] for i in 4: textures.append(data.get_texture(HTerrainData.CHANNEL_SPLAT, i, true)) var heightmap_texture := data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0) for i in 4: var image : Image = data.get_image(HTerrainData.CHANNEL_SPLAT, i) var texture : Texture = textures[i] var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_SPLAT mm.map_index = i mm.painter_index = i _modified_maps.append(mm) var p : HT_Painter = _painters[i] var other_splatmaps := [] for tex in textures: if tex != texture: other_splatmaps.append(tex) p.set_brush_shader(HT_Splat16Shader) p.set_brush_shader_param("u_splat", splats[i]) p.set_brush_shader_param("u_other_splatmap_1", other_splatmaps[0]) p.set_brush_shader_param("u_other_splatmap_2", other_splatmaps[1]) p.set_brush_shader_param("u_other_splatmap_3", other_splatmaps[2]) _set_slope_limit_shader_params(p, heightmap_texture) p.set_image(image, texture) p.paint_input(position) func _paint_color(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_COLOR) var texture := data.get_texture(HTerrainData.CHANNEL_COLOR, 0, true) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_COLOR mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] # There was a problem with painting colors because of sRGB # https://github.com/Zylann/godot_heightmap_plugin/issues/17#issuecomment-734001879 p.set_brush_shader(HT_ColorShader) p.set_brush_shader_param("u_color", _color) p.set_brush_shader_param("u_normal_min_y", 0.0) p.set_brush_shader_param("u_normal_max_y", 1.0) p.set_image(image, texture) p.paint_input(position) func _paint_mask(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_COLOR) var texture := data.get_texture(HTerrainData.CHANNEL_COLOR, 0, true) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_COLOR mm.map_index = 0 mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] p.set_brush_shader(HT_AlphaShader) p.set_brush_shader_param("u_value", 1.0 if _mask_flag else 0.0) p.set_image(image, texture) p.paint_input(position) func _paint_detail(data: HTerrainData, position: Vector2): var image := data.get_image(HTerrainData.CHANNEL_DETAIL, _detail_index) var texture := data.get_texture(HTerrainData.CHANNEL_DETAIL, _detail_index, true) var heightmap_texture = data.get_texture(HTerrainData.CHANNEL_HEIGHT, 0) var mm := HT_ModifiedMap.new() mm.map_type = HTerrainData.CHANNEL_DETAIL mm.map_index = _detail_index mm.painter_index = 0 _modified_maps = [mm] var p : HT_Painter = _painters[0] var c := Color(_detail_density, _detail_density, _detail_density, 1.0) # TODO Don't use this shader (why?) p.set_brush_shader(HT_ColorShader) p.set_brush_shader_param("u_color", c) _set_slope_limit_shader_params(p, heightmap_texture) p.set_image(image, texture) p.paint_input(position) func _set_slope_limit_shader_params(p: HT_Painter, heightmap_texture: Texture): p.set_brush_shader_param("u_normal_min_y", cos(_slope_limit_high_angle)) p.set_brush_shader_param("u_normal_max_y", cos(_slope_limit_low_angle) + 0.001) p.set_brush_shader_param("u_heightmap", heightmap_texture)