Files
Brachistochrone/addons/Assets/enviroment assets/fleet_2/naf_fx.gd
T
scorpion21211-cmd 8efae1a0d0 changelog
arcology updated
ship models in
particle fx is broken on kestrel
2026-10-04 19:37:48 -04:00

320 lines
11 KiB
GDScript

extends RefCounted
## Hangar particle look for Resolute and Kestrel. See PARTICLE_FX.md.
## Drive, RCS, and abort flames are crossed additive ribbons. They are not GPUParticles.
const RIBBON := preload("res://ribbon.gdshader")
static func exhaust_local(socket: Node3D) -> Vector3:
var dir := socket.position
if dir.length() < 0.001:
dir = Vector3.UP
return (socket.quaternion.inverse() * dir).normalized()
static 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)
static func cone(dir: Vector3, deg: float) -> Vector3:
var axis := dir.normalized()
var up := Vector3.RIGHT if absf(axis.dot(Vector3.UP)) > 0.9 else Vector3.UP
var tangent := axis.cross(up).normalized()
var bitangent := axis.cross(tangent).normalized()
var a := randf() * TAU
var r := randf() * deg_to_rad(deg)
return (axis + tangent * (cos(a) * r) + bitangent * (sin(a) * r)).normalized()
static func _ribbon_mat(root: Color, mid: Color, tip: Color) -> ShaderMaterial:
var mat := ShaderMaterial.new()
mat.shader = RIBBON
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
static 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()
static 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
var v := t
st.set_uv(Vector2(0, v))
st.add_vertex(Vector3(-w, y, 0))
st.set_uv(Vector2(1, v))
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()
static func _crossed(socket: Node3D, mesh: ArrayMesh, mat: ShaderMaterial, planes: int, name: String, length: float, opacity: float, seed: float) -> Node3D:
var g := Node3D.new()
g.name = 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_local(socket))
g.visible = false
socket.add_child(g)
return g
static func spear(socket: Node3D, length: float, width: float, root: Color, mid: Color, tip: Color, opacity: float, planes: int, seed: float, node_name: String = "Spear") -> Node3D:
if socket.get_node_or_null(node_name):
return socket.get_node(node_name) as Node3D
return _crossed(socket, _sheet(length, width), _ribbon_mat(root, mid, tip), planes, node_name, length, opacity, seed)
static 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:
if socket.get_node_or_null(node_name):
return socket.get_node(node_name) as Node3D
return _crossed(socket, _taper(length, w0, w1), _ribbon_mat(root, mid, tip), planes, node_name, length, opacity, 1.0)
static func set_opacity(ribbon: Node3D, opacity: float) -> void:
if ribbon == null:
return
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)
static func set_spear(ribbon: Node3D, amount: float, length_scale: float, time: float) -> void:
if ribbon == null:
return
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)
var base := float(ribbon.get_meta("opacity"))
var s := maxf(length_scale, 0.001)
ribbon.scale = Vector3(1, s, 1)
set_opacity(ribbon, base * minf(a, 1.0) * flick)
static func spot(socket: Node3D, along: float, color: Color, angle_rad: float, range_m: float, decay: float) -> SpotLight3D:
var existing := socket.get_node_or_null("ExhaustLight") as SpotLight3D
if existing:
return existing
var aim := basis_y(exhaust_local(socket))
var light := SpotLight3D.new()
light.name = "ExhaustLight"
light.light_color = color
light.light_energy = 0.0
light.spot_range = range_m
light.spot_angle = rad_to_deg(angle_rad)
light.spot_attenuation = decay
light.shadow_enabled = false
light.position = aim.y * along
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
static 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)
static 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
static func _ramp(offsets: PackedFloat32Array, colors: PackedColorArray) -> GradientTexture1D:
var g := Gradient.new()
g.offsets = offsets
g.colors = colors
var tex := GradientTexture1D.new()
tex.gradient = g
return tex
static func sparks(socket: Node3D, node_name: String, amount: int, life: float, vmin: float, vmax: float, spread_deg: float, size: float, reach: float) -> GPUParticles3D:
var existing := socket.get_node_or_null(node_name) as GPUParticles3D
if existing:
return existing
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)
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_local(socket))
socket.add_child(p)
return p
static func abort_flash(socket: Node3D) -> MeshInstance3D:
var existing := socket.get_node_or_null("AbortFlash") as MeshInstance3D
if existing:
return existing
var aim := basis_y(exhaust_local(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
static func tracer_mesh() -> ArrayMesh:
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var sides := 5
var r0 := 0.016
var r1 := 0.008
for i in sides:
var a0 := TAU * float(i) / float(sides)
var a1 := TAU * float(i + 1) / float(sides)
var p0 := Vector3(cos(a0) * r0, 0.0, sin(a0) * r0)
var p1 := Vector3(cos(a1) * r0, 0.0, sin(a1) * r0)
var q0 := Vector3(cos(a0) * r1, 1.0, sin(a0) * r1)
var q1 := Vector3(cos(a1) * r1, 1.0, sin(a1) * r1)
st.add_vertex(p0)
st.add_vertex(q0)
st.add_vertex(p1)
st.add_vertex(p1)
st.add_vertex(q0)
st.add_vertex(q1)
return st.commit()
static func tracer_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.albedo_color = Color(1.0, 0.902, 0.690, 0.95)
draw.cull_mode = BaseMaterial3D.CULL_DISABLED
draw.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_DISABLED
return draw
static func chaff(socket: Node3D) -> GPUParticles3D:
var existing := socket.get_node_or_null("ChaffCloud") as GPUParticles3D
if existing:
return existing
var p := GPUParticles3D.new()
p.name = "ChaffCloud"
p.amount = 28
p.lifetime = 1.55
p.explosiveness = 0.0
p.local_coords = true
p.visibility_aabb = AABB(Vector3(-6, 0, -6), Vector3(12, 16, 12))
var proc := ParticleProcessMaterial.new()
proc.direction = Vector3(0, 1, 0)
proc.spread = 22.0
proc.initial_velocity_min = 3.0
proc.initial_velocity_max = 8.0
proc.gravity = Vector3.ZERO
proc.damping_min = 0.0
proc.damping_max = 0.0
proc.scale_min = 0.07
proc.scale_max = 0.16
proc.color = Color(0.90, 0.93, 0.97, 0.92)
proc.color_ramp = _ramp(
PackedFloat32Array([0.0, 0.55, 1.0]),
PackedColorArray([
Color(0.90, 0.93, 0.97, 0.92),
Color(0.90, 0.93, 0.97, 0.7),
Color(0.90, 0.93, 0.97, 0.0),
])
)
p.process_material = proc
p.material_override = _disc_mat()
var quad := QuadMesh.new()
quad.size = Vector2(1, 1)
p.draw_pass_1 = quad
p.emitting = false
p.basis = basis_y(exhaust_local(socket))
socket.add_child(p)
return p
static 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