khanat-client/addons/zylann.hterrain/tools/importer/importer_dialog.gd

267 lines
6.9 KiB
GDScript

tool
extends WindowDialog
const Util = preload("../../util/util.gd")
const HTerrainData = preload("../../hterrain_data.gd")
const Errors = preload("../../util/errors.gd")
const Logger = preload("../../util/logger.gd")
signal permanent_change_performed(message)
onready var _inspector = $VBoxContainer/Inspector
onready var _errors_label = $VBoxContainer/ColorRect/ScrollContainer/VBoxContainer/Errors
onready var _warnings_label = $VBoxContainer/ColorRect/ScrollContainer/VBoxContainer/Warnings
const RAW_LITTLE_ENDIAN = 0
const RAW_BIG_ENDIAN = 1
var _terrain = null
var _logger = Logger.get_for(self)
func _ready():
_inspector.set_prototype({
"heightmap": {
"type": TYPE_STRING,
"usage": "file",
"exts": ["raw", "png", "exr"]
},
"raw_endianess": {
"type": TYPE_INT,
"usage": "enum",
"enum_items": ["Little Endian", "Big Endian"],
"enabled": false
},
"min_height": {
"type": TYPE_REAL,
"range": {"min": -2000.0, "max": 2000.0, "step": 1.0},
"default_value": 0.0
},
"max_height": {
"type": TYPE_REAL,
"range": {"min": -2000.0, "max": 2000.0, "step": 1.0},
"default_value": 400.0
},
"splatmap": {
"type": TYPE_STRING,
"usage": "file",
"exts": ["png"]
},
"colormap": {
"type": TYPE_STRING,
"usage": "file",
"exts": ["png"]
}
})
# Testing
# _errors_label.text = "- Hello World!"
# _warnings_label.text = "- Yolo Jesus!"
func set_terrain(terrain):
_terrain = terrain
func _notification(what: int):
if what == NOTIFICATION_VISIBILITY_CHANGED:
if visible and is_inside_tree():
_clear_feedback()
static func _format_feedbacks(feed):
var a = []
for s in feed:
a.append("- " + s)
return PoolStringArray(a).join("\n")
func _clear_feedback():
_errors_label.text = ""
_warnings_label.text = ""
func _show_feedback(res):
for e in res.errors:
_logger.error(e)
for w in res.warnings:
_logger.warn(w)
_clear_feedback()
if len(res.errors) > 0:
_errors_label.text = _format_feedbacks(res.errors)
if len(res.warnings) > 0:
_warnings_label.text = _format_feedbacks(res.warnings)
func _on_CheckButton_pressed():
var res = _validate_form()
_show_feedback(res)
func _on_ImportButton_pressed():
assert(_terrain != null and _terrain.get_data() != null)
# Verify input to inform the user of potential issues
var res = _validate_form()
_show_feedback(res)
if len(res.errors) != 0:
_logger.debug("Cannot import due to errors, aborting")
return
var params = {}
var heightmap_path = _inspector.get_value("heightmap")
if heightmap_path != "":
var endianess = _inspector.get_value("raw_endianess")
params[HTerrainData.CHANNEL_HEIGHT] = {
"path": heightmap_path,
"min_height": _inspector.get_value("min_height"),
"max_height": _inspector.get_value("max_height"),
"big_endian": endianess == RAW_BIG_ENDIAN
}
var colormap_path = _inspector.get_value("colormap")
if colormap_path != "":
params[HTerrainData.CHANNEL_COLOR] = {
"path": colormap_path
}
var splatmap_path = _inspector.get_value("splatmap")
if splatmap_path != "":
params[HTerrainData.CHANNEL_SPLAT] = {
"path": splatmap_path
}
var data = _terrain.get_data()
data._edit_import_maps(params)
emit_signal("permanent_change_performed", "Import maps")
_logger.debug("Terrain import finished")
hide()
func _on_CancelButton_pressed():
hide()
func _on_Inspector_property_changed(key: String, value):
if key == "heightmap":
var is_raw = value.get_extension().to_lower() == "raw"
_inspector.set_property_enabled("raw_endianess", is_raw)
func _validate_form():
var res = {
"errors": [],
"warnings": []
}
var heightmap_path = _inspector.get_value("heightmap")
var splatmap_path = _inspector.get_value("splatmap")
var colormap_path = _inspector.get_value("colormap")
if colormap_path == "" and heightmap_path == "" and splatmap_path == "":
res.errors.append("No maps specified.")
return res
# If a heightmap is specified, it will override the size of the existing terrain.
# If not specified, maps will have to match the resolution of the existing terrain.
var heightmap_size = _terrain.get_data().get_resolution()
if heightmap_path != "":
var min_height = _inspector.get_value("min_height")
var max_height = _inspector.get_value("max_height")
if min_height >= max_height:
res.errors.append("Minimum height must be lower than maximum height")
# Returning early because min and max can be slided,
# so we avoid loading other maps everytime to do further checks
return res
var size = _load_image_size(heightmap_path, _logger)
if size.has("error"):
res.errors.append(str("Cannot open heightmap file: ", _error_to_string(size.error)))
return res
var adjusted_size = HTerrainData.get_adjusted_map_size(size.width, size.height)
if adjusted_size != size.width:
res.warnings.append(
"The square resolution deduced from heightmap file size is not power of two + 1.\n" + \
"The heightmap will be cropped.")
heightmap_size = adjusted_size
if splatmap_path != "":
_check_map_size(splatmap_path, "splatmap", heightmap_size, res, _logger)
if colormap_path != "":
_check_map_size(colormap_path, "colormap", heightmap_size, res, _logger)
return res
static func _check_map_size(path, map_name, heightmap_size, res, logger):
var size = _load_image_size(path, logger)
if size.has("error"):
res.errors.append("Cannot open splatmap file: ", _error_to_string(size.error))
return
var adjusted_size = HTerrainData.get_adjusted_map_size(size.width, size.height)
if adjusted_size != heightmap_size:
res.errors.append(str(
"The ", map_name,
" must have the same resolution as the heightmap (", heightmap_size, ")"))
else:
if adjusted_size != size.width:
res.warnings.append(
"The square resolution deduced from ", map_name,
" file size is not power of two + 1.\nThe ",
map_name, " will be cropped.")
static func _load_image_size(path, logger):
var ext = path.get_extension().to_lower()
if ext == "png" or ext == "exr":
var im = Image.new()
var err = im.load(path)
if err != OK:
logger.error("An error occurred loading image '{0}', code {1}" \
.format([path, err]))
return { "error": err }
return { "width": im.get_width(), "height": im.get_height() }
elif ext == "raw":
var f = File.new()
var err = f.open(path, File.READ)
if err != OK:
logger.error("Error opening file {0}".format([path]))
return { "error": err }
# Assume the raw data is square in 16-bit format,
# so its size is function of file length
var flen = f.get_len()
f.close()
var size = Util.integer_square_root(flen / 2)
if size == -1:
return { "error": "RAW image is not square" }
logger.debug("Deduced RAW heightmap resolution: {0}*{1}, for a length of {2}" \
.format([size, size, flen]))
return { "width": size, "height": size }
else:
return { "error": ERR_FILE_UNRECOGNIZED }
static func _error_to_string(err):
if typeof(err) == TYPE_STRING:
return err
return str("code ", err, ": ", Errors.get_message(err))