feat! js_interop improvements

This commit is contained in:
Nick Fisher
2025-05-07 17:06:38 +08:00
parent 63e2dcd0ca
commit 2f16908992
159 changed files with 12989 additions and 8377 deletions

View File

@@ -1,4 +1,4 @@
export 'src/filament_app.dart';
export 'src/engine.dart';
export 'src/layers.dart';
export 'src/light_options.dart';
export 'src/interface/filament_app.dart';
export 'src/interface/engine.dart';
export 'src/interface/layers.dart';
export 'src/interface/light_options.dart';

View File

@@ -1,32 +0,0 @@
import 'dart:typed_data';
import '../../viewer/viewer.dart';
class Geometry {
final Float32List vertices;
final Uint16List indices;
final Float32List normals;
final Float32List uvs;
final PrimitiveType primitiveType;
Geometry(
this.vertices,
List<int> indices, {
Float32List? normals,
Float32List? uvs,
this.primitiveType = PrimitiveType.TRIANGLES,
}) : indices = Uint16List.fromList(indices),
normals = normals ?? Float32List(0),
uvs = uvs ?? Float32List(0) {
assert(this.uvs.length == 0 || this.uvs.length == (vertices.length ~/ 3 * 2), "Expected either zero or ${indices.length * 2} UVs, got ${this.uvs.length}");
}
void scale(double factor) {
for (int i = 0; i < vertices.length; i++) {
vertices[i] = vertices[i] * factor;
}
}
bool get hasNormals => normals.isNotEmpty;
bool get hasUVs => uvs.isNotEmpty;
}

View File

