diff --git a/.gitignore b/.gitignore index e657b0a2..0315f7dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ # User specific editor and IDE configurations .vs -launchSettings.json -!SeeSharp.Templates/content/SeeSharp.Blazor.Template/Properties/launchSettings.json .idea SeeSharp.sln.DotSettings.user diff --git a/BlenderExtension/see_blender/__init__.py b/BlenderExtension/see_blender/__init__.py index 5758292a..47c46164 100644 --- a/BlenderExtension/see_blender/__init__.py +++ b/BlenderExtension/see_blender/__init__.py @@ -1,4 +1,4 @@ -from . import exporter, render_engine, material_ui, material, world +from . import exporter, render_engine, material_ui, material, world, importer def register(): exporter.register() @@ -6,6 +6,7 @@ def register(): material_ui.register() material.register() world.register() + importer.register() def unregister(): exporter.unregister() @@ -13,3 +14,4 @@ def unregister(): material_ui.unregister() material.unregister() world.unregister() + importer.unregister() diff --git a/BlenderExtension/see_blender/exporter.py b/BlenderExtension/see_blender/exporter.py index 00975799..4e40d115 100644 --- a/BlenderExtension/see_blender/exporter.py +++ b/BlenderExtension/see_blender/exporter.py @@ -140,6 +140,8 @@ def material_to_json(material, out_dir): material.emission_color[1] * material.emission_strength, material.emission_color[2] * material.emission_strength )), + "emission_color": map_rgb(material.emission_color), + "emission_strength": material.emission_strength, "emissionIsGlossy": material.emission_is_glossy, "emissionExponent": material.emission_glossy_exponent } @@ -179,26 +181,47 @@ def export_camera(result, scene): return aspect_ratio = scene.render.resolution_y / scene.render.resolution_x + import mathutils + blend_cam2world = camera.matrix_world.copy() + blend_world2see_world = axis_conversion( + to_forward="Z", + to_up="Y", + ).to_4x4() + def matrix_to_row_major_list(m): + return [ + m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3], + ] + result["transforms"] = [ { "name": "camera", - "position": [ - -camera.location.x, - camera.location.z, - camera.location.y - ], - # At (0,0,0) rotation, the Blender camera faces towards negative z, with positive y pointing up - # We account for this extra rotation here, because we want it to face _our_ negative z with _our_ - # y axis pointing upwards instead. - "rotation": [ - degrees(camera.rotation_euler.x) - 90, - degrees(camera.rotation_euler.z) + 180, - degrees(camera.rotation_euler.y) - ], - "scale": [ 1.0, 1.0, 1.0 ] + "matrix": matrix_to_row_major_list((blend_world2see_world @ blend_cam2world).transposed()) } ] + # result["transforms"] = [ + # { + # "name": "camera", + # "position": [ + # -camera.location.x, + # camera.location.z, + # camera.location.y + # ], + # # At (0,0,0) rotation, the Blender camera faces towards negative z, with positive y pointing up + # # We account for this extra rotation here, because we want it to face _our_ negative z with _our_ + # # y axis pointing upwards instead. + # "rotation": [ + # degrees(camera.rotation_euler.x) - 90, + # degrees(camera.rotation_euler.z) + 180, + # degrees(camera.rotation_euler.y) + # ], + # "scale": [ 1.0, 1.0, 1.0 ] + # } + # ] + result["cameras"] = [ { # convert horizontal FOV to vertical FOV diff --git a/BlenderExtension/see_blender/importer.py b/BlenderExtension/see_blender/importer.py new file mode 100644 index 00000000..e46f8c33 --- /dev/null +++ b/BlenderExtension/see_blender/importer.py @@ -0,0 +1,876 @@ +import os +import json +import math +import bpy +import mathutils +import bmesh +from bpy_extras.io_utils import axis_conversion + +# ------------------------------------------------------------------------ +# Utility +# ------------------------------------------------------------------------ + +def load_image(path): + """Loads image into Blender or returns existing.""" + abspath = bpy.path.abspath(path) + if not os.path.exists(abspath): + print(f"WARNING: Missing texture: {abspath}") + return None + img = bpy.data.images.load(abspath, check_existing=True) + return img + + +def make_material(name, mat_json, base_path): + """Create a Blender material based on SeeSharp material JSON definition.""" + mat = bpy.data.materials.new(name) + mat.use_nodes = True + nt = mat.node_tree + nodes = nt.nodes + links = nt.links + + nodes.clear() + + output = nodes.new("ShaderNodeOutputMaterial") + principled = nodes.new("ShaderNodeBsdfPrincipled") + principled.location = (-200, 0) + output.location = (200, 0) + links.new(principled.outputs["BSDF"], output.inputs["Surface"]) + + # -------- Base color (texture or rgb) + # base_color = mat_json["baseColor"] + base_color = mat_json.get("baseColor") + if base_color: + if base_color["type"] == "rgb": + principled.inputs["Base Color"].default_value = base_color["value"] + [1.0] + elif base_color["type"] == "image": + img_path = os.path.join(base_path, base_color["filename"]) + img = load_image(img_path) + if img: + tex = nodes.new("ShaderNodeTexImage") + tex.image = img + links.new(tex.outputs["Color"], principled.inputs["Base Color"]) + + # -------- Roughness (texture or float) + roughness = mat_json.get("roughness", 1.0) + if isinstance(roughness, str): # texture + img_path = os.path.join(base_path, roughness) + img = load_image(img_path) + if img: + tex = nodes.new("ShaderNodeTexImage") + tex.image = img + tex.location = (-200, -250) + links.new(tex.outputs["Color"], principled.inputs["Roughness"]) + else: + principled.inputs["Roughness"].default_value = float(roughness) + + # Metallic, IOR, Anisotropic + principled.inputs["Metallic"].default_value = mat_json.get("metallic", 0.0) + principled.inputs["IOR"].default_value = mat_json.get("IOR", 1.45) + principled.inputs["Anisotropic"].default_value = mat_json.get("anisotropic", 0.0) + + # Specular Tint + tint = mat_json.get("specularTintStrength", 0.0) + principled.inputs["Specular Tint"].default_value = (tint, tint, tint, 1.0) + + # Specular Transmittance (Transmission) + principled.inputs["Transmission Weight"].default_value = mat_json.get("specularTransmittance", 0.0) + + + # Emission + emission_json = mat_json.get("emission") + if emission_json and emission_json.get("type") == "rgb": + # color = mat_json["emission_color"]["value"] + if "emission_color" in mat_json: + color = mat_json["emission_color"].get("value", [1.0, 1.0, 1.0]) + strength = mat_json.get("emission_strength", 0.0) + principled.inputs["Emission Color"].default_value = (*color[:3], 1.0) + principled.inputs["Emission Strength"].default_value = strength + else: + # fallback to emission value itself + raw = emission_json.get("value", [0.0, 0.0, 0.0]) + strength = max(raw) + if strength > 0.0: + color = [c / strength for c in raw] + else: + color = [0.0, 0.0, 0.0] + principled.inputs["Emission Color"].default_value = (*color, 1.0) + principled.inputs["Emission Strength"].default_value = strength + # color = emission_json.get("value", [0.0, 0.0, 0.0]) + # principled.inputs["Emission Color"].default_value = (*color[:3], 1.0) + # principled.inputs["Emission Strength"].default_value = color[0] + # strength = mat_json.get("emission_strength", 0.0) + # principled.inputs["Emission Color"].default_value = (*color[:3], 1.0) + # principled.inputs["Emission Strength"].default_value = strength + # if mat_json.get("emissionIsGlossy", False): + # principled.inputs["Emission Strength"].default_value = mat_json["emissionExponent"] + + return mat + +def load_mesh_with_transform(filepath, global_matrix): + """ + Load a .ply or .obj mesh and apply a transformation to all imported objects. + Returns a list of imported objects. + """ + before = set(bpy.data.objects) + bpy.ops.wm.obj_import(filepath=filepath) + + after = set(bpy.data.objects) + new_objs = list(after - before) + + # Apply the global transform to all imported objects + for obj in new_objs: + obj.matrix_world = global_matrix + + return new_objs + +# def load_ply(filepath): +# """Load a .ply mesh and return the created object.""" +# before = set(bpy.data.objects) +# bpy.ops.wm.ply_import(filepath=filepath) +# after = set(bpy.data.objects) + +# new_objs = list(after - before) +# if new_objs: +# return new_objs[0] +# return None + +# def load_ply(filepath): +# import struct +# with open(filepath, "rb") as f: +# data = f.read() + +# # -------------------------------- +# # Parse header +# # -------------------------------- + +# header_end = data.find(b"end_header\n") + len(b"end_header\n") +# header = data[:header_end].decode("ascii") +# body = data[header_end:] + +# lines = header.splitlines() + +# is_ascii = True +# vertex_count = 0 +# face_count = 0 +# vertex_props = [] + +# for line in lines: +# if line.startswith("format"): +# is_ascii = "ascii" in line +# elif line.startswith("element vertex"): +# vertex_count = int(line.split()[-1]) +# elif line.startswith("element face"): +# face_count = int(line.split()[-1]) +# elif line.startswith("property") and "vertex_indices" not in line: +# vertex_props.append(line.split()[-1]) + +# has_normals = "nx" in vertex_props +# has_uvs = "s" in vertex_props +# has_colors = "red" in vertex_props + +# # -------------------------------- +# # Read vertices +# # -------------------------------- + +# verts = [] +# normals = [] +# uvs = [] +# colors = [] + +# offset = 0 + +# if is_ascii: +# lines = body.splitlines() +# for i in range(vertex_count): +# parts = lines[i].split() +# idx = 0 + +# x, y, z = map(float, parts[idx:idx+3]) +# idx += 3 +# verts.append((x, y, z)) + +# if has_normals: +# nx, ny, nz = map(float, parts[idx:idx+3]) +# normals.append((nx, ny, nz)) +# idx += 3 + +# if has_uvs: +# s, t = map(float, parts[idx:idx+2]) +# uvs.append((s, 1 - t)) +# idx += 2 + +# if has_colors: +# r, g, b, a = map(int, parts[idx:idx+4]) +# colors.append((r / 255, g / 255, b / 255, a / 255)) + +# face_lines = lines[vertex_count:vertex_count + face_count] + +# else: +# for _ in range(vertex_count): +# x, y, z = struct.unpack_from("<3f", body, offset) +# offset += 12 +# verts.append((x, y, z)) + +# if has_normals: +# normals.append(struct.unpack_from("<3f", body, offset)) +# offset += 12 + +# if has_uvs: +# s, t = struct.unpack_from("<2f", body, offset) +# uvs.append((s, 1 - t)) +# offset += 8 + +# if has_colors: +# r, g, b, a = struct.unpack_from("<4B", body, offset) +# colors.append((r / 255, g / 255, b / 255, a / 255)) +# offset += 4 + +# # print("------------------------------------") +# # print("Is ascii:", is_ascii) +# # print("Vertex count:", vertex_count) +# # print("Face count:", face_count) +# # print("Vertex properties:", vertex_props) +# # print("Body length:", len(body)) +# # print("Offset after vertices:", offset) +# # print("Bytes remaining for faces:", len(body) - offset) + +# # -------------------------------- +# # Read faces +# # -------------------------------- + +# faces = [] + +# if is_ascii: +# for line in face_lines: +# parts = list(map(int, line.split())) +# faces.append(parts[1:]) + +# else: +# for _ in range(face_count): +# length = struct.unpack_from("Blazor] Connected") + except Exception as e: + print("[Blender->Blazor] Connect failed:", e) + _blazor_socket = None + + +def send_to_blazor(data: dict): + global _blazor_socket + + if not _blazor_socket: + _connect() + if not _blazor_socket: + return + + try: + msg = json.dumps(data) + "\n" + _blazor_socket.sendall(msg.encode("utf8")) + except Exception: + _blazor_socket = None diff --git a/BlenderExtension/see_blender_link/utils/__init__.py b/BlenderExtension/see_blender_link/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/BlenderExtension/see_blender_link/utils/helper.py b/BlenderExtension/see_blender_link/utils/helper.py new file mode 100644 index 00000000..b9e7405e --- /dev/null +++ b/BlenderExtension/see_blender_link/utils/helper.py @@ -0,0 +1,28 @@ +import bpy +from mathutils import Vector +from bpy_extras.io_utils import axis_conversion + +def get_scene_scale(): + """Return diagonal of bounding box of all mesh objects.""" + meshes = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH'] + if not meshes: + return 1.0 + + min_v = Vector((1e10, 1e10, 1e10)) + max_v = Vector((-1e10, -1e10, -1e10)) + + for obj in meshes: + for v in obj.bound_box: + w = obj.matrix_world @ Vector(v) + min_v = Vector((min(min_v[i], w[i]) for i in range(3))) + max_v = Vector((max(max_v[i], w[i]) for i in range(3))) + + return (max_v - min_v).length + +def renderer_to_blender_world(hit_render_pos): + # This must match your export axis conversion + global_matrix = axis_conversion( + to_forward="Z", + to_up="Y", + ).to_4x4() + return global_matrix.inverted() @ Vector(hit_render_pos) \ No newline at end of file diff --git a/Data/Scenes/CornellBox/CornellBox.json b/Data/Scenes/CornellBox/CornellBox.json index 40b19015..ca726cfd 100644 --- a/Data/Scenes/CornellBox/CornellBox.json +++ b/Data/Scenes/CornellBox/CornellBox.json @@ -1,880 +1,1103 @@ { - "name": "Cornell Box", - "transforms": [ - { - "name": "camera", - "position": [ 0.0, 1.0, 6.8 ], - "rotation": [ 0.0, 0.0, 0.0 ], - "scale": [ 1.0, 1.0, 1.0 ] - } - ], - "cameras": [ - { - "name": "default", - "type": "perspective", - "fov": 19.5, - "transform": "camera" - } - ], - "materials": [ - { - "name": "LeftWall", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [0.63, 0.065, 0.05] - } - }, - { - "name": "RightWall", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [0.14, 0.45, 0.091] - } - }, - { - "name": "Floor", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [0.725, 0.71, 0.68] - } - }, - { - "name": "Ceiling", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [0.725, 0.71, 0.68] - } - }, - { - "name": "BackWall", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [0.725, 0.71, 0.68] - } - }, - { - "name": "ShortBox", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [0.725, 0.71, 0.68] - } + "name": "Cornell Box", + "transforms": [ + { + "name": "camera", + "position": [ + 0.0, + 1.0, + 6.8 + ], + "rotation": [ + 0.0, + 0.0, + 0.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ], + "cameras": [ + { + "name": "default", + "type": "perspective", + "fov": 19.5, + "transform": "camera" + } + ], + "materials": [ + { + "name": "LeftWall", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.63, + 0.065, + 0.05 + ] }, - { - "name": "TallBox", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [0.725, 0.71, 0.68] - } + "roughness": 1.0, + "anisotropic": 0.0, + "IOR": 1.4500000476837158, + "metallic": 0.0, + "specularTintStrength": 0.0, + "specularTransmittance": 0.0, + "emission": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 + ] }, - { - "name": "Light", - "type": "diffuse", - "baseColor": { - "type": "rgb", - "value": [ 0.0, 0.0, 0.0] - } - } - ], - "objects": [ - { - "name": "mesh0", - "emission": { - "type": "rgb", - "unit": "radiance", - "value": [17.0, 12.0, 4.0] - }, - "material": "Light", - "type": "trimesh", - "indices": [ - 0, - 1, - 2, - 0, - 2, - 3 - ], - "vertices": [ - -0.24, - 1.98, - -0.22, - 0.23, - 1.98, - -0.22, - 0.23, - 1.98, - 0.16, - -0.24, - 1.98, - 0.16 - ], - "normals": [ - -8.74228e-08, - -1.0, - 1.86006e-07, - -8.74228e-08, - -1.0, - 1.86006e-07, - -8.74228e-08, - -1.0, - 1.86006e-07, - -8.74228e-08, - -1.0, - 1.86006e-07 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, + "emission_color": { + "type": "rgb", + "value": [ 1.0, 1.0, - 0.0, 1.0 ] }, - { - "name": "mesh1", - "material": "Floor", - "type": "trimesh", - "indices": [ - 0, - 1, - 2, - 0, - 2, - 3 - ], - "vertices": [ - -1.0, - 1.74846e-07, - -1.0, - -1.0, - 1.74846e-07, - 1.0, - 1.0, - -1.74846e-07, - 1.0, - 1.0, - -1.74846e-07, - -1.0 - ], - "normals": [ - 4.37114e-08, - 1.0, - 1.91069e-15, - 4.37114e-08, - 1.0, - 1.91069e-15, - 4.37114e-08, - 1.0, - 1.91069e-15, - 4.37114e-08, - 1.0, - 1.91069e-15 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0 + "emission_strength": 0.0, + "emissionIsGlossy": false, + "emissionExponent": 20.0 + }, + { + "name": "RightWall", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.14, + 0.45, + 0.091 ] }, - { - "name": "mesh2", - "material": "Ceiling", - "type": "trimesh", - "indices": [ - 0, - 1, - 2, - 0, - 2, - 3 - ], - "vertices": [ - 1.0, - 2.0, - 1.0, - -1.0, - 2.0, - 1.0, - -1.0, - 2.0, - -1.0, - 1.0, - 2.0, - -1.0 - ], - "normals": [ - -8.74228e-08, - -1.0, - -4.37114e-08, - -8.74228e-08, - -1.0, - -4.37114e-08, - -8.74228e-08, - -1.0, - -4.37114e-08, - -8.74228e-08, - -1.0, - -4.37114e-08 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0 + "roughness": 1.0, + "anisotropic": 0.0, + "IOR": 1.4500000476837158, + "metallic": 0.0, + "specularTintStrength": 0.0, + "specularTransmittance": 0.0, + "emission": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 ] }, - { - "name": "mesh3", - "material": "BackWall", - "type": "trimesh", - "indices": [ - 0, - 1, - 2, - 0, - 2, - 3 - ], - "vertices": [ - -1.0, - 0.0, - -1.0, - -1.0, - 2.0, - -1.0, + "emission_color": { + "type": "rgb", + "value": [ 1.0, - 2.0, - -1.0, 1.0, - 0.0, - -1.0 - ], - "normals": [ - 8.74228e-08, - -4.37114e-08, - -1.0, - 8.74228e-08, - -4.37114e-08, - -1.0, - 8.74228e-08, - -4.37114e-08, - -1.0, - 8.74228e-08, - -4.37114e-08, - -1.0 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, 1.0 ] }, - { - "name": "mesh4", - "material": "RightWall", - "type": "trimesh", - "indices": [ - 0, - 1, - 2, - 0, - 2, - 3 - ], - "vertices": [ - 1.0, - 0.0, - -1.0, - 1.0, - 2.0, - -1.0, - 1.0, - 2.0, + "emission_strength": 0.0, + "emissionIsGlossy": false, + "emissionExponent": 20.0 + }, + { + "name": "Floor", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.725, + 0.71, + 0.68 + ] + }, + "roughness": 1.0, + "anisotropic": 0.0, + "IOR": 1.4500000476837158, + "metallic": 0.0, + "specularTintStrength": 0.0, + "specularTransmittance": 0.0, + "emission": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 + ] + }, + "emission_color": { + "type": "rgb", + "value": [ 1.0, 1.0, - 0.0, 1.0 - ], - "normals": [ - 1.0, - -4.37114e-08, - 1.31134e-07, - 1.0, - -4.37114e-08, - 1.31134e-07, - 1.0, - -4.37114e-08, - 1.31134e-07, - 1.0, - -4.37114e-08, - 1.31134e-07 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, + ] + }, + "emission_strength": 0.0, + "emissionIsGlossy": false, + "emissionExponent": 20.0 + }, + { + "name": "Ceiling", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.725, + 0.71, + 0.68 + ] + }, + "roughness": 1.0, + "anisotropic": 0.0, + "IOR": 1.4500000476837158, + "metallic": 0.0, + "specularTintStrength": 0.0, + "specularTransmittance": 0.0, + "emission": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 + ] + }, + "emission_color": { + "type": "rgb", + "value": [ 1.0, 1.0, - 0.0, 1.0 ] }, - { - "name": "mesh5", - "material": "LeftWall", - "type": "trimesh", - "indices": [ - 0, - 1, - 2, - 0, - 2, - 3 - ], - "vertices": [ - -1.0, - 0.0, - 1.0, - -1.0, - 2.0, - 1.0, - -1.0, - 2.0, - -1.0, - -1.0, - 0.0, - -1.0 - ], - "normals": [ - -1.0, - -4.37114e-08, - -4.37114e-08, - -1.0, - -4.37114e-08, - -4.37114e-08, - -1.0, - -4.37114e-08, - -4.37114e-08, - -1.0, - -4.37114e-08, - -4.37114e-08 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, + "emission_strength": 0.0, + "emissionIsGlossy": false, + "emissionExponent": 20.0 + }, + { + "name": "BackWall", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.725, + 0.71, + 0.68 + ] + }, + "roughness": 1.0, + "anisotropic": 0.0, + "IOR": 1.4500000476837158, + "metallic": 0.0, + "specularTintStrength": 0.0, + "specularTransmittance": 0.0, + "emission": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 + ] + }, + "emission_color": { + "type": "rgb", + "value": [ 1.0, 1.0, - 0.0, 1.0 ] }, - { - "name": "mesh6", - "material": "ShortBox", - "type": "trimesh", - "indices": [ - 0, - 2, - 1, - 0, - 3, - 2, - 4, - 6, - 5, - 4, - 7, - 6, - 8, - 10, - 9, - 8, - 11, - 10, - 12, - 14, - 13, - 12, - 15, - 14, - 16, - 18, - 17, - 16, - 19, - 18, - 20, - 22, - 21, - 20, - 23, - 22 - ], - "vertices": [ - -0.0460751, - 0.6, - 0.573007, - -0.0460751, - -2.98023e-08, - 0.573007, - 0.124253, - 0.0, - 0.00310463, - 0.124253, - 0.6, - 0.00310463, - 0.533009, - 0.0, - 0.746079, - 0.533009, - 0.6, - 0.746079, - 0.703337, - 0.6, - 0.176177, - 0.703337, - 2.98023e-08, - 0.176177, - 0.533009, - 0.6, - 0.746079, - -0.0460751, - 0.6, - 0.573007, - 0.124253, - 0.6, - 0.00310463, - 0.703337, - 0.6, - 0.176177, - 0.703337, - 2.98023e-08, - 0.176177, - 0.124253, - 0.0, - 0.00310463, - -0.0460751, - -2.98023e-08, - 0.573007, - 0.533009, - 0.0, - 0.746079, - 0.533009, - 0.0, - 0.746079, - -0.0460751, - -2.98023e-08, - 0.573007, - -0.0460751, - 0.6, - 0.573007, - 0.533009, - 0.6, - 0.746079, - 0.703337, - 0.6, - 0.176177, - 0.124253, - 0.6, - 0.00310463, - 0.124253, - 0.0, - 0.00310463, - 0.703337, - 2.98023e-08, - 0.176177 - ], - "normals": [ - -0.958123, - -4.18809e-08, - -0.286357, - -0.958123, - -4.18809e-08, - -0.286357, - -0.958123, - -4.18809e-08, - -0.286357, - -0.958123, - -4.18809e-08, - -0.286357, - 0.958123, - 4.18809e-08, - 0.286357, - 0.958123, - 4.18809e-08, - 0.286357, - 0.958123, - 4.18809e-08, - 0.286357, - 0.958123, - 4.18809e-08, - 0.286357, - -4.37114e-08, - 1.0, - -1.91069e-15, - -4.37114e-08, - 1.0, - -1.91069e-15, - -4.37114e-08, - 1.0, - -1.91069e-15, - -4.37114e-08, - 1.0, - -1.91069e-15, - 4.37114e-08, - -1.0, - 1.91069e-15, - 4.37114e-08, - -1.0, - 1.91069e-15, - 4.37114e-08, - -1.0, - 1.91069e-15, - 4.37114e-08, - -1.0, - 1.91069e-15, - -0.286357, - -1.25171e-08, - 0.958123, - -0.286357, - -1.25171e-08, - 0.958123, - -0.286357, - -1.25171e-08, - 0.958123, - -0.286357, - -1.25171e-08, - 0.958123, - 0.286357, - 1.25171e-08, - -0.958123, - 0.286357, - 1.25171e-08, - -0.958123, - 0.286357, - 1.25171e-08, - -0.958123, - 0.286357, - 1.25171e-08, - -0.958123 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, + "emission_strength": 0.0, + "emissionIsGlossy": false, + "emissionExponent": 20.0 + }, + { + "name": "ShortBox", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.725, + 0.71, + 0.68 + ] + } + }, + { + "name": "TallBox", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.725, + 0.71, + 0.68 + ] + }, + "roughness": 1.0, + "anisotropic": 0.0, + "IOR": 1.4500000476837158, + "metallic": 0.0, + "specularTintStrength": 0.0, + "specularTransmittance": 0.0, + "emission": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 + ] + }, + "emission_color": { + "type": "rgb", + "value": [ 1.0, 1.0, - 0.0, 1.0 ] }, - { - "name": "mesh7", - "material": "TallBox", - "type": "trimesh", - "indices": [ - 0, - 2, - 1, - 0, - 3, - 2, - 4, - 6, - 5, - 4, - 7, - 6, - 8, - 10, - 9, - 8, - 11, - 10, - 12, - 14, - 13, - 12, - 15, - 14, - 16, - 18, - 17, - 16, - 19, - 18, - 20, - 22, - 21, - 20, - 23, - 22 - ], - "vertices": [ - -0.720444, - 1.2, - -0.473882, - -0.720444, - 0.0, - -0.473882, - -0.146892, - 0.0, - -0.673479, - -0.146892, - 1.2, - -0.673479, - -0.523986, - 0.0, - 0.0906493, - -0.523986, - 1.2, - 0.0906492, - 0.0495656, - 1.2, - -0.108948, - 0.0495656, - 0.0, - -0.108948, - -0.523986, - 1.2, - 0.0906492, - -0.720444, - 1.2, - -0.473882, - -0.146892, - 1.2, - -0.673479, - 0.0495656, - 1.2, - -0.108948, - 0.0495656, - 0.0, - -0.108948, - -0.146892, - 0.0, - -0.673479, - -0.720444, - 0.0, - -0.473882, - -0.523986, - 0.0, - 0.0906493, - -0.523986, - 0.0, - 0.0906493, - -0.720444, - 0.0, - -0.473882, - -0.720444, - 1.2, - -0.473882, - -0.523986, - 1.2, - 0.0906492, - 0.0495656, - 1.2, - -0.108948, - -0.146892, - 1.2, - -0.673479, - -0.146892, - 0.0, - -0.673479, - 0.0495656, - 0.0, - -0.108948 - ], - "normals": [ - -0.328669, - -4.1283e-08, - -0.944445, - -0.328669, - -4.1283e-08, - -0.944445, - -0.328669, - -4.1283e-08, - -0.944445, - -0.328669, - -4.1283e-08, - -0.944445, - 0.328669, - 4.1283e-08, - 0.944445, - 0.328669, - 4.1283e-08, - 0.944445, - 0.328669, - 4.1283e-08, - 0.944445, - 0.328669, - 4.1283e-08, - 0.944445, - 3.82137e-15, - 1.0, - -4.37114e-08, - 3.82137e-15, - 1.0, - -4.37114e-08, - 3.82137e-15, - 1.0, - -4.37114e-08, - 3.82137e-15, - 1.0, - -4.37114e-08, - -3.82137e-15, - -1.0, - 4.37114e-08, - -3.82137e-15, - -1.0, - 4.37114e-08, - -3.82137e-15, - -1.0, - 4.37114e-08, - -3.82137e-15, - -1.0, - 4.37114e-08, - -0.944445, - 1.43666e-08, - 0.328669, - -0.944445, - 1.43666e-08, - 0.328669, - -0.944445, - 1.43666e-08, - 0.328669, - -0.944445, - 1.43666e-08, - 0.328669, - 0.944445, - -1.43666e-08, - -0.328669, - 0.944445, - -1.43666e-08, - -0.328669, - 0.944445, - -1.43666e-08, - -0.328669, - 0.944445, - -1.43666e-08, - -0.328669 - ], - "uv": [ - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, - 1.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 1.0, - 0.0, + "emission_strength": 0.0, + "emissionIsGlossy": false, + "emissionExponent": 20.0 + }, + { + "name": "Light", + "type": "generic", + "baseColor": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 + ] + }, + "roughness": 1.0, + "anisotropic": 0.0, + "IOR": 1.4500000476837158, + "metallic": 0.0, + "specularTintStrength": 0.0, + "specularTransmittance": 0.0, + "emission": { + "type": "rgb", + "value": [ + 0.0, + 0.0, + 0.0 + ] + }, + "emission_color": { + "type": "rgb", + "value": [ 1.0, 1.0, - 0.0, 1.0 ] - } - ] - } \ No newline at end of file + }, + "emission_strength": 10.0, + "emissionIsGlossy": false, + "emissionExponent": 20.0 + } + ], + "objects": [ + { + "name": "mesh0", + "emission": { + "type": "rgb", + "unit": "radiance", + "value": [ + 17.0, + 12.0, + 4.0 + ] + }, + "material": "Light", + "type": "trimesh", + "indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "vertices": [ + -0.24, + 1.98, + -0.22, + 0.23, + 1.98, + -0.22, + 0.23, + 1.98, + 0.16, + -0.24, + 1.98, + 0.16 + ], + "normals": [ + -8.74228e-08, + -1.0, + 1.86006e-07, + -8.74228e-08, + -1.0, + 1.86006e-07, + -8.74228e-08, + -1.0, + 1.86006e-07, + -8.74228e-08, + -1.0, + 1.86006e-07 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "name": "mesh1", + "material": "Floor", + "type": "trimesh", + "indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "vertices": [ + -1.0, + 1.74846e-07, + -1.0, + -1.0, + 1.74846e-07, + 1.0, + 1.0, + -1.74846e-07, + 1.0, + 1.0, + -1.74846e-07, + -1.0 + ], + "normals": [ + 4.37114e-08, + 1.0, + 1.91069e-15, + 4.37114e-08, + 1.0, + 1.91069e-15, + 4.37114e-08, + 1.0, + 1.91069e-15, + 4.37114e-08, + 1.0, + 1.91069e-15 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "name": "mesh2", + "material": "Ceiling", + "type": "trimesh", + "indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "vertices": [ + 1.0, + 2.0, + 1.0, + -1.0, + 2.0, + 1.0, + -1.0, + 2.0, + -1.0, + 1.0, + 2.0, + -1.0 + ], + "normals": [ + -8.74228e-08, + -1.0, + -4.37114e-08, + -8.74228e-08, + -1.0, + -4.37114e-08, + -8.74228e-08, + -1.0, + -4.37114e-08, + -8.74228e-08, + -1.0, + -4.37114e-08 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "name": "mesh3", + "material": "BackWall", + "type": "trimesh", + "indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "vertices": [ + -1.0, + 0.0, + -1.0, + -1.0, + 2.0, + -1.0, + 1.0, + 2.0, + -1.0, + 1.0, + 0.0, + -1.0 + ], + "normals": [ + 8.74228e-08, + -4.37114e-08, + -1.0, + 8.74228e-08, + -4.37114e-08, + -1.0, + 8.74228e-08, + -4.37114e-08, + -1.0, + 8.74228e-08, + -4.37114e-08, + -1.0 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "name": "mesh4", + "material": "RightWall", + "type": "trimesh", + "indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "vertices": [ + 1.0, + 0.0, + -1.0, + 1.0, + 2.0, + -1.0, + 1.0, + 2.0, + 1.0, + 1.0, + 0.0, + 1.0 + ], + "normals": [ + 1.0, + -4.37114e-08, + 1.31134e-07, + 1.0, + -4.37114e-08, + 1.31134e-07, + 1.0, + -4.37114e-08, + 1.31134e-07, + 1.0, + -4.37114e-08, + 1.31134e-07 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "name": "mesh5", + "material": "LeftWall", + "type": "trimesh", + "indices": [ + 0, + 1, + 2, + 0, + 2, + 3 + ], + "vertices": [ + -1.0, + 0.0, + 1.0, + -1.0, + 2.0, + 1.0, + -1.0, + 2.0, + -1.0, + -1.0, + 0.0, + -1.0 + ], + "normals": [ + -1.0, + -4.37114e-08, + -4.37114e-08, + -1.0, + -4.37114e-08, + -4.37114e-08, + -1.0, + -4.37114e-08, + -4.37114e-08, + -1.0, + -4.37114e-08, + -4.37114e-08 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "name": "mesh6", + "material": "ShortBox", + "type": "trimesh", + "indices": [ + 0, + 2, + 1, + 0, + 3, + 2, + 4, + 6, + 5, + 4, + 7, + 6, + 8, + 10, + 9, + 8, + 11, + 10, + 12, + 14, + 13, + 12, + 15, + 14, + 16, + 18, + 17, + 16, + 19, + 18, + 20, + 22, + 21, + 20, + 23, + 22 + ], + "vertices": [ + -0.0460751, + 0.6, + 0.573007, + -0.0460751, + -2.98023e-08, + 0.573007, + 0.124253, + 0.0, + 0.00310463, + 0.124253, + 0.6, + 0.00310463, + 0.533009, + 0.0, + 0.746079, + 0.533009, + 0.6, + 0.746079, + 0.703337, + 0.6, + 0.176177, + 0.703337, + 2.98023e-08, + 0.176177, + 0.533009, + 0.6, + 0.746079, + -0.0460751, + 0.6, + 0.573007, + 0.124253, + 0.6, + 0.00310463, + 0.703337, + 0.6, + 0.176177, + 0.703337, + 2.98023e-08, + 0.176177, + 0.124253, + 0.0, + 0.00310463, + -0.0460751, + -2.98023e-08, + 0.573007, + 0.533009, + 0.0, + 0.746079, + 0.533009, + 0.0, + 0.746079, + -0.0460751, + -2.98023e-08, + 0.573007, + -0.0460751, + 0.6, + 0.573007, + 0.533009, + 0.6, + 0.746079, + 0.703337, + 0.6, + 0.176177, + 0.124253, + 0.6, + 0.00310463, + 0.124253, + 0.0, + 0.00310463, + 0.703337, + 2.98023e-08, + 0.176177 + ], + "normals": [ + -0.958123, + -4.18809e-08, + -0.286357, + -0.958123, + -4.18809e-08, + -0.286357, + -0.958123, + -4.18809e-08, + -0.286357, + -0.958123, + -4.18809e-08, + -0.286357, + 0.958123, + 4.18809e-08, + 0.286357, + 0.958123, + 4.18809e-08, + 0.286357, + 0.958123, + 4.18809e-08, + 0.286357, + 0.958123, + 4.18809e-08, + 0.286357, + -4.37114e-08, + 1.0, + -1.91069e-15, + -4.37114e-08, + 1.0, + -1.91069e-15, + -4.37114e-08, + 1.0, + -1.91069e-15, + -4.37114e-08, + 1.0, + -1.91069e-15, + 4.37114e-08, + -1.0, + 1.91069e-15, + 4.37114e-08, + -1.0, + 1.91069e-15, + 4.37114e-08, + -1.0, + 1.91069e-15, + 4.37114e-08, + -1.0, + 1.91069e-15, + -0.286357, + -1.25171e-08, + 0.958123, + -0.286357, + -1.25171e-08, + 0.958123, + -0.286357, + -1.25171e-08, + 0.958123, + -0.286357, + -1.25171e-08, + 0.958123, + 0.286357, + 1.25171e-08, + -0.958123, + 0.286357, + 1.25171e-08, + -0.958123, + 0.286357, + 1.25171e-08, + -0.958123, + 0.286357, + 1.25171e-08, + -0.958123 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + }, + { + "name": "mesh7", + "material": "TallBox", + "type": "trimesh", + "indices": [ + 0, + 2, + 1, + 0, + 3, + 2, + 4, + 6, + 5, + 4, + 7, + 6, + 8, + 10, + 9, + 8, + 11, + 10, + 12, + 14, + 13, + 12, + 15, + 14, + 16, + 18, + 17, + 16, + 19, + 18, + 20, + 22, + 21, + 20, + 23, + 22 + ], + "vertices": [ + -0.720444, + 1.2, + -0.473882, + -0.720444, + 0.0, + -0.473882, + -0.146892, + 0.0, + -0.673479, + -0.146892, + 1.2, + -0.673479, + -0.523986, + 0.0, + 0.0906493, + -0.523986, + 1.2, + 0.0906492, + 0.0495656, + 1.2, + -0.108948, + 0.0495656, + 0.0, + -0.108948, + -0.523986, + 1.2, + 0.0906492, + -0.720444, + 1.2, + -0.473882, + -0.146892, + 1.2, + -0.673479, + 0.0495656, + 1.2, + -0.108948, + 0.0495656, + 0.0, + -0.108948, + -0.146892, + 0.0, + -0.673479, + -0.720444, + 0.0, + -0.473882, + -0.523986, + 0.0, + 0.0906493, + -0.523986, + 0.0, + 0.0906493, + -0.720444, + 0.0, + -0.473882, + -0.720444, + 1.2, + -0.473882, + -0.523986, + 1.2, + 0.0906492, + 0.0495656, + 1.2, + -0.108948, + -0.146892, + 1.2, + -0.673479, + -0.146892, + 0.0, + -0.673479, + 0.0495656, + 0.0, + -0.108948 + ], + "normals": [ + -0.328669, + -4.1283e-08, + -0.944445, + -0.328669, + -4.1283e-08, + -0.944445, + -0.328669, + -4.1283e-08, + -0.944445, + -0.328669, + -4.1283e-08, + -0.944445, + 0.328669, + 4.1283e-08, + 0.944445, + 0.328669, + 4.1283e-08, + 0.944445, + 0.328669, + 4.1283e-08, + 0.944445, + 0.328669, + 4.1283e-08, + 0.944445, + 3.82137e-15, + 1.0, + -4.37114e-08, + 3.82137e-15, + 1.0, + -4.37114e-08, + 3.82137e-15, + 1.0, + -4.37114e-08, + 3.82137e-15, + 1.0, + -4.37114e-08, + -3.82137e-15, + -1.0, + 4.37114e-08, + -3.82137e-15, + -1.0, + 4.37114e-08, + -3.82137e-15, + -1.0, + 4.37114e-08, + -3.82137e-15, + -1.0, + 4.37114e-08, + -0.944445, + 1.43666e-08, + 0.328669, + -0.944445, + 1.43666e-08, + 0.328669, + -0.944445, + 1.43666e-08, + 0.328669, + -0.944445, + 1.43666e-08, + 0.328669, + 0.944445, + -1.43666e-08, + -0.328669, + 0.944445, + -1.43666e-08, + -0.328669, + 0.944445, + -1.43666e-08, + -0.328669, + 0.944445, + -1.43666e-08, + -0.328669 + ], + "uv": [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 1.0, + 0.0, + 1.0 + ] + } + ] +} \ No newline at end of file diff --git a/Examples/BlenderSync/App.razor b/Examples/BlenderSync/App.razor new file mode 100644 index 00000000..6fd3ed1b --- /dev/null +++ b/Examples/BlenderSync/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/Examples/BlenderSync/BlenderSync.csproj b/Examples/BlenderSync/BlenderSync.csproj new file mode 100644 index 00000000..3f177c5f --- /dev/null +++ b/Examples/BlenderSync/BlenderSync.csproj @@ -0,0 +1,18 @@ + + + + Exe + net9.0 + enable + + + + + + + + + + + + diff --git a/SeeSharp.Examples/Imports.cs b/Examples/BlenderSync/Imports.cs similarity index 92% rename from SeeSharp.Examples/Imports.cs rename to Examples/BlenderSync/Imports.cs index 0162e168..dee65517 100644 --- a/SeeSharp.Examples/Imports.cs +++ b/Examples/BlenderSync/Imports.cs @@ -29,4 +29,6 @@ global using SeeSharp.Shading; global using SeeSharp.Shading.Background; global using SeeSharp.Shading.Emitters; -global using SeeSharp.Shading.Materials; \ No newline at end of file +global using SeeSharp.Shading.Materials; + +global using SeeSharp.Blazor; diff --git a/Examples/BlenderSync/MainLayout.razor b/Examples/BlenderSync/MainLayout.razor new file mode 100644 index 00000000..a5af3489 --- /dev/null +++ b/Examples/BlenderSync/MainLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +
@Body
diff --git a/Examples/BlenderSync/Pages/BlenderImporter.razor b/Examples/BlenderSync/Pages/BlenderImporter.razor new file mode 100644 index 00000000..829b7cbd --- /dev/null +++ b/Examples/BlenderSync/Pages/BlenderImporter.razor @@ -0,0 +1,118 @@ +@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage +@inject ProtectedSessionStorage ProtectedSessionStore +@using SeeSharp.Experiments +@using SeeSharp.Blazor +@using System; +@using System.IO; + +
+

