feat(渲染能力): 完善三维渲染、字体及版本保存
build.yaml / build (push) Successful in 2m39s

This commit is contained in:
xiaoqidun committed 2026-10-11 10:20:14 +08:00
1 parent 13dbacad15
commit f71000befd
45 files changed
+4383 -422

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
// 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 (
"context"
"fmt"
"math"
)
// U3DTrackState 指定动作资源、轨道和采样时间,Time与关键帧使用相同单位
// Loop将时间循环到首末帧之间,未启用时停留在端点,不自动解释动画修饰器的时间参数
type U3DTrackState struct {
Motion, Track string
Time float64
Loop bool
}
// Sample 按时间采样轨道,不修改原始帧或按帧数量分配临时数组
// 位移和缩放采用线性插值,旋转归一化后采用最短弧球面插值
// 允许帧乱序,同一时刻取文件中最后一帧;循环区间不包含末端,单帧轨道始终返回该帧
// 入参: ctx 取消上下文, time 与关键帧同单位的时间, loop 是否循环
// 返回: U3DKeyFrame 采样姿态, error 空轨道、数值或取消错误
func (track U3DMotionTrack) Sample(ctx context.Context, time float64, loop bool) (U3DKeyFrame, error) {
if ctx == nil || !finite(time) || len(track.Frames) == 0 {
return U3DKeyFrame{}, fmt.Errorf("invalid U3D motion sample")
}
first, last := 0, 0
for index, frame := range track.Frames {
if index%256 == 0 {
if err := ctx.Err(); err != nil {
return U3DKeyFrame{}, err
}
}
if !finite(float64(frame.Time)) {
return U3DKeyFrame{}, fmt.Errorf("invalid U3D key frame time")
}
if frame.Time <= track.Frames[first].Time {
first = index
}
if frame.Time >= track.Frames[last].Time {
last = index
}
}
minimum, maximum := float64(track.Frames[first].Time), float64(track.Frames[last].Time)
if loop && maximum > minimum {
time = math.Mod(time-minimum, maximum-minimum)
if time < 0 {
time += maximum - minimum
}
time += minimum
} else {
time = max(minimum, min(time, maximum))
}
lower, upper := first, last
for index, frame := range track.Frames {
if index%256 == 0 {
if err := ctx.Err(); err != nil {
return U3DKeyFrame{}, err
}
}
value := float64(frame.Time)
if value <= time && frame.Time >= track.Frames[lower].Time {
lower = index
}
if value > time && frame.Time <= track.Frames[upper].Time {
upper = index
}
}
a, b := track.Frames[lower], track.Frames[upper]
if float64(a.Time) == time {
b = a
}
qa, err := a.quaternion()
if err != nil {
return U3DKeyFrame{}, err
}
qb, err := b.quaternion()
if err != nil {
return U3DKeyFrame{}, err
}
fraction := float64(0)
if b.Time != a.Time {
fraction = (time - float64(a.Time)) / (float64(b.Time) - float64(a.Time))
}
result := U3DKeyFrame{Time: float32(time)}
for i := range result.Displacement {
result.Displacement[i] = float32(float64(a.Displacement[i])*(1-fraction) + float64(b.Displacement[i])*fraction)
result.Scale[i] = float32(float64(a.Scale[i])*(1-fraction) + float64(b.Scale[i])*fraction)
}
var dot float64
for i := range qa {
dot += qa[i] * qb[i]
}
if dot < 0 {
dot = -dot
for i := range qb {
qb[i] = -qb[i]
}
}
left, right := 1-fraction, fraction
if dot < .9995 {
angle := math.Acos(min(1, dot))
divisor := math.Sin(angle)
left, right = math.Sin(left*angle)/divisor, math.Sin(right*angle)/divisor
}
var rotation [4]float64
var length float64
for i := range rotation {
rotation[i] = left*qa[i] + right*qb[i]
length += rotation[i] * rotation[i]
}
length = math.Sqrt(length)
for i := range result.Rotation {
result.Rotation[i] = float32(rotation[i] / length)
}
if err := ctx.Err(); err != nil {
return U3DKeyFrame{}, err
}
return result, nil
}
// quaternion 校验姿态分量并归一化W、X、Y、Z旋转四元数
// 返回: [4]float64 单位四元数, error 非有限或零旋转错误
func (frame U3DKeyFrame) quaternion() ([4]float64, error) {
var rotation [4]float64
for _, values := range [][3]float32{frame.Displacement, frame.Scale} {
for _, value := range values {
if !finite(float64(value)) {
return rotation, fmt.Errorf("invalid U3D key frame transform")
}
}
}
var length float64
for i, value := range frame.Rotation {
rotation[i] = float64(value)
length += rotation[i] * rotation[i]
}
if !finite(length) || length == 0 {
return rotation, fmt.Errorf("invalid U3D key frame rotation")
}
length = math.Sqrt(length)
for i := range rotation {
rotation[i] /= length
}
return rotation, nil
}
// Matrix 将关键帧位移、旋转和缩放组合成相对父项的列式仿射矩阵,不使用Time
// 返回: [16]float64 先缩放、再旋转、后平移的变换, error 姿态数值错误
func (frame U3DKeyFrame) Matrix() ([16]float64, error) {
q, err := frame.quaternion()
if err != nil {
return [16]float64{}, err
}
w, x, y, z := q[0], q[1], q[2], q[3]
matrix := [16]float64{
1 - 2*(y*y+z*z), 2 * (x*y + w*z), 2 * (x*z - w*y), 0,
2 * (x*y - w*z), 1 - 2*(x*x+z*z), 2 * (y*z + w*x), 0,
2 * (x*z + w*y), 2 * (y*z - w*x), 1 - 2*(x*x+y*y), 0,
float64(frame.Displacement[0]), float64(frame.Displacement[1]), float64(frame.Displacement[2]), 1,
}
for axis, scale := range frame.Scale {
for component := range 3 {
matrix[axis*4+component] *= float64(scale)
}
}
return matrix, nil
}
// trackMatrix 按资源和轨道名称采样节点变换,不推测未命名轨道的绑定关系
// 入参: state 动作轨道状态
// 返回: [16]float64 节点变换, error 引用、数值或取消错误
func (r *u3dRender) trackMatrix(state U3DTrackState) ([16]float64, error) {
var track *U3DMotionTrack
if err := r.ctx.Err(); err != nil {
return [16]float64{}, err
}
if index, exists := r.motionIndex[state.Motion]; exists {
motion := &r.model.Motions[index]
for i := range motion.Tracks {
if i%256 == 0 {
if err := r.ctx.Err(); err != nil {
return [16]float64{}, err
}
}
if motion.Tracks[i].Name == state.Track {
if track != nil {
return [16]float64{}, fmt.Errorf("ambiguous U3D motion track %q", state.Track)
}
track = &motion.Tracks[i]
}
}
}
if track == nil {
return [16]float64{}, fmt.Errorf("missing U3D motion track %q in %q", state.Track, state.Motion)
}
frame, err := track.Sample(r.ctx, state.Time, state.Loop)
if err != nil {
return [16]float64{}, err
}
return frame.Matrix()
}