@@ -0,0 +1,358 @@
import 'dart:typed_data';
import 'package:vector_math/vector_math_64.dart' as v64;
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/interface/layers.dart';
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_scene.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/thermion_dart.dart';
class BackgroundImage extends ThermionAsset {
final ThermionAsset asset;
ThermionEntity get entity => asset.entity;
Texture? texture;
FFITextureSampler? sampler;
final MaterialInstance mi;
final FFIScene scene;
BackgroundImage._(
this.asset, this.scene, this.texture, this.sampler, this.mi);
///
///
///
Future destroy() async {
Scene_removeEntity(scene.scene, entity);
await texture?.dispose();
await sampler?.dispose();
await mi.destroy();
}
///
///
///
static Future<BackgroundImage> create(
ThermionViewer viewer, FFIScene scene) async {
var imageMaterialInstance =
await FilamentApp.instance!.createImageMaterialInstance();
var backgroundImage =
await viewer.createGeometry(GeometryHelper.fullscreenQuad());
await imageMaterialInstance.setParameterInt("showImage", 0);
var transform = Matrix4.identity();
await imageMaterialInstance.setParameterMat4(
"transform", transform);
await backgroundImage.setMaterialInstanceAt(imageMaterialInstance);
await scene.add(backgroundImage as FFIAsset);
return BackgroundImage._(
backgroundImage, scene, null, null, imageMaterialInstance);
}
///
///
///
Future setBackgroundColor(double r, double g, double b, double a) async {
await mi.setParameterFloat4("backgroundColor", r, g, b, a);
}
Future hideImage() async {
await mi.setParameterInt("showImage", 0);
}
///
///
///
Future setImage(Uint8List imageData) async {
final image = await FilamentApp.instance!.decodeImage(imageData);
texture ??= await FilamentApp.instance!.createTexture(
await image.getWidth(), await image.getHeight(),
textureFormat: TextureFormat.RGBA32F);
await texture!
.setLinearImage(image, PixelDataFormat.RGBA, PixelDataType.FLOAT);
sampler ??=
await FilamentApp.instance!.createTextureSampler() as FFITextureSampler;
await mi.setParameterTexture(
"image", texture as FFITexture, sampler as FFITextureSampler);
await setBackgroundColor(1, 1, 1, 0);
await mi.setParameterInt("showImage", 1);
}
///
///
///
@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 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(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(int animationIndex) {
// TODO: implement getAnimationDuration
throw UnimplementedError();
}
@override
Future<List<String>> getAnimationNames() {
// TODO: implement getAnimationNames
throw UnimplementedError();
}
@override
Future<ThermionEntity> getBone(int boneIndex, {int skinIndex = 0}) {
// TODO: implement getBone
throw UnimplementedError();
}
@override
Future<List<String>> getBoneNames({int skinIndex = 0}) {
// TODO: implement getBoneNames
throw UnimplementedError();
}
@override
Future<ThermionEntity?> getChildEntity(String childName) {
// TODO: implement getChildEntity
throw UnimplementedError();
}
@override
Future<Matrix4> getInverseBindMatrix(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({ThermionEntity? entity}) {
// TODO: implement getMorphTargetNames
throw UnimplementedError();
}
@override
Future<Matrix4> getWorldTransform({ThermionEntity? entity}) {
// TODO: implement getWorldTransform
throw UnimplementedError();
}
@override
Future playAnimation(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(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() {
// 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(int index, int animationFrame) {
// TODO: implement setGltfAnimationFrame
throw UnimplementedError();
}
@override
Future setMorphAnimationData(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(Matrix4 transform, {ThermionEntity? entity}) {
// TODO: implement setTransform
throw UnimplementedError();
}
@override
Future stopAnimation(int animationIndex) {
// TODO: implement stopAnimation
throw UnimplementedError();
}
@override
Future stopAnimationByName(String name) {
// TODO: implement stopAnimationByName
throw UnimplementedError();
}
@override
Future updateBoneMatrices(ThermionEntity entity) {
// TODO: implement updateBoneMatrices
throw UnimplementedError();
}
@override
Future<List<String>> getChildEntityNames() async {
return [];
}
@override
Future<bool> isCastShadowsEnabled({ThermionEntity? entity}) async {
return false;
}
@override
Future<bool> isReceiveShadowsEnabled({ThermionEntity? entity}) async {
return false;
}
@override
Future transformToUnitCube() {
// TODO: implement transformToUnitCube
throw UnimplementedError();
}
@override
Future<MaterialInstance> getMaterialInstanceAt(
{ThermionEntity? entity, int index = 0}) {
throw UnimplementedError();
}
ThermionAsset? get boundingBoxAsset => throw UnimplementedError();
@override
Future<ThermionAsset> createBoundingBoxAsset() {
throw UnimplementedError();
}
Future<v64.Aabb3> getBoundingBox() {
throw UnimplementedError();
}
}

View File

@@ -0,0 +1,978 @@
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:logging/logging.dart';
import 'package:thermion_dart/src/utils/src/matrix.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_material.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart' as v64;
class FFIAsset extends ThermionAsset {
///
///
///
final Pointer<TSceneAsset> asset;
///
///
///
final FFIFilamentApp app;
///
///
///
final Pointer<TAnimationManager> animationManager;
///
///
///
FFIAsset? _highlight;
///
///
///
final bool isInstance;
///
///
///
late final ThermionEntity entity;
late final _logger = Logger(this.runtimeType.toString());
///
///
///
FFIAsset(this.asset, this.app, this.animationManager,
{this.isInstance = false}) {
entity = SceneAsset_getEntity(asset);
}
Int32List? _childEntities;
///
///
///
@override
Future<List<ThermionEntity>> getChildEntities() async {
if (_childEntities == null) {
var count = SceneAsset_getChildEntityCount(asset);
var childEntities = Int32List(count);
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
if (count > 0) {
SceneAsset_getChildEntities(asset, childEntities.address);
}
_childEntities = Int32List.fromList(childEntities);
childEntities.free();
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
}
}
return _childEntities!;
}
///
///
///
@override
Future<List<String?>> getChildEntityNames() async {
final childEntities = await getChildEntities();
var names = <String?>[];
for (final entity in childEntities) {
var name = NameComponentManager_getName(app.nameComponentManager, entity);
if (name == nullptr) {
names.add(null);
} else {
names.add(name.cast<Utf8>().toDartString());
}
}
return names;
}
///
///
///
@override
Future<ThermionEntity?> getChildEntity(String childName) async {
final childEntities = await getChildEntities();
for (final entity in childEntities) {
var name = NameComponentManager_getName(app.nameComponentManager, entity);
if (name == childName) {
return entity;
}
}
return null;
}
///
///
///
@override
Future<ThermionAsset> getInstance(int index) async {
if (isInstance) {
throw Exception(
"This is itself an instance. Call getInstance on the original asset that this instance was created from");
}
var instance = SceneAsset_getInstance(asset, index);
if (instance == nullptr) {
throw Exception("No instance available at index $index");
}
return FFIAsset(instance, app, animationManager);
}
///
///
///
@override
Future<FFIAsset> createInstance(
{covariant List<MaterialInstance>? materialInstances = null}) async {
var ptrList = IntPtrList(materialInstances?.length ?? 0);
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
if (materialInstances != null && materialInstances.isNotEmpty) {
ptrList.setRange(
0,
materialInstances.length,
materialInstances
.cast<FFIMaterialInstance>()
.map((mi) => mi.pointer.address)
.toList());
}
var created = await withPointerCallback<TSceneAsset>((cb) {
SceneAsset_createInstanceRenderThread(
asset, ptrList.address.cast(), materialInstances?.length ?? 0, cb);
});
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
ptrList.free();
}
if (created == FILAMENT_ASSET_ERROR) {
throw Exception("Failed to create instance");
}
return FFIAsset(created, app, animationManager);
}
///
///
///
@override
Future<int> getInstanceCount() async {
return SceneAsset_getInstanceCount(asset);
}
///
///
///
@override
Future<List<ThermionAsset>> getInstances() async {
var count = await getInstanceCount();
final result = List<ThermionAsset>.generate(count, (i) {
return FFIAsset(SceneAsset_getInstance(asset, i), app, animationManager);
});
return result;
}
///
///
///
@override
Future removeStencilHighlight() async {
throw UnimplementedError();
// if (_highlight != null) {
// SceneManager_removeFromScene(sceneManager, _highlight!.entity);
// final childEntities = await _highlight!.getChildEntities();
// for (final child in childEntities) {
// SceneManager_removeFromScene(sceneManager, child);
// }
// }
}
///
///
///
@override
Future setStencilHighlight(
{double r = 1.0,
double g = 0.0,
double b = 0.0,
int? entityIndex}) async {
if (_highlight == null) {
var targetEntity = this.entity;
if (entityIndex != null) {
final childEntities = await this.getChildEntities();
targetEntity = childEntities[entityIndex];
}
var sourceMaterialInstance = FFIMaterialInstance(
RenderableManager_getMaterialInstanceAt(
app.renderableManager, targetEntity, 0),
app);
await sourceMaterialInstance.setStencilWriteEnabled(true);
await sourceMaterialInstance.setDepthWriteEnabled(true);
await sourceMaterialInstance
.setStencilOpDepthStencilPass(StencilOperation.REPLACE);
await sourceMaterialInstance.setStencilReferenceValue(1);
final materialInstancePtr =
await withPointerCallback<TMaterialInstance>((cb) {
final key = Struct.create<TMaterialKey>();
MaterialProvider_createMaterialInstanceRenderThread(
app.ubershaderMaterialProvider, key.address, cb);
});
final highlightMaterialInstance =
FFIMaterialInstance(materialInstancePtr, app);
await highlightMaterialInstance
.setStencilCompareFunction(SamplerCompareFunction.NE);
await highlightMaterialInstance.setStencilReferenceValue(1);
await highlightMaterialInstance.setDepthCullingEnabled(false);
await highlightMaterialInstance.setParameterFloat4(
"baseColorFactor", r, g, b, 1.0);
var highlightInstance = await this
.createInstance(materialInstances: [highlightMaterialInstance]);
_highlight = highlightInstance;
await highlightMaterialInstance.setStencilReferenceValue(1);
RenderableManager_setPriority(app.renderableManager, targetEntity, 0);
TransformManager_setParent(
app.transformManager, _highlight!.entity, entity, false);
}
var targetHighlightEntity = _highlight!.entity;
if (entityIndex != null) {
var highlightChildEntities = await _highlight!.getChildEntities();
targetHighlightEntity = highlightChildEntities[entityIndex];
}
RenderableManager_setPriority(
app.renderableManager, targetHighlightEntity, 7);
throw UnimplementedError();
}
///
///
///
ThermionAsset? boundingBoxAsset;
///
///
///
Future dispose() async {
_childEntities?.free();
}
///
///
///
Future<v64.Aabb3> getBoundingBox() async {
final entities = <ThermionEntity>[];
if (RenderableManager_isRenderable(app.renderableManager, entity)) {
entities.add(entity);
} else {
entities.addAll(await getChildEntities());
}
var boundingBox = v64.Aabb3();
for (final entity in entities) {
final aabb3 = RenderableManager_getAabb(app.renderableManager, entity);
final entityBB = v64.Aabb3.centerAndHalfExtents(
v64.Vector3(aabb3.centerX, aabb3.centerY, aabb3.centerZ),
v64.Vector3(aabb3.halfExtentX, aabb3.halfExtentY, aabb3.halfExtentZ),
);
boundingBox.hull(entityBB);
}
return boundingBox;
}
///
///
///
Future<ThermionAsset> createBoundingBoxAsset() async {
if (boundingBoxAsset == null) {
final boundingBox = await SceneAsset_getBoundingBox(asset);
final min = [
boundingBox.centerX - boundingBox.halfExtentX,
boundingBox.centerY - boundingBox.halfExtentY,
boundingBox.centerZ - boundingBox.halfExtentZ
];
final max = [
boundingBox.centerX + boundingBox.halfExtentX,
boundingBox.centerY + boundingBox.halfExtentY,
boundingBox.centerZ + boundingBox.halfExtentZ
];
// Create vertices for the bounding box wireframe
// 8 vertices for a cube
final vertices = Float32List(8 * 3);
// Bottom vertices
vertices[0] = min[0];
vertices[1] = min[1];
vertices[2] = min[2]; // v0
vertices[3] = max[0];
vertices[4] = min[1];
vertices[5] = min[2]; // v1
vertices[6] = max[0];
vertices[7] = min[1];
vertices[8] = max[2]; // v2
vertices[9] = min[0];
vertices[10] = min[1];
vertices[11] = max[2]; // v3
// Top vertices
vertices[12] = min[0];
vertices[13] = max[1];
vertices[14] = min[2]; // v4
vertices[15] = max[0];
vertices[16] = max[1];
vertices[17] = min[2]; // v5
vertices[18] = max[0];
vertices[19] = max[1];
vertices[20] = max[2]; // v6
vertices[21] = min[0];
vertices[22] = max[1];
vertices[23] = max[2]; // v7
// Indices for lines (24 indices for 12 lines)
final indices = Uint16List.fromList([
// Bottom face
0, 1, 1, 2, 2, 3, 3, 0,
// Top face
4, 5, 5, 6, 6, 7, 7, 4,
// Vertical edges
0, 4, 1, 5, 2, 6, 3, 7
]);
// Create unlit material instance for the wireframe
final materialInstancePtr =
await withPointerCallback<TMaterialInstance>((cb) {
final key = Struct.create<TMaterialKey>();
MaterialProvider_createMaterialInstanceRenderThread(
app.ubershaderMaterialProvider, key.address, cb);
});
final material = FFIMaterialInstance(materialInstancePtr, app);
await material.setParameterFloat4(
"baseColorFactor", 1.0, 1.0, 0.0, 1.0); // Yellow wireframe
// Create geometry for the bounding box
final geometry = Geometry(
vertices,
indices,
primitiveType: PrimitiveType.LINES,
);
boundingBoxAsset = await FilamentApp.instance!.createGeometry(
geometry,
animationManager,
materialInstances: [material],
keepData: false,
) as FFIAsset;
await boundingBoxAsset!.setCastShadows(false);
await boundingBoxAsset!.setReceiveShadows(false);
TransformManager_setParent(Engine_getTransformManager(app.engine),
boundingBoxAsset!.entity, entity, false);
geometry.uvs?.free();
geometry.normals?.free();
geometry.vertices.free();
geometry.indices.free();
}
return boundingBoxAsset!;
}
///
///
///
@override
Future<MaterialInstance> getMaterialInstanceAt(
{ThermionEntity? entity, int index = 0}) async {
entity ??= this.entity;
var ptr = RenderableManager_getMaterialInstanceAt(
Engine_getRenderableManager(app.engine), entity, 0);
return FFIMaterialInstance(ptr, app);
}
///
///
///
@override
Future setMaterialInstanceAt(FFIMaterialInstance instance) async {
var childEntities = await getChildEntities();
final entities = <ThermionEntity>[entity, ...childEntities];
for (final entity in entities) {
RenderableManager_setMaterialInstanceAt(
Engine_getRenderableManager(app.engine), entity, 0, instance.pointer);
}
}
///
///
///
Future setCastShadows(bool castShadows) async {
RenderableManager_setCastShadows(
app.renderableManager, this.entity, castShadows);
for (final entity in await this.getChildEntities()) {
RenderableManager_setCastShadows(
app.renderableManager, entity, castShadows);
}
}
///
///
///
Future setReceiveShadows(bool receiveShadows) async {
RenderableManager_setReceiveShadows(
app.renderableManager, this.entity, receiveShadows);
for (final entity in await this.getChildEntities()) {
RenderableManager_setReceiveShadows(
app.renderableManager, entity, receiveShadows);
}
}
///
///
///
Future<bool> isCastShadowsEnabled({ThermionEntity? entity}) async {
entity ??= this.entity;
return RenderableManager_isShadowCaster(app.renderableManager, entity);
}
///
///
///
Future<bool> isReceiveShadowsEnabled({ThermionEntity? entity}) async {
entity ??= this.entity;
return RenderableManager_isShadowReceiver(app.renderableManager, entity);
}
///
///
///
Future transformToUnitCube() async {
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 = allocate<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);
});
free(weightsPtr);
if (!success) {
throw Exception(
"Failed to set morph target weights, check logs for details");
}
}
///
///
///
@override
Future<List<String>> getMorphTargetNames({ThermionEntity? entity}) async {
var names = <String>[];
entity ??= this.entity;
var count = AnimationManager_getMorphTargetNameCount(
animationManager, asset, entity);
var outPtr = allocate<Char>(255);
for (int i = 0; i < count; i++) {
AnimationManager_getMorphTargetName(
animationManager, asset, entity, outPtr, i);
names.add(outPtr.cast<Utf8>().toDartString());
}
free(outPtr);
return names.cast<String>();
}
///
///
///
Future<List<String>> getBoneNames({int skinIndex = 0}) async {
var count =
AnimationManager_getBoneCount(animationManager, asset, skinIndex);
var out = allocate<PointerClass<Char>>(count);
for (int i = 0; i < count; i++) {
out[i] = allocate<Char>(255);
}
AnimationManager_getBoneNames(animationManager, asset, out, skinIndex);
var names = <String>[];
for (int i = 0; i < count; i++) {
var namePtr = out[i];
names.add(namePtr.cast<Utf8>().toDartString());
}
for (int i = 0; i < count; i++) {
free(out[i]);
}
free(out);
return names;
}
///
///
///
@override
Future<List<String>> getAnimationNames() async {
var animationCount =
AnimationManager_getAnimationCount(animationManager, asset);
var names = <String>[];
var outPtr = allocate<Char>(255);
for (int i = 0; i < animationCount; i++) {
AnimationManager_getAnimationName(animationManager, asset, outPtr, i);
names.add(outPtr.cast<Utf8>().toDartString());
}
free(outPtr);
return names;
}
///
///
///
@override
Future<double> getAnimationDuration(int animationIndex) async {
return AnimationManager_getAnimationDuration(
animationManager, asset, animationIndex);
}
///
///
///
Future<double> getAnimationDurationByName(String name) async {
var animations = await getAnimationNames();
var index = animations.indexOf(name);
if (index == -1) {
throw Exception("Failed to find animation $name");
}
return getAnimationDuration(index);
}
///
///
///
Future clearMorphAnimationData(ThermionEntity entity) async {
if (!AnimationManager_clearMorphAnimation(animationManager, entity)) {
throw Exception("Failed to clear morph animation");
}
}
///
///
///
@override
Future setMorphAnimationData(MorphAnimationData animation,
{List<String>? targetMeshNames}) async {
var meshEntities = await getChildEntities();
var meshNames = meshEntities
.map((e) => FilamentApp.instance!.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(entity: 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);
frameData.data.free();
indices.free();
if (!result) {
throw Exception("Failed to set morph animation data for ${meshName}");
}
}
}
///
/// Currently, scale is not supported.
///
@override
Future addBoneAnimation(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();
var restLocalTransformsRaw = allocate<Float>(boneNames.length * 16);
AnimationManager_getRestLocalTransforms(animationManager, 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));
}
free(restLocalTransformsRaw);
var numFrames = animation.frameData.length;
var data = allocate<Float>(numFrames * 16);
var bones = await Future.wait(List<Future<ThermionEntity>>.generate(
boneNames.length, (i) => getBone(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 FilamentApp.instance!.getParent(boneEntity))!;
while (true) {
if (!bones.contains(parentBoneEntity!)) {
break;
}
world = restLocalTransforms[bones.indexOf(parentBoneEntity!)] * world;
parentBoneEntity =
(await FilamentApp.instance!.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[(frameNum * 16) + j] = newLocalTransform.storage[j];
}
}
AnimationManager_addBoneAnimation(
animationManager,
asset,
skinIndex,
entityBoneIndex,
data,
numFrames,
animation.frameLengthInMs,
fadeOutInSecs,
fadeInInSecs,
maxDelta);
}
free(data);
}
///
///
///
Future<Matrix4> getLocalTransform({ThermionEntity? entity}) async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
entity ??= this.entity;
final transform = double4x4ToMatrix4(
TransformManager_getLocalTransform(app.transformManager, entity));
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
}
return transform;
}
///
///
///
Future<Matrix4> getWorldTransform({ThermionEntity? entity}) async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
entity ??= this.entity;
var transform = double4x4ToMatrix4(
TransformManager_getWorldTransform(app.transformManager, entity));
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
}
return transform;
}
///
///
///
Future setTransform(Matrix4 transform, {ThermionEntity? entity}) async {
entity ??= this.entity;
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(int boneIndex,
{int skinIndex = 0}) async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
var matrixIn = Float32List(16);
AnimationManager_getInverseBindMatrix(
animationManager, asset, skinIndex, boneIndex, matrixIn.address);
var matrixOut = Matrix4.fromList(matrixIn);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
matrixIn.free();
}
return matrixOut;
}
///
///
///
Future<ThermionEntity> getBone(int boneIndex, {int skinIndex = 0}) async {
if (skinIndex != 0) {
throw UnimplementedError("TOOD");
}
return AnimationManager_getBone(
animationManager, asset, skinIndex, boneIndex);
}
///
///
///
@override
Future setBoneTransform(
ThermionEntity entity, int boneIndex, Matrix4 transform,
{int skinIndex = 0}) async {
if (skinIndex != 0) {
throw UnimplementedError("TOOD");
}
final ptr = allocate<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);
});
free(ptr);
if (!result) {
throw Exception("Failed to set bone transform");
}
}
///
///
///
///
@override
Future resetBones() async {
AnimationManager_resetToRestPose(animationManager, asset);
}
///
///
///
@override
Future playAnimation(int index,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0,
double startOffset = 0.0}) async {
AnimationManager_playAnimation(animationManager, asset, index, loop,
reverse, replaceActive, crossfade, startOffset);
}
///
///
///
@override
Future stopAnimation(int animationIndex) async {
AnimationManager_stopAnimation(animationManager, asset, animationIndex);
}
///
///
///
@override
Future stopAnimationByName(String name) async {
var animations = await getAnimationNames();
await stopAnimation(animations.indexOf(name));
}
///
///
///
@override
Future playAnimationByName(String name,
{bool loop = false,
bool reverse = false,
bool replaceActive = true,
double crossfade = 0.0,
bool wait = false}) async {
var animations = await getAnimationNames();
var index = animations.indexOf(name);
var duration = await getAnimationDuration(index);
await playAnimation(index,
loop: loop,
reverse: reverse,
replaceActive: replaceActive,
crossfade: crossfade);
if (wait) {
await Future.delayed(Duration(milliseconds: (duration * 1000).toInt()));
}
}
///
///
///
@override
Future setGltfAnimationFrame(int index, int animationFrame) async {
AnimationManager_setGltfAnimationFrame(
animationManager, asset, index, animationFrame);
}
///
///
///
@override
Future addAnimationComponent(ThermionEntity entity) async {
AnimationManager_addAnimationComponent(animationManager, entity);
}
///
///
///
Future removeAnimationComponent(ThermionEntity entity) async {
AnimationManager_removeAnimationComponent(animationManager, entity);
}
}

