diff --git a/thermion_dart/lib/thermion_dart/utils/geometry.dart b/thermion_dart/lib/thermion_dart/utils/geometry.dart index 62660536..84985d84 100644 --- a/thermion_dart/lib/thermion_dart/utils/geometry.dart +++ b/thermion_dart/lib/thermion_dart/utils/geometry.dart @@ -1,14 +1,16 @@ import 'dart:math'; - +import 'dart:typed_data'; import 'package:thermion_dart/thermion_dart/viewer/shared_types/geometry.dart'; +import 'package:thermion_dart/thermion_dart/viewer/thermion_viewer_base.dart'; class GeometryHelper { static Geometry sphere({bool normals = true, bool uvs = true}) { int latitudeBands = 20; int longitudeBands = 20; - List vertices = []; - List _normals = []; + List verticesList = []; + List normalsList = []; + List uvsList = []; List indices = []; for (int latNumber = 0; latNumber <= latitudeBands; latNumber++) { @@ -25,12 +27,10 @@ class GeometryHelper { double y = cosTheta; double z = sinPhi * sinTheta; - vertices.addAll([x, y, z]); - _normals.addAll([ - x, - y, - z - ]); // For a sphere, normals are the same as vertex positions + verticesList.addAll([x, y, z]); + normalsList.addAll([x, y, z]); + + uvsList.addAll([longNumber / longitudeBands, latNumber / latitudeBands]); } } @@ -39,127 +39,171 @@ class GeometryHelper { int first = (latNumber * (longitudeBands + 1)) + longNumber; int second = first + longitudeBands + 1; - indices - .addAll([first, second, first + 1, second, second + 1, first + 1]); + indices.addAll([first, second, first + 1, second, second + 1, first + 1]); } } - return Geometry(vertices, indices, normals: normals ? _normals : null); + Float32List vertices = Float32List.fromList(verticesList); + Float32List? _normals = normals ? Float32List.fromList(normalsList) : null; + Float32List? _uvs = uvs ? Float32List.fromList(uvsList) : null; + + return Geometry(vertices, indices, normals: _normals, uvs: _uvs); } - static Geometry cube({bool normals = true, bool uvs = true}) { - final vertices = [ - // Front face - -1, -1, 1, - 1, -1, 1, - 1, 1, 1, - -1, 1, 1, +static Geometry cube({bool normals = true, bool uvs = true}) { + final vertices = Float32List.fromList([ + // Front face + -1, -1, 1, + 1, -1, 1, + 1, 1, 1, + -1, 1, 1, - // Back face - -1, -1, -1, - -1, 1, -1, - 1, 1, -1, - 1, -1, -1, + // Back face + -1, -1, -1, + -1, 1, -1, + 1, 1, -1, + 1, -1, -1, - // Top face - -1, 1, -1, - -1, 1, 1, - 1, 1, 1, - 1, 1, -1, + // Top face + -1, 1, -1, + -1, 1, 1, + 1, 1, 1, + 1, 1, -1, - // Bottom face - -1, -1, -1, - 1, -1, -1, - 1, -1, 1, - -1, -1, 1, + // Bottom face + -1, -1, -1, + 1, -1, -1, + 1, -1, 1, + -1, -1, 1, - // Right face - 1, -1, -1, - 1, 1, -1, - 1, 1, 1, - 1, -1, 1, + // Right face + 1, -1, -1, + 1, 1, -1, + 1, 1, 1, + 1, -1, 1, - // Left face - -1, -1, -1, - -1, -1, 1, - -1, 1, 1, - -1, 1, -1, - ]; + // Left face + -1, -1, -1, + -1, -1, 1, + -1, 1, 1, + -1, 1, -1, + ]); - final _normals = [ - // Front face - 0, 0, 1, - 0, 0, 1, - 0, 0, 1, - 0, 0, 1, + final _normals = normals ? Float32List.fromList([ + // Front face + 0, 0, 1, + 0, 0, 1, + 0, 0, 1, + 0, 0, 1, - // Back face - 0, 0, -1, - 0, 0, -1, - 0, 0, -1, - 0, 0, -1, + // Back face + 0, 0, -1, + 0, 0, -1, + 0, 0, -1, + 0, 0, -1, - // Top face - 0, 1, 0, - 0, 1, 0, - 0, 1, 0, - 0, 1, 0, + // Top face + 0, 1, 0, + 0, 1, 0, + 0, 1, 0, + 0, 1, 0, - // Bottom face - 0, -1, 0, - 0, -1, 0, - 0, -1, 0, - 0, -1, 0, + // Bottom face + 0, -1, 0, + 0, -1, 0, + 0, -1, 0, + 0, -1, 0, - // Right face - 1, 0, 0, - 1, 0, 0, - 1, 0, 0, - 1, 0, 0, + // Right face + 1, 0, 0, + 1, 0, 0, + 1, 0, 0, + 1, 0, 0, - // Left face - -1, 0, 0, - -1, 0, 0, - -1, 0, 0, - -1, 0, 0, - ]; + // Left face + -1, 0, 0, + -1, 0, 0, + -1, 0, 0, + -1, 0, 0, + ]) : null; - final indices = [ - // Front face - 0, 1, 2, 0, 2, 3, - // Back face - 4, 5, 6, 4, 6, 7, - // Top face - 8, 9, 10, 8, 10, 11, - // Bottom face - 12, 13, 14, 12, 14, 15, - // Right face - 16, 17, 18, 16, 18, 19, - // Left face - 20, 21, 22, 20, 22, 23 - ]; - return Geometry(vertices, indices, normals: normals ? _normals : null); - } + final _uvs = uvs ? Float32List.fromList([ + // Front face + 1/3, 1/3, + 2/3, 1/3, + 2/3, 2/3, + 1/3, 2/3, + + // Back face + 2/3, 2/3, + 2/3, 1, + 1, 1, + 1, 2/3, + + // Top face + 1/3, 0, + 1/3, 1/3, + 2/3, 1/3, + 2/3, 0, + + // Bottom face + 1/3, 2/3, + 2/3, 2/3, + 2/3, 1, + 1/3, 1, + + // Right face + 2/3, 1/3, + 2/3, 2/3, + 1, 2/3, + 1, 1/3, + + // Left face + 0, 1/3, + 1/3, 1/3, + 1/3, 2/3, + 0, 2/3, + ]) : null; + + final indices = [ + // Front face + 0, 1, 2, 0, 2, 3, + // Back face + 4, 5, 6, 4, 6, 7, + // Top face + 8, 9, 10, 8, 10, 11, + // Bottom face + 12, 13, 14, 12, 14, 15, + // Right face + 16, 17, 18, 16, 18, 19, + // Left face + 20, 21, 22, 20, 22, 23 + ]; + return Geometry(vertices, indices, normals: _normals, uvs: _uvs); +} static Geometry cylinder({double radius = 1.0, double length = 1.0, bool normals = true, bool uvs = true }) { int segments = 32; - List vertices = []; - List _normals = []; + List verticesList = []; + List normalsList = []; + List uvsList = []; List indices = []; - // Create vertices and normals + // Create vertices, normals, and UVs for (int i = 0; i <= segments; i++) { double theta = i * 2 * pi / segments; double x = radius * cos(theta); double z = radius * sin(theta); // Top circle - vertices.addAll([x, length / 2, z]); - _normals.addAll([x / radius, 0, z / radius]); + verticesList.addAll([x, length / 2, z]); + normalsList.addAll([x / radius, 0, z / radius]); + uvsList.addAll([i / segments, 1]); // Bottom circle - vertices.addAll([x, -length / 2, z]); - _normals.addAll([x / radius, 0, z / radius]); + verticesList.addAll([x, -length / 2, z]); + normalsList.addAll([x / radius, 0, z / radius]); + uvsList.addAll([i / segments, 0]); } // Create indices @@ -178,45 +222,62 @@ class GeometryHelper { indices.addAll([bottomFirst, bottomSecond, topSecond]); } - // Add center vertices and normals for top and bottom faces - vertices.addAll([0, length / 2, 0]); // Top center - _normals.addAll([0, 1, 0]); - vertices.addAll([0, -length / 2, 0]); // Bottom center - _normals.addAll([0, -1, 0]); + // Add center vertices, normals, and UVs for top and bottom faces + verticesList.addAll([0, length / 2, 0]); // Top center + normalsList.addAll([0, 1, 0]); + uvsList.addAll([0.5, 0.5]); // Center of top face - // Add top and bottom face normals + verticesList.addAll([0, -length / 2, 0]); // Bottom center + normalsList.addAll([0, -1, 0]); + uvsList.addAll([0.5, 0.5]); // Center of bottom face + + // Add top and bottom face normals and UVs for (int i = 0; i <= segments; i++) { - _normals.addAll([0, 1, 0]); // Top face normal - _normals.addAll([0, -1, 0]); // Bottom face normal + normalsList.addAll([0, 1, 0]); // Top face normal + normalsList.addAll([0, -1, 0]); // Bottom face normal + + double u = 0.5 + 0.5 * cos(i * 2 * pi / segments); + double v = 0.5 + 0.5 * sin(i * 2 * pi / segments); + uvsList.addAll([u, v]); // Top face UV + uvsList.addAll([u, v]); // Bottom face UV } - return Geometry(vertices, indices, normals: normals ? _normals : null); + Float32List vertices = Float32List.fromList(verticesList); + Float32List? _normals = normals ? Float32List.fromList(normalsList) : null; + Float32List? _uvs = uvs ? Float32List.fromList(uvsList) : null; + + return Geometry(vertices, indices, normals: _normals, uvs: _uvs); } static Geometry conic({double radius = 1.0, double length = 1.0, bool normals = true, bool uvs = true}) { int segments = 32; - List vertices = []; - List _normals = []; + List verticesList = []; + List normalsList = []; + List uvsList = []; List indices = []; - // Create vertices and normals + // Create vertices, normals, and UVs for (int i = 0; i <= segments; i++) { double theta = i * 2 * pi / segments; double x = radius * cos(theta); double z = radius * sin(theta); // Base circle - vertices.addAll([x, 0, z]); + verticesList.addAll([x, 0, z]); // Calculate normal for the side double nx = x / sqrt(x * x + length * length); double nz = z / sqrt(z * z + length * length); double ny = radius / sqrt(radius * radius + length * length); - _normals.addAll([nx, ny, nz]); + normalsList.addAll([nx, ny, nz]); + + // UV coordinates + uvsList.addAll([i / segments, 0]); } // Apex - vertices.addAll([0, length, 0]); - _normals.addAll([0, 1, 0]); // Normal at apex points straight up + verticesList.addAll([0, length, 0]); + normalsList.addAll([0, 1, 0]); // Normal at apex points straight up + uvsList.addAll([0.5, 1]); // UV for apex // Create indices for (int i = 0; i < segments; i++) { @@ -226,54 +287,48 @@ class GeometryHelper { indices.addAll([i, segments, i + 1]); } - // Add base face normals + // Add base face normals and UVs for (int i = 0; i <= segments; i++) { - _normals.addAll([0, -1, 0]); // Base face normal + normalsList.addAll([0, -1, 0]); // Base face normal + double u = 0.5 + 0.5 * cos(i * 2 * pi / segments); + double v = 0.5 + 0.5 * sin(i * 2 * pi / segments); + uvsList.addAll([u, v]); // Base face UV } - return Geometry(vertices, indices, normals: normals ? _normals : null); + Float32List vertices = Float32List.fromList(verticesList); + Float32List? _normals = normals ? Float32List.fromList(normalsList) : null; + Float32List? _uvs = uvs ? Float32List.fromList(uvsList) : null; + + return Geometry(vertices, indices, normals: _normals, uvs: _uvs); } static Geometry plane({double width = 1.0, double height = 1.0, bool normals = true, bool uvs = true}) { - List vertices = [ - -width / 2, - 0, - -height / 2, - width / 2, - 0, - -height / 2, - width / 2, - 0, - height / 2, - -width / 2, - 0, - height / 2, - ]; + Float32List vertices = Float32List.fromList([ + -width / 2, 0, -height / 2, + width / 2, 0, -height / 2, + width / 2, 0, height / 2, + -width / 2, 0, height / 2, + ]); - List _normals = [ - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - 0, - 1, - 0, - ]; + Float32List? _normals = normals ? Float32List.fromList([ + 0, 1, 0, + 0, 1, 0, + 0, 1, 0, + 0, 1, 0, + ]) : null; + + Float32List? _uvs = uvs ? Float32List.fromList([ + 0, 0, + 1, 0, + 1, 1, + 0, 1, + ]) : null; List indices = [ - 0, - 2, - 1, - 0, - 3, - 2, + 0, 2, 1, + 0, 3, 2, ]; - return Geometry(vertices, indices, normals: normals ? _normals : null); + return Geometry(vertices, indices, normals: _normals, uvs: _uvs); } -} +} \ No newline at end of file diff --git a/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_dart.g.dart b/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_dart.g.dart index bc6ac154..6bdafd91 100644 --- a/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_dart.g.dart +++ b/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_dart.g.dart @@ -1039,37 +1039,22 @@ external void remove_animation_component( ffi.Pointer, ffi.Pointer, ffi.Int, + ffi.Pointer, + ffi.Int, + ffi.Pointer, + ffi.Int, ffi.Pointer, ffi.Int, ffi.Int, ffi.Pointer)>(isLeaf: true) external int create_geometry( - ffi.Pointer sceneManager, - ffi.Pointer vertices, - int numVertices, - ffi.Pointer indices, - int numIndices, - int primitiveType, - ffi.Pointer materialPath, -); - -@ffi.Native< - EntityId Function( - ffi.Pointer, - ffi.Pointer, - ffi.Int, - ffi.Pointer, - ffi.Int, - ffi.Pointer, - ffi.Int, - ffi.Int, - ffi.Pointer)>(isLeaf: true) -external int create_geometry_with_normals( ffi.Pointer sceneManager, ffi.Pointer vertices, int numVertices, ffi.Pointer normals, int numNormals, + ffi.Pointer uvs, + int numUvs, ffi.Pointer indices, int numIndices, int primitiveType, @@ -1214,6 +1199,44 @@ external void set_material_property_float4( float4 value, ); +@ffi.Native< + ffi.Void Function(ffi.Pointer, EntityId, ffi.Pointer, + ffi.Uint32, ffi.Uint32)>(isLeaf: true) +external void unproject_texture( + ffi.Pointer sceneManager, + int entity, + ffi.Pointer out, + int outWidth, + int outHeight, +); + +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer, ffi.Size)>(isLeaf: true) +external ffi.Pointer create_texture( + ffi.Pointer sceneManager, + ffi.Pointer data, + int length, +); + +@ffi.Native, ffi.Pointer)>( + isLeaf: true) +external void destroy_texture( + ffi.Pointer sceneManager, + ffi.Pointer texture, +); + +@ffi.Native< + ffi.Void Function(ffi.Pointer, EntityId, ffi.Pointer, + ffi.Pointer, ffi.Int)>(isLeaf: true) +external void apply_texture_to_material( + ffi.Pointer sceneManager, + int entity, + ffi.Pointer texture, + ffi.Pointer parameterName, + int materialIndex, +); + @ffi.Native< ffi.Void Function( ffi.Pointer, @@ -1683,6 +1706,10 @@ external void reset_to_rest_pose_ffi( ffi.Pointer, ffi.Pointer, ffi.Int, + ffi.Pointer, + ffi.Int, + ffi.Pointer, + ffi.Int, ffi.Pointer, ffi.Int, ffi.Int, @@ -1694,6 +1721,10 @@ external void create_geometry_ffi( ffi.Pointer sceneManager, ffi.Pointer vertices, int numVertices, + ffi.Pointer normals, + int numNormals, + ffi.Pointer uvs, + int numUvs, ffi.Pointer indices, int numIndices, int primitiveType, @@ -1703,31 +1734,20 @@ external void create_geometry_ffi( ); @ffi.Native< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Int, - ffi.Pointer, - ffi.Int, - ffi.Pointer, - ffi.Int, - ffi.Int, - ffi.Pointer, - ffi.Bool, - ffi.Pointer>)>( - isLeaf: true) -external void create_geometry_with_normals_ffi( + ffi.Void Function( + ffi.Pointer, + EntityId, + ffi.Pointer, + ffi.Uint32, + ffi.Uint32, + ffi.Pointer>)>(isLeaf: true) +external void unproject_texture_ffi( ffi.Pointer sceneManager, - ffi.Pointer vertices, - int numVertices, - ffi.Pointer normals, - int numNormals, - ffi.Pointer indices, - int numIndices, - int primitiveType, - ffi.Pointer materialPath, - bool keepData, - ffi.Pointer> callback, + int entity, + ffi.Pointer out, + int outWidth, + int outHeight, + ffi.Pointer> callback, ); final class ResourceBuffer extends ffi.Struct { diff --git a/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_viewer_ffi.dart b/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_viewer_ffi.dart index 42c6e9de..82e7b3e3 100644 --- a/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/thermion_dart/viewer/ffi/thermion_viewer_ffi.dart @@ -58,7 +58,8 @@ class ThermionViewerFFI extends ThermionViewer { /// Stream get sceneUpdated => _sceneUpdateEventController.stream; - final _sceneUpdateEventController = StreamController.broadcast(); + final _sceneUpdateEventController = + StreamController.broadcast(); final Pointer resourceLoader; @@ -355,9 +356,6 @@ class ThermionViewerFFI extends ThermionViewer { remove_ibl_ffi(_viewer!); } - /// - /// - /// @override Future addLight( LightType type, @@ -376,30 +374,21 @@ class ThermionViewerFFI extends ThermionViewer { double sunHaloSize = 10.0, double sunHaloFallof = 80.0, bool castShadows = true}) async { - var entity = await withIntCallback((callback) => add_light_ffi( - _viewer!, - type.index, - colour, - intensity, - posX, - posY, - posZ, - dirX, - dirY, - dirZ, - falloffRadius, - spotLightConeInner, - spotLightConeOuter, - sunAngularRadius = 0.545, - sunHaloSize = 10.0, - sunHaloFallof = 80.0, - castShadows, - callback)); - if (entity == _FILAMENT_ASSET_ERROR) { - throw Exception("Failed to add light to scene"); - } + DirectLight directLight = DirectLight( + type: type, + color: colour, + intensity: intensity, + position: Vector3(posX, posY, posZ), + direction: Vector3(dirX, dirY, dirZ), + falloffRadius: falloffRadius, + spotLightConeInner: spotLightConeInner, + spotLightConeOuter: spotLightConeOuter, + sunAngularRadius: sunAngularRadius, + sunHaloSize: sunHaloSize, + sunHaloFallof: sunHaloFallof, + castShadows: castShadows); - return entity; + return addDirectLight(directLight); } /// @@ -1827,49 +1816,25 @@ class ThermionViewerFFI extends ThermionViewer { final materialPathPtr = geometry.materialPath?.toNativeUtf8(allocator: allocator) ?? nullptr; - final vertexPtr = allocator(geometry.vertices.length); - final indicesPtr = allocator(geometry.indices.length); - for (int i = 0; i < geometry.vertices.length; i++) { - vertexPtr[i] = geometry.vertices[i]; - } - for (int i = 0; i < geometry.indices.length; i++) { - (indicesPtr + i).value = geometry.indices[i]; - } - - var normalsPtr = nullptr.cast(); - if (geometry.normals != null) { - normalsPtr = allocator(geometry.normals!.length); - for (int i = 0; i < geometry.normals!.length; i++) { - normalsPtr[i] = geometry.normals![i]; - } - } - - var entity = await withIntCallback((callback) => - create_geometry_with_normals_ffi( - _sceneManager!, - vertexPtr, - geometry.vertices.length, - normalsPtr, - geometry.normals?.length ?? 0, - indicesPtr, - geometry.indices.length, - geometry.primitiveType.index, - materialPathPtr.cast(), - keepData, - callback)); + var entity = await withIntCallback((callback) => create_geometry_ffi( + _sceneManager!, + geometry.vertices.address, + geometry.vertices.length, + geometry.normals.address, + geometry.normals.length, + geometry.uvs.address, + geometry.uvs.length, + geometry.indices.address, + geometry.indices.length, + geometry.primitiveType.index, + materialPathPtr.cast(), + keepData, + callback)); if (entity == _FILAMENT_ASSET_ERROR) { throw Exception("Failed to create geometry"); } - allocator.free(materialPathPtr); - allocator.free(vertexPtr); - allocator.free(indicesPtr); - - if (geometry.normals != null) { - allocator.free(normalsPtr); - } - _sceneUpdateEventController .add(SceneUpdateEvent.addGeometry(entity, geometry)); @@ -2001,4 +1966,38 @@ class ThermionViewerFFI extends ThermionViewer { _sceneManager!, entity, materialIndex, ptr.cast(), struct); allocator.free(ptr); } + + Future unproject( + ThermionEntity entity, int outWidth, int outHeight) async { + final outPtr = Uint8List(outWidth * outHeight * 4); + await withVoidCallback((callback) { + unproject_texture_ffi( + _viewer!, entity, outPtr.address, outWidth, outHeight, callback); + }); + + return outPtr.buffer.asUint8List(); + } + + Future createTexture(Uint8List data) async { + var ptr = create_texture(_sceneManager!, data.address, data.length); + return ThermionFFITexture(ptr); + } + + Future applyTexture(ThermionFFITexture texture, ThermionEntity entity, + {int materialIndex = 0, String parameterName = "baseColorMap"}) async { + using(parameterName.toNativeUtf8(), (namePtr) async { + apply_texture_to_material(_sceneManager!, entity, texture._pointer, + namePtr.cast(), materialIndex); + }); + } + + Future destroyTexture(ThermionFFITexture texture) async { + destroy_texture(_sceneManager!, texture._pointer); + } +} + +class ThermionFFITexture extends ThermionTexture { + final Pointer _pointer; + + ThermionFFITexture(this._pointer); } diff --git a/thermion_dart/lib/thermion_dart/viewer/shared_types/entities.dart b/thermion_dart/lib/thermion_dart/viewer/shared_types/entities.dart index 637bb800..b59631ac 100644 --- a/thermion_dart/lib/thermion_dart/viewer/shared_types/entities.dart +++ b/thermion_dart/lib/thermion_dart/viewer/shared_types/entities.dart @@ -7,3 +7,7 @@ export 'light_options.dart'; // a handle that can be safely passed back to the rendering layer to manipulate an Entity typedef ThermionEntity = int; + +abstract class ThermionTexture { + +} diff --git a/thermion_dart/lib/thermion_dart/viewer/shared_types/geometry.dart b/thermion_dart/lib/thermion_dart/viewer/shared_types/geometry.dart index b6fa4f96..076b36c4 100644 --- a/thermion_dart/lib/thermion_dart/viewer/shared_types/geometry.dart +++ b/thermion_dart/lib/thermion_dart/viewer/shared_types/geometry.dart @@ -1,19 +1,34 @@ +import 'dart:typed_data'; + import 'package:thermion_dart/thermion_dart/viewer/thermion_viewer_base.dart'; class Geometry { - final List vertices; - final List indices; - final List? normals; - final List<(double, double)>? uvs; + final Float32List vertices; + final Uint16List indices; + final Float32List normals; + final Float32List uvs; final PrimitiveType primitiveType; final String? materialPath; - Geometry(this.vertices, this.indices, { this.normals=null, this.uvs=null, - this.primitiveType = PrimitiveType.TRIANGLES, this.materialPath = null}); + Geometry( + this.vertices, + List indices, { + Float32List? normals, + Float32List? uvs, + this.primitiveType = PrimitiveType.TRIANGLES, + this.materialPath, + }) : indices = Uint16List.fromList(indices), + normals = normals ?? Float32List(0), + uvs = uvs ?? Float32List(0) { + assert(this.uvs.length == 0 || this.uvs.length == (vertices.length ~/ 3) * 2); + } void scale(double factor) { for (int i = 0; i < vertices.length; i++) { vertices[i] = vertices[i] * factor; } } + + bool get hasNormals => normals.isNotEmpty; + bool get hasUVs => uvs.isNotEmpty; } diff --git a/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart b/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart index 621e64c5..025af6e9 100644 --- a/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart +++ b/thermion_dart/lib/thermion_dart/viewer/thermion_viewer_base.dart @@ -11,10 +11,9 @@ import 'dart:async'; import 'package:animation_tools_dart/animation_tools_dart.dart'; abstract class ThermionViewer { - - /// + /// /// A Future that resolves when the underlying rendering context has been successfully created. - /// + /// Future get initialized; /// @@ -136,7 +135,8 @@ abstract class ThermionViewer { /// Note that [sunAngularRadius] is in degrees, /// whereas [spotLightConeInner] and [spotLightConeOuter] are in radians /// - @Deprecated("This will be removed in future versions. Use addDirectLight instead.") + @Deprecated( + "This will be removed in future versions. Use addDirectLight instead.") Future addLight( LightType type, double colour, @@ -161,8 +161,7 @@ abstract class ThermionViewer { /// Note that [sunAngularRadius] is in degrees, /// whereas [spotLightConeInner] and [spotLightConeOuter] are in radians /// - Future addDirectLight( - DirectLight light); + Future addDirectLight(DirectLight light); /// /// Remove a light from the scene. @@ -787,7 +786,7 @@ abstract class ThermionViewer { /// Creates a (renderable) entity with the specified geometry and adds to the scene. /// If [keepData] is true, the source data will not be released. /// - Future createGeometry(Geometry geometry, { bool keepData= false}); + Future createGeometry(Geometry geometry, {bool keepData = false}); /// /// Gets the parent entity of [entity]. Returns null if the entity has no parent. @@ -853,4 +852,21 @@ abstract class ThermionViewer { /// Removes the outline around [entity]. Noop if there was no highlight. /// Future removeStencilHighlight(ThermionEntity entity); + + /// + /// Decodes the specified image data and creates a texture. + /// + Future createTexture(Uint8List data); + + /// + /// + /// + Future applyTexture(covariant ThermionTexture texture, ThermionEntity entity, + {int materialIndex = 0, String parameterName = "baseColorMap"}); + + /// + /// + /// + Future destroyTexture(covariant ThermionTexture texture); + } diff --git a/thermion_dart/native/include/CustomGeometry.hpp b/thermion_dart/native/include/CustomGeometry.hpp index 93df18dc..5ecc568d 100644 --- a/thermion_dart/native/include/CustomGeometry.hpp +++ b/thermion_dart/native/include/CustomGeometry.hpp @@ -24,6 +24,8 @@ public: uint32_t numVertices, float* normals, uint32_t numNormals, + float *uvs, + uint32_t numUvs, uint16_t* indices, uint32_t numIndices, RenderableManager::PrimitiveType primitiveType, @@ -36,6 +38,7 @@ public: float* vertices = nullptr; float* normals = nullptr; + float *uvs = nullptr; uint32_t numVertices = 0; uint16_t* indices = 0; uint32_t numIndices = 0; diff --git a/thermion_dart/native/include/FilamentViewer.hpp b/thermion_dart/native/include/FilamentViewer.hpp index 92c8133c..6f26b3e3 100644 --- a/thermion_dart/native/include/FilamentViewer.hpp +++ b/thermion_dart/native/include/FilamentViewer.hpp @@ -138,7 +138,7 @@ namespace thermion_filament void setRecording(bool recording); void setRecordingOutputDirectory(const char *path); - void capture(uint8_t *out, void (*onComplete)()); + void capture(uint8_t *out, bool useFence, void (*onComplete)()); void setAntiAliasing(bool msaaEnabled, bool fxaaEnabled, bool taaEnabled); void setDepthOfField(); @@ -151,6 +151,8 @@ namespace thermion_filament return (SceneManager *const)_sceneManager; } + void unprojectTexture(EntityId entity, uint8_t* out, uint32_t outWidth, uint32_t outHeight); + private: const ResourceLoaderWrapperImpl *const _resourceLoaderWrapper; void* _context = nullptr; diff --git a/thermion_dart/native/include/SceneManager.hpp b/thermion_dart/native/include/SceneManager.hpp index fb23d4b7..6e054c91 100644 --- a/thermion_dart/native/include/SceneManager.hpp +++ b/thermion_dart/native/include/SceneManager.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -168,7 +169,11 @@ namespace thermion_filament void playAnimation(EntityId e, int index, bool loop, bool reverse, bool replaceActive, float crossfade = 0.3f, float startOffset = 0.0f); void stopAnimation(EntityId e, int index); void setMorphTargetWeights(const char *const entityName, float *weights, int count); - void loadTexture(EntityId entity, const char *resourcePath, int renderableIndex); + + Texture* createTexture(const uint8_t* data, size_t length, const char* name); + bool applyTexture(EntityId entityId, Texture *texture, const char* slotName, int materialIndex); + void destroyTexture(Texture* texture); + void setAnimationFrame(EntityId entity, int animationIndex, int animationFrame); bool hide(EntityId entity, const char *meshName); bool reveal(EntityId entity, const char *meshName); @@ -226,29 +231,16 @@ namespace thermion_filament void setLayerEnabled(int layer, bool enabled); /// - /// Creates an entity with the specified geometry/material and adds to the scene. + /// Creates an entity with the specified geometry/material/normals and adds to the scene. /// If [keepData] is true, stores /// EntityId createGeometry( - float *vertices, - uint32_t numVertices, - uint16_t *indices, - uint32_t numIndices, - filament::RenderableManager::PrimitiveType primitiveType = RenderableManager::PrimitiveType::TRIANGLES, - const char *materialPath = nullptr, - bool keepData = false - ); - - - /// - /// Creates an entity with the specified geometry/material/normals and adds to the scene. - /// If [keepData] is true, stores - /// - EntityId createGeometryWithNormals( float *vertices, uint32_t numVertices, float *normals, uint32_t numNormals, + float *uvs, + uint32_t numUvs, uint16_t *indices, uint32_t numIndices, filament::RenderableManager::PrimitiveType primitiveType = RenderableManager::PrimitiveType::TRIANGLES, @@ -313,9 +305,9 @@ namespace thermion_filament _instances; tsl::robin_map _assets; tsl::robin_map> _geometry; - tsl::robin_map> _highlighted; - + tsl::robin_map> _highlighted; tsl::robin_map> _transformUpdates; + std::set _textures; AnimationComponentManager *_animationComponentManager = nullptr; CollisionComponentManager *_collisionComponentManager = nullptr; diff --git a/thermion_dart/native/include/ThermionDartApi.h b/thermion_dart/native/include/ThermionDartApi.h index baba1538..3ee7a047 100644 --- a/thermion_dart/native/include/ThermionDartApi.h +++ b/thermion_dart/native/include/ThermionDartApi.h @@ -262,8 +262,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE bool add_animation_component(void *const sceneManager, EntityId entityId); EMSCRIPTEN_KEEPALIVE void remove_animation_component(void *const sceneManager, EntityId entityId); - EMSCRIPTEN_KEEPALIVE EntityId create_geometry(void *const sceneManager, float *vertices, int numVertices, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath); - EMSCRIPTEN_KEEPALIVE EntityId create_geometry_with_normals(void *const sceneManager, float *vertices, int numVertices, float *normals, int numNormals, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath); + EMSCRIPTEN_KEEPALIVE EntityId create_geometry(void *const sceneManager, float *vertices, int numVertices, float *normals, int numNormals, float *uvs, int numUvs, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath); EMSCRIPTEN_KEEPALIVE EntityId get_parent(void *const sceneManager, EntityId child); EMSCRIPTEN_KEEPALIVE EntityId get_ancestor(void *const sceneManager, EntityId child); EMSCRIPTEN_KEEPALIVE void set_parent(void *const sceneManager, EntityId child, EntityId parent, bool preserveScaling); @@ -279,6 +278,10 @@ extern "C" EMSCRIPTEN_KEEPALIVE void remove_stencil_highlight(void *const sceneManager, EntityId entity); EMSCRIPTEN_KEEPALIVE void set_material_property_float(void *const sceneManager, EntityId entity, int materialIndex, const char* property, float value); EMSCRIPTEN_KEEPALIVE void set_material_property_float4(void *const sceneManager, EntityId entity, int materialIndex, const char* property, float4 value); + EMSCRIPTEN_KEEPALIVE void unproject_texture(void *const sceneManager, EntityId entity, uint8_t* out, uint32_t outWidth, uint32_t outHeight); + EMSCRIPTEN_KEEPALIVE void* const create_texture(void *const sceneManager, uint8_t* data, size_t length); + EMSCRIPTEN_KEEPALIVE void destroy_texture(void *const sceneManager, void* const texture); + EMSCRIPTEN_KEEPALIVE void apply_texture_to_material(void *const sceneManager, EntityId entity, void* const texture, const char* parameterName, int materialIndex); #ifdef __cplusplus diff --git a/thermion_dart/native/include/ThermionDartFFIApi.h b/thermion_dart/native/include/ThermionDartFFIApi.h index b4ae938e..4c2d3518 100644 --- a/thermion_dart/native/include/ThermionDartFFIApi.h +++ b/thermion_dart/native/include/ThermionDartFFIApi.h @@ -101,8 +101,9 @@ extern "C" void (*callback)(bool)); EMSCRIPTEN_KEEPALIVE void set_post_processing_ffi(void *const viewer, bool enabled); EMSCRIPTEN_KEEPALIVE void reset_to_rest_pose_ffi(void *const sceneManager, EntityId entityId, void(*callback)()); - EMSCRIPTEN_KEEPALIVE void create_geometry_ffi(void *const sceneManager, float *vertices, int numVertices, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath, bool keepData, void (*callback)(EntityId)); - EMSCRIPTEN_KEEPALIVE void create_geometry_with_normals_ffi(void *const sceneManager, float *vertices, int numVertices, float *normals, int numNormals, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath, bool keepData, void (*callback)(EntityId)); + EMSCRIPTEN_KEEPALIVE void create_geometry_ffi(void *const sceneManager, float *vertices, int numVertices, float *normals, int numNormals, float *uvs, int numUvs, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath, bool keepData, void (*callback)(EntityId)); + EMSCRIPTEN_KEEPALIVE void unproject_texture_ffi(void *const sceneManager, EntityId entity, uint8_t* out, uint32_t outWidth, uint32_t outHeight, void(*callback)()); + #ifdef __cplusplus } diff --git a/thermion_dart/native/include/UnprojectTexture.hpp b/thermion_dart/native/include/UnprojectTexture.hpp new file mode 100644 index 00000000..0f04dba0 --- /dev/null +++ b/thermion_dart/native/include/UnprojectTexture.hpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "CustomGeometry.hpp" + +namespace thermion_filament { + +class UnprojectTexture { +public: + UnprojectTexture(const CustomGeometry * geometry, Camera& camera, Engine* engine) + : _geometry(geometry), _camera(camera), _engine(engine) {} + + void unproject(utils::Entity entity, const uint8_t* inputTexture, uint8_t* outputTexture, uint32_t inputWidth, uint32_t inputHeight, + uint32_t outputWidth, uint32_t outputHeight); + +private: + const CustomGeometry * _geometry; + const Camera& _camera; + Engine* _engine; + + math::float3 doUnproject(const math::float2& screenPos, float depth, const math::mat4& invViewProj); + bool isInsideTriangle(const math::float2& p, const math::float2& a, const math::float2& b, const math::float2& c); + math::float3 barycentric(const math::float2& p, const math::float2& a, const math::float2& b, const math::float2& c); +}; +} \ No newline at end of file diff --git a/thermion_dart/native/src/CustomGeometry.cpp b/thermion_dart/native/src/CustomGeometry.cpp index 48d7aa91..0736bdaf 100644 --- a/thermion_dart/native/src/CustomGeometry.cpp +++ b/thermion_dart/native/src/CustomGeometry.cpp @@ -1,7 +1,5 @@ #include - #include "math.h" - #include #include #include @@ -22,6 +20,8 @@ CustomGeometry::CustomGeometry( uint32_t numVertices, float* normals, uint32_t numNormals, + float* uvs, + uint32_t numUvs, uint16_t* indices, uint32_t numIndices, RenderableManager::PrimitiveType primitiveType, @@ -35,6 +35,16 @@ CustomGeometry::CustomGeometry( Log("numNormals %d", numNormals); this->normals = new float[numNormals]; std::memcpy(this->normals, normals, numNormals * sizeof(float)); + } else { + Log("no normals"); + } + + if(numUvs > 0) { + Log("numUvs %d", numUvs); + this->uvs = new float[numUvs]; + std::memcpy(this->uvs, uvs, numUvs * sizeof(float)); + } else { + this->uvs = nullptr; } this->indices = new uint16_t[numIndices]; @@ -76,7 +86,6 @@ VertexBuffer* CustomGeometry::vertexBuffer() const { triangles.push_back(triangle); } - // Create a SurfaceOrientation builder geometry::SurfaceOrientation::Builder builder; builder.vertexCount(numVertices) @@ -92,8 +101,13 @@ VertexBuffer* CustomGeometry::vertexBuffer() const { auto quats = new std::vector(numVertices); orientation->getQuats(quats->data(), numVertices); - // Create dummy UV data - auto dummyUVs = new std::vector(numVertices, filament::math::float2{0.0f, 0.0f}); + // Use provided UVs or create dummy UV data + std::vector* uvData; + if (this->uvs != nullptr) { + uvData = new std::vector((filament::math::float2*)this->uvs, (filament::math::float2*)(this->uvs + numVertices * 2)); + } else { + uvData = new std::vector(numVertices, filament::math::float2{0.0f, 0.0f}); + } // Create dummy vertex color data (white color for all vertices) auto dummyColors = new std::vector(numVertices, filament::math::float4{1.0f, 1.0f, 1.0f, 1.0f}); @@ -118,22 +132,21 @@ VertexBuffer* CustomGeometry::vertexBuffer() const { vertexBuffer->setBufferAt(*_engine, 0, VertexBuffer::BufferDescriptor( this->vertices, vertexBuffer->getVertexCount() * sizeof(math::float3), vertexCallback)); - - // Set UV0 buffer + // Set UV0 buffer vertexBuffer->setBufferAt(*_engine, 1, VertexBuffer::BufferDescriptor( - dummyUVs->data(), dummyUVs->size() * sizeof(math::float2), + uvData->data(), uvData->size() * sizeof(math::float2), [](void* buf, size_t, void* data) { delete static_cast*>(data); - }, dummyUVs)); + }, uvData)); - // Set UV1 buffer + // Set UV1 buffer (reusing UV0 data) vertexBuffer->setBufferAt(*_engine, 2, VertexBuffer::BufferDescriptor( - dummyUVs->data(), dummyUVs->size() * sizeof(math::float2), + uvData->data(), uvData->size() * sizeof(math::float2), [](void* buf, size_t, void* data) { // Do nothing here, as we're reusing the same data as UV0 }, nullptr)); - // Set vertex color buffer + // Set vertex color buffer vertexBuffer->setBufferAt(*_engine, 3, VertexBuffer::BufferDescriptor( dummyColors->data(), dummyColors->size() * sizeof(math::float4), [](void* buf, size_t, void* data) { @@ -154,6 +167,8 @@ VertexBuffer* CustomGeometry::vertexBuffer() const { CustomGeometry::~CustomGeometry() { delete[] vertices; delete[] indices; + if (normals) delete[] normals; + if (uvs) delete[] uvs; } void CustomGeometry::computeBoundingBox() { diff --git a/thermion_dart/native/src/FilamentViewer.cpp b/thermion_dart/native/src/FilamentViewer.cpp index 59cc7988..95c7a004 100644 --- a/thermion_dart/native/src/FilamentViewer.cpp +++ b/thermion_dart/native/src/FilamentViewer.cpp @@ -97,7 +97,7 @@ #include "StreamBufferAdapter.hpp" #include "material/image.h" #include "TimeIt.hpp" -#include "ThreadPool.hpp" +#include "UnprojectTexture.hpp" namespace filament { @@ -306,7 +306,7 @@ namespace thermion_filament bool shadows) { auto light = EntityManager::get().create(); - + auto result = LightManager::Builder(t) .color(Color::cct(colour)) .intensity(intensity) @@ -1197,7 +1197,7 @@ namespace thermion_filament } }; - void FilamentViewer::capture(uint8_t *out, void (*onComplete)()) + void FilamentViewer::capture(uint8_t *out, bool useFence, void (*onComplete)()) { Viewport const &vp = _view->getViewport(); @@ -1209,16 +1209,19 @@ namespace thermion_filament uint8_t *out = (uint8_t *)(frameCallbackData->at(0)); void *callbackPtr = frameCallbackData->at(1); - void (*callback)(void) = (void (*)(void))callbackPtr; memcpy(out, buf, size); delete frameCallbackData; - callback(); + if(callbackPtr) { + void (*callback)(void) = (void (*)(void))callbackPtr; + callback(); + } }; // Create a fence - #ifndef __EMSCRIPTEN__ - Fence* fence = _engine->createFence(); - #endif + Fence* fence = nullptr; + if(useFence) { + fence = _engine->createFence(); + } auto userData = new std::vector{out, (void *)onComplete}; @@ -1245,9 +1248,10 @@ namespace thermion_filament #ifdef __EMSCRIPTEN__ _engine->execute(); emscripten_webgl_commit_frame(); -#else - Fence::waitAndDestroy(fence); #endif + if(fence) { + Fence::waitAndDestroy(fence); + } } void FilamentViewer::savePng(void *buf, size_t size, int frameNumber) @@ -1464,6 +1468,21 @@ namespace thermion_filament }); } + void FilamentViewer::unprojectTexture(EntityId entityId, uint8_t* out, uint32_t outWidth, uint32_t outHeight) { + const auto * geometry = _sceneManager->getGeometry(entityId); + if(!geometry->uvs) { + Log("No UVS"); + return; + } + + const auto& viewport = _view->getViewport(); + auto viewportCapture = new uint8_t[viewport.width * viewport.height * 4]; + capture(viewportCapture, true, nullptr); + UnprojectTexture unproject(geometry, _view->getCamera(), _engine); + + unproject.unproject(utils::Entity::import(entityId), viewportCapture, out, viewport.width, viewport.height, outWidth, outHeight); + } + } // namespace thermion_filament diff --git a/thermion_dart/native/src/SceneManager.cpp b/thermion_dart/native/src/SceneManager.cpp index 80f4490b..71370760 100644 --- a/thermion_dart/native/src/SceneManager.cpp +++ b/thermion_dart/native/src/SceneManager.cpp @@ -31,6 +31,7 @@ #include "Log.hpp" #include "SceneManager.hpp" #include "CustomGeometry.hpp" +#include "UnprojectTexture.hpp" extern "C" { @@ -217,8 +218,9 @@ namespace thermion_filament FilamentInstance *inst = asset->getInstance(); inst->getAnimator()->updateBoneMatrices(); inst->recomputeBoundingBoxes(); - - if(!keepData) { + + if (!keepData) + { asset->releaseSourceData(); } @@ -292,7 +294,8 @@ namespace thermion_filament _instances.emplace(instanceEntityId, inst); } - if(!keepData) { + if (!keepData) + { asset->releaseSourceData(); } @@ -362,7 +365,8 @@ namespace thermion_filament const auto asset = pos->second; auto instance = _assetLoader->createInstance(asset); - if(!instance) { + if (!instance) + { Log("Failed to create instance"); return 0; } @@ -497,6 +501,12 @@ namespace thermion_filament asset.second->getLightEntityCount()); _assetLoader->destroyAsset(asset.second); } + for(auto* texture : _textures) { + _engine->destroy(texture); + } + + // TODO - free geometry? + _textures.clear(); _assets.clear(); } @@ -608,6 +618,7 @@ namespace thermion_filament auto entity = Entity::import(entityId); + if (_animationComponentManager->hasComponent(entity)) { _animationComponentManager->removeComponent(entity); @@ -620,6 +631,10 @@ namespace thermion_filament _scene->remove(entity); + if(isGeometryEntity(entityId)) { + return; + } + const auto *instance = getInstanceByEntityId(entityId); if (instance) @@ -645,7 +660,6 @@ namespace thermion_filament if (!asset) { - Log("ERROR: could not find FilamentInstance or FilamentAsset associated with the given entity id"); return; } _assets.erase(entityId); @@ -1288,78 +1302,100 @@ namespace thermion_filament animationComponent.gltfAnimations.end()); } - void SceneManager::loadTexture(EntityId entity, const char *resourcePath, int renderableIndex) + Texture *SceneManager::createTexture(const uint8_t *data, size_t length, const char *name) { + using namespace filament; - // const auto &pos = _instances.find(entity); - // if (pos == _instances.end()) - // { - // Log("ERROR: asset not found for entity."); - // return; - // } - // const auto *instance = pos->second; + // Create an input stream from the data + std::istringstream stream(std::string(reinterpret_cast(data), length)); - // Log("Loading texture at %s for renderableIndex %d", resourcePath, renderableIndex); + // Decode the image + image::LinearImage linearImage = image::ImageDecoder::decode(stream, name, image::ImageDecoder::ColorSpace::SRGB); - // string rp(resourcePath); + if (!linearImage.isValid()) + { + Log("Failed to decode image."); + return nullptr; + } - // if (asset.texture) - // { - // _engine->destroy(asset.texture); - // asset.texture = nullptr; - // } + uint32_t w = linearImage.getWidth(); + uint32_t h = linearImage.getHeight(); + uint32_t channels = linearImage.getChannels(); - // ResourceBuffer imageResource = _resourceLoaderWrapper->load(rp.c_str()); + Texture::InternalFormat textureFormat = channels == 3 ? Texture::InternalFormat::RGB16F + : Texture::InternalFormat::RGBA16F; + Texture::Format bufferFormat = channels == 3 ? Texture::Format::RGB + : Texture::Format::RGBA; - // StreamBufferAdapter sb((char *)imageResource.data, (char *)imageResource.data + imageResource.size); + Texture *texture = Texture::Builder() + .width(w) + .height(h) + .levels(1) + .format(textureFormat) + .sampler(Texture::Sampler::SAMPLER_2D) + .build(*_engine); - // istream *inputStream = new std::istream(&sb); + if (!texture) + { + Log("Failed to create texture: "); + return nullptr; + } - // LinearImage *image = new LinearImage(ImageDecoder::decode( - // *inputStream, rp.c_str(), ImageDecoder::ColorSpace::SRGB)); + Texture::PixelBufferDescriptor buffer( + linearImage.getPixelRef(), + size_t(w * h * channels * sizeof(float)), + bufferFormat, + Texture::Type::FLOAT); - // if (!image->isValid()) - // { - // Log("Invalid image : %s", rp.c_str()); - // delete inputStream; - // _resourceLoaderWrapper->free(imageResource); - // return; - // } + texture->setImage(*_engine, 0, std::move(buffer)); - // uint32_t channels = image->getChannels(); - // uint32_t w = image->getWidth(); - // uint32_t h = image->getHeight(); - // asset.texture = Texture::Builder() - // .width(w) - // .height(h) - // .levels(0xff) - // .format(channels == 3 ? Texture::InternalFormat::RGB16F - // : Texture::InternalFormat::RGBA16F) - // .sampler(Texture::Sampler::SAMPLER_2D) - // .build(*_engine); + Log("Created texture: %s (%d x %d, %d channels)", name, w, h, channels); - // Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, - // void *data) - // { - // delete reinterpret_cast(data); - // }; + _textures.insert(texture); - // Texture::PixelBufferDescriptor buffer( - // image->getPixelRef(), size_t(w * h * channels * sizeof(float)), - // channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA, - // Texture::Type::FLOAT, freeCallback); + return texture; + } - // asset.texture->setImage(*_engine, 0, std::move(buffer)); - // MaterialInstance *const *inst = instance->getMaterialInstances(); - // size_t mic = instance->getMaterialInstanceCount(); - // Log("Material instance count : %d", mic); + bool SceneManager::applyTexture(EntityId entityId, Texture *texture, const char* parameterName, int materialIndex) + { + auto entity = Entity::import(entityId); - // auto sampler = TextureSampler(); - // inst[0]->setParameter("baseColorIndex", 0); - // inst[0]->setParameter("baseColorMap", asset.texture, sampler); - // delete inputStream; + if (entity.isNull()) + { + Log("Entity %d is null?", entityId); + return false; + } - // _resourceLoaderWrapper->free(imageResource); + RenderableManager &rm = _engine->getRenderableManager(); + + auto renderable = rm.getInstance(entity); + + if (!renderable.isValid()) + { + Log("Renderable not valid, was the entity id correct (%d)?", entityId); + return false; + } + + MaterialInstance *mi = rm.getMaterialInstanceAt(renderable, materialIndex); + + if (!mi) + { + Log("ERROR: material index must be less than number of material instances"); + return false; + } + + auto sampler = TextureSampler(); + mi->setParameter(parameterName, texture, sampler); + Log("Applied texture to entity %d", entityId); + return true; + } + + void SceneManager::destroyTexture(Texture* texture) { + if(_textures.find(texture) == _textures.end()) { + Log("Warning: couldn't find texture"); + } + _textures.erase(texture); + _engine->destroy(texture); } void SceneManager::setAnimationFrame(EntityId entityId, int animationIndex, int animationFrame) @@ -1549,10 +1585,12 @@ namespace thermion_filament const auto child = Entity::import(childEntityId); auto transformInstance = tm.getInstance(child); Entity parent; - - while(true) { + + while (true) + { auto newParent = tm.getParent(transformInstance); - if(newParent.isNull()) { + if (newParent.isNull()) + { break; } parent = newParent; @@ -1571,12 +1609,14 @@ namespace thermion_filament const auto &parentInstance = tm.getInstance(parent); const auto &childInstance = tm.getInstance(child); - if(!parentInstance.isValid()) { + if (!parentInstance.isValid()) + { Log("Parent instance is not valid"); return; } - if(!childInstance.isValid()) { + if (!childInstance.isValid()) + { Log("Child instance is not valid"); return; } @@ -1843,170 +1883,174 @@ namespace thermion_filament tm.setTransform(transformInstance, newTransform); } -void SceneManager::queueRelativePositionUpdateFromViewportVector(EntityId entityId, float viewportCoordX, float viewportCoordY) -{ - // Get the camera and viewport - const auto &camera = _view->getCamera(); - const auto &vp = _view->getViewport(); - - // Convert viewport coordinates to NDC space - float ndcX = (2.0f * viewportCoordX) / vp.width - 1.0f; - float ndcY = 1.0f - (2.0f * viewportCoordY) / vp.height; - - // Get the current position of the entity - auto &tm = _engine->getTransformManager(); - auto entity = Entity::import(entityId); - auto transformInstance = tm.getInstance(entity); - auto currentTransform = tm.getTransform(transformInstance); - - // get entity model origin in camera space - auto entityPositionInCameraSpace = camera.getViewMatrix() * currentTransform * filament::math::float4 { 0.0f, 0.0f, 0.0f, 1.0f }; - // get entity model origin in clip space - auto entityPositionInClipSpace = camera.getProjectionMatrix() * entityPositionInCameraSpace; - auto entityPositionInNdcSpace = entityPositionInClipSpace / entityPositionInClipSpace.w; - - // Viewport coords in NDC space (use entity position in camera space Z to project onto near plane) - math::float4 ndcNearPlanePos = {ndcX, ndcY, -1.0f, 1.0f}; - math::float4 ndcFarPlanePos = {ndcX, ndcY, 0.99f, 1.0f}; - math::float4 ndcEntityPlanePos = {ndcX, ndcY, entityPositionInNdcSpace.z, 1.0f}; - - // Get viewport coords in clip space - math::float4 nearPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcNearPlanePos; - auto nearPlaneInCameraSpace = nearPlaneInClipSpace / nearPlaneInClipSpace.w; - math::float4 farPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcFarPlanePos; - auto farPlaneInCameraSpace = farPlaneInClipSpace / farPlaneInClipSpace.w; - math::float4 entityPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcEntityPlanePos; - auto entityPlaneInCameraSpace = entityPlaneInClipSpace / entityPlaneInClipSpace.w; - auto entityPlaneInWorldSpace = camera.getModelMatrix() * entityPlaneInCameraSpace; - - // Queue the position update (as a relative movement) - queuePositionUpdate(entityId, entityPlaneInWorldSpace.x, entityPlaneInWorldSpace.y, entityPlaneInWorldSpace.z, false); -} -void SceneManager::queueRelativePositionUpdateWorldAxis(EntityId entity, float viewportCoordX, float viewportCoordY, float x, float y, float z) -{ - auto worldAxis = math::float3{x, y, z}; - - // Get the camera - const auto &camera = _view->getCamera(); - const auto &vp = _view->getViewport(); - auto viewMatrix = camera.getViewMatrix(); - - math::float3 cameraPosition = camera.getPosition(); - math::float3 cameraForward = -viewMatrix.upperLeft()[2]; - - // Scale the viewport movement to NDC coordinates view axis - math::float2 viewportMovementNDC(viewportCoordX / (vp.width / 2), viewportCoordY / (vp.height / 2)); - - // calculate the translation axis in view space - math::float3 viewSpaceAxis = viewMatrix.upperLeft() * worldAxis; - - // Apply projection matrix to get clip space axis - math::float4 clipAxis = camera.getProjectionMatrix() * math::float4(viewSpaceAxis, 0.0f); - - // Perform perspective division to get the translation axis in normalized device coordinates (NDC) - math::float2 ndcAxis = (clipAxis.xyz / clipAxis.w).xy; - - const float epsilon = 1e-6f; - bool isAligned = false; - if (std::isnan(ndcAxis.x) || std::isnan(ndcAxis.y) || length(ndcAxis) < epsilon || std::abs(dot(normalize(worldAxis), cameraForward)) > 0.99f) + void SceneManager::queueRelativePositionUpdateFromViewportVector(EntityId entityId, float viewportCoordX, float viewportCoordY) { - isAligned = true; - // Find a suitable perpendicular axis: - math::float3 perpendicularAxis; - if (std::abs(worldAxis.x) < epsilon && std::abs(worldAxis.z) < epsilon) - { - // If worldAxis is (0, y, 0), use (1, 0, 0) - perpendicularAxis = {1.0f, 0.0f, 0.0f}; - } - else - { - // Otherwise, calculate a perpendicular vector - perpendicularAxis = normalize(cross(cameraForward, worldAxis)); - } + // Get the camera and viewport + const auto &camera = _view->getCamera(); + const auto &vp = _view->getViewport(); - ndcAxis = (camera.getProjectionMatrix() * math::float4(viewMatrix.upperLeft() * perpendicularAxis, 0.0f)).xy; + // Convert viewport coordinates to NDC space + float ndcX = (2.0f * viewportCoordX) / vp.width - 1.0f; + float ndcY = 1.0f - (2.0f * viewportCoordY) / vp.height; - if (std::isnan(ndcAxis.x) || std::isnan(ndcAxis.y)) { - return; - } + // Get the current position of the entity + auto &tm = _engine->getTransformManager(); + auto entity = Entity::import(entityId); + auto transformInstance = tm.getInstance(entity); + auto currentTransform = tm.getTransform(transformInstance); + // get entity model origin in camera space + auto entityPositionInCameraSpace = camera.getViewMatrix() * currentTransform * filament::math::float4{0.0f, 0.0f, 0.0f, 1.0f}; + // get entity model origin in clip space + auto entityPositionInClipSpace = camera.getProjectionMatrix() * entityPositionInCameraSpace; + auto entityPositionInNdcSpace = entityPositionInClipSpace / entityPositionInClipSpace.w; + + // Viewport coords in NDC space (use entity position in camera space Z to project onto near plane) + math::float4 ndcNearPlanePos = {ndcX, ndcY, -1.0f, 1.0f}; + math::float4 ndcFarPlanePos = {ndcX, ndcY, 0.99f, 1.0f}; + math::float4 ndcEntityPlanePos = {ndcX, ndcY, entityPositionInNdcSpace.z, 1.0f}; + + // Get viewport coords in clip space + math::float4 nearPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcNearPlanePos; + auto nearPlaneInCameraSpace = nearPlaneInClipSpace / nearPlaneInClipSpace.w; + math::float4 farPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcFarPlanePos; + auto farPlaneInCameraSpace = farPlaneInClipSpace / farPlaneInClipSpace.w; + math::float4 entityPlaneInClipSpace = Camera::inverseProjection(camera.getProjectionMatrix()) * ndcEntityPlanePos; + auto entityPlaneInCameraSpace = entityPlaneInClipSpace / entityPlaneInClipSpace.w; + auto entityPlaneInWorldSpace = camera.getModelMatrix() * entityPlaneInCameraSpace; + + // Queue the position update (as a relative movement) + queuePositionUpdate(entityId, entityPlaneInWorldSpace.x, entityPlaneInWorldSpace.y, entityPlaneInWorldSpace.z, false); } - - // project the viewport movement (i.e pointer drag) vector onto the translation axis - // this gives the proportion of the pointer drag vector to translate along the translation axis - float projectedMovement = dot(viewportMovementNDC, normalize(ndcAxis)); - auto translationNDC = projectedMovement * normalize(ndcAxis); - - float dotProduct = dot(normalize(worldAxis), cameraForward); - - // Ensure minimum translation and correct direction - const float minTranslation = 0.01f; - if (isAligned || std::abs(projectedMovement) < minTranslation) { - // Use the dominant component of the viewport movement - float dominantMovement = std::abs(viewportMovementNDC.x) > std::abs(viewportMovementNDC.y) ? viewportMovementNDC.x : viewportMovementNDC.y; - projectedMovement = (std::abs(dominantMovement) < minTranslation) ? minTranslation : std::abs(dominantMovement); - projectedMovement *= (dominantMovement >= 0) ? 1.0f : -1.0f; - translationNDC = projectedMovement * normalize(ndcAxis); - } - - // Log("projectedMovement %f dotProduct %f", projectedMovement, dotProduct); - - // Get the camera's field of view and aspect ratio - float fovY = camera.getFieldOfViewInDegrees(filament::Camera::Fov::VERTICAL); - float fovX = camera.getFieldOfViewInDegrees(filament::Camera::Fov::HORIZONTAL); - - // Convert to radians - fovY = (fovY / 180) * M_PI; - fovX = (fovX / 180) * M_PI; - - float aspectRatio = static_cast(vp.width) / vp.height; - - auto &transformManager = _engine->getTransformManager(); - auto transformInstance = transformManager.getInstance(Entity::import(entity)); - const auto &transform = transformManager.getWorldTransform(transformInstance); - - math::float3 translation; - math::quatf rotation; - math::float3 scale; - - decomposeMatrix(transform, &translation, &rotation, &scale); - - const auto entityWorldPosition = transform * math::float4{0.0f, 0.0f, 0.0f, 1.0f}; - - float distanceToCamera = length(entityWorldPosition.xyz - camera.getPosition()); - - // Calculate the height of the view frustum at the given distance - float frustumHeight = 2.0f * distanceToCamera * tan(fovY * 0.5f); - - // Calculate the width of the view frustum at the given distance - float frustumWidth = frustumHeight * aspectRatio; - - // Convert projected viewport movement to world space distance - float worldDistance = length(math::float2{(translationNDC / 2) * math::float2{frustumWidth, frustumHeight}}); - - // Determine the sign based on the alignment of world axis and camera forward - float sign = (dotProduct >= 0) ? -1.0f : 1.0f; - - // If aligned, use the sign of the projected movement instead - if (isAligned) { - sign = (projectedMovement >= 0) ? 1.0f : -1.0f; - } else if (projectedMovement < 0) { - sign *= -1.0f; - } - - // Flip the sign for the Z-axis - if (std::abs(z) > 0.001) + void SceneManager::queueRelativePositionUpdateWorldAxis(EntityId entity, float viewportCoordX, float viewportCoordY, float x, float y, float z) { - sign *= -1.0f; + auto worldAxis = math::float3{x, y, z}; + + // Get the camera + const auto &camera = _view->getCamera(); + const auto &vp = _view->getViewport(); + auto viewMatrix = camera.getViewMatrix(); + + math::float3 cameraPosition = camera.getPosition(); + math::float3 cameraForward = -viewMatrix.upperLeft()[2]; + + // Scale the viewport movement to NDC coordinates view axis + math::float2 viewportMovementNDC(viewportCoordX / (vp.width / 2), viewportCoordY / (vp.height / 2)); + + // calculate the translation axis in view space + math::float3 viewSpaceAxis = viewMatrix.upperLeft() * worldAxis; + + // Apply projection matrix to get clip space axis + math::float4 clipAxis = camera.getProjectionMatrix() * math::float4(viewSpaceAxis, 0.0f); + + // Perform perspective division to get the translation axis in normalized device coordinates (NDC) + math::float2 ndcAxis = (clipAxis.xyz / clipAxis.w).xy; + + const float epsilon = 1e-6f; + bool isAligned = false; + if (std::isnan(ndcAxis.x) || std::isnan(ndcAxis.y) || length(ndcAxis) < epsilon || std::abs(dot(normalize(worldAxis), cameraForward)) > 0.99f) + { + isAligned = true; + // Find a suitable perpendicular axis: + math::float3 perpendicularAxis; + if (std::abs(worldAxis.x) < epsilon && std::abs(worldAxis.z) < epsilon) + { + // If worldAxis is (0, y, 0), use (1, 0, 0) + perpendicularAxis = {1.0f, 0.0f, 0.0f}; + } + else + { + // Otherwise, calculate a perpendicular vector + perpendicularAxis = normalize(cross(cameraForward, worldAxis)); + } + + ndcAxis = (camera.getProjectionMatrix() * math::float4(viewMatrix.upperLeft() * perpendicularAxis, 0.0f)).xy; + + if (std::isnan(ndcAxis.x) || std::isnan(ndcAxis.y)) + { + return; + } + } + + // project the viewport movement (i.e pointer drag) vector onto the translation axis + // this gives the proportion of the pointer drag vector to translate along the translation axis + float projectedMovement = dot(viewportMovementNDC, normalize(ndcAxis)); + auto translationNDC = projectedMovement * normalize(ndcAxis); + + float dotProduct = dot(normalize(worldAxis), cameraForward); + + // Ensure minimum translation and correct direction + const float minTranslation = 0.01f; + if (isAligned || std::abs(projectedMovement) < minTranslation) + { + // Use the dominant component of the viewport movement + float dominantMovement = std::abs(viewportMovementNDC.x) > std::abs(viewportMovementNDC.y) ? viewportMovementNDC.x : viewportMovementNDC.y; + projectedMovement = (std::abs(dominantMovement) < minTranslation) ? minTranslation : std::abs(dominantMovement); + projectedMovement *= (dominantMovement >= 0) ? 1.0f : -1.0f; + translationNDC = projectedMovement * normalize(ndcAxis); + } + + // Log("projectedMovement %f dotProduct %f", projectedMovement, dotProduct); + + // Get the camera's field of view and aspect ratio + float fovY = camera.getFieldOfViewInDegrees(filament::Camera::Fov::VERTICAL); + float fovX = camera.getFieldOfViewInDegrees(filament::Camera::Fov::HORIZONTAL); + + // Convert to radians + fovY = (fovY / 180) * M_PI; + fovX = (fovX / 180) * M_PI; + + float aspectRatio = static_cast(vp.width) / vp.height; + + auto &transformManager = _engine->getTransformManager(); + auto transformInstance = transformManager.getInstance(Entity::import(entity)); + const auto &transform = transformManager.getWorldTransform(transformInstance); + + math::float3 translation; + math::quatf rotation; + math::float3 scale; + + decomposeMatrix(transform, &translation, &rotation, &scale); + + const auto entityWorldPosition = transform * math::float4{0.0f, 0.0f, 0.0f, 1.0f}; + + float distanceToCamera = length(entityWorldPosition.xyz - camera.getPosition()); + + // Calculate the height of the view frustum at the given distance + float frustumHeight = 2.0f * distanceToCamera * tan(fovY * 0.5f); + + // Calculate the width of the view frustum at the given distance + float frustumWidth = frustumHeight * aspectRatio; + + // Convert projected viewport movement to world space distance + float worldDistance = length(math::float2{(translationNDC / 2) * math::float2{frustumWidth, frustumHeight}}); + + // Determine the sign based on the alignment of world axis and camera forward + float sign = (dotProduct >= 0) ? -1.0f : 1.0f; + + // If aligned, use the sign of the projected movement instead + if (isAligned) + { + sign = (projectedMovement >= 0) ? 1.0f : -1.0f; + } + else if (projectedMovement < 0) + { + sign *= -1.0f; + } + + // Flip the sign for the Z-axis + if (std::abs(z) > 0.001) + { + sign *= -1.0f; + } + + worldDistance *= sign; + + auto newWorldTranslation = worldAxis * worldDistance; + + queuePositionUpdate(entity, newWorldTranslation.x, newWorldTranslation.y, newWorldTranslation.z, true); } - worldDistance *= sign; - - auto newWorldTranslation = worldAxis * worldDistance; - - queuePositionUpdate(entity, newWorldTranslation.x, newWorldTranslation.y, newWorldTranslation.z, true); -} - void SceneManager::queuePositionUpdate(EntityId entity, float x, float y, float z, bool relative) { std::lock_guard lock(_mutex); @@ -2343,10 +2387,12 @@ void SceneManager::queueRelativePositionUpdateWorldAxis(EntityId entity, float v _view->setLayerEnabled(layer, enabled); } - void SceneManager::removeStencilHighlight(EntityId entityId) { + void SceneManager::removeStencilHighlight(EntityId entityId) + { std::lock_guard lock(_stencilMutex); auto found = _highlighted.find(entityId); - if(found == _highlighted.end()) { + if (found == _highlighted.end()) + { Log("Entity %d has no stencil highlight, skipping removal", entityId); return; } @@ -2355,147 +2401,164 @@ void SceneManager::queueRelativePositionUpdateWorldAxis(EntityId entity, float v _highlighted.erase(entityId); } - void SceneManager::setStencilHighlight(EntityId entityId, float r, float g, float b) { - + void SceneManager::setStencilHighlight(EntityId entityId, float r, float g, float b) + { + std::lock_guard lock(_stencilMutex); auto highlightEntity = std::make_unique(entityId, this, _engine, r, g, b); - if(highlightEntity->isValid()) { + if (highlightEntity->isValid()) + { _highlighted.emplace(entityId, std::move(highlightEntity)); } - } - /// - /// Creates an entity with the specified geometry/material/normals and adds to the scene. - /// If [keepData] is true, stores - /// - EntityId SceneManager::createGeometryWithNormals( - float *vertices, - uint32_t numVertices, - float *normals, - uint32_t numNormals, - uint16_t *indices, - uint32_t numIndices, - filament::RenderableManager::PrimitiveType primitiveType, - const char *materialPath, - bool keepData - ) { - auto geometry = std::make_unique(vertices, numVertices, normals, numNormals, indices, numIndices, primitiveType, _engine); +EntityId SceneManager::createGeometry( + float *vertices, + uint32_t numVertices, + float *normals, + uint32_t numNormals, + float *uvs, + uint32_t numUvs, + uint16_t *indices, + uint32_t numIndices, + filament::RenderableManager::PrimitiveType primitiveType, + const char *materialPath, + bool keepData) +{ + auto geometry = std::make_unique(vertices, numVertices, normals, numNormals, uvs, numUvs, indices, numIndices, primitiveType, _engine); - auto renderable = utils::EntityManager::get().create(); - RenderableManager::Builder builder(1); + auto entity = utils::EntityManager::get().create(); + RenderableManager::Builder builder(1); - builder.boundingBox(geometry->getBoundingBox()) - .geometry(0, primitiveType, geometry->vertexBuffer(), geometry->indexBuffer(), 0, numIndices) - .culling(true) - .receiveShadows(true) - .castShadows(true); + builder.boundingBox(geometry->getBoundingBox()) + .geometry(0, primitiveType, geometry->vertexBuffer(), geometry->indexBuffer(), 0, numIndices) + .culling(true) + .receiveShadows(true) + .castShadows(true); - if (materialPath) { - filament::Material* mat = nullptr; - - auto matData = _resourceLoaderWrapper->load(materialPath); - mat = Material::Builder().package(matData.data, matData.size).build(*_engine); - _resourceLoaderWrapper->free(matData); - builder.material(0, mat->getDefaultInstance()); - } else { - filament::gltfio::MaterialKey config; - - config.unlit = false; - config.doubleSided = false; - config.useSpecularGlossiness = false; - config.alphaMode = filament::gltfio::AlphaMode::OPAQUE; - config.hasBaseColorTexture = false; - config.hasClearCoat = false; - config.hasClearCoatNormalTexture = false; - config.hasClearCoatRoughnessTexture = false; - config.hasEmissiveTexture = false; - config.hasIOR = false; - config.hasMetallicRoughnessTexture = false; - config.hasNormalTexture = false; - config.hasOcclusionTexture = false; - config.hasSheen = false; - config.hasSheenColorTexture = false; - config.hasSheenRoughnessTexture = false; - config.hasSpecularGlossinessTexture = false; - config.hasTextureTransforms = false; - config.hasTransmission = false; - config.hasTransmissionTexture = false; - config.hasVolume = false; - config.hasVolumeThicknessTexture = false; + filament::Material *mat = nullptr; + filament::MaterialInstance* materialInstance = nullptr; - config.hasVertexColors = false; - config.hasVolume = false; - // config.enableDiagnostics = true; - - filament::gltfio::UvMap uvmap; - auto materialInstance = _ubershaderProvider->createMaterialInstance(&config, &uvmap); - materialInstance->setParameter("baseColorFactor", RgbaType::sRGB, filament::math::float4 { 1.0f, 0.0f, 0.0f, 1.0f }); - // auto materialInstance = _unlitMaterialProvider->createMaterialInstance(&config, &uvmap); - builder.material(0, materialInstance); + if (materialPath) + { + auto matData = _resourceLoaderWrapper->load(materialPath); + mat = Material::Builder().package(matData.data, matData.size).build(*_engine); + _resourceLoaderWrapper->free(matData); + materialInstance = mat->getDefaultInstance(); + } + else + { + filament::gltfio::MaterialKey config; - } + config.unlit = false; + config.doubleSided = false; + config.useSpecularGlossiness = false; + config.alphaMode = filament::gltfio::AlphaMode::OPAQUE; + config.hasBaseColorTexture = uvs != nullptr; + config.hasClearCoat = false; + config.hasClearCoatNormalTexture = false; + config.hasClearCoatRoughnessTexture = false; + config.hasEmissiveTexture = false; + config.hasIOR = false; + config.hasMetallicRoughnessTexture = false; + config.hasNormalTexture = false; + config.hasOcclusionTexture = false; + config.hasSheen = false; + config.hasSheenColorTexture = false; + config.hasSheenRoughnessTexture = false; + config.hasSpecularGlossinessTexture = false; + config.hasTextureTransforms = false; + config.hasTransmission = false; + config.hasTransmissionTexture = false; + config.hasVolume = false; + config.hasVolumeThicknessTexture = false; + config.baseColorUV = 0; + config.hasVertexColors = false; + config.hasVolume = false; - builder.build(*_engine, renderable); + filament::gltfio::UvMap uvmap; + materialInstance = _ubershaderProvider->createMaterialInstance(&config, &uvmap); - _scene->addEntity(renderable); - - auto entityId = Entity::smuggle(renderable); - - _geometry.emplace(entityId, std::move(geometry)); - - return entityId; + materialInstance->setParameter("baseColorFactor", RgbaType::sRGB, filament::math::float4{1.0f, 0.0f, 1.0f, 1.0f}); } - EntityId SceneManager::createGeometry( - float *vertices, - uint32_t numVertices, - uint16_t *indices, - uint32_t numIndices, - RenderableManager::PrimitiveType primitiveType, - const char *materialPath, - bool keepData - ) { - return createGeometryWithNormals(vertices, numVertices, nullptr, 0, indices, numIndices, primitiveType, materialPath, keepData); - } + // Set up texture and sampler if UVs are available + if (uvs != nullptr && numUvs > 0) + { + // Create a default white texture + static constexpr uint32_t textureSize = 1; + static constexpr uint32_t white = 0x00ffffff; + Texture* texture = Texture::Builder() + .width(textureSize) + .height(textureSize) + .levels(1) + .format(Texture::InternalFormat::RGBA8) + .build(*_engine); - void SceneManager::setMaterialProperty(EntityId entityId, int materialIndex, const char* property, float value) { - auto entity = Entity::import(entityId); - const auto& rm = _engine->getRenderableManager(); - auto renderableInstance = rm.getInstance(entity); - if(!renderableInstance.isValid()) { - Log("ERROR"); - return; - } - auto materialInstance = rm.getMaterialInstanceAt(renderableInstance, materialIndex); - - if(!materialInstance->getMaterial()->hasParameter(property)) { - Log("Parameter %s not found", property); - return; - } - materialInstance->setParameter(property, value); - } + _textures.insert(texture); + + filament::backend::PixelBufferDescriptor pbd(&white, 4, Texture::Format::RGBA, Texture::Type::UBYTE); + texture->setImage(*_engine, 0, std::move(pbd)); - void SceneManager::setMaterialProperty(EntityId entityId, int materialIndex, const char* property, filament::math::float4 value) { - auto entity = Entity::import(entityId); - const auto& rm = _engine->getRenderableManager(); - auto renderableInstance = rm.getInstance(entity); - if(!renderableInstance.isValid()) { - Log("ERROR"); - return; + // Create a sampler + TextureSampler sampler(TextureSampler::MinFilter::LINEAR, TextureSampler::MagFilter::LINEAR); + + // Set the texture and sampler to the material instance + materialInstance->setParameter("baseColorMap", texture, sampler); } - auto materialInstance = rm.getMaterialInstanceAt(renderableInstance, materialIndex); - - if(!materialInstance->getMaterial()->hasParameter(property)) { - Log("Parameter %s not found", property); - return; + + builder.material(0, materialInstance); + builder.build(*_engine, entity); + + _scene->addEntity(entity); + + auto entityId = Entity::smuggle(entity); + + _geometry.emplace(entityId, std::move(geometry)); + + return entityId; +} + + void SceneManager::setMaterialProperty(EntityId entityId, int materialIndex, const char *property, float value) + { + auto entity = Entity::import(entityId); + const auto &rm = _engine->getRenderableManager(); + auto renderableInstance = rm.getInstance(entity); + if (!renderableInstance.isValid()) + { + Log("ERROR"); + return; + } + auto materialInstance = rm.getMaterialInstanceAt(renderableInstance, materialIndex); + + if (!materialInstance->getMaterial()->hasParameter(property)) + { + Log("Parameter %s not found", property); + return; + } + materialInstance->setParameter(property, value); + } + + void SceneManager::setMaterialProperty(EntityId entityId, int materialIndex, const char *property, filament::math::float4 value) + { + auto entity = Entity::import(entityId); + const auto &rm = _engine->getRenderableManager(); + auto renderableInstance = rm.getInstance(entity); + if (!renderableInstance.isValid()) + { + Log("ERROR"); + return; + } + auto materialInstance = rm.getMaterialInstanceAt(renderableInstance, materialIndex); + + if (!materialInstance->getMaterial()->hasParameter(property)) + { + Log("Parameter %s not found", property); + return; + } + materialInstance->setParameter(property, value); } - materialInstance->setParameter(property, value); - } } // namespace thermion_filament - - - \ No newline at end of file diff --git a/thermion_dart/native/src/ThermionDartApi.cpp b/thermion_dart/native/src/ThermionDartApi.cpp index 16d6fbdc..f5ab7c2e 100644 --- a/thermion_dart/native/src/ThermionDartApi.cpp +++ b/thermion_dart/native/src/ThermionDartApi.cpp @@ -352,7 +352,12 @@ extern "C" uint8_t *pixelBuffer, void (*callback)(void)) { - ((FilamentViewer *)viewer)->capture(pixelBuffer, callback); + #ifdef __EMSCRIPTEN__ + bool useFence = true; + #else + bool useFence = false; + #endif + ((FilamentViewer *)viewer)->capture(pixelBuffer, useFence, callback); }; EMSCRIPTEN_KEEPALIVE void set_frame_interval( @@ -850,14 +855,10 @@ extern "C" ((SceneManager *)sceneManager)->removeAnimationComponent(entityId); } - EMSCRIPTEN_KEEPALIVE EntityId create_geometry(void *const sceneManager, float *vertices, int numVertices, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath) - { - return ((SceneManager *)sceneManager)->createGeometry(vertices, (uint32_t)numVertices, indices, numIndices, (filament::RenderableManager::PrimitiveType)primitiveType, materialPath); - } - EMSCRIPTEN_KEEPALIVE EntityId create_geometry_with_normals(void *const sceneManager, float *vertices, int numVertices, float *normals, int numNormals, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath) + EMSCRIPTEN_KEEPALIVE EntityId create_geometry(void *const sceneManager, float *vertices, int numVertices, float *normals, int numNormals, float *uvs, int numUvs, uint16_t *indices, int numIndices, int primitiveType, const char *materialPath) { - return ((SceneManager *)sceneManager)->createGeometryWithNormals(vertices, (uint32_t)numVertices, normals, (uint32_t)numNormals, indices, numIndices, (filament::RenderableManager::PrimitiveType)primitiveType, materialPath); + return ((SceneManager *)sceneManager)->createGeometry(vertices, (uint32_t)numVertices, normals, (uint32_t)numNormals, uvs, numUvs, indices, numIndices, (filament::RenderableManager::PrimitiveType)primitiveType, materialPath); } EMSCRIPTEN_KEEPALIVE EntityId find_child_entity_by_name(void *const sceneManager, const EntityId parent, const char *name) @@ -950,9 +951,24 @@ extern "C" EMSCRIPTEN_KEEPALIVE void set_material_property_float4(void *const sceneManager, EntityId entity, int materialIndex, const char* property, float4 value) { filament::math::float4 filamentValue { value.x, value.y, value.z, value.w }; - ((SceneManager *)sceneManager)->setMaterialProperty(entity, materialIndex, property, filamentValue); } + EMSCRIPTEN_KEEPALIVE void unproject_texture(void *const viewer, EntityId entity, uint8_t* out, uint32_t outWidth, uint32_t outHeight) { + ((FilamentViewer *)viewer)->unprojectTexture(entity, out, outWidth, outHeight); + } + + EMSCRIPTEN_KEEPALIVE void* const create_texture(void *const sceneManager, uint8_t* data, size_t length) { + return (void* const) ((SceneManager *)sceneManager)->createTexture(data, length, "SOMETEXTURE"); + } + + EMSCRIPTEN_KEEPALIVE void apply_texture_to_material(void *const sceneManager, EntityId entity, void* const texture, const char* parameterName, int materialIndex) { + ((SceneManager*)sceneManager)->applyTexture(entity, reinterpret_cast(texture), parameterName, materialIndex); + } + + EMSCRIPTEN_KEEPALIVE void destroy_texture(void *const sceneManager, void* const texture) { + ((SceneManager*)sceneManager)->destroyTexture(reinterpret_cast(texture)); + } + } diff --git a/thermion_dart/native/src/ThermionDartFFIApi.cpp b/thermion_dart/native/src/ThermionDartFFIApi.cpp index f7750910..f8153fca 100644 --- a/thermion_dart/native/src/ThermionDartFFIApi.cpp +++ b/thermion_dart/native/src/ThermionDartFFIApi.cpp @@ -78,32 +78,24 @@ public: } } - void iter() { - - auto frameStart = std::chrono::high_resolution_clock::now(); + void iter() { + const auto frameStart = std::chrono::steady_clock::now(); if (_rendering) { - doRender(); + doRender(); } - auto now = std::chrono::high_resolution_clock::now(); - - auto elapsed = std::chrono::duration_cast(now - frameStart).count(); - - std::function task; - std::unique_lock lock(_access); - while(true) { - now = std::chrono::high_resolution_clock::now(); - elapsed = std::chrono::duration_cast(now - frameStart).count(); - if(elapsed >= _frameIntervalInMicroseconds) { - break; - } - if(!_tasks.empty()) { - task = std::move(_tasks.front()); - _tasks.pop_front(); - task(); - } else { - _cond.wait_for(lock, std::chrono::duration(1)); + const auto frameEnd = frameStart + std::chrono::microseconds(_frameIntervalInMicroseconds); + + while (std::chrono::steady_clock::now() < frameEnd) { + if (!_tasks.empty()) { + auto task = std::move(_tasks.front()); + _tasks.pop_front(); + lock.unlock(); + task(); + lock.lock(); + } else { + _cond.wait_until(lock, frameEnd); } } } @@ -841,6 +833,10 @@ extern "C" void *const sceneManager, float *vertices, int numVertices, + float *normals, + int numNormals, + float *uvs, + int numUvs, uint16_t *indices, int numIndices, int primitiveType, @@ -851,7 +847,7 @@ extern "C" std::packaged_task lambda( [=] { - auto entity = create_geometry(sceneManager, vertices, numVertices, indices, numIndices, primitiveType, materialPath); + auto entity = create_geometry(sceneManager, vertices, numVertices, normals, numNormals, uvs, numUvs, indices, numIndices, primitiveType, materialPath); #ifdef __EMSCRIPTEN__ MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0,$1); @@ -864,31 +860,12 @@ extern "C" auto fut = _rl->add_task(lambda); } - EMSCRIPTEN_KEEPALIVE void create_geometry_with_normals_ffi( - void *const sceneManager, - float *vertices, - int numVertices, - float *normals, - int numNormals, - uint16_t *indices, - int numIndices, - int primitiveType, - const char *materialPath, - bool keepData, - void (*callback)(EntityId)) - { - std::packaged_task lambda( + EMSCRIPTEN_KEEPALIVE void unproject_texture_ffi(void *const viewer, EntityId entity, uint8_t* out, uint32_t outWidth, uint32_t outHeight, void(*callback)()) { + std::packaged_task lambda( [=] { - auto entity = create_geometry_with_normals(sceneManager, vertices, numVertices, normals, numNormals, indices, numIndices, primitiveType, materialPath); - #ifdef __EMSCRIPTEN__ - MAIN_THREAD_EM_ASM({ - moduleArg.dartFilamentResolveCallback($0,$1); - }, callback, entity); - #else - callback(entity); - #endif - return entity; + unproject_texture(viewer, entity, out, outWidth, outHeight); + callback(); }); auto fut = _rl->add_task(lambda); } diff --git a/thermion_dart/native/src/UnprojectTexture.cpp b/thermion_dart/native/src/UnprojectTexture.cpp new file mode 100644 index 00000000..1c1a554e --- /dev/null +++ b/thermion_dart/native/src/UnprojectTexture.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Log.hpp" +#include +#include + +#include + +#include "CustomGeometry.hpp" +#include "UnprojectTexture.hpp" + +namespace thermion_filament { + +void UnprojectTexture::unproject(utils::Entity entity, const uint8_t* inputTexture, uint8_t* outputTexture, uint32_t inputWidth, uint32_t inputHeight, + uint32_t outputWidth, uint32_t outputHeight) { + auto& rm = _engine->getRenderableManager(); + auto& tm = _engine->getTransformManager(); + + // Get the inverse view-projection matrix + math::mat4 invViewProj = Camera::inverseProjection(_camera.getProjectionMatrix()) * _camera.getModelMatrix(); + + // Get the world transform of the entity + auto ti = tm.getInstance(entity); + math::mat4f worldTransform = tm.getWorldTransform(ti); + auto inverseWorldTransform = inverse(worldTransform); + + // Get vertex, normal, UV, and index data from CustomGeometry + const float* vertices = _geometry->vertices; + const float* uvs = _geometry->uvs; + const uint16_t* indices = _geometry->indices; + uint32_t numIndices = _geometry->numIndices; + + // Iterate over each pixel in the output texture + for (uint32_t y = 0; y < outputHeight; ++y) { + for (uint32_t x = 0; x < outputWidth; ++x) { + // Convert output texture coordinates to UV space + math::float2 uv(static_cast(x) / outputWidth, static_cast(y) / outputHeight); + + // Use the UV coordinates to get the corresponding 3D position on the renderable + math::float3 objectPos; + math::float2 interpolatedUV; + bool found = false; + + // Iterate over triangles to find which one contains this UV coordinate + for (size_t i = 0; i < numIndices; i += 3) { + math::float2 uv0 = *(math::float2*)&uvs[indices[i] * 2]; + math::float2 uv1 = *(math::float2*)&uvs[indices[i+1] * 2]; + math::float2 uv2 = *(math::float2*)&uvs[indices[i+2] * 2]; + + if (isInsideTriangle(uv, uv0, uv1, uv2)) { + // Compute barycentric coordinates in UV space + math::float3 bary = barycentric(uv, uv0, uv1, uv2); + + // Interpolate 3D position + math::float3 v0(vertices[indices[i] * 3], vertices[indices[i] * 3 + 1], vertices[indices[i] * 3 + 2]); + math::float3 v1(vertices[indices[i+1] * 3], vertices[indices[i+1] * 3 + 1], vertices[indices[i+1] * 3 + 2]); + math::float3 v2(vertices[indices[i+2] * 3], vertices[indices[i+2] * 3 + 1], vertices[indices[i+2] * 3 + 2]); + objectPos = v0 * bary.x + v1 * bary.y + v2 * bary.z; + + interpolatedUV = uv; + found = true; + break; + } + } + + if (found) { + // Transform the object position to world space + math::float3 worldPos = (worldTransform * math::float4(objectPos, 1.0f)).xyz; + + // Project the world position to screen space + math::float4 clipPos = _camera.getProjectionMatrix() * _camera.getViewMatrix() * math::float4(worldPos, 1.0f); + math::float3 ndcPos = clipPos.xyz / clipPos.w; + + // Convert NDC to screen coordinates + int sx = static_cast((ndcPos.x * 0.5f + 0.5f) * inputWidth); + int sy = static_cast((1.0f - (ndcPos.y * 0.5f + 0.5f)) * inputHeight); + + // Ensure we're within the input texture bounds + if (sx >= 0 && sx < inputWidth && sy >= 0 && sy < inputHeight) { + // Sample the input texture + int inputIndex = (sy * inputWidth + sx) * 4; + int outputIndex = (y * outputWidth + x) * 4; + + // Copy the color to the output texture + std::copy_n(&inputTexture[inputIndex], 4, &outputTexture[outputIndex]); + } + } + } + } +} + +math::float3 UnprojectTexture::doUnproject(const math::float2& screenPos, float depth, const math::mat4& invViewProj) { + math::float4 clipSpace(screenPos.x * 2.0f - 1.0f, screenPos.y * 2.0f - 1.0f, depth * 2.0f - 1.0f, 1.0f); + math::float4 worldSpace = invViewProj * clipSpace; + return math::float3(worldSpace.xyz) / worldSpace.w; +} + +bool UnprojectTexture::isInsideTriangle(const math::float2& p, const math::float2& a, const math::float2& b, const math::float2& c) { + float d1 = (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y); + float d2 = (p.x - c.x) * (b.y - c.y) - (b.x - c.x) * (p.y - c.y); + float d3 = (p.x - a.x) * (c.y - a.y) - (c.x - a.x) * (p.y - a.y); + return (d1 >= 0 && d2 >= 0 && d3 >= 0) || (d1 <= 0 && d2 <= 0 && d3 <= 0); +} + +math::float3 UnprojectTexture::barycentric(const math::float2& p, const math::float2& a, const math::float2& b, const math::float2& c) { + math::float2 v0 = b - a, v1 = c - a, v2 = p - a; + float d00 = dot(v0, v0); + float d01 = dot(v0, v1); + float d11 = dot(v1, v1); + float d20 = dot(v2, v0); + float d21 = dot(v2, v1); + float denom = d00 * d11 - d01 * d01; + float v = (d11 * d20 - d01 * d21) / denom; + float w = (d00 * d21 - d01 * d20) / denom; + float u = 1.0f - v - w; + return math::float3(u, v, w); +} + +} // namespace thermion_filament \ No newline at end of file diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart index 83f742e4..900df23b 100644 --- a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'dart:ffi'; import 'package:thermion_dart/thermion_dart.dart'; -import 'package:thermion_dart/thermion_dart/thermion_viewer_ffi.dart'; +import 'package:thermion_dart/thermion_dart/viewer/ffi/thermion_viewer_ffi.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; import 'package:logging/logging.dart';