feat(渲染编辑): 完善渲染编辑,优化版本压缩
build.yaml / build (push) Successful in 4m25s

This commit is contained in:
xiaoqidun committed 2026-10-12 10:47:20 +08:00
1 parent 19dc79e8e3
commit a710bc5f41
68 files changed
+2149 -724

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"image/color"
"math"
"slices"
)
// MeshSampler 保存独立网格快照和空间索引,支持并发采样
type MeshSampler struct {
mesh MeshShading
nodes []meshSampleNode
}
// meshSampleNode 保存连续三角形组的包围盒及右子树位置
type meshSampleNode struct {
minX, minY, maxX, maxY float64
right int
}
// NewSampler 创建网格快照,后续修改原网格不影响采样结果
// 返回: *MeshSampler 只读采样器
func (m *MeshShading) NewSampler() *MeshSampler {
s := &MeshSampler{}
if m != nil {
s.mesh = *m
s.mesh.Triangles = slices.Clone(m.Triangles)
if len(m.Triangles) > 8 {
s.build(0, len(m.Triangles))
}
}
return s
}
// At 采样快照中的预乘颜色,后绘制的三角形优先
// 入参: x 横坐标, y 纵坐标
// 返回: color.RGBA 预乘颜色
func (s *MeshSampler) At(x, y float64) color.RGBA {
if len(s.nodes) == 0 || !finite(x) || !finite(y) {
return s.mesh.At(x, y)
}
if value, ok := s.sample(0, 0, len(s.mesh.Triangles), x, y); ok {
return value
}
return s.mesh.Background
}
// build 按绘制顺序构建包围盒层级,不重排原三角形
// 入参: first 起始三角形, end 结束位置
// 返回: int 节点位置
func (s *MeshSampler) build(first, end int) int {
index := len(s.nodes)
s.nodes = append(s.nodes, meshSampleNode{})
if end-first <= 8 {
node := meshSampleNode{minX: math.Inf(1), minY: math.Inf(1), maxX: math.Inf(-1), maxY: math.Inf(-1)}
for _, triangle := range s.mesh.Triangles[first:end] {
a, b, c := triangle[0].Point, triangle[1].Point, triangle[2].Point
d := (b.Y-c.Y)*(a.X-c.X) + (c.X-b.X)*(a.Y-c.Y)
if !finite(d) {
node = meshSampleNode{minX: math.Inf(-1), minY: math.Inf(-1), maxX: math.Inf(1), maxY: math.Inf(1)}
break
}
for _, vertex := range triangle {
node.minX, node.minY = math.Min(node.minX, vertex.Point.X), math.Min(node.minY, vertex.Point.Y)
node.maxX, node.maxY = math.Max(node.maxX, vertex.Point.X), math.Max(node.maxY, vertex.Point.Y)
}
}
if finite(node.minX) && finite(node.minY) && finite(node.maxX) && finite(node.maxY) {
dx := 2e-12*(node.maxX-node.minX) + 1e-14*math.Max(math.Abs(node.minX), math.Abs(node.maxX))
dy := 2e-12*(node.maxY-node.minY) + 1e-14*math.Max(math.Abs(node.minY), math.Abs(node.maxY))
node.minX, node.maxX = math.Nextafter(node.minX-dx, math.Inf(-1)), math.Nextafter(node.maxX+dx, math.Inf(1))
node.minY, node.maxY = math.Nextafter(node.minY-dy, math.Inf(-1)), math.Nextafter(node.maxY+dy, math.Inf(1))
}
s.nodes[index] = node
return index
}
middle := first + (end-first)/2
left := s.build(first, middle)
right := s.build(middle, end)
a, b := s.nodes[left], s.nodes[right]
s.nodes[index] = meshSampleNode{minX: math.Min(a.minX, b.minX), minY: math.Min(a.minY, b.minY), maxX: math.Max(a.maxX, b.maxX), maxY: math.Max(a.maxY, b.maxY), right: right}
return index
}
// sample 优先查询后绘制的区域,包围盒外跳过整组三角形
// 入参: index 节点位置, first 起始三角形, end 结束位置, x 横坐标, y 纵坐标
// 返回: color.RGBA 采样颜色, bool 是否命中
func (s *MeshSampler) sample(index, first, end int, x, y float64) (color.RGBA, bool) {
node := s.nodes[index]
if x < node.minX || x > node.maxX || y < node.minY || y > node.maxY {
return color.RGBA{}, false
}
if end-first <= 8 {
for i := end - 1; i >= first; i-- {
if value, ok := meshTriangleColor(&s.mesh.Triangles[i], x, y); ok {
return value, true
}
}
return color.RGBA{}, false
}
middle := first + (end-first)/2
if value, ok := s.sample(node.right, middle, end, x, y); ok {
return value, true
}
return s.sample(index+1, first, middle, x, y)
}