260 lines
6.6 KiB
Go
260 lines
6.6 KiB
Go
package preview
|
|
|
|
import (
|
|
"math"
|
|
|
|
"aego/asset"
|
|
"aego/core/mathx"
|
|
"aego/editor/core/shape"
|
|
"aego/editor/core/tasks"
|
|
"aego/engine"
|
|
"aego/gpu"
|
|
"aego/render/rt"
|
|
"aego/render/view"
|
|
"aego/scene"
|
|
"aego/scene/frame"
|
|
"aego/scene/sceneio"
|
|
)
|
|
|
|
type sceneJob struct {
|
|
kind asset.ArtifactKind
|
|
name string
|
|
data []byte
|
|
target *rt.Target
|
|
runner *frame.Runner
|
|
out pixels
|
|
}
|
|
|
|
func (s *Service) startScene(e asset.Entry, it *item, spec tasks.Spec) {
|
|
key := it.key
|
|
j := &sceneJob{kind: e.Kind, name: e.Name}
|
|
art, _ := e.Artifact(e.Kind)
|
|
files := s.d.Pipeline.Artifacts()
|
|
cached := false
|
|
it.task = s.d.Tasks.Work(spec, func(*tasks.Task) error {
|
|
if p, err := s.load(key); err == nil {
|
|
j.out, cached = p, true
|
|
return nil
|
|
}
|
|
data, err := files.ReadFile(art.Path)
|
|
j.data = data
|
|
return err
|
|
}).Then(func(t *tasks.Task) {
|
|
if it.key != key {
|
|
return
|
|
}
|
|
if cached || t.State() != tasks.Done {
|
|
s.finish(it, key, j.out, t)
|
|
return
|
|
}
|
|
it.task = s.d.Tasks.Step(spec, tasks.LaneRender, s.renderStep(j)).Then(func(dt *tasks.Task) {
|
|
if it.key != key || dt.State() != tasks.Done {
|
|
s.release(j)
|
|
s.finish(it, key, j.out, dt)
|
|
return
|
|
}
|
|
it.task = s.d.Tasks.Step(spec, tasks.LaneMain, s.readStep(j)).Then(func(rt *tasks.Task) {
|
|
s.release(j)
|
|
s.finish(it, key, j.out, rt)
|
|
if rt.State() == tasks.Done {
|
|
out := j.out
|
|
s.d.Tasks.Work(tasks.Spec{Title: spec.Title, Kind: "preview"}, func(*tasks.Task) error { return s.save(key, out) })
|
|
}
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *Service) renderStep(j *sceneJob) tasks.Step {
|
|
return func(*tasks.Task) (bool, error) {
|
|
return true, s.draw(j)
|
|
}
|
|
}
|
|
|
|
func (s *Service) readStep(j *sceneJob) tasks.Step {
|
|
return func(*tasks.Task) (bool, error) {
|
|
buf := make([]byte, Size*Size*4)
|
|
if err := s.d.GPU.ReadPixels(j.target.Texture, 0, 0, Size, Size, buf); err != nil {
|
|
return true, err
|
|
}
|
|
j.out = pixels{buf: buf, w: Size, h: Size}
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
func (s *Service) draw(j *sceneJob) error {
|
|
sc, _, err := sceneio.Load(j.data, j.name, s.d.Types, s.d.Resolver, sceneio.LoadOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if s.pool == nil {
|
|
s.pool = rt.New(s.d.GPU, gpu.TexSRGBA8, "editor.preview")
|
|
}
|
|
tgt, err := s.pool.Acquire(Size, Size, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
j.target = tgt
|
|
w := engine.NewWorld("preview")
|
|
r := frame.New()
|
|
r.Bind(sc, s.d.Renderer, s.d.Resolver)
|
|
w.SetService(frame.ServiceID, r)
|
|
w.SetService(scene.ServiceID, sc)
|
|
j.runner = r
|
|
r.SetTarget(tgt.Texture, tgt.BucketW, tgt.BucketH)
|
|
r.SetDepthTarget(tgt.Depth)
|
|
r.SetViewportSize(Size, Size)
|
|
clear := s.d.Background
|
|
if j.kind == asset.ArtPrefab {
|
|
clear = mathx.Color{}
|
|
}
|
|
e := s.d.Engine
|
|
fixed := false
|
|
if j.kind == asset.ArtScene {
|
|
e.RunPhase(w, engine.PhaseRenderPrepare, engine.ScopeEditor, 0, 0)
|
|
if v, ok := cameraView(r.Views(), s.reference(), tgt); ok {
|
|
r.SetFixedView(v)
|
|
fixed = true
|
|
} else if len(r.Views()) > 0 {
|
|
e.RunPhase(w, engine.PhaseRender, engine.ScopeEditor, 0, 0)
|
|
return nil
|
|
}
|
|
}
|
|
if !fixed {
|
|
r.SetFixedView(Fit(sc, shape.Set{Reg: s.d.Shapes, Res: s.d.Resolver}, tgt, clear))
|
|
}
|
|
e.RunPhase(w, engine.PhaseRenderPrepare, engine.ScopeEditor, 0, 0)
|
|
e.RunPhase(w, engine.PhaseRender, engine.ScopeEditor, 0, 0)
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) release(j *sceneJob) {
|
|
if j.runner != nil {
|
|
j.runner.Close()
|
|
j.runner = nil
|
|
}
|
|
if j.target != nil && s.pool != nil {
|
|
s.pool.Release(j.target)
|
|
j.target = nil
|
|
}
|
|
}
|
|
|
|
func Fit(sc *scene.Scene, set shape.Set, tgt *rt.Target, clear mathx.Color) view.View {
|
|
sc.UpdateTransforms()
|
|
var b2 mathx.AABB2
|
|
var b3 mathx.AABB3
|
|
has2, has3 := false, false
|
|
add2 := func(b mathx.AABB2) {
|
|
if has2 {
|
|
b2 = b2.Union(b)
|
|
} else {
|
|
b2, has2 = b, true
|
|
}
|
|
}
|
|
add3 := func(b mathx.AABB3) {
|
|
if has3 {
|
|
b3 = mathx.AABB3{Min: mathx.Vec3Min(b3.Min, b.Min), Max: mathx.Vec3Max(b3.Max, b.Max)}
|
|
} else {
|
|
b3, has3 = b, true
|
|
}
|
|
}
|
|
root := sc.Root()
|
|
for _, h := range sc.PreOrder() {
|
|
if h == root || !sc.IsAlive(h) || !sc.EffectiveEnabled(h) {
|
|
continue
|
|
}
|
|
sh, ok := set.Of(sc, h)
|
|
switch {
|
|
case ok && sh.Kind == shape.Box:
|
|
add3(sh.Box)
|
|
case ok && sh.Kind == shape.Rect:
|
|
add2(rectBounds(sc, h, sh.Local))
|
|
case sc.Space(h) == scene.Space3D:
|
|
p := sc.WorldPosition3D(h)
|
|
add3(mathx.AABB3{Min: p, Max: p})
|
|
default:
|
|
p := sc.WorldPosition(h)
|
|
add2(mathx.AABB2{Min: p, Max: p})
|
|
}
|
|
}
|
|
if has3 && (sc.DefaultSpace() == scene.Space3D || !has2) {
|
|
return fit3(b3, tgt, clear)
|
|
}
|
|
return fit2(b2, has2, tgt, clear)
|
|
}
|
|
|
|
func rectBounds(sc *scene.Scene, h scene.NodeHandle, lr mathx.Rect) mathx.AABB2 {
|
|
m := sc.World(h)
|
|
pts := [4]mathx.Vec2{
|
|
m.Apply(mathx.Vec2{X: lr.X, Y: lr.Y}),
|
|
m.Apply(mathx.Vec2{X: lr.X + lr.W, Y: lr.Y}),
|
|
m.Apply(mathx.Vec2{X: lr.X + lr.W, Y: lr.Y + lr.H}),
|
|
m.Apply(mathx.Vec2{X: lr.X, Y: lr.Y + lr.H}),
|
|
}
|
|
b := mathx.AABB2{Min: pts[0], Max: pts[0]}
|
|
for _, p := range pts[1:] {
|
|
b = b.Union(mathx.AABB2{Min: p, Max: p})
|
|
}
|
|
return b
|
|
}
|
|
|
|
func target(v view.View, tgt *rt.Target, clear mathx.Color) view.View {
|
|
v.Target, v.TargetW, v.TargetH = tgt.Texture, tgt.BucketW, tgt.BucketH
|
|
v.Clear, v.DoClear, v.Label = clear, true, "preview"
|
|
return v
|
|
}
|
|
|
|
func fit2(b mathx.AABB2, has bool, tgt *rt.Target, clear mathx.Color) view.View {
|
|
zoom := float32(64)
|
|
if has {
|
|
sz := b.Size()
|
|
zoom = min(Size/max(sz.X, 1), Size/max(sz.Y, 1)) / 1.15
|
|
}
|
|
v := view.View{
|
|
Camera: view.Camera2D{Position: b.Center(), Zoom: zoom},
|
|
Viewport: mathx.Rect{W: Size, H: Size},
|
|
PixelScale: 1,
|
|
PixelsPerUnit: 1,
|
|
ZMax: 1,
|
|
Layers: view.AllLayers,
|
|
}
|
|
return target(v, tgt, clear)
|
|
}
|
|
|
|
func fit3(b mathx.AABB3, tgt *rt.Target, clear mathx.Color) view.View {
|
|
c := b.Min.Add(b.Max).Scale(0.5)
|
|
r := max(b.Max.Sub(b.Min).Len()*0.5, 0.25)
|
|
fov := mathx.Pi / 3
|
|
d := r / float32(math.Sin(float64(fov)/2)) * 1.1
|
|
rot := mathx.QuatYawPitch(0.6, -0.45)
|
|
pos := c.Add(rot.Rotate(mathx.Vec3{Z: d}))
|
|
eye := mathx.Mat4TRS(pos, rot, mathx.Vec3{X: 1, Y: 1, Z: 1})
|
|
v := view.PerspectiveView(eye, fov, mathx.Clamp(d*0.002, 0.01, 1), max(d*50, 1000), mathx.Rect{W: Size, H: Size})
|
|
v.DepthTarget = tgt.Depth
|
|
return target(v, tgt, clear)
|
|
}
|
|
|
|
func cameraView(views []view.View, ref mathx.Vec2, tgt *rt.Target) (view.View, bool) {
|
|
if len(views) == 0 || views[0].Is3D() {
|
|
return view.View{}, false
|
|
}
|
|
v := views[0]
|
|
if v.Virtual == (view.View{}).Virtual && ref.Y > 0 {
|
|
v.Camera.Zoom *= Size / ref.Y
|
|
}
|
|
v.Viewport = mathx.Rect{W: Size, H: Size}
|
|
v.Target, v.TargetW, v.TargetH = tgt.Texture, tgt.BucketW, tgt.BucketH
|
|
v.PixelScale = 1
|
|
v.Label = "preview"
|
|
return v, true
|
|
}
|
|
|
|
func (s *Service) reference() mathx.Vec2 {
|
|
if s.d.Reference != nil {
|
|
if v := s.d.Reference(); v.Y > 0 {
|
|
return v
|
|
}
|
|
}
|
|
return mathx.Vec2{X: 1280, Y: 720}
|
|
}
|