Files
ofdgo/internal/base/threed/u3d_scene.go
T
2026-10-14 00:44:44 +08:00

342 lines
9.4 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"
)
// u3dVector 保存计算过程中的三维双精度坐标
type u3dVector [3]float64
// u3dMatrix 保存按列排列的仿射矩阵
type u3dMatrix [16]float64
// u3dInstance 保存一个父路径产生的世界实例,parent为负值时连接默认根
type u3dInstance struct {
node, parent int
world u3dMatrix
opacity float64
visibility int8
}
// u3dIdentity 创建单位矩阵
// 返回: u3dMatrix 单位矩阵
func u3dIdentity() u3dMatrix { return u3dMatrix{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1} }
// multiply 连接子节点到父节点的变换
// 入参: other 子变换
// 返回: u3dMatrix 组合变换
func (m u3dMatrix) multiply(other u3dMatrix) u3dMatrix {
var result u3dMatrix
for column := range 4 {
for row := range 4 {
for k := range 4 {
result[column*4+row] += m[k*4+row] * other[column*4+k]
}
}
}
return result
}
// valid 判断矩阵是否为有限仿射变换
// 返回: bool 是否可用于静态绘制
func (m u3dMatrix) valid() bool {
for _, v := range m {
if !finite(v) {
return false
}
}
return m[3] == 0 && m[7] == 0 && m[11] == 0 && m[15] == 1
}
// point 将点从局部坐标映射到目标坐标
// 入参: p 局部点
// 返回: u3dVector 目标点
func (m u3dMatrix) point(p u3dVector) u3dVector {
return u3dVector{m[0]*p[0] + m[4]*p[1] + m[8]*p[2] + m[12], m[1]*p[0] + m[5]*p[1] + m[9]*p[2] + m[13], m[2]*p[0] + m[6]*p[1] + m[10]*p[2] + m[14]}
}
// inverse 通过带主元消元计算仿射逆矩阵
// 返回: u3dMatrix 逆矩阵, bool 是否存在有限逆矩阵
func (m u3dMatrix) inverse() (u3dMatrix, bool) {
if !m.valid() {
return u3dMatrix{}, false
}
var rows [3][6]float64
for i := range 3 {
for j := range 3 {
rows[i][j] = m[j*4+i]
}
rows[i][i+3] = 1
}
for column := range 3 {
pivot := column
for row := column + 1; row < 3; row++ {
if math.Abs(rows[row][column]) > math.Abs(rows[pivot][column]) {
pivot = row
}
}
if rows[pivot][column] == 0 {
return u3dMatrix{}, false
}
rows[pivot], rows[column] = rows[column], rows[pivot]
divisor := rows[column][column]
for j := range 6 {
rows[column][j] /= divisor
}
for row := range 3 {
if row != column {
factor := rows[row][column]
for j := range 6 {
rows[row][j] -= factor * rows[column][j]
}
}
}
}
result := u3dIdentity()
for row := range 3 {
for col := range 3 {
result[col*4+row] = rows[row][col+3]
}
result[12+row] = -(result[row]*m[12] + result[4+row]*m[13] + result[8+row]*m[14])
}
return result, result.valid()
}
// normal 以逆矩阵的转置变换并归一化法线
// 入参: n 局部法线,接收者为局部变换的逆矩阵
// 返回: u3dVector 目标法线
func (m u3dMatrix) normal(n u3dVector) u3dVector {
return (u3dVector{m[0]*n[0] + m[1]*n[1] + m[2]*n[2], m[4]*n[0] + m[5]*n[1] + m[6]*n[2], m[8]*n[0] + m[9]*n[1] + m[10]*n[2]}).unit()
}
// unit 归一化向量,零向量保持为零
// 返回: u3dVector 单位向量
func (v u3dVector) unit() u3dVector {
scale := max(math.Abs(v[0]), math.Abs(v[1]), math.Abs(v[2]))
if scale == 0 || !finite(scale) {
return u3dVector{}
}
for i := range v {
v[i] /= scale
}
length := math.Sqrt(v.dot(v))
return u3dVector{v[0] / length, v[1] / length, v[2] / length}
}
// sub 求向量差
// 入参: other 被减向量
// 返回: u3dVector 差向量
func (v u3dVector) sub(other u3dVector) u3dVector {
return u3dVector{v[0] - other[0], v[1] - other[1], v[2] - other[2]}
}
// dot 求向量内积
// 入参: other 另一向量
// 返回: float64 内积
func (v u3dVector) dot(other u3dVector) float64 { return v[0]*other[0] + v[1]*other[1] + v[2]*other[2] }
// cross 求向量叉积
// 入参: other 另一向量
// 返回: u3dVector 叉积
func (v u3dVector) cross(other u3dVector) u3dVector {
return u3dVector{v[1]*other[2] - v[2]*other[1], v[2]*other[0] - v[0]*other[2], v[0]*other[1] - v[1]*other[0]}
}
// u3dTriangleNormal 选取夹角更稳定的顶点计算法线,避免长瘦三角面的相消
// 入参: points 按绕序排列的顶点
// 返回: u3dVector 单位法线,退化三角面返回零向量
func u3dTriangleNormal(points [3]u3dVector) u3dVector {
var normal u3dVector
score := 0.0
for i := range 3 {
a := points[(i+1)%3].sub(points[i]).unit()
b := points[(i+2)%3].sub(points[i]).unit()
candidate := a.cross(b)
if length := max(math.Abs(candidate[0]), math.Abs(candidate[1]), math.Abs(candidate[2])); length > score {
normal, score = candidate, length
}
}
return normal.unit()
}
// sceneGraph 校验父子关系并确定拓扑顺序,只有默认根的后代接入场景
// 返回: []int 拓扑顺序, map[string]int 节点索引, error 结构、预算或取消错误
func (r *Renderer) sceneGraph() ([]int, map[string]int, error) {
nodes := r.model.Nodes
if err := r.Reserve(uint64(len(nodes)), 161); err != nil {
return nil, nil, err
}
index := make(map[string]int, len(nodes))
for i, node := range nodes {
if i%256 == 0 {
if err := r.ctx.Err(); err != nil {
return nil, nil, err
}
}
if _, exists := index[node.Name]; exists || node.Name == "" {
return nil, nil, fmt.Errorf("invalid U3D render node name")
}
switch node.Kind {
case "Group", "Model", "Light", "View":
default:
return nil, nil, fmt.Errorf("unsupported U3D render node kind")
}
index[node.Name] = i
}
counts := make([]int, len(nodes))
children := make([][]int, len(nodes))
r.byNode = make([][]int, len(nodes))
r.connected = make([]bool, len(nodes))
for i, node := range nodes {
if err := r.Reserve(uint64(len(node.Parents)), 16); err != nil {
return nil, nil, err
}
for k, parent := range node.Parents {
if k%256 == 0 {
if err := r.ctx.Err(); err != nil {
return nil, nil, err
}
}
if j, ok := index[parent.Name]; ok {
counts[i]++
children[j] = append(children[j], i)
}
}
}
queue := make([]int, 0, len(nodes))
for i, n := range counts {
if i%256 == 0 {
if err := r.ctx.Err(); err != nil {
return nil, nil, err
}
}
if n == 0 {
queue = append(queue, i)
}
}
for cursor := 0; cursor < len(queue); cursor++ {
if err := r.ctx.Err(); err != nil {
return nil, nil, err
}
i := queue[cursor]
for j, parent := range nodes[i].Parents {
if j%256 == 0 {
if err := r.ctx.Err(); err != nil {
return nil, nil, err
}
}
if parent.Name == "" {
r.connected[i] = true
break
}
if p, exists := index[parent.Name]; exists && r.connected[p] {
r.connected[i] = true
break
}
}
for childIndex, child := range children[i] {
if childIndex%256 == 0 {
if err := r.ctx.Err(); err != nil {
return nil, nil, err
}
}
counts[child]--
if counts[child] == 0 {
queue = append(queue, child)
}
}
}
if len(queue) != len(nodes) {
return nil, nil, fmt.Errorf("cyclic U3D node parents")
}
return queue, index, nil
}
// instances 按拓扑顺序展开接入场景的父路径,不将未解析的父节点视为默认根
// 入参: order 拓扑顺序, index 节点索引, limit 最大实例数
// 返回: error 变换、预算、实例上限或取消错误
func (r *Renderer) instances(order []int, index map[string]int, limit int) error {
nodes := r.model.Nodes
for _, i := range order {
if err := r.ctx.Err(); err != nil {
return err
}
if !r.connected[i] {
continue
}
state := r.states[nodes[i].Name]
for parentIndex, parent := range nodes[i].Parents {
if parentIndex%256 == 0 {
if err := r.ctx.Err(); err != nil {
return err
}
}
parents := []int{-1}
if parent.Name != "" {
j, exists := index[parent.Name]
if !exists || !r.connected[j] {
continue
}
parents = r.byNode[j]
}
var local u3dMatrix
for k, v := range parent.Transform {
local[k] = float64(v)
}
if animation, exists := r.animated[nodes[i].Name]; exists {
local = local.multiply(animation.matrix)
}
if state.Matrix != nil {
local = *state.Matrix
}
if !local.valid() {
return fmt.Errorf("unsupported U3D non-affine node transform")
}
for _, p := range parents {
if len(r.scene) >= limit {
return fmt.Errorf("U3D instance count exceeds limit")
}
if err := r.Reserve(1, 384); err != nil {
return err
}
world := local
opacity, visibility := -1.0, int8(-1)
if p >= 0 {
world = r.scene[p].world.multiply(local)
opacity, visibility = r.scene[p].opacity, r.scene[p].visibility
}
if state.Opacity != nil {
opacity = *state.Opacity
}
if state.Visible != nil {
visibility = 0
if *state.Visible {
visibility = 1
}
}
if !world.valid() {
return fmt.Errorf("invalid U3D world transform")
}
r.byNode[i] = append(r.byNode[i], len(r.scene))
r.scene = append(r.scene, u3dInstance{node: i, parent: p, world: world, opacity: opacity, visibility: visibility})
}
}
}
return r.ctx.Err()
}