Files
scorpion21211-cmd 13bbef255a Update kestrel_driver.gd
gear and FX fixed!!!!
2026-10-08 21:58:16 -04:00

666 lines
22 KiB
GDScript

@tool
extends Node3D
## Kestrel EC-01 plume driver. Godot 4.7.
## Attach to the Kestrel child. Leave escape_capsule.gd on EscapeCapsule.
## Nose +Y, starboard +X, dorsal +Z. The four abort motors are the drive plume.
##
## This script does not move the hull. escape_capsule.gd owns velocity.
## listen_keys is off so walking the player does not light jets or shove the craft.
## Capsule sets abort_burn, att_pitch/yaw/roll, rcs_direction, hatches, cabin_dim.
@export var listen_keys := false # leave off; capsule owns G H J L K Space
@export_range(0.0, 1.0) var gear_deploy := 1.0
@export_range(0.0, 1.0) var hatch_crew_open := 0.0
@export_range(0.0, 1.0) var hatch_ladder_deploy := 0.0
@export_range(0.0, 1.0) var hatch_dock_open := 0.0
@export_range(0.0, 1.0) var instruments_demo := 0.4
@export var cabin_lights := true:
set(value):
cabin_lights = value
if is_inside_tree():
_apply_cabin()
@export var cabin_dim := false:
set(value):
cabin_dim = value
if is_inside_tree():
_apply_cabin()
@export_enum("Off", "Nose", "Tail", "Starboard", "Port", "Dorsal", "Ventral") var rcs_direction := 0
@export var att_pitch := 0.0
@export var att_yaw := 0.0
@export var att_roll := 0.0
@export var abort_burn := false
const _RIBBON := "shader_type spatial;
render_mode blend_add, unshaded, cull_disabled, depth_draw_never, fog_disabled;
uniform vec4 root_color : source_color = vec4(1.0, 1.0, 1.0, 0.95);
uniform vec4 mid_color : source_color = vec4(0.47, 0.71, 1.0, 0.35);
uniform vec4 tip_color : source_color = vec4(0.16, 0.31, 1.0, 0.0);
uniform float opacity : hint_range(0.0, 2.0, 0.001) = 0.0;
void fragment() {
float v = UV.y;
float edge = abs(UV.x - 0.5) * 2.0;
float side = 1.0 - smoothstep(0.36, 1.0, edge);
vec3 col = mix(root_color.rgb, mid_color.rgb, smoothstep(0.0, 0.45, v));
col = mix(col, tip_color.rgb, smoothstep(0.35, 1.0, v));
float a = mix(root_color.a, mid_color.a, smoothstep(0.05, 0.45, v));
a = mix(a, tip_color.a, smoothstep(0.45, 1.0, v));
ALBEDO = col;
ALPHA = clamp(a * side * opacity, 0.0, 1.0);
}"
var _player: AnimationPlayer
var _clip_players: Dictionary = {}
var _shader: Shader
var _rcs: Array[Node3D] = []
var _aborts: Array[Dictionary] = []
var _abort_hold := 0.0
var _time := 0.0
var _prev := {}
var _cabin_ready := false
var _cabin_lights_n: Array[Light3D] = []
var _cabin_rest: Array[Dictionary] = []
var _cabin_mats: Array[Dictionary] = []
const _DIRS: Array[Vector3] = [
Vector3.ZERO, Vector3(0, 1, 0), Vector3(0, -1, 0),
Vector3(1, 0, 0), Vector3(-1, 0, 0), Vector3(0, 0, 1), Vector3(0, 0, -1),
]
func _ready() -> void:
_shader = Shader.new()
_shader.code = _RIBBON
_player = get_node_or_null("AnimationPlayer") as AnimationPlayer
if _player == null:
_player = find_child("AnimationPlayer", true, false) as AnimationPlayer
if _player == null and get_parent():
_player = get_parent().find_child("AnimationPlayer", true, false) as AnimationPlayer
_split_clips()
_arm()
if _aborts.is_empty():
_fallback_abort()
_collect_cabin()
_gear_shown = gear_deploy
_crew_shown = hatch_crew_open
_dock_shown = hatch_dock_open
_ladder_shown = hatch_ladder_deploy
_apply_clips()
var clips := PackedStringArray()
if _player:
clips = _player.get_animation_list()
print("Kestrel driver ready rcs=", _rcs.size(), " abort=", _aborts.size(), " clips=", clips, " lights=", _cabin_lights_n.size())
func drive(thrusting: bool, pitch: float, yaw: float, roll: float) -> void:
abort_burn = thrusting
att_pitch = pitch
att_yaw = yaw
att_roll = roll
func _process(delta: float) -> void:
_time += delta
if listen_keys and not Engine.is_editor_hint():
_read_keys()
_ease_poses(delta)
_apply_clips()
_apply_cabin()
_burn(_wish(), delta)
func _read_keys() -> void:
if _pressed(KEY_G):
gear_deploy = 0.0 if gear_deploy > 0.5 else 1.0
if _pressed(KEY_C):
hatch_crew_open = 0.0 if hatch_crew_open > 0.5 else 1.0
if _pressed(KEY_L):
hatch_ladder_deploy = 0.0 if hatch_ladder_deploy > 0.5 else 1.0
if _pressed(KEY_H):
hatch_dock_open = 0.0 if hatch_dock_open > 0.5 else 1.0
if _pressed(KEY_I):
instruments_demo = 0.15 if instruments_demo > 0.5 else 1.0
if _pressed(KEY_K):
cabin_lights = not cabin_lights
_apply_cabin()
abort_burn = Input.is_physical_key_pressed(KEY_SPACE)
func _pressed(key: Key) -> bool:
var down := Input.is_physical_key_pressed(key)
var was: bool = _prev.get(key, false)
_prev[key] = down
return down and not was
func _wish() -> Vector3:
var local := _DIRS[clampi(rcs_direction, 0, _DIRS.size() - 1)]
if listen_keys:
var pad := Vector3.ZERO
if Input.is_physical_key_pressed(KEY_W):
pad.y += 1.0
if Input.is_physical_key_pressed(KEY_S):
pad.y -= 1.0
if Input.is_physical_key_pressed(KEY_D):
pad.x += 1.0
if Input.is_physical_key_pressed(KEY_A):
pad.x -= 1.0
if Input.is_physical_key_pressed(KEY_E):
pad.z += 1.0
if Input.is_physical_key_pressed(KEY_Q):
pad.z -= 1.0
if pad.length_squared() > 0.0:
local = pad.normalized()
if local == Vector3.ZERO:
return Vector3.ZERO
return (global_transform.basis * local).normalized()
var _anim_root: Node = null
var _hinge_report: bool = false
var _gear_shown: float = 1.0
var _crew_shown: float = 0.0
var _dock_shown: float = 0.0
var _ladder_shown: float = 0.0
const GEAR_SECONDS: float = 4.0
const HATCH_SECONDS: float = 2.5
func _split_clips() -> void:
_anim_root = self
if _player == null:
print("Kestrel clips: no AnimationPlayer")
return
var root := _player.get_node_or_null(_player.root_node)
if root:
_anim_root = root
print("Kestrel clips root=", _anim_root.name, " list=", _player.get_animation_list())
func _apply_clips() -> void:
_hard_pose()
func _hinge(node_name: String) -> Node3D:
var n := find_child(node_name, true, false) as Node3D
if n == null and get_parent() != null:
n = get_parent().find_child(node_name, true, false) as Node3D
return n
func _q(x: float, y: float, z: float, w: float) -> Quaternion:
return Quaternion(x, y, z, w)
func _ease_poses(delta: float) -> void:
_gear_shown = move_toward(_gear_shown, gear_deploy, delta / GEAR_SECONDS)
_crew_shown = move_toward(_crew_shown, hatch_crew_open, delta / HATCH_SECONDS)
_dock_shown = move_toward(_dock_shown, hatch_dock_open, delta / HATCH_SECONDS)
_ladder_shown = move_toward(_ladder_shown, hatch_ladder_deploy, delta / HATCH_SECONDS)
func _hard_pose() -> void:
var g := clampf(_gear_shown, 0.0, 1.0)
var hip0 := _q(0.572867, 0.0, 0.0, 0.819648)
var hip1 := _q(0.470626, 0.0, 0.0, 0.882333)
var knee0 := _q(0.998152, 0.0, 0.0, 0.060759)
var knee1 := _q(0.208460, 0.0, 0.0, 0.978031)
var hit := 0
for side in ["FR", "FL", "RR", "RL"]:
hit += _swing("Gear_%s_Hip" % side, hip0, hip1, g)
hit += _slide_z("Gear_%s_UpperStrut" % side, 0.0138, 0.46, g)
hit += _stretch_y("Gear_%s_UpperStrut" % side, 0.16, 1.0, g)
hit += _slide_z("Gear_%s_Knee" % side, 0.0276, 0.92, g)
hit += _swing("Gear_%s_Knee" % side, knee0, knee1, g)
hit += _slide_z("Gear_%s_Shock" % side, 0.0147, 0.49, g)
hit += _stretch_y("Gear_%s_Shock" % side, 0.16, 1.0, g)
hit += _slide_z("Gear_%s_Foot" % side, 0.0294, 0.98, g)
hit += _swing("CrewHatch_Hinge", Quaternion.IDENTITY, _q(0.0, -0.854714, 0.0, 0.519099), _crew_shown)
hit += _swing("DockHatch_Hinge", Quaternion.IDENTITY, _q(0.0, 0.0, -0.854714, 0.519099), _dock_shown)
hit += _swing("CrewStep_Hinge", _q(0.0, 0.0, 0.02, 0.9998), _q(0.0, 0.0, 0.305059, 0.952334), _ladder_shown)
hit += _stretch_y("CrewStep_Ladder", 0.12, 1.0, _ladder_shown)
if not _hinge_report:
_hinge_report = true
print("Kestrel hinges hit=", hit, " crew=", _hinge("CrewHatch_Hinge"), " gear=", _hinge("Gear_FR_Hip"))
func _swing(node_name: String, a: Quaternion, b: Quaternion, t: float) -> int:
var n := _hinge(node_name)
if n == null:
return 0
n.quaternion = a.slerp(b, clampf(t, 0.0, 1.0))
return 1
func _slide_z(node_name: String, a: float, b: float, t: float) -> int:
var n := _hinge(node_name)
if n == null:
return 0
n.position.z = lerpf(a, b, clampf(t, 0.0, 1.0))
return 1
func _stretch_y(node_name: String, a: float, b: float, t: float) -> int:
var n := _hinge(node_name)
if n == null:
return 0
n.scale.y = lerpf(a, b, clampf(t, 0.0, 1.0))
return 1
func _pose(clip: StringName, t: float) -> void:
if _player == null or not _player.has_animation(clip):
return
var anim := _player.get_animation(clip)
var at := clampf(t, 0.0, 1.0) * anim.length
var root: Node = _anim_root if _anim_root else self
for i in anim.get_track_count():
var full := String(anim.track_get_path(i))
var node_path := full.get_slice(":", 0)
var prop := full.get_slice(":", 1)
var node := root.get_node_or_null(NodePath(node_path))
if node == null:
node = find_child(node_path.get_file(), true, false)
if node == null:
continue
var typ := anim.track_get_type(i)
if typ == Animation.TYPE_ROTATION_3D:
var q := anim.rotation_track_interpolate(i, at)
if prop == "" or prop == "quaternion" or prop == "rotation":
(node as Node3D).quaternion = q
else:
node.set(prop, q)
elif typ == Animation.TYPE_POSITION_3D:
var pos := anim.position_track_interpolate(i, at)
if prop == "" or prop == "position":
(node as Node3D).position = pos
else:
node.set(prop, pos)
elif typ == Animation.TYPE_SCALE_3D:
var sc := anim.scale_track_interpolate(i, at)
if prop == "" or prop == "scale":
(node as Node3D).scale = sc
else:
node.set(prop, sc)
elif typ == Animation.TYPE_VALUE and prop != "":
node.set(prop, anim.value_track_interpolate(i, at))
func _collect_cabin() -> void:
_cabin_lights_n.clear()
_cabin_rest.clear()
_cabin_mats.clear()
for n in find_children("*", "Light3D", true, false):
var nm := String(n.name)
if nm.begins_with("Nav"):
continue
var keep := nm.begins_with("PointLight_") or nm == "CabinFill" or nm == "PanelLight" or nm.begins_with("CabinLamp")
if not keep:
var p := n.get_parent()
while p:
if String(p.name) == "CabinLights":
keep = true
break
p = p.get_parent()
if not keep:
continue
_cabin_lights_n.append(n)
_cabin_rest.append({"energy": n.light_energy, "color": n.light_color})
for n in find_children("*", "MeshInstance3D", true, false):
if not String(n.name).begins_with("LightStrip"):
continue
var mesh := n as MeshInstance3D
var mat := mesh.get_active_material(0)
if mat == null or not (mat is StandardMaterial3D):
continue
var copy := (mat as StandardMaterial3D).duplicate() as StandardMaterial3D
mesh.set_surface_override_material(0, copy)
_cabin_mats.append({"mat": copy, "energy": copy.emission_energy_multiplier, "color": copy.emission, "on": copy.emission_enabled})
_cabin_ready = true
_apply_cabin()
func _apply_cabin() -> void:
if not _cabin_ready:
return
var dim := cabin_dim or not cabin_lights
for i in _cabin_lights_n.size():
var lamp := _cabin_lights_n[i]
var rest: Dictionary = _cabin_rest[i]
lamp.light_color = rest.color
lamp.light_energy = float(rest.energy) * (0.16 if dim else 1.0)
for row in _cabin_mats:
var mat := row.mat as StandardMaterial3D
mat.emission_enabled = true if dim else bool(row.on)
mat.emission = row.color
mat.emission_energy_multiplier = float(row.energy) * (0.18 if dim else 1.0)
func _scope() -> Array[Node]:
var found: Array[Node] = []
var roots: Array[Node] = [self]
if get_parent():
roots.append(get_parent())
for root in roots:
found.append(root)
for n in root.find_children("*", "Node", true, false):
found.append(n)
return found
func _arm() -> void:
var i := 0
for n in _scope():
if not (n is Node3D):
continue
var nm := String(n.name)
if nm.begins_with("Vfx_RCS_"):
_rcs.append(_spear(n, 1.2, 0.09, Color(0.863, 0.937, 1.0, 0.95), Color(0.41, 0.67, 1.0, 0.35), Color(0.25, 0.45, 0.8, 0.0), 0.82, 2, float(i)))
i += 1
elif nm.begins_with("Vfx_Abort_"):
_aborts.append(_make_abort(n))
func _fallback_abort() -> void:
var spots: Array[Vector3] = [Vector3(0.55, -0.35, 0.55), Vector3(-0.55, -0.35, 0.55), Vector3(0.55, -0.35, -0.55), Vector3(-0.55, -0.35, -0.55)]
for i in spots.size():
var s := Marker3D.new()
s.name = "Vfx_Abort_Fallback_%d" % i
s.position = spots[i]
add_child(s)
_aborts.append(_make_abort(s))
print("Kestrel driver built fallback abort plumes")
func _burn(wish: Vector3, delta: float) -> void:
var ship := global_transform.basis
var cmd := Vector3(att_pitch, att_roll, -att_yaw)
var turning := cmd.length() > 0.15
var origin := global_position
for ribbon in _rcs:
var want := 0.0
var exhaust: Vector3 = (ship.inverse() * ribbon.global_transform.basis.y).normalized()
if turning:
var local_pos: Vector3 = ship.inverse() * (ribbon.global_position - origin)
if local_pos.length() < 0.05:
local_pos = -exhaust
var torque: Vector3 = local_pos.cross(-exhaust)
if torque.dot(cmd) > 0.12:
want = 1.0
if want < 0.5 and wish != Vector3.ZERO:
if exhaust.dot(-(ship.inverse() * wish)) > 0.7:
want = 1.0
var burn := float(ribbon.get_meta("burn"))
burn = _smooth(burn, want, delta, 0.08 if want > burn else 0.14)
ribbon.set_meta("burn", burn)
_set_spear(ribbon, burn, _time)
if abort_burn:
_abort_hold = minf(1.0, _abort_hold + delta / 0.22)
else:
_abort_hold = maxf(0.0, _abort_hold - delta / 0.28)
var on := _abort_hold > 0.03
var flick := 0.86 + 0.14 * sin(_time * 41.0)
var sheath_scale := 0.9 + 0.1 * sin(_time * 19.0)
for a in _aborts:
var core := a.core as Node3D
var sheath := a.sheath as Node3D
_set_opacity(core, _abort_hold * 0.95 * flick if on else 0.0)
_set_opacity(sheath, _abort_hold * 0.82 * flick if on else 0.0)
sheath.scale = Vector3(1, sheath_scale, 1)
var sparks := a.sparks as GPUParticles3D
var sheath_sparks := a.sheath_sparks as GPUParticles3D
sparks.emitting = on
sheath_sparks.emitting = on
var proc := sparks.process_material as ParticleProcessMaterial
var proc2 := sheath_sparks.process_material as ParticleProcessMaterial
if proc:
proc.color = Color(1, 1, 1, _abort_hold * 0.55)
if proc2:
proc2.color = Color(1, 1, 1, _abort_hold * 0.55)
var flash := a.flash as MeshInstance3D
flash.visible = on
var fmat := flash.material_override as StandardMaterial3D
if fmat:
var tint := fmat.albedo_color
tint.a = _abort_hold * 0.45
fmat.albedo_color = tint
flash.scale = Vector3.ONE * (0.06 + _abort_hold * 0.04)
var light := a.light as SpotLight3D
light.light_energy = _abort_hold * 8.0 if on else 0.0
func _exhaust(socket: Node3D) -> Vector3:
var dir := socket.position
if dir.length() < 0.001:
dir = Vector3.UP
return (socket.quaternion.inverse() * dir).normalized()
func _basis_y(dir: Vector3) -> Basis:
var y := dir.normalized()
if y.length() < 0.001:
y = Vector3.UP
var up := Vector3.FORWARD if absf(y.dot(Vector3.UP)) > 0.9 else Vector3.UP
var x := up.cross(y).normalized()
var z := x.cross(y).normalized()
return Basis(x, y, z)
func _ribbon_mat(root: Color, mid: Color, tip: Color) -> ShaderMaterial:
var mat := ShaderMaterial.new()
mat.shader = _shader
mat.set_shader_parameter("root_color", root)
mat.set_shader_parameter("mid_color", mid)
mat.set_shader_parameter("tip_color", tip)
mat.set_shader_parameter("opacity", 0.0)
return mat
func _sheet(length: float, width: float) -> ArrayMesh:
var half := width * 0.5
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
st.set_uv(Vector2(0, 0)); st.add_vertex(Vector3(-half, 0, 0))
st.set_uv(Vector2(1, 0)); st.add_vertex(Vector3(half, 0, 0))
st.set_uv(Vector2(1, 1)); st.add_vertex(Vector3(half, length, 0))
st.set_uv(Vector2(0, 1)); st.add_vertex(Vector3(-half, length, 0))
st.add_index(0); st.add_index(1); st.add_index(2)
st.add_index(0); st.add_index(2); st.add_index(3)
return st.commit()
func _taper(length: float, w0: float, w1: float) -> ArrayMesh:
var rows := 11
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
for i in rows:
var t := float(i) / float(rows - 1)
var y := t * length
var w := (w0 + (w1 - w0) * pow(t, 0.7)) * 0.5
st.set_uv(Vector2(0, t)); st.add_vertex(Vector3(-w, y, 0))
st.set_uv(Vector2(1, t)); st.add_vertex(Vector3(w, y, 0))
for i in rows - 1:
var a := i * 2
st.add_index(a); st.add_index(a + 1); st.add_index(a + 2)
st.add_index(a + 1); st.add_index(a + 3); st.add_index(a + 2)
return st.commit()
func _crossed(socket: Node3D, mesh: ArrayMesh, mat: ShaderMaterial, planes: int, node_name: String, length: float, opacity: float, seed: float) -> Node3D:
var g := Node3D.new()
g.name = node_name
g.set_meta("mat", mat)
g.set_meta("len", length)
g.set_meta("opacity", opacity)
g.set_meta("seed", seed)
g.set_meta("burn", 0.0)
for i in planes:
var m := MeshInstance3D.new()
m.mesh = mesh
m.material_override = mat
m.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
m.extra_cull_margin = 8.0
m.rotation.y = (float(i) / float(planes)) * PI
g.add_child(m)
g.basis = _basis_y(_exhaust(socket))
g.visible = false
socket.add_child(g)
return g
func _spear(socket: Node3D, length: float, width: float, root: Color, mid: Color, tip: Color, opacity: float, planes: int, seed: float) -> Node3D:
return _crossed(socket, _sheet(length, width), _ribbon_mat(root, mid, tip), planes, "Spear", length, opacity, seed)
func _taper_ribbon(socket: Node3D, length: float, w0: float, w1: float, root: Color, mid: Color, tip: Color, opacity: float, planes: int, node_name: String) -> Node3D:
return _crossed(socket, _taper(length, w0, w1), _ribbon_mat(root, mid, tip), planes, node_name, length, opacity, 1.0)
func _set_opacity(ribbon: Node3D, opacity: float) -> void:
var mat := ribbon.get_meta("mat") as ShaderMaterial
var shown := opacity > 0.02
ribbon.visible = shown
mat.set_shader_parameter("opacity", opacity if shown else 0.0)
func _set_spear(ribbon: Node3D, amount: float, time: float) -> void:
var a := maxf(amount, 0.0)
if a < 0.02:
_set_opacity(ribbon, 0.0)
return
var seed := float(ribbon.get_meta("seed"))
var flick := 0.82 + 0.18 * sin(time * 37.0 + seed * 2.1)
ribbon.scale = Vector3.ONE
_set_opacity(ribbon, float(ribbon.get_meta("opacity")) * minf(a, 1.0) * flick)
func _smooth(current: float, target: float, delta: float, seconds: float) -> float:
var k := 1.0 - exp(-delta / maxf(0.05, seconds))
return current + (target - current) * k
func _soft_disc() -> Texture2D:
var img := Image.create(64, 64, false, Image.FORMAT_RGBA8)
for y in 64:
for x in 64:
var d := Vector2(x - 31.5, y - 31.5).length() / 32.0
var t := clampf(d, 0.0, 1.0)
var s := t * t * (3.0 - 2.0 * t)
var a := (1.0 - s) * (1.0 - s)
img.set_pixel(x, y, Color(1, 1, 1, a))
return ImageTexture.create_from_image(img)
func _disc_mat() -> StandardMaterial3D:
var draw := StandardMaterial3D.new()
draw.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
draw.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
draw.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
draw.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
draw.vertex_color_use_as_albedo = true
draw.albedo_texture = _soft_disc()
draw.albedo_color = Color.WHITE
draw.cull_mode = BaseMaterial3D.CULL_DISABLED
draw.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_DISABLED
return draw
func _abort_ramp() -> GradientTexture1D:
var g := Gradient.new()
g.offsets = PackedFloat32Array([0.0, 0.15, 0.45, 1.0])
g.colors = PackedColorArray([
Color(1.0, 0.97, 0.92, 1.0),
Color(1.0, 0.46, 0.12, 0.9),
Color(0.55, 0.16, 0.05, 0.45),
Color(0.25, 0.05, 0.01, 0.0),
])
var tex := GradientTexture1D.new()
tex.gradient = g
return tex
func _sparks(socket: Node3D, node_name: String, amount: int, life: float, vmin: float, vmax: float, spread_deg: float, size: float, reach: float) -> GPUParticles3D:
var p := GPUParticles3D.new()
p.name = node_name
p.amount = amount
p.lifetime = life
p.explosiveness = 0.0
p.local_coords = true
p.visibility_aabb = AABB(Vector3(-reach, 0, -reach), Vector3(reach * 2.0, reach * 3.0, reach * 2.0))
var proc := ParticleProcessMaterial.new()
proc.direction = Vector3(0, 1, 0)
proc.spread = spread_deg
proc.initial_velocity_min = vmin
proc.initial_velocity_max = vmax
proc.gravity = Vector3.ZERO
proc.scale_min = 0.85
proc.scale_max = 1.15
proc.color = Color(1, 1, 1, 0.55)
proc.color_ramp = _abort_ramp()
p.process_material = proc
p.material_override = _disc_mat()
var quad := QuadMesh.new()
quad.size = Vector2(size, size)
p.draw_pass_1 = quad
p.emitting = false
p.basis = _basis_y(_exhaust(socket))
socket.add_child(p)
return p
func _abort_flash(socket: Node3D) -> MeshInstance3D:
var aim := _basis_y(_exhaust(socket))
var mat := StandardMaterial3D.new()
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
mat.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.billboard_mode = BaseMaterial3D.BILLBOARD_ENABLED
mat.albedo_texture = _soft_disc()
mat.albedo_color = Color(1.0, 0.945, 0.867, 0.0)
mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_DISABLED
var m := MeshInstance3D.new()
m.name = "AbortFlash"
var quad := QuadMesh.new()
quad.size = Vector2(1, 1)
m.mesh = quad
m.material_override = mat
m.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
m.position = aim.y * 0.02
m.scale = Vector3.ONE * 0.08
m.visible = false
socket.add_child(m)
return m
func _spot(socket: Node3D) -> SpotLight3D:
var aim := _basis_y(_exhaust(socket))
var light := SpotLight3D.new()
light.name = "ExhaustLight"
light.light_color = Color(1.0, 0.416, 0.157)
light.light_energy = 0.0
light.spot_range = 6.5
light.spot_angle = rad_to_deg(0.5)
light.spot_attenuation = 1.8
light.shadow_enabled = false
light.position = aim.y * 0.08
var z := -(aim.y)
var up := aim.x
var x := up.cross(z).normalized()
var y := z.cross(x).normalized()
light.basis = Basis(x, y, z)
socket.add_child(light)
return light
func _make_abort(socket: Node3D) -> Dictionary:
var core := _taper_ribbon(socket, 2.1, 0.06, 0.20, Color(1.0, 0.97, 0.92, 1.0), Color(1.0, 0.45, 0.12, 0.72), Color(0.40, 0.08, 0.02, 0.0), 0.95, 3, "AbortCore")
var sheath := _taper_ribbon(socket, 5.2, 0.10, 0.78, Color(1.0, 0.55, 0.18, 1.0), Color(1.0, 0.28, 0.06, 0.55), Color(0.35, 0.06, 0.01, 0.0), 0.82, 3, "AbortSheath")
var row: Dictionary = {}
row["core"] = core
row["sheath"] = sheath
row["sparks"] = _sparks(socket, "AbortCoreSparks", 10, 0.15, 18.0, 28.0, 1.6, 0.05, 6.0)
row["sheath_sparks"] = _sparks(socket, "AbortSheathSparks", 18, 0.26, 14.0, 22.0, 4.5, 0.09, 8.0)
row["flash"] = _abort_flash(socket)
row["light"] = _spot(socket)
return row