feat: (web) add capture() method and missing camera navigation controls

This commit is contained in:
Nick Fisher
2024-08-21 14:29:40 +08:00
parent 1051b1bb0e
commit 1d8faa70a1
4 changed files with 106 additions and 35 deletions

View File

@@ -48,6 +48,11 @@ class ThermionViewerJSDartBridge {
@JSExport() @JSExport()
JSPromise render() => viewer.render().toJS; JSPromise render() => viewer.render().toJS;
@JSExport()
JSPromise<JSUint8Array> capture() {
return viewer.capture().then((captured) => captured.toJS).toJS;
}
@JSExport() @JSExport()
JSPromise setFrameRate(int framerate) => viewer.setFrameRate(framerate).toJS; JSPromise setFrameRate(int framerate) => viewer.setFrameRate(framerate).toJS;
@@ -342,15 +347,12 @@ class ThermionViewerJSDartBridge {
double crossfade = 0.0, double crossfade = 0.0,
double startOffset = 0.0}) => double startOffset = 0.0}) =>
viewer viewer
.playAnimation( .playAnimation(entity, index,
entity, loop: loop,
index, reverse: reverse,
loop: loop, replaceActive: replaceActive,
reverse: reverse, crossfade: crossfade,
replaceActive: replaceActive, startOffset: startOffset)
crossfade: crossfade,
startOffset: startOffset
)
.toJS; .toJS;
@JSExport() @JSExport()

View File

@@ -1,6 +1,7 @@
import 'dart:js_interop'; import 'dart:js_interop';
import 'dart:js_interop_unsafe'; import 'dart:js_interop_unsafe';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@@ -371,7 +372,8 @@ class ThermionViewerJS implements ThermionViewer {
double crossfade = 0.0, double crossfade = 0.0,
double startOffset = 0.0}) async { double startOffset = 0.0}) async {
await _shim await _shim
.playAnimation(entity, index, loop, reverse, replaceActive, crossfade, startOffset) .playAnimation(
entity, index, loop, reverse, replaceActive, crossfade, startOffset)
.toDart; .toDart;
} }
@@ -849,4 +851,10 @@ class ThermionViewerJS implements ThermionViewer {
Future setSoftShadowOptions(double penumbraScale, double penumbraRatioScale) { Future setSoftShadowOptions(double penumbraScale, double penumbraRatioScale) {
return _shim.setSoftShadowOptions(penumbraScale, penumbraRatioScale).toDart; return _shim.setSoftShadowOptions(penumbraScale, penumbraRatioScale).toDart;
} }
@override
Future<Uint8List> capture() async {
final captured = await _shim.capture().toDart;
return captured.toDart;
}
} }

View File