View File

@@ -0,0 +1,216 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/thermion_dart.dart';
import '../../../utils/src/matrix.dart';
class FFICamera extends Camera {
final Pointer<TCamera> camera;
final FFIFilamentApp app;
late ThermionEntity _entity;
FFICamera(this.camera, this.app) {
_entity = Camera_getEntity(camera);
}
///
///
///
@override
Future setProjectionMatrixWithCulling(
Matrix4 projectionMatrix, double near, double far) async {
Camera_setCustomProjectionWithCulling(
camera, matrix4ToDouble4x4(projectionMatrix), near, far);
}
///
///
///
Future<Matrix4> getModelMatrix() async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
stackPtr = stackSave();
}
final modelMatrix = double4x4ToMatrix4(Camera_getModelMatrix(camera));
if (FILAMENT_WASM) {
stackRestore(stackPtr);
}
return modelMatrix;
}
///
///
///
@override
Future<Matrix4> getProjectionMatrix() async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
var matrixStruct = Camera_getProjectionMatrix(camera);
final pMat = double4x4ToMatrix4(matrixStruct);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
}
return pMat;
}
///
///
///
@override
Future<Matrix4> getCullingProjectionMatrix() async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
var matrixStruct = Camera_getCullingProjectionMatrix(camera);
final cpMat = double4x4ToMatrix4(matrixStruct);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
}
return cpMat;
}
@override
Future setTransform(Matrix4 transform) async {
var entity = Camera_getEntity(camera);
TransformManager_setTransform(
app.transformManager, entity, matrix4ToDouble4x4(transform));
}
@override
Future setLensProjection(
{double near = kNear,
double far = kFar,
double aspect = 1.0,
double focalLength = kFocalLength}) async {
Camera_setLensProjection(camera, near, far, aspect, focalLength);
}
///
///
///
@override
ThermionEntity getEntity() {
return _entity;
}
///
///
///
@override
Future setModelMatrix(Matrix4 matrix) async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
stackPtr = stackSave();
}
Camera_setModelMatrix(camera, matrix.storage.address);
if (FILAMENT_WASM) {
stackRestore(stackPtr);
matrix.storage.free();
}
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is FFICamera &&
runtimeType == other.runtimeType &&
camera == other.camera;
@override
int get hashCode => camera.hashCode;
///
///
///
@override
Future<double> getCullingFar() async {
return Camera_getCullingFar(camera);
}
///
///
///
@override
Future<double> getNear() async {
return Camera_getNear(camera);
}
///
///
///
@override
Future<double> getFocalLength() async {
return Camera_getFocalLength(camera);
}
///
///
///
Future<Frustum> getFrustum() async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
var out = Float64List(24);
Camera_getFrustum(camera, out.address);
var frustum = Frustum();
frustum.plane0.setFromComponents(out[0], out[1], out[2], out[3]);
frustum.plane1.setFromComponents(out[4], out[5], out[6], out[7]);
frustum.plane2.setFromComponents(out[8], out[9], out[10], out[11]);
frustum.plane3.setFromComponents(out[12], out[13], out[14], out[15]);
frustum.plane4.setFromComponents(out[16], out[17], out[18], out[19]);
frustum.plane5.setFromComponents(out[20], out[21], out[22], out[23]);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
out.free();
}
return frustum;
}
@override
Future<Matrix4> getViewMatrix() async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
final matrix = double4x4ToMatrix4(Camera_getViewMatrix(camera));
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
}
return matrix;
}
@override
Future setProjection(Projection projection, double left, double right,
double bottom, double top, double near, double far) async {
Camera_setProjection(
camera, projection.index, left, right, bottom, top, near, far);
}
Future destroy() async {
Engine_destroyCamera(app.engine, camera);
}
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);
@override
Future<double> getHorizontalFieldOfView() async {
return Camera_getFov(camera, true);
}
@override
Future<double> getVerticalFieldOfView() async {
return Camera_getFov(camera, false);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
import 'dart:async';
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'ffi_view.dart';
class FFIGizmo extends FFIAsset implements GizmoAsset {
final Set<ThermionEntity> entities;
late final CallbackHolder<GizmoPickCallbackFunction> _callbackHolder;
void Function(GizmoPickResultType axis, Vector3 coords)? _callback;
late FFIView view;
FFIGizmo(
super.asset,
super.app,
super.animationManager,
{
required this.view,
required this.entities,
}) {
_callbackHolder = _onPickResult.asCallback();
}
///
///
///
Future dispose() async {
_callbackHolder.dispose();
}
void _onPickResult(int resultType, double x, double y, double z) {
final type = switch(resultType) {
TGizmoPickResultType.AxisX => GizmoPickResultType.AxisX,
TGizmoPickResultType.AxisY => GizmoPickResultType.AxisY,
TGizmoPickResultType.AxisZ => GizmoPickResultType.AxisZ,
TGizmoPickResultType.None => GizmoPickResultType.None,
TGizmoPickResultType.Parent => GizmoPickResultType.Parent,
_ => throw UnsupportedError(resultType.toString())
};
_callback?.call(type, Vector3(x, y, z));
}
bool isNonPickable(ThermionEntity entity) {
throw UnimplementedError();
// return SceneManager_isGridEntity(sceneManager, entity);
}
bool isGizmoEntity(ThermionEntity entity) => entities.contains(entity);
@override
Future removeStencilHighlight() async {
throw Exception("Not supported for gizmo");
}
@override
Future setStencilHighlight(
{double r = 1.0,
double g = 0.0,
double b = 0.0,
int? entityIndex}) async {
throw Exception("Not supported for gizmo");
}
@override
Future pick(int x, int y,
{Future Function(GizmoPickResultType result, Vector3 coords)?
handler}) async {
_callback = handler;
final viewport = await view.getViewport();
y = viewport.height - y;
Gizmo_pick(asset.cast<TGizmo>(), x, y, _callbackHolder.pointer);
}
@override
Future highlight(Axis axis) async {
Gizmo_unhighlight(asset.cast<TGizmo>());
final tAxis = switch(axis) {
Axis.X => TGizmoAxis.X,
Axis.Y => TGizmoAxis.Y,
Axis.Z => TGizmoAxis.Z
};
Gizmo_highlight(asset.cast<TGizmo>(), tAxis);
}
@override
Future unhighlight() async {
Gizmo_unhighlight(asset.cast<TGizmo>());
}
}

View File

@@ -0,0 +1,199 @@
import 'dart:async';
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFIMaterial extends Material {
final FFIFilamentApp app;
final Pointer<TMaterial> pointer;
FFIMaterial(this.pointer, this.app);
@override
Future<MaterialInstance> createInstance() async {
var ptr = await withPointerCallback<TMaterialInstance>((cb) {
Material_createInstanceRenderThread(pointer, cb);
});
return FFIMaterialInstance(ptr, this.app);
}
Future destroy() async {
await withVoidCallback((cb) {
Engine_destroyMaterialRenderThread(app.engine, pointer, cb);
});
}
@override
Future<bool> hasParameter(String propertyName) async {
return Material_hasParameter(
pointer, propertyName.toNativeUtf8().cast<Char>());
}
}
class FFIMaterialInstance extends MaterialInstance {
final Pointer<TMaterialInstance> pointer;
final FFIFilamentApp app;
FFIMaterialInstance(this.pointer, this.app) {
if (pointer == nullptr) {
throw Exception("MaterialInstance not found");
}
}
@override
Future setDepthCullingEnabled(bool enabled) async {
MaterialInstance_setDepthCulling(this.pointer, enabled);
}
@override
Future setDepthWriteEnabled(bool enabled) async {
MaterialInstance_setDepthWrite(this.pointer, enabled);
}
@override
Future setParameterFloat(String name, double value) async {
MaterialInstance_setParameterFloat(
pointer, name.toNativeUtf8().cast<Char>(), value);
}
@override
Future setParameterFloat2(String name, double x, double y) async {
MaterialInstance_setParameterFloat2(
pointer, name.toNativeUtf8().cast<Char>(), x, y);
}
@override
Future setParameterFloat3(String name, double x, double y, double z) async {
MaterialInstance_setParameterFloat3(
pointer, name.toNativeUtf8().cast<Char>(), x, y, z);
}
@override
Future setParameterFloat3Array(String name, List<Vector3> array) async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
final ptr = name.toNativeUtf8().cast<Char>();
final data = Float64List(array.length * 3);
int i = 0;
for (final item in array) {
data[i] = item.x;
data[i + 1] = item.y;
data[i + 2] = item.z;
i += 3;
}
MaterialInstance_setParameterFloat3Array(
pointer, ptr, data.address, array.length * 3);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
data.free();
}
}
@override
Future setParameterFloat4(
String name, double x, double y, double z, double w) async {
MaterialInstance_setParameterFloat4(
pointer, name.toNativeUtf8().cast<Char>(), x, y, z, w);
}
@override
Future setParameterInt(String name, int value) async {
MaterialInstance_setParameterInt(
pointer, name.toNativeUtf8().cast<Char>(), value);
}
@override
Future setDepthFunc(SamplerCompareFunction depthFunc) async {
MaterialInstance_setDepthFunc(pointer, depthFunc.index);
}
@override
Future setStencilCompareFunction(SamplerCompareFunction func,
[StencilFace face = StencilFace.FRONT_AND_BACK]) async {
MaterialInstance_setStencilCompareFunction(pointer, func.index, face.index);
}
@override
Future setStencilOpDepthFail(StencilOperation op,
[StencilFace face = StencilFace.FRONT_AND_BACK]) async {
MaterialInstance_setStencilOpDepthFail(pointer, op.index, face.index);
}
@override
Future setStencilOpDepthStencilPass(StencilOperation op,
[StencilFace face = StencilFace.FRONT_AND_BACK]) async {
MaterialInstance_setStencilOpDepthStencilPass(
pointer, op.index, face.index);
}
@override
Future setStencilOpStencilFail(StencilOperation op,
[StencilFace face = StencilFace.FRONT_AND_BACK]) async {
MaterialInstance_setStencilOpStencilFail(pointer, op.index, face.index);
}
@override
Future setStencilReferenceValue(int value,
[StencilFace face = StencilFace.FRONT_AND_BACK]) async {
MaterialInstance_setStencilReferenceValue(pointer, value, face.index);
}
@override
Future setStencilWriteEnabled(bool enabled) async {
MaterialInstance_setStencilWrite(pointer, enabled);
}
@override
Future setCullingMode(CullingMode cullingMode) async {
MaterialInstance_setCullingMode(pointer, cullingMode.index);
;
}
@override
Future<bool> isStencilWriteEnabled() async {
return MaterialInstance_isStencilWriteEnabled(pointer);
}
@override
Future setStencilReadMask(int mask) async {
MaterialInstance_setStencilReadMask(pointer, mask);
}
@override
Future setStencilWriteMask(int mask) async {
MaterialInstance_setStencilWriteMask(pointer, mask);
}
Future destroy() async {
await withVoidCallback((cb) {
Engine_destroyMaterialInstanceRenderThread(app.engine, this.pointer, cb);
});
}
@override
Future setTransparencyMode(TransparencyMode mode) async {
MaterialInstance_setTransparencyMode(pointer, mode.index);
}
@override
Future setParameterTexture(String name, covariant FFITexture texture,
covariant FFITextureSampler sampler) async {
MaterialInstance_setParameterTexture(pointer,
name.toNativeUtf8().cast<Char>(), texture.pointer, sampler.pointer);
}
@override
Future setParameterBool(String name, bool value) async {
MaterialInstance_setParameterBool(
pointer, name.toNativeUtf8().cast<Char>(), value);
}
@override
Future setParameterMat4(String name, Matrix4 matrix) async {
MaterialInstance_setParameterMat4(
pointer, name.toNativeUtf8().cast<Char>(), matrix.storage.address);
}
}

