Files
2026-10-14 00:44:44 +08:00

284 lines
8.7 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 threed
import (
"fmt"
"math"
)
// U3DCrossSection 定义世界坐标中的剖切面,AxisU与AxisV须为非零正交方向
// 两轴叉积指向被裁去的一侧;平面方形边长取静态场景包围盒对角线
// Opacity为0时不显示平面,Intersection为空时不显示交线,颜色分量范围为0到1
type U3DCrossSection struct {
Center, AxisU, AxisV [3]float64
Opacity float64
Color [3]float64
Intersection *[3]float64
}
// u3dSection 保存相机空间裁剪方程、平面四角与交线颜色
type u3dSection struct {
plane [4]float64
corners [4]u3dVertex
intersection *[3]float64
opacity float64
}
// PrepareSections 准备剖切缓存,不复制网格或修改调用方参数
// 入参: sections 世界坐标剖切面
// 返回: error 参数、场景、预算或取消错误
func (r *Renderer) PrepareSections(sections []U3DCrossSection) error {
if len(sections) == 0 {
return nil
}
if err := r.Reserve(uint64(len(sections)), 1152); err != nil {
return err
}
if err := r.Reserve(uint64(len(sections))+3, 512); err != nil {
return err
}
r.sections = make([]u3dSection, len(sections))
for i := range r.sectionScratch {
r.sectionScratch[i] = make([]u3dVertex, len(sections)+3)
}
visible := false
for i, section := range sections {
if i%256 == 0 {
if err := r.ctx.Err(); err != nil {
return err
}
}
if !finite(section.Opacity) || section.Opacity < 0 || section.Opacity > 1 {
return fmt.Errorf("invalid U3D section opacity")
}
visible = visible || section.Opacity > 0
}
diagonal := 0.0
if visible {
var err error
diagonal, err = r.sceneDiagonal()
if err != nil {
return err
}
}
world := u3dMatrix(r.camera.ToWorld)
if world == (u3dMatrix{}) {
world = u3dIdentity()
}
for i, section := range sections {
if err := r.ctx.Err(); err != nil {
return err
}
for _, vector := range [][3]float64{section.Center, section.AxisU, section.AxisV} {
for _, value := range vector {
if !finite(value) {
return fmt.Errorf("invalid U3D section coordinates")
}
}
}
for _, color := range []*[3]float64{&section.Color, section.Intersection} {
if color != nil {
for _, v := range color {
if !finite(v) || v < 0 || v > 1 {
return fmt.Errorf("invalid U3D section color")
}
}
}
}
u, v := u3dVector(section.AxisU).unit(), u3dVector(section.AxisV).unit()
if u == (u3dVector{}) || v == (u3dVector{}) || math.Abs(u.dot(v)) > 1e-12 {
return fmt.Errorf("invalid U3D section axes")
}
normal := u.cross(v).unit()
prepared := &r.sections[i]
for j := range 3 {
prepared.plane[j] = -(normal[0]*world[j*4] + normal[1]*world[j*4+1] + normal[2]*world[j*4+2])
}
prepared.plane[3] = normal.dot(u3dVector(section.Center).sub(u3dVector{world[12], world[13], world[14]}))
for _, coefficient := range prepared.plane {
if !finite(coefficient) {
return fmt.Errorf("invalid U3D section plane")
}
}
prepared.intersection, prepared.opacity = section.Intersection, section.Opacity
for j, signs := range [4][2]float64{{-1, -1}, {1, -1}, {1, 1}, {-1, 1}} {
point := u3dVector(section.Center)
for k := range point {
point[k] += diagonal / 2 * (signs[0]*u[k] + signs[1]*v[k])
}
prepared.corners[j] = u3dVertex{point: r.toCamera.point(point), color: [4]float64{section.Color[0], section.Color[1], section.Color[2], section.Opacity}}
for _, coordinate := range prepared.corners[j].point {
if !finite(coordinate) {
return fmt.Errorf("invalid U3D section corner")
}
}
}
}
return nil
}
// sceneDiagonal 计算静态场景世界包围盒的对角线,包含隐藏节点的几何
// 返回: float64 对角线长度, error 几何、数值或取消错误
func (r *Renderer) sceneDiagonal() (float64, error) {
minimum := u3dVector{math.Inf(1), math.Inf(1), math.Inf(1)}
maximum := u3dVector{math.Inf(-1), math.Inf(-1), math.Inf(-1)}
for _, instance := range r.scene {
if err := r.ctx.Err(); err != nil {
return 0, err
}
node := r.model.Nodes[instance.node]
mesh := r.nodeMesh(&node)
if node.Kind != "Model" || mesh == nil {
continue
}
for i := range len(mesh.Faces) + len(mesh.Lines) + len(mesh.Points) {
_, corners := mesh.primitive(i)
if i%256 == 0 {
if err := r.ctx.Err(); err != nil {
return 0, err
}
}
for _, corner := range corners {
if uint64(corner.Position) >= uint64(len(mesh.Positions)) {
return 0, fmt.Errorf("invalid U3D position index")
}
p := mesh.Positions[corner.Position]
world := instance.world.point(u3dVector{float64(p[0]), float64(p[1]), float64(p[2])})
for j, v := range world {
if !finite(v) {
return 0, fmt.Errorf("invalid U3D scene bounds")
}
minimum[j], maximum[j] = min(minimum[j], v), max(maximum[j], v)
}
}
}
}
if math.IsInf(minimum[0], 1) {
return 0, nil
}
delta := maximum.sub(minimum)
diagonal := math.Hypot(math.Hypot(delta[0], delta[1]), delta[2])
if !finite(diagonal) {
return 0, fmt.Errorf("invalid U3D scene diagonal")
}
return diagonal, nil
}
// sectionTriangle 用复用的凸多边形缓存应用全部剖切面,再进行视锥裁剪
// 入参: triangle 三角面, shader 着色器, pass 绘制轮次
// 返回: error 数值、资源或取消错误
func (r *Renderer) sectionTriangle(triangle [3]u3dVertex, shader U3DShader, pass U3DViewPass) error {
if len(r.sections) == 0 {
return r.clipTriangle(triangle, shader, pass)
}
copy(r.sectionScratch[0], triangle[:])
size, current := 3, 0
for _, section := range r.sections {
if err := r.ctx.Err(); err != nil {
return err
}
next, count := 1-current, 0
previous := r.sectionScratch[current][size-1]
previousDistance := u3dPlaneDistance(section.plane, previous.point)
for i, vertex := range r.sectionScratch[current][:size] {
if i%256 == 0 {
if err := r.ctx.Err(); err != nil {
return err
}
}
distance := u3dPlaneDistance(section.plane, vertex.point)
if !finite(distance) || !finite(previousDistance) {
return fmt.Errorf("invalid U3D section distance")
}
if (distance >= 0) != (previousDistance >= 0) {
var err error
count, err = u3dAppendSectionVertex(r.sectionScratch[next], count, u3dInterpolate(previous, vertex, u3dClipFraction(previousDistance, distance)))
if err != nil {
return err
}
}
if distance >= 0 {
var err error
count, err = u3dAppendSectionVertex(r.sectionScratch[next], count, vertex)
if err != nil {
return err
}
}
previous, previousDistance = vertex, distance
}
if count > 1 && r.sectionScratch[next][0].point == r.sectionScratch[next][count-1].point {
count--
}
size, current = count, next
if size < 3 {
break
}
}
if size < 2 {
return nil
}
for i := 1; i+1 < size; i++ {
if err := r.clipTriangle([3]u3dVertex{r.sectionScratch[current][0], r.sectionScratch[current][i], r.sectionScratch[current][i+1]}, shader, pass); err != nil {
return err
}
}
if r.depthOnly {
return nil
}
for i, section := range r.sections {
if section.intersection != nil {
if err := r.sectionIntersection(triangle, i); err != nil {
return err
}
}
}
return nil
}
// u3dAppendSectionVertex 合并连续重复交点,维持凸多边形缓存上限
// 入参: buffer 固定缓存, count 已有顶点数, vertex 新顶点
// 返回: int 顶点数, error 缓存越界错误
func u3dAppendSectionVertex(buffer []u3dVertex, count int, vertex u3dVertex) (int, error) {
if count > 0 && buffer[count-1].point == vertex.point {
return count, nil
}
if count >= len(buffer) {
return count, fmt.Errorf("invalid U3D section polygon")
}
buffer[count] = vertex
return count + 1, nil
}
// drawSectionPlanes 在最后一轮绘制平面方形,不把平面本身当作待剖切几何
// 返回: error 数值、资源或取消错误
func (r *Renderer) drawSectionPlanes() error {
for _, section := range r.sections {
if err := r.ctx.Err(); err != nil {
return err
}
if section.opacity == 0 {
continue
}
for i := 1; i < 3; i++ {
r.order++
if err := r.clipTriangle([3]u3dVertex{section.corners[0], section.corners[i], section.corners[i+1]}, U3DShader{Blend: 0x606}, U3DViewPass{}); err != nil {
return err
}
}
}
return nil
}