From 8d8acef48134aeaff8bcbe51473648c9dba06715 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Sat, 21 Dec 2024 07:45:50 +0800 Subject: [PATCH] feat: remove bounding box from SceneAsset and create renderable wireframe bounding box in ThermionAsset --- .../lib/src/viewer/src/ffi/src/ffi_asset.dart | 109 ++++++++++++++++- .../lib/src/viewer/src/ffi/src/ffi_gizmo.dart | 1 + .../viewer/src/ffi/src/thermion_dart.g.dart | 55 +++++++++ .../src/ffi/src/thermion_viewer_ffi.dart | 11 +- .../src/viewer/src/shared_types/entities.dart | 8 +- .../include/scene/GeometrySceneAsset.hpp | 8 -- thermion_dart/native/include/scene/Gizmo.hpp | 5 - .../native/include/scene/GltfSceneAsset.hpp | 4 - .../include/scene/GltfSceneAssetInstance.hpp | 4 - .../native/include/scene/GridOverlay.hpp | 5 - .../native/include/scene/SceneAsset.hpp | 2 - thermion_dart/native/src/scene/Gizmo.cpp | 3 + thermion_dart/test/overlay_tests.dart | 112 ++++++++++++++++-- 13 files changed, 274 insertions(+), 53 deletions(-) diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/ffi_asset.dart b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_asset.dart index ba4213a3..8c3ae94e 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/ffi_asset.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_asset.dart @@ -16,9 +16,10 @@ class FFIAsset extends ThermionAsset { final bool isInstance; late final ThermionEntity entity; + final ThermionViewer viewer; - FFIAsset( - this.pointer, this.sceneManager, this.engine, this._unlitMaterialProvider, + FFIAsset(this.pointer, this.sceneManager, this.engine, + this._unlitMaterialProvider, this.viewer, {this.isInstance = false}) { entity = SceneAsset_getEntity(pointer); } @@ -41,7 +42,8 @@ class FFIAsset extends ThermionAsset { if (instance == nullptr) { throw Exception("No instance available at index $index"); } - return FFIAsset(instance, sceneManager, engine, _unlitMaterialProvider); + return FFIAsset( + instance, sceneManager, engine, _unlitMaterialProvider, viewer); } /// @@ -71,7 +73,8 @@ class FFIAsset extends ThermionAsset { if (created == FILAMENT_ASSET_ERROR) { throw Exception("Failed to create instance"); } - return FFIAsset(created, sceneManager, engine, _unlitMaterialProvider); + return FFIAsset( + created, sceneManager, engine, _unlitMaterialProvider, viewer); } /// @@ -90,7 +93,7 @@ class FFIAsset extends ThermionAsset { var count = await getInstanceCount(); final result = List.generate(count, (i) { return FFIAsset(SceneAsset_getInstance(pointer, i), sceneManager, engine, - _unlitMaterialProvider); + _unlitMaterialProvider, viewer); }); return result; @@ -184,4 +187,100 @@ class FFIAsset extends ThermionAsset { SceneManager_removeFromScene(sceneManager, child); } } + + ThermionAsset? _boundingBoxAsset; + + Aabb3 getBoundingBox() { + final aabb3 = SceneManager_getRenderableBoundingBox(sceneManager, entity); + return aabb3; + } + + @override + Future setBoundingBoxVisibility(bool visible) async { + if (_boundingBoxAsset == null) { + final boundingBox = await getBoundingBox(); + 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 = [ + // 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((cb) { + final key = Struct.create(); + MaterialProvider_createMaterialInstanceRenderThread( + _unlitMaterialProvider, key.address, cb); + }); + + final material = FFIMaterialInstance(materialInstancePtr, sceneManager); + 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 viewer.createGeometry( + geometry, + materialInstances: [material], + keepData: false, + ); + } + if (visible) { + await _boundingBoxAsset!.addToScene(); + } else { + await _boundingBoxAsset!.removeFromScene(); + } + } } diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/ffi_gizmo.dart b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_gizmo.dart index 59138066..11c6da82 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/ffi_gizmo.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_gizmo.dart @@ -32,6 +32,7 @@ class FFIGizmo extends FFIAsset implements GizmoAsset { super.sceneManager, super.renderableManager, super.unlitMaterialProvider, + super.viewer, this.gizmoEntities) { _nativeCallback = NativeCallable.listener(_onPickResult); diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart index 3c9fd0fe..55b4a8b3 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart @@ -230,6 +230,22 @@ external void MaterialInstance_setStencilWriteMask( int mask, ); +@ffi.Native, ffi.UnsignedInt)>( + symbol: "MaterialInstance_setTransparencyMode", isLeaf: true) +external void _MaterialInstance_setTransparencyMode( + ffi.Pointer materialInstance, + int transparencyMode, +); + +void MaterialInstance_setTransparencyMode( + ffi.Pointer materialInstance, + TTransparencyMode transparencyMode, +) => + _MaterialInstance_setTransparencyMode( + materialInstance, + transparencyMode.value, + ); + @ffi.Native< ffi.Pointer Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer)>(isLeaf: true) @@ -741,6 +757,12 @@ external void View_setFrustumCullingEnabled( bool enabled, ); +@ffi.Native Function(ffi.Pointer)>( + isLeaf: true) +external ffi.Pointer View_getRenderTarget( + ffi.Pointer tView, +); + @ffi.Native, ffi.Bool)>(isLeaf: true) external void View_setPostProcessing( ffi.Pointer tView, @@ -1310,6 +1332,15 @@ external void Viewer_createRenderTargetRenderThread( onComplete, ); +@ffi.Native< + ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer>)>(isLeaf: true) +external void Viewer_destroyRenderTargetRenderThread( + ffi.Pointer viewer, + ffi.Pointer tRenderTarget, + ffi.Pointer> onComplete, +); + @ffi.Native< ffi.Void Function( ffi.Pointer, @@ -2730,6 +2761,30 @@ enum TCullingMode { }; } +enum TTransparencyMode { + /// ! the transparent object is drawn honoring the raster state + DEFAULT(0), + + /// the transparent object is first drawn in the depth buffer, + /// then in the color buffer, honoring the culling mode, but ignoring the depth test function + TWO_PASSES_ONE_SIDE(1), + + /// the transparent object is drawn twice in the color buffer, + /// first with back faces only, then with front faces; the culling + /// mode is ignored. Can be combined with two-sided lighting + TWO_PASSES_TWO_SIDES(2); + + final int value; + const TTransparencyMode(this.value); + + static TTransparencyMode fromValue(int value) => switch (value) { + 0 => DEFAULT, + 1 => TWO_PASSES_ONE_SIDE, + 2 => TWO_PASSES_TWO_SIDES, + _ => throw ArgumentError("Unknown value for TTransparencyMode: $value"), + }; +} + typedef EntityId = ffi.Int32; typedef DartEntityId = int; 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 8f47f411..c579dde1 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 @@ -511,7 +511,7 @@ class ThermionViewerFFI extends ThermionViewer { } var thermionAsset = - FFIAsset(asset, _sceneManager!, _engine!, _unlitMaterialProvider!); + FFIAsset(asset, _sceneManager!, _engine!, _unlitMaterialProvider!, this); return thermionAsset; } @@ -551,7 +551,7 @@ class ThermionViewerFFI extends ThermionViewer { throw Exception("An error occurred loading GLB from buffer"); } return FFIAsset( - assetPtr, _sceneManager!, _engine!, _unlitMaterialProvider!); + assetPtr, _sceneManager!, _engine!, _unlitMaterialProvider!, this); } /// @@ -573,7 +573,7 @@ class ThermionViewerFFI extends ThermionViewer { } return FFIAsset( - assetPtr, _sceneManager!, _engine!, _unlitMaterialProvider!); + assetPtr, _sceneManager!, _engine!, _unlitMaterialProvider!, this); } /// @@ -1642,7 +1642,7 @@ class ThermionViewerFFI extends ThermionViewer { } var asset = - FFIAsset(assetPtr, _sceneManager!, _engine!, _unlitMaterialProvider!); + FFIAsset(assetPtr, _sceneManager!, _engine!, _unlitMaterialProvider!, this); return asset; } @@ -1760,7 +1760,7 @@ class ThermionViewerFFI extends ThermionViewer { _sceneManager!, material.pointer, cb); } }); - _grid = FFIAsset(ptr, _sceneManager!, _engine!, _unlitMaterialProvider!); + _grid = FFIAsset(ptr, _sceneManager!, _engine!, _unlitMaterialProvider!, this); } await _grid!.addToScene(); await setLayerVisibility(VisibilityLayers.OVERLAY, true); @@ -2093,6 +2093,7 @@ class ThermionViewerFFI extends ThermionViewer { _sceneManager!, _engine!, nullptr, + this, gizmoEntities.toSet() ..add(SceneAsset_getEntity(gizmo.cast()))); } diff --git a/thermion_dart/lib/src/viewer/src/shared_types/entities.dart b/thermion_dart/lib/src/viewer/src/shared_types/entities.dart index c6f048f1..159c7c48 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/entities.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/entities.dart @@ -12,9 +12,8 @@ export 'light_options.dart'; typedef ThermionEntity = int; abstract class ThermionAsset { - ThermionEntity get entity; - + Future> getChildEntities(); /// @@ -28,6 +27,11 @@ abstract class ThermionAsset { /// Future removeStencilHighlight(); + /// + /// When visible is [true], renders the bounding box. + /// + Future setBoundingBoxVisibility(bool visible); + /// /// /// diff --git a/thermion_dart/native/include/scene/GeometrySceneAsset.hpp b/thermion_dart/native/include/scene/GeometrySceneAsset.hpp index 361680b2..2ad34a3d 100644 --- a/thermion_dart/native/include/scene/GeometrySceneAsset.hpp +++ b/thermion_dart/native/include/scene/GeometrySceneAsset.hpp @@ -53,14 +53,6 @@ namespace thermion return _materialInstanceCount; } - const Aabb getBoundingBox() const override - { - return Aabb { - - .min = _boundingBox.getMin(), // center - halfExtent - .max = _boundingBox.getMax() // center + halfExtent - }; - } VertexBuffer *getVertexBuffer() const { return _vertexBuffer; } IndexBuffer *getIndexBuffer() const { return _indexBuffer; } diff --git a/thermion_dart/native/include/scene/Gizmo.hpp b/thermion_dart/native/include/scene/Gizmo.hpp index 6acae69a..2df4e2d9 100644 --- a/thermion_dart/native/include/scene/Gizmo.hpp +++ b/thermion_dart/native/include/scene/Gizmo.hpp @@ -58,11 +58,6 @@ namespace thermion None }; - const Aabb getBoundingBox() const override - { - return Aabb{}; - } - typedef void (*GizmoPickCallback)(Gizmo::GizmoPickResultType result, float x, float y, float z); void pick(uint32_t x, uint32_t y, GizmoPickCallback callback); diff --git a/thermion_dart/native/include/scene/GltfSceneAsset.hpp b/thermion_dart/native/include/scene/GltfSceneAsset.hpp index 6da6f7af..bb4ad079 100644 --- a/thermion_dart/native/include/scene/GltfSceneAsset.hpp +++ b/thermion_dart/native/include/scene/GltfSceneAsset.hpp @@ -78,10 +78,6 @@ namespace thermion return _asset; } - const Aabb getBoundingBox() const override { - return _asset->getBoundingBox(); - } - void addAllEntities(Scene *scene) override { scene->addEntities(_asset->getEntities(), _asset->getEntityCount()); diff --git a/thermion_dart/native/include/scene/GltfSceneAssetInstance.hpp b/thermion_dart/native/include/scene/GltfSceneAssetInstance.hpp index 4a2041a2..2dcc3f96 100644 --- a/thermion_dart/native/include/scene/GltfSceneAssetInstance.hpp +++ b/thermion_dart/native/include/scene/GltfSceneAssetInstance.hpp @@ -43,10 +43,6 @@ namespace thermion return std::nullptr_t(); }; - const Aabb getBoundingBox() const override { - return _instance->getBoundingBox(); - } - SceneAssetType getType() override { return SceneAsset::SceneAssetType::Gltf; diff --git a/thermion_dart/native/include/scene/GridOverlay.hpp b/thermion_dart/native/include/scene/GridOverlay.hpp index 4864822e..b8a57eee 100644 --- a/thermion_dart/native/include/scene/GridOverlay.hpp +++ b/thermion_dart/native/include/scene/GridOverlay.hpp @@ -36,11 +36,6 @@ public: void setLayer(RenderableManager& rm, int layer) override; - const Aabb getBoundingBox() const override - { - return Aabb { }; - } - size_t getInstanceCount() override { return _instances.size(); } SceneAsset* getInstanceByEntity(utils::Entity entity) override; SceneAsset* getInstanceAt(size_t index) override; diff --git a/thermion_dart/native/include/scene/SceneAsset.hpp b/thermion_dart/native/include/scene/SceneAsset.hpp index 8aa7229a..11336654 100644 --- a/thermion_dart/native/include/scene/SceneAsset.hpp +++ b/thermion_dart/native/include/scene/SceneAsset.hpp @@ -23,8 +23,6 @@ class SceneAsset { } - virtual const Aabb getBoundingBox() const = 0; - virtual SceneAssetType getType() = 0; virtual utils::Entity getEntity() { diff --git a/thermion_dart/native/src/scene/Gizmo.cpp b/thermion_dart/native/src/scene/Gizmo.cpp index 9a690a6f..2bb64006 100644 --- a/thermion_dart/native/src/scene/Gizmo.cpp +++ b/thermion_dart/native/src/scene/Gizmo.cpp @@ -105,6 +105,9 @@ namespace thermion auto *hitTestMaterialInstance = _material->createInstance(); _materialInstances.push_back(hitTestMaterialInstance); hitTestMaterialInstance->setParameter("baseColorFactor", math::float4{0.0f, 0.0f, 0.0f, 0.0f}); + hitTestMaterialInstance->setTransparencyMode(MaterialInstance::TransparencyMode::TWO_PASSES_ONE_SIDE); + hitTestMaterialInstance->setCullingMode(MaterialInstance::CullingMode::BACK); + hitTestMaterialInstance->setParameter("scale", _scale); rm.setMaterialInstanceAt(renderableInstance, 0, hitTestMaterialInstance); } diff --git a/thermion_dart/test/overlay_tests.dart b/thermion_dart/test/overlay_tests.dart index 58022c54..ba099954 100644 --- a/thermion_dart/test/overlay_tests.dart +++ b/thermion_dart/test/overlay_tests.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'dart:math'; -import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart'; +import 'dart:typed_data'; + import 'package:thermion_dart/thermion_dart.dart'; import 'package:test/test.dart'; @@ -10,27 +10,91 @@ import 'helpers.dart'; void main() async { final testHelper = TestHelper("overlay"); + // var material = await viewer.createMaterial(File( + // "/Users/nickfisher/Documents/thermion/materials/grid.filamat") + // .readAsBytesSync()); + // var materialInstance = await material.createInstance(); + // await materialInstance.setCullingMode(CullingMode.NONE); + // await materialInstance.setParameterFloat("distance", 10000.0); + // await materialInstance.setParameterFloat("lineSize", 0.001); + + // var grid = await viewer.createGeometry(await createGridGeometry(), + // materialInstances: [materialInstance]); + + // await viewer.setPriority(grid.entity, 7); + + // await viewer.setViewFrustumCulling(false); + Future createGridGeometry() async { + List vertices = []; + List indices = []; + double stepSize = 1 / 4.0; + + for (double x = -1.0; x < 1.0; x += stepSize) { + for (double z = -1.0; z < 1.0; z += stepSize) { + int baseIndex = vertices.length ~/ 3; + var verts = [ + x, + 0.0, + z, + x, + 0.0, + z + stepSize, + x + stepSize, + 0.0, + z + stepSize, + x + stepSize, + 0.0, + z + ]; + vertices.addAll(verts); + + indices.addAll([ + baseIndex, + baseIndex + 1, + baseIndex + 2, + baseIndex + 2, + baseIndex + 3, + baseIndex + ]); + } + } + + return Geometry(Float32List.fromList(vertices), indices); + } group("overlay tests", () { group("grid", () { test('enable grid', () async { - await testHelper.withViewer((viewer) async { - await viewer.showGridOverlay(); - await viewer.setLayerVisibility(VisibilityLayers.OVERLAY, true); - await testHelper.capture(viewer, "grid_added_layer_visible"); - await viewer.setLayerVisibility(VisibilityLayers.OVERLAY, false); - await testHelper.capture(viewer, "grid_added_layer_invisible"); - await viewer.setLayerVisibility(VisibilityLayers.OVERLAY, true); - await viewer.removeGridOverlay(); - await testHelper.capture(viewer, "grid_remove_layer_visible"); - }, postProcessing: true); + await testHelper.withViewer( + (viewer) async { + var viewMatrix = makeViewMatrix( + Vector3(0, 20, 0), Vector3(0, 0, 0), Vector3(0, 0, -1)); + + var modelMatrix = viewMatrix.clone()..invert(); + await viewer.setCameraModelMatrix4(modelMatrix); + + await viewer.showGridOverlay(); + await viewer.setLayerVisibility(VisibilityLayers.OVERLAY, true); + + final cube = await viewer.createGeometry( + GeometryHelper.cube(normals: false, uvs: false)); + await testHelper.capture(viewer, "grid_added_layer_visible"); + await viewer.setLayerVisibility(VisibilityLayers.OVERLAY, false); + await testHelper.capture(viewer, "grid_added_layer_invisible"); + await viewer.setLayerVisibility(VisibilityLayers.OVERLAY, true); + await viewer.removeGridOverlay(); + await testHelper.capture(viewer, "grid_remove_layer_visible"); + }, + postProcessing: true, + ); }); }); group("stencil", () { test('set stencil highlight for geometry', () async { await testHelper.withViewer((viewer) async { - var cube = await viewer.createGeometry(GeometryHelper.cube()); + var cube = await viewer + .createGeometry(GeometryHelper.cube(normals: false, uvs: false)); await testHelper.capture(viewer, "geometry_before_stencil_highlight"); await cube.setStencilHighlight(); @@ -39,6 +103,12 @@ void main() async { await cube.removeStencilHighlight(); await testHelper.capture(viewer, "geometry_remove_stencil_highlight"); + + await viewer.setTransform( + cube.entity, Matrix4.translation(Vector3(1, 0, 0))); + await cube.setStencilHighlight(); + + await testHelper.capture(viewer, "geometry_add_stencil_highlight2"); }, postProcessing: true); }); @@ -62,6 +132,22 @@ void main() async { }, postProcessing: true, bg: kWhite); }); }); + + group("bounding box", () { + test('bounding box', () async { + await testHelper.withViewer( + (viewer) async { + final cube = await viewer.createGeometry( + GeometryHelper.cube(normals: false, uvs: false)); + await cube.setBoundingBoxVisibility(true); + await testHelper.capture(viewer, "bounding_box_visible"); + await cube.setBoundingBoxVisibility(false); + await testHelper.capture(viewer, "bounding_box_not_visible"); + }, + postProcessing: true, + ); + }); + }); }); // test('set uv scaling (unlit)', () async {