diff --git a/thermion_dart/test/view_dependent_texture_mapping_tests.dart b/thermion_dart/test/view_dependent_texture_mapping_tests.dart new file mode 100644 index 00000000..5e1c0bc1 --- /dev/null +++ b/thermion_dart/test/view_dependent_texture_mapping_tests.dart @@ -0,0 +1,328 @@ +@Timeout(const Duration(seconds: 600)) +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; +import 'package:test/test.dart'; +import 'package:thermion_dart/src/utils/src/texture_projection.dart'; +import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'helpers.dart'; + +Future<(LinearImage, Texture, TextureSampler)> createTextureFromImage( + TestHelper testHelper) async { + final image = await FilamentApp.instance!.decodeImage( + File("${testHelper.testDir}/assets/cube_texture2_512x512.png") + .readAsBytesSync()); + final texture = await FilamentApp.instance! + .createTexture(await image.getWidth(), await image.getHeight()); + await texture.setLinearImage( + image, PixelDataFormat.RGBA, PixelDataType.FLOAT); + + return (image, texture, await FilamentApp.instance!.createTextureSampler()); +} + +Future<(MaterialInstance, Texture)> _makeVDTMMaterial( + ThermionViewer viewer, + List cameraForwardVectors, + int width, + int height, + int channels, +) async { + final sampler = await FilamentApp.instance!.createTextureSampler( + compareMode: TextureCompareMode.COMPARE_TO_TEXTURE, + compareFunc: TextureCompareFunc.GREATER); + + var texture = await FilamentApp.instance!.createTexture( + width, + height, + textureSamplerType: TextureSamplerType.SAMPLER_3D, + depth: cameraForwardVectors.length, + textureFormat: TextureFormat.RGBA32F, + ); + + final vdtm = await FilamentApp.instance!.createMaterial( + File( + "/Users/nickfisher/Documents/thermion/materials/vdtm.filamat", + ).readAsBytesSync(), + ); + + final materialInstance = await vdtm.createInstance(); + await materialInstance.setParameterBool("flipUVs", true); + await materialInstance.setParameterFloat3Array( + "cameraForwardVectors", + cameraForwardVectors, + ); + await materialInstance.setParameterTexture( + "perspectives", + texture, + sampler, + ); + return (materialInstance, texture); +} + +Future _makeCube( + TestHelper testHelper, ThermionViewer viewer) async { + final cube = await testHelper.createCube(viewer); + var ubershader = await cube.getMaterialInstanceAt(); + await ubershader.setDepthCullingEnabled(true); + await ubershader.setDepthWriteEnabled(true); + await ubershader.setCullingMode(CullingMode.BACK); + await ubershader.setParameterInt("baseColorIndex", 0); + await ubershader.setParameterFloat4("baseColorFactor", 1.0, 1.0, 1.0, 0.0); + + return cube; +} + +void main() async { + final testHelper = TestHelper("vdtm"); + await testHelper.setup(); + test('basic color interpolation', () async { + await testHelper.withViewer((viewer) async { + final dist = 5.0; + final numPositions = 3; + final cameraPositions = List.generate( + numPositions, + (i) => Vector3( + sin(i / numPositions * pi / 4) * dist, + dist, + cos(i / numPositions * pi / 4) * dist, + ), + ); + + final cameraForwardVectors = cameraPositions.map((c) { + var viewMatrix = makeViewMatrix(c, Vector3.zero(), Vector3(0, 1, 0)); + viewMatrix.invert(); + return -viewMatrix.forward; + }).toList(); + + final camera = await viewer.view.getCamera(); + + final (numCameraPositions, width, height, channels) = ( + cameraPositions.length, + 1, + 1, + 4, + ); + final (vdtmMi, texture) = await _makeVDTMMaterial( + viewer, cameraForwardVectors, width, height, channels); + + await FilamentApp.instance!.flush(); + final cube = await _makeCube(testHelper, viewer); + await cube.setMaterialInstanceAt(vdtmMi); + for (int i = 0; i < numCameraPositions; i++) { + await camera.lookAt(cameraPositions[i]); + final pixelBuffer = Float32List.fromList([ + 1 - (i / numCameraPositions), + i / numCameraPositions, + 0.0, + 1.0, + ]); + var byteBuffer = pixelBuffer.buffer.asUint8List( + pixelBuffer.offsetInBytes, + ); + await texture.setImage3D( + 0, + 0, + 0, + i, + width, + height, + channels, + 1, + byteBuffer, + PixelDataFormat.RGBA, + PixelDataType.FLOAT, + ); + await testHelper.capture(viewer.view, "vdtm_interpolated_$i"); + } + }, addSkybox: true); + }); + + test('static texture check', () async { + await testHelper.withViewer((viewer) async { + final dist = 5.0; + final numPositions = 3; + final cameraPositions = List.generate( + numPositions, + (i) => Vector3( + sin(i / (numPositions - 1) * pi / 2) * dist, + 0, + cos(i / (numPositions - 1) * pi / 2) * dist, + ), + ); + final cameraForwardVectors = cameraPositions.map((c) { + var viewMatrix = makeViewMatrix(c, Vector3.zero(), Vector3(0, 1, 0)); + viewMatrix.invert(); + var forward = (-viewMatrix.forward).normalized(); + return forward; + }).toList(); + + final camera = await viewer.view.getCamera(); + final vp = await viewer.view.getViewport(); + final (width, height) = (vp.width, vp.height); + + final cube = await _makeCube(testHelper, viewer); + final ubershader = await cube.getMaterialInstanceAt(); + + final (image, originalTexture, sampler) = + await createTextureFromImage(testHelper); + await ubershader.setParameterTexture( + "baseColorMap", originalTexture, sampler); + + await testHelper.capture(viewer.view, "vdtm_static_texture_initial"); + + final (vdtmMi, texture) = await _makeVDTMMaterial( + viewer, + cameraForwardVectors, + await image.getWidth(), + await image.getHeight(), + await image.getChannels()); + + await cube.setMaterialInstanceAt(vdtmMi); + + final pixelData = (await image.getData()).buffer.asUint8List(); + for (int i = 0; i < cameraPositions.length; i++) { + await camera.lookAt(cameraPositions[i]); + await texture.setImage3D( + 0, + 0, + 0, + i, + await image.getWidth(), + await image.getHeight(), + await image.getChannels(), + 1, + pixelData, + PixelDataFormat.RGBA, + PixelDataType.FLOAT, + ); + await Future.delayed(Duration(milliseconds: 100)); + await testHelper.capture(viewer.view, "vdtm_static_texture_$i"); + } + }, addSkybox: true); + }); + + test('VDTM + texture projection', () async { + await testHelper.withViewer((viewer) async { + final dist = 5.0; + final numPositions = 3; + final cameraPositions = List.generate( + numPositions, + (i) => Vector3( + sin(i / numPositions * pi) * dist, + dist, + cos(i / numPositions * pi) * dist, + ), + ); + final cameraForwardVectors = cameraPositions.map((c) { + var viewMatrix = makeViewMatrix(c, Vector3.zero(), Vector3(0, 1, 0)); + viewMatrix.invert(); + return -viewMatrix.forward; + }).toList(); + final camera = await viewer.view.getCamera(); + await camera.setLensProjection(near: 0.75, far: 100); + final vp = await viewer.view.getViewport(); + + final (numCameraPositions, width, height, channels) = ( + cameraPositions.length, + vp.width, + vp.height, + 4, + ); + + final (image, originalTexture, sampler) = + await createTextureFromImage(testHelper); + + final (vdtmMi, vdtmTexture) = await _makeVDTMMaterial( + viewer, cameraForwardVectors, width, height, 4); + + await FilamentApp.instance!.flush(); + + final cube = await _makeCube(testHelper, viewer); + + final ubershader = await cube.getMaterialInstanceAt(); + + await ubershader.setParameterTexture( + "baseColorMap", originalTexture, sampler); + + final textureProjection = + await TextureProjection.create(viewer.view, testHelper.swapChain); + + await FilamentApp.instance!.setClearOptions(0, 0, 0, 1, + clearStencil: 0, discard: false, clear: true); + + final projectedImage = + await FilamentApp.instance!.createImage(width, height, 4); + final projectedTexture = await FilamentApp.instance!.createTexture( + width, + height, + textureFormat: TextureFormat.RGBA32F, + ); + + print(cameraForwardVectors[0].dot(cameraForwardVectors[1])); + + // capture the cube with its original texture from each camera position + for (final entry in cameraPositions.asMap().entries) { + var (i, position) = (entry.key, entry.value); + await camera.lookAt(position); + + await textureProjection.project(cube); + final projectedPixelBuffer = + textureProjection.getProjectedPixelBuffer(); + await cube.setMaterialInstanceAt(ubershader); + + await savePixelBufferToBmp(textureProjection.getColorBuffer(), width, + height, "${testHelper.outDir.path}/initial_$i.bmp"); + // await savePixelBufferToBmp(projectedPixelBuffer, width, height, + // "${testHelper.outDir.path}/initial_projected_uv_mapped_$i.bmp"); + + await vdtmTexture.setImage3D( + 0, + 0, + 0, + i, + width, + height, + 4, + 1, + projectedPixelBuffer.buffer.asUint8List(), + PixelDataFormat.RGBA, + PixelDataType.FLOAT); + + final data = await projectedImage.getData(); + data.setRange( + 0, data.length, projectedPixelBuffer.buffer.asFloat32List()); + await projectedTexture.setLinearImage( + projectedImage, + PixelDataFormat.RGBA, + PixelDataType.FLOAT, + ); + await ubershader.setParameterTexture( + "baseColorMap", projectedTexture, sampler); + await cube.setMaterialInstanceAt(ubershader); + // await testHelper.capture(viewer.view, "initial_reprojected_$i"); + + await cube.setMaterialInstanceAt(vdtmMi); + + await testHelper.capture(viewer.view, "vdtm_$i"); + await cube.setMaterialInstanceAt(ubershader); + await ubershader.setParameterTexture( + "baseColorMap", originalTexture, sampler); + } + + await cube.setMaterialInstanceAt(vdtmMi); + + // now check in between camera positions + for (int i = 0; i < 6; i++) { + final cameraPosition = Vector3( + sin(i / 6 * pi / 4) * dist, + dist, + cos(i / 6 * pi / 4) * dist, + ); + await camera.lookAt(cameraPosition); + await testHelper.capture(viewer.view, "vdtm_interpolated_texture_$i"); + } + }, createRenderTarget: true); + }); +}