View File

@@ -0,0 +1,28 @@
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFIRenderTarget extends RenderTarget {
final Pointer<TRenderTarget> renderTarget;
final FFIFilamentApp app;
FFIRenderTarget(this.renderTarget, this.app);
@override
Future<Texture> getColorTexture() async {
final ptr = RenderTarget_getColorTexture(renderTarget);
return FFITexture(app.engine, ptr);
}
@override
Future<Texture> getDepthTexture() async {
final ptr = RenderTarget_getDepthTexture(renderTarget);
return FFITexture(app.engine, ptr);
}
@override
Future destroy() async {
await withVoidCallback((cb) => RenderTarget_destroyRenderThread(app.engine, renderTarget, cb));
}
}

View File

@@ -0,0 +1,19 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/src/filament/src/interface/scene.dart';
import 'package:thermion_dart/src/bindings/bindings.dart';
class FFIScene extends Scene {
final Pointer<TScene> scene;
FFIScene(this.scene);
@override
Future add(covariant FFIAsset asset) async {
SceneAsset_addToScene(asset.asset, scene);
}
@override
Future remove(covariant FFIAsset asset) async {
SceneAsset_removeFromScene(asset.asset, scene);
}
}

View File

@@ -0,0 +1,8 @@
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFISwapChain extends SwapChain {
final Pointer<TSwapChain> swapChain;
FFISwapChain(this.swapChain);
}

