mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
121 lines
4.2 KiB
Go
121 lines
4.2 KiB
Go
// 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)
|
|
}
|