feat: remove bounding box from SceneAsset and create renderable wireframe bounding box in ThermionAsset

This commit is contained in:
Nick Fisher
2024-12-21 07:45:50 +08:00
parent 51bdca7158
commit 8d8acef481
13 changed files with 274 additions and 53 deletions

View File

@@ -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<ThermionAsset>.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<void> 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<TMaterialInstance>((cb) {
final key = Struct.create<TMaterialKey>();
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();
}
}
}

View File

@@ -32,6 +32,7 @@ class FFIGizmo extends FFIAsset implements GizmoAsset {
super.sceneManager,
super.renderableManager,
super.unlitMaterialProvider,
super.viewer,
this.gizmoEntities) {
_nativeCallback =
NativeCallable<GizmoPickCallbackFunction>.listener(_onPickResult);

View File

@@ -230,6 +230,22 @@ external void MaterialInstance_setStencilWriteMask(
int mask,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TMaterialInstance>, ffi.UnsignedInt)>(
symbol: "MaterialInstance_setTransparencyMode", isLeaf: true)
external void _MaterialInstance_setTransparencyMode(
ffi.Pointer<TMaterialInstance> materialInstance,
int transparencyMode,
);
void MaterialInstance_setTransparencyMode(
ffi.Pointer<TMaterialInstance> materialInstance,
TTransparencyMode transparencyMode,
) =>
_MaterialInstance_setTransparencyMode(
materialInstance,
transparencyMode.value,
);
@ffi.Native<
ffi.Pointer<TViewer> Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Char>)>(isLeaf: true)
@@ -741,6 +757,12 @@ external void View_setFrustumCullingEnabled(
bool enabled,
);
@ffi.Native<ffi.Pointer<TRenderTarget> Function(ffi.Pointer<TView>)>(
isLeaf: true)
external ffi.Pointer<TRenderTarget> View_getRenderTarget(
ffi.Pointer<TView> tView,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TView>, ffi.Bool)>(isLeaf: true)
external void View_setPostProcessing(
ffi.Pointer<TView> tView,
@@ -1310,6 +1332,15 @@ external void Viewer_createRenderTargetRenderThread(
onComplete,
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<TRenderTarget>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void Viewer_destroyRenderTargetRenderThread(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TRenderTarget> tRenderTarget,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TEngine>,
@@ -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;

View File

@@ -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<TSceneAsset>())));
}

View File

@@ -12,9 +12,8 @@ export 'light_options.dart';
typedef ThermionEntity = int;
abstract class ThermionAsset {
ThermionEntity get entity;
Future<List<ThermionEntity>> getChildEntities();
///
@@ -28,6 +27,11 @@ abstract class ThermionAsset {
///
Future removeStencilHighlight();
///
/// When visible is [true], renders the bounding box.
///
Future setBoundingBoxVisibility(bool visible);
///
///
///

View File

@@ -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; }

View File

@@ -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);

View File

@@ -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());

View File

@@ -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;

View File

@@ -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;

View File

@@ -23,8 +23,6 @@ class SceneAsset {
}
virtual const Aabb getBoundingBox() const = 0;
virtual SceneAssetType getType() = 0;
virtual utils::Entity getEntity() {

View File

@@ -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);
}

View File

@@ -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<Geometry> createGridGeometry() async {
List<double> vertices = [];
List<int> 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 {