View File

@@ -0,0 +1,341 @@
import 'dart:typed_data';
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFITexture extends Texture {
final Pointer<TEngine> _engine;
final Pointer<TTexture> pointer;
FFITexture(this._engine, this.pointer);
Future<void> setLinearImage(covariant FFILinearImage image,
PixelDataFormat format, PixelDataType type) async {
final tPixelDataFormat = format.value;
final tPixelDataType = type.value;
final result = await withBoolCallback((cb) {
Texture_loadImageRenderThread(
_engine,
pointer,
image.pointer,
tPixelDataFormat,
tPixelDataType,
cb);
});
if (!result) {
throw Exception("Failed to set linear image");
}
}
@override
Future<void> dispose() async {
await withVoidCallback((cb) {
Engine_destroyTextureRenderThread(_engine, pointer, cb);
});
}
@override
Future<void> generateMipmaps() {
// TODO: implement generateMipmaps
throw UnimplementedError();
}
@override
Future<int> getDepth([int level = 0]) async {
return Texture_getDepth(pointer, level);
}
@override
Future<TextureFormat> getFormat() {
// TODO: implement getFormat
throw UnimplementedError();
}
@override
Future<int> getHeight([int level = 0]) async {
return Texture_getHeight(pointer, level);
}
@override
Future<int> getLevels() {
// TODO: implement getLevels
throw UnimplementedError();
}
@override
Future<TextureSamplerType> getTarget() {
// TODO: implement getTarget
throw UnimplementedError();
}
@override
Future<int> getWidth([int level = 0]) async {
return Texture_getWidth(pointer, level);
}
@override
Future<void> setExternalImage(externalImage) {
// TODO: implement setExternalImage
throw UnimplementedError();
}
@override
Future<void> setImage(int level, Uint8List buffer, int width, int height,
int channels, PixelDataFormat format, PixelDataType type) async {
final success = await withBoolCallback((cb) {
Texture_setImageRenderThread(
_engine,
pointer,
level,
buffer.address,
buffer.lengthInBytes,
width,
height,
channels,
format.index,
type.index,
cb);
});
if (!success) {
throw Exception("Failed to set image");
}
}
@override
Future<void> setImage3D(
int level,
int xOffset,
int yOffset,
int zOffset,
int width,
int height,
int channels,
int depth,
Uint8List buffer,
PixelDataFormat format,
PixelDataType type) async {
throw UnimplementedError();
// final success = await withBoolCallback((cb) {
// Texture_setImageWithDepthRenderThread(
// _engine,
// pointer,
// level,
// buffer.address,
// buffer.lengthInBytes,
// 0,
// 0,
// zOffset,
// width,
// height,
// channels,
// depth,
// format.index,
// type.index,
// cb);
// });
// if (!success) {
// throw Exception("Failed to set image");
// }
}
@override
Future<void> setSubImage(
int level,
int xOffset,
int yOffset,
int width,
int height,
Uint8List buffer,
PixelDataFormat format,
PixelDataType type) {
// TODO: implement setSubImage
throw UnimplementedError();
}
}
class FFILinearImage extends LinearImage {
final Pointer<TLinearImage> pointer;
FFILinearImage(this.pointer);
static Future<FFILinearImage> createEmpty(
int width, int height, int channels) async {
final imagePtr = await withPointerCallback<TLinearImage>((cb) {
Image_createEmptyRenderThread(width, height, channels, cb);
});
return FFILinearImage(imagePtr);
}
static Future<FFILinearImage> decode(Uint8List data,
[String name = "image"]) async {
final namePtr = name.toNativeUtf8();
final imagePtr = await withPointerCallback<TLinearImage>((cb) {
Image_decodeRenderThread(
data.address, data.lengthInBytes, namePtr.cast(), cb);
});
return FFILinearImage(imagePtr);
}
Future<void> destroy() async {
await withVoidCallback((cb) {
Image_destroyRenderThread(this.pointer, cb);
});
}
@override
Future<int> getChannels() async {
return await withUInt32Callback((cb) {
Image_getChannelsRenderThread(pointer, cb);
});
}
@override
Future<int> getHeight() async {
return await withUInt32Callback((cb) {
Image_getHeightRenderThread(pointer, cb);
});
}
@override
Future<int> getWidth() async {
return await withUInt32Callback((cb) {
Image_getWidthRenderThread(pointer, cb);
});
}
@override
Future<Float32List> getData() async {
final height = await getHeight();
final width = await getWidth();
final channels = await getChannels();
final ptr = await withPointerCallback<Float>((cb) {
Image_getBytesRenderThread(pointer, cb);
});
return ptr.asTypedList(height * width * channels);
}
}
// Add these to access TextureSampler functionality:
class FFITextureSampler extends TextureSampler {
final Pointer<TTextureSampler> pointer;
FFITextureSampler(this.pointer);
static Future<FFITextureSampler> create() async {
final samplerPtr = await withPointerCallback<TTextureSampler>((cb) {
TextureSampler_createRenderThread(cb);
});
return FFITextureSampler(samplerPtr);
}
// static Future<FFITextureSampler> createWithFiltering(
// SamplerMinFilter minFilter,
// SamplerMagFilter magFilter,
// SamplerWrapMode wrapS,
// SamplerWrapMode wrapT,
// SamplerWrapMode wrapR) async {
// final samplerPtr = await withPointerCallback<TTextureSampler>((cb) {
// TextureSampler_createWithFilteringRenderThread(
// TSamplerMinFilter.values[minFilter.index],
// TSamplerMagFilter.values[magFilter.index],
// TSamplerWrapMode.values[wrapS.index],
// TSamplerWrapMode.values[wrapT.index],
// TSamplerWrapMode.values[wrapR.index],
// cb);
// });
// return FFITextureSampler(samplerPtr);
// }
// static Future<FFITextureSampler> createWithComparison(
// SamplerCompareMode compareMode,
// SamplerCompareFunc compareFunc) async {
// final samplerPtr = await withPointerCallback<TTextureSampler>((cb) {
// TextureSampler_createWithComparisonRenderThread(
// TSamplerCompareMode.values[compareMode.index],
// TTextureSamplerCompareFunc.values[compareFunc.index],
// cb);
// });
// return FFITextureSampler(samplerPtr);
// }
// Future<void> setMinFilter(SamplerMinFilter filter) async {
// await withVoidCallback((cb) {
// TextureSampler_setMinFilterRenderThread(
// pointer,
// TSamplerMinFilter.values[filter.index],
// cb);
// });
// }
// Future<void> setMagFilter(SamplerMagFilter filter) async {
// await withVoidCallback((cb) {
// TextureSampler_setMagFilterRenderThread(
// pointer,
// TSamplerMagFilter.values[filter.index],
// cb);
// });
// }
// Future<void> setWrapModeS(SamplerWrapMode mode) async {
// await withVoidCallback((cb) {
// TextureSampler_setWrapModeSRenderThread(
// pointer,
// TSamplerWrapMode.values[mode.index],
// cb);
// });
// }
// Future<void> setWrapModeT(SamplerWrapMode mode) async {
// await withVoidCallback((cb) {
// TextureSampler_setWrapModeTRenderThread(
// pointer,
// TSamplerWrapMode.values[mode.index],
// cb);
// });
// }
// Future<void> setWrapModeR(SamplerWrapMode mode) async {
// await withVoidCallback((cb) {
// TextureSampler_setWrapModeRRenderThread(
// pointer,
// TSamplerWrapMode.values[mode.index],
// cb);
// });
// }
Future<void> setAnisotropy(double anisotropy) async {
await withVoidCallback((cb) {
TextureSampler_setAnisotropyRenderThread(pointer, anisotropy, cb);
});
}
// Future<void> setCompareMode(
// SamplerCompareMode mode, SamplerCompareFunc func) async {
// await withVoidCallback((cb) {
// TextureSampler_setCompareModeRenderThread(
// pointer,
// TSamplerCompareMode.values[mode.index],
// TTextureSamplerCompareFunc.values[func.index],
// cb);
// });
// }
@override
Future dispose() async {
await withVoidCallback((cb) {
TextureSampler_destroyRenderThread(pointer, cb);
});
}
}

