Skip to content

Commit b470ec3

Browse files
hannojgmrousavy
andauthored
Feat/animation api (#20)
Co-authored-by: Marc Rousavy <[email protected]>
1 parent 12e9a70 commit b470ec3

File tree

12 files changed

+288
-25
lines changed

12 files changed

+288
-25
lines changed

package/android/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ add_library(
3737
../cpp/core/ViewWrapper.cpp
3838
../cpp/core/SwapChainWrapper.cpp
3939
../cpp/core/FilamentAssetWrapper.cpp
40+
../cpp/core/AnimatorWrapper.cpp
4041

4142
# Filament Utils
4243
../cpp/core/utils/EntityWrapper.cpp

package/cpp/core/AnimatorWrapper.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// Created by Hanno Gödecke on 29.02.24.
3+
//
4+
5+
#include "AnimatorWrapper.h"
6+
7+
namespace margelo {
8+
void AnimatorWrapper::loadHybridMethods() {
9+
registerHybridMethod("applyAnimation", &AnimatorWrapper::applyAnimation, this);
10+
registerHybridMethod("updateBoneMatrices", &AnimatorWrapper::updateBoneMatrices, this);
11+
registerHybridMethod("applyCrossFade", &AnimatorWrapper::applyCrossFade, this);
12+
registerHybridMethod("resetBoneMatrices", &AnimatorWrapper::resetBoneMatrices, this);
13+
registerHybridMethod("getAnimationCount", &AnimatorWrapper::getAnimationCount, this);
14+
registerHybridMethod("getAnimationDuration", &AnimatorWrapper::getAnimationDuration, this);
15+
registerHybridMethod("getAnimationName", &AnimatorWrapper::getAnimationName, this);
16+
}
17+
18+
Animator* AnimatorWrapper::getAnimator() {
19+
FilamentInstance* instance = _asset->getInstance();
20+
if (instance == nullptr) {
21+
[[unlikely]];
22+
throw std::runtime_error("Filament Asset does not contain a valid FilamentInstance!");
23+
}
24+
return instance->getAnimator();
25+
}
26+
27+
void AnimatorWrapper::applyAnimation(int animationIndex, double time) {
28+
Animator* animator = getAnimator();
29+
animator->applyAnimation(animationIndex, time);
30+
}
31+
32+
void AnimatorWrapper::updateBoneMatrices() {
33+
Animator* animator = getAnimator();
34+
animator->updateBoneMatrices();
35+
}
36+
37+
void AnimatorWrapper::applyCrossFade(int previousAnimationIndex, double previousAnimationTime, double alpha) {
38+
Animator* animator = getAnimator();
39+
animator->applyCrossFade(previousAnimationIndex, previousAnimationTime, alpha);
40+
}
41+
42+
void AnimatorWrapper::resetBoneMatrices() {
43+
Animator* animator = getAnimator();
44+
animator->resetBoneMatrices();
45+
}
46+
47+
int AnimatorWrapper::getAnimationCount() {
48+
Animator* animator = getAnimator();
49+
return animator->getAnimationCount();
50+
}
51+
52+
double AnimatorWrapper::getAnimationDuration(int animationIndex) {
53+
Animator* animator = getAnimator();
54+
return animator->getAnimationDuration(animationIndex);
55+
}
56+
57+
std::string AnimatorWrapper::getAnimationName(int animationIndex) {
58+
Animator* animator = getAnimator();
59+
return animator->getAnimationName(animationIndex);
60+
}
61+
} // namespace margelo

package/cpp/core/AnimatorWrapper.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Created by Hanno Gödecke on 29.02.24.
3+
//
4+
5+
#pragma once
6+
7+
#include "jsi/HybridObject.h"
8+
#include <gltfio/Animator.h>
9+
10+
namespace margelo {
11+
12+
using namespace filament::gltfio;
13+
14+
class AnimatorWrapper : public HybridObject {
15+
public:
16+
explicit AnimatorWrapper(const std::shared_ptr<FilamentAsset>& asset) : HybridObject("AnimatorWrapper"), _asset(std::move(asset)) {}
17+
18+
void loadHybridMethods() override;
19+
20+
private:
21+
void applyAnimation(int animationIndex, double time);
22+
void applyCrossFade(int previousAnimationIndex, double previousAnimationTime, double alpha);
23+
void updateBoneMatrices();
24+
void resetBoneMatrices();
25+
int getAnimationCount();
26+
double getAnimationDuration(int animationIndex);
27+
std::string getAnimationName(int animationIndex);
28+
29+
Animator* getAnimator();
30+
31+
private:
32+
std::shared_ptr<FilamentAsset> _asset;
33+
};
34+
35+
} // namespace margelo

package/cpp/core/EngineWrapper.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ void EngineWrapper::destroySurface() {
179179
_swapChain = nullptr;
180180
}
181181

182-
void EngineWrapper::setRenderCallback(std::function<void(std::shared_ptr<EngineWrapper>)> callback) {
182+
void EngineWrapper::setRenderCallback(std::function<void(double, double, double)> callback) {
183183
_renderCallback = std::move(callback);
184184
}
185185

@@ -209,7 +209,8 @@ void EngineWrapper::renderFrame(double timestamp) {
209209

210210
if (_renderCallback) {
211211
// Call JS callback with scene information
212-
_renderCallback(nullptr);
212+
double passedSeconds = (timestamp - _startTime) / 1e9;
213+
_renderCallback(timestamp, _startTime, passedSeconds);
213214
}
214215

215216
std::shared_ptr<Renderer> renderer = _renderer->getRenderer();

package/cpp/core/EngineWrapper.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class EngineWrapper : public HybridObject {
6060
void setSurface(std::shared_ptr<Surface> surface);
6161
void destroySurface();
6262
void surfaceSizeChanged(int width, int height);
63-
void setRenderCallback(std::function<void(std::shared_ptr<EngineWrapper>)> callback);
63+
void setRenderCallback(std::function<void(double, double, double)> callback);
6464
void renderFrame(double timestamp);
6565

6666
void transformToUnitCube(std::shared_ptr<FilamentAssetWrapper> asset);
@@ -82,7 +82,7 @@ class EngineWrapper : public HybridObject {
8282
std::shared_ptr<Engine> _engine;
8383
std::shared_ptr<SurfaceProvider> _surfaceProvider;
8484
std::shared_ptr<Listener> _listener;
85-
std::function<void(std::shared_ptr<EngineWrapper>)> _renderCallback;
85+
std::function<void(double, double, double)> _renderCallback;
8686
std::function<std::shared_ptr<FilamentBuffer>(std::string)> _getAssetBytes;
8787
std::shared_ptr<Choreographer> _choreographer;
8888
std::shared_ptr<Listener> _choreographerListener;

package/cpp/core/FilamentAssetWrapper.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ using namespace utils;
99

1010
void FilamentAssetWrapper::loadHybridMethods() {
1111
registerHybridMethod("getRoot", &FilamentAssetWrapper::getRoot, this);
12+
registerHybridMethod("releaseSourceData", &FilamentAssetWrapper::releaseSourceData, this);
13+
registerHybridMethod("getAnimator", &FilamentAssetWrapper::getAnimator, this);
1214
}
1315

1416
/**
@@ -30,4 +32,12 @@ std::shared_ptr<EntityWrapper> FilamentAssetWrapper::getRoot() {
3032
return std::make_shared<EntityWrapper>(rootEntity);
3133
}
3234

35+
void FilamentAssetWrapper::releaseSourceData() {
36+
_asset->releaseSourceData();
37+
}
38+
39+
std::shared_ptr<AnimatorWrapper> FilamentAssetWrapper::getAnimator() {
40+
return std::make_shared<AnimatorWrapper>(_asset);
41+
}
42+
3343
} // namespace margelo

package/cpp/core/FilamentAssetWrapper.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include "AnimatorWrapper.h"
34
#include "core/utils/EntityWrapper.h"
45
#include "jsi/HybridObject.h"
56
#include <filament/TransformManager.h>
@@ -17,12 +18,10 @@ class FilamentAssetWrapper : public HybridObject {
1718

1819
void transformToUnitCube(TransformManager& transformManager);
1920

20-
const std::shared_ptr<gltfio::FilamentAsset> getAsset() {
21-
return _asset;
22-
}
23-
2421
private:
2522
std::shared_ptr<EntityWrapper> getRoot();
23+
void releaseSourceData();
24+
std::shared_ptr<AnimatorWrapper> getAnimator();
2625

2726
private:
2827
std::shared_ptr<gltfio::FilamentAsset> _asset;

package/example/ios/FilamentExample.xcodeproj/project.pbxproj

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
59249653BB174593BEDB9F3D /* pengu.glb in Resources */ = {isa = PBXBuildFile; fileRef = 3E2439B417DA497E83EB1F05 /* pengu.glb */; };
1717
7699B88040F8A987B510C191 /* libPods-FilamentExample-FilamentExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-FilamentExample-FilamentExampleTests.a */; };
1818
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
19+
0B65F1CE0E1D4DBB9E25D0BD /* default_env_ibl.ktx in Resources */ = {isa = PBXBuildFile; fileRef = 7A69823713B34897AF31CAF0 /* default_env_ibl.ktx */; };
20+
59249653BB174593BEDB9F3D /* pengu.glb in Resources */ = {isa = PBXBuildFile; fileRef = 3E2439B417DA497E83EB1F05 /* pengu.glb */; };
1921
/* End PBXBuildFile section */
2022

2123
/* Begin PBXContainerItemProxy section */
@@ -48,6 +50,8 @@
4850
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = FilamentExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
4951
89C6BE57DB24E9ADA2F236DE /* Pods-FilamentExample-FilamentExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FilamentExample-FilamentExampleTests.release.xcconfig"; path = "Target Support Files/Pods-FilamentExample-FilamentExampleTests/Pods-FilamentExample-FilamentExampleTests.release.xcconfig"; sourceTree = "<group>"; };
5052
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
53+
7A69823713B34897AF31CAF0 /* default_env_ibl.ktx */ = {isa = PBXFileReference; name = "default_env_ibl.ktx"; path = "../assets/default_env_ibl.ktx"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
54+
3E2439B417DA497E83EB1F05 /* pengu.glb */ = {isa = PBXFileReference; name = "pengu.glb"; path = "../assets/pengu.glb"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
5155
/* End PBXFileReference section */
5256

5357
/* Begin PBXFrameworksBuildPhase section */
@@ -163,6 +167,16 @@
163167
path = Pods;
164168
sourceTree = "<group>";
165169
};
170+
995DFB4F30584326AD631A9F /* Resources */ = {
171+
isa = "PBXGroup";
172+
children = (
173+
7A69823713B34897AF31CAF0 /* default_env_ibl.ktx */,
174+
3E2439B417DA497E83EB1F05 /* pengu.glb */,
175+
);
176+
name = Resources;
177+
sourceTree = "<group>";
178+
path = "";
179+
};
166180
/* End PBXGroup section */
167181

168182
/* Begin PBXNativeTarget section */
@@ -599,10 +613,7 @@
599613
"-DFOLLY_USE_LIBCPP=1",
600614
"-DFOLLY_CFG_NO_COROUTINES=1",
601615
);
602-
OTHER_LDFLAGS = (
603-
"$(inherited)",
604-
" ",
605-
);
616+
OTHER_LDFLAGS = "$(inherited) ";
606617
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
607618
SDKROOT = iphoneos;
608619
USE_HERMES = true;
@@ -670,10 +681,7 @@
670681
"-DFOLLY_USE_LIBCPP=1",
671682
"-DFOLLY_CFG_NO_COROUTINES=1",
672683
);
673-
OTHER_LDFLAGS = (
674-
"$(inherited)",
675-
" ",
676-
);
684+
OTHER_LDFLAGS = "$(inherited) ";
677685
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
678686
SDKROOT = iphoneos;
679687
USE_HERMES = true;

package/src/FilamentView.tsx

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from 'react'
2-
import { findNodeHandle, NativeMethods, Platform } from 'react-native'
2+
import { Button, findNodeHandle, NativeMethods, Platform, ScrollView, View } from 'react-native'
33
import { FilamentProxy } from './native/FilamentProxy'
44
import { FilamentNativeView, NativeProps } from './native/FilamentNativeView'
55
import type { Float3 } from './types/float3'
6+
import { Animator } from './types/Animator'
67

78
type FilamentViewProps = NativeProps
89

@@ -18,14 +19,36 @@ const indirectLightPath = Platform.select({
1819
ios: 'default_env_ibl.ktx',
1920
})!
2021

21-
export class FilamentView extends React.PureComponent<FilamentViewProps> {
22+
// Temporarily
23+
type _State = {
24+
animationNames: string[]
25+
currentAnimation: number
26+
}
27+
28+
const animationInterpolationTime = 5 // 1 second
29+
30+
export class FilamentView extends React.PureComponent<FilamentViewProps, _State> {
2231
private readonly ref: React.RefObject<RefType>
2332
private readonly engine = FilamentProxy.createEngine()
33+
private animator: Animator
34+
35+
private prevAnimationIndex: number | null = null
36+
private prevAnimationStarted: number | null = null
37+
private animationInterpolation = 0
2438

2539
constructor(props: FilamentViewProps) {
2640
super(props)
2741
this.ref = React.createRef<RefType>()
2842

43+
const pengu = this.loadPengu()
44+
this.animator = pengu.getAnimator()
45+
this.state = {
46+
animationNames: Array.from({ length: this.animator.getAnimationCount() }, (_, i) => this.animator.getAnimationName(i)),
47+
currentAnimation: 0,
48+
}
49+
pengu.releaseSourceData() // Cleanup memory after loading the asset
50+
console.log('animationNames', this.state.animationNames)
51+
2952
this.setup3dScene()
3053
}
3154

@@ -46,18 +69,19 @@ export class FilamentView extends React.PureComponent<FilamentViewProps> {
4669
}, 100)
4770
}
4871

49-
setup3dScene = () => {
72+
loadPengu = () => {
5073
// Load a model into the scene:
5174
const modelBuffer = FilamentProxy.getAssetByteBuffer(penguModelPath)
5275
const penguAsset = this.engine.loadAsset(modelBuffer)
76+
5377
// By default all assets get added to the origin at 0,0,0,
5478
// we transform it to fit into a unit cube at the origin using this utility:
5579
this.engine.transformToUnitCube(penguAsset)
5680

57-
// We can also change the pengus position, rotation and scale:
58-
const penguEntity = penguAsset.getRoot()
59-
this.engine.setEntityPosition(penguEntity, [0, 2, 0], true) // Move the pengu up by 2 units
81+
return penguAsset
82+
}
6083

84+
setup3dScene = () => {
6185
// Create a default light:
6286
const indirectLightBuffer = FilamentProxy.getAssetByteBuffer(indirectLightPath)
6387
this.engine.setIndirectLight(indirectLightBuffer)
@@ -67,11 +91,33 @@ export class FilamentView extends React.PureComponent<FilamentViewProps> {
6791
this.engine.getScene().addEntity(light)
6892
}
6993

70-
renderCallback = () => {
94+
renderCallback = (_timestamp: number, _startTime: number, passedSeconds: number) => {
7195
const cameraPosition: Float3 = [0, 0, 5]
7296
const cameraTarget: Float3 = [0, 0, 0]
7397
const cameraUp: Float3 = [0, 1, 0]
7498

99+
if (this.animator) {
100+
this.animator.applyAnimation(this.state.currentAnimation, passedSeconds)
101+
if (this.prevAnimationIndex !== null) {
102+
if (this.prevAnimationStarted === null) {
103+
this.prevAnimationStarted = passedSeconds
104+
}
105+
this.animationInterpolation += passedSeconds - this.prevAnimationStarted
106+
const alpha = this.animationInterpolation / animationInterpolationTime
107+
108+
this.animator.applyCrossFade(this.prevAnimationIndex, this.prevAnimationStarted!, alpha)
109+
console.log('this.animationInterpolation', this.animationInterpolation)
110+
console.log('passedSeconds', passedSeconds)
111+
if (this.animationInterpolation >= animationInterpolationTime) {
112+
this.prevAnimationIndex = null
113+
this.prevAnimationStarted = null
114+
this.animationInterpolation = 0
115+
}
116+
}
117+
118+
this.animator.updateBoneMatrices()
119+
}
120+
75121
this.engine.getCamera().lookAt(cameraPosition, cameraTarget, cameraUp)
76122
}
77123

@@ -99,6 +145,23 @@ export class FilamentView extends React.PureComponent<FilamentViewProps> {
99145

100146
/** @internal */
101147
public render(): React.ReactNode {
102-
return <FilamentNativeView ref={this.ref} {...this.props} />
148+
return (
149+
<View style={this.props.style}>
150+
<FilamentNativeView ref={this.ref} {...this.props} />
151+
152+
<ScrollView style={{ position: 'absolute', bottom: 0, maxHeight: 200, width: '100%' }}>
153+
{this.state.animationNames.map((name, i) => (
154+
<Button
155+
key={i}
156+
onPress={() => {
157+
this.prevAnimationIndex = this.state.currentAnimation
158+
this.setState({ currentAnimation: i })
159+
}}
160+
title={name}
161+
/>
162+
))}
163+
</ScrollView>
164+
</View>
165+
)
103166
}
104167
}

0 commit comments

Comments
 (0)