mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
This commit is contained in:
1 parent
13dbacad15
commit
f71000befd
45 files changed
+4383
-422
No files matched your search
@@ -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()
|
||||
}
|
||||
Reference in new issue
Block a user