View File

@@ -0,0 +1,224 @@
import 'dart:async';
import 'dart:math';
import 'package:logging/logging.dart';
import 'package:thermion_dart/src/filament/src/interface/scene.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_render_target.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_scene.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'ffi_camera.dart';
class FFIView extends View {
late final _logger = Logger(this.runtimeType.toString());
int _renderOrder = 0;
int get renderOrder => _renderOrder;
final Pointer<TView> view;
final FFIFilamentApp app;
bool _renderable = false;
bool get renderable => _renderable;
FFIRenderTarget? renderTarget;
late CallbackHolder<PickCallbackFunction> _onPickResultHolder;
FFIView(this.view, this.app) {
final renderTargetPtr = View_getRenderTarget(view);
if (renderTargetPtr != nullptr) {
renderTarget = FFIRenderTarget(renderTargetPtr, app);
}
_onPickResultHolder =
_onPickResult.asCallback();
}
///
///
///
Future dispose() async {
_onPickResultHolder.dispose();
}
///
///
///
Future setRenderOrder(int order) async {
this._renderOrder = order;
await FilamentApp.instance!.updateRenderOrder();
}
///
///
///
Future setRenderable(bool renderable) async {
this._renderable = renderable;
await FilamentApp.instance!.updateRenderOrder();
}
@override
Future setViewport(int width, int height) async {
View_setViewport(view, width, height);
}
Future<RenderTarget?> getRenderTarget() async {
return renderTarget;
}
@override
Future setRenderTarget(covariant FFIRenderTarget? renderTarget) async {
if (renderTarget != null) {
View_setRenderTarget(view, renderTarget.renderTarget);
this.renderTarget = renderTarget;
} else {
View_setRenderTarget(view, nullptr);
}
}
@override
Future setCamera(FFICamera camera) async {
View_setCamera(view, camera.camera);
}
@override
Future<Viewport> getViewport() async {
final vp = View_getViewport(view);
return Viewport(vp.left, vp.bottom, vp.width, vp.height);
}
@override
Future<Camera> getCamera() async {
final cameraPtr = View_getCamera(view);
return FFICamera(cameraPtr, app);
}
@override
Future setAntiAliasing(bool msaa, bool fxaa, bool taa) async {
View_setAntiAliasing(view, msaa, fxaa, taa);
}
@override
Future setPostProcessing(bool enabled) async {
View_setPostProcessing(view, enabled);
}
@override
Future setFrustumCullingEnabled(bool enabled) async {
View_setFrustumCullingEnabled(view, enabled);
}
@override
Future setBloom(bool enabled, double strength) async {
await withVoidCallback((cb) {
View_setBloomRenderThread(view, enabled, strength, cb);
});
}
final colorGrading = <ToneMapper, Pointer<TColorGrading>>{};
@override
Future setToneMapper(ToneMapper mapper) async {
if (colorGrading[mapper] == null) {
colorGrading[mapper] =
await FilamentApp.instance!.createColorGrading(mapper);
if (colorGrading[mapper] == nullptr) {
throw Exception("Failed to create color grading");
}
}
View_setColorGrading(view, colorGrading[mapper]!);
}
Future setStencilBufferEnabled(bool enabled) async {
return View_setStencilBufferEnabled(view, enabled);
}
Future<bool> isStencilBufferEnabled() async {
return View_isStencilBufferEnabled(view);
}
Future setDithering(bool enabled) async {
View_setDitheringEnabled(view, enabled);
}
Future<bool> isDitheringEnabled() async {
return View_isDitheringEnabled(view);
}
@override
Future setRenderQuality(QualityLevel quality) async {
View_setRenderQuality(view, quality.index);
}
Future setScene(covariant FFIScene scene) async {
View_setScene(view, scene.scene);
}
@override
Future setLayerVisibility(VisibilityLayers layer, bool visible) async {
View_setLayerEnabled(view, layer.value, visible);
}
Future setBlendMode(BlendMode blendMode) async {
View_setBlendMode(view, blendMode.index);
}
@override
Future<Scene> getScene() async {
final ptr = View_getScene(view);
return FFIScene(ptr);
}
int _pickRequestId = -1;
static int kMaxPickRequests = 1024;
final _pickRequests = List<({void Function(PickResult) handler, int x, int y})?>.generate(kMaxPickRequests, (idx) => null);
///
///
///
@override
Future pick(int x, int y, void Function(PickResult) resultHandler) async {
_pickRequestId = max(_pickRequestId + 1, kMaxPickRequests);
_pickRequests[_pickRequestId % kMaxPickRequests] =
(handler: resultHandler, x: x, y: y);
var pickRequestId = _pickRequestId;
var viewport = await getViewport();
y = viewport.height - y;
View_pick(
view, pickRequestId, x, y, _onPickResultHolder.pointer);
}
void _onPickResult(int requestId, ThermionEntity entityId, double depth,
double fragX, double fragY, double fragZ) async {
final modRequestId = requestId % kMaxPickRequests;
if (_pickRequests[modRequestId] == null) {
_logger.severe(
"Warning : pick result received with no matching request ID. This indicates you're clearing the pick cache too quickly");
return;
}
final (:handler, :x, :y) = _pickRequests[modRequestId]!;
_pickRequests[modRequestId] = null;
final viewport = await getViewport();
handler.call((
entity: entityId,
x: x,
y: y,
depth: depth,
fragX: fragX,
fragY: viewport.height - fragY,
fragZ: fragZ,
));
}
}

