mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:44:23 +08:00
177 lines
5.3 KiB
Go
177 lines
5.3 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 (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// U3DAnimation 保存ECMA-363第9.7.3节的动画修饰器,Index为链内位置
|
|
// Attributes的1、2、4、8分别表示启动、锁定根骨骼、单轨播放和动作间平滑过渡
|
|
// TimeScale为整体时间倍率,BlendTime以毫秒计;Motions为空时使用无轨道的默认动作
|
|
type U3DAnimation struct {
|
|
Index, Attributes uint32
|
|
TimeScale, BlendTime float32
|
|
Motions []U3DAnimationMotion
|
|
}
|
|
|
|
// U3DAnimationMotion 保存动画中的动作引用,空名称引用默认动作
|
|
// Attributes的1、2分别表示循环和同步结束,TimeOffset以毫秒计,TimeScale为动作时间倍率
|
|
type U3DAnimationMotion struct {
|
|
Name string
|
|
Attributes uint32
|
|
TimeOffset, TimeScale float32
|
|
}
|
|
|
|
// animation 读取动画参数及有序动作引用,不在解码时调度或求值
|
|
// 入参: r 字段读取器, name 所属链名, chain 链类型, index 链内位置
|
|
// 返回: error 结构、参数、预算或取消错误
|
|
func (d *u3dDecoder) animation(r *u3dValues, name string, chain int, index uint32) error {
|
|
animation := U3DAnimation{Index: index, Attributes: r.u32(), TimeScale: r.f32()}
|
|
count := r.u32()
|
|
if r.err != nil {
|
|
return r.err
|
|
}
|
|
if len(r.data)-r.pos < 4 || uint64(count) > uint64(len(r.data)-r.pos-4)/14 {
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
if err := d.reserve(uint64(count), 32); err != nil {
|
|
return err
|
|
}
|
|
if count != 0 {
|
|
animation.Motions = make([]U3DAnimationMotion, int(count))
|
|
}
|
|
for i := range animation.Motions {
|
|
if i%256 == 0 {
|
|
if err := d.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
motion := &animation.Motions[i]
|
|
motion.Name, motion.Attributes = r.text(), r.u32()
|
|
motion.TimeOffset, motion.TimeScale = r.f32(), r.f32()
|
|
if r.err != nil {
|
|
return r.err
|
|
}
|
|
}
|
|
animation.BlendTime = r.f32()
|
|
if err := r.done(); err != nil {
|
|
return err
|
|
}
|
|
if err := animation.validate(d.ctx); err != nil {
|
|
return err
|
|
}
|
|
if chain == 1 {
|
|
mesh := &d.meshes[name].mesh
|
|
mesh.Animations = append(mesh.Animations, animation)
|
|
} else {
|
|
node := &d.model.Nodes[d.nodes[name]]
|
|
node.Animations = append(node.Animations, animation)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validate 校验动画属性及有限数值,保留零倍率与反向播放倍率
|
|
// 入参: ctx 取消上下文
|
|
// 返回: error 参数或取消错误
|
|
func (animation U3DAnimation) validate(ctx context.Context) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if animation.Index == 0 || animation.Attributes & ^uint32(15) != 0 || !finite(float64(animation.TimeScale)) || !finite(float64(animation.BlendTime)) || animation.BlendTime < 0 {
|
|
return fmt.Errorf("invalid U3D animation parameters")
|
|
}
|
|
for i, motion := range animation.Motions {
|
|
if i%256 == 0 {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if motion.Attributes & ^uint32(3) != 0 || !finite(float64(motion.TimeOffset)) || !finite(float64(motion.TimeScale)) {
|
|
return fmt.Errorf("invalid U3D animation motion parameters")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkAnimations 校验全部动作引用,仅对接入场景的修饰器检查求值能力
|
|
// 返回: error 引用、能力或取消错误
|
|
func (r *u3dRender) checkAnimations() error {
|
|
check := func(animations []U3DAnimation, evaluate bool) error {
|
|
for _, animation := range animations {
|
|
if err := animation.validate(r.ctx); err != nil {
|
|
return err
|
|
}
|
|
for i, motion := range animation.Motions {
|
|
if i%256 == 0 {
|
|
if err := r.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if motion.Name == "" {
|
|
continue
|
|
}
|
|
index, exists := r.motionIndex[motion.Name]
|
|
if !exists {
|
|
return fmt.Errorf("missing U3D animation motion %q", motion.Name)
|
|
}
|
|
if evaluate && len(animation.Motions) > 1 && len(r.model.Motions[index].Tracks) != 0 {
|
|
return fmt.Errorf("unsupported U3D animation evaluation for motion %q", motion.Name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
var active []bool
|
|
if r.connected != nil {
|
|
if err := r.reserve(uint64(len(r.model.Meshes)), 1); err != nil {
|
|
return err
|
|
}
|
|
defer func() { r.remaining += uint64(len(active)) }()
|
|
active = make([]bool, len(r.model.Meshes))
|
|
for i, node := range r.model.Nodes {
|
|
if i%256 == 0 {
|
|
if err := r.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if r.connected[i] && node.Kind == "Model" {
|
|
if mesh, exists := r.meshes[node.Resource]; exists {
|
|
active[mesh] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for i := range r.model.Meshes {
|
|
if err := r.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if err := check(r.model.Meshes[i].Animations, active == nil || active[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for i := range r.model.Nodes {
|
|
if err := r.ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if err := check(r.model.Nodes[i].Animations, r.connected == nil || r.connected[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return r.ctx.Err()
|
|
}
|