refactoring

This commit is contained in:
Nick Fisher
2025-03-19 12:27:13 +08:00
parent d5bffd5ad2
commit 627447f8b0
65 changed files with 2304 additions and 3005 deletions

View File

@@ -0,0 +1,2 @@
export 'src/filament_app.dart';
export 'src/engine.dart';

View File

@@ -0,0 +1,267 @@
library;
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
import 'entity.dart';
export 'geometry.dart';
export 'gltf.dart';
export 'light_options.dart';
abstract class ThermionAsset {
///
///
///
ThermionEntity get entity;
///
///
///
Future<List<ThermionEntity>> getChildEntities();
///
///
///
Future setMaterialInstanceAt(covariant MaterialInstance instance);
///
/// Renders an outline around [entity] with the given color.
///
Future setStencilHighlight(
{double r = 1.0, double g = 0.0, double b = 0.0, int? entityIndex});
///
/// Removes the outline around [entity]. Noop if there was no highlight.
///
Future removeStencilHighlight();
///
/// When visible is [true], renders the bounding box.
///
Future setBoundingBoxVisibility(bool visible);
///
///
///
Future<ThermionAsset> getInstance(int index);
///
/// Create a new instance of [entity].
/// Instances are not automatically added to the scene; you must
/// call [addToScene].
///
Future<ThermionAsset> createInstance(
{covariant List<MaterialInstance>? materialInstances = null});
///
/// Returns the number of instances associated with this asset.
///
Future<int> getInstanceCount();
///
/// Returns all instances of associated with this asset.
///
Future<List<ThermionAsset>> getInstances();
///
///
///
Future setCastShadows(bool castShadows);
///
///
///
Future setReceiveShadows(bool castShadows);
///
/// All renderable entities are assigned a layer mask.
///
/// By calling [setLayerVisibility], all renderable entities allocated to
/// the given layer can be efficiently hidden/revealed.
///
/// By default, all renderable entities are assigned to layer 0 (and this
/// layer is enabled by default). Call [setVisibilityLayer] to change the
/// layer for the specified entity.
///
/// Note that we currently also assign gizmos to layer 1 (enabled by default)
/// and the world grid to layer 2 (disabled by default). We suggest you avoid
/// using these layers.
///
Future setVisibilityLayer(ThermionEntity entity, VisibilityLayers layer);
///
/// Schedules the glTF animation at [index] in [asset] to start playing on the next frame.
///
Future playAnimation(ThermionAsset asset, int index,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0,
double startOffset = 0.0});
///
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
///
Future playAnimationByName(covariant ThermionAsset asset, String name,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0});
///
///
///
Future setGltfAnimationFrame(
covariant ThermionAsset asset, int index, int animationFrame);
///
///
///
Future stopAnimation(covariant ThermionAsset asset, int animationIndex);
///
///
///
Future stopAnimationByName(covariant ThermionAsset asset, String name);
///
/// Set the weights for all morph targets in [entity] to [weights].
/// Note that [weights] must contain values for ALL morph targets, but no exception will be thrown if you don't do so (you'll just get incorrect results).
/// If you only want to set one value, set all others to zero (check [getMorphTargetNames] if you need the get a list of all morph targets).
/// IMPORTANT - this accepts the actual ThermionEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name).
/// Use [getChildEntityByName] if you are setting the weights for a child mesh.
///
Future setMorphTargetWeights(ThermionEntity entity, List<double> weights);
///
/// Gets the names of all morph targets for the child renderable [childEntity] under [entity].
///
Future<List<String>> getMorphTargetNames(
covariant ThermionAsset asset, ThermionEntity childEntity);
///
/// Gets the names of all bones for the armature at [skinIndex] under the specified [entity].
///
Future<List<String>> getBoneNames(covariant ThermionAsset asset,
{int skinIndex = 0});
///
/// Gets the names of all glTF animations embedded in the specified entity.
///
Future<List<String>> getAnimationNames(covariant ThermionAsset asset);
///
/// Returns the length (in seconds) of the animation at the given index.
///
Future<double> getAnimationDuration(
covariant ThermionAsset asset, int animationIndex);
///
/// Construct animation(s) for every entity under [asset]. If [targetMeshNames] is provided, only entities with matching names will be animated.
/// [MorphTargetAnimation] for an explanation as to how to construct the animation frame data.
/// This method will check the morph target names specified in [animation] against the morph target names that actually exist exist under [meshName] in [entity],
/// throwing an exception if any cannot be found.
/// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated.
///
Future setMorphAnimationData(
covariant ThermionAsset asset, MorphAnimationData animation,
{List<String>? targetMeshNames});
///
/// Clear all current morph animations for [entity].
///
Future clearMorphAnimationData(ThermionEntity entity);
///
/// Resets all bones in the given entity to their rest pose.
/// This should be done before every call to addBoneAnimation.
///
Future resetBones(ThermionAsset asset);
///
/// Enqueues and plays the [animation] for the specified bone(s).
/// By default, frame data is interpreted as being in *parent* bone space;
/// a 45 degree around Y means the bone will rotate 45 degrees around the
/// Y axis of the parent bone *in its current orientation*.
/// (i.e NOT the parent bone's rest position!).
/// Currently, only [Space.ParentBone] and [Space.Model] are supported; if you want
/// to transform to another space, you will need to do so manually.
///
/// [fadeInInSecs]/[fadeOutInSecs]/[maxDelta] are used to cross-fade between
/// the current active glTF animation ("animation1") and the animation you
/// set via this method ("animation2"). The bone orientations will be
/// linearly interpolated between animation1 and animation2; at time 0,
/// the orientation will be 100% animation1, at time [fadeInInSecs], the
/// animation will be ((1 - maxDelta) * animation1) + (maxDelta * animation2).
/// This will be applied in reverse after [fadeOutInSecs].
///
///
Future addBoneAnimation(ThermionAsset asset, BoneAnimationData animation,
{int skinIndex = 0,
double fadeInInSecs = 0.0,
double fadeOutInSecs = 0.0,
double maxDelta = 1.0});
///
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
/// The returned entity is only intended for use with [getWorldTransform].
///
Future<ThermionEntity> getBone(covariant ThermionAsset asset, int boneIndex,
{int skinIndex = 0});
///
/// Gets the local (relative to parent) transform for [entity].
///
Future<Matrix4> getLocalTransform(ThermionEntity entity);
///
/// Gets the world transform for [entity].
///
Future<Matrix4> getWorldTransform(ThermionEntity entity);
///
/// Gets the inverse bind (pose) matrix for the bone.
/// Note that [parent] must be the ThermionEntity returned by [loadGlb/loadGltf], not any other method ([getChildEntity] etc).
/// This is because all joint information is internally stored with the parent entity.
///
Future<Matrix4> getInverseBindMatrix(
covariant ThermionAsset asset, int boneIndex,
{int skinIndex = 0});
///
/// Sets the transform (relative to its parent) for [entity].
///
Future setTransform(ThermionEntity entity, Matrix4 transform);
///
/// Updates the bone matrices for [entity] (which must be the ThermionEntity
/// returned by [loadGlb/loadGltf]).
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
/// instance of the relevant FilamentInstance (which uses the local
/// bone transform and the inverse bind matrix to set the bone matrix).
///
Future updateBoneMatrices(ThermionEntity entity);
///
/// Directly set the bone matrix for the bone at the given index.
/// Don't call this manually unless you know what you're doing.
///
Future setBoneTransform(
ThermionEntity entity, int boneIndex, Matrix4 transform,
{int skinIndex = 0});
///
/// An [entity] will only be animatable after an animation component is attached.
/// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance.
///
Future addAnimationComponent(ThermionEntity entity);
///
/// Removes an animation component from [entity].
///
Future removeAnimationComponent(ThermionEntity entity);
}

View File