View File

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

View File

@@ -0,0 +1,3 @@
export 'resource_loader_io.dart'
if (dart.library.io) 'resource_loader_io.dart'
if (dart.library.js_interop) 'resource_loader_js.dart';

View File

@@ -0,0 +1,8 @@
import 'dart:io';
import 'package:thermion_dart/thermion_dart.dart';
Future<Uint8List> defaultResourceLoader(String path) {
print("Loading file $path");
return File(path).readAsBytes();
}

View File

@@ -0,0 +1,11 @@
import 'package:thermion_dart/thermion_dart.dart';
import 'package:http/http.dart' as http;
Future<Uint8List> defaultResourceLoader(String path) async {
if(path.startsWith("file://")) {
throw Exception("Unsupported URI : $path");
}
final response = await http.get(Uri.parse(path));
return response.bodyBytes;
}

View File

@@ -1,7 +1,7 @@
library;
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/src/filament/src/interface/layers.dart';
import 'package:thermion_dart/thermion_dart.dart';
export 'geometry.dart';

View File

@@ -1,4 +1,4 @@
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/src/filament/src/interface/layers.dart';
import 'package:thermion_dart/thermion_dart.dart';
enum Projection { Perspective, Orthographic }

View File

@@ -1,15 +1,11 @@
import 'dart:typed_data';
import 'package:thermion_dart/src/filament/src/engine.dart';
import 'package:thermion_dart/src/filament/src/scene.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_material.dart';
import 'package:thermion_dart/src/filament/src/interface/scene.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FilamentConfig<T, U> {
final Backend backend;
final T? renderCallback;
final U? renderCallbackOwner;
final Future<Uint8List> Function(String) resourceLoader;
Future<Uint8List> Function(String)? resourceLoader;
final U? platform;
final U? sharedContext;
final String? uberArchivePath;

