267 lines
6.9 KiB
GDScript
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))
|