This repository has been archived by the owner on Feb 15, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
import_ebm_cabal.ms
473 lines (377 loc) · 12.5 KB
/
import_ebm_cabal.ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
----------------------------------------------------------------
---- Cabal Online EBM file importer script
---- Author: Kanaho
----------------------------------------------------------------
--------------------------------
-- Functions
--------------------------------
/* Reads a string of length '_length' from the file */
function readtext _file _length =
(
local result = ""
-- Read each byte, convert it to a character, and append it to our string
for i = 1 to _length do
(
next_byte = readbyte _file
result += bit.intaschar(next_byte)
)
-- Return the resulting string
result
)
/* Reads the header chunk from the file */
function readheader _file =
(
ebm_header = EBMHeader()
-- Populate the header struct with the data from the file
ebm_header.magic = readlong _file #unsigned
ebm_header.unk0 = readshort _file #unsigned
ebm_header.flag = readbyte _file #unsigned
ebm_header.alpha_threshold = readbyte _file #unsigned
ebm_header.unk1 = readshort _file #unsigned
x = readfloat _file; y = readfloat _file; z = readfloat _file
ebm_header.bounds_min = [x, y, z]
x = readfloat _file; y = readfloat _file; z = readfloat _file
ebm_header.bounds_max = [x, y, z]
ebm_header.scale_percentage = readlong _file
)
/* Reads the materials chunk from the file */
function readmaterials _file =
(
global in_materials = #()
mat_count = readshort _file
-- Iterate through each material and read its data
for i = 1 to mat_count do
(
-- Colours need to be converted from a max value of 1.0 to a max value of 255
r = readfloat _file; g = readfloat _file; b = readfloat _file; a = readfloat _file
mp_ambient = color (r * 255) (g * 255) (b * 255) (a * 255)
r = readfloat _file; g = readfloat _file; b = readfloat _file; a = readfloat _file
mp_diffuse = color (r * 255) (g * 255) (b * 255) (a * 255)
r = readfloat _file; g = readfloat _file; b = readfloat _file; a = readfloat _file
mp_specular = color (r * 255) (g * 255) (b * 255) (a * 255)
r = readfloat _file; g = readfloat _file; b = readfloat _file; a = readfloat _file
mp_emissive = color (r * 255) (g * 255) (b * 255) (a * 255)
-- Self-illumination strength
mp_power = readfloat _file
-- Read the texture name
name_length = readshort _file
tex_name = readtext _file name_length
-- Read the texture data and save it as an external file
tex_size = readlong _file #unsigned
tex_file = (pathconfig.getdir #import) + "\\" + tex_name
tex_out = fopen tex_file "wb"
for j = 1 to tex_size do
(
val = readbyte _file
writebyte tex_out val
)
fclose tex_out
-- Open the saved texture file
tex = openbitmap tex_file
tex_bmp = bitmaptexture bitmap:tex
-- Create the material and set its attributes
local new_mat = standard name:tex_name
new_mat.adlock = false
new_mat.diffusemap = tex_bmp
new_mat.showinviewport = true
new_mat.ambient = mp_ambient
new_mat.diffuse = mp_diffuse
new_mat.specular = mp_specular
new_mat.selfillumination = mp_power
new_mat.filtercolor = mp_emissive
-- Add the material to an array for later use
append in_materials new_mat
-- Skip the Layer information for now
fseek _file 26 #seek_cur
)
)
/* Reads the bones from the file */
function readarmature _file =
(
-- More globals!
global bone_array = #()
global bone_names = #()
global bone_matrices = #()
global bone_p_matrices = #()
global bone_count = readshort _file
-- Set our array lengths
bone_array.count = bone_count
bone_names.count = bone_count
bone_matrices.count = bone_count
bone_p_matrices.count = bone_count
-- Iterate through each bone and read its data
for i = 1 to bone_count do
(
-- Read the bone name
local name_length = readshort _file
local bone_name = readtext _file name_length
bone_names[i] = bone_name
-- Read the parent bone ID
parent_id = readlong _file
-- Read the transformation matrix
m11 = readfloat _file; m12 = readfloat _file; m13 = readfloat _file
readfloat _file
m21 = readfloat _file; m22 = readfloat _file; m23 = readfloat _file
readfloat _file
m31 = readfloat _file; m32 = readfloat _file; m33 = readfloat _file
readfloat _file
m41 = readfloat _file; m42 = readfloat _file; m43 = readfloat _file
readfloat _file
-- Read the parent transformation matrix (not actually used by 3ds Max)
pm11 = readfloat _file; pm12 = readfloat _file; pm13 = readfloat _file
readfloat _file
pm21 = readfloat _file; pm22 = readfloat _file; pm23 = readfloat _file
readfloat _file
pm31 = readfloat _file; pm32 = readfloat _file; pm33 = readfloat _file
readfloat _file
pm41 = readfloat _file; pm42 = readfloat _file; pm43 = readfloat _file
readfloat _file
-- Create 4x3 matrices from the data we read and add it to our arrays
tm = matrix3 \
[m11, m12, m13] \
[m21, m22, m23] \
[m31, m32, m33] \
[m41, m42, m43]
bone_matrices[i] = tm
-- Invert the transformation matrix because Cabal is ghey like that
tm = inverse tm
ptm = matrix3 \
[pm11, pm12, pm13] \
[pm21, pm22, pm23] \
[pm31, pm32, pm33] \
[pm41, pm42, pm43]
bone_p_matrices[i] = ptm
-- Create a bone and set its attributes
new_bone = bonesys.createbone [0, 0, 0] [0, 0, 0] [0, 0, 0]
new_bone.transform = tm
new_bone.name = bone_name
new_bone.wirecolor = yellow
-- Set the bones parent if it has one
if (parent_id > -1) then
new_bone.parent = bone_array[parent_id + 1]
-- Add the bone to an array for later use
bone_array[i] = new_bone
)
-- Reset the stretch of all bones so that they extend to their child (stupid 3ds Max...)
for i in 1 to bone_count do
bone_array[i].resetbonestretch()
)
function readanimations _file =
(
anim_count = readshort _file
animate off
offset = 1f
-- Initialise our animations list and array
cabalebmpanel.lb_anims.items = #()
animations = #()
-- Iterate through each animation and read its data
for i = 1 to anim_count do
(
-- Read the animation name
local name_length = readshort _file
local anim_name = readtext _file name_length
cabalebmpanel.lb_anims.items = append cabalebmpanel.lb_anims.items anim_name
local trans_count = readshort _file
local anim_length = 0f
-- Tell 3ds Max to start making keys
with animate on
(
-- Iterate through each transform and read its data
for j = 1 to trans_count do
(
-- Read the affected node name
local name_length = readshort _file
local node_name = readtext _file name_length
local anim_node = getnodebyname node_name
local t_count = readlong _file
-- Iterate through each translation and read its data
for k = 1 to t_count do
(
local kfs = readfloat _file -- Keyframe second
local x = readfloat _file -- New position (relative to parent)
local y = readfloat _file
local z = readfloat _file
transl = [x, y, z]
-- Get the animation length
if (kfs * 30.0) > anim_length then
anim_length = (kfs * 30.0)
if ebm_header.magic < 0x3ef03 then
at time (offset + kfs * 30.0) (in coordsys parent anim_node.pos = transl)
--else
--at time (offset + kfs * 30) _node.pos = [_x, _y, _z]
)
local r_count = readlong _file
-- Iterate through each rotation and read its data
for k = 1 to r_count do
(
local kfs = readfloat _file
local x = readfloat _file -- New rotation (relative to parent)
local y = readfloat _file
local z = readfloat _file
local w = readfloat _file
-- Create a quaternion from the read data and inverse it
rot = quat x y z w
rot = inverse rot
if (kfs * 30.0) > anim_length then
anim_length = (kfs * 30.0)
if ebm_header.magic < 0x3ef03 then
at time (offset + kfs * 30.0) (in coordsys parent anim_node.rotation = rot)
--else
--at time (offset + kfs * 30) (in coordsys ((matrix3 1) * (transmatrix _node.transform.pos)) _node.rotation = _q)
)
)
)
-- Add our animation range to an array for later use
animations[i] = interval offset (offset + anim_length)
-- Increase the offset by the animation length and leave a 1 frame gap
offset += anim_length + 1
)
-- Set the animation range to the first animation
if animations.count > 0 then
animationrange = animations[1]
else
animationrange = interval 0 100
)
/* Reads the meshes from the file */
function readmeshes _file =
(
mesh_count = readshort _file
-- Iterate through each mesh and read its data
for c = 1 to mesh_count do
(
vert_array = #()
normal_array = #()
tvert_array = #()
face_array = #()
-- Read the mesh name
name_length = readshort _file
mesh_name = readtext _file name_length
-- Skip the matrices for now as we don't need them
fseek _file 128 #seek_cur
root_bone = readlong _file
mat_id = readbyte _file #unsigned
vert_count = readshort _file #unsigned
face_count = readshort _file #unsigned
-- Initialise our data arrays
vert_array.count = vert_count
normal_array.count = vert_count
tvert_array.count = vert_count
face_array.count = face_count
-- Iterate through each vertex and read its data
for i = 1 to vert_count do
(
x = readfloat _file; y = readfloat _file; z = readfloat _file
norm_x = readfloat _file; norm_y = readfloat _file; norm_z = readfloat _file
uv_x = readfloat _file; uv_y = readfloat _file
vert_array[i] = [x, y, z]
normal_array[i] = [norm_x, norm_y, norm_z]
tvert_array[i] = [uv_x, 1 - uv_y, 0]
)
-- Iterate through each face and read its data
for i = 1 to face_count do
(
v1 = readshort _file; v2 = readshort _file; v3 = readshort _file
face_array[i] = [v1 + 1, v2 + 1, v3 + 1]
)
new_mesh = mesh vertices:vert_array faces:face_array tverts:tvert_array
-- Set the mesh's texture vertices so we have UV
if vert_count > 0 then
buildtvfaces new_mesh
for i = 1 to new_mesh.numfaces do
setTVFace new_mesh i (getFace new_mesh i)
if vert_count > 0 then
(
fseek _file 4 #seek_cur -- Influence chunk ID
inf_count = readshort _file #unsigned
-- Create our skin modifier
if inf_count > 0 then
(
mesh_skin = skin()
addmodifier new_mesh mesh_skin
max modify mode
modpanel.setcurrentobject mesh_skin
-- Add all of our bones to the skin modifier
for i = 1 to bone_count do
skinops.addbone mesh_skin bone_array[i] 0
-- Needed in order to populate the skin with our vertices
completeredraw()
)
-- Iterate through each influence and read its data
for i = 1 to inf_count do
(
-- Read the influences for each bone
for j = 1 to bone_count do
(
vert_inf_count = readlong _file #unsigned
vertid_array = #()
weight_array = #()
for k = 1 to vert_inf_count do
vertid_array[k] = readlong _file
for k = 1 to vert_inf_count do
weight_array[k] = (readfloat _file)
-- Set the vertex weights in our skin
for k = 1 to vert_inf_count do
skinops.setvertexweights mesh_skin (vertid_array[k] + 1) j (weight_array[k] as float)
)
)
)
-- Set our mesh's name and material
new_mesh.name = mesh_name
new_mesh.material = in_materials[mat_id + 1]
-- Create an edit_normals modifier
-- (because setting them with setnormal() doesn't seem to work...)
mesh_norm = edit_normals()
addmodifier new_mesh mesh_norm
max modify mode
modpanel.setcurrentobject mesh_norm
for i = 1 to new_mesh.numfaces do
(
for j in 1 to 3 do
(
local n = mesh_norm.getnormalid i j
local v = mesh_norm.getvertexid i j
mesh_norm.setnormal n normal_array[v]
)
)
)
)
/* Cleans up our arrays (probably don't need this) */
function _cleanup =
(
free vert_array
free face_array
free normal_array
free tvert_array
free bone_array
vert_array = undefined
face_array = undefined
normal_array = undefined
tvert_array = undefined
bone_array = undefined
)
--------------------------------
-- Main
--------------------------------
global file_name = getopenfilename \
caption:"Select a model to import..." \
types:"Cabal Model Files|*.ebm;*.ech"
if file_name != undefined then
(
-- Open the file with read rights in binary mode
local in_file = fopen file_name "rb"
-- Clear our scene
delete objects
readheader in_file
while true do
(
id = readlong in_file -- Chunk ID
if id == 0x41470201 then readmaterials in_file else
if id == 0x41470202 then readmeshes in_file else
if id == 0x41470203 then readarmature in_file else
if id == 0x41470204 then (readanimations in_file; exit())
)
fclose in_file
_cleanup()
clearselection()
print "EBM successfully imported!"
)