View File

@@ -0,0 +1,37 @@
import 'dart:typed_data';
import 'package:thermion_dart/src/bindings/bindings.dart';
import '../../../viewer/viewer.dart';
class Geometry {
final Float32List vertices;
final Uint16List indices;
late final Float32List normals;
late final Float32List uvs;
final PrimitiveType primitiveType;
Geometry(
this.vertices,
this.indices, {
Float32List? normals,
Float32List? uvs,
this.primitiveType = PrimitiveType.TRIANGLES,
}) {
this.uvs = uvs ?? Float32List(0);
this.normals = normals ?? Float32List(0);
if (this.uvs.length != 0 && this.uvs.length != (vertices.length ~/ 3 * 2)) {
throw Exception(
"Expected either ${indices.length * 2} UVs, got ${this.uvs!.length}");
}
}
void scale(double factor) {
for (int i = 0; i < vertices.length; i++) {
vertices[i] = vertices[i] * factor;
}
}
bool get hasNormals => normals?.isNotEmpty == true;
bool get hasUVs => uvs?.isNotEmpty == true;
}

View File

@@ -25,4 +25,7 @@ abstract class GizmoAsset extends ThermionAsset {
Future unhighlight();
bool isNonPickable(ThermionEntity entity);
bool isGizmoEntity(ThermionEntity entity);
Future dispose();
}

View File

@@ -1,5 +1,3 @@
import 'dart:typed_data';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';

View File

@@ -1,4 +1,4 @@
import '../../viewer/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,5 +1,5 @@
import 'package:thermion_dart/src/filament/src/layers.dart';
import 'package:thermion_dart/src/filament/src/scene.dart';
import 'package:thermion_dart/src/filament/src/interface/layers.dart';
import 'package:thermion_dart/src/filament/src/interface/scene.dart';
import 'package:thermion_dart/thermion_dart.dart';
enum BlendMode {
@@ -57,7 +57,10 @@ abstract class View {
Future pick(int x, int y, void Function(PickResult) resultHandler);
///
///
///
Future dispose();
}