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 b207a48175
commit 479a9ead03
4 changed files with 106 additions and 35 deletions

View File

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

View File

@@ -1,6 +1,7 @@
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:math';
import 'dart:typed_data';
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:logging/logging.dart';
@@ -371,7 +372,8 @@ class ThermionViewerJS implements ThermionViewer {
double crossfade = 0.0,
double startOffset = 0.0}) async {
await _shim
.playAnimation(entity, index, loop, reverse, replaceActive, crossfade, startOffset)
.playAnimation(
entity, index, loop, reverse, replaceActive, crossfade, startOffset)
.toDart;
}
@@ -849,4 +851,10 @@ class ThermionViewerJS implements ThermionViewer {
Future setSoftShadowOptions(double penumbraScale, double penumbraRatioScale) {
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')
external JSPromise render();
@JS('capture')
external JSPromise<JSUint8Array> capture();
@JS('setFrameRate')
external JSPromise setFrameRate(int framerate);

View File

@@ -31,6 +31,9 @@ extension type _EmscriptenModule(JSObject _) implements JSObject {
external void stringToUTF8(
JSString str, JSNumber ptr, JSNumber maxBytesToWrite);
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 HEAPU32;
external JSAny get HEAP32;
@@ -52,6 +55,8 @@ class ThermionViewerWasm implements ThermionViewer {
String? assetPathPrefix;
late (double, double) viewportDimensions;
///
/// Construct an instance of this class by explicitly passing the
/// module instance via the [module] property, or by specifying [moduleName],
@@ -81,6 +86,8 @@ class ThermionViewerWasm implements ThermionViewer {
JSNumber? _viewer;
JSNumber? _sceneManager;
int _width = 0;
int _height = 0;
Future initialize(int width, int height, {String? uberArchivePath}) async {
if (!_initialized) {
@@ -103,7 +110,7 @@ class ThermionViewerWasm implements ThermionViewer {
"create_filament_viewer",
"void*",
["void*".toJS, "void*".toJS, "void*".toJS, "string".toJS].toJS,
[context, loader, null, uberArchivePath?.toJS].toJS,
[context!, loader, null, uberArchivePath?.toJS].toJS,
null) as JSNumber;
await createSwapChain(width, height);
updateViewportAndCameraProjection(width, height, 1.0);
@@ -143,6 +150,9 @@ class ThermionViewerWasm implements ThermionViewer {
void updateViewportAndCameraProjection(
int width, int height, double scaleFactor) {
_width = width;
_height = height;
viewportDimensions = (width.toDouble(), height.toDouble());
_module!.ccall(
"update_viewport_and_camera_projection",
"void",
@@ -772,6 +782,54 @@ class ThermionViewerWasm implements ThermionViewer {
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
Scene get scene => throw UnimplementedError();
@@ -1330,21 +1388,21 @@ class ThermionViewerWasm implements ThermionViewer {
}
@override
Future panEnd() {
// TODO: implement panEnd
throw UnimplementedError();
Future panEnd() async {
_module!.ccall("grab_end", "void",
["void*".toJS].toJS, [_viewer!].toJS, null);
}
@override
Future panStart(double x, double y) {
// TODO: implement panStart
throw UnimplementedError();
Future panStart(double x, double y) async {
_module!.ccall("grab_begin", "void",
["void*".toJS, "float".toJS, "float".toJS, "bool".toJS].toJS, [_viewer!, x.toJS, y.toJS, true.toJS].toJS, null);
}
@override
Future panUpdate(double x, double y) {
// TODO: implement panUpdate
throw UnimplementedError();
Future panUpdate(double x, double y) async {
_module!.ccall("grab_update", "void",
["void*".toJS, "float".toJS, "float".toJS].toJS, [_viewer!, x.toJS, y.toJS].toJS, null);
}
@override
@@ -1525,12 +1583,6 @@ class ThermionViewerWasm implements ThermionViewer {
}
}
@override
Future rotateEnd() {
// TODO: implement rotateEnd
throw UnimplementedError();
}
@override
Future rotateIbl(Matrix3 rotation) async {
final ptr = _module!._malloc(9 * 4) as JSNumber;
@@ -1544,15 +1596,21 @@ class ThermionViewerWasm implements ThermionViewer {
}
@override
Future rotateStart(double x, double y) {
// TODO: implement rotateStart
throw UnimplementedError();
Future rotateStart(double x, double y) async {
_module!.ccall("grab_begin", "void",
["void*".toJS, "float".toJS, "float".toJS, "bool".toJS].toJS, [_viewer!, x.toJS, y.toJS, false.toJS].toJS, null);
}
@override
Future rotateUpdate(double x, double y) {
// TODO: implement rotateUpdate
throw UnimplementedError();
Future rotateUpdate(double x, double y) async {
_module!.ccall("grab_update", "void",
["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
@@ -1936,19 +1994,19 @@ class ThermionViewerWasm implements ThermionViewer {
@override
Future zoomBegin() async {
_module!.ccall(
"zoom_begin", "void", ["void*".toJS].toJS, [_viewer!].toJS, null);
"scroll_begin", "void", ["void*".toJS].toJS, [_viewer!].toJS, null);
}
@override
Future zoomEnd() async {
_module!
.ccall("zoom_end", "void", ["void*".toJS].toJS, [_viewer!].toJS, null);
.ccall("scroll_end", "void", ["void*".toJS].toJS, [_viewer!].toJS, null);
}
@override
Future zoomUpdate(double x, double y, double z) async {
_module!.ccall(
"zoom_update",
"scroll_update",
"void",
["void*".toJS, "float".toJS, "float".toJS, "float".toJS].toJS,
[_viewer!, x.toJS, y.toJS, z.toJS].toJS,