@@ -1,10 +1,10 @@
import 'package:thermion_dart/src/viewer/src/shared_types/layers.dart';
import 'package:vector_math/vector_math_64.dart';
import '../thermion_viewer_base.dart';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/thermion_dart.dart';
enum Projection { Perspective, Orthographic }
abstract class Camera {
Future lookAt(Vector3 position, {Vector3? focus, Vector3? up}) async {
focus ??= Vector3.zero();
up ??= Vector3(0, 1, 0);
@@ -13,6 +13,12 @@ abstract class Camera {
await setModelMatrix(viewMatrix);
}
///
/// Sets the camera exposure.
///
Future setCameraExposure(
double aperture, double shutterSpeed, double sensitivity);
Future setProjection(Projection projection, double left, double right,
double bottom, double top, double near, double far);
Future setProjectionMatrixWithCulling(
@@ -37,6 +43,8 @@ abstract class Camera {
Future<double> getNear();
Future<double> getCullingFar();
Future<double> getFocalLength();
Future<double> getFocusDistance();
Future setFocusDistance(double focusDistance);
Future destroy();
}

View File

@@ -0,0 +1,2 @@
typedef ThermionEntity = int;
final ThermionEntity FILAMENT_ENTITY_NULL = 0;

View File

@@ -0,0 +1,209 @@
import 'dart:typed_data';
import 'package:thermion_dart/src/filament/src/engine.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FilamentConfig<T, U> {
final Backend backend;
final T? renderCallback;
final U? renderCallbackOwner;
final U resourceLoader;
final U? platform;
final U? driver;
final U? sharedContext;
final String? uberArchivePath;
final int stereoscopicEyeCount;
final bool disableHandleUseAfterFreeCheck;
FilamentConfig(
{required this.backend,
required this.resourceLoader,
this.uberArchivePath,
this.renderCallback,
this.renderCallbackOwner,
this.platform,
this.driver,
this.sharedContext,
this.stereoscopicEyeCount = 1,
this.disableHandleUseAfterFreeCheck = false});
}
abstract class FilamentApp<T> {
static FilamentApp? instance;
final T engine;
final T gltfAssetLoader;
final T gltfResourceLoader;
final T renderer;
final T transformManager;
final T lightManager;
final T renderableManager;
final T ubershaderMaterialProvider;
FilamentApp(
{required this.engine,
required this.gltfAssetLoader,
required this.gltfResourceLoader,
required this.renderer,
required this.transformManager,
required this.lightManager,
required this.renderableManager,
required this.ubershaderMaterialProvider});
///
///
///
Future<SwapChain> createHeadlessSwapChain(int width, int height,
{bool hasStencilBuffer = false});
///
///
///
Future<SwapChain> createSwapChain(T handle, {bool hasStencilBuffer = false});
///
///
///
Future destroySwapChain(SwapChain swapChain);
///
///
///
Future destroy();
///
///
///
Future<RenderTarget> createRenderTarget(int width, int height,
{covariant Texture? color, covariant Texture? depth});
///
///
///
Future<Texture> createTexture(int width, int height,
{int depth = 1,
int levels = 1,
Set<TextureUsage> flags = const {TextureUsage.TEXTURE_USAGE_SAMPLEABLE},
TextureSamplerType textureSamplerType = TextureSamplerType.SAMPLER_2D,
TextureFormat textureFormat = TextureFormat.RGBA16F,
int? importedTextureHandle});
///
///
///
Future<TextureSampler> createTextureSampler(
{TextureMinFilter minFilter = TextureMinFilter.LINEAR,
TextureMagFilter magFilter = TextureMagFilter.LINEAR,
TextureWrapMode wrapS = TextureWrapMode.CLAMP_TO_EDGE,
TextureWrapMode wrapT = TextureWrapMode.CLAMP_TO_EDGE,
TextureWrapMode wrapR = TextureWrapMode.CLAMP_TO_EDGE,
double anisotropy = 0.0,
TextureCompareMode compareMode = TextureCompareMode.NONE,
TextureCompareFunc compareFunc = TextureCompareFunc.LESS_EQUAL});
///
/// Decodes the specified image data.
///
Future<LinearImage> decodeImage(Uint8List data);
///
/// Creates an (empty) imge with the given dimensions.
///
Future<LinearImage> createImage(int width, int height, int channels);
///
///
///
Future<Material> createMaterial(Uint8List data);
///
///
///
Future<MaterialInstance> createUbershaderMaterialInstance({
bool doubleSided = false,
bool unlit = false,
bool hasVertexColors = false,
bool hasBaseColorTexture = false,
bool hasNormalTexture = false,
bool hasOcclusionTexture = false,
bool hasEmissiveTexture = false,
bool useSpecularGlossiness = false,
AlphaMode alphaMode = AlphaMode.OPAQUE,
bool enableDiagnostics = false,
bool hasMetallicRoughnessTexture = false,
int metallicRoughnessUV = -1,
int baseColorUV = -1,
bool hasClearCoatTexture = false,
int clearCoatUV = -1,
bool hasClearCoatRoughnessTexture = false,
int clearCoatRoughnessUV = -1,
bool hasClearCoatNormalTexture = false,
int clearCoatNormalUV = -1,
bool hasClearCoat = false,
bool hasTransmission = false,
bool hasTextureTransforms = false,
int emissiveUV = -1,
int aoUV = -1,
int normalUV = -1,
bool hasTransmissionTexture = false,
int transmissionUV = -1,
bool hasSheenColorTexture = false,
int sheenColorUV = -1,
bool hasSheenRoughnessTexture = false,
int sheenRoughnessUV = -1,
bool hasVolumeThicknessTexture = false,
int volumeThicknessUV = -1,
bool hasSheen = false,
bool hasIOR = false,
bool hasVolume = false,
});
///
///
///
Future<MaterialInstance> createUnlitMaterialInstance();
///
///
///
Future<MaterialInstance> getMaterialInstanceAt(
ThermionEntity entity, int index);
///
///
///
@override
Future<ThermionAsset> createGeometry(Geometry geometry,
{List<MaterialInstance>? materialInstances, bool keepData = false});
///
///
///
Future setRenderable(covariant View view, bool renderable);
///
///
///
Future register(covariant SwapChain swapChain, covariant View view);
///
///
///
Future render();
///
///
///
Future requestFrame();
///
///
///
Future registerRequestFrameHook(Future Function() hook);
///
///
///
Future unregisterRequestFrameHook(Future Function() hook);
}

View File

@@ -1,6 +1,6 @@
import 'dart:typed_data';
import '../../viewer.dart';
import '../../viewer/viewer.dart';
class Geometry {
final Float32List vertices;

View File

@@ -0,0 +1,28 @@
import 'entity.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
enum Axis {
X(const [1.0, 0.0, 0.0]),
Y(const [0.0, 1.0, 0.0]),
Z(const [0.0, 0.0, 1.0]);
const Axis(this.vector);
final List<double> vector;
Vector3 asVector() => Vector3(vector[0], vector[1], vector[2]);
}
enum GizmoPickResultType { AxisX, AxisY, AxisZ, Parent, None }
enum GizmoType { translation, rotation }
abstract class GizmoAsset extends ThermionAsset {
Future pick(int x, int y,
{Future Function(GizmoPickResultType axis, Vector3 coords)? handler});
Future highlight(Axis axis);
Future unhighlight();
bool isNonPickable(ThermionEntity entity);
bool isGizmoEntity(ThermionEntity entity);
}

View File

@@ -1,4 +1,4 @@
import '../../viewer.dart';
import '../../viewer/viewer.dart';
/// The result of a picking operation (see [ThermionViewer.pick] for more details).
/// [x] and [y] refer to the original screen coordinates used to call [pick]; this should

View File

@@ -1,4 +1,4 @@
import 'package:thermion_dart/src/viewer/src/thermion_viewer_base.dart';
import 'package:thermion_dart/thermion_dart.dart';
abstract class Scene {
Future add(covariant ThermionAsset asset);

View File

@@ -6,7 +6,9 @@ export 'render_target.dart';
export 'camera.dart';
export 'material.dart';
export 'texture.dart';
export 'entities.dart';
export 'asset.dart';
export 'entity.dart';
export 'gizmo.dart';
export 'light.dart';
export 'shadow.dart';
export 'manipulator.dart';

View File

@@ -1,4 +1,4 @@
import 'package:thermion_dart/src/viewer/src/shared_types/layers.dart';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/thermion_dart.dart';
///
@@ -26,7 +26,6 @@ abstract class View {
Future<Camera> getCamera();
Future setPostProcessing(bool enabled);
Future setAntiAliasing(bool msaa, bool fxaa, bool taa);
Future setRenderable(bool renderable, covariant SwapChain swapChain);
Future setFrustumCullingEnabled(bool enabled);
Future setToneMapper(ToneMapper mapper);
Future setStencilBufferEnabled(bool enabled);

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'package:vector_math/vector_math_64.dart';
import '../../../viewer/src/shared_types/camera.dart';
import '../../../filament/src/camera.dart';
import '../../../viewer/viewer.dart';
import '../../input.dart';
import '../input_handler.dart';

View File

@@ -6,6 +6,13 @@ import 'package:vector_math/vector_math_64.dart';
import '../../../thermion_dart.dart';
class GeometryHelper {
static Geometry fullscreenQuad() {
final vertices = Float32List.fromList(
[-1.0, -1.0, 1.0, 1.0, 3.0, -1.0, 1.0, 1.0, -1.0, 3.0, 1.0, 1.0]);
final indices = [0, 1, 2];
return Geometry(vertices, indices);
}
static Geometry sphere({bool normals = true, bool uvs = true}) {
int latitudeBands = 20;
int longitudeBands = 20;
@@ -459,13 +466,18 @@ class GeometryHelper {
])
: null;
Float32List? _uvs =
uvs ? Float32List.fromList([
0, 0,
1, 0,
1, 1,
0, 1,
]) : null;
Float32List? _uvs = uvs
? Float32List.fromList([
0,
0,
1,
0,
1,
1,
0,
1,
])
: null;
List<int> indices = [
0,

View File

@@ -0,0 +1,262 @@
import 'dart:typed_data';
import 'package:animation_tools_dart/src/bone_animation_data.dart';
import 'package:animation_tools_dart/src/morph_animation_data.dart';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_asset.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_filament_app.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_material.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_scene.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_texture.dart';
import 'package:thermion_dart/thermion_dart.dart';
class BackgroundImage extends ThermionAsset {
final ThermionAsset asset;
ThermionEntity get entity => asset.entity;
Texture? _backgroundImageTexture;
FFIMaterial? _imageMaterial;
FFITextureSampler? _imageSampler;
final FFIScene scene;
final FilamentApp app;
BackgroundImage._(this.asset, this.scene, this.app,
this._backgroundImageTexture, this._imageMaterial, this._imageSampler);
Future destroy() async {
Scene_removeEntity(scene.scene, entity);
await _backgroundImageTexture!.dispose();
await _imageSampler!.dispose();
}
static Future<BackgroundImage> create(
FFIFilamentApp app, FFIScene scene, Uint8List imageData) async {
final image = await app.decodeImage(imageData);
var backgroundImageTexture = await app.createTexture(
await image.getWidth(), await image.getHeight());
var imageMaterial = FFIMaterial(Material_createImageMaterial(), app);
var imageSampler = await app.createTextureSampler() as FFITextureSampler;
var imageMaterialInstance =
await imageMaterial!.createInstance() as FFIMaterialInstance;
await imageMaterialInstance.setParameterTexture(
"image",
backgroundImageTexture as FFITexture,
imageSampler as FFITextureSampler);
var backgroundImage =
await app.createGeometry(GeometryHelper.fullscreenQuad()) as FFIAsset;
backgroundImage.setMaterialInstanceAt(imageMaterialInstance);
await scene.add(backgroundImage);
return BackgroundImage._(backgroundImage, scene, app,
backgroundImageTexture, imageMaterial, imageSampler);
}
@override
Future<ThermionAsset> createInstance(
{covariant List<MaterialInstance>? materialInstances = null}) {
throw UnimplementedError();
}
@override
Future<List<ThermionEntity>> getChildEntities() async {
return [];
}
@override
Future<ThermionAsset> getInstance(int index) {
throw UnimplementedError();
}
@override
Future<int> getInstanceCount() async {
return 0;
}
@override
Future<List<ThermionAsset>> getInstances() async {
return [];
}
@override
Future removeStencilHighlight() {
// TODO: implement removeStencilHighlight
throw UnimplementedError();
}
@override
Future setBoundingBoxVisibility(bool visible) {
// TODO: implement setBoundingBoxVisibility
throw UnimplementedError();
}
@override
Future setCastShadows(bool castShadows) {
// TODO: implement setCastShadows
throw UnimplementedError();
}
@override
Future setMaterialInstanceAt(covariant MaterialInstance instance) {
// TODO: implement setMaterialInstanceAt
throw UnimplementedError();
}
@override
Future setReceiveShadows(bool castShadows) {
// TODO: implement setReceiveShadows
throw UnimplementedError();
}
@override
Future setStencilHighlight(
{double r = 1.0, double g = 0.0, double b = 0.0, int? entityIndex}) {
// TODO: implement setStencilHighlight
throw UnimplementedError();
}
@override
Future setVisibilityLayer(ThermionEntity entity, VisibilityLayers layer) {
// TODO: implement setVisibilityLayer
throw UnimplementedError();
}
@override
Future addAnimationComponent(ThermionEntity entity) {
// TODO: implement addAnimationComponent
throw UnimplementedError();
}
@override
Future addBoneAnimation(ThermionAsset asset, BoneAnimationData animation, {int skinIndex = 0, double fadeInInSecs = 0.0, double fadeOutInSecs = 0.0, double maxDelta = 1.0}) {
// TODO: implement addBoneAnimation
throw UnimplementedError();
}
@override
Future clearMorphAnimationData(ThermionEntity entity) {
// TODO: implement clearMorphAnimationData
throw UnimplementedError();
}
@override
Future<double> getAnimationDuration(covariant ThermionAsset asset, int animationIndex) {
// TODO: implement getAnimationDuration
throw UnimplementedError();
}
@override
Future<List<String>> getAnimationNames(covariant ThermionAsset asset) {
// TODO: implement getAnimationNames
throw UnimplementedError();
}
@override
Future<ThermionEntity> getBone(covariant ThermionAsset asset, int boneIndex, {int skinIndex = 0}) {
// TODO: implement getBone
throw UnimplementedError();
}
@override
Future<List<String>> getBoneNames(covariant ThermionAsset asset, {int skinIndex = 0}) {
// TODO: implement getBoneNames
throw UnimplementedError();
}
@override
Future<Matrix4> getInverseBindMatrix(covariant ThermionAsset asset, int boneIndex, {int skinIndex = 0}) {
// TODO: implement getInverseBindMatrix
throw UnimplementedError();
}
@override
Future<Matrix4> getLocalTransform(ThermionEntity entity) {
// TODO: implement getLocalTransform
throw UnimplementedError();
}
@override
Future<List<String>> getMorphTargetNames(covariant ThermionAsset asset, ThermionEntity childEntity) {
// TODO: implement getMorphTargetNames
throw UnimplementedError();
}
@override
Future<Matrix4> getWorldTransform(ThermionEntity entity) {
// TODO: implement getWorldTransform
throw UnimplementedError();
}
@override
Future playAnimation(ThermionAsset asset, int index, {bool loop = false, bool reverse = false, bool replaceActive = true, double crossfade = 0.0, double startOffset = 0.0}) {
// TODO: implement playAnimation
throw UnimplementedError();
}
@override
Future playAnimationByName(covariant ThermionAsset asset, String name, {bool loop = false, bool reverse = false, bool replaceActive = true, double crossfade = 0.0}) {
// TODO: implement playAnimationByName
throw UnimplementedError();
}
@override
Future removeAnimationComponent(ThermionEntity entity) {
// TODO: implement removeAnimationComponent
throw UnimplementedError();
}
@override
Future resetBones(ThermionAsset asset) {
// TODO: implement resetBones
throw UnimplementedError();
}
@override
Future setBoneTransform(ThermionEntity entity, int boneIndex, Matrix4 transform, {int skinIndex = 0}) {
// TODO: implement setBoneTransform
throw UnimplementedError();
}
@override
Future setGltfAnimationFrame(covariant ThermionAsset asset, int index, int animationFrame) {
// TODO: implement setGltfAnimationFrame
throw UnimplementedError();
}
@override
Future setMorphAnimationData(covariant ThermionAsset asset, MorphAnimationData animation, {List<String>? targetMeshNames}) {
// TODO: implement setMorphAnimationData
throw UnimplementedError();
}
@override
Future setMorphTargetWeights(ThermionEntity entity, List<double> weights) {
// TODO: implement setMorphTargetWeights
throw UnimplementedError();
}
@override
Future setTransform(ThermionEntity entity, Matrix4 transform) {
// TODO: implement setTransform
throw UnimplementedError();
}
@override
Future stopAnimation(covariant ThermionAsset asset, int animationIndex) {
// TODO: implement stopAnimation
throw UnimplementedError();
}
@override
Future stopAnimationByName(covariant ThermionAsset asset, String name) {
// TODO: implement stopAnimationByName
throw UnimplementedError();
}
@override
Future updateBoneMatrices(ThermionEntity entity) {
// TODO: implement updateBoneMatrices
throw UnimplementedError();
}
}

View File

@@ -1,13 +1,17 @@
import 'dart:typed_data';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/src/utils/src/matrix.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_filament_app.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_material.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart' as v64;
import 'package:vector_math/vector_math_64.dart';
class FFIAsset extends ThermionAsset {
///
///
///
@@ -389,7 +393,477 @@ class FFIAsset extends ThermionAsset {
return RenderableManager_isShadowReceiver(app.renderableManager, entity);
}
///
///
///
Future transformToUnitCube() async {
SceneAsset_transformToUnitCube(asset);
TransformManager_transformToUnitCube(app.transformManager, entity, SceneAsset_getBoundingBox(asset));
}
///
///
///
Future setVisibilityLayer(
ThermionEntity entity, VisibilityLayers layer) async {
RenderableManager_setVisibilityLayer(
app.renderableManager, entity, layer.value);
}
///
///
///
@override
Future setMorphTargetWeights(
ThermionEntity entity, List<double> weights) async {
if (weights.isEmpty) {
throw Exception("Weights must not be empty");
}
var weightsPtr = allocator<Float>(weights.length);
for (int i = 0; i < weights.length; i++) {
weightsPtr[i] = weights[i];
}
var success = await withBoolCallback((cb) {
AnimationManager_setMorphTargetWeightsRenderThread(
animationManager, entity, weightsPtr, weights.length, cb);
});
allocator.free(weightsPtr);
if (!success) {
throw Exception(
"Failed to set morph target weights, check logs for details");
}
}
///
///
///
@override
Future<List<String>> getMorphTargetNames(
covariant FFIAsset asset, ThermionEntity childEntity) async {
var names = <String>[];
var count = AnimationManager_getMorphTargetNameCount(
animationManager, asset.asset, childEntity);
var outPtr = allocator<Char>(255);
for (int i = 0; i < count; i++) {
AnimationManager_getMorphTargetName(
animationManager, asset.asset, childEntity, outPtr, i);
names.add(outPtr.cast<Utf8>().toDartString());
}
allocator.free(outPtr);
return names.cast<String>();
}
Future<List<String>> getBoneNames(covariant FFIAsset asset,
{int skinIndex = 0}) async {
var count =
AnimationManager_getBoneCount(animationManager, asset.asset, skinIndex);
var out = allocator<Pointer<Char>>(count);
for (int i = 0; i < count; i++) {
out[i] = allocator<Char>(255);
}
AnimationManager_getBoneNames(
animationManager, asset.asset, out, skinIndex);
var names = <String>[];
for (int i = 0; i < count; i++) {
var namePtr = out[i];
names.add(namePtr.cast<Utf8>().toDartString());
}
return names;
}
///
///
///
@override
Future<List<String>> getAnimationNames(covariant FFIAsset asset) async {
var animationCount =
AnimationManager_getAnimationCount(animationManager, asset.asset);
var names = <String>[];
var outPtr = allocator<Char>(255);
for (int i = 0; i < animationCount; i++) {
AnimationManager_getAnimationName(
animationManager, asset.asset, outPtr, i);
names.add(outPtr.cast<Utf8>().toDartString());
}
allocator.free(outPtr);
return names;
}
///
///
///
@override
Future<double> getAnimationDuration(
FFIAsset asset, int animationIndex) async {
return AnimationManager_getAnimationDuration(
animationManager, asset.asset, animationIndex);
}
///
///
///
Future<double> getAnimationDurationByName(FFIAsset asset, String name) async {
var animations = await getAnimationNames(asset);
var index = animations.indexOf(name);
if (index == -1) {
throw Exception("Failed to find animation $name");
}
return getAnimationDuration(asset, index);
}
Future clearMorphAnimationData(ThermionEntity entity) async {
if (!AnimationManager_clearMorphAnimation(animationManager, entity)) {
throw Exception("Failed to clear morph animation");
}
}
///
///
///
@override
Future setMorphAnimationData(FFIAsset asset, MorphAnimationData animation,
{List<String>? targetMeshNames}) async {
var meshEntities = await getChildEntities(asset);
var meshNames = meshEntities.map((e) => getNameForEntity(e)).toList();
if (targetMeshNames != null) {
for (final targetMeshName in targetMeshNames) {
if (!meshNames.contains(targetMeshName)) {
throw Exception(
"Error: mesh ${targetMeshName} does not exist under the specified entity. Available meshes : ${meshNames}");
}
}
}
// Entities are not guaranteed to have the same morph targets (or share the same order),
// either from each other, or from those specified in [animation].
// We therefore set morph targets separately for each mesh.
// For each mesh, allocate enough memory to hold FxM 32-bit floats
// (where F is the number of Frames, and M is the number of morph targets in the mesh).
// we call [extract] on [animation] to return frame data only for morph targets that present in both the mesh and the animation
for (int i = 0; i < meshNames.length; i++) {
var meshName = meshNames[i];
var meshEntity = meshEntities[i];
if (targetMeshNames?.contains(meshName) == false) {
// _logger.info("Skipping $meshName, not contained in target");
continue;
}
var meshMorphTargets = await getMorphTargetNames(asset, meshEntity);
var intersection = animation.morphTargets
.toSet()
.intersection(meshMorphTargets.toSet())
.toList();
if (intersection.isEmpty) {
throw Exception(
"""No morph targets specified in animation are present on mesh $meshName.
If you weren't intending to animate every mesh, specify [targetMeshNames] when invoking this method.
Animation morph targets: ${animation.morphTargets}\n
Mesh morph targets ${meshMorphTargets}
Child meshes: ${meshNames}""");
}
var indices = Uint32List.fromList(
intersection.map((m) => meshMorphTargets.indexOf(m)).toList());
// var frameData = animation.data;
var frameData = animation.subset(intersection);
assert(
frameData.data.length == animation.numFrames * intersection.length);
var result = AnimationManager_setMorphAnimation(
animationManager,
meshEntity,
frameData.data.address,
indices.address,
indices.length,
animation.numFrames,
animation.frameLengthInMs);
if (!result) {
throw Exception("Failed to set morph animation data for ${meshName}");
}
}
}
///
/// Currently, scale is not supported.
///
@override
Future addBoneAnimation(covariant FFIAsset asset, BoneAnimationData animation,
{int skinIndex = 0,
double fadeOutInSecs = 0.0,
double fadeInInSecs = 0.0,
double maxDelta = 1.0}) async {
if (animation.space != Space.Bone &&
animation.space != Space.ParentWorldRotation) {
throw UnimplementedError("TODO - support ${animation.space}");
}
if (skinIndex != 0) {
throw UnimplementedError("TODO - support skinIndex != 0 ");
}
var boneNames = await getBoneNames(asset);
var restLocalTransformsRaw = allocator<Float>(boneNames.length * 16);
AnimationManager_getRestLocalTransforms(animationManager, asset.asset,
skinIndex, restLocalTransformsRaw, boneNames.length);
var restLocalTransforms = <Matrix4>[];
for (int i = 0; i < boneNames.length; i++) {
var values = <double>[];
for (int j = 0; j < 16; j++) {
values.add(restLocalTransformsRaw[(i * 16) + j]);
}
restLocalTransforms.add(Matrix4.fromList(values));
}
allocator.free(restLocalTransformsRaw);
var numFrames = animation.frameData.length;
var data = allocator<Float>(numFrames * 16);
var bones = await Future.wait(List<Future<ThermionEntity>>.generate(
boneNames.length, (i) => getBone(asset, i)));
for (int i = 0; i < animation.bones.length; i++) {
var boneName = animation.bones[i];
var entityBoneIndex = boneNames.indexOf(boneName);
if (entityBoneIndex == -1) {
_logger.warning("Bone $boneName not found, skipping");
continue;
}
var boneEntity = bones[entityBoneIndex];
var baseTransform = restLocalTransforms[entityBoneIndex];
var world = Matrix4.identity();
// this odd use of ! is intentional, without it, the WASM optimizer gets in trouble
var parentBoneEntity = (await getParent(boneEntity))!;
while (true) {
if (!bones.contains(parentBoneEntity!)) {
break;
}
world = restLocalTransforms[bones.indexOf(parentBoneEntity!)] * world;
parentBoneEntity = (await getParent(parentBoneEntity))!;
}
world = Matrix4.identity()..setRotation(world.getRotation());
var worldInverse = Matrix4.identity()..copyInverse(world);
for (int frameNum = 0; frameNum < numFrames; frameNum++) {
var rotation = animation.frameData[frameNum][i].rotation;
var translation = animation.frameData[frameNum][i].translation;
var frameTransform =
Matrix4.compose(translation, rotation, Vector3.all(1.0));
var newLocalTransform = frameTransform.clone();
if (animation.space == Space.Bone) {
newLocalTransform = baseTransform * frameTransform;
} else if (animation.space == Space.ParentWorldRotation) {
newLocalTransform =
baseTransform * (worldInverse * frameTransform * world);
}
for (int j = 0; j < 16; j++) {
data.elementAt((frameNum * 16) + j).value =
newLocalTransform.storage[j];
}
}
AnimationManager_addBoneAnimation(
animationManager,
asset.asset,
skinIndex,
entityBoneIndex,
data,
numFrames,
animation.frameLengthInMs,
fadeOutInSecs,
fadeInInSecs,
maxDelta);
}
allocator.free(data);
}
///
///
///
Future<Matrix4> getLocalTransform(ThermionEntity entity) async {
return double4x4ToMatrix4(
TransformManager_getLocalTransform(app.transformManager, entity));
}
///
///
///
Future<Matrix4> getWorldTransform(ThermionEntity entity) async {
return double4x4ToMatrix4(
TransformManager_getWorldTransform(app.transformManager, entity));
}
///
///
///
Future setTransform(ThermionEntity entity, Matrix4 transform) async {
TransformManager_setTransform(
app.transformManager, entity, matrix4ToDouble4x4(transform));
}
///
///
///
Future updateBoneMatrices(ThermionEntity entity) async {
throw UnimplementedError();
// var result = await withBoolCallback((cb) {
// update_bone_matrices_render_thread(_sceneManager!, entity, cb);
// });
// if (!result) {
// throw Exception("Failed to update bone matrices");
// }
}
///
///
///
Future<Matrix4> getInverseBindMatrix(FFIAsset asset, int boneIndex,
{int skinIndex = 0}) async {
var matrix = Float32List(16);
AnimationManager_getInverseBindMatrix(
animationManager, asset.asset, skinIndex, boneIndex, matrix.address);
return Matrix4.fromList(matrix);
}
///
///
///
Future<ThermionEntity> getBone(FFIAsset asset, int boneIndex,
{int skinIndex = 0}) async {
if (skinIndex != 0) {
throw UnimplementedError("TOOD");
}
return AnimationManager_getBone(
animationManager, asset.asset, skinIndex, boneIndex);
}
///
///
///
@override
Future setBoneTransform(
ThermionEntity entity, int boneIndex, Matrix4 transform,
{int skinIndex = 0}) async {
if (skinIndex != 0) {
throw UnimplementedError("TOOD");
}
final ptr = allocator<Float>(16);
for (int i = 0; i < 16; i++) {
ptr[i] = transform.storage[i];
}
var result = await withBoolCallback((cb) {
AnimationManager_setBoneTransformRenderThread(
animationManager, entity, skinIndex, boneIndex, ptr, cb);
});
allocator.free(ptr);
if (!result) {
throw Exception("Failed to set bone transform");
}
}
///
///
///
///
@override
Future resetBones(covariant FFIAsset asset) async {
AnimationManager_resetToRestPose(animationManager, asset.asset);
}
///
///
///
@override
Future playAnimation(covariant FFIAsset asset, int index,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0,
double startOffset = 0.0}) async {
AnimationManager_playAnimation(animationManager, asset.asset, index, loop,
reverse, replaceActive, crossfade, startOffset);
}
///
///
///
@override
Future stopAnimation(FFIAsset asset, int animationIndex) async {
AnimationManager_stopAnimation(
animationManager, asset.asset, animationIndex);
}
///
///
///
@override
Future stopAnimationByName(FFIAsset asset, String name) async {
var animations = await getAnimationNames(asset);
await stopAnimation(asset, animations.indexOf(name));
}
///
///
///
@override
Future playAnimationByName(FFIAsset asset, String name,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0,
bool wait = false}) async {
var animations = await getAnimationNames(asset);
var index = animations.indexOf(name);
var duration = await getAnimationDuration(asset, index);
await playAnimation(asset, index,
loop: loop,
reverse: reverse,
replaceActive: replaceActive,
crossfade: crossfade);
if (wait) {
await Future.delayed(Duration(milliseconds: (duration * 1000).toInt()));
}
}
///
///
///
@override
Future setGltfAnimationFrame(
FFIAsset asset, int index, int animationFrame) async {
AnimationManager_setGltfAnimationFrame(
animationManager, asset.asset, index, animationFrame);
}
///
///
///
@override
Future addAnimationComponent(ThermionEntity entity) async {
AnimationManager_addAnimationComponent(animationManager, entity);
}
///
///
///
Future removeAnimationComponent(ThermionEntity entity) async {
AnimationManager_removeAnimationComponent(animationManager, entity);
}
}

View File

@@ -3,10 +3,10 @@ import 'dart:typed_data';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_filament_app.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/layers.dart';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
import '../../../../utils/src/matrix.dart';
import '../../thermion_viewer_base.dart';
class FFICamera extends Camera {
final Pointer<TCamera> camera;
@@ -150,4 +150,12 @@ class FFICamera extends Camera {
Future destroy() async {
throw UnimplementedError();
}
Future setCameraExposure(
double aperture, double shutterSpeed, double sensitivity) async {
Camera_setExposure(camera, aperture, shutterSpeed, sensitivity);
}
Future<double> getFocusDistance() async => Camera_getFocusDistance(camera);
Future setFocusDistance(double focusDistance) async => Camera_setFocusDistance(camera, focusDistance);
}

View File

@@ -1,8 +1,13 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_asset.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_material.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_render_target.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_swapchain.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_texture.dart';
import 'package:thermion_dart/src/viewer/src/filament/filament.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_view.dart';
import 'package:thermion_dart/thermion_dart.dart';
typedef RenderCallback = Pointer<NativeFunction<Void Function(Pointer<Void>)>>;
@@ -11,12 +16,13 @@ class FFIFilamentConfig extends FilamentConfig<RenderCallback, Pointer<Void>> {
FFIFilamentConfig(
{required super.backend,
required super.resourceLoader,
required super.driver,
required super.platform,
required super.sharedContext,
required super.uberArchivePath});
}
class FFIFilamentApp extends FilamentApp<Pointer> {
static FFIFilamentApp? _instance;
final Pointer<TEngine> engine;
final Pointer<TGltfAssetLoader> gltfAssetLoader;
final Pointer<TGltfResourceLoader> gltfResourceLoader;
@@ -47,53 +53,82 @@ class FFIFilamentApp extends FilamentApp<Pointer> {
transformManager: transformManager,
lightManager: lightManager,
renderableManager: renderableManager,
ubershaderMaterialProvider: ubershaderMaterialProvider);
ubershaderMaterialProvider: ubershaderMaterialProvider) {}
Future<FFIFilamentApp> create(FFIFilamentConfig config) async {
if (_instance == null) {
RenderLoop_destroy();
RenderLoop_create();
final engine = await withPointerCallback<TEngine>((cb) =>
Engine_createRenderThread(
TBackend.values[config.backend.index].index,
config.platform ?? nullptr,
config.sharedContext ?? nullptr,
config.stereoscopicEyeCount,
config.disableHandleUseAfterFreeCheck,
cb));
final gltfResourceLoader = await withPointerCallback<TGltfResourceLoader>(
(cb) => GltfResourceLoader_createRenderThread(engine, cb));
final gltfAssetLoader = await withPointerCallback<TGltfAssetLoader>(
(cb) => GltfAssetLoader_createRenderThread(engine, nullptr, cb));
final renderer = await withPointerCallback<TRenderer>(
(cb) => Engine_createRendererRenderThread(engine, cb));
final ubershaderMaterialProvider =
await withPointerCallback<TMaterialProvider>(
(cb) => GltfAssetLoader_getMaterialProvider(gltfAssetLoader));
final transformManager = Engine_getTransformManager(engine);
final lightManager = Engine_getLightManager(engine);
final renderableManager = Engine_getRenderableManager(engine);
final renderTicker = await withPointerCallback<TRenderTicker>(
(cb) => RenderTicker_create(renderer));
final nameComponentManager = NameComponentManager_create();
_instance = FFIFilamentApp(
engine,
gltfAssetLoader,
gltfResourceLoader,
renderer,
transformManager,
lightManager,
renderableManager,
ubershaderMaterialProvider,
renderTicker, nameComponentManager);
static Future create(FFIFilamentConfig config) async {
if (FilamentApp.instance != null) {
await FilamentApp.instance!.destroy();
}
return _instance!;
RenderLoop_destroy();
RenderLoop_create();
final engine = await withPointerCallback<TEngine>((cb) =>
Engine_createRenderThread(
TBackend.values[config.backend.index].index,
config.platform ?? nullptr,
config.sharedContext ?? nullptr,
config.stereoscopicEyeCount,
config.disableHandleUseAfterFreeCheck,
cb));
final gltfResourceLoader = await withPointerCallback<TGltfResourceLoader>(
(cb) => GltfResourceLoader_createRenderThread(engine, cb));
final gltfAssetLoader = await withPointerCallback<TGltfAssetLoader>(
(cb) => GltfAssetLoader_createRenderThread(engine, nullptr, cb));
final renderer = await withPointerCallback<TRenderer>(
(cb) => Engine_createRendererRenderThread(engine, cb));
final ubershaderMaterialProvider =
await withPointerCallback<TMaterialProvider>(
(cb) => GltfAssetLoader_getMaterialProvider(gltfAssetLoader));
final transformManager = Engine_getTransformManager(engine);
final lightManager = Engine_getLightManager(engine);
final renderableManager = Engine_getRenderableManager(engine);
final renderTicker = await withPointerCallback<TRenderTicker>(
(cb) => RenderTicker_create(renderer));
final nameComponentManager = NameComponentManager_create();
FilamentApp.instance = FFIFilamentApp(
engine,
gltfAssetLoader,
gltfResourceLoader,
renderer,
transformManager,
lightManager,
renderableManager,
ubershaderMaterialProvider,
renderTicker,
nameComponentManager);
}
final _views = <FFISwapChain, List<FFIView>>{};
final _viewMappings = <FFIView, FFISwapChain>{};
///
///
///
Future setRenderable(covariant FFIView view, bool renderable) async {
final swapChain = _viewMappings[view]!;
if (renderable && !_views[swapChain]!.contains(view)) {
_views[swapChain]!.add(view);
} else if (!renderable && _views[swapChain]!.contains(view)) {
_views[swapChain]!.remove(view);
}
final views = calloc<Pointer<TView>>(255);
for (final swapChain in _views.keys) {
var numViews = _views[swapChain]!.length;
for (int i = 0; i < numViews; i++) {
views[i] = _views[swapChain]![i].view;
}
RenderTicker_setRenderable(
renderTicker, swapChain.swapChain, views, numViews);
}
calloc.free(views);
}
@override
@@ -136,8 +171,12 @@ class FFIFilamentApp extends FilamentApp<Pointer> {
}
@override
Future destroy() {
throw UnimplementedError();
Future destroy() async {
for (final swapChain in _views.keys) {
for (final view in _views[swapChain]!) {
await setRenderable(view, false);
}
}
}
///
@@ -153,16 +192,316 @@ class FFIFilamentApp extends FilamentApp<Pointer> {
return FFIRenderTarget(renderTarget, this);
}
// ///
// ///
// ///
// Future<RenderTarget> createRenderTarget(int width, int height,
// {covariant FFITexture? color, covariant FFITexture? depth}) async {
// final renderTarget = await withPointerCallback<TRenderTarget>((cb) {
// RenderTarget_createRenderThread(app.engine, width, height,
// color?.pointer ?? nullptr, depth?.pointer ?? nullptr, cb);
// });
///
///
///
Future<Texture> createTexture(int width, int height,
{int depth = 1,
int levels = 1,
Set<TextureUsage> flags = const {TextureUsage.TEXTURE_USAGE_SAMPLEABLE},
TextureSamplerType textureSamplerType = TextureSamplerType.SAMPLER_2D,
TextureFormat textureFormat = TextureFormat.RGBA16F,
int? importedTextureHandle}) async {
var bitmask = flags.fold(0, (a, b) => a | b.index);
final texturePtr = await withPointerCallback<TTexture>((cb) {
Texture_buildRenderThread(
engine!,
width,
height,
depth,
levels,
importedTextureHandle ?? 0,
bitmask,
TTextureSamplerType.values[textureSamplerType.index],
TTextureFormat.values[textureFormat.index],
cb);
});
if (texturePtr == nullptr) {
throw Exception("Failed to create texture");
}
return FFITexture(
engine!,
texturePtr,
);
}
// return FFIRenderTarget(renderTarget, app);
// }
Future<TextureSampler> createTextureSampler(
{TextureMinFilter minFilter = TextureMinFilter.LINEAR,
TextureMagFilter magFilter = TextureMagFilter.LINEAR,
TextureWrapMode wrapS = TextureWrapMode.CLAMP_TO_EDGE,
TextureWrapMode wrapT = TextureWrapMode.CLAMP_TO_EDGE,
TextureWrapMode wrapR = TextureWrapMode.CLAMP_TO_EDGE,
double anisotropy = 0.0,
TextureCompareMode compareMode = TextureCompareMode.NONE,
TextureCompareFunc compareFunc = TextureCompareFunc.LESS_EQUAL}) async {
final samplerPtr = TextureSampler_create();
TextureSampler_setMinFilter(
samplerPtr, TSamplerMinFilter.values[minFilter.index]);
TextureSampler_setMagFilter(
samplerPtr, TSamplerMagFilter.values[magFilter.index]);
TextureSampler_setWrapModeS(
samplerPtr, TSamplerWrapMode.values[wrapS.index]);
TextureSampler_setWrapModeT(
samplerPtr, TSamplerWrapMode.values[wrapT.index]);
TextureSampler_setWrapModeR(
samplerPtr, TSamplerWrapMode.values[wrapR.index]);
if (anisotropy > 0) {
TextureSampler_setAnisotropy(samplerPtr, anisotropy);
}
TextureSampler_setCompareMode(
samplerPtr,
TSamplerCompareMode.values[compareMode.index],
TSamplerCompareFunc.values[compareFunc.index]);
return FFITextureSampler(samplerPtr);
}
///
///
///
Future<LinearImage> decodeImage(Uint8List data) async {
final name = "image";
var ptr = Image_decode(
data.address,
data.length,
name.toNativeUtf8().cast<Char>(),
);
if (ptr == nullptr) {
throw Exception("Failed to decode image");
}
return FFILinearImage(ptr);
}
///
/// Creates an (empty) imge with the given dimensions.
///
Future<LinearImage> createImage(int width, int height, int channels) async {
final ptr = Image_createEmpty(width, height, channels);
return FFILinearImage(ptr);
}
///
///
///
Future<Material> createMaterial(Uint8List data) async {
var ptr = await withPointerCallback<TMaterial>((cb) {
Engine_buildMaterialRenderThread(engine!, data.address, data.length, cb);
});
return FFIMaterial(ptr, this);
}
///
///
///
Future<MaterialInstance> createUbershaderMaterialInstance(
{bool doubleSided = false,
bool unlit = false,
bool hasVertexColors = false,
bool hasBaseColorTexture = false,
bool hasNormalTexture = false,
bool hasOcclusionTexture = false,
bool hasEmissiveTexture = false,
bool useSpecularGlossiness = false,
AlphaMode alphaMode = AlphaMode.OPAQUE,
bool enableDiagnostics = false,
bool hasMetallicRoughnessTexture = false,
int metallicRoughnessUV = 0,
int baseColorUV = 0,
bool hasClearCoatTexture = false,
int clearCoatUV = 0,
bool hasClearCoatRoughnessTexture = false,
int clearCoatRoughnessUV = 0,
bool hasClearCoatNormalTexture = false,
int clearCoatNormalUV = 0,
bool hasClearCoat = false,
bool hasTransmission = false,
bool hasTextureTransforms = false,
int emissiveUV = 0,
int aoUV = 0,
int normalUV = 0,
bool hasTransmissionTexture = false,
int transmissionUV = 0,
bool hasSheenColorTexture = false,
int sheenColorUV = 0,
bool hasSheenRoughnessTexture = false,
int sheenRoughnessUV = 0,
bool hasVolumeThicknessTexture = false,
int volumeThicknessUV = 0,
bool hasSheen = false,
bool hasIOR = false,
bool hasVolume = false}) async {
final key = Struct.create<TMaterialKey>();
key.doubleSided = doubleSided;
key.unlit = unlit;
key.hasVertexColors = hasVertexColors;
key.hasBaseColorTexture = hasBaseColorTexture;
key.hasNormalTexture = hasNormalTexture;
key.hasOcclusionTexture = hasOcclusionTexture;
key.hasEmissiveTexture = hasEmissiveTexture;
key.useSpecularGlossiness = useSpecularGlossiness;
key.alphaMode = alphaMode.index;
key.enableDiagnostics = enableDiagnostics;
key.unnamed.unnamed.hasMetallicRoughnessTexture =
hasMetallicRoughnessTexture;
key.unnamed.unnamed.metallicRoughnessUV = 0;
key.baseColorUV = baseColorUV;
key.hasClearCoatTexture = hasClearCoatTexture;
key.clearCoatUV = clearCoatUV;
key.hasClearCoatRoughnessTexture = hasClearCoatRoughnessTexture;
key.clearCoatRoughnessUV = clearCoatRoughnessUV;
key.hasClearCoatNormalTexture = hasClearCoatNormalTexture;
key.clearCoatNormalUV = clearCoatNormalUV;
key.hasClearCoat = hasClearCoat;
key.hasTransmission = hasTransmission;
key.hasTextureTransforms = hasTextureTransforms;
key.emissiveUV = emissiveUV;
key.aoUV = aoUV;
key.normalUV = normalUV;
key.hasTransmissionTexture = hasTransmissionTexture;
key.transmissionUV = transmissionUV;
key.hasSheenColorTexture = hasSheenColorTexture;
key.sheenColorUV = sheenColorUV;
key.hasSheenRoughnessTexture = hasSheenRoughnessTexture;
key.sheenRoughnessUV = sheenRoughnessUV;
key.hasVolumeThicknessTexture = hasVolumeThicknessTexture;
key.volumeThicknessUV = volumeThicknessUV;
key.hasSheen = hasSheen;
key.hasIOR = hasIOR;
key.hasVolume = hasVolume;
final materialInstance = await withPointerCallback<TMaterialInstance>((cb) {
MaterialProvider_createMaterialInstanceRenderThread(
ubershaderMaterialProvider, key.address, cb);
});
if (materialInstance == nullptr) {
throw Exception("Failed to create material instance");
}
var instance = FFIMaterialInstance(materialInstance, this);
return instance;
}
///
///
///
Future<FFIMaterialInstance> createUnlitMaterialInstance() async {
final instance = await createUbershaderMaterialInstance(unlit: true);
return instance as FFIMaterialInstance;
}
FFIMaterial? _gridMaterial;
Future<FFIMaterial> get gridMaterial async {
_gridMaterial ??= FFIMaterial(Material_createGridMaterial(), this);
return _gridMaterial!;
}
///
///
///
@override
Future<ThermionAsset> createGeometry(Geometry geometry,
{List<MaterialInstance>? materialInstances,
bool keepData = false}) async {
var assetPtr = await withPointerCallback<TSceneAsset>((callback) {
var ptrList = Int64List(materialInstances?.length ?? 0);
if (materialInstances != null && materialInstances.isNotEmpty) {
ptrList.setRange(
0,
materialInstances.length,
materialInstances
.cast<FFIMaterialInstance>()
.map((mi) => mi.pointer.address)
.toList());
}
return SceneAsset_createGeometryRenderThread(
engine,
geometry.vertices.address,
geometry.vertices.length,
geometry.normals.address,
geometry.normals.length,
geometry.uvs.address,
geometry.uvs.length,
geometry.indices.address,
geometry.indices.length,
geometry.primitiveType.index,
ptrList.address.cast<Pointer<TMaterialInstance>>(),
ptrList.length,
callback);
});
if (assetPtr == nullptr) {
throw Exception("Failed to create geometry");
}
var asset = FFIAsset(assetPtr, this);
return asset;
}
///
///
///
Future<MaterialInstance> getMaterialInstanceAt(
ThermionEntity entity, int index) async {
final instancePtr = RenderableManager_getMaterialInstanceAt(
renderableManager, entity, index);
final instance = FFIMaterialInstance(instancePtr, this);
return instance;
}
///
///
///
@override
Future render() async {
RenderTicker_renderRenderThread(renderTicker, 0);
}
@override
Future register(
covariant FFISwapChain swapChain, covariant FFIView view) async {
_viewMappings[view] = swapChain;
}
final _hooks = <Future Function()>[];
@override
Future registerRequestFrameHook(Future Function() hook) async {
if (!_hooks.contains(hook)) {
_hooks.add(hook);
}
}
@override
Future unregisterRequestFrameHook(Future Function() hook) async {
if (_hooks.contains(hook)) {
_hooks.remove(hook);
}
}
///
///
///
@override
Future requestFrame() async {
for (final hook in _hooks) {
await hook.call();
}
final completer = Completer();
final callback = NativeCallable<Void Function()>.listener(() {
completer.complete(true);
});
RenderLoop_requestAnimationFrame(callback.nativeFunction.cast());
try {
await completer.future.timeout(Duration(seconds: 1));
} catch (err) {
print("WARNING - render call timed out");
}
}
}

View File

@@ -1,14 +1,12 @@
import 'dart:async';
import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_asset.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/entities.dart';
import 'thermion_dart.g.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
import 'ffi_view.dart';
class FFIGizmo extends FFIAsset implements GizmoAsset {
final Set<ThermionEntity> gizmoEntities;
late NativeCallable<GizmoPickCallbackFunction> _nativeCallback;

View File

@@ -1,7 +1,7 @@
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_asset.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_filament_app.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_render_target.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/scene.dart';
import 'package:thermion_dart/src/filament/src/scene.dart';
import 'callbacks.dart';

View File

@@ -2,8 +2,8 @@ import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_filament_app.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_render_target.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_scene.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_swapchain.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/layers.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/shared_types.dart';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/src/filament/src/shared_types.dart';
import 'callbacks.dart';
import 'ffi_camera.dart';
@@ -66,10 +66,6 @@ class FFIView extends View {
View_setPostProcessing(view, enabled);
}
Future setRenderable(bool renderable, FFISwapChain swapChain) async {
throw UnimplementedError();
}
@override
Future setFrustumCullingEnabled(bool enabled) async {
View_setFrustumCullingEnabled(view, enabled);
@@ -84,7 +80,8 @@ class FFIView extends View {
@override
Future setToneMapper(ToneMapper mapper) async {
View_setToneMappingRenderThread(view, app.engine, TToneMapping.values[mapper.index].value);
View_setToneMappingRenderThread(
view, app.engine, TToneMapping.values[mapper.index].value);
}
Future setStencilBufferEnabled(bool enabled) async {
@@ -111,7 +108,7 @@ class FFIView extends View {
Future setScene(covariant FFIScene scene) async {
await withVoidCallback((cb) => View_setScene(view, scene.scene));
}
@override
Future setLayerVisibility(VisibilityLayers layer, bool visible) async {
View_setLayerEnabled(view, layer.value, visible);

View File

@@ -0,0 +1,13 @@
import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_asset.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_filament_app.dart';
class GridOverlay extends FFIAsset {
GridOverlay(super.asset, super.app);
static Future<GridOverlay> create(FFIFilamentApp app) async {
final gridMaterial = await app.gridMaterial;
final asset = SceneAsset_createGrid(app.engine, gridMaterial.pointer);
return GridOverlay(asset, app);
}
}

View File

@@ -25,6 +25,12 @@ external ffi.Pointer<TMaterialInstance> Material_createInstance(
ffi.Pointer<TMaterial> tMaterial,
);
@ffi.Native<ffi.Pointer<TMaterial> Function()>(isLeaf: true)
external ffi.Pointer<TMaterial> Material_createImageMaterial();
@ffi.Native<ffi.Pointer<TMaterial> Function()>(isLeaf: true)
external ffi.Pointer<TMaterial> Material_createGridMaterial();
@ffi.Native<ffi.Bool Function(ffi.Pointer<TMaterial>, ffi.Pointer<ffi.Char>)>(
isLeaf: true)
external bool Material_hasParameter(
@@ -1227,6 +1233,11 @@ external double Camera_getFov(
bool horizontal,
);
@ffi.Native<ffi.Double Function(ffi.Pointer<TCamera>)>(isLeaf: true)
external double Camera_getFocusDistance(
ffi.Pointer<TCamera> camera,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TCamera>, ffi.Float)>(isLeaf: true)
external void Camera_setFocusDistance(
ffi.Pointer<TCamera> camera,
@@ -1324,11 +1335,12 @@ external void TransformManager_setTransform(
double4x4 transform,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TTransformManager>, EntityId)>(
@ffi.Native<ffi.Void Function(ffi.Pointer<TTransformManager>, EntityId, Aabb3)>(
isLeaf: true)
external void TransformManager_transformToUnitCube(
ffi.Pointer<TTransformManager> tTransformManager,
int entityId,
Aabb3 boundingBox,
);
@ffi.Native<
@@ -1458,7 +1470,7 @@ external void RenderTicker_render(
ffi.Void Function(ffi.Pointer<TRenderTicker>, ffi.Pointer<TSwapChain>,
ffi.Pointer<ffi.Pointer<TView>>, ffi.Uint8)>(isLeaf: true)
external void RenderTicker_setRenderable(
ffi.Pointer<TRenderTicker> tFilamentRender,
ffi.Pointer<TRenderTicker> tRenderTicker,
ffi.Pointer<TSwapChain> swapChain,
ffi.Pointer<ffi.Pointer<TView>> views,
int numViews,
@@ -2719,15 +2731,15 @@ external void TextureSampler_destroyRenderThread(
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TSceneManager>,
ffi.Pointer<TAnimationManager>,
EntityId,
ffi.Int,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Bool)>>)>(
isLeaf: true)
external void set_bone_transform_render_thread(
ffi.Pointer<TSceneManager> sceneManager,
external void AnimationManager_setBoneTransformRenderThread(
ffi.Pointer<TAnimationManager> tAnimationManager,
int asset,
int skinIndex,
int boneIndex,
@@ -2736,10 +2748,10 @@ external void set_bone_transform_render_thread(
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TSceneManager>, EntityId,
ffi.Void Function(ffi.Pointer<TAnimationManager>, EntityId,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void reset_to_rest_pose_render_thread(
ffi.Pointer<TSceneManager> sceneManager,
external void AnimationManager_resetToRestPoseRenderThread(
ffi.Pointer<TAnimationManager> tAnimationManager,
int entityId,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> callback,
);
@@ -3181,6 +3193,14 @@ external ffi.Pointer<TSceneAsset> SceneAsset_loadGltf(
int numInstances,
);
@ffi.Native<
ffi.Pointer<TSceneAsset> Function(
ffi.Pointer<TEngine>, ffi.Pointer<TMaterial>)>(isLeaf: true)
external ffi.Pointer<TSceneAsset> SceneAsset_createGrid(
ffi.Pointer<TEngine> tEngine,
ffi.Pointer<TMaterial> tMaterial,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external void SceneAsset_destroy(
ffi.Pointer<TSceneAsset> tSceneAsset,

View File

@@ -1,3 +1,4 @@
library;
export 'src/thermion_viewer_ffi.dart' show ThermionViewerFFI;

View File

@@ -1,78 +0,0 @@
import 'package:thermion_dart/src/viewer/src/shared_types/engine.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FilamentConfig<T, U> {
final Backend backend;
final T? renderCallback;
final U? renderCallbackOwner;
final U resourceLoader;
final U? platform;
final U? driver;
final U? sharedContext;
final String uberArchivePath;
final int stereoscopicEyeCount;
final bool disableHandleUseAfterFreeCheck;
FilamentConfig(
{required this.backend,
required this.resourceLoader,
required this.uberArchivePath,
this.renderCallback,
this.renderCallbackOwner,
this.platform,
this.driver,
this.sharedContext,
this.stereoscopicEyeCount = 1,
this.disableHandleUseAfterFreeCheck = false});
}
abstract class FilamentApp<T> {
final T engine;
final T gltfAssetLoader;
final T gltfResourceLoader;
final T renderer;
final T transformManager;
final T lightManager;
final T renderableManager;
final T ubershaderMaterialProvider;
FilamentApp(
{required this.engine,
required this.gltfAssetLoader,
required this.gltfResourceLoader,
required this.renderer,
required this.transformManager,
required this.lightManager,
required this.renderableManager,
required this.ubershaderMaterialProvider
});
///
///
///
Future<SwapChain> createHeadlessSwapChain(int width, int height,
{bool hasStencilBuffer = false});
///
///
///
Future<SwapChain> createSwapChain(T handle, {bool hasStencilBuffer = false});
///
///
///
Future destroySwapChain(SwapChain swapChain);
///
///
///
Future destroy();
///
///
///
Future<RenderTarget> createRenderTarget(
int width, int height, { covariant Texture? color, covariant Texture? depth });
}

View File

@@ -1,96 +0,0 @@
library;
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
export 'geometry.dart';
export 'gltf.dart';
export 'light_options.dart';
// handle manipulate an Entity
typedef ThermionEntity = int;
abstract class ThermionAsset {
///
///
///
ThermionEntity get entity;
///
///
///
Future<List<ThermionEntity>> getChildEntities();
///
///
///
Future setMaterialInstanceAt(covariant MaterialInstance instance);
///
/// Renders an outline around [entity] with the given color.
///
Future setStencilHighlight(
{double r = 1.0, double g = 0.0, double b = 0.0, int? entityIndex});
///
/// Removes the outline around [entity]. Noop if there was no highlight.
///
Future removeStencilHighlight();
///
/// When visible is [true], renders the bounding box.
///
Future setBoundingBoxVisibility(bool visible);
///
///
///
Future<ThermionAsset> getInstance(int index);
///
/// Create a new instance of [entity].
/// Instances are not automatically added to the scene; you must
/// call [addToScene].
///
Future<ThermionAsset> createInstance(
{covariant List<MaterialInstance>? materialInstances = null});
///
/// Returns the number of instances associated with this asset.
///
Future<int> getInstanceCount();
///
/// Returns all instances of associated with this asset.
///
Future<List<ThermionAsset>> getInstances();
Future setCastShadows(bool castShadows);
Future setReceiveShadows(bool castShadows);
}
enum Axis {
X(const [1.0, 0.0, 0.0]),
Y(const [0.0, 1.0, 0.0]),
Z(const [0.0, 0.0, 1.0]);
const Axis(this.vector);
final List<double> vector;
Vector3 asVector() => Vector3(vector[0], vector[1], vector[2]);
}
enum GizmoPickResultType { AxisX, AxisY, AxisZ, Parent, None }
enum GizmoType { translation, rotation }
abstract class GizmoAsset extends ThermionAsset {
Future pick(int x, int y,
{Future Function(GizmoPickResultType axis, Vector3 coords)? handler});
Future highlight(Axis axis);
Future unhighlight();
bool isNonPickable(ThermionEntity entity);
bool isGizmoEntity(ThermionEntity entity);
}

View File

@@ -1,3 +0,0 @@
import 'package:thermion_dart/thermion_dart.dart';
final ThermionEntity FILAMENT_ENTITY_NULL = 0;

View File

@@ -1,23 +1,28 @@
import 'package:thermion_dart/src/viewer/src/shared_types/layers.dart';
import '../../utils/src/gizmo.dart';
import 'shared_types/shared_types.dart';
export 'shared_types/shared_types.dart';
import 'dart:math';
import 'package:thermion_dart/src/filament/src/filament_app.dart';
import '../../filament/src/shared_types.dart';
import 'dart:typed_data';
import 'package:vector_math/vector_math_64.dart';
import 'dart:async';
import 'package:animation_tools_dart/animation_tools_dart.dart';
///
/// A high-level interface for interacting with a 3D scene.
/// This broadly maps to a single scene/view
/// A (high-level) interface for a 3D scene.
///
/// Use this to add/remove assets, lights and cameras.
///
/// Multiple instances can be created; each will correspond
/// broadly to a single Filament Scene/View.
///
/// If you know yhat you are doing, you can use a lower level interface by
/// using the methods directly via FilamentApp.instance;
///
abstract class ThermionViewer {
///
/// Whether the controller is currently rendering at [framerate].
///
///
View get view;
///
/// If [true], this Viewer should render itself
///
bool get rendering;
@@ -31,29 +36,6 @@ abstract class ThermionViewer {
///
Future render();
///
/// Requests a single frame to be rendered. This is only intended to be used internally.
///
Future requestFrame();
///
/// Render a single frame and return the captured image as a pixel buffer.
///
Future<List<Uint8List>> capture(
covariant List<
({View view, SwapChain? swapChain, RenderTarget? renderTarget})>
targets);
///
///
///
Future<View> createView();
///
///
///
Future<View> getViewAt(int index);
///
///
///
@@ -119,32 +101,6 @@ abstract class ThermionViewer {
///
Future removeIbl();
///
/// Add a light to the scene.
/// See LightManager.h for details
/// Note that [sunAngularRadius] is in degrees,
/// whereas [spotLightConeInner] and [spotLightConeOuter] are in radians
///
@Deprecated(
"This will be removed in future versions. Use addDirectLight instead.")
Future<ThermionEntity> addLight(
LightType type,
double colour,
double intensity,
double posX,
double posY,
double posZ,
double dirX,
double dirY,
double dirZ,
{double falloffRadius = 1.0,
double spotLightConeInner = pi / 8,
double spotLightConeOuter = pi / 4,
double sunAngularRadius = 0.545,
double sunHaloSize = 10.0,
double sunHaloFallof = 80.0,
bool castShadows = true});
///
/// Adds a direct light to the scene.
/// See LightManager.h for details
@@ -198,139 +154,6 @@ abstract class ThermionViewer {
Future<ThermionAsset> loadGltf(String path, String relativeResourcePath,
{bool keepData = false});
///
/// Set the weights for all morph targets in [entity] to [weights].
/// Note that [weights] must contain values for ALL morph targets, but no exception will be thrown if you don't do so (you'll just get incorrect results).
/// If you only want to set one value, set all others to zero (check [getMorphTargetNames] if you need the get a list of all morph targets).
/// IMPORTANT - this accepts the actual ThermionEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name).
/// Use [getChildEntityByName] if you are setting the weights for a child mesh.
///
Future setMorphTargetWeights(ThermionEntity entity, List<double> weights);
///
/// Gets the names of all morph targets for the child renderable [childEntity] under [entity].
///
Future<List<String>> getMorphTargetNames(
covariant ThermionAsset asset, ThermionEntity childEntity);
///
/// Gets the names of all bones for the armature at [skinIndex] under the specified [entity].
///
Future<List<String>> getBoneNames(covariant ThermionAsset asset,
{int skinIndex = 0});
///
/// Gets the names of all glTF animations embedded in the specified entity.
///
Future<List<String>> getAnimationNames(covariant ThermionAsset asset);
///
/// Returns the length (in seconds) of the animation at the given index.
///
Future<double> getAnimationDuration(
covariant ThermionAsset asset, int animationIndex);
///
/// Construct animation(s) for every entity under [asset]. If [targetMeshNames] is provided, only entities with matching names will be animated.
/// [MorphTargetAnimation] for an explanation as to how to construct the animation frame data.
/// This method will check the morph target names specified in [animation] against the morph target names that actually exist exist under [meshName] in [entity],
/// throwing an exception if any cannot be found.
/// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated.
///
Future setMorphAnimationData(
covariant ThermionAsset asset, MorphAnimationData animation,
{List<String>? targetMeshNames});
///
/// Clear all current morph animations for [entity].
///
Future clearMorphAnimationData(ThermionEntity entity);
///
/// Resets all bones in the given entity to their rest pose.
/// This should be done before every call to addBoneAnimation.
///
Future resetBones(ThermionAsset asset);
///
/// Enqueues and plays the [animation] for the specified bone(s).
/// By default, frame data is interpreted as being in *parent* bone space;
/// a 45 degree around Y means the bone will rotate 45 degrees around the
/// Y axis of the parent bone *in its current orientation*.
/// (i.e NOT the parent bone's rest position!).
/// Currently, only [Space.ParentBone] and [Space.Model] are supported; if you want
/// to transform to another space, you will need to do so manually.
///
/// [fadeInInSecs]/[fadeOutInSecs]/[maxDelta] are used to cross-fade between
/// the current active glTF animation ("animation1") and the animation you
/// set via this method ("animation2"). The bone orientations will be
/// linearly interpolated between animation1 and animation2; at time 0,
/// the orientation will be 100% animation1, at time [fadeInInSecs], the
/// animation will be ((1 - maxDelta) * animation1) + (maxDelta * animation2).
/// This will be applied in reverse after [fadeOutInSecs].
///
///
Future addBoneAnimation(ThermionAsset asset, BoneAnimationData animation,
{int skinIndex = 0,
double fadeInInSecs = 0.0,
double fadeOutInSecs = 0.0,
double maxDelta = 1.0});
///
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
/// The returned entity is only intended for use with [getWorldTransform].
///
Future<ThermionEntity> getBone(covariant ThermionAsset asset, int boneIndex,
{int skinIndex = 0});
///
/// Gets the local (relative to parent) transform for [entity].
///
Future<Matrix4> getLocalTransform(ThermionEntity entity);
///
/// Gets the world transform for [entity].
///
Future<Matrix4> getWorldTransform(ThermionEntity entity);
///
/// Gets the inverse bind (pose) matrix for the bone.
/// Note that [parent] must be the ThermionEntity returned by [loadGlb/loadGltf], not any other method ([getChildEntity] etc).
/// This is because all joint information is internally stored with the parent entity.
///
Future<Matrix4> getInverseBindMatrix(
covariant ThermionAsset asset, int boneIndex,
{int skinIndex = 0});
///
/// Sets the transform (relative to its parent) for [entity].
///
Future setTransform(ThermionEntity entity, Matrix4 transform);
///
/// Sets multiple transforms (relative to parent) simultaneously for [entity].
/// Uses mutex to ensure that transform updates aren't split across frames.
///
Future queueTransformUpdates(
List<ThermionEntity> entities, List<Matrix4> transforms);
///
/// Updates the bone matrices for [entity] (which must be the ThermionEntity
/// returned by [loadGlb/loadGltf]).
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
/// instance of the relevant FilamentInstance (which uses the local
/// bone transform and the inverse bind matrix to set the bone matrix).
///
Future updateBoneMatrices(ThermionEntity entity);
///
/// Directly set the bone matrix for the bone at the given index.
/// Don't call this manually unless you know what you're doing.
///
Future setBoneTransform(
ThermionEntity entity, int boneIndex, Matrix4 transform,
{int skinIndex = 0});
///
/// Destroys [asset] and all underlying resources
/// (including instances, but excluding any manually created material instances).
@@ -343,72 +166,6 @@ abstract class ThermionViewer {
///
Future destroyAssets();
///
/// Schedules the glTF animation at [index] in [asset] to start playing on the next frame.
///
Future playAnimation(ThermionAsset asset, int index,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0,
double startOffset = 0.0});
///
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
///
Future playAnimationByName(covariant ThermionAsset asset, String name,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0});
///
///
///
Future setGltfAnimationFrame(
covariant ThermionAsset asset, int index, int animationFrame);
///
///
///
Future stopAnimation(covariant ThermionAsset asset, int animationIndex);
///
///
///
Future stopAnimationByName(covariant ThermionAsset asset, String name);
///
/// Sets the current scene camera to the glTF camera under [name] in [entity].
///
Future setCamera(ThermionEntity entity, String? name);
///
/// Sets the current scene camera to the main camera (which is always available and added to every scene by default).
///
Future setMainCamera();
///
/// Returns the entity associated with the main camera. You probably never need this; use getMainCamera instead.
///
Future<ThermionEntity> getMainCameraEntity();
///
/// Returns the Camera instance for the main camera.
///
Future<Camera> getMainCamera();
///
/// Sets the horizontal field of view (if [horizontal] is true) or vertical field of view for the currently active camera to [degrees].
/// The aspect ratio of the current viewport is used.
///
Future setCameraFov(double degrees, {bool horizontal = true});
///
/// Gets the field of view (in degrees).
///
Future<double> getCameraFov(bool horizontal);
///
/// Sets the tone mapping (requires postprocessing).
///
@@ -419,115 +176,11 @@ abstract class ThermionViewer {
///
Future setBloom(bool enabled, double strength);
///
/// Sets the focal length of the camera. Default value is 28.0.
///
Future setCameraFocalLength(double focalLength);
///
/// Sets the distance (in world units) to the near/far planes for the active camera. Default values are 0.05/1000.0. See Camera.h for details.
///
Future setCameraCulling(double near, double far);
///
/// Get the distance (in world units) to the near plane for the active camera.
///
@Deprecated("Use getCameraNear")
Future<double> getCameraCullingNear();
///
/// Get the distance (in world units) to the near plane for the active camera.
///
Future<double> getCameraNear();
///
/// Get the distance (in world units) to the far culling plane for the active camera.
///
Future<double> getCameraCullingFar();
///
/// Sets the focus distance for the camera.
///
Future setCameraFocusDistance(double focusDistance);
///
/// Get the camera position in world space.
///
Future<Vector3> getCameraPosition();
///
/// Get the camera's model matrix.
///
Future<Matrix4> getCameraModelMatrix();
///
/// Get the camera's view matrix. See Camera.h for more details.
///
Future<Matrix4> getCameraViewMatrix();
///
/// Get the camera's projection matrix. See Camera.h for more details.
///
Future<Matrix4> getCameraProjectionMatrix();
///
/// Get the camera's culling projection matrix. See Camera.h for more details.
///
Future<Matrix4> getCameraCullingProjectionMatrix();
///
/// Get the camera's culling frustum in world space. Returns a (vector_math) [Frustum] instance where plane0-plane6 define the left, right, bottom, top, far and near planes respectively.
/// See Camera.h and (filament) Frustum.h for more details.
///
Future<Frustum> getCameraFrustum();
///
/// Set the camera position in world space. Note this is not persistent - any viewport navigation will reset the camera transform.
///
Future setCameraPosition(double x, double y, double z);
///
/// Get the camera rotation matrix.
///
Future<Matrix3> getCameraRotation();
///
/// Repositions the camera to the last vertex of the bounding box of [entity], looking at the penultimate vertex.
///
Future moveCameraToAsset(ThermionEntity entity);
///
/// Enables/disables frustum culling.
///
Future setViewFrustumCulling(bool enabled);
///
/// Sets the camera exposure.
///
Future setCameraExposure(
double aperture, double shutterSpeed, double sensitivity);
///
/// Rotate the camera by [rads] around the given axis.
///
Future setCameraRotation(Quaternion quaternion);
///
/// Sets the camera model matrix.
///
@Deprecated("Will be superseded by setCameraModelMatrix4")
Future setCameraModelMatrix(List<double> matrix);
///
/// Sets the camera model matrix.
///
Future setCameraModelMatrix4(Matrix4 matrix);
///
/// Scale [entity] to fit within the unit cube.
///
Future transformToUnitCube(ThermionEntity entity);
///
/// Set the world space position for [lightEntity] to the given coordinates.
///
@@ -539,18 +192,6 @@ abstract class ThermionViewer {
///
Future setLightDirection(ThermionEntity lightEntity, Vector3 direction);
///
/// TODO
///
Future queuePositionUpdateFromViewportCoords(
ThermionEntity entity, double x, double y);
///
/// TODO
///
Future queueRelativePositionUpdateWorldAxis(ThermionEntity entity,
double viewportX, double viewportY, double x, double y, double z);
///
/// Enable/disable postprocessing effects (anti-aliasing, tone mapping, bloom). Disabled by default.
///
@@ -576,16 +217,6 @@ abstract class ThermionViewer {
///
Future setAntiAliasing(bool msaa, bool fxaa, bool taa);
///
/// Adds a single [entity] to the scene.
///
Future addEntityToScene(ThermionEntity entity);
///
/// Removes a single [entity] from the scene.
///
Future removeAssetFromScene(ThermionEntity entity);
///
/// Hit test the viewport at the given coordinates. If the coordinates intersect
/// with a renderable entity, [resultHandler] will be called.
@@ -599,37 +230,6 @@ abstract class ThermionViewer {
///
String? getNameForEntity(ThermionEntity entity);
///
/// Returns all child entities under [asset].
///
Future<List<ThermionEntity>> getChildEntities(covariant ThermionAsset asset);
///
/// Finds the child entity named [childName] associated with the given parent.
/// Usually, [parent] will be the return value from [loadGlb]/[loadGltf] and [childName] will be the name of a node/mesh.
///
Future<ThermionEntity?> getChildEntity(
covariant ThermionAsset asset, String childName);
///
/// An [entity] will only be animatable after an animation component is attached.
/// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance.
///
Future addAnimationComponent(ThermionEntity entity);
///
/// Removes an animation component from [entity].
///
Future removeAnimationComponent(ThermionEntity entity);
///
/// Creates a (renderable) entity with the specified geometry and adds to the scene.
/// If [keepData] is true, the source data will not be released.
///
Future<ThermionAsset> createGeometry(Geometry geometry,
{covariant List<MaterialInstance>? materialInstances,
bool keepData = false});
///
/// Gets the parent entity of [entity]. Returns null if the entity has no parent.
///
@@ -672,131 +272,15 @@ abstract class ThermionViewer {
Future<Aabb2> getViewportBoundingBox(ThermionEntity entity);
///
/// Toggles the visibility of the respective layer.
///
Future setLayerVisibility(VisibilityLayers layer, bool visible);
///
/// All renderable entities are assigned a layer mask.
///
/// By calling [setLayerVisibility], all renderable entities allocated to
/// the given layer can be efficiently hidden/revealed.
///
/// By default, all renderable entities are assigned to layer 0 (and this
/// layer is enabled by default). Call [setVisibilityLayer] to change the
/// layer for the specified entity.
///
/// Note that we currently also assign gizmos to layer 1 (enabled by default)
/// and the world grid to layer 2 (disabled by default). We suggest you avoid
/// using these layers.
///
Future setVisibilityLayer(ThermionEntity entity, VisibilityLayers layer);
///
///
///
Future showGridOverlay({covariant Material? material});
Future showGridOverlay();
///
///
///
Future removeGridOverlay();
///
///
///
Future<Texture> createTexture(int width, int height,
{int depth = 1,
int levels = 1,
TextureSamplerType textureSamplerType = TextureSamplerType.SAMPLER_2D,
TextureFormat textureFormat = TextureFormat.RGBA32F});
///
///
///
Future<TextureSampler> createTextureSampler(
{TextureMinFilter minFilter = TextureMinFilter.LINEAR,
TextureMagFilter magFilter = TextureMagFilter.LINEAR,
TextureWrapMode wrapS = TextureWrapMode.CLAMP_TO_EDGE,
TextureWrapMode wrapT = TextureWrapMode.CLAMP_TO_EDGE,
TextureWrapMode wrapR = TextureWrapMode.CLAMP_TO_EDGE,
double anisotropy = 0.0,
TextureCompareMode compareMode = TextureCompareMode.NONE,
TextureCompareFunc compareFunc = TextureCompareFunc.LESS_EQUAL});
///
/// Decodes the specified image data.
///
Future<LinearImage> decodeImage(Uint8List data);
///
/// Creates an (empty) imge with the given dimensions.
///
Future<LinearImage> createImage(int width, int height, int channels);
///
///
///
Future<Material> createMaterial(Uint8List data);
///
///
///
Future<MaterialInstance> createUbershaderMaterialInstance({
bool doubleSided = false,
bool unlit = false,
bool hasVertexColors = false,
bool hasBaseColorTexture = false,
bool hasNormalTexture = false,
bool hasOcclusionTexture = false,
bool hasEmissiveTexture = false,
bool useSpecularGlossiness = false,
AlphaMode alphaMode = AlphaMode.OPAQUE,
bool enableDiagnostics = false,
bool hasMetallicRoughnessTexture = false,
int metallicRoughnessUV = -1,
int baseColorUV = -1,
bool hasClearCoatTexture = false,
int clearCoatUV = -1,
bool hasClearCoatRoughnessTexture = false,
int clearCoatRoughnessUV = -1,
bool hasClearCoatNormalTexture = false,
int clearCoatNormalUV = -1,
bool hasClearCoat = false,
bool hasTransmission = false,
bool hasTextureTransforms = false,
int emissiveUV = -1,
int aoUV = -1,
int normalUV = -1,
bool hasTransmissionTexture = false,
int transmissionUV = -1,
bool hasSheenColorTexture = false,
int sheenColorUV = -1,
bool hasSheenRoughnessTexture = false,
int sheenRoughnessUV = -1,
bool hasVolumeThicknessTexture = false,
int volumeThicknessUV = -1,
bool hasSheen = false,
bool hasIOR = false,
bool hasVolume = false,
});
///
///
///
Future destroyMaterialInstance(covariant MaterialInstance materialInstance);
///
///
///
Future<MaterialInstance> createUnlitMaterialInstance();
///
///
///
Future<MaterialInstance> getMaterialInstanceAt(
ThermionEntity entity, int index);
///
///
///
@@ -817,52 +301,8 @@ abstract class ThermionViewer {
///
Future<Camera> getActiveCamera();
///
///
///
Future registerRequestFrameHook(Future Function() hook);
///
///
///
Future unregisterRequestFrameHook(Future Function() hook);
///
///
///
int getCameraCount();
///
/// Returns the camera specified by the given index. Note that the camera at
/// index 0 is always the main camera; this cannot be destroyed.
///
/// Throws an exception if the index is out-of-bounds.
///
Camera getCameraAt(int index);
///
///
///
Future setCastShadows(ThermionEntity entity, bool castShadows);
///
///
///
Future<bool> isCastShadowsEnabled(ThermionEntity entity);
///
///
///
Future setReceiveShadows(ThermionEntity entity, bool receiveShadows);
///
///
///
Future<bool> isReceiveShadowsEnabled(ThermionEntity entity);
///
///
///
Future setClearOptions(
Vector4 clearColor, int clearStencil, bool clear, bool discard);
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ library thermion_flutter_js;
import 'dart:js_interop';
import '../../shared_types/shared_types.dart';
import '../../../../filament/src/shared_types.dart';
///
/// An extension type on [JSObject] that represents a

View File

@@ -1,7 +1,9 @@
library thermion_viewer;
export 'src/thermion_viewer_base.dart';
export '../filament/src/filament_app.dart';
export 'src/thermion_viewer_stub.dart'
if (dart.library.io) 'src/ffi/thermion_viewer_ffi.dart'
if (dart.library.js_interop) 'src/web_wasm/thermion_viewer_web_wasm.dart';
export 'src/shared_types/shared_types.dart';
export '../filament/src/shared_types.dart';

View File

@@ -1,5 +1,6 @@
library filament_dart;
export 'package:vector_math/vector_math_64.dart' hide Colors;
export 'src/viewer/viewer.dart';
export 'src/input/input.dart';
export 'src/utils/utils.dart';