Skip to content

Commit 4866a3f

Browse files
committed
Initial lightmapdata import
1 parent 4b2ba7e commit 4866a3f

4 files changed

Lines changed: 163 additions & 7 deletions

File tree

io_scene_a3d/BattleMapBlenderImporter.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from .A3D import A3D
3232
from .A3DBlenderImporter import A3DBlenderImporter
33-
from .BlenderMaterialUtils import addImageTextureToMaterial
33+
from .BlenderMaterialUtils import addImageTextureToMaterial, decodeIntColorToTuple
3434

3535
class Prop:
3636
def __init__(self):
@@ -150,8 +150,9 @@ class BattleMapBlenderImporter:
150150
# Allows subsequent map loads to be faster
151151
libraryCache = {}
152152

153-
def __init__(self, mapData, propLibrarySourcePath, import_static_geom=True, import_collision_geom=False, import_spawn_points=False):
153+
def __init__(self, mapData, lightmapData, propLibrarySourcePath, import_static_geom=True, import_collision_geom=False, import_spawn_points=False):
154154
self.mapData = mapData
155+
self.lightmapData = lightmapData
155156
self.propLibrarySourcePath = propLibrarySourcePath
156157
self.import_static_geom = import_static_geom
157158
self.import_collision_geom = import_collision_geom
@@ -211,6 +212,26 @@ def importData(self):
211212
for ob in spawnPointObjects:
212213
ob.parent = groupOB
213214

215+
# Create a sun light object
216+
li = bpy.data.lights.new("DirectionalLight", "SUN")
217+
li.color = decodeIntColorToTuple(self.lightmapData.lightColour)
218+
219+
ob = bpy.data.objects.new(li.name, li)
220+
ob.location = (0.0, 0.0, 1000.0) # Just place it like 10 meters off the ground (in alternativa units)
221+
lightAngleX, lightAngleZ = self.lightmapData.lightAngle
222+
ob.rotation_mode = "XYZ"
223+
ob.rotation_euler = (lightAngleX, 0.0, lightAngleZ)
224+
objects.append(ob)
225+
226+
# Set ambient world light
227+
scene = bpy.context.scene
228+
if scene.world == None:
229+
wd = bpy.data.worlds.new("map")
230+
scene.world = wd
231+
world = scene.world
232+
world.use_nodes = False
233+
world.color = decodeIntColorToTuple(self.lightmapData.ambientLightColour)
234+
214235
return objects
215236

216237
def getPropLibrary(self, libraryName):
@@ -253,6 +274,16 @@ def getBlenderProp(self, propData):
253274
propScale = (1.0, 1.0, 1.0)
254275
propOB.scale = propScale
255276

277+
# Lighting info
278+
lightingMapObject = None
279+
for mapObject in self.lightmapData.mapObjects:
280+
if mapObject.index == propData.ID:
281+
lightingMapObject = mapObject
282+
break
283+
if lightingMapObject != None:
284+
#XXX: do something with lightingMapObject.recieveShadows??
285+
propOB.visible_shadow = lightingMapObject.castShadows
286+
256287
# Material
257288
ma = self.materials[propData.materialID]
258289
if len(propOB.data.materials) != 0:

