From f07e9a58ee8714b5b8bbd6c97c3e4dfb362599cc Mon Sep 17 00:00:00 2001 From: Pyogenics Date: Sun, 30 Mar 2025 21:09:38 +0100 Subject: [PATCH] Import spawnpoints and some collision geometry --- io_scene_a3d/BattleMap.py | 23 ++++- io_scene_a3d/BattleMapBlenderImporter.py | 118 +++++++++++++++++++++-- io_scene_a3d/__init__.py | 23 ++++- 3 files changed, 149 insertions(+), 15 deletions(-) diff --git a/io_scene_a3d/BattleMap.py b/io_scene_a3d/BattleMap.py index de2288e..a5fb3dc 100644 --- a/io_scene_a3d/BattleMap.py +++ b/io_scene_a3d/BattleMap.py @@ -235,6 +235,25 @@ class Material: if optionalMask.getOptional(): self.vector4Parameters = AlternativaProtocol.readObjectArray(stream, Vector4Parameter, optionalMask) +#TODO: tanki has more than this number of spawn types now, investigate it +BATTLEMAP_SPAWNPOINTTYPE_DM = 0 +BATTLEMAP_SPAWNPOINTTYPE_DOM_TEAMA = 1 +BATTLEMAP_SPAWNPOINTTYPE_DOM_TEAMB = 2 +BATTLEMAP_SPAWNPOINTTYPE_RUGBY_TEAMA = 3 +BATTLEMAP_SPAWNPOINTTYPE_RUGBY_TEAMB = 4 +BATTLEMAP_SPAWNPOINTTYPE_TEAMA = 5 +BATTLEMAP_SPAWNPOINTTYPE_TEAMB = 6 +BATTLEMAP_SPAWNPOINTTYPE_UNKNOWN = 7 +BattleMapSpawnPointTypeName = { + BATTLEMAP_SPAWNPOINTTYPE_DM: "Deathmatch", + BATTLEMAP_SPAWNPOINTTYPE_DOM_TEAMA: "DominationTeamA", + BATTLEMAP_SPAWNPOINTTYPE_DOM_TEAMB: "DominationTeamB", + BATTLEMAP_SPAWNPOINTTYPE_RUGBY_TEAMA: "RugbyTeamA", + BATTLEMAP_SPAWNPOINTTYPE_RUGBY_TEAMB: "RugbyTeamB", + BATTLEMAP_SPAWNPOINTTYPE_TEAMA: "TeamA", + BATTLEMAP_SPAWNPOINTTYPE_TEAMB: "TeamB", + BATTLEMAP_SPAWNPOINTTYPE_UNKNOWN: "Unknown" +} class SpawnPoint: def __init__(self): self.position = (0.0, 0.0, 0.0) @@ -279,8 +298,8 @@ class BattleMap: def __init__(self): self.atlases = [] self.batches = [] - self.collisionGeometry = [] - self.collisionGeometryOutsideGamingZone = [] + self.collisionGeometry = None + self.collisionGeometryOutsideGamingZone = None self.materials = [] self.spawnPoints = [] self.staticGeometry = [] diff --git a/io_scene_a3d/BattleMapBlenderImporter.py b/io_scene_a3d/BattleMapBlenderImporter.py index 9148da4..c13a311 100644 --- a/io_scene_a3d/BattleMapBlenderImporter.py +++ b/io_scene_a3d/BattleMapBlenderImporter.py @@ -22,6 +22,8 @@ SOFTWARE. from json import load +import bpy + from .A3D import A3D from .A3DBlenderImporter import A3DBlenderImporter @@ -67,20 +69,61 @@ class BattleMapBlenderImporter: # Allows subsequent map loads to be faster libraryCache = {} - def __init__(self, mapData, propLibrarySourcePath): + def __init__(self, mapData, propLibrarySourcePath, import_static_geom=True, import_collision_geom=False, import_spawn_points=False): self.mapData = mapData self.propLibrarySourcePath = propLibrarySourcePath + self.import_static_geom = import_static_geom + self.import_collision_geom = import_collision_geom + self.import_spawn_points = import_spawn_points def importData(self): print("Importing BattleMap data into blender") - # Load props propObjects = [] - for propData in self.mapData.staticGeometry: - ob = self.getBlenderProp(propData) - propObjects.append(ob) - - return propObjects + if self.import_static_geom: + # Load props + for propData in self.mapData.staticGeometry: + ob = self.getBlenderProp(propData) + propObjects.append(ob) + collisionObjects = [] + if self.import_collision_geom: + # Load collision meshes + collisionTriangles = self.mapData.collisionGeometry.triangles + self.mapData.collisionGeometryOutsideGamingZone.triangles + collisionTriangleObjects = self.createBlenderCollisionTriangles(collisionTriangles) + collisionPlanes = self.mapData.collisionGeometry.planes + self.mapData.collisionGeometryOutsideGamingZone.planes + collisionPlaneObjects = self.createBlenderCollisionPlanes(collisionPlanes) + collisionBoxes = self.mapData.collisionGeometry.boxes + self.mapData.collisionGeometryOutsideGamingZone.boxes + collisionBoxObjects = self.createBlenderCollisionBoxes(collisionBoxes) + + collisionObjects += collisionTriangleObjects + collisionObjects += collisionPlaneObjects + collisionObjects += collisionBoxObjects + spawnPointObjects = [] + if self.import_spawn_points: + # Create spawn points + for spawnPointData in self.mapData.spawnPoints: + ob = self.createBlenderSpawnPoint(spawnPointData) + spawnPointObjects.append(ob) + + # Create empty objects to house each type of object + objects = propObjects + collisionObjects + spawnPointObjects + if self.import_static_geom: + groupOB = bpy.data.objects.new("StaticGeometry", None) + objects.append(groupOB) + for ob in propObjects: + ob.parent = groupOB + if self.import_collision_geom: + groupOB = bpy.data.objects.new("CollisionGeometry", None) + objects.append(groupOB) + for ob in collisionObjects: + ob.parent = groupOB + if self.import_spawn_points: + groupOB = bpy.data.objects.new("SpawnPoints", None) + objects.append(groupOB) + for ob in spawnPointObjects: + ob.parent = groupOB + + return objects def getBlenderProp(self, propData): # First check if we've already loaded the required prop library @@ -102,4 +145,63 @@ class BattleMapBlenderImporter: propOB.rotation_euler = propData.rotation propOB.scale = propData.scale - return propOB \ No newline at end of file + return propOB + + def createBlenderCollisionTriangles(self, collisionTriangles): + objects = [] + for collisionTriangle in collisionTriangles: + # Create the mesh + me = bpy.data.meshes.new("collisionTriangle") + + # Create array for coordinate data, blender doesn't like tuples + vertices = [] + vertices += collisionTriangle.v0 + vertices += collisionTriangle.v1 + vertices += collisionTriangle.v2 + + # Assign coordinates + me.vertices.add(3) + me.vertices.foreach_set("co", vertices) + me.loops.add(3) + me.loops.foreach_set("vertex_index", [0, 1, 2]) + me.polygons.add(1) + me.polygons.foreach_set("loop_start", [0]) + + me.validate() + me.update() + + # Create object + ob = bpy.data.objects.new("collisionTriangle", me) + ob.location = collisionTriangle.position + ob.rotation_mode = "XYZ" + ob.rotation_euler = collisionTriangle.rotation + #print(collisionTriangle.length) # XXX: how to handle collisionTriangle.length? + + objects.append(ob) + + return objects + + def createBlenderCollisionPlanes(self, collisionPlanes): + objects = [] + for collisionPlane in collisionPlanes: + pass + + return objects + + def createBlenderCollisionBoxes(self, collisionBoxes): + objects = [] + for collisionBox in collisionBoxes: + pass + + return objects + + def createBlenderSpawnPoint(self, spawnPointData): + #TODO: implement spawn type name lookup + ob = bpy.data.objects.new(f"SpawnPoint_{spawnPointData.type}", None) + ob.empty_display_type = "ARROWS" + ob.empty_display_size = 100 + ob.location = spawnPointData.position + ob.rotation_mode = "XYZ" + ob.rotation_euler = spawnPointData.rotation + + return ob \ No newline at end of file diff --git a/io_scene_a3d/__init__.py b/io_scene_a3d/__init__.py index 89a762e..c93de91 100644 --- a/io_scene_a3d/__init__.py +++ b/io_scene_a3d/__init__.py @@ -63,7 +63,7 @@ class ImportA3D(Operator, ImportHelper): reset_empty_transform: BoolProperty(name="Reset empty transforms", description="Reset rotation and scale if it is set to 0, more useful for version 2 models like props", default=True) def draw(self, context): - import_panel_options(self.layout, self) + import_panel_options_a3d(self.layout, self) def invoke(self, context, event): return ImportHelper.invoke(self, context, event) @@ -101,8 +101,13 @@ class ImportBattleMap(Operator, ImportHelper): filter_glob: StringProperty(default="*.bin", options={'HIDDEN'}) directory: StringProperty(subtype="DIR_PATH", options={'HIDDEN'}) + # User options + import_static_geom: BoolProperty(name="Import static geometry", description="Static geometry includes all the visual aspects of the map", default=True) + import_collision_geom: BoolProperty(name="Import collision geometry", description="Collision geometry defines the geometry used for collision checks and cannot normally be seen by players", default=False) + import_spawn_points: BoolProperty(name="Import spawn points", description="Places a marker at locations where tanks can spawn", default=False) + def draw(self, context): - pass + import_panel_options_battlemap(self.layout, self) def invoke(self, context, event): return ImportHelper.invoke(self, context, event) @@ -114,8 +119,8 @@ class ImportBattleMap(Operator, ImportHelper): mapData.read(file) # Import data into blender - preferences = context.preferences.addons[__package__].preferences - mapImporter = BattleMapBlenderImporter(mapData, preferences.propLibrarySourcePath) + preferences = context.preferences.addons[__package__].preferences # TODO: check if this is set before proceeding + mapImporter = BattleMapBlenderImporter(mapData, preferences.propLibrarySourcePath, self.import_static_geom, self.import_collision_geom, self.import_spawn_points) objects = mapImporter.importData() # Link objects @@ -128,7 +133,7 @@ class ImportBattleMap(Operator, ImportHelper): ''' Menu ''' -def import_panel_options(layout, operator): +def import_panel_options_a3d(layout, operator): header, body = layout.panel("alternativa_import_options", default_closed=False) header.label(text="Options") if body: @@ -136,6 +141,14 @@ def import_panel_options(layout, operator): body.prop(operator, "try_import_textures") body.prop(operator, "reset_empty_transform") +def import_panel_options_battlemap(layout, operator): + header, body = layout.panel("tanki_battlemap_import_options", default_closed=False) + header.label(text="Options") + if body: + body.prop(operator, "import_static_geom") + body.prop(operator, "import_collision_geom") + body.prop(operator, "import_spawn_points") + def menu_func_import_a3d(self, context): self.layout.operator(ImportA3D.bl_idname, text="Alternativa3D HTML5 (.a3d)")