soul-symphony-ling
↓ 0 downloadsSoul Symphony — Ling Edition. A spiritual 3D adventure in the soul realm: collect the letters of the world-song, clear the monster lattice, and duel in the arcade arena. Wind Waker-style toon renderer with volumetric light pools, SSAO, bloom and oil-slick tilt-shift — all ling-lang vectors.
Keywords game, 3d, toon, music, soul-symphony
lingfu install soul-symphony-lingCode / Files (1947)
game/lattice.ling
# ════════════════════════════════════════════════════════════════════════════
# lattice.ling — โครงเลเวลแบบ "ห้อง + ทางเดิน" (room/hallway lattice)
# ห้องเรียบที่ระดับ L ต่อกันด้วยทางเดิน; ทางเดินต่างระดับ = ทางลาด (ขึ้น 1F→2F)
# ดีเทอร์มินิสติกต่อ seed (ไม่เก็บสถานะ) · สตรีมเฉพาะ 5 ห้อง (กลาง + 4 ข้าง)
# โลกนี้ +y ลง → ระดับสูง = y น้อย. ฐาน LAT_BASE (= LVL_F ในสตอรี่)
# ════════════════════════════════════════════════════════════════════════════
bind LAT_BASE = 3.0 # y ของระดับ 0 (= LVL_F ของวิหารทิวทอเรียล)
bind ROOM_S = 256.0 # ขนาดห้อง — ใหญ่เท่าทิวทอเรียล (±256)
bind HALL_W = 64.0 # กว้างทางเดิน (ลูกผ่านสบาย)
bind LAT_H = 56.0 # สูงต่อชั้น (ชั้น 2/3 — สูงพอบล็อกลูก ต้องใช้ทางลาด)
bind LAT_MAXL = 2.0 # ชั้นสูงสุด → 3 ชั้น: พื้น(0)/ชั้นสอง(1)/ชั้นสาม(2)
bind RAMP_REACH = 96.0 # ระยะทางลาด (< ROOM_S/2=128 → กลางห้องราบ, ลาด ~30°)
# y ของพื้นระดับ L
ฟังก์ชัน latY(L) { คืน LAT_BASE - L * LAT_H }
# แฮชสุ่ม 0..1 (GLSL-style fract) — ต่อขอบ/มุม
ฟังก์ชัน latHash(a, b, seed) {
bind h = ไซน์(a*12.9898 + b*78.233 + seed*37.719) * 43758.5453
คืน h - ปัดลง(h)
}
# ระดับพื้นของห้อง (rc,rr) — แฮชต่อห้อง (ความถี่สูง → เพื่อนบ้านต่างระดับ → มีทางลาดให้เห็น)
# พื้นเป็นหลัก (~45%) + ชั้นสอง (~33%) + ชั้นสาม (~22%) → "ใหญ่เหมือนทิวทอเรียล + ชั้น 2/3"
ฟังก์ชัน roomLevel(rc, rr, seed) {
bind h = latHash(rc, rr, seed)
bind L = 0.0
ถ้า h > 0.45 { bind L = 1.0 }
ถ้า h > 0.78 { bind L = 2.0 }
ถ้า L > LAT_MAXL { bind L = LAT_MAXL }
คืน L
}
# ขอบเปิด (ทางเดิน) หรือไม่ — สมมาตร (สองห้องอ้างขอบเดียวกัน) · nExits คุมความหนาแน่น
# horiz=1 ขอบนอน (ระหว่าง (ec,er) กับ (ec+1,er)) · horiz=0 ขอบตั้ง (กับ (ec,er+1))
ฟังก์ชัน edgeOpen(ec, er, horiz, seed, nExits) {
bind h = latHash(ec*1.7 + horiz*131.0, er*1.3 + 9.0, seed)
bind thr = nExits/4.0 * 0.9 + 0.06
ถ้า h < thr { คืน 1.0 }
คืน 0.0
}
# ── ความสูงพื้นเดินได้ที่ (x,z): ห้องเรียบ + ทางลาดในแถบทางเดิน (ต่อเนื่อง) ──
ฟังก์ชัน latticeHeight(x, z, seed, nExits) {
bind rc = ปัดลง(x / ROOM_S); bind rr = ปัดลง(z / ROOM_S)
bind L = roomLevel(rc, rr, seed)
bind base = latY(L)
bind lx = x - rc*ROOM_S; bind lz = z - rr*ROOM_S
bind hc = ROOM_S * 0.5
bind hw = HALL_W * 0.5
# E (lx ใหญ่) — แถบรอบ lz=กลาง
bind dzc = lz - hc; ถ้า dzc < 0.0 { bind dzc = 0.0 - dzc }
bind dxc = lx - hc; ถ้า dxc < 0.0 { bind dxc = 0.0 - dxc }
ถ้า lx > ROOM_S - RAMP_REACH { ถ้า dzc < hw { ถ้า edgeOpen(rc, rr, 1.0, seed, nExits) > 0.5 {
bind mid = (base + latY(roomLevel(rc+1, rr, seed))) * 0.5
คืน base + (mid - base) * ((lx - (ROOM_S - RAMP_REACH)) / RAMP_REACH)
} } }
ถ้า lx < RAMP_REACH { ถ้า dzc < hw { ถ้า edgeOpen(rc-1, rr, 1.0, seed, nExits) > 0.5 {
bind mid = (base + latY(roomLevel(rc-1, rr, seed))) * 0.5
คืน base + (mid - base) * ((RAMP_REACH - lx) / RAMP_REACH)
} } }
ถ้า lz > ROOM_S - RAMP_REACH { ถ้า dxc < hw { ถ้า edgeOpen(rc, rr, 0.0, seed, nExits) > 0.5 {
bind mid = (base + latY(roomLevel(rc, rr+1, seed))) * 0.5
คืน base + (mid - base) * ((lz - (ROOM_S - RAMP_REACH)) / RAMP_REACH)
} } }
ถ้า lz < RAMP_REACH { ถ้า dxc < hw { ถ้า edgeOpen(rc, rr-1, 0.0, seed, nExits) > 0.5 {
bind mid = (base + latY(roomLevel(rc, rr-1, seed))) * 0.5
คืน base + (mid - base) * ((RAMP_REACH - lz) / RAMP_REACH)
} } }
คืน base
}
ฟังก์ชัน สีห้อง(L, hueR, hueG, hueB, k) {
bind m = 1.0 + k # k<0 → เข้มลง (ผนัง/ขั้น) · k=0 → พื้น
bind dim = 1.05
คืน [floor(((hueR*0.5 + 0.5)*70 + L*22 + 30) * m * dim), floor(((hueG*0.5 + 0.5)*70 + L*20 + 32) * m * dim), floor(((hueB*0.5 + 0.5)*90 + L*26 + 46) * m * dim)]
}
# ผนังข้างทางลาด (ปิดร่อง/สันข้าง → ไม่มีรูข้างลาดตอนใกล้ถึงยอด) — สามเหลี่ยมจากฐาน→ขอบ
ฟังก์ชัน ข้างลาดX(xTop, xEdge, za, zb, yBase, yMid) {
วาดสามเหลี่ยม3มิติ(xTop, yBase, za, xEdge, yBase, za, xEdge, yMid, za)
วาดสามเหลี่ยม3มิติ(xTop, yBase, zb, xEdge, yBase, zb, xEdge, yMid, zb)
}
ฟังก์ชัน ข้างลาดZ(zTop, zEdge, xa, xb, yBase, yMid) {
วาดสามเหลี่ยม3มิติ(xa, yBase, zTop, xa, yBase, zEdge, xa, yMid, zEdge)
วาดสามเหลี่ยม3มิติ(xb, yBase, zTop, xb, yBase, zEdge, xb, yMid, zEdge)
}
# ════════════════════════════════════════════════════════════════════════════
# พื้นเช็คเกอร์ (CHECKER FLOOR) — เอกสารอธิบายทั้งระบบ
# ────────────────────────────────────────────────────────────────────────────
# ห้องหนึ่ง = ตาราง SUB×SUB กระเบื้อง (SUB=8 → 64 ใบ, ขนาดใบ cell=ROOM_S/8=32).
# สีกระเบื้องสลับลายหมากรุกตาม parity ของ (i+j): คู่=ct0 (สว่าง), คี่=ct1 (เข้มลง 17%).
# สีฐาน ct0/ct1/ผนัง cw มาจาก สีห้อง(L,hue...) → เปลี่ยนโทนตามเพลง (songIdx ใน main).
# * "พื้นเขียว" = เมื่อ hue ของเพลงนั้นออกโทนเขียว; ระบบสีเดียวคุมทุกสเตจ.
#
# ความสูงกระเบื้อง — แต่ละใบเป็น "ราบ" หรือ "ลาด" เต็มใบ (ไม่มีใบครึ่ง ๆ) เพราะ
# ขอบแถบทางลาดถูกจัดให้ตรงเส้นกริดพอดี: ROOM_S=256, RAMP_REACH=96, HALL_W=64
# → แถบลาดด้าน E อยู่คอลัมน์ i=5,6,7 (lx 160..256) แถวกลาง j=3,4 (lz 96..160).
# ค่าทั้งหมด (96,160) หาร 32 ลงตัว → กระเบื้องไม่คร่อมขอบ → ไม่มีรอยต่อ/รู.
# ใบราบ: y ทุกมุม = base (latY ของระดับห้อง). ใบลาด: y = สูตร slope ตรง ๆ
# (ไม่เรียก latticeHeight ต่อจุด — เร็วกว่าเดิม ~4 เท่า, ไม่มี sin ใน hot loop).
# สูตร slope ตรงกับ latticeHeight ทุกประการ → คอลลิชัน (latticeBlock) ตรงกับภาพ.
#
# กันรู: (1) มุมกระเบื้องที่ใช้ร่วมกันคำนวณ y เท่ากันเป๊ะ → ไม่มีรอยต่อ.
# (2) ผนังข้างลาด (ข้างลาดX/Z) ปิดด้านข้างร่องลาด → ไม่ทะลุเห็นฟ็อกข้างลาด.
# (3) ผนังขั้นขอบ (วาดขอบ) ปิดขอบที่เพื่อนบ้านต่ำกว่า เว้นช่องทางเดิน.
# ════════════════════════════════════════════════════════════════════════════
# ── วาดห้องเดียว: แคชข้อมูลห้องครั้งเดียว → พื้นเช็คเกอร์ (ความสูงเร็ว, มุมตรง = ไม่มีรู) ──
# + ผนังข้างลาด + ผนังขั้นขอบ. ROOM_S=256,SUB=8 → cell=32, ขอบแถบลาด (96/160) ตรงเส้นกริดพอดี
ฟังก์ชัน วาดห้อง(rc, rr, seed, nExits, hueR, hueG, hueB, t) {
bind L = roomLevel(rc, rr, seed)
bind ox = rc*ROOM_S; bind oz = rr*ROOM_S
bind base = latY(L)
bind hc = ROOM_S*0.5; bind hw = HALL_W*0.5
# แคชเพื่อนบ้าน + ขอบเปิด + จุดกึ่งกลางลาด (คำนวณครั้งเดียวต่อห้อง)
bind LE = roomLevel(rc+1, rr, seed); bind opE = edgeOpen(rc, rr, 1.0, seed, nExits)
bind LW = roomLevel(rc-1, rr, seed); bind opW = edgeOpen(rc-1, rr, 1.0, seed, nExits)
bind LS = roomLevel(rc, rr+1, seed); bind opS = edgeOpen(rc, rr, 0.0, seed, nExits)
bind LN = roomLevel(rc, rr-1, seed); bind opN = edgeOpen(rc, rr-1, 0.0, seed, nExits)
bind midE = (base+latY(LE))*0.5; bind midW = (base+latY(LW))*0.5
bind midS = (base+latY(LS))*0.5; bind midN = (base+latY(LN))*0.5
bind ct0 = สีห้อง(L, hueR, hueG, hueB, 0.0)
bind ct1 = สีห้อง(L, hueR, hueG, hueB, 0.0-0.26) # เช็คเกอร์: ช่องสลับเข้มลง
bind cw = สีห้อง(L, hueR, hueG, hueB, 0.0-0.50) # ผนัง: เข้มชัด (กำแพง 90°)
bind SUB = 8.0; bind cell = ROOM_S / SUB; bind RBASE = ROOM_S - RAMP_REACH
# ── พื้นเช็คเกอร์: แต่ละกระเบื้องเป็น "ราบ" หรือ "ลาด" เต็มใบ (ขอบแถบตรงเส้นกริด) ──
# ลาด = คำนวณ slope ตรง ๆ (ไม่เรียกฟังก์ชันความสูงต่อจุด → เร็วมาก)
bind i = 0.0
ขณะที่ i < SUB {
bind j = 0.0
ขณะที่ j < SUB {
bind lx0 = i*cell; bind lx1 = lx0 + cell; bind lz0 = j*cell; bind lz1 = lz0 + cell
bind x0 = ox + lx0; bind x1 = ox + lx1; bind z0 = oz + lz0; bind z1 = oz + lz1
bind y00 = base; bind y10 = base; bind y11 = base; bind y01 = base
ถ้า opE > 0.5 { ถ้า i > 4.5 { ถ้า j > 2.5 { ถ้า j < 4.5 {
bind y00 = base + (midE-base)*((lx0-RBASE)/RAMP_REACH); bind y10 = base + (midE-base)*((lx1-RBASE)/RAMP_REACH); bind y01 = y00; bind y11 = y10 } } } }
ถ้า opW > 0.5 { ถ้า i < 2.5 { ถ้า j > 2.5 { ถ้า j < 4.5 {
bind y00 = base + (midW-base)*((RAMP_REACH-lx0)/RAMP_REACH); bind y10 = base + (midW-base)*((RAMP_REACH-lx1)/RAMP_REACH); bind y01 = y00; bind y11 = y10 } } } }
ถ้า opS > 0.5 { ถ้า j > 4.5 { ถ้า i > 2.5 { ถ้า i < 4.5 {
bind y00 = base + (midS-base)*((lz0-RBASE)/RAMP_REACH); bind y01 = base + (midS-base)*((lz1-RBASE)/RAMP_REACH); bind y10 = y00; bind y11 = y01 } } } }
ถ้า opN > 0.5 { ถ้า j < 2.5 { ถ้า i > 2.5 { ถ้า i < 4.5 {
bind y00 = base + (midN-base)*((RAMP_REACH-lz0)/RAMP_REACH); bind y01 = base + (midN-base)*((RAMP_REACH-lz1)/RAMP_REACH); bind y10 = y00; bind y11 = y01 } } } }
# เช็คเกอร์ = 2 สามเหลี่ยม (baker จับเฉพาะ วาดสามเหลี่ยม3มิติ; draw_quad_3d ไม่ถูกจับ → พื้น/ลาดหายตอนอบ)
ถ้า (i+j) - ปัดลง((i+j)/2.0)*2.0 == 0.0 { สีดินสอ(list_get(ct0,0), list_get(ct0,1), list_get(ct0,2)) }
มิฉะนั้น { สีดินสอ(list_get(ct1,0), list_get(ct1,1), list_get(ct1,2)) }
วาดสามเหลี่ยม3มิติ(x0,y00,z0, x1,y10,z0, x1,y11,z1)
วาดสามเหลี่ยม3มิติ(x0,y00,z0, x1,y11,z1, x0,y01,z1)
bind j = j + 1.0
}
bind i = i + 1.0
}
# ── ผนังข้างทางลาด (ปิดด้านข้างร่องลาด → กันรูตอนใกล้ยอด) ──
สีดินสอ(list_get(cw,0), list_get(cw,1), list_get(cw,2))
ถ้า opE > 0.5 { ข้างลาดX(ox+ROOM_S-RAMP_REACH, ox+ROOM_S, oz+hc-hw, oz+hc+hw, base, midE) }
ถ้า opW > 0.5 { ข้างลาดX(ox+RAMP_REACH, ox, oz+hc-hw, oz+hc+hw, base, midW) }
ถ้า opS > 0.5 { ข้างลาดZ(oz+ROOM_S-RAMP_REACH, oz+ROOM_S, ox+hc-hw, ox+hc+hw, base, midS) }
ถ้า opN > 0.5 { ข้างลาดZ(oz+RAMP_REACH, oz, ox+hc-hw, ox+hc+hw, base, midN) }
# ── ผนังขั้นขอบ: ทุกขอบที่เพื่อนบ้านต่ำกว่า → กำแพง เว้นช่องทางเดิน ──
ถ้า LE < L { วาดขอบ(ox+ROOM_S, oz, ox+ROOM_S, oz+ROOM_S, base, latY(LE), opE, hw) }
ถ้า LW < L { วาดขอบ(ox, oz, ox, oz+ROOM_S, base, latY(LW), opW, hw) }
ถ้า LS < L { วาดขอบ(ox, oz+ROOM_S, ox+ROOM_S, oz+ROOM_S, base, latY(LS), opS, hw) }
ถ้า LN < L { วาดขอบ(ox, oz, ox+ROOM_S, oz, base, latY(LN), opN, hw) }
}
# ผนังขั้นแนวตั้งจาก (ax,az)-(bx,bz) ระหว่าง y_top (สูง) → y_bot (ต่ำ)
ฟังก์ชัน วาดผนังขั้น(ax, az, bx, bz, yt, yb) {
วาดสามเหลี่ยม3มิติ(ax, yt, az, bx, yt, bz, bx, yb, bz)
วาดสามเหลี่ยม3มิติ(ax, yt, az, bx, yb, bz, ax, yb, az)
}
# กำแพงตามขอบ (ax,az)->(bx,bz); ถ้า gapOpen เว้นช่องกลางกว้าง 2*gw (ทางเดิน/ทางลาด)
ฟังก์ชัน วาดขอบ(ax, az, bx, bz, yt, yb, gapOpen, gw) {
ถ้า gapOpen > 0.5 {
bind dx = bx - ax; bind dz = bz - az; bind ln = รากที่สอง(dx*dx + dz*dz)
bind ux = dx/ln; bind uz = dz/ln
bind mx = (ax+bx)*0.5; bind mz = (az+bz)*0.5
วาดผนังขั้น(ax, az, mx - ux*gw, mz - uz*gw, yt, yb) # ก่อนช่อง
วาดผนังขั้น(mx + ux*gw, mz + uz*gw, bx, bz, yt, yb) # หลังช่อง
}
มิฉะนั้น { วาดผนังขั้น(ax, az, bx, bz, yt, yb) }
}
# ── คอลลิชันผนัง: กันลูกปีนขึ้นห้องที่สูงกว่า (ผ่านได้เฉพาะแถบทางเดินที่เปิด) ──
# คืน [hit, qx, qz] (รูปแบบเดียวกับ หากล่อง2) · ลงต่ำ = ปล่อย (ตกได้)
ฟังก์ชัน latticeBlock(cx, cz, cy, R, seed, nExits) {
bind rc = ปัดลง(cx / ROOM_S); bind rr = ปัดลง(cz / ROOM_S)
bind L = roomLevel(rc, rr, seed)
bind lx = cx - rc*ROOM_S; bind lz = cz - rr*ROOM_S
bind hc = ROOM_S*0.5; bind hw = HALL_W*0.5
bind dzc = lz - hc; ถ้า dzc < 0.0 { bind dzc = 0.0 - dzc }
bind dxc = lx - hc; ถ้า dxc < 0.0 { bind dxc = 0.0 - dxc }
ถ้า lx > ROOM_S - R { # E
bind blk = 1.0
ถ้า roomLevel(rc+1, rr, seed) <= L { bind blk = 0.0 }
ถ้า dzc < hw { ถ้า edgeOpen(rc, rr, 1.0, seed, nExits) > 0.5 { bind blk = 0.0 } }
ถ้า blk > 0.5 { คืน [1.0, rc*ROOM_S + ROOM_S - R, cz] }
}
ถ้า lx < R { # W
bind blk = 1.0
ถ้า roomLevel(rc-1, rr, seed) <= L { bind blk = 0.0 }
ถ้า dzc < hw { ถ้า edgeOpen(rc-1, rr, 1.0, seed, nExits) > 0.5 { bind blk = 0.0 } }
ถ้า blk > 0.5 { คืน [1.0, rc*ROOM_S + R, cz] }
}
ถ้า lz > ROOM_S - R { # S
bind blk = 1.0
ถ้า roomLevel(rc, rr+1, seed) <= L { bind blk = 0.0 }
ถ้า dxc < hw { ถ้า edgeOpen(rc, rr, 0.0, seed, nExits) > 0.5 { bind blk = 0.0 } }
ถ้า blk > 0.5 { คืน [1.0, cx, rr*ROOM_S + ROOM_S - R] }
}
ถ้า lz < R { # N
bind blk = 1.0
ถ้า roomLevel(rc, rr-1, seed) <= L { bind blk = 0.0 }
ถ้า dxc < hw { ถ้า edgeOpen(rc, rr-1, 0.0, seed, nExits) > 0.5 { bind blk = 0.0 } }
ถ้า blk > 0.5 { คืน [1.0, cx, rr*ROOM_S + R] }
}
คืน [0.0, cx, cz]
}
# ── มองเห็นในแลตทิซ: เรย์ 3 มิติจากตา (vx,vy,vz) → เป้า (ox,oy,oz) ผ่านสนามความสูง ──
# จุดตัวอย่างจมใต้พื้นห้อง (y > latticeHeight — บล็อกตันของห้องยกสูง/ผนังขั้น) = ถูกบัง
# สเต็ป ~14u แคป 20 จุด (มอนในระยะวาด 200u) → ถูกพอสำหรับต่อมอนต่อเฟรม
ฟังก์ชัน มองเห็นแลตทิซ(vx, vy, vz, ox, oy, oz, seed, nExits) {
bind dx = ox - vx; bind dz = oz - vz
bind dist = รากที่สอง(dx*dx + dz*dz)
ถ้า dist < 20.0 { คืน 1 } # ประชิด: เห็นเสมอ (ห้องเดียวกันแน่)
bind n = ปัดลง(dist / 14.0) + 1
ถ้า n > 20 { bind n = 20 }
bind dy = oy - vy
bind k = 1
ขณะที่ k < n {
bind f = k / n
bind sy = vy + dy*f
ถ้า sy > LAT_BASE - 0.75 { bind k = k + 1 } # ต่ำกว่าพื้นฐาน: ไม่มีบล็อกให้บัง (ข้ามเช็ค)
มิฉะนั้น {
bind sx = vx + dx*f; bind sz = vz + dz*f
ถ้า sy > latticeHeight(sx, sz, seed, nExits) + 0.75 { คืน 0 } # ใต้ผิวพื้น = ในบล็อกตัน
bind k = k + 1
}
}
คืน 1
}
# ── วาดทั้งโครง: สตรีมเฉพาะ 5 ห้อง (กลาง + 4 ข้าง) ──
# ห้องอบ: เรขาคณิตห้องคงที่ต่อเซลล์ (ไม่ขึ้นกับ t) → อบดิสเพลย์ลิสต์ครั้งเดียว
# ต่อเซลล์ แล้วเล่นซ้ำเนทีฟ (สีฮิวแช่แข็งตอนอบ; ล้างแคชเมื่อเปลี่ยนเลเวล)
ฟังก์ชัน ห้องอบ(rc, rr, seed, nExits, hueR, hueG, hueB) {
bind key = rc * 100000.0 + rr
bind h = เมชแคชรับ(key)
ถ้า h < 0.0 {
เริ่มอบเมช()
วาดห้อง(rc, rr, seed, nExits, hueR, hueG, hueB, 0.0)
bind h = จบอบเมช()
เมชแคชตั้ง(key, h)
}
วาดอบเมชสี(h, 0.0,0.0,0.0, 1.0,0.0,0.0, 0.0,1.0,0.0, 1.0)
}
ฟังก์ชัน drawLattice(cx, cz, seed, nExits, hueR, hueG, hueB, t) {
โหมดผสม(0)
set_normal_override(0.0, 0.0 - 1.0, 0.0)
# พื้นด้าน (matte): ปิดสเปกคูลาร์/เคลียร์โค้ต — ไม่มีชีนวาว/รอยขาวสาดบนพื้นห้องแลตทิซ
set_material("roughness", 0.85)
set_material("specular", 0.05)
set_material("specular_tint", 0.0)
set_material("clearcoat", 0.0)
set_material("clearcoat_roughness", 0.10)
set_material("toon_bands", 4.0)
set_material("shadow_softness", 0.10)
bind rc = ปัดลง(cx / ROOM_S); bind rr = ปัดลง(cz / ROOM_S)
ห้องอบ(rc, rr, seed, nExits, hueR, hueG, hueB)
ห้องอบ(rc+1.0, rr, seed, nExits, hueR, hueG, hueB)
ห้องอบ(rc-1.0, rr, seed, nExits, hueR, hueG, hueB)
ห้องอบ(rc, rr+1.0, seed, nExits, hueR, hueG, hueB)
ห้องอบ(rc, rr-1.0, seed, nExits, hueR, hueG, hueB)
flush_3d()
reset_material()
reset_normal_override()
}