@@ -22,6 +22,9 @@ extension type ThermionViewerJSShim(JSObject _) implements JSObject {
@JS('render') @JS('render')
external JSPromise render(); external JSPromise render();
@JS('capture')
external JSPromise<JSUint8Array> capture();
@JS('setFrameRate') @JS('setFrameRate')
external JSPromise setFrameRate(int framerate); external JSPromise setFrameRate(int framerate);

View File

@@ -31,6 +31,9 @@ extension type _EmscriptenModule(JSObject _) implements JSObject {
external void stringToUTF8( external void stringToUTF8(
JSString str, JSNumber ptr, JSNumber maxBytesToWrite); JSString str, JSNumber ptr, JSNumber maxBytesToWrite);
external void writeArrayToMemory(JSUint8Array data, JSNumber ptr); external void writeArrayToMemory(JSUint8Array data, JSNumber ptr);
external JSNumber addFunction(JSFunction f, String signature);
external void removeFunction(JSNumber f);
external JSAny get ALLOC_STACK; external JSAny get ALLOC_STACK;
external JSAny get HEAPU32; external JSAny get HEAPU32;
external JSAny get HEAP32; external JSAny get HEAP32;
@@ -52,6 +55,8 @@ class ThermionViewerWasm implements ThermionViewer {
String? assetPathPrefix; String? assetPathPrefix;
late (double, double) viewportDimensions;
/// ///
/// Construct an instance of this class by explicitly passing the /// Construct an instance of this class by explicitly passing the
/// module instance via the [module] property, or by specifying [moduleName], /// module instance via the [module] property, or by specifying [moduleName],
@@ -81,6 +86,8 @@ class ThermionViewerWasm implements ThermionViewer {
JSNumber? _viewer; JSNumber? _viewer;
JSNumber? _sceneManager; JSNumber? _sceneManager;
int _width = 0;
int _height = 0;
Future initialize(int width, int height, {String? uberArchivePath}) async { Future initialize(int width, int height, {String? uberArchivePath}) async {
if (!_initialized) { if (!_initialized) {
@@ -103,7 +110,7 @@ class ThermionViewerWasm implements ThermionViewer {
"create_filament_viewer", "create_filament_viewer",
"void*", "void*",
["void*".toJS, "void*".toJS, "void*".toJS, "string".toJS].toJS, ["void*".toJS, "void*".toJS, "void*".toJS, "string".toJS].toJS,
[context, loader, null, uberArchivePath?.toJS].toJS, [context!, loader, null, uberArchivePath?.toJS].toJS,
null) as JSNumber; null) as JSNumber;
await createSwapChain(width, height); await createSwapChain(width, height);
updateViewportAndCameraProjection(width, height, 1.0); updateViewportAndCameraProjection(width, height, 1.0);
@@ -143,6 +150,9 @@ class ThermionViewerWasm implements ThermionViewer {
void updateViewportAndCameraProjection( void updateViewportAndCameraProjection(
int width, int height, double scaleFactor) { int width, int height, double scaleFactor) {
_width = width;
_height = height;
viewportDimensions = (width.toDouble(), height.toDouble());
_module!.ccall( _module!.ccall(
"update_viewport_and_camera_projection", "update_viewport_and_camera_projection",
"void", "void",
@@ -772,6 +782,54 @@ class ThermionViewerWasm implements ThermionViewer {
null); null);
} }
Future<Uint8List> capture() async {
bool wasRendering = rendering;
await setRendering(false);
final pixelBuffer = _module!._malloc(_width * _height * 4) as JSNumber;
final completer = Completer();
final callback = () {
print("Callback invoked!");
completer.complete();
};
final callbackPtr = _module!.addFunction(callback.toJS, "v");
print("Aded functrion ${callbackPtr}, calling capture...");
_module!.ccall(
"capture",
"void",
["void*".toJS, "uint8_t*".toJS, "void*".toJS].toJS,
[_viewer!, pixelBuffer, callbackPtr].toJS,
null);
print("Waiting for completer...");
int iter = 0;
while (true) {
await Future.delayed(Duration(milliseconds: 5));
await render();
if (completer.isCompleted) {
break;
}
iter++;
if (iter > 1000) {
_module!._free(pixelBuffer);
throw Exception("Failed to complete capture");
}
}
// Create a Uint8ClampedList to store the pixel data
var data = Uint8List(_width * _height * 4);
for (int i = 0; i < data.length; i++) {
data[i] = (_module!.getValue(((pixelBuffer.toDartInt) + i).toJS, "i8")
as JSNumber)
.toDartInt;
}
_module!._free(pixelBuffer);
await setRendering(wasRendering);
print("Captured to ${data.length} pixel buffer");
_module!.removeFunction(callbackPtr);
return data;
}
@override @override
Scene get scene => throw UnimplementedError(); Scene get scene => throw UnimplementedError();
@@ -1330,21 +1388,21 @@ class ThermionViewerWasm implements ThermionViewer {
} }
@override @override
Future panEnd() { Future panEnd() async {
// TODO: implement panEnd _module!.ccall("grab_end", "void",
throw UnimplementedError(); ["void*".toJS].toJS, [_viewer!].toJS, null);
} }
@override @override
Future panStart(double x, double y) { Future panStart(double x, double y) async {
// TODO: implement panStart _module!.ccall("grab_begin", "void",
throw UnimplementedError(); ["void*".toJS, "float".toJS, "float".toJS, "bool".toJS].toJS, [_viewer!, x.toJS, y.toJS, true.toJS].toJS, null);
} }
@override @override
Future panUpdate(double x, double y) { Future panUpdate(double x, double y) async {
// TODO: implement panUpdate _module!.ccall("grab_update", "void",
throw UnimplementedError(); ["void*".toJS, "float".toJS, "float".toJS].toJS, [_viewer!, x.toJS, y.toJS].toJS, null);
} }
@override @override
@@ -1525,12 +1583,6 @@ class ThermionViewerWasm implements ThermionViewer {
} }
} }
@override
Future rotateEnd() {
// TODO: implement rotateEnd
throw UnimplementedError();
}
@override @override
Future rotateIbl(Matrix3 rotation) async { Future rotateIbl(Matrix3 rotation) async {
final ptr = _module!._malloc(9 * 4) as JSNumber; final ptr = _module!._malloc(9 * 4) as JSNumber;
@@ -1544,15 +1596,21 @@ class ThermionViewerWasm implements ThermionViewer {
} }
@override @override
Future rotateStart(double x, double y) { Future rotateStart(double x, double y) async {
// TODO: implement rotateStart _module!.ccall("grab_begin", "void",
throw UnimplementedError(); ["void*".toJS, "float".toJS, "float".toJS, "bool".toJS].toJS, [_viewer!, x.toJS, y.toJS, false.toJS].toJS, null);
} }
@override @override
Future rotateUpdate(double x, double y) { Future rotateUpdate(double x, double y) async {
// TODO: implement rotateUpdate _module!.ccall("grab_update", "void",
throw UnimplementedError(); ["void*".toJS, "float".toJS, "float".toJS].toJS, [_viewer!, x.toJS, y.toJS].toJS, null);
}
@override
Future rotateEnd() async {
_module!.ccall("grab_end", "void",
["void*".toJS].toJS, [_viewer!].toJS, null);
} }
@override @override
@@ -1936,19 +1994,19 @@ class ThermionViewerWasm implements ThermionViewer {
@override @override
Future zoomBegin() async { Future zoomBegin() async {
_module!.ccall( _module!.ccall(
"zoom_begin", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); "scroll_begin", "void", ["void*".toJS].toJS, [_viewer!].toJS, null);
} }
@override @override
Future zoomEnd() async { Future zoomEnd() async {
_module! _module!
.ccall("zoom_end", "void", ["void*".toJS].toJS, [_viewer!].toJS, null); .ccall("scroll_end", "void", ["void*".toJS].toJS, [_viewer!].toJS, null);
} }
@override @override
Future zoomUpdate(double x, double y, double z) async { Future zoomUpdate(double x, double y, double z) async {
_module!.ccall( _module!.ccall(
"zoom_update", "scroll_update",
"void", "void",
["void*".toJS, "float".toJS, "float".toJS, "float".toJS].toJS, ["void*".toJS, "float".toJS, "float".toJS, "float".toJS].toJS,
[_viewer!, x.toJS, y.toJS, z.toJS].toJS, [_viewer!, x.toJS, y.toJS, z.toJS].toJS,