feat: remove bounding box from SceneAsset and create renderable wireframe bounding box in ThermionAsset
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>())));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,8 +23,6 @@ class SceneAsset {
|
||||
|
||||
}
|
||||
|
||||
virtual const Aabb getBoundingBox() const = 0;
|
||||
|
||||
virtual SceneAssetType getType() = 0;
|
||||
|
||||
virtual utils::Entity getEntity() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user