From 5ad0f9ff325881a08056559afc9546a7cfbf7dfe Mon Sep 17 00:00:00 2001 From: Matteo De Carlo Date: Sat, 6 Jan 2024 19:33:34 +0100 Subject: [PATCH] proper normals, uv and better init - correct normal reading from Blender, which respects per face smoothing setting - exporting UVs - engine initialization separated into a init function --- BlenderExporter/python_bindings.cpp | 49 ++++--- .../wicked_blender_exporter/__init__.py | 125 +++++++++++------- 2 files changed, 107 insertions(+), 67 deletions(-) diff --git a/BlenderExporter/python_bindings.cpp b/BlenderExporter/python_bindings.cpp index 668b4a868..577e3bce9 100644 --- a/BlenderExporter/python_bindings.cpp +++ b/BlenderExporter/python_bindings.cpp @@ -27,30 +27,45 @@ PYBIND11_MAKE_OPAQUE(wi::scene::TransformComponent); PYBIND11_MAKE_OPAQUE(wi::scene::ObjectComponent); static wi::Application app; +static sdl2::sdlsystem_ptr_t sdl_system { nullptr, nullptr }; +static sdl2::window_ptr_t sdl_window { nullptr, nullptr }; void init_wicked(py::module_& mod) { - //TODO we can probably initialize less stuff + static bool INIT_DONE = false; - sdl2::sdlsystem_ptr_t system = sdl2::make_sdlsystem(SDL_INIT_VIDEO); - if (!system) { - throw sdl2::SDLError("Error creating SDL2 system"); - } + mod.def("init", []() + { + if (INIT_DONE) return; + INIT_DONE = true; - sdl2::window_ptr_t window = sdl2::make_window("WickedBlenderExporter", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - 50, 50, - SDL_WINDOW_HIDDEN | SDL_WINDOW_VULKAN); - if (!window) { - throw sdl2::SDLError("Error creating window"); - } + //TODO we can probably initialize less stuff + sdl_system = sdl2::make_sdlsystem(SDL_INIT_VIDEO); - app.SetWindow(window.get()); - wi::renderer::SetShaderSourcePath(WICKED_ROOT_DIR"/WickedEngine/shaders/"); - wi::renderer::SetShaderPath(WICKED_ROOT_DIR"/build/BlenderExporter/shaders/"); + if (!sdl_system) { + throw sdl2::SDLError("Error creating SDL2 system"); + } - wi::initializer::InitializeComponentsImmediate(); + sdl_window = sdl2::make_window("WickedBlenderExporter", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + 50, 50, + SDL_WINDOW_HIDDEN | SDL_WINDOW_VULKAN); + if (!sdl_window) { + throw sdl2::SDLError("Error creating window"); + } + + app.SetWindow(sdl_window.get()); + wi::renderer::SetShaderSourcePath(WICKED_ROOT_DIR"/WickedEngine/shaders/"); + wi::renderer::SetShaderPath(WICKED_ROOT_DIR"/build/BlenderExporter/shaders/"); + + //wi::initializer::InitializeComponentsImmediate(); + app.Initialize(); + }); + + mod.def("deinit", [](){ + //TODO INIT_DONE = false; + }); } void export_primitives(py::module_& mod) diff --git a/BlenderExporter/wicked_blender_exporter/__init__.py b/BlenderExporter/wicked_blender_exporter/__init__.py index 84833d2e1..ec545164a 100644 --- a/BlenderExporter/wicked_blender_exporter/__init__.py +++ b/BlenderExporter/wicked_blender_exporter/__init__.py @@ -1,10 +1,17 @@ #!/usr/bin/env python3 + import bpy import os import ctypes from mathutils import Vector, Quaternion, Matrix +import os +import sys +importpath=os.path.dirname(os.path.realpath(__file__)) +sys.path.append(importpath) +import pywickedengine as wi + bl_info = { "name": "Wicked wiscene Extension", @@ -20,32 +27,31 @@ bl_info = { } -def WIVector3(blvector3: Vector): - import pywickedengine as wi +def WIVector3(blvector3: Vector) -> wi.XMFLOAT3: return wi.XMFLOAT3(blvector3[0], blvector3[2], blvector3[1]) -def WIQuaternion(q: Quaternion): - import pywickedengine as wi +def WIQuaternion(q: Quaternion) -> wi.XMFLOAT4: # [W X Y Z] -> [X Y Z W] -> [X Z Y -W] # q2=[q1.x,q1.z,q1.y,−q1.w] return wi.XMFLOAT4(q[1], q[3], q[2], -q[0]) -def wicked_add_mesh(item, scene, root): - print(f"Adding {item.name} mesh ...") - import pywickedengine as wi - entity = scene.Entity_CreateObject(item.name) +def wicked_add_mesh(obj, scene, root): + print(f"Adding {obj.name} mesh ...") + mesh = obj.data + entity = scene.Entity_CreateObject(obj.name) + print(f'{obj.name}={entity}') object = scene.objects().GetComponent(entity) scene.meshes().Create(entity) - #entity = scene.Entity_CreateMesh(item.name) + #entity = scene.Entity_CreateMesh(obj.name) #scene.Component_Attach(entity, root) #TODO this creates a self referencing entity, getting the loading screen stuck in an infintie while loop - mesh = scene.meshes().GetComponent(entity) + wimesh = scene.meshes().GetComponent(entity) transform = scene.transforms().GetComponent(entity) - transform.translation_local = WIVector3(item.location) - transform.scale_local = WIVector3(item.scale) - if item.rotation_mode == 'QUATERNION': - transform.rotation_local = WIQuaternion(item.rotation_quaternion) + transform.translation_local = WIVector3(obj.location) + transform.scale_local = WIVector3(obj.scale) + if obj.rotation_mode == 'QUATERNION': + transform.rotation_local = WIQuaternion(obj.rotation_quaternion) else: - transform.rotation_local = WIQuaternion(item.rotation_euler.to_matrix().to_quaternion()) + transform.rotation_local = WIQuaternion(obj.rotation_euler.to_matrix().to_quaternion()) transform.UpdateTransform() object.meshID = entity @@ -55,69 +61,87 @@ def wicked_add_mesh(item, scene, root): #vertices[i].co # position data #vertices[i].undeformed_co # position data - no deforming modifiers applied + for mat in mesh.materials: + pass + #TODO create a subset per material # create only one submesh for now - #TODO later create a submesh for each material (or similar) subset = wi.MeshComponent_MeshSubset() material = scene.materials().GetComponent(subset.materialID) - vertex_offset = len(mesh.vertex_positions) + vertex_offset = len(wimesh.vertex_positions) + + print("Generating vertex data") + mesh.calc_loop_triangles() + #After using mesh.calc_normals_split() you access the normals with loop.normal + mesh.calc_normals_split() + + vertices_map = {} + vertices_array = [] #index_remap = [0,1,-1] index_remap = [0,0,0] - # append vertices - print(f"Adding vertex data (len={len(item.data.vertices)})") - for vertex in item.data.vertices: - mesh.vertex_positions.append(WIVector3(vertex.co)) - mesh.vertex_normals.append(WIVector3(vertex.normal)) - mesh.vertex_uvset_0.append(wi.XMFLOAT2(0,0)) - - print("Adding index data") - for face in item.data.loop_triangles: - #print("Polygon index: %d, length: %d" % (face.index, 3)) + for triangle in mesh.loop_triangles: + face_index = mesh.loop_triangle_polygons[triangle.index].value + face = mesh.polygons[face_index] + #print(f'triangle[{triangle.index}]{triangle}={face_index} SmoothGroup={smooth_groups[face_index]}') offset=0 - for i in face.loops: - ix = item.data.loops[i + index_remap[offset]].vertex_index - mesh.indices.append(ix) - #print(" Vertex: %d %r" % (item.data.loops[i].vertex_index, item.data.vertices[ix].co)) - #print(" UV: %r" % item.data.uv_layers.active.data[i].uv) + for i in triangle.loops: + i += index_remap[offset] + ix = mesh.loops[i].vertex_index + normal = mesh.loops[i].normal + uv = mesh.uv_layers.active.data[i].uv + assert len(uv) == 2 + v = (ix, WIVector3(normal), wi.XMFLOAT2(uv[0], uv[1])) + if v in vertices_map: + newix = vertices_map[v] + else: + newix = len(vertices_array) + vertices_array.append(v) + vertices_map[v] = newix + wimesh.indices.append(newix) offset +=1 - #verts_in_face = face.vertices[:] - #print("face index ", face.index) - #print("normal ", face.normal) - #for vert in verts_in_face: - # print("vertex coords ", item.data.vertices[vert].co) - if len(mesh.vertex_normals) == 0 and len(mesh.vertex_positions) > 0: - for _ in range(len(mesh.vertex_positions)): - mesh.vertex_normals.append(wi.XMFLOAT3(0,0,0)) - mesh.ComputeNormals(wi.MeshComponentComputeNormals.SMOOTH_FAST); + print("Adding vertex data") + for i, normal, uv in vertices_array: + vertex = mesh.vertices[i] + wimesh.vertex_positions.append(WIVector3(vertex.co)) + wimesh.vertex_normals.append(normal) + wimesh.vertex_uvset_0.append(uv) + + if len(wimesh.vertex_normals) == 0 and len(wimesh.vertex_positions) > 0: + print("Generating missing normal data") + for _ in range(len(wimesh.vertex_positions)): + wimesh.vertex_normals.append(wi.XMFLOAT3(0,0,0)) + wimesh.ComputeNormals(wi.MeshComponentComputeNormals.SMOOTH_FAST); subset.materialID = scene.materials().GetEntity(0)#max(0, face.material)) subset.indexOffset = 0 - subset.indexCount = len(mesh.indices) - mesh.subsets.append(subset) + subset.indexCount = len(wimesh.indices) + wimesh.subsets.append(subset) - print(f"Finished adding {item.name} mesh") + print(f"Finished adding {obj.name} mesh") def create_wiscene(scene_name): - import pywickedengine as wi ### Load Scene scene = wi.Scene() rootEntity = wi.CreateEntity() + print(f'rootEntity({scene_name})={rootEntity}') scene.transforms().Create(rootEntity) scene.names().Create(rootEntity) scene.names().GetComponent(rootEntity).name = scene_name #TODO materials + for mat in bpy.data.materials: + pass # Meshes print("Exporting meshes") - for item in bpy.data.objects: - print(f"Element in scene {item.name}") - match item.type: + for obj in bpy.data.objects: + print(f"Element in scene {obj.name}") + match obj.type: case 'MESH': - wicked_add_mesh(item, scene, rootEntity) + wicked_add_mesh(obj, scene, rootEntity) print("Finished exporting meshes") #TODO armatures @@ -145,7 +169,6 @@ def create_wiscene(scene_name): def write_wiscene(context, filepath, dump_to_header: bool): print("running write_wiscene...") - import pywickedengine as wi filename = os.path.splitext(filepath)[0] scene_name = os.path.basename(filename) @@ -218,6 +241,7 @@ def menu_func_export(self, context): # Register and add to the "file selector" menu (required to use F3 search "Text Export Operator" for quick access). def register(): + wi.init() bpy.utils.register_class(ExportWiScene) bpy.types.TOPBAR_MT_file_export.append(menu_func_export) @@ -225,6 +249,7 @@ def register(): def unregister(): bpy.utils.unregister_class(ExportWiScene) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) + wi.deinit() if __name__ == "__main__":