Files

144 lines
4.4 KiB
GDScript

@tool
extends RefCounted
## Value coercion helpers for camera authoring.
##
## Handles:
## - enum-by-name (keep_aspect="keep_height" -> Camera3D.KEEP_HEIGHT)
## - {x, y} dict -> Vector2 (zoom, offset, drag_*_offset)
## - serialization back to JSON-friendly shapes
const _ENUM_TABLES := {
"projection": {
"perspective": Camera3D.PROJECTION_PERSPECTIVE,
"orthogonal": Camera3D.PROJECTION_ORTHOGONAL,
"frustum": Camera3D.PROJECTION_FRUSTUM,
},
"keep_aspect": {
"keep_width": Camera3D.KEEP_WIDTH,
"keep_height": Camera3D.KEEP_HEIGHT,
},
"anchor_mode": {
"fixed_top_left": Camera2D.ANCHOR_MODE_FIXED_TOP_LEFT,
"drag_center": Camera2D.ANCHOR_MODE_DRAG_CENTER,
},
"doppler_tracking": {
"disabled": Camera3D.DOPPLER_TRACKING_DISABLED,
"idle_step": Camera3D.DOPPLER_TRACKING_IDLE_STEP,
"physics_step": Camera3D.DOPPLER_TRACKING_PHYSICS_STEP,
},
"process_callback": {
"physics": Camera2D.CAMERA2D_PROCESS_PHYSICS,
"idle": Camera2D.CAMERA2D_PROCESS_IDLE,
},
}
## Return the enum int for (property, string_name), or null if not a known enum string.
static func resolve_enum(property: String, value: Variant) -> Variant:
if not (value is String):
return null
if not _ENUM_TABLES.has(property):
return null
var table: Dictionary = _ENUM_TABLES[property]
var key: String = String(value).to_lower()
if table.has(key):
return table[key]
return null
## Valid enum names for a property, for error messages.
static func enum_keys(property: String) -> Array:
if not _ENUM_TABLES.has(property):
return []
return (_ENUM_TABLES[property] as Dictionary).keys()
static func parse_vector2(value: Variant) -> Variant:
if value is Vector2:
return value
if value is Dictionary:
var d: Dictionary = value
return Vector2(float(d.get("x", 0)), float(d.get("y", 0)))
if value is Array and value.size() >= 2:
return Vector2(float(value[0]), float(value[1]))
if value is int or value is float:
return Vector2(float(value), float(value))
return null
static func parse_vector3(value: Variant) -> Variant:
if value is Vector3:
return value
if value is Dictionary:
var d: Dictionary = value
return Vector3(float(d.get("x", 0)), float(d.get("y", 0)), float(d.get("z", 0)))
if value is Array and value.size() >= 3:
return Vector3(float(value[0]), float(value[1]), float(value[2]))
return null
## Coerce a JSON-shaped value for a camera property against the declared type.
## Returns {ok: true, value: ...} or {ok: false, error: "..."}.
static func coerce(property: String, value: Variant, target_type: int) -> Dictionary:
# Enum-by-name: must match before generic TYPE_INT coercion.
if _ENUM_TABLES.has(property):
if value is String:
var enum_val = resolve_enum(property, value)
if enum_val == null:
return {
"ok": false,
"error": "Invalid %s value: '%s'. Valid: %s" % [
property, value, ", ".join(enum_keys(property))
],
}
return {"ok": true, "value": int(enum_val)}
if value is int or value is float:
return {"ok": true, "value": int(value)}
match target_type:
TYPE_VECTOR2:
var v2 = parse_vector2(value)
if v2 == null:
return {"ok": false, "error": "Invalid vector2 for %s: %s" % [property, value]}
return {"ok": true, "value": v2}
TYPE_VECTOR3:
var v3 = parse_vector3(value)
if v3 == null:
return {"ok": false, "error": "Invalid vector3 for %s: %s" % [property, value]}
return {"ok": true, "value": v3}
TYPE_BOOL:
if value is bool:
return {"ok": true, "value": value}
if value is int or value is float:
return {"ok": true, "value": bool(value)}
return {"ok": false, "error": "Expected bool for %s" % property}
TYPE_INT:
if value is int:
return {"ok": true, "value": value}
if value is float:
return {"ok": true, "value": int(value)}
return {"ok": false, "error": "Expected int for %s" % property}
TYPE_FLOAT:
if value is float:
return {"ok": true, "value": value}
if value is int:
return {"ok": true, "value": float(value)}
return {"ok": false, "error": "Expected number for %s" % property}
TYPE_STRING:
return {"ok": true, "value": String(value)}
return {"ok": true, "value": value}
## Serialize a Variant into a JSON-friendly shape for responses.
static func serialize(value: Variant) -> Variant:
if value == null:
return null
if value is Vector2:
return {"x": value.x, "y": value.y}
if value is Vector3:
return {"x": value.x, "y": value.y, "z": value.z}
return value