Files
aego/render/debug/debug.go
T
sanspie 17cb5e4291 Stage 2-5: assets, scene/ECS, editor, 3D, and Aego Script
Stage 2: render pipeline and UI toolkit over gpu/platform.
Stage 3: schema registry, asset pipeline + runtime loader, scene graph and ECS.
Stage 4: editor — docking, inspector, scene/game views, play mode, asset browser, console, profiler; 4.5 adds the 3D foundation (mixed 2D/3D spaces, meshes, depth).
Stage 5: Aego Script — compiler (lexer/parser/types/resolver/checker/asm/compiler), bytecode with verifier and register VM, instance runtime, stdlib, native bindings, and editor/CLI integration (aego run/edit/check/test/dump).
2026-10-08 13:17:28 +03:00

304 lines
7.1 KiB
Go

package debug
import (
"unsafe"
"aego/core/mathx"
"aego/gpu"
)
type Space uint8
const (
World Space = iota
Screen
Overlay
)
const VertexStride = 16
type Vertex struct {
Pos mathx.Vec3
Color uint32
}
const _ = uint(VertexStride - unsafe.Sizeof(Vertex{}))
const _ = uint(unsafe.Sizeof(Vertex{}) - VertexStride)
type Range struct {
First int
Count int
}
type Draw struct {
dev gpu.Device
vb gpu.BufferHandle
max int
world []Vertex
overlay []Vertex
screen []Vertex
space Space
dropped uint32
}
func New(dev gpu.Device, maxVerts int) (*Draw, error) {
if maxVerts < 2 {
maxVerts = 2
}
vb, err := dev.CreateBuffer(gpu.BufferDesc{
Usage: gpu.BufferVertex,
Size: maxVerts * VertexStride,
Dynamic: true,
Label: "debug.lines",
})
if err != nil {
return nil, err
}
return &Draw{
dev: dev,
vb: vb,
max: maxVerts,
world: make([]Vertex, 0, maxVerts),
overlay: make([]Vertex, 0, maxVerts),
screen: make([]Vertex, 0, maxVerts),
}, nil
}
func (d *Draw) Buffer() gpu.BufferHandle {
return d.vb
}
func (d *Draw) Reset() {
d.world = d.world[:0]
d.overlay = d.overlay[:0]
d.screen = d.screen[:0]
d.space = World
d.dropped = 0
}
func (d *Draw) Space(s Space) {
d.space = s
}
func (d *Draw) Dropped() uint32 {
return d.dropped
}
func (d *Draw) Counts() (world, overlay, screen int) {
return len(d.world), len(d.overlay), len(d.screen)
}
func (d *Draw) push3(a, b mathx.Vec3, c mathx.Color) {
if len(d.world)+len(d.overlay)+len(d.screen)+2 > d.max {
d.dropped += 2
return
}
packed := c.Premultiplied().PackRGBA8()
va, vb := Vertex{Pos: a, Color: packed}, Vertex{Pos: b, Color: packed}
switch d.space {
case Screen:
d.screen = append(d.screen, va, vb)
case Overlay:
d.overlay = append(d.overlay, va, vb)
default:
d.world = append(d.world, va, vb)
}
}
func (d *Draw) push(a, b mathx.Vec2, c mathx.Color) {
d.push3(mathx.Vec3{X: a.X, Y: a.Y}, mathx.Vec3{X: b.X, Y: b.Y}, c)
}
func (d *Draw) Line(a, b mathx.Vec2, c mathx.Color) {
d.push(a, b, c)
}
func (d *Draw) Rect(r mathx.Rect, c mathx.Color) {
p0 := mathx.Vec2{X: r.X, Y: r.Y}
p1 := mathx.Vec2{X: r.X + r.W, Y: r.Y}
p2 := mathx.Vec2{X: r.X + r.W, Y: r.Y + r.H}
p3 := mathx.Vec2{X: r.X, Y: r.Y + r.H}
d.push(p0, p1, c)
d.push(p1, p2, c)
d.push(p2, p3, c)
d.push(p3, p0, c)
}
func (d *Draw) AABB(b mathx.AABB2, c mathx.Color) {
size := b.Size()
d.Rect(mathx.Rect{X: b.Min.X, Y: b.Min.Y, W: size.X, H: size.Y}, c)
}
func (d *Draw) RectRot(center, size mathx.Vec2, rad float32, c mathx.Color) {
hx, hy := size.X/2, size.Y/2
s, co := mathx.Sin(rad), mathx.Cos(rad)
rot := func(x, y float32) mathx.Vec2 {
return mathx.Vec2{X: center.X + x*co - y*s, Y: center.Y + x*s + y*co}
}
p0 := rot(-hx, -hy)
p1 := rot(hx, -hy)
p2 := rot(hx, hy)
p3 := rot(-hx, hy)
d.push(p0, p1, c)
d.push(p1, p2, c)
d.push(p2, p3, c)
d.push(p3, p0, c)
}
func (d *Draw) Circle(center mathx.Vec2, radius float32, segments int, c mathx.Color) {
if segments < 3 {
segments = 3
}
step := mathx.TwoPi / float32(segments)
prev := mathx.Vec2{X: center.X + radius, Y: center.Y}
for i := 1; i <= segments; i++ {
a := step * float32(i)
next := mathx.Vec2{X: center.X + radius*mathx.Cos(a), Y: center.Y + radius*mathx.Sin(a)}
d.push(prev, next, c)
prev = next
}
}
func (d *Draw) Cross(p mathx.Vec2, size float32, c mathx.Color) {
h := size / 2
d.push(mathx.Vec2{X: p.X - h, Y: p.Y}, mathx.Vec2{X: p.X + h, Y: p.Y}, c)
d.push(mathx.Vec2{X: p.X, Y: p.Y - h}, mathx.Vec2{X: p.X, Y: p.Y + h}, c)
}
func (d *Draw) Arrow(a, b mathx.Vec2, c mathx.Color) {
d.push(a, b, c)
dir := b.Sub(a)
l := dir.Len()
if l == 0 {
return
}
dir = dir.Scale(1 / l)
head := min(l*0.25, 16)
left := dir.Rotated(2.6).Scale(head)
right := dir.Rotated(-2.6).Scale(head)
d.push(b, b.Add(left), c)
d.push(b, b.Add(right), c)
}
func (d *Draw) Line3(a, b mathx.Vec3, c mathx.Color) {
d.push3(a, b, c)
}
func (d *Draw) AABB3(b mathx.AABB3, c mathx.Color) {
lo, hi := b.Min, b.Max
p := [8]mathx.Vec3{
{X: lo.X, Y: lo.Y, Z: lo.Z}, {X: hi.X, Y: lo.Y, Z: lo.Z},
{X: hi.X, Y: hi.Y, Z: lo.Z}, {X: lo.X, Y: hi.Y, Z: lo.Z},
{X: lo.X, Y: lo.Y, Z: hi.Z}, {X: hi.X, Y: lo.Y, Z: hi.Z},
{X: hi.X, Y: hi.Y, Z: hi.Z}, {X: lo.X, Y: hi.Y, Z: hi.Z},
}
d.box(&p, c)
}
func (d *Draw) box(p *[8]mathx.Vec3, c mathx.Color) {
for i := 0; i < 4; i++ {
d.push3(p[i], p[(i+1)%4], c)
d.push3(p[4+i], p[4+(i+1)%4], c)
d.push3(p[i], p[4+i], c)
}
}
func (d *Draw) Grid3(center mathx.Vec3, step float32, count int, c mathx.Color) {
if step <= 0 || count <= 0 {
return
}
ext := step * float32(count)
for i := -count; i <= count; i++ {
o := step * float32(i)
d.push3(mathx.Vec3{X: center.X + o, Y: center.Y, Z: center.Z - ext}, mathx.Vec3{X: center.X + o, Y: center.Y, Z: center.Z + ext}, c)
d.push3(mathx.Vec3{X: center.X - ext, Y: center.Y, Z: center.Z + o}, mathx.Vec3{X: center.X + ext, Y: center.Y, Z: center.Z + o}, c)
}
}
func (d *Draw) Axes3(m mathx.Mat4, size float32) {
o := m.Translation()
d.push3(o, o.Add(m.TransformDir(mathx.Vec3{X: size})), mathx.Color{R: 1, G: 0.2, B: 0.2, A: 1})
d.push3(o, o.Add(m.TransformDir(mathx.Vec3{Y: size})), mathx.Color{R: 0.2, G: 1, B: 0.3, A: 1})
d.push3(o, o.Add(m.TransformDir(mathx.Vec3{Z: size})), mathx.Color{R: 0.3, G: 0.5, B: 1, A: 1})
}
func Basis(normal mathx.Vec3) (mathx.Vec3, mathx.Vec3) {
n := normal.Normalized()
a := mathx.Vec3{Y: 1}
if mathx.Abs(n.Y) > 0.9 {
a = mathx.Vec3{X: 1}
}
u := n.Cross(a).Normalized()
return u, n.Cross(u)
}
func (d *Draw) Circle3(center, normal mathx.Vec3, r float32, segs int, c mathx.Color) {
if segs < 3 {
segs = 3
}
u, w := Basis(normal)
step := mathx.TwoPi / float32(segs)
prev := center.Add(u.Scale(r))
for i := 1; i <= segs; i++ {
a := step * float32(i)
next := center.Add(u.Scale(r * mathx.Cos(a))).Add(w.Scale(r * mathx.Sin(a)))
d.push3(prev, next, c)
prev = next
}
}
func (d *Draw) Frustum(viewProj mathx.Mat4, c mathx.Color) {
inv, ok := viewProj.Inverse()
if !ok {
return
}
var p [8]mathx.Vec3
for i := 0; i < 8; i++ {
x := float32(-1)
if i == 1 || i == 2 || i == 5 || i == 6 {
x = 1
}
y := float32(-1)
if i == 2 || i == 3 || i == 6 || i == 7 {
y = 1
}
z := float32(-1)
if i >= 4 {
z = 1
}
p[i] = inv.TransformPoint(mathx.Vec3{X: x, Y: y, Z: z})
}
d.box(&p, c)
}
func (d *Draw) upload(list []Vertex, first int) Range {
if len(list) == 0 {
return Range{First: first}
}
raw := unsafe.Slice((*byte)(unsafe.Pointer(&list[0])), len(list)*VertexStride)
d.dev.UpdateBuffer(d.vb, first*VertexStride, raw)
return Range{First: first, Count: len(list)}
}
func (d *Draw) Upload() (world, overlay, screen Range) {
world = d.upload(d.world, 0)
overlay = d.upload(d.overlay, world.First+world.Count)
screen = d.upload(d.screen, overlay.First+overlay.Count)
return world, overlay, screen
}
func (d *Draw) Close() {
d.dev.DestroyBuffer(d.vb)
}
func Layout() gpu.VertexLayout {
return gpu.VertexLayout{
Attrs: []gpu.VertexAttr{
{Location: 0, Slot: 0, Format: gpu.VFFloat3, Offset: 0},
{Location: 1, Slot: 0, Format: gpu.VFUByte4Norm, Offset: 12},
},
Strides: [gpu.MaxVertexSlots]uint16{VertexStride},
}
}