Skip to content

Instantly share code, notes, and snippets.

@celsowm
Created October 8, 2025 00:01
Show Gist options
  • Select an option

  • Save celsowm/b62ce09d3c8ddcad1874b1dda304149e to your computer and use it in GitHub Desktop.

Select an option

Save celsowm/b62ce09d3c8ddcad1874b1dda304149e to your computer and use it in GitHub Desktop.
PlatformTriplanar.gd
extends Node3D
# Bloco flare + tampa com bordinha picotada (soldada ao topo)
# Com VERTEX COLORS: topo levemente mais claro; ponta do dente mais escura
# ----- Parâmetros do bloco -----
@export var height_y: float = 2.5
@export var base_size: Vector2 = Vector2(4.5, 4.5)
@export var top_size: Vector2 = Vector2(6.0, 6.0)
# ----- Cores -----
@export var dirt_color: Color = Color(0.45, 0.25, 0.12)
@export var grass_color: Color = Color(0.25, 0.70, 0.55, 1.0)
# ----- Tampa / serrilha -----
@export var cap_thickness: float = 0.14 # altura do tampo acima do topo
@export var skirt_height: float = 0.55 # quanto descem os “dentes”
@export var teeth_per_side: int = 10 # >= 2 (usa 2*teeth segmentos por lado)
@export var overhang_xy: float = 0.0 # 0 = alinhado; >0 avança um pouco
@export var debug_disable_cull := false # útil pra depurar faces
# ----- Câmera orbital -----
var _cam: Camera3D
var _target := Vector3.ZERO
var _pan_vector := Vector3.ZERO
var _orbit_distance := 10.0
var _orbit_angle_h := 0.0
var _orbit_angle_v := 0.0
var _dragging_orbit := false
var _dragging_pan := false
var _zoom_speed := 0.5
func _ready() -> void:
_add_environment()
_add_light()
_cam = _add_camera()
# ---- materiais ----
var mat_dirt := StandardMaterial3D.new()
mat_dirt.albedo_color = dirt_color
mat_dirt.roughness = 0.8
mat_dirt.metallic = 0.0
var mat_grass := StandardMaterial3D.new()
# vamos pintar via cor de vértice, então o albedo base é multiplicador neutro
mat_grass.albedo_color = Color(1, 1, 1)
mat_grass.roughness = 0.45
mat_grass.metallic = 0.0
mat_grass.vertex_color_use_as_albedo = true
mat_grass.vertex_color_is_srgb = true
mat_grass.cull_mode = BaseMaterial3D.CULL_DISABLED if debug_disable_cull else BaseMaterial3D.CULL_BACK
# ---- corpo (terra) ----
var dirt := MeshInstance3D.new()
dirt.mesh = _create_flared_box_mesh(height_y, base_size, top_size)
dirt.material_override = mat_dirt
add_child(dirt)
# ---- tampa + saia (grama em 2 superfícies) ----
var cap := MeshInstance3D.new()
cap.mesh = _create_cap_with_welded_skirt(height_y, top_size, cap_thickness, skirt_height, teeth_per_side, overhang_xy)
# surface 0 = topo / surface 1 = saia
cap.set_surface_override_material(0, mat_grass)
cap.set_surface_override_material(1, mat_grass)
add_child(cap)
func _process(_dt: float) -> void:
_update_camera()
# ========================= MALHA DO BLOCO =========================
func _create_flared_box_mesh(h: float, base_xy: Vector2, top_xy: Vector2) -> ArrayMesh:
var bx := base_xy.x * 0.5
var bz := base_xy.y * 0.5
var tx := top_xy.x * 0.5
var tz := top_xy.y * 0.5
var hy := h * 0.5
var b0 := Vector3(-bx, -hy, -bz)
var b1 := Vector3( bx, -hy, -bz)
var b2 := Vector3( bx, -hy, bz)
var b3 := Vector3(-bx, -hy, bz)
var t0 := Vector3(-tx, hy, -tz)
var t1 := Vector3( tx, hy, -tz)
var t2 := Vector3( tx, hy, tz)
var t3 := Vector3(-tx, hy, tz)
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
# base
_add_tri(st, b0, b2, b1)
_add_tri(st, b0, b3, b2)
# topo do corpo (coberto pela tampa)
_add_tri(st, t0, t1, t2)
_add_tri(st, t0, t2, t3)
# lados
_add_tri(st, b0, b1, t1); _add_tri(st, b0, t1, t0) # frente
_add_tri(st, b1, b2, t2); _add_tri(st, b1, t2, t1) # direita
_add_tri(st, b2, b3, t3); _add_tri(st, b2, t3, t2) # fundo
_add_tri(st, b3, b0, t0); _add_tri(st, b3, t0, t3) # esquerda
st.generate_normals()
st.index()
return st.commit()
# ============== TAMPA SOLDADA (topo + saia com anel compartilhado) ==============
# 2 superfícies: 0 = topo (leque), 1 = saia (quads)
# Com vertex colors: topo mais claro; ponta do dente mais escura
func _create_cap_with_welded_skirt(h: float, top_xy: Vector2, t: float, skirt: float, teeth: int, over: float) -> ArrayMesh:
teeth = max(2, teeth)
var tx := (top_xy.x * 0.5) + over
var tz := (top_xy.y * 0.5) + over
var y_top := h * 0.5 + t
var y_edge := h * 0.5
var segs_side := teeth * 2
var dx := (tx * 2.0) / float(segs_side)
var dz := (tz * 2.0) / float(segs_side)
# ---------- anel CCW (visto de cima) ----------
var ring: Array[Vector3] = []
# esquerda: x=-tx, z:-tz..+tz
for i in range(segs_side):
ring.append(Vector3(-tx, y_top, -tz + dz * float(i)))
ring.append(Vector3(-tx, y_top, +tz))
# fundo: z=+tz, x:-tx..+tx
for i in range(1, segs_side + 1):
ring.append(Vector3(-tx + dx * float(i), y_top, +tz))
# direita: x=+tx, z:+tz..-tz
for i in range(1, segs_side + 1):
ring.append(Vector3(+tx, y_top, +tz - dz * float(i)))
# frente: z=-tz, x:+tx..-tx
for i in range(1, segs_side + 1):
ring.append(Vector3(+tx - dx * float(i), y_top, -tz))
# o último coincide com o primeiro (-tx,-tz)
var mesh := ArrayMesh.new()
# ---------- paleta da grama ----------
var grass_base := grass_color
var grass_top := grass_base.lightened(0.08) # topo levemente mais claro
var grass_mid := grass_base # borda
var grass_tip := grass_base.darkened(0.20) # ponta do dente mais escura
# ---------- Superfície 0: TOPO (leque) ----------
var st_top := SurfaceTool.new()
st_top.begin(Mesh.PRIMITIVE_TRIANGLES)
var center := Vector3(0, y_top, 0)
for i in range(ring.size()):
var a := ring[i]
var b := ring[(i + 1) % ring.size()]
_add_tri_colored(st_top, center, b, a, grass_top, grass_mid, grass_mid)
st_top.generate_normals()
st_top.index()
st_top.commit(mesh) # surface 0
# ---------- Superfície 1: SAIA (quads CCW) ----------
var st_skirt := SurfaceTool.new()
st_skirt.begin(Mesh.PRIMITIVE_TRIANGLES)
for i in range(ring.size()):
var top0 := ring[i]
var top1 := ring[(i + 1) % ring.size()]
var yb0 := (y_edge - skirt) if ((i % 2) == 0) else y_edge
var yb1 := (y_edge - skirt) if (((i + 1) % 2) == 0) else y_edge
var bot0 := Vector3(top0.x, yb0, top0.z)
var bot1 := Vector3(top1.x, yb1, top1.z)
# cores por vértice: clareia na borda, escurece se for ponta (quando desce)
var c_top0 := grass_mid
var c_top1 := grass_mid
var c_bot0 := (grass_tip if (yb0 < y_edge) else grass_mid)
var c_bot1 := (grass_tip if (yb1 < y_edge) else grass_mid)
_add_rect_ccw_colored(st_skirt, top0, top1, bot1, bot0, c_top0, c_top1, c_bot1, c_bot0)
st_skirt.generate_normals()
st_skirt.index()
st_skirt.commit(mesh) # surface 1
return mesh
# ========================= HELPERS (geom) =========================
func _yb_alternate(idx: int, y_edge: float, skirt: float) -> float:
return y_edge - (skirt if (idx % 2) == 0 else 0.0)
func _add_tri(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3) -> void:
st.add_vertex(a); st.add_vertex(b); st.add_vertex(c)
func _add_rect_ccw(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, d: Vector3) -> void:
# a,b,c,d em sentido anti-horário visto de fora
st.add_vertex(a); st.add_vertex(b); st.add_vertex(c)
st.add_vertex(a); st.add_vertex(c); st.add_vertex(d)
# ===== HELPERS com COLOR (usar set_color antes de add_vertex) =====
func _add_tri_colored(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, ca: Color, cb: Color, cc: Color) -> void:
st.set_color(ca); st.add_vertex(a)
st.set_color(cb); st.add_vertex(b)
st.set_color(cc); st.add_vertex(c)
func _add_rect_ccw_colored(st: SurfaceTool, a: Vector3, b: Vector3, c: Vector3, d: Vector3,
ca: Color, cb: Color, cc: Color, cd: Color) -> void:
# a,b,c,d em CCW
st.set_color(ca); st.add_vertex(a)
st.set_color(cb); st.add_vertex(b)
st.set_color(cc); st.add_vertex(c)
st.set_color(ca); st.add_vertex(a)
st.set_color(cc); st.add_vertex(c)
st.set_color(cd); st.add_vertex(d)
# ==================== AMBIENTE / LUZ / CÂMERA ====================
func _add_environment() -> void:
var we := WorldEnvironment.new()
var env := Environment.new()
var psky := ProceduralSkyMaterial.new()
psky.sky_top_color = Color(0.62, 0.78, 1.0)
psky.sky_horizon_color = Color(0.9, 0.95, 1.0)
psky.ground_bottom_color = Color(0.95, 0.88, 0.78)
var sky := Sky.new()
sky.sky_material = psky
env.background_mode = Environment.BG_SKY
env.sky = sky
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
env.ssao_enabled = true
env.ssao_intensity = 0.9
we.environment = env
add_child(we)
func _add_light() -> void:
var sun := DirectionalLight3D.new()
sun.light_energy = 2.0
sun.light_indirect_energy = 0.4
sun.shadow_enabled = true
sun.directional_shadow_max_distance = 100.0
sun.rotation_degrees = Vector3(45, -30, 0)
add_child(sun)
func _add_camera() -> Camera3D:
var cam := Camera3D.new()
var initial_pos := Vector3(8, 6, 8)
_orbit_distance = initial_pos.length()
var horiz := Vector2(initial_pos.x, initial_pos.z)
_orbit_angle_h = atan2(horiz.y, horiz.x)
_orbit_angle_v = asin(clampf(initial_pos.y / _orbit_distance, -1.0, 1.0))
add_child(cam)
return cam
func _update_camera() -> void:
if _cam == null:
return
var look_center := _target + _pan_vector
var dir := Vector3(
cos(_orbit_angle_h) * cos(_orbit_angle_v),
sin(_orbit_angle_v),
sin(_orbit_angle_h) * cos(_orbit_angle_v)
).normalized()
_cam.position = look_center + dir * _orbit_distance
_cam.look_at(look_center, Vector3.UP)
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
var delta := (event as InputEventMouseMotion).relative
if _dragging_orbit:
_orbit_angle_h -= delta.x * 0.005
_orbit_angle_v -= delta.y * 0.005
_orbit_angle_v = clampf(_orbit_angle_v, -PI/2 + 0.1, PI/2 - 0.1)
elif _dragging_pan:
var right := Vector3(cos(_orbit_angle_h), 0, sin(_orbit_angle_h))
_pan_vector -= right * delta.x * 0.01
_pan_vector -= Vector3.UP * delta.y * 0.01 * (_orbit_distance / 10.0)
elif event is InputEventMouseButton:
var mb := event as InputEventMouseButton
match mb.button_index:
MOUSE_BUTTON_MIDDLE:
if mb.pressed:
_dragging_orbit = not Input.is_key_pressed(KEY_SHIFT)
_dragging_pan = Input.is_key_pressed(KEY_SHIFT)
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
else:
_dragging_orbit = false
_dragging_pan = false
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
MOUSE_BUTTON_WHEEL_UP:
_orbit_distance = maxf(_orbit_distance * (1.0 - _zoom_speed * 0.1), 1.0)
MOUSE_BUTTON_WHEEL_DOWN:
_orbit_distance *= (1.0 + _zoom_speed * 0.1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment