Files
ofdgo/ofdgo_u3d_mapping.go
T
2026-10-11 10:20:14 +08:00

142 lines
4.1 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 (
"fmt"
"math"
)
// u3dTextureInverse 计算包裹变换的逆矩阵,保留非仿射齐次分量
// 入参: matrix 按列排列的包裹变换
// 返回: [16]float64 逆矩阵, error 奇异矩阵或非有限结果
func u3dTextureInverse(matrix [16]float32) ([16]float64, error) {
var rows [4][8]float64
for row := range 4 {
for col := range 4 {
rows[row][col] = float64(matrix[col*4+row])
if !finite(rows[row][col]) {
return [16]float64{}, fmt.Errorf("invalid U3D texture wrap transform")
}
}
rows[row][row+4] = 1
}
for col := range 4 {
pivot := col
for row := col + 1; row < 4; row++ {
if math.Abs(rows[row][col]) > math.Abs(rows[pivot][col]) {
pivot = row
}
}
if rows[pivot][col] == 0 {
return [16]float64{}, fmt.Errorf("singular U3D texture wrap transform")
}
rows[col], rows[pivot] = rows[pivot], rows[col]
divisor := rows[col][col]
for i := range 8 {
rows[col][i] /= divisor
}
for row := range 4 {
if row == col {
continue
}
factor := rows[row][col]
for i := range 8 {
rows[row][i] -= factor * rows[col][i]
}
}
}
var result [16]float64
for row := range 4 {
for col := range 4 {
value := rows[row][col+4]
if !finite(value) {
return [16]float64{}, fmt.Errorf("invalid U3D inverse texture wrap transform")
}
result[col*4+row] = value
}
}
return result, nil
}
// generated 将插值后的位置或法线映射到纹理,再应用纹理变换
// 经线未定义时取0,球心纬度取1/2,反射方向奇点取图像中心
// 入参: channel 通道索引, input 模型位置或相机法线, position 相机空间位置
// 返回: [3]float64 齐次纹理坐标, error 非有限值或无效投影
func (s *u3dTextureSet) generated(channel int, input [3]float64, position u3dVector) ([3]float64, error) {
layer := s.layers[channel]
for _, value := range input {
if !finite(value) {
return [3]float64{}, fmt.Errorf("invalid U3D texture mapping input")
}
}
uv := [4]float64{0, 0, 0, 1}
if layer.Mode == 4 {
incident := u3dVector{0, 0, 1}
if s.perspective {
incident = position.unit()
}
normal := u3dVector(input).unit()
factor := 2 * normal.dot(incident)
reflected := incident.sub(u3dVector{factor * normal[0], factor * normal[1], factor * normal[2]})
denominator := 2 * math.Hypot(math.Hypot(reflected[0], reflected[1]), 1-reflected[2])
uv[0], uv[1] = .5, .5
if denominator != 0 {
uv[0] += reflected[0] / denominator
uv[1] -= reflected[1] / denominator
}
} else {
var point [4]float64
for row := range 4 {
point[row] = s.wrap[channel][12+row]
for col, value := range input {
point[row] += s.wrap[channel][col*4+row] * value
}
}
for i := range 3 {
point[i] /= point[3]
if !finite(point[i]) {
return [3]float64{}, fmt.Errorf("invalid U3D texture wrap projection")
}
}
switch layer.Mode {
case 1:
uv[0], uv[1] = point[0], point[1]
case 2, 3:
if point[0] != 0 || point[1] != 0 {
uv[0] = math.Atan2(point[1], point[0]) / (2 * math.Pi)
if uv[0] < 0 {
uv[0]++
}
}
uv[1] = point[2]
if layer.Mode == 3 {
direction := (u3dVector{point[0], point[1], point[2]}).unit()
uv[1] = math.Acos(min(1, max(-1, direction[2]))) / math.Pi
}
}
}
var result [3]float64
for component, row := range [3]int{0, 1, 3} {
for col, value := range uv {
result[component] += float64(layer.Transform[col*4+row]) * value
}
if !finite(result[component]) {
return [3]float64{}, fmt.Errorf("invalid U3D generated texture coordinate")
}
}
return result, nil
}