Skip to content

Commit b8fe362

Browse files
authored
Merge map.bin support into main
Support for loading map.bin files
2 parents 46c8b0e + 1fc2856 commit b8fe362

9 files changed

Lines changed: 1162 additions & 25 deletions

images/demo7.png

2.07 MB
Loading

images/demo8.png

975 KB
Loading

io_scene_a3d/A3DBlenderImporter.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,7 @@
3232
A3D_VERTEXTYPE_COLOR,
3333
A3D_VERTEXTYPE_NORMAL2
3434
)
35-
36-
def addImageTextureToMaterial(image, node_tree):
37-
nodes = node_tree.nodes
38-
links = node_tree.links
39-
40-
# Check if this material already has a texture on it
41-
if len(nodes) > 2:
42-
return
43-
44-
# Create nodes
45-
principledBSDFNode = nodes[0]
46-
textureNode = nodes.new(type="ShaderNodeTexImage")
47-
links.new(textureNode.outputs["Color"], principledBSDFNode.inputs["Base Color"])
48-
# Apply image
49-
if image != None: textureNode.image = image
35+
from .BlenderMaterialUtils import addImageTextureToMaterial
5036

5137
def mirrorUVY(uv):
5238
x, y = uv
@@ -171,8 +157,16 @@ def buildBlenderMesh(self, meshData):
171157
me.polygons[faceI+faceIndexBase].material_index = submeshI
172158
faceIndexBase += submesh.indexCount//3
173159

174-
# Finalise
160+
#XXX: call this before we assign split normals, if you do not it causes a segmentation fault
175161
me.validate()
162+
163+
# Split normals
164+
if len(normal1) != 0:
165+
me.normals_split_custom_set_from_vertices(normal1)
166+
elif len(normal2) != 0:
167+
me.normals_split_custom_set_from_vertices(normal2)
168+
169+
# Finalise
176170
me.update()
177171
return me
178172

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
'''
2+
Copyright (c) 2025 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 zlib import decompress
24+
from io import BytesIO
25+
26+
from .IOTools import unpackStream
27+
28+
def unwrapPacket(stream):
29+
print("Unwrapping packet")
30+
31+
# Determine size and compression
32+
packetFlags = int.from_bytes(stream.read(1))
33+
compressedPacket = (packetFlags & 0b01000000) > 0
34+
35+
packetLength = 0
36+
packetLengthType = packetFlags & 0b10000000
37+
if packetLengthType == 0:
38+
# This is a short packet
39+
packetLength = int.from_bytes(stream.read(1))
40+
packetLength += (packetFlags & 0b00111111) << 8 # Part of the length is embedded in the flags field
41+
else:
42+
# This is a long packet
43+
packetLength = int.from_bytes(stream.read(3), "big")
44+
packetLength += (packetFlags & 0b00111111) << 24
45+
46+
# Decompress the packet if needed
47+
packetData = stream.read(packetLength)
48+
if compressedPacket:
49+
print("Decompressing packet")
50+
packetData = decompress(packetData)
51+
52+
return BytesIO(packetData)
53+
54+
def readOptionalMask(stream):
55+
print("Reading optional mask")
56+
57+
optionalMask = []
58+
59+
# Determine mask type (there are multiple length types)
60+
maskFlags = int.from_bytes(stream.read(1))
61+
maskLengthType = maskFlags & 0b10000000
62+
if maskLengthType == 0:
63+
# Short mask: 5 optional bits + upto 3 extra bytes
64+
# First read the integrated optional bits
65+
integratedOptionalBits = maskFlags << 3 # Trim flag bits so we're left with the optionals and some padding bits
66+
for bitI in range(7, 2, -1): #0b11111000 left to right
67+
optional = (integratedOptionalBits & 2**bitI) == 0
68+
optionalMask.append(optional)
69+
70+
# Now read the external bytes
71+
externalByteCount = (maskFlags & 0b01100000) >> 5
72+
externalBytes = stream.read(externalByteCount)
73+
for externalByte in externalBytes:
74+
for bitI in range(7, -1, -1): #0b11111111 left to right
75+
optional = (externalByte & 2**bitI) == 0
76+
optionalMask.append(optional)
77+
else:
78+
# This type of mask encodes an extra length/count field to increase the number of possible optionals significantly
79+
maskLengthType = maskFlags & 0b01000000
80+
externalByteCount = 0
81+
if maskLengthType == 0:
82+
# Medium mask: stores number of bytes used for the optional mask in the last 6 bits of the flags
83+
externalByteCount = maskFlags & 0b00111111
84+
else:
85+
# Long mask: # Medium mask: stores number of bytes used for the optional mask in the last 6 bits of the flags + 2 extra bytes
86+
externalByteCount = (maskFlags & 0b00111111) << 16
87+
externalByteCount += int.from_bytes(stream.read(2), "big")
88+
89+
# Read the external bytes
90+
externalBytes = stream.read(externalByteCount)
91+
for externalByte in externalBytes:
92+
for bitI in range(7, -1, -1): #0b11111111 left to right
93+
optional = (externalByte & 2**bitI) == 0
94+
optionalMask.append(optional)
95+
96+
optionalMask.reverse()
97+
return optionalMask
98+
99+
'''
100+
Array type readers
101+
'''
102+
def readArrayLength(packet):
103+
arrayLength = 0
104+
105+
arrayFlags = int.from_bytes(packet.read(1))
106+
arrayLengthType = arrayFlags & 0b10000000
107+
if arrayLengthType == 0:
108+
# Short array
109+
arrayLength = arrayFlags & 0b01111111
110+
else:
111+
# Long array
112+
arrayLengthType = arrayFlags & 0b01000000
113+
if arrayLengthType == 0:
114+
# Length in last 6 bits of flags + next byte
115+
arrayLength = (arrayFlags & 0b00111111) << 8
116+
arrayLength += int.from_bytes(packet.read(1))
117+
else:
118+
# Length in last 6 bits of flags + next 2 byte
119+
arrayLength = (arrayFlags & 0b00111111) << 16
120+
arrayLength += int.from_bytes(packet.read(2), "big")
121+
122+
return arrayLength
123+
124+
def readObjectArray(packet, objReader, optionalMask):
125+
arrayLength = readArrayLength(packet)
126+
objects = []
127+
for _ in range(arrayLength):
128+
obj = objReader()
129+
obj.read(packet, optionalMask)
130+
objects.append(obj)
131+
132+
return objects
133+
134+
def readString(packet):
135+
stringLength = readArrayLength(packet)
136+
string = packet.read(stringLength)
137+
string = string.decode("utf-8")
138+
139+
return string
140+
141+
def readInt16Array(packet):
142+
arrayLength = readArrayLength(packet)
143+
integers = unpackStream(f"{arrayLength}h", packet)
144+
145+
return list(integers)
146+
147+
def readIntArray(packet):
148+
arrayLength = readArrayLength(packet)
149+
integers = unpackStream(f"{arrayLength}i", packet)
150+
151+
return list(integers)
152+
153+
def readInt64Array(packet):
154+
arrayLength = readArrayLength(packet)
155+
integers = unpackStream(f"{arrayLength}q", packet)
156+
157+
return list(integers)
158+
159+
def readFloatArray(packet):
160+
arrayLength = readArrayLength(packet)
161+
floats = unpackStream(f">{arrayLength}f", packet)
162+
163+
return list(floats)

0 commit comments

Comments
 (0)