Select a scene

+ +
+ +
+ @if (isSceneNameValid && !loading && scene?.Name != sceneNameInput.Text) + { + + } + else + { + + + @if (!loading) + { + + } + } +
+
+ +
+ Available scenes + +
+ + @if (loading) + { +

Loading...

+ } + else if (!string.IsNullOrEmpty(scene?.Name)) + { +

Loaded "@(scene.Name)"

+ } + +
+ +@code { + [Parameter] + public EventCallback OnSceneLoaded { get; set; } + + + IEnumerable availableSceneNames + { + get + { + if (_availableSceneNames == null) + _availableSceneNames = SceneRegistry.FindAvailableScenes().Order(); + return _availableSceneNames; + } + } + IEnumerable _availableSceneNames; + + public AutocompleteInput sceneNameInput { get; set; } + + SceneFromFile scene; + bool loading = false; + + SceneFromFile Scene => scene; + + bool isSceneNameValid => sceneNameInput?.Valid == true; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var result = await ProtectedSessionStore.GetAsync("lastScene"); + if (result.Success) + sceneNameInput.Text = result.Value; + } + } + + async Task OnSceneNameUpdate(string newName) + { + await ProtectedSessionStore.SetAsync("lastScene", newName); + } + + async Task LoadScene() + { + if (!isSceneNameValid || loading) return; + loading = true; + await Task.Run(() => scene = SceneRegistry.LoadScene(sceneNameInput.Text)); + loading = false; + await OnSceneLoaded.InvokeAsync(scene); + } + + public string FindJson(string folder) + { + folder = "../../Data/Scenes/" + folder; + folder = Path.GetFullPath(folder); + var files = Directory.GetFiles(folder, "*.json"); + if (files.Length == 0) + { + Console.WriteLine("Error: No JSON file found in the folder."); + return null; + } + + return Path.GetFullPath(files[0]); + } +} diff --git a/Examples/BlenderSync/Pages/BlenderImporter.razor.css b/Examples/BlenderSync/Pages/BlenderImporter.razor.css new file mode 100644 index 00000000..7dd987e3 --- /dev/null +++ b/Examples/BlenderSync/Pages/BlenderImporter.razor.css @@ -0,0 +1,66 @@ +::deep .scene-button { + margin-top: 0px; + margin-bottom: 2px; + background-color: #cfe8ef; + color: rgb(12, 12, 12); + padding-left: 4px; + padding-top: 2px; + padding-bottom: 2px; + overflow: hidden; +} + +::deep .scene-button:hover { + cursor: pointer; + background-color: #8edeee; +} + +.dropdown-header { + font-weight: bold; +} + +.dropdown-header:hover { + cursor: pointer; +} + +.dropdown-body { + column-count: auto; + column-width: 12rem; + margin-left: 8px; + border-left-width: 8px; + border-left-color: #208dab; + border-left-style: solid; + background-color: #cfe8ef; +} + +.scene-picker { + background-color: #e6f1f4; + padding: 8px; + + border-style: solid; + border-width: 1pt; + border-color: #2b3f44; +} + +.btn { + background-color: #a4e1f2; + border-style: none; + /* border-width: 2px; + border-color: #245e6f; */ + color: black; + font-size: medium; + padding-left: 8px; + padding-right: 8px; + padding-bottom: 4px; + padding-top: 4px; +} + +.btn:hover { + background-color: #c9eff4; + cursor: pointer; +} + +.btn:disabled { + background-color: #e5f1f5; + color: #96b4bd; + border-color: #96b4bd; +} \ No newline at end of file diff --git a/Examples/BlenderSync/Pages/CursorTracker.razor b/Examples/BlenderSync/Pages/CursorTracker.razor new file mode 100644 index 00000000..91bce976 --- /dev/null +++ b/Examples/BlenderSync/Pages/CursorTracker.razor @@ -0,0 +1,44 @@ +@page "/CursorTracker" +@using SeeSharp.Blender +@inject SeeSharp.Blender.CursorTrackerClient Client +@inject SeeSharp.Blender.BlenderCommandSender Commander +@inject SeeSharp.Blender.BlenderEventListener Listener +

Blender Live Data

+ +@if (_data is null) +{ +

Waiting for Blender...

+} +else +{ +
+ @if (_data.Hit_Position != null) + { +

Cursor: @Format(_data.Cursor_Position)

+

Object: @_data.Object

+

Hit Position: @Format(_data.Hit_Position)

+

Normal: @Format(_data.Normal)

+

Face Index: @_data.Face_Index

+ } + else + { +

No object hit

+ } +
+} + +@code { + private BlenderCursorData? _data; + protected override void OnInitialized() + { + Client._cursor_tracked.OnCursorTracked += data => + { + Console.WriteLine("RAW JSON: " + JsonSerializer.Serialize(data)); + _data = data; + InvokeAsync(StateHasChanged); + }; + } + + string Format(float[]? arr) + => arr == null ? "null" : string.Join(", ", arr); +} \ No newline at end of file diff --git a/Examples/BlenderSync/Pages/Experiment.razor b/Examples/BlenderSync/Pages/Experiment.razor new file mode 100644 index 00000000..76543c49 --- /dev/null +++ b/Examples/BlenderSync/Pages/Experiment.razor @@ -0,0 +1,97 @@ +@using SeeSharp.Experiments +@using SeeSharp +@using SeeSharp.Blazor + +@inject IJSRuntime JS + +@page "/Experiment" + +

Example experiment

+ +
+
+ +
+ +
+ +
+
+ @if (readyToRun) + { +

+ } + + + +
+ + @if (!running) + { + @if (resultsAvailable) + { +
+ + + @if (selected.HasValue && selected.Value) + { + + + + + +
Mesh@(selected.Value.Mesh.Name)
Material@(selected.Value.Mesh.Material.Name) (roughness: @(selected.Value.Mesh.Material.GetRoughness(selected.Value)), transmissive: @(selected.Value.Mesh.Material.IsTransmissive(selected.Value)))
Distance@(selected.Value.Distance)
Position@(selected.Value.Position)
+ } + +
+ } + } + else + { +

Rendering...

+ } +
+ + + +@code { + SceneSelector sceneSelector; + Scene scene; + bool readyToRun = false; + bool running = false; + bool sceneJustLoaded = false; + bool resultsAvailable = false; + ElementReference runButton; + + SimpleImageIO.FlipBook flip; + + async Task OnSceneLoaded(SceneFromFile sceneFromFile) + { + await Task.Run(() => scene = sceneFromFile.MakeScene()); + flip = null; + resultsAvailable = false; + readyToRun = true; + sceneJustLoaded = true; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (readyToRun && sceneJustLoaded) + { + await runButton.FocusAsync(); + } + + sceneJustLoaded = false; + } + + async Task OnRunClick() + { + readyToRun = false; + resultsAvailable = false; + running = true; + await Task.Run(() => RunExperiment()); + readyToRun = true; + running = false; + resultsAvailable = true; + } +} \ No newline at end of file diff --git a/Examples/BlenderSync/Pages/Experiment.razor.cs b/Examples/BlenderSync/Pages/Experiment.razor.cs new file mode 100644 index 00000000..2ee7003f --- /dev/null +++ b/Examples/BlenderSync/Pages/Experiment.razor.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Components; + +namespace BlenderSync.Pages; + +public partial class Experiment : ComponentBase +{ + const int Width = 1280; + const int Height = 720; + const int MaxDepth = 10; + + int NumSamples = 2; + + long renderTimePT, renderTimeVCM; + + void RunExperiment() + { + flip = new FlipBook(660, 580) + .SetZoom(FlipBook.InitialZoom.FillWidth) + .SetToneMapper(FlipBook.InitialTMO.Exposure(scene.RecommendedExposure)) + .SetToolVisibility(false); + + scene.FrameBuffer = new(Width, Height, ""); + scene.Prepare(); + + PathTracer pathTracer = new() + { + TotalSpp = NumSamples, + MaxDepth = MaxDepth, + }; + pathTracer.Render(scene); + flip.Add($"PT", scene.FrameBuffer.Image); + renderTimePT = scene.FrameBuffer.RenderTimeMs; + + scene.FrameBuffer = new(Width, Height, ""); + VertexConnectionAndMerging vcm = new() + { + NumIterations = NumSamples, + MaxDepth = MaxDepth, + }; + vcm.Render(scene); + flip.Add($"VCM", scene.FrameBuffer.Image); + renderTimeVCM = scene.FrameBuffer.RenderTimeMs; + } + + SurfacePoint? selected; + + void OnFlipClick(FlipViewer.OnClickEventArgs args) + { + if (args.CtrlKey) + { + selected = scene.RayCast(new(args.X, args.Y)); + } + } + + async Task OnDownloadClick() + { + HtmlReport report = new(); + report.AddMarkdown(""" + # Example experiment + $$ L_\mathrm{o} = \int_\Omega L_\mathrm{i} f_\mathrm{r} |\cos\theta_\mathrm{i}| \, d\omega_\mathrm{i} $$ + """); + report.AddFlipBook(flip); + await SeeSharp.Blazor.Scripts.DownloadAsFile(JS, "report.html", report.ToString()); + } +} \ No newline at end of file diff --git a/Examples/BlenderSync/Pages/Index.razor b/Examples/BlenderSync/Pages/Index.razor new file mode 100644 index 00000000..dcc10eb2 --- /dev/null +++ b/Examples/BlenderSync/Pages/Index.razor @@ -0,0 +1,39 @@ +@page "/" + +@using System.Reflection +@using System.Text.RegularExpressions + + +
+ +
+ + +@code { + /// Enumerates all .razor components in this folder + public IEnumerable<(string Name, string Url)> GetExperimentPages() + { + var routableComponents = Assembly + .GetExecutingAssembly() + .ExportedTypes + .Where(t => t.IsSubclassOf(typeof(ComponentBase))) + .Where(c => c + .GetCustomAttributes(inherit: true) + .OfType() + .Count() > 0); + + foreach (var routableComponent in routableComponents) + { + string name = routableComponent.ToString().Replace("BlenderSync.Pages.", string.Empty); + if (name != "Index") + yield return (name, name); + } + } +} diff --git a/Examples/BlenderSync/Pages/PathViewer.razor b/Examples/BlenderSync/Pages/PathViewer.razor new file mode 100644 index 00000000..ac32a78a --- /dev/null +++ b/Examples/BlenderSync/Pages/PathViewer.razor @@ -0,0 +1,236 @@ +@page "/PathViewer" +@inject SeeSharp.Blender.PathViewerClient Client +@inject SeeSharp.Blender.BlenderCommandSender Commander +@inject SeeSharp.Blender.BlenderEventListener Listener + +@using Markdig.Extensions.SelfPipeline +@using Microsoft.AspNetCore.Routing.Internal +@using SeeSharp.Experiments +@using SeeSharp +@using SeeSharp.Blazor +@using SeeSharp.Common; +@using SeeSharp.Integrators; +@using SeeSharp.Integrators.Bidir; +@using SeeSharp.Blender; +@inject IJSRuntime JS +

Render Scene

+ +
+
+ +
+ +
+ + + + +
    + @foreach (var path in paths) + { +
  • + @path + + +
  • + @if (path == SelectedId) + { + + } + } +
+ +@if (renderedImage != null) +{ + + +
+

Rendered Image

+ +
+} + +@code { + BlenderImporter sceneSelector; + private string renderedImage; + private string hitInfo; + + private List paths = new(); + private string? SelectedId = null; + public PathGraphNode? viewer_root = null; + public Dictionary? viewer_roots = new Dictionary(); + + Scene scene; + Integrator path_tracer = new PathTracer() + { + MaxDepth = 5, + TotalSpp = 1, + EnableDenoiser = false + }; + + Integrator bi_path_tracer = new CameraStoringVCM() + { + NumIterations = 10, + MaxDepth = 10, + EnableDenoiser = false + }; + Integrator integrator = new PathTracer() + { + MaxDepth = 5, + TotalSpp = 1, + EnableDenoiser = false + }; + + void OnModeChanged(ChangeEventArgs e) + { + var SelectedMode = e.Value?.ToString() ?? ""; + + // Update ModeExecutor based on SelectedMode + integrator = SelectedMode switch + { + "PathTracer" => path_tracer, + "Bidir" => bi_path_tracer, + _ => path_tracer + }; + } + PathGraph graph; + RgbColor estimate; + + async Task OnSceneLoaded(SceneFromFile sceneFromFile) + { + await Task.Run(() => scene = sceneFromFile.MakeScene()); + Console.WriteLine("About to load this scene: " + sceneSelector.FindJson(sceneSelector.sceneNameInput.Text)); + + paths.Clear(); + viewer_roots.Clear(); + + await Commander.SendCommandAsync(new + { + command = "import_scene", + scene_name = sceneSelector.FindJson(sceneSelector.sceneNameInput.Text) + }); + } + + async Task Render() + { + scene.FrameBuffer = new(1920, 1080, "../Data/Render.exr"); + scene.Prepare(); + path_tracer.Render(scene); + scene.FrameBuffer.Image.WriteToFile("./Data/Render.png"); + var bytes = File.ReadAllBytes("./Data/Render.png"); + renderedImage = $"data:image/png;base64,{Convert.ToBase64String(bytes)}"; + } + + protected override void OnInitialized() + { + Client._created.OnCreated += obj => + { + paths.Add(obj); + SelectedId = null; + InvokeAsync(StateHasChanged); + }; + Client._deleted.OnDeleted += obj => + { + paths.Remove(obj); + viewer_roots.Remove(obj); + InvokeAsync(StateHasChanged); + }; + Client._selected.OnSelected += obj => + { + SelectedId = obj; + InvokeAsync(StateHasChanged); + }; + } + + async Task OnImageClick(MouseEventArgs e) + { + var pt = await JS.InvokeAsync("getBlenderPixelCoords", + "renderImg", e.ClientX, e.ClientY); + + scene.FrameBuffer = new(1920, 1080, "../Data/Render.exr"); + scene.Prepare(); + (graph, estimate) = integrator.ReplayPixel(scene, new Pixel((int)pt.x, (int)pt.y), 0); + PathGraphNode node = graph.Roots[0]; + viewer_root = graph.Roots[0]; + string jsonGraph = System.Text.Json.JsonSerializer.Serialize(node, new JsonSerializerOptions() + { + IncludeFields = true, + }); + Console.WriteLine(jsonGraph); + string path_id = Guid.NewGuid().ToString(); + viewer_roots.Add(path_id, graph.Roots[0]); + await Commander.SendCommandAsync(new + { + command = "create_path", + id = path_id, + graph = jsonGraph + }); + } + + async Task DeletePath(string id) + { + await Commander.SendCommandAsync(new { command = "delete_path", id }); + } + + async Task SelectPath(string id) + { + await Commander.SendCommandAsync(new { command = "select_path", id }); + } + public class PixelPos { public float x { get; set; } public float y { get; set; } } + + async void OnNodeClicked(PathGraphNode node) + { + var node_list = new List(); + for (var n = node; n != null; n = n.Ancestor) + { + node_list.Add(n.Id); + } + + node_list.Reverse(); + bool is_full_graph = (node == viewer_roots[SelectedId]); + + await Commander.SendCommandAsync(new + { + command = "click_on_node", + path = JsonSerializer.Serialize(node_list, new + JsonSerializerOptions + { + WriteIndented = false + }), + path_id = SelectedId, + is_full_graph + }); + } + async void OnNodeDoubleClicked(PathGraphNode node) + { + var node_list = new List(); + for (var n = node; n != null; n = n.Ancestor) + { + node_list.Add(n.Id); + } + + node_list.Reverse(); + bool is_full_graph = (node == viewer_roots[SelectedId]); + await Commander.SendCommandAsync(new + { + command = "dbclick_on_node", + path = JsonSerializer.Serialize(node_list, new + JsonSerializerOptions + { + WriteIndented = false + }), + path_id = SelectedId, + is_full_graph + }); + } + string GetStyle(string id) + { + return id == SelectedId + ? "background-color: yellow; font-weight: bold;" + : ""; + } +} \ No newline at end of file diff --git a/Examples/BlenderSync/Pages/_Host.cshtml b/Examples/BlenderSync/Pages/_Host.cshtml new file mode 100644 index 00000000..98496a08 --- /dev/null +++ b/Examples/BlenderSync/Pages/_Host.cshtml @@ -0,0 +1,35 @@ +@page "/" +@using Microsoft.AspNetCore.Components.Web +@namespace BlenderSync.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + @Html.Raw(SeeSharp.Blazor.Scripts.AllScripts) + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + diff --git a/Examples/BlenderSync/Program.cs b/Examples/BlenderSync/Program.cs new file mode 100644 index 00000000..7cb53f2c --- /dev/null +++ b/Examples/BlenderSync/Program.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + +ProgressBar.Silent = true; +SceneRegistry.AddSourceRelativeToScript("../../Data/Scenes"); + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +var app = builder.Build(); + +var eventListener = app.Services.GetRequiredService(); +_ = eventListener.StartAsync(); + +if (!app.Environment.IsDevelopment()) +{ + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseStaticFiles(); + +app.UseRouting(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); \ No newline at end of file diff --git a/Examples/BlenderSync/Properties/launchSettings.json b/Examples/BlenderSync/Properties/launchSettings.json new file mode 100644 index 00000000..a99c6ef0 --- /dev/null +++ b/Examples/BlenderSync/Properties/launchSettings.json @@ -0,0 +1,35 @@ +{ + "iisSettings": { + "iisExpress": { + "applicationUrl": "http://localhost:18831", + "sslPort": 44326 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5229", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7055;http://localhost:5229", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Examples/BlenderSync/_Imports.razor b/Examples/BlenderSync/_Imports.razor new file mode 100644 index 00000000..7d8a1710 --- /dev/null +++ b/Examples/BlenderSync/_Imports.razor @@ -0,0 +1,6 @@ +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using BlenderSync + +@using SeeSharp.Blazor diff --git a/Examples/BlenderSync/appsettings.Development.json b/Examples/BlenderSync/appsettings.Development.json new file mode 100644 index 00000000..770d3e93 --- /dev/null +++ b/Examples/BlenderSync/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Examples/BlenderSync/appsettings.json b/Examples/BlenderSync/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Examples/BlenderSync/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Examples/BlenderSync/wwwroot/css/site.css b/Examples/BlenderSync/wwwroot/css/site.css new file mode 100644 index 00000000..ddc98cca --- /dev/null +++ b/Examples/BlenderSync/wwwroot/css/site.css @@ -0,0 +1,86 @@ +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 3.5rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +html { + font-family: system-ui; +} + +button { + background-color: #a4e1f2; + border-style: none; + /* border-width: 2px; + border-color: #245e6f; */ + color: black; + font-size: medium; + padding-left: 8px; + padding-right: 8px; + padding-bottom: 4px; + padding-top: 4px; +} + button:hover { + background-color: #c9eff4; + cursor: pointer; + } + button:disabled { + background-color: #e5f1f5; + color: #96b4bd; + border-color: #96b4bd; + } + +.experiment-settings { + display: flex; + gap: 0.25em; + flex-direction: column; + float: left; + margin-right: 1em; +} + +.experiment-results { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: flex-start; +} + +table { + border-collapse: collapse; +} +td, th { + border: none; + padding: 4px; +} +tr:hover { background-color: #e7f2f1; } +th { + padding-top: 6px; + padding-bottom: 6px; + text-align: left; + background-color: #4a96af; + color: white; + font-size: smaller; +} \ No newline at end of file diff --git a/Examples/BlenderSync/wwwroot/raycast.js b/Examples/BlenderSync/wwwroot/raycast.js new file mode 100644 index 00000000..2bd8ffc0 --- /dev/null +++ b/Examples/BlenderSync/wwwroot/raycast.js @@ -0,0 +1,23 @@ +window.getBlenderPixelCoords = function (imgId, clickX, clickY) { + const img = document.getElementById(imgId); + if (!img) return null; + + const rect = img.getBoundingClientRect(); + + // Pixel inside displayed image + const x_ui = clickX - rect.left; + const y_ui = clickY - rect.top; + + // Displayed size + const Wu = rect.width; + const Hu = rect.height; + + // Blender render resolution + const Wb = img.naturalWidth; + const Hb = img.naturalHeight; + + return { + x: x_ui / Wu * Wb, + y: y_ui / Hu * Hb + }; +}; \ No newline at end of file diff --git a/SeeSharp.Examples/MisCompensation.dib b/Examples/MisCompensation.dib similarity index 100% rename from SeeSharp.Examples/MisCompensation.dib rename to Examples/MisCompensation.dib diff --git a/SeeSharp.Examples/SphericalSampling.dib b/Examples/SphericalSampling.dib similarity index 100% rename from SeeSharp.Examples/SphericalSampling.dib rename to Examples/SphericalSampling.dib diff --git a/SeeSharp.Blazor/GraphBrowser.razor b/SeeSharp.Blazor/GraphBrowser.razor new file mode 100644 index 00000000..fd9bada3 --- /dev/null +++ b/SeeSharp.Blazor/GraphBrowser.razor @@ -0,0 +1,67 @@ +@using SeeSharp.Integrators.Util + + +@namespace SeeSharp.Blazor +@if (Node != null) +{ +
+ + @if (HasChildren) + { + @(expanded ? "▼" : "▶") + } + else + { + + } + + + + @Node.GetType().Name + +
+ + @if (expanded && HasChildren) + { +
+ @foreach (var child in Node.Successors) + { + + } +
+ } + +} + +@code { + [Parameter] public PathGraphNode Node { get; set; } = default!; + [Parameter] public Action OnClick { get; set; } = default!; + [Parameter] public Action OnDoubleClick { get; set; } = default!; + [Parameter] public bool ForceExpand { get; set; } = false; + bool expanded = false; + bool HasChildren => Node.Successors?.Count > 0; + void Toggle() => expanded = !expanded; + + bool AutoExpand => + Node is ConnectionNode || Node is MergeNode || ForceExpand; + + protected override void OnParametersSet() + { + if (ForceExpand) + { + expanded = true; + } + } + void Click() + { + OnClick?.Invoke(Node); + } + + void DoubleClick() + { + OnDoubleClick?.Invoke(Node); + } +} \ No newline at end of file diff --git a/SeeSharp.Blazor/GraphBrowser.razor.css b/SeeSharp.Blazor/GraphBrowser.razor.css new file mode 100644 index 00000000..6b647257 --- /dev/null +++ b/SeeSharp.Blazor/GraphBrowser.razor.css @@ -0,0 +1,26 @@ +.tree-node { + display: flex; + align-items: center; + font-family: monospace; +} + +.toggle { + width: 20px; + cursor: pointer; + user-select: none; +} + +.label { + cursor: pointer; + padding: 2px 4px; +} + +.label:hover { + background-color: #2d2d2d; +} + +.children { + margin-left: 20px; + border-left: 1px dotted #555; + padding-left: 6px; +} \ No newline at end of file diff --git a/SeeSharp.Blender/Addons/CursorTracker.cs b/SeeSharp.Blender/Addons/CursorTracker.cs new file mode 100644 index 00000000..9aee36ee --- /dev/null +++ b/SeeSharp.Blender/Addons/CursorTracker.cs @@ -0,0 +1,13 @@ +namespace SeeSharp.Blender +{ + public class CursorTrackerClient + { + public readonly CursorTrackedHandler _cursor_tracked; + public CursorTrackerClient(IEnumerable handlers) + { + _cursor_tracked = handlers + .OfType() + .Single(); // or First() + } + } +} diff --git a/SeeSharp.Blender/Addons/PathViewer.cs b/SeeSharp.Blender/Addons/PathViewer.cs new file mode 100644 index 00000000..75ca4397 --- /dev/null +++ b/SeeSharp.Blender/Addons/PathViewer.cs @@ -0,0 +1,22 @@ +namespace SeeSharp.Blender +{ + public class PathViewerClient + { + public readonly CreatedHandler _created; + public readonly DeletedHandler _deleted; + public readonly SelectedHandler _selected; + + public PathViewerClient(IEnumerable handlers) + { + _created = handlers + .OfType() + .Single(); // or First() + _deleted = handlers + .OfType() + .Single(); + _selected = handlers + .OfType() + .Single(); + } + } +} diff --git a/SeeSharp.Blender/BlenderCommandSender.cs b/SeeSharp.Blender/BlenderCommandSender.cs new file mode 100644 index 00000000..b432ce06 --- /dev/null +++ b/SeeSharp.Blender/BlenderCommandSender.cs @@ -0,0 +1,129 @@ +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +namespace SeeSharp.Blender +{ + public class BlenderCommandSender + { + private TcpClient _client; + private NetworkStream _stream; + private bool _connected = false; + + public async Task TryConnectAsync() + { + try + { + _client = new TcpClient(); + await _client.ConnectAsync("127.0.0.1", 5051); + _stream = _client.GetStream(); + _connected = true; + Console.WriteLine("✔ Connected to Blender cursor port"); + return true; + } + catch (Exception ex) + { + Console.WriteLine("❌ Blender not listening (connection failed): " + ex.Message); + _connected = false; + return false; + } + } + + public async Task SendCursorAsync(float x, float y, float z) + { + // Ensure connection exists + if (true) + { + bool ok = await TryConnectAsync(); + if (!ok) + { + // Do NOT crash — just gracefully fail + Console.WriteLine("⚠ Could not send cursor update, Blender not running"); + return; + } + } + + try + { + var data = new + { + cursor_position = new[] { x, y, z } + }; + + string json = JsonSerializer.Serialize(data) + "\n"; + byte[] bytes = Encoding.UTF8.GetBytes(json); + + await _stream.WriteAsync(bytes, 0, bytes.Length); + await _stream.FlushAsync(); + + Console.WriteLine("➡ Sent cursor update to Blender: " + json); + } + catch (Exception ex) + { + Console.WriteLine("❌ Error sending cursor data: " + ex.Message); + + // Force cleanup of dead connection + try { _stream?.Close(); } catch { } + try { _client?.Close(); } catch { } + + _stream = null; + _client = null; + _connected = false; + } + } + + public async Task SendCommandAsync(object cmd) + { + if (!_connected || !IsSocketConnected()) + { + _connected = false; + + Console.WriteLine("🔌 Lost connection — reconnecting…"); + bool ok = await TryConnectAsync(); + + if (!ok) + { + Console.WriteLine("⚠ Failed to reconnect"); + return; + } + } + try + { + string json = JsonSerializer.Serialize(cmd) + "\n"; + byte[] bytes = Encoding.UTF8.GetBytes(json); + + await _stream.WriteAsync(bytes, 0, bytes.Length); + await _stream.FlushAsync(); + + Console.WriteLine("➡ Sent command: " + json); + } + catch (Exception ex) + { + Console.WriteLine("❌ Error sending command: " + ex.Message); + _connected = false; + } + } + private bool IsSocketConnected() + { + try + { + if (_client == null || !_client.Connected) + return false; + + var s = _client.Client; + + // Check if the socket has been closed + bool readReady = s.Poll(0, SelectMode.SelectRead); + bool noBytes = (s.Available == 0); + + if (readReady && noBytes) + return false; + + return true; + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/SeeSharp.Blender/BlenderEventDispatcher.cs b/SeeSharp.Blender/BlenderEventDispatcher.cs new file mode 100644 index 00000000..dfb346b3 --- /dev/null +++ b/SeeSharp.Blender/BlenderEventDispatcher.cs @@ -0,0 +1,46 @@ +using System.Text.Json; + +namespace SeeSharp.Blender +{ + public interface IBlenderEventHandler +{ + string EventType { get; } + void Handle(JsonElement root); +} + +public class BlenderEventDispatcher +{ + private readonly Dictionary _handlers; + + public BlenderEventDispatcher(IEnumerable handlers) + { + _handlers = handlers.ToDictionary(h => h.EventType); + } + + public void Register(IBlenderEventHandler handler) + { + _handlers.Add(handler.EventType, handler); + } + + public void Unregister(IBlenderEventHandler handler) + { + _handlers.Remove(handler.EventType); + } + + public void Dispatch(JsonElement root) + { + if (!root.TryGetProperty("event", out var evtProp)) + return; + + var evt = evtProp.GetString(); + if (evt == null) + return; + + if (_handlers.TryGetValue(evt, out var handler)) + handler.Handle(root); + else + Console.WriteLine($"⚠️ Unknown Blender event: {evt}"); + } +} + +} \ No newline at end of file diff --git a/SeeSharp.Blender/BlenderEventListener.cs b/SeeSharp.Blender/BlenderEventListener.cs new file mode 100644 index 00000000..5f9ec2ec --- /dev/null +++ b/SeeSharp.Blender/BlenderEventListener.cs @@ -0,0 +1,66 @@ +using System.Net.Sockets; +using System.Net; +using System.Text.Json; +namespace SeeSharp.Blender +{ + public class BlenderEventListener +{ + private TcpListener? _listener; + private BlenderEventDispatcher _dispatcher; + + public BlenderEventListener(BlenderEventDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public void RegisterDispatcher(BlenderEventDispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public async Task StartAsync() + { + _listener = new TcpListener(IPAddress.Loopback, 5052); + _listener.Start(); + Console.WriteLine("📡 Listening for Blender events on 5052..."); + + // while (true) + // { + // var client = await _listener.AcceptTcpClientAsync(); + // _ = HandleClientAsync(client); + // } + _ = Task.Run(async () => + { + while (true) + { + var client = await _listener.AcceptTcpClientAsync(); + Console.WriteLine("🔌 Blender connected to Blazor Event Listener"); + + _ = HandleClientAsync(client); + } + }); + } + + private async Task HandleClientAsync(TcpClient client) + { + using var reader = new StreamReader(client.GetStream()); + + while (true) + { + var line = await reader.ReadLineAsync(); + if (line == null) break; + Console.WriteLine("📨 Received event: " + line); + try + { + using var doc = JsonDocument.Parse(line); + _dispatcher.Dispatch(doc.RootElement); + } + catch (Exception ex) + { + Console.WriteLine("❌ Parse error: " + ex.Message); + } + } + } +} + +} \ No newline at end of file diff --git a/SeeSharp.Blender/Handlers/CreatedHandler.cs b/SeeSharp.Blender/Handlers/CreatedHandler.cs new file mode 100644 index 00000000..261f4ec7 --- /dev/null +++ b/SeeSharp.Blender/Handlers/CreatedHandler.cs @@ -0,0 +1,13 @@ +using SeeSharp.Blender; +using System.Text.Json; +public class CreatedHandler : IBlenderEventHandler +{ + public string EventType => "created"; + + public event Action? OnCreated; + + public void Handle(JsonElement root) + { + OnCreated?.Invoke(root.GetProperty("id").GetString()); + } +} diff --git a/SeeSharp.Blender/Handlers/CursorTrackedHandler.cs b/SeeSharp.Blender/Handlers/CursorTrackedHandler.cs new file mode 100644 index 00000000..78824a5f --- /dev/null +++ b/SeeSharp.Blender/Handlers/CursorTrackedHandler.cs @@ -0,0 +1,38 @@ +using SeeSharp.Blender; +using System.Text.Json; +public class BlenderCursorData + { + // [JsonPropertyName("object")] + public string? Object { get; set; } + // [JsonPropertyName("cursor_position")] + public float[] Cursor_Position { get; set; } = Array.Empty(); + // [JsonPropertyName("hit_position")] + public float[]? Hit_Position { get; set; } + // [JsonPropertyName("face_index")] + public int? Face_Index { get; set; } + // [JsonPropertyName("normal")] + public float[]? Normal { get; set; } + } + +public class CursorTrackedHandler : IBlenderEventHandler +{ + public string EventType => "cursor_tracked"; + public event Action? OnCursorTracked; + + public void Handle(JsonElement root) + { + OnCursorTracked?.Invoke(new BlenderCursorData + { + Object = root.TryGetProperty("object", out var obj) ? + obj.GetString() : null, + Cursor_Position = + JsonSerializer.Deserialize(root.GetProperty("cursor_position")), + Hit_Position = root.TryGetProperty("hit_position", out var pos) ? + JsonSerializer.Deserialize(pos.GetRawText()) : null, + Face_Index = root.TryGetProperty("face_index", out var face) ? + face.GetInt32() : null, + Normal = root.TryGetProperty("normal", out var norm) ? + JsonSerializer.Deserialize(norm.GetRawText()) : null, + }); + } +} \ No newline at end of file diff --git a/SeeSharp.Blender/Handlers/DeletedHandler.cs b/SeeSharp.Blender/Handlers/DeletedHandler.cs new file mode 100644 index 00000000..0e419a72 --- /dev/null +++ b/SeeSharp.Blender/Handlers/DeletedHandler.cs @@ -0,0 +1,12 @@ +using SeeSharp.Blender; +using System.Text.Json; +public class DeletedHandler : IBlenderEventHandler +{ + public string EventType => "deleted"; + public event Action? OnDeleted; + + public void Handle(JsonElement root) + { + OnDeleted?.Invoke(root.GetProperty("id").GetString()); + } +} \ No newline at end of file diff --git a/SeeSharp.Blender/Handlers/SelectedHandler.cs b/SeeSharp.Blender/Handlers/SelectedHandler.cs new file mode 100644 index 00000000..06245874 --- /dev/null +++ b/SeeSharp.Blender/Handlers/SelectedHandler.cs @@ -0,0 +1,12 @@ +using SeeSharp.Blender; +using System.Text.Json; +public class SelectedHandler : IBlenderEventHandler +{ + public string EventType => "selected"; + public event Action? OnSelected; + + public void Handle(JsonElement root) + { + OnSelected?.Invoke(root.GetProperty("id").GetString()); + } +} \ No newline at end of file diff --git a/SeeSharp.Blender/SeeSharp.Blender.csproj b/SeeSharp.Blender/SeeSharp.Blender.csproj new file mode 100644 index 00000000..f773e381 --- /dev/null +++ b/SeeSharp.Blender/SeeSharp.Blender.csproj @@ -0,0 +1,12 @@ + + + + net9.0 + enable + enable + + + + + + \ No newline at end of file diff --git a/SeeSharp.Examples/MakeFigure.py b/SeeSharp.Examples/MakeFigure.py deleted file mode 100644 index c8d45cd5..00000000 --- a/SeeSharp.Examples/MakeFigure.py +++ /dev/null @@ -1,48 +0,0 @@ -import figuregen -from figuregen.util.image import Cropbox -from figuregen.util.templates import FullSizeWithCrops -import simpleimageio as sio -import sys, os - -def make_figure(dirname, method_names): - """ - Creates a simple overview figure using the FullSizeWithCrops template. - Assumes the given directory contains a reference image and subdirectories for each method: - - - Reference.exr - - method_names[0].exr - - method_names[1].exr - """ - names = ["Reference"] - names.extend(method_names) - return FullSizeWithCrops( - reference_image=sio.read(os.path.join(dirname, "Reference.exr")), - method_images=[ - sio.read(os.path.join(dirname, f"{name}.exr")) - for name in method_names - ], - method_names=names, - crops=[ - Cropbox(top=345, left=25, width=64, height=48, scale=4), - Cropbox(top=155, left=200, width=64, height=48, scale=4), - ] - ).figure - -if __name__ == "__main__": - result_dir = sys.argv[1] - - method_names = [] - for i in range(2, len(sys.argv)): - method_names.append(sys.argv[i]) - - # Find all scenes by enumerating the result directory - rows = [] - for path in os.listdir(result_dir): - if not os.path.isdir(os.path.join(result_dir, path)): - continue - try: - rows.extend(make_figure(os.path.join(result_dir, path), method_names)) - except: - print(f"skipping scene with invalid data: {path}") - - figuregen.figure(rows, 18, os.path.join(result_dir, "Overview.pdf")) \ No newline at end of file diff --git a/SeeSharp.Examples/PathVsVcm.cs b/SeeSharp.Examples/PathVsVcm.cs deleted file mode 100644 index d33ec990..00000000 --- a/SeeSharp.Examples/PathVsVcm.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SeeSharp.Experiments; -using SeeSharp.Integrators; -using SeeSharp.Integrators.Bidir; -using System.Collections.Generic; - -namespace SeeSharp.Examples; - -/// -/// Renders a scene with a path tracer and with VCM. -/// -class PathVsVcm : Experiment { - public override List MakeMethods() => [ - new("PathTracer", new PathTracer() { TotalSpp = 4 }), - new("Vcm", new VertexConnectionAndMerging() { NumIterations = 2 }) - ]; -} \ No newline at end of file diff --git a/SeeSharp.Examples/Program.cs b/SeeSharp.Examples/Program.cs deleted file mode 100644 index 928e49db..00000000 --- a/SeeSharp.Examples/Program.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Diagnostics; -using SeeSharp.Experiments; -using SeeSharp.Images; -using SeeSharp.Examples; - -// Register the directory as a scene file provider. -// Asides from the geometry, it is also used as a reference image cache. -SceneRegistry.AddSource("Data/Scenes"); - -// Configure a benchmark to compare path tracing and VCM on the CornellBox -// at 512x512 resolution. Display images in tev during rendering (localhost, default port) -Benchmark benchmark = new(new PathVsVcm(), [ - SceneRegistry.LoadScene("CornellBox", maxDepth: 5), - // SceneRegistry.LoadScene("CornellBox", maxDepth: 2).WithName("CornellBoxDirectIllum") -], "Results/PathVsVcm", 512, 512, FrameBuffer.Flags.SendToTev); - -// Render the images -benchmark.Run(); - -// Optional, but usually a good idea: assemble the rendering results in an overview -// figure using a Python script. -Process.Start("python", "./SeeSharp.Examples/MakeFigure.py Results/PathVsVcm PathTracer Vcm") - .WaitForExit(); - -// For our README file, we further convert the pdf to png with ImageMagick -Process.Start("magick", "-density 300 ./Results/PathVsVcm/Overview.pdf ExampleFigure.png") - .WaitForExit(); diff --git a/SeeSharp.Examples/SeeSharp.Examples.csproj b/SeeSharp.Examples/SeeSharp.Examples.csproj deleted file mode 100644 index 1bebeb4c..00000000 --- a/SeeSharp.Examples/SeeSharp.Examples.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - Exe - net9.0 - false - - - diff --git a/SeeSharp.sln b/SeeSharp.sln index c0161e52..b1687443 100644 --- a/SeeSharp.sln +++ b/SeeSharp.sln @@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.IntegrationTests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.Benchmark", "SeeSharp.Benchmark\SeeSharp.Benchmark.csproj", "{957383F5-B5A0-4CA5-A283-0F2154FFC96A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.Examples", "SeeSharp.Examples\SeeSharp.Examples.csproj", "{5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.PreviewRender", "SeeSharp.PreviewRender\SeeSharp.PreviewRender.csproj", "{93EBE289-351C-4F29-95CA-B559F3675031}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.ToMitsuba", "SeeSharp.ToMitsuba\SeeSharp.ToMitsuba.csproj", "{83FE3967-6070-4BF3-8861-6410B877CB45}" @@ -25,6 +23,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.Blazor", "SeeSharp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaterialTest", "MaterialTest\MaterialTest.csproj", "{5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.Blender", "SeeSharp.Blender\SeeSharp.Blender.csproj", "{BB86935A-DF0E-4B98-9823-41A15F85BD7D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlenderSync", "Examples\BlenderSync\BlenderSync.csproj", "{B7B4BA23-171F-49F2-B711-654C3D8F2A18}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,9 +38,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {3B8C0540-28DA-4071-A3B1-03391D07630F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B8C0540-28DA-4071-A3B1-03391D07630F}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -98,18 +99,6 @@ Global {957383F5-B5A0-4CA5-A283-0F2154FFC96A}.Release|x64.Build.0 = Release|Any CPU {957383F5-B5A0-4CA5-A283-0F2154FFC96A}.Release|x86.ActiveCfg = Release|Any CPU {957383F5-B5A0-4CA5-A283-0F2154FFC96A}.Release|x86.Build.0 = Release|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Debug|x64.ActiveCfg = Debug|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Debug|x64.Build.0 = Debug|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Debug|x86.ActiveCfg = Debug|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Debug|x86.Build.0 = Debug|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Release|Any CPU.Build.0 = Release|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Release|x64.ActiveCfg = Release|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Release|x64.Build.0 = Release|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Release|x86.ActiveCfg = Release|Any CPU - {5FB3E5AD-2DB5-4A84-9E03-268AF95C5C6D}.Release|x86.Build.0 = Release|Any CPU {93EBE289-351C-4F29-95CA-B559F3675031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93EBE289-351C-4F29-95CA-B559F3675031}.Debug|Any CPU.Build.0 = Debug|Any CPU {93EBE289-351C-4F29-95CA-B559F3675031}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -170,5 +159,35 @@ Global {5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}.Release|x64.Build.0 = Release|Any CPU {5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}.Release|x86.ActiveCfg = Release|Any CPU {5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}.Release|x86.Build.0 = Release|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Debug|x64.ActiveCfg = Debug|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Debug|x64.Build.0 = Debug|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Debug|x86.Build.0 = Debug|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Release|Any CPU.Build.0 = Release|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Release|x64.ActiveCfg = Release|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Release|x64.Build.0 = Release|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Release|x86.ActiveCfg = Release|Any CPU + {BB86935A-DF0E-4B98-9823-41A15F85BD7D}.Release|x86.Build.0 = Release|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Debug|x64.ActiveCfg = Debug|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Debug|x64.Build.0 = Debug|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Debug|x86.ActiveCfg = Debug|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Debug|x86.Build.0 = Debug|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Release|Any CPU.Build.0 = Release|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Release|x64.ActiveCfg = Release|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Release|x64.Build.0 = Release|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Release|x86.ActiveCfg = Release|Any CPU + {B7B4BA23-171F-49F2-B711-654C3D8F2A18}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B7B4BA23-171F-49F2-B711-654C3D8F2A18} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F} EndGlobalSection EndGlobal diff --git a/SeeSharp/Integrators/Util/PathGraph.cs b/SeeSharp/Integrators/Util/PathGraph.cs index dce389af..824b4d10 100644 --- a/SeeSharp/Integrators/Util/PathGraph.cs +++ b/SeeSharp/Integrators/Util/PathGraph.cs @@ -1,150 +1,6 @@ using System.Linq; namespace SeeSharp.Integrators.Util; - -public class PathGraphNode(Vector3 pos, PathGraphNode ancestor = null) { - public Vector3 Position = pos; - public PathGraphNode Ancestor = ancestor; - public List Successors = []; - - public virtual bool IsBackground => false; - - public PathGraphNode AddSuccessor(PathGraphNode vertex) { - Successors.Add(vertex); - vertex.Ancestor = this; - return vertex; - } - - public virtual RgbColor ComputeVisualizerColor() { - return RgbColor.Black; - } - - public PathGraphNode Clone() { - var result = MemberwiseClone() as PathGraphNode; - result.Successors = []; - foreach (var s in Successors) { - var sClone = s.Clone(); - sClone.Ancestor = result; - result.Successors.Add(sClone); - } - return result; - } -} - -public interface IContribNode { - public RgbColor Contrib { get; } - public float MISWeight { get; } -} - -public class NextEventNode : PathGraphNode, IContribNode { - public NextEventNode(Vector3 direction, PathGraphNode ancestor, RgbColor emission, float pdf, - RgbColor bsdfCos, float misWeight, RgbColor prefixWeight) - : base(ancestor.Position + direction, ancestor) { - Emission = emission; - Pdf = pdf; - BsdfTimesCosine = bsdfCos; - MISWeight = misWeight; - PrefixWeight = prefixWeight; - } - - public NextEventNode(SurfacePoint point, RgbColor emission, float pdf, RgbColor bsdfCos, float misWeight, RgbColor prefixWeight) - : base(point.Position) { - Emission = emission; - Pdf = pdf; - BsdfTimesCosine = bsdfCos; - MISWeight = misWeight; - Point = point; - PrefixWeight = prefixWeight; - } - - public readonly RgbColor Emission; - public readonly float Pdf; - public readonly RgbColor BsdfTimesCosine; - public readonly float MISWeight; - public readonly SurfacePoint? Point; - public readonly RgbColor PrefixWeight; - - public override bool IsBackground => !Point.HasValue; - - public RgbColor Contrib => PrefixWeight * Emission / Pdf * BsdfTimesCosine; - - float IContribNode.MISWeight => MISWeight; - - public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(170, 231, 232); -} - -public class BSDFSampleNode : PathGraphNode { - public BSDFSampleNode(SurfacePoint point, RgbColor scatterWeight, float survivalProb) : base(point.Position) { - ScatterWeight = scatterWeight; - SurvivalProbability = survivalProb; - Point = point; - } - - public BSDFSampleNode(SurfacePoint point, RgbColor scatterWeight, float survivalProb, RgbColor emission, float misWeight) : base(point.Position) { - ScatterWeight = scatterWeight; - SurvivalProbability = survivalProb; - Emission = emission; - MISWeight = misWeight; - Point = point; - } - - public readonly RgbColor ScatterWeight; - public readonly float SurvivalProbability; - public readonly RgbColor Emission; - public readonly float MISWeight; - public readonly SurfacePoint Point; - - public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(41, 107, 177); -} - -public class LightPathNode(PathVertex lightVertex) : PathGraphNode(lightVertex.Point.Position) { - public readonly PathVertex LightVertex = lightVertex; - - public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(228, 135, 17); -} - -public class ConnectionNode : PathGraphNode, IContribNode { - public ConnectionNode(PathVertex lightVertex, float misWeight, RgbColor contrib) - : base(lightVertex.Point.Position) { - Contrib = contrib; - MISWeight = misWeight; - LightVertex = lightVertex; - } - - public RgbColor Contrib { get; init; } - public float MISWeight { get; init; } - public readonly PathVertex LightVertex; - - public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(167, 214, 170); -} - -public class MergeNode : PathGraphNode, IContribNode { - public MergeNode(PathVertex lightVertex, float misWeight, RgbColor contrib) - : base(lightVertex.Point.Position) { - Contrib = contrib; - MISWeight = misWeight; - LightVertex = lightVertex; - } - - public RgbColor Contrib { get; init; } - public float MISWeight { get; init; } - public readonly PathVertex LightVertex; - - public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(218, 152, 204); -} - -public class BackgroundNode : PathGraphNode { - public BackgroundNode(Vector3 direction, PathGraphNode ancestor, RgbColor contrib, float misWeight) : base(ancestor.Position + direction) { - Emission = contrib; - MISWeight = misWeight; - } - public readonly RgbColor Emission; - public readonly float MISWeight; - public override bool IsBackground => true; - - public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(170, 231, 232); -} - public class PathGraph { public List Roots = []; diff --git a/SeeSharp/Integrators/Util/PathGraph/BSDFSampleNode.cs b/SeeSharp/Integrators/Util/PathGraph/BSDFSampleNode.cs new file mode 100644 index 00000000..f4e219c9 --- /dev/null +++ b/SeeSharp/Integrators/Util/PathGraph/BSDFSampleNode.cs @@ -0,0 +1,25 @@ +namespace SeeSharp.Integrators.Util; + +public class BSDFSampleNode : PathGraphNode { + public BSDFSampleNode(SurfacePoint point, RgbColor scatterWeight, float survivalProb) : base(point.Position) { + ScatterWeight = scatterWeight; + SurvivalProbability = survivalProb; + Point = point; + } + + public BSDFSampleNode(SurfacePoint point, RgbColor scatterWeight, float survivalProb, RgbColor emission, float misWeight) : base(point.Position) { + ScatterWeight = scatterWeight; + SurvivalProbability = survivalProb; + Emission = emission; + MISWeight = misWeight; + Point = point; + } + + public readonly RgbColor ScatterWeight; + public readonly float SurvivalProbability; + public readonly RgbColor Emission; + public readonly float MISWeight; + public SurfacePoint Point { get; } + + public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(41, 107, 177); +} diff --git a/SeeSharp/Integrators/Util/PathGraph/BackgroundNode.cs b/SeeSharp/Integrators/Util/PathGraph/BackgroundNode.cs new file mode 100644 index 00000000..a8a1bdfe --- /dev/null +++ b/SeeSharp/Integrators/Util/PathGraph/BackgroundNode.cs @@ -0,0 +1,13 @@ +namespace SeeSharp.Integrators.Util; + +public class BackgroundNode : PathGraphNode { + public BackgroundNode(Vector3 direction, PathGraphNode ancestor, RgbColor contrib, float misWeight) : base(ancestor.Position + direction) { + Emission = contrib; + MISWeight = misWeight; + } + public readonly RgbColor Emission; + public readonly float MISWeight; + public override bool IsBackground => true; + + public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(170, 231, 232); +} \ No newline at end of file diff --git a/SeeSharp/Integrators/Util/PathGraph/ConnectionNode.cs b/SeeSharp/Integrators/Util/PathGraph/ConnectionNode.cs new file mode 100644 index 00000000..5e1de10a --- /dev/null +++ b/SeeSharp/Integrators/Util/PathGraph/ConnectionNode.cs @@ -0,0 +1,16 @@ +namespace SeeSharp.Integrators.Util; + +public class ConnectionNode : PathGraphNode, IContribNode { + public ConnectionNode(PathVertex lightVertex, float misWeight, RgbColor contrib) + : base(lightVertex.Point.Position) { + Contrib = contrib; + MISWeight = misWeight; + LightVertex = lightVertex; + } + + public RgbColor Contrib { get; init; } + public float MISWeight { get; init; } + public PathVertex LightVertex { get; } + + public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(167, 214, 170); +} \ No newline at end of file diff --git a/SeeSharp/Integrators/Util/PathGraph/LightPathNode.cs b/SeeSharp/Integrators/Util/PathGraph/LightPathNode.cs new file mode 100644 index 00000000..8cb19022 --- /dev/null +++ b/SeeSharp/Integrators/Util/PathGraph/LightPathNode.cs @@ -0,0 +1,7 @@ +namespace SeeSharp.Integrators.Util; + +public class LightPathNode(PathVertex lightVertex) : PathGraphNode(lightVertex.Point.Position) { + public PathVertex LightVertex { get; } = lightVertex; + + public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(228, 135, 17); +} \ No newline at end of file diff --git a/SeeSharp/Integrators/Util/PathGraph/MergeNode.cs b/SeeSharp/Integrators/Util/PathGraph/MergeNode.cs new file mode 100644 index 00000000..cf12e04a --- /dev/null +++ b/SeeSharp/Integrators/Util/PathGraph/MergeNode.cs @@ -0,0 +1,16 @@ +namespace SeeSharp.Integrators.Util; + +public class MergeNode : PathGraphNode, IContribNode { + public MergeNode(PathVertex lightVertex, float misWeight, RgbColor contrib) + : base(lightVertex.Point.Position) { + Contrib = contrib; + MISWeight = misWeight; + LightVertex = lightVertex; + } + + public RgbColor Contrib { get; init; } + public float MISWeight { get; init; } + public PathVertex LightVertex { get; } + + public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(218, 152, 204); +} \ No newline at end of file diff --git a/SeeSharp/Integrators/Util/PathGraph/NextEventNode.cs b/SeeSharp/Integrators/Util/PathGraph/NextEventNode.cs new file mode 100644 index 00000000..aa7dace3 --- /dev/null +++ b/SeeSharp/Integrators/Util/PathGraph/NextEventNode.cs @@ -0,0 +1,37 @@ +namespace SeeSharp.Integrators.Util; +public class NextEventNode : PathGraphNode, IContribNode { + public NextEventNode(Vector3 direction, PathGraphNode ancestor, RgbColor emission, float pdf, + RgbColor bsdfCos, float misWeight, RgbColor prefixWeight) + : base(ancestor.Position + direction, ancestor) { + Emission = emission; + Pdf = pdf; + BsdfTimesCosine = bsdfCos; + MISWeight = misWeight; + PrefixWeight = prefixWeight; + } + + public NextEventNode(SurfacePoint point, RgbColor emission, float pdf, RgbColor bsdfCos, float misWeight, RgbColor prefixWeight) + : base(point.Position) { + Emission = emission; + Pdf = pdf; + BsdfTimesCosine = bsdfCos; + MISWeight = misWeight; + Point = point; + PrefixWeight = prefixWeight; + } + + public readonly RgbColor Emission; + public readonly float Pdf; + public readonly RgbColor BsdfTimesCosine; + public readonly float MISWeight; + public readonly SurfacePoint? Point; + public readonly RgbColor PrefixWeight; + + public override bool IsBackground => !Point.HasValue; + + public RgbColor Contrib => PrefixWeight * Emission / Pdf * BsdfTimesCosine; + + float IContribNode.MISWeight => MISWeight; + + public override RgbColor ComputeVisualizerColor() => RgbColor.SrgbToLinear(170, 231, 232); +} diff --git a/SeeSharp/Integrators/Util/PathGraph/PathGraphNode.cs b/SeeSharp/Integrators/Util/PathGraph/PathGraphNode.cs new file mode 100644 index 00000000..53ccfb2f --- /dev/null +++ b/SeeSharp/Integrators/Util/PathGraph/PathGraphNode.cs @@ -0,0 +1,47 @@ +namespace SeeSharp.Integrators.Util; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")] +[JsonDerivedType(typeof(PathGraphNode), "PathGraphNode")] +[JsonDerivedType(typeof(NextEventNode), "NextEventNode")] +[JsonDerivedType(typeof(BSDFSampleNode), "BSDFSampleNode")] +[JsonDerivedType(typeof(LightPathNode), "LightPathNode")] +[JsonDerivedType(typeof(ConnectionNode), "ConnectionNode")] +[JsonDerivedType(typeof(MergeNode), "MergeNode")] +[JsonDerivedType(typeof(BackgroundNode), "BackgroundNode")] +public class PathGraphNode(Vector3 pos, PathGraphNode ancestor = null) { + public string Id { get; init; } = Guid.NewGuid().ToString("N")[..30]; + public Vector3 Position { get; set; } = pos; + [JsonIgnore] public PathGraphNode Ancestor = ancestor; + + [JsonPropertyName("ancestorId")] + public string? AncestorId => Ancestor?.Id; + public List Successors = []; + + public virtual bool IsBackground => false; + + public PathGraphNode AddSuccessor(PathGraphNode vertex) { + Successors.Add(vertex); + vertex.Ancestor = this; + return vertex; + } + + public virtual RgbColor ComputeVisualizerColor() { + return RgbColor.Black; + } + + public PathGraphNode Clone() { + var result = MemberwiseClone() as PathGraphNode; + result.Successors = []; + foreach (var s in Successors) { + var sClone = s.Clone(); + sClone.Ancestor = result; + result.Successors.Add(sClone); + } + return result; + } +} + +public interface IContribNode { + public RgbColor Contrib { get; } + public float MISWeight { get; } +} \ No newline at end of file diff --git a/justfile b/justfile index eebeff0b..75875147 100644 --- a/justfile +++ b/justfile @@ -14,7 +14,7 @@ _blender_binaries: # Builds the Blender add-on .zip [working-directory: "./BlenderExtension/see_blender/"] blender: _build_dotnet _blender_binaries - blender --command extension build --output-dir .. + blender --factory-startup --command extension build --output-dir .. @echo "" @echo "Blender plugin built. Open Blender and go to 'Edit - Preferences - Addons - Install from Disk' (dropdown menu in the top-right corner)" @echo "Browse to the 'BlenderExtension/see_sharp_renderer-VERSION.zip' file in this directory and install it."