Skip to content

Commit fe676e6

Browse files
committed
Refactor skeletal animation
1 parent d0ee042 commit fe676e6

File tree

4 files changed

+130
-21
lines changed

4 files changed

+130
-21
lines changed

Sources/GateEngine/ECS/3D Specific/Rig/Rig3DSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public final class Rig3DSystem: System {
4949
{
5050
if let animation = component.activeAnimation, animation.isReady {
5151
component.update(
52-
deltaTime: deltaTime + component.deltaAccumulator,
52+
deltaTime: deltaTime,
5353
objectScale: entity.component(ofType: Transform3Component.self)?.scale ?? .one
5454
)
5555
if component.playbackState != .pause {

Sources/GateEngine/Resources/Import & Export/Importers/GLTransmissionFormat.swift

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,90 @@ private class GLTF: Decodable {
450450
}
451451
}
452452
}
453+
454+
func animationValues<T: BinaryFloatingPoint>(forAccessor accessorIndex: Int) async -> [T]? {
455+
let accessor = accessors[accessorIndex]
456+
let bufferView = bufferViews[accessor.bufferView]
457+
let count = accessor.count * accessor.primitiveCount
458+
459+
return buffer(at: bufferView.buffer)?.withUnsafeBytes {
460+
switch accessor.componentType {
461+
case .uint8:
462+
typealias Scalar = UInt8
463+
let size = MemoryLayout<Scalar>.size
464+
var array: [Scalar] = []
465+
array.reserveCapacity(count)
466+
for index in 0 ..< count {
467+
let offset = (index * size) + bufferView.byteOffset
468+
array.append(
469+
Scalar(littleEndian: $0.load(fromByteOffset: offset, as: Scalar.self))
470+
)
471+
}
472+
return array.map({ T($0) / 255.0 })
473+
case .uint16:
474+
typealias Scalar = UInt16
475+
let size = MemoryLayout<Scalar>.size
476+
var array: [Scalar] = []
477+
array.reserveCapacity(count)
478+
for index in 0 ..< count {
479+
let offset = (index * size) + bufferView.byteOffset
480+
array.append(
481+
Scalar(littleEndian: $0.load(fromByteOffset: offset, as: Scalar.self))
482+
)
483+
}
484+
return array.map({ T($0) / 65535.0 })
485+
case .uint32:
486+
typealias Scalar = UInt32
487+
let size = MemoryLayout<Scalar>.size
488+
var array: [Scalar] = []
489+
array.reserveCapacity(count)
490+
for index in 0 ..< count {
491+
let offset = (index * size) + bufferView.byteOffset
492+
array.append(
493+
Scalar(littleEndian: $0.load(fromByteOffset: offset, as: Scalar.self))
494+
)
495+
}
496+
return array.map({ T($0) })
497+
case .int8:
498+
typealias Scalar = Int8
499+
let size = MemoryLayout<Scalar>.size
500+
var array: [Scalar] = []
501+
array.reserveCapacity(count)
502+
for index in 0 ..< count {
503+
let offset = (index * size) + bufferView.byteOffset
504+
array.append(
505+
Scalar(littleEndian: $0.load(fromByteOffset: offset, as: Scalar.self))
506+
)
507+
}
508+
return array.map({ .maximum(T($0) / 127.0, -1.0) })
509+
case .int16:
510+
typealias Scalar = Int16
511+
let size = MemoryLayout<Scalar>.size
512+
var array: [Scalar] = []
513+
array.reserveCapacity(count)
514+
for index in 0 ..< count {
515+
let offset = (index * size) + bufferView.byteOffset
516+
array.append(
517+
Scalar(littleEndian: $0.load(fromByteOffset: offset, as: Scalar.self))
518+
)
519+
}
520+
return array.map({ .maximum(T($0) / 32767.0, -1.0) })
521+
case .float32:
522+
typealias Scalar = Float
523+
let size = MemoryLayout<Scalar>.size
524+
var array: [Scalar] = []
525+
array.reserveCapacity(count)
526+
for index in 0 ..< count {
527+
let offset = (index * size) + bufferView.byteOffset
528+
let pattern = UInt32(
529+
littleEndian: $0.load(fromByteOffset: offset, as: UInt32.self)
530+
)
531+
array.append(Scalar(bitPattern: pattern))
532+
}
533+
return array.map({ T($0) })
534+
}
535+
}
536+
}
453537
}
454538

455539
// Helpers for exploring the gltf document
@@ -961,10 +1045,10 @@ extension GLTransmissionFormat: SkeletalAnimationImporter {
9611045

9621046
let sampler = animation.samplers[channel.sampler]
9631047

964-
guard let times: [Float] = await gltf.values(forAccessor: sampler.input) else {
1048+
guard let times: [Float] = await gltf.animationValues(forAccessor: sampler.input) else {
9651049
continue
9661050
}
967-
guard let values: [Float] = await gltf.values(forAccessor: sampler.output) else {
1051+
guard let values: [Float] = await gltf.animationValues(forAccessor: sampler.output) else {
9681052
continue
9691053
}
9701054

@@ -978,8 +1062,10 @@ extension GLTransmissionFormat: SkeletalAnimationImporter {
9781062
switch sampler.interpolation {
9791063
case .step:
9801064
jointAnimation.positionOutput.interpolation = .step
981-
default:
1065+
case .linear:
9821066
jointAnimation.positionOutput.interpolation = .linear
1067+
default:
1068+
throw GateEngineError.failedToDecode("Unhandled animation interpolation: \(sampler.interpolation)")
9831069
}
9841070
case .rotation:
9851071
jointAnimation.rotationOutput.times = times
@@ -995,8 +1081,11 @@ extension GLTransmissionFormat: SkeletalAnimationImporter {
9951081
switch sampler.interpolation {
9961082
case .step:
9971083
jointAnimation.rotationOutput.interpolation = .step
998-
default:
1084+
case .linear:
9991085
jointAnimation.rotationOutput.interpolation = .linear
1086+
default:
1087+
throw GateEngineError.failedToDecode("Unhandled animation interpolation: \(sampler.interpolation)")
1088+
10001089
}
10011090
case .scale:
10021091
jointAnimation.scaleOutput.times = times
@@ -1010,8 +1099,10 @@ extension GLTransmissionFormat: SkeletalAnimationImporter {
10101099
switch sampler.interpolation {
10111100
case .step:
10121101
jointAnimation.scaleOutput.interpolation = .step
1013-
default:
1102+
case .linear:
10141103
jointAnimation.scaleOutput.interpolation = .linear
1104+
default:
1105+
throw GateEngineError.failedToDecode("Unhandled animation interpolation: \(sampler.interpolation)")
10151106
}
10161107
case .weight:
10171108
break
@@ -1031,6 +1122,13 @@ extension GLTransmissionFormat: SkeletalAnimationImporter {
10311122
if duration.isFinite == false {
10321123
duration = 0
10331124
}
1125+
1126+
// Slide animations to start at time zero
1127+
for animationKey in animations.keys {
1128+
animations[animationKey]!.positionOutput.times = animations[animationKey]!.positionOutput.times.map({$0 - timeMin})
1129+
animations[animationKey]!.rotationOutput.times = animations[animationKey]!.rotationOutput.times.map({$0 - timeMin})
1130+
animations[animationKey]!.scaleOutput.times = animations[animationKey]!.scaleOutput.times.map({$0 - timeMin})
1131+
}
10341132

10351133
return RawSkeletalAnimation(name: animation.name, duration: duration, animations: animations)
10361134
}
@@ -1070,8 +1168,10 @@ extension GLTransmissionFormat: ObjectAnimation3DImporter {
10701168
switch sampler.interpolation {
10711169
case .step:
10721170
objectAnimation.positionOutput.interpolation = .step
1073-
default:
1171+
case .linear:
10741172
objectAnimation.positionOutput.interpolation = .linear
1173+
default:
1174+
throw GateEngineError.failedToDecode("Unhandled animation interpolation: \(sampler.interpolation)")
10751175
}
10761176
case .rotation:
10771177
objectAnimation.rotationOutput.times = times
@@ -1087,8 +1187,10 @@ extension GLTransmissionFormat: ObjectAnimation3DImporter {
10871187
switch sampler.interpolation {
10881188
case .step:
10891189
objectAnimation.rotationOutput.interpolation = .step
1090-
default:
1190+
case .linear:
10911191
objectAnimation.rotationOutput.interpolation = .linear
1192+
default:
1193+
throw GateEngineError.failedToDecode("Unhandled animation interpolation: \(sampler.interpolation)")
10921194
}
10931195
case .scale:
10941196
objectAnimation.scaleOutput.times = times
@@ -1102,8 +1204,10 @@ extension GLTransmissionFormat: ObjectAnimation3DImporter {
11021204
switch sampler.interpolation {
11031205
case .step:
11041206
objectAnimation.scaleOutput.interpolation = .step
1105-
default:
1207+
case .linear:
11061208
objectAnimation.scaleOutput.interpolation = .linear
1209+
default:
1210+
throw GateEngineError.failedToDecode("Unhandled animation interpolation: \(sampler.interpolation)")
11071211
}
11081212
case .weight:
11091213
break

Sources/GateEngine/Resources/Skinning/SkeletalAnimation.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ extension SkeletalAnimation {
122122
switch interpolation {
123123
case .linear:
124124
let plus1 = currentIndex + 1
125-
let nextIndex: Int = times.indices.contains(plus1) ? plus1 : times.endIndex - 1
125+
guard times.indices.contains(plus1) else {fallthrough}
126+
let nextIndex: Int = plus1
126127

127128
let time1 = times[currentIndex]
128129
let time2 = times[nextIndex]
@@ -131,7 +132,7 @@ extension SkeletalAnimation {
131132
let position2 = positions[nextIndex]
132133

133134
let currentTime: Float = time - time1
134-
let currentDuration: Float = time2 - time1
135+
let currentDuration: Float = time1.distance(to: time2)
135136
let factor: Float = currentTime / currentDuration
136137

137138
guard factor.isFinite else { return position1 }
@@ -159,7 +160,8 @@ extension SkeletalAnimation {
159160
switch interpolation {
160161
case .linear:
161162
let plus1 = currentIndex + 1
162-
let nextIndex: Int = times.indices.contains(plus1) ? plus1 : times.endIndex - 1
163+
guard times.indices.contains(plus1) else {fallthrough}
164+
let nextIndex: Int = plus1
163165

164166
let time1 = times[currentIndex]
165167
let time2 = times[nextIndex]
@@ -168,7 +170,7 @@ extension SkeletalAnimation {
168170
let rotation2 = rotations[nextIndex]
169171

170172
let currentTime: Float = time - time1
171-
let currentDuration: Float = time2 - time1
173+
let currentDuration: Float = time1.distance(to: time2)
172174
let factor: Float = currentTime / currentDuration
173175

174176
guard factor.isFinite else { return rotation1 }
@@ -193,7 +195,8 @@ extension SkeletalAnimation {
193195
switch interpolation {
194196
case .linear:
195197
let plus1 = currentIndex + 1
196-
let nextIndex: Int = times.indices.contains(plus1) ? plus1 : times.endIndex - 1
198+
guard times.indices.contains(plus1) else {fallthrough}
199+
let nextIndex: Int = plus1
197200

198201
let time1 = times[currentIndex]
199202
let time2 = times[nextIndex]
@@ -202,7 +205,7 @@ extension SkeletalAnimation {
202205
let scale2 = scales[nextIndex]
203206

204207
let currentTime: Float = time - time1
205-
let currentDuration: Float = time2 - time1
208+
let currentDuration: Float = time1.distance(to: time2)
206209
let factor: Float = currentTime / currentDuration
207210

208211
guard factor.isFinite else { return scale1 }

Sources/GateEngine/Resources/Skinning/Skeleton.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,15 @@ extension Skeleton {
201201
skipJoints: [Skeleton.SkipJoint],
202202
interpolateProgress: Float
203203
) {
204-
let interpolate = interpolateProgress < 1
205-
206204
let time: Float
207-
if repeating {
208-
time = _time.truncatingRemainder(dividingBy: duration)
205+
if _time < 0 {
206+
time = 0
209207
}else if _time > duration {
210-
time = duration
208+
if repeating {
209+
time = _time.truncatingRemainder(dividingBy: duration)
210+
}else{
211+
time = duration
212+
}
211213
}else{
212214
time = _time
213215
}
@@ -255,7 +257,7 @@ extension Skeleton {
255257
}
256258

257259
if skip == false {
258-
if interpolate {
260+
if interpolateProgress < 1 {
259261
joint.localTransform.interpolate(to: transform, .linear(interpolateProgress))
260262
} else {
261263
joint.localTransform = transform

0 commit comments

Comments
 (0)