Replace dasher-pack with unified animation-pack using original Blender bone names
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
|
||||
## Value coercion + gradient/curve builders for particle properties.
|
||||
|
||||
const MaterialValues := preload("res://addons/godot_ai/handlers/material_values.gd")
|
||||
|
||||
const _EMISSION_SHAPES := {
|
||||
"point": ParticleProcessMaterial.EMISSION_SHAPE_POINT,
|
||||
"sphere": ParticleProcessMaterial.EMISSION_SHAPE_SPHERE,
|
||||
"sphere_surface": ParticleProcessMaterial.EMISSION_SHAPE_SPHERE_SURFACE,
|
||||
"box": ParticleProcessMaterial.EMISSION_SHAPE_BOX,
|
||||
"points": ParticleProcessMaterial.EMISSION_SHAPE_POINTS,
|
||||
"directed_points": ParticleProcessMaterial.EMISSION_SHAPE_DIRECTED_POINTS,
|
||||
"ring": ParticleProcessMaterial.EMISSION_SHAPE_RING,
|
||||
}
|
||||
|
||||
|
||||
## Resolve a shape name to the int enum, or return null.
|
||||
static func resolve_emission_shape(value: Variant) -> Variant:
|
||||
if value is int:
|
||||
return value
|
||||
if value is float:
|
||||
return int(value)
|
||||
if value is String:
|
||||
var key := String(value).to_lower()
|
||||
if _EMISSION_SHAPES.has(key):
|
||||
return _EMISSION_SHAPES[key]
|
||||
return null
|
||||
|
||||
|
||||
static func emission_shape_names() -> Array:
|
||||
return _EMISSION_SHAPES.keys()
|
||||
|
||||
|
||||
## Build a Gradient from {stops: [{time, color}]} dict.
|
||||
static func build_gradient(value: Variant) -> Variant:
|
||||
if value is Gradient:
|
||||
return value
|
||||
if value is GradientTexture1D:
|
||||
return (value as GradientTexture1D).gradient
|
||||
if not (value is Dictionary):
|
||||
return null
|
||||
var d: Dictionary = value
|
||||
if not d.has("stops"):
|
||||
return null
|
||||
var stops_array = d.get("stops")
|
||||
if not (stops_array is Array):
|
||||
return null
|
||||
var offsets := PackedFloat32Array()
|
||||
var colors := PackedColorArray()
|
||||
for stop in stops_array:
|
||||
if not (stop is Dictionary):
|
||||
return null
|
||||
offsets.append(float(stop.get("time", 0.0)))
|
||||
var c = MaterialValues.parse_color(stop.get("color"))
|
||||
if c == null:
|
||||
return null
|
||||
colors.append(c)
|
||||
var grad := Gradient.new()
|
||||
grad.offsets = offsets
|
||||
grad.colors = colors
|
||||
return grad
|
||||
|
||||
|
||||
## Build a GradientTexture1D wrapping a Gradient (what ParticleProcessMaterial.color_ramp wants).
|
||||
static func build_gradient_texture(value: Variant) -> Variant:
|
||||
if value is GradientTexture1D:
|
||||
return value
|
||||
var grad = build_gradient(value)
|
||||
if grad == null:
|
||||
return null
|
||||
var tex := GradientTexture1D.new()
|
||||
tex.gradient = grad
|
||||
return tex
|
||||
|
||||
|
||||
## Build a Curve from [{time, value}] or {points: [...]} (float-over-time).
|
||||
static func build_curve(value: Variant) -> Variant:
|
||||
if value is Curve:
|
||||
return value
|
||||
if value is CurveTexture:
|
||||
return (value as CurveTexture).curve
|
||||
var points_array: Variant = null
|
||||
if value is Array:
|
||||
points_array = value
|
||||
elif value is Dictionary and value.has("points"):
|
||||
points_array = value["points"]
|
||||
if not (points_array is Array):
|
||||
return null
|
||||
var curve := Curve.new()
|
||||
for pt in points_array:
|
||||
if not (pt is Dictionary):
|
||||
return null
|
||||
var t := float(pt.get("time", 0.0))
|
||||
var v := float(pt.get("value", 0.0))
|
||||
curve.add_point(Vector2(t, v))
|
||||
return curve
|
||||
|
||||
|
||||
static func build_curve_texture(value: Variant) -> Variant:
|
||||
if value is CurveTexture:
|
||||
return value
|
||||
var curve = build_curve(value)
|
||||
if curve == null:
|
||||
return null
|
||||
var tex := CurveTexture.new()
|
||||
tex.curve = curve
|
||||
return tex
|
||||
|
||||
|
||||
## Coerce a particle property value to the appropriate type.
|
||||
## Handles: Vector3/gravity/direction, Color, float, int, bool, enum strings.
|
||||
## For color_ramp returns a GradientTexture1D; for *_curve returns CurveTexture.
|
||||
static func coerce(property: String, value: Variant, target_type: int) -> Dictionary:
|
||||
# Special-cased properties.
|
||||
if property == "emission_shape":
|
||||
var shape = resolve_emission_shape(value)
|
||||
if shape == null:
|
||||
return {
|
||||
"ok": false,
|
||||
"error": "Invalid emission_shape '%s'. Valid: %s" % [
|
||||
value, ", ".join(emission_shape_names())
|
||||
],
|
||||
}
|
||||
return {"ok": true, "value": int(shape)}
|
||||
|
||||
if property == "color_ramp" or property == "color_initial_ramp":
|
||||
var tex = build_gradient_texture(value)
|
||||
if tex == null:
|
||||
return {"ok": false, "error": "Invalid gradient for %s (expected {stops: [{time, color}]})" % property}
|
||||
return {"ok": true, "value": tex}
|
||||
|
||||
if property == "color" and value is Dictionary and not (value as Dictionary).has("stops"):
|
||||
# color is a single Color, not a ramp.
|
||||
var c = MaterialValues.parse_color(value)
|
||||
if c == null:
|
||||
return {"ok": false, "error": "Invalid color"}
|
||||
return {"ok": true, "value": c}
|
||||
|
||||
if property.ends_with("_curve"):
|
||||
var tex = build_curve_texture(value)
|
||||
if tex == null:
|
||||
return {"ok": false, "error": "Invalid curve for %s (expected [{time, value}])" % property}
|
||||
return {"ok": true, "value": tex}
|
||||
|
||||
# Fall through to the material coercer (handles Color/Vec3/Vec2/float/int/bool/enum).
|
||||
return MaterialValues.coerce_material_value(property, value, target_type)
|
||||
|
||||
|
||||
## Build a StandardMaterial3D suitable for GPUParticles3D draw-pass rendering.
|
||||
##
|
||||
## Godot's default Mesh has no material, which means ParticleProcessMaterial's
|
||||
## color_ramp (which drives the COLOR varying) gets ignored and particles
|
||||
## render as flat white squares that don't face the camera. A correct default
|
||||
## must have vertex_color_use_as_albedo=true, billboard=particles, unshaded,
|
||||
## and alpha transparency so the gradient actually modulates the pixels.
|
||||
##
|
||||
## Config is an optional dict that overrides individual properties. Supported
|
||||
## keys match BaseMaterial3D properties (plus enum-by-name via MaterialValues):
|
||||
## blend_mode: "mix" | "add" | "sub" | "mul"
|
||||
## transparency: "disabled" | "alpha" | "alpha_scissor" | "alpha_hash" | "alpha_depth_pre_pass"
|
||||
## shading_mode: "unshaded" | "per_pixel" | "per_vertex"
|
||||
## billboard_mode: "disabled" | "enabled" | "fixed_y" | "particles"
|
||||
## vertex_color_use_as_albedo: bool
|
||||
## emission_enabled: bool
|
||||
## emission: Color
|
||||
## emission_energy_multiplier: float
|
||||
## albedo_color: Color
|
||||
## albedo_texture: res:// path
|
||||
## (anything else accepted by BaseMaterial3D.set())
|
||||
static func build_draw_material(config: Dictionary) -> StandardMaterial3D:
|
||||
var mat := StandardMaterial3D.new()
|
||||
# Sensible defaults for particle draw-pass rendering.
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.vertex_color_use_as_albedo = true
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
||||
mat.billboard_keep_scale = true
|
||||
# Configure from dict overrides.
|
||||
for key in config:
|
||||
var prop_name := String(key)
|
||||
var prop_type := _object_property_type(mat, prop_name)
|
||||
if prop_type == TYPE_NIL:
|
||||
continue
|
||||
var coerce_result := MaterialValues.coerce_material_value(
|
||||
prop_name, config[prop_name], prop_type
|
||||
)
|
||||
if coerce_result.ok:
|
||||
mat.set(prop_name, coerce_result.value)
|
||||
return mat
|
||||
|
||||
|
||||
static func _object_property_type(obj: Object, name: String) -> int:
|
||||
if obj == null:
|
||||
return TYPE_NIL
|
||||
for prop in obj.get_property_list():
|
||||
if prop.name == name:
|
||||
return int(prop.get("type", TYPE_NIL))
|
||||
return TYPE_NIL
|
||||
|
||||
|
||||
## Serialize for response.
|
||||
static func serialize(value: Variant) -> Variant:
|
||||
if value == null:
|
||||
return null
|
||||
if value is GradientTexture1D:
|
||||
var grad := (value as GradientTexture1D).gradient
|
||||
if grad == null:
|
||||
return {"type": "GradientTexture1D", "stops": []}
|
||||
var stops: Array = []
|
||||
for i in grad.offsets.size():
|
||||
var c: Color = grad.colors[i]
|
||||
stops.append({
|
||||
"time": grad.offsets[i],
|
||||
"color": {"r": c.r, "g": c.g, "b": c.b, "a": c.a},
|
||||
})
|
||||
return {"type": "GradientTexture1D", "stops": stops}
|
||||
if value is CurveTexture:
|
||||
var curve := (value as CurveTexture).curve
|
||||
if curve == null:
|
||||
return {"type": "CurveTexture", "points": []}
|
||||
var points: Array = []
|
||||
for i in curve.get_point_count():
|
||||
var p := curve.get_point_position(i)
|
||||
points.append({"time": p.x, "value": p.y})
|
||||
return {"type": "CurveTexture", "points": points}
|
||||
return MaterialValues.serialize_value(value)
|
||||
Reference in New Issue
Block a user