io_scene_a3d/BlenderMaterialUtils.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
SOFTWARE.
2121
'''
2222

23-
from bpy.types import ShaderNodeBsdfPrincipled
24-
2523
'''
2624
Functions
2725
'''
@@ -41,4 +39,13 @@ def addImageTextureToMaterial(image, node_tree, linkAlpha=False):
4139
links.new(textureNode.outputs["Alpha"], bsdfNode.inputs["Alpha"])
4240

4341
# Apply image
44-
if image != None: textureNode.image = image
42+
if image != None: textureNode.image = image
43+
44+
def decodeIntColorToTuple(intColor):
45+
# Fromat is argb
46+
a = (intColor >> 24) & 255
47+
r = (intColor >> 16) & 255
48+
g = (intColor >> 8) & 255
49+
b = intColor & 255
50+
51+
return (r/255, g/255, b/255)

io_scene_a3d/LightmapData.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'''
2+
Copyright (c) 2025 Pyogenics <https://github.com/Pyogenics>
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
'''
22+
23+
from .IOTools import unpackStream
24+
from . import AlternativaProtocol
25+
26+
class LightmapData:
27+
def __init__(self):
28+
self.lightColour = (0.0, 0.0, 0.0)
29+
self.ambientLightColour = (0.0, 0.0, 0.0)
30+
self.lightAngle = (0.0, 0.0) # (x, z)
31+
self.lightmaps = []
32+
self.mapObjects = []
33+
34+
def read(self, stream):
35+
print("Reading LightmapData")
36+
37+
# There is no signature so just start reading data and hope this is actually a lightmap data file
38+
version, = unpackStream("<I", stream)
39+
print(f"Reading LightmapData version {version}")
40+
41+
if version == 1:
42+
self.read1(stream)
43+
elif version == 2:
44+
self.read2(stream)
45+
else:
46+
raise RuntimeError(f"Unknown LightmapData version: {version}")
47+
48+
'''
49+
Version specific readers
50+
'''
51+
def read1(self, stream):
52+
raise RuntimeError("Version 1 LightmapData is not implemented yet")
53+
54+
def read2(self, stream):
55+
# Light info
56+
self.lightColour, self.ambientLightColour = unpackStream("<2I", stream)
57+
self.lightAngle = unpackStream("<2f", stream)
58+
59+
# Lightmaps
60+
lightmapCount, = unpackStream("<I", stream)
61+
print(f"Reading {lightmapCount} lightmaps")
62+
for _ in range(lightmapCount):
63+
lightmap = AlternativaProtocol.readString(stream)
64+
self.lightmaps.append(lightmap)
65+
66+
# Map objects
67+
mapObjectCount, = unpackStream("<I", stream)
68+
print(f"Reading {mapObjectCount} map objects")
69+
for _ in range(mapObjectCount):
70+
mapObject = MapObject()
71+
mapObject.read(stream)
72+
self.mapObjects.append(mapObject)
73+
74+
#XXX: there is more data but do we actually care about it?
75+
76+
print(f"[LightmapData2 lightColour: {hex(self.lightColour)} ambientLightColour: {hex(self.ambientLightColour)} lightAngle: {self.lightAngle}]")
77+
78+
'''
79+
Objects
80+
'''
81+
class MapObject:
82+
def __init__(self):
83+
self.index = 0
84+
self.lightmapIndex = 0
85+
self.lightmapScaleOffset = (0.0, 0.0, 0.0, 0.0)
86+
self.UV1 = []
87+
self.UV2 = []
88+
self.castShadows = False
89+
self.recieveShadows = False
90+
91+
def read(self, stream):
92+
self.index, self.lightmapIndex = unpackStream("<2i", stream)
93+
94+
# Read lightmap data
95+
if self.lightmapIndex >= 0:
96+
self.lightmapScaleOffset = unpackStream("<4f", stream)
97+
98+
# Check if we have UVs and read them
99+
hasUVs, = unpackStream("b", stream)
100+
if hasUVs > 0:
101+
vertexCount, = unpackStream("<I", stream)
102+
for _ in range(vertexCount//2):
103+
UV1 = unpackStream("<2f", stream)
104+
self.UV1.append(UV1)
105+
UV2 = unpackStream("<2f", stream)
106+
self.UV2.append(UV2)
107+
108+
# Light settings
109+
castShadows, recieveShadows = unpackStream("2b", stream)
110+
self.castShadows = castShadows > 0
111+
self.recieveShadows = recieveShadows > 0
112+
113+
print(f"[MapObject index: {self.index} lightmapIndex: {self.lightmapIndex} lightmapScaleOffset: {self.lightmapScaleOffset} UV1: {len(self.UV1)} UV2: {len(self.UV2)} castShadows: {self.castShadows} recieveShadows: {self.recieveShadows}]")

io_scene_a3d/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from .A3DBlenderImporter import A3DBlenderImporter
3030
from .BattleMap import BattleMap
3131
from .BattleMapBlenderImporter import BattleMapBlenderImporter
32+
from .LightmapData import LightmapData
3233

3334
from glob import glob
3435
from time import time
@@ -122,14 +123,18 @@ def execute(self, context):
122123
print(f"Reading BattleMap data from {self.filepath}")
123124

124125
importStartTime = time()
125-
126+
127+
lightmapData = LightmapData()
128+
with open(f"{self.directory}/lightmapdata", "rb") as file:
129+
lightmapData.read(file)
130+
126131
mapData = BattleMap()
127132
with open(self.filepath, "rb") as file:
128133
mapData.read(file)
129134

130135
# Import data into blender
131136
preferences = context.preferences.addons[__package__].preferences # TODO: check if this is set before proceeding
132-
mapImporter = BattleMapBlenderImporter(mapData, preferences.propLibrarySourcePath, self.import_static_geom, self.import_collision_geom, self.import_spawn_points)
137+
mapImporter = BattleMapBlenderImporter(mapData, lightmapData, preferences.propLibrarySourcePath, self.import_static_geom, self.import_collision_geom, self.import_spawn_points)
133138
objects = mapImporter.importData()
134139

135140
# Link objects

0 commit comments

Comments
 (0)