From ae9ce197c135ff8992299048cafa6a33246464bb Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Tue, 10 Jun 2025 10:27:03 +0800 Subject: [PATCH] move setStencilHighlight/removeStencilHighlight to Scene --- .../src/implementation/ffi_asset.dart | 83 ------------------- .../src/implementation/ffi_filament_app.dart | 8 ++ .../src/implementation/ffi_scene.dart | 66 +++++++++++++++ .../lib/src/filament/src/interface/asset.dart | 11 --- .../lib/src/filament/src/interface/scene.dart | 20 ++++- .../lib/src/filament/src/interface/view.dart | 4 +- .../src/ffi/src/thermion_viewer_ffi.dart | 4 +- .../test/material_stencil_buffer_tests.dart | 53 +++--------- thermion_dart/test/scene_tests.dart | 30 +++++++ 9 files changed, 139 insertions(+), 140 deletions(-) create mode 100644 thermion_dart/test/scene_tests.dart diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart index 45b689c9..b45068df 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart @@ -190,89 +190,6 @@ class FFIAsset extends ThermionAsset { 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); - throw Exception("TODO"); - // final materialInstancePtr = - // await withPointerCallback((cb) { - - // MaterialProvider_createMaterialInstanceRenderThread( - // app.ubershaderMaterialProvider, , 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(); - } - /// /// /// diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart index d4a6cfb3..ce5423ef 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart @@ -1177,4 +1177,12 @@ class FFIFilamentApp extends FilamentApp { } return transform; } + + /// + /// + /// + @override + Future setPriority(ThermionEntity entity, int priority) async { + RenderableManager_setPriority(renderableManager, entity, priority); + } } diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart index 4a747a80..446f024c 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart @@ -21,4 +21,70 @@ class FFIScene extends Scene { Future remove(covariant FFIAsset asset) async { SceneAsset_removeFromScene(asset.asset, scene); } + + final _outlines = {}; + + /// + /// + /// + @override + Future removeStencilHighlight(ThermionAsset asset) async { + if (_outlines.containsKey(asset)) { + final highlight = _outlines[asset]!; + await remove(highlight); + } + } + + /// + /// + /// + @override + Future setStencilHighlight(ThermionAsset asset, + {double r = 1.0, + double g = 0.0, + double b = 0.0, + int? entity, + int primitiveIndex = 0}) async { + entity ??= asset.entity; + + if (!_outlines.containsKey(asset)) { + var sourceMaterialInstance = + await asset.getMaterialInstanceAt(entity: entity); + await sourceMaterialInstance.setStencilWriteEnabled(true); + await sourceMaterialInstance.setDepthWriteEnabled(true); + await sourceMaterialInstance + .setStencilOpDepthStencilPass(StencilOperation.REPLACE); + await sourceMaterialInstance + .setStencilCompareFunction(SamplerCompareFunction.A); + + await sourceMaterialInstance + .setStencilReferenceValue(View.STENCIL_HIGHLIGHT_REFERENCE_VALUE); + + var highlightMaterialInstance = + await FilamentApp.instance!.createUnlitMaterialInstance(); + + await highlightMaterialInstance + .setStencilCompareFunction(SamplerCompareFunction.NE); + await highlightMaterialInstance + .setStencilReferenceValue(View.STENCIL_HIGHLIGHT_REFERENCE_VALUE); + await highlightMaterialInstance.setDepthCullingEnabled(false); + await highlightMaterialInstance.setParameterFloat4( + "baseColorFactor", r, g, b, 1.0); + + var highlightInstance = await asset + .createInstance(materialInstances: [highlightMaterialInstance]); + await add(highlightInstance as FFIAsset); + _outlines[asset] = highlightInstance as FFIAsset; + + var transform = await FilamentApp.instance! + .getWorldTransform(highlightInstance.entity); + + await FilamentApp.instance!.setTransform(highlightInstance.entity, + Matrix4.diagonal3(Vector3(1.1, 1.1, 1.1)) * transform); + + await FilamentApp.instance!.setPriority(highlightInstance.entity, 7); + + await FilamentApp.instance!.setParent(highlightInstance.entity, entity); + } + } } diff --git a/thermion_dart/lib/src/filament/src/interface/asset.dart b/thermion_dart/lib/src/filament/src/interface/asset.dart index 156c9922..1811cd32 100644 --- a/thermion_dart/lib/src/filament/src/interface/asset.dart +++ b/thermion_dart/lib/src/filament/src/interface/asset.dart @@ -87,17 +87,6 @@ abstract class ThermionAsset { throw UnimplementedError(); } - /// 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(); - /// /// The dimensions of the bounding box for this asset. /// This is independent of the boundingBoxAsset (which is used to visualize diff --git a/thermion_dart/lib/src/filament/src/interface/scene.dart b/thermion_dart/lib/src/filament/src/interface/scene.dart index b941f130..07540578 100644 --- a/thermion_dart/lib/src/filament/src/interface/scene.dart +++ b/thermion_dart/lib/src/filament/src/interface/scene.dart @@ -1,13 +1,12 @@ import 'package:thermion_dart/thermion_dart.dart'; abstract class Scene { - /// Adds all renderable entities in [asset] to this scene. /// /// Future add(covariant ThermionAsset asset); - - /// Adds [entity] to this scene (which is assumed to have a Renderable + + /// Adds [entity] to this scene (which is assumed to have a Renderable /// component). /// Future addEntity(ThermionEntity entity); @@ -15,4 +14,19 @@ abstract class Scene { /// Removes all renderable entities in [asset] from this scene. /// Future remove(covariant ThermionAsset asset); + + + /// Renders an outline around [entity] with the given color. + /// + /// + Future setStencilHighlight(ThermionAsset asset,{double r = 1.0, + double g = 0.0, + double b = 0.0, + int? entity, + int primitiveIndex = 0}); + + /// Removes the outline around [entity]. Noop if there was no highlight. + /// + /// + Future removeStencilHighlight(ThermionAsset asset); } diff --git a/thermion_dart/lib/src/filament/src/interface/view.dart b/thermion_dart/lib/src/filament/src/interface/view.dart index 4d13f289..cc75e5f2 100644 --- a/thermion_dart/lib/src/filament/src/interface/view.dart +++ b/thermion_dart/lib/src/filament/src/interface/view.dart @@ -53,6 +53,8 @@ enum QualityLevel { LOW, MEDIUM, HIGH, ULTRA } abstract class View { + static int STENCIL_HIGHLIGHT_REFERENCE_VALUE = 1; + /// Gets the scene currently associated with this View. /// /// @@ -79,7 +81,7 @@ abstract class View { Future setShadowsEnabled(bool enabled); Future setLayerVisibility(VisibilityLayers layer, bool visible); - /// Sets the fog options for this view. + /// Sets the fog options for this view. /// Fog is disabled by default /// Future setFogOptions(FogOptions options); diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart index 5999b974..67d0671c 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart @@ -592,8 +592,8 @@ class ThermionViewerFFI extends ThermionViewer { /// /// @override - Future setPriority(ThermionEntity entityId, int priority) async { - RenderableManager_setPriority(app.renderableManager, entityId, priority); + Future setPriority(ThermionEntity entity, int priority) async { + return FilamentApp.instance!.setPriority(entity, priority); } /// diff --git a/thermion_dart/test/material_stencil_buffer_tests.dart b/thermion_dart/test/material_stencil_buffer_tests.dart index bf66efa6..d03c29db 100644 --- a/thermion_dart/test/material_stencil_buffer_tests.dart +++ b/thermion_dart/test/material_stencil_buffer_tests.dart @@ -98,7 +98,7 @@ void main() async { await mi.setStencilCompareFunction( SamplerCompareFunction.N, StencilFace.FRONT_AND_BACK); } - + // should be totally empty await testHelper.capture(viewer.view, "stencil_never"); @@ -113,30 +113,30 @@ void main() async { // set the blue cube to always pass the stencil test await blueMaterialInstance.setStencilCompareFunction( - SamplerCompareFunction.A, StencilFace.FRONT_AND_BACK); + SamplerCompareFunction.A, StencilFace.FRONT_AND_BACK); // when blue cube passes depth + stencil, replace the default stencil value (0) with 1 - await blueMaterialInstance.setStencilOpDepthStencilPass(StencilOperation.REPLACE); + await blueMaterialInstance + .setStencilOpDepthStencilPass(StencilOperation.REPLACE); await blueMaterialInstance.setStencilReferenceValue(1); - - // set the green cube to only pass the stencil test where stencil value is + + // set the green cube to only pass the stencil test where stencil value is // not equal to 0 await greenMaterialInstance.setStencilCompareFunction( - SamplerCompareFunction.NE, StencilFace.FRONT_AND_BACK); + SamplerCompareFunction.NE, StencilFace.FRONT_AND_BACK); await greenMaterialInstance.setStencilReferenceValue(0); - // green cube will only be rendered where it overlaps with blue cube + // green cube will only be rendered where it overlaps with blue cube await testHelper.capture(viewer.view, "stencil_ne"); - // set the green cube to only pass the stencil test where stencil value is + // set the green cube to only pass the stencil test where stencil value is // equal to 0 await greenMaterialInstance.setStencilCompareFunction( - SamplerCompareFunction.E, StencilFace.FRONT_AND_BACK); - - // green cube renders where it does not overlap with blue cube (same as if - // we had disabled depth writes and rendered the green cube, then the blue + SamplerCompareFunction.E, StencilFace.FRONT_AND_BACK); + + // green cube renders where it does not overlap with blue cube (same as if + // we had disabled depth writes and rendered the green cube, then the blue // cube) await testHelper.capture(viewer.view, "stencil_eq"); - }, bg: null, postProcessing: true, @@ -144,32 +144,5 @@ void main() async { createRenderTarget: false); }); - // test('fail stencil not equal', () async { - // await testHelper.withViewer((viewer) async { - // final ( - // :blueCube, - // :blueMaterialInstance, - // :greenCube, - // :greenMaterialInstance - // ) = await setup(viewer); - // // this ensures the blue cube is rendered before the green cube - // await viewer.setPriority(blueCube.entity, 0); - // await viewer.setPriority(greenCube.entity, 1); - - // await blueMaterialInstance.setStencilWriteEnabled(true); - // await blueMaterialInstance.setStencilReferenceValue(1); - // await blueMaterialInstance - // .setStencilCompareFunction(SamplerCompareFunction.A); - // await blueMaterialInstance - // .setStencilOpDepthStencilPass(StencilOperation.REPLACE); - - // await greenMaterialInstance.setStencilReferenceValue(1); - // await greenMaterialInstance - // .setStencilCompareFunction(SamplerCompareFunction.E); - - // // green cube is only rendered where it intersects with the blue cube - // await testHelper.capture(viewer.view, "fail_stencil_ne"); - // }, postProcessing: true); - // }); } diff --git a/thermion_dart/test/scene_tests.dart b/thermion_dart/test/scene_tests.dart new file mode 100644 index 00000000..17a233eb --- /dev/null +++ b/thermion_dart/test/scene_tests.dart @@ -0,0 +1,30 @@ +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:test/test.dart'; +import 'helpers.dart'; + +void main() async { + final testHelper = TestHelper("scene"); + await testHelper.setup(); + + test('show stencil highlight', () async { + await testHelper.withViewer((viewer) async { + await viewer.view.setStencilBufferEnabled(true); + final cube = await viewer.createGeometry( + GeometryHelper.cube( + normals: true, + uvs: true, + ), + keepData: true, + materialInstances: []); + + await viewer.addToScene(cube); + + final scene = await viewer.view.getScene(); + await scene.setStencilHighlight(cube); + + await testHelper.capture(viewer.view, "stencil_highlight"); + await scene.removeStencilHighlight(cube); + await testHelper.capture(viewer.view, "stencil_highlight_removed"); + }, createStencilBuffer: true); + }); +}