├── .gitignore ├── addons └── GoldSRC_mdl_importer │ ├── plugin.cfg │ ├── material_import.gd │ ├── matCap.shader │ ├── import_plugin.gd │ ├── DFile.gd │ └── mdlLoad.gd ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | -------------------------------------------------------------------------------- /addons/GoldSRC_mdl_importer/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="GoldSrc mdl importer" 4 | description="Imports Gold mdl files" 5 | author="Data Plus" 6 | version="1.0" 7 | script="material_import.gd" -------------------------------------------------------------------------------- /addons/GoldSRC_mdl_importer/material_import.gd: -------------------------------------------------------------------------------- 1 | # material_import.gd 2 | tool 3 | extends EditorPlugin 4 | 5 | 6 | var import_plugin 7 | 8 | 9 | func _enter_tree(): 10 | import_plugin = preload("import_plugin.gd").new() 11 | add_import_plugin(import_plugin) 12 | 13 | 14 | func _exit_tree(): 15 | remove_import_plugin(import_plugin) 16 | import_plugin = null 17 | -------------------------------------------------------------------------------- /addons/GoldSRC_mdl_importer/matCap.shader: -------------------------------------------------------------------------------- 1 | shader_type spatial; 2 | render_mode unshaded;//, world_vertex_coords; 3 | 4 | uniform sampler2D matcap_texture : hint_black_albedo; 5 | uniform bool use_world_normals = false; 6 | 7 | varying vec3 world_normal; 8 | 9 | void vertex() { 10 | world_normal = NORMAL; 11 | } 12 | 13 | void fragment() { 14 | vec2 matcap_uv = ((use_world_normals ? world_normal.xy : NORMAL.xy) * vec2(0.5, -0.5) + vec2(0.5, 0.5)); 15 | vec3 matcap_value = texture(matcap_texture, matcap_uv).rgb; 16 | 17 | ALBEDO = matcap_value; 18 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot-GoldSrc-MDL-Importer 2 | 3 | This is a plugin that allows you to import GldSrc .mdl model files into godot. 4 | 5 | ![](https://i.postimg.cc/7ZtBxKNy/anims.gif) 6 | ![](https://i.imgur.com/4Xn3oS5.png) 7 | ![](https://i.imgur.com/fWF6Ojj.png) 8 | ![](https://i.imgur.com/zc8w2sw.png) 9 | 10 | ## Usage 11 | Simply enable the plugin and drag and drop the .mdl files into your project directory. 12 | 13 | **Note that some model files have external dependencies such as textures that are stored in a different .mdl file these must be present in your project directory first before import.** 14 | 15 | For example suit.mdl has textures stored in suitt.mdl and scientist.mdl has various data stored accross scientistt.mdl, scientist01.mdl, scientist02.mdl, etc... -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DataPlusProgram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/GoldSRC_mdl_importer/import_plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorImportPlugin 3 | 4 | enum Presets { DEFAULT } 5 | 6 | func get_importer_name(): 7 | return "GoldSrc mdl importer" 8 | 9 | func get_recognized_extensions(): 10 | return ["mdl"] 11 | 12 | 13 | func get_visible_name(): 14 | return "GoldSrc mdl" 15 | 16 | func get_preset_count(): 17 | return Presets.size() 18 | 19 | func get_preset_name(preset): 20 | match preset: 21 | Presets.DEFAULT: return "Default" 22 | _: return "Unknown" 23 | 24 | 25 | func get_import_options(preset): 26 | match preset: 27 | Presets.DEFAULT: return[{"name":"texture_filtering", "default_value":false}] 28 | _: return[] 29 | 30 | func get_option_visibility(option, options): 31 | return true 32 | 33 | func get_save_extension(): 34 | return ".tscn" 35 | 36 | func get_resource_type(): 37 | return "PackedScene" 38 | 39 | func import(source_file, save_path, options, r_platform_variants, r_gen_files): 40 | var textureFiltering = options["texture_filtering"] 41 | 42 | var mdlLoader = load("res://addons/GoldSRC_mdl_importer/mdlLoad.gd").new() 43 | var skel = mdlLoader.mdlParse(source_file,textureFiltering) 44 | if skel == null: 45 | return false 46 | var packed_scene = PackedScene.new() 47 | packed_scene.pack(skel) 48 | 49 | var filename = save_path + "." + get_save_extension() 50 | return ResourceSaver.save(filename, packed_scene) 51 | 52 | -------------------------------------------------------------------------------- /addons/GoldSRC_mdl_importer/DFile.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | var timings = {} 3 | 4 | var filePath 5 | var data 6 | var pos = 0 7 | 8 | func loadFile(path): 9 | filePath = path 10 | var file = File.new() 11 | var err = file.open(path,File.READ) 12 | if err != 0: 13 | #print("Error opening file:",path) 14 | return false 15 | data = file.get_buffer(file.get_len()) 16 | 17 | file.close() 18 | return true 19 | 20 | 21 | func seek(offset): 22 | pos = offset 23 | 24 | func get_position(): 25 | return pos 26 | 27 | func get_8(): 28 | var ret = data[pos] 29 | pos+=1 30 | return ret 31 | 32 | func bulkByteArr(size): 33 | var ret = data.subarray(pos,pos+size) 34 | pos += size 35 | return ret 36 | 37 | func get_16(): 38 | var ret = data.subarray(pos,pos+1) 39 | pos+=2 40 | 41 | var rety = (ret[1] << 8) + ret[0] 42 | return (ret[1] << 8) + ret[0] 43 | 44 | func get_32u(): 45 | var ret = data.subarray(pos,pos+3) 46 | pos+=4 47 | return (ret[3] << 24) + (ret[2] << 16) + (ret[1] << 8 ) + ret[0] 48 | 49 | 50 | func get_32(): 51 | if pos == 4063337: 52 | breakpoint 53 | var ret = data.subarray(pos,pos+3) 54 | var spb = StreamPeerBuffer.new() 55 | spb.data_array = ret 56 | var single_float = spb.get_32() 57 | pos+=4 58 | return single_float 59 | 60 | func get_16u(): 61 | var ret = data.subarray(pos,pos+1) 62 | ret = (ret[1] << 8) + ret[0] 63 | if (ret & 0x8000): 64 | ret -= 0x8000 65 | ret = (-32767 + ret) -1 66 | 67 | pos+=2 68 | return ret 69 | 70 | func get_Vector32(): 71 | var x = get_float32() 72 | var y = get_float32() 73 | var z = get_float32() 74 | return Vector3(x,y,z) 75 | 76 | func get_float32(): 77 | var ret = data.subarray(pos,pos+3) 78 | var spb = StreamPeerBuffer.new() 79 | spb.data_array = ret 80 | var single_float = spb.get_float() 81 | pos+=4 82 | return single_float 83 | 84 | 85 | func get_buffer(size): 86 | var ret = data.subarray(pos,pos+(size-1))#not sure why using -1 here 87 | pos+=size 88 | return ret 89 | 90 | func get_String(length): 91 | var ret = data.subarray(pos,pos+(length-1)).get_string_from_ascii() 92 | 93 | pos+=length 94 | return ret.to_upper() 95 | 96 | #func get_line(): 97 | # return data.get_line() 98 | func get_len(): 99 | return data.size() 100 | 101 | func eof_reached(): 102 | return pos >= data.size() 103 | -------------------------------------------------------------------------------- /addons/GoldSRC_mdl_importer/mdlLoad.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node 3 | var file 4 | var textures = [] 5 | var fileDict = {} 6 | var bones = [] 7 | var boneIndex = 0 8 | var boneMap = {} 9 | var matCache = {} 10 | var boneLocalTransforms = [] 11 | var boneLocalTransformsInv = [] 12 | var seqGroupFiles = [] 13 | var textureFiltering 14 | 15 | var DFilePath = "res://addons/GoldSRC_mdl_importer/DFile.gd" 16 | var matCapShaderPath = "res://addons/GoldSRC_mdl_importer/matCap.shader" 17 | 18 | 19 | enum A{ 20 | POSX = 0, 21 | POSY = 1, 22 | POSZ = 2, 23 | ROTX = 3, 24 | ROTY = 4, 25 | ROTZ = 5, 26 | } 27 | 28 | func clearData(): 29 | textures = [] 30 | fileDict = {} 31 | bones = [] 32 | boneIndex = 0 33 | boneMap = {} 34 | matCache = {} 35 | boneLocalTransforms = [] 36 | boneLocalTransformsInv = [] 37 | seqGroupFiles = [] 38 | 39 | 40 | func mdlParse(path,textureFilter = false): 41 | textureFiltering = textureFilter 42 | 43 | clearData() 44 | var modelName = getModelNameFromPath(path) 45 | var meshArr = [] 46 | 47 | file = load(DFilePath).new() 48 | if !file.loadFile(path): 49 | print("model file not found:",path) 50 | return false 51 | 52 | fileDict["magic"] = file.get_String(4) 53 | 54 | if fileDict["magic"] == "IDSQ": 55 | return null 56 | 57 | fileDict["version"] = file.get_32() 58 | 59 | if fileDict["version"] < 10: 60 | return null 61 | 62 | fileDict["name"] = file.get_String(64) 63 | fileDict["size"] = file.get_32() 64 | fileDict["eyePosition"] = getVectorXZY(file) 65 | fileDict["min"] = getVectorXZY(file) 66 | fileDict["max"] = getVectorXZY(file) 67 | fileDict["bbmin"] = getVectorXZY(file) 68 | fileDict["bbmax"] = getVectorXZY(file) 69 | fileDict["flags"] = file.get_32() 70 | fileDict["numBones"] = file.get_32() 71 | fileDict["boneIndex"] = file.get_32() 72 | fileDict["numbonecontrollers"] = file.get_32() 73 | fileDict["bonecontrollerindex"] = file.get_32() 74 | fileDict["numhitboxes"] = file.get_32() 75 | fileDict["hitboxindex"] = file.get_32() 76 | fileDict["numseq"] = file.get_32() 77 | fileDict["seqindex"] = file.get_32() 78 | fileDict["numseqgroups"] = file.get_32() 79 | fileDict["seqgroupindex"] = file.get_32() 80 | fileDict["numTextures"] = file.get_32() 81 | fileDict["textureindex"] = file.get_32() 82 | fileDict["texturedataindex"] = file.get_32() 83 | fileDict["numskinref"] = file.get_32() 84 | fileDict["numskinfamilies"] = file.get_32() 85 | fileDict["skinindex"] = file.get_32() 86 | fileDict["numbodyparts"] = file.get_32() 87 | fileDict["bodypartindex"] = file.get_32() 88 | fileDict["numattachments"] = file.get_32() 89 | fileDict["attachmentindex"] = file.get_32() 90 | fileDict["soundtable"] = file.get_32() 91 | fileDict["soundindex"] = file.get_32() 92 | fileDict["soundgroups"] = file.get_32() 93 | fileDict["soundgroupindex"] = file.get_32() 94 | fileDict["numtransitions"] = file.get_32() 95 | fileDict["transitionindex"] = file.get_32() 96 | 97 | 98 | 99 | seqGroupFiles.append(file) 100 | 101 | for file in fileDict["numseqgroups"]-1: 102 | var fileToFind = path.substr(path.find_last("/")) 103 | fileToFind = fileToFind.split(".")[0] 104 | fileToFind += "0" + String(file+1) + ".mdl" 105 | fileToFind = path.substr(0,path.find_last("/")) + fileToFind 106 | var fExist= File.new() 107 | var doesExist = fExist.file_exists(fileToFind) 108 | if doesExist: 109 | file = load(DFilePath).new() 110 | file.loadFile(fileToFind) 111 | seqGroupFiles.append(file) 112 | 113 | 114 | 115 | file.seek(fileDict["textureindex"]) 116 | for i in fileDict["numTextures"]: 117 | textures.append(parseTexture()) 118 | 119 | if fileDict["numTextures"] == 0: 120 | 121 | var textureFile = doesTextureFileExist(path) 122 | 123 | if textureFile != null: 124 | var textureParse = Node.new() 125 | var script = load(self.get_script().get_path()) 126 | textureParse.set_script(script) 127 | add_child(textureParse) 128 | #print("loading from texture file:"+textureFile) 129 | textures = textureParse.mdlParseTextures(textureFile,textureFilter) 130 | 131 | parseBones() 132 | fileDict["sequences"] = parseSequence() 133 | if !fileDict["sequences"].empty(): 134 | boneItt3(fileDict["sequences"][0]["blends"][0][0]) 135 | 136 | meshArr = parseBodyParts(fileDict["numbodyparts"],modelName) 137 | var meshNodeArr = [] 138 | 139 | 140 | for m in meshArr: 141 | var meshNode = MeshInstance.new() 142 | meshNode.mesh = m["mesh"] 143 | meshNode.name = m["meshName"] 144 | meshNode.visible = m["visible"] 145 | var eyePos = fileDict["eyePosition"] 146 | meshNodeArr.append(meshNode) 147 | 148 | 149 | var skel = initSkel() 150 | skel.name = modelName 151 | 152 | if !fileDict["sequences"].empty(): 153 | initSkelAnimations(skel) 154 | 155 | for i in meshNodeArr: 156 | skel.add_child(i) 157 | i.set_owner(skel) 158 | 159 | 160 | skel.rotation_degrees.x = -90 161 | skel.rotation_degrees.z = 90 162 | 163 | return skel 164 | 165 | 166 | 167 | func mdlParseTextures(path,textureFilter): 168 | 169 | textureFiltering = textureFilter 170 | file = load(DFilePath).new() 171 | 172 | if !file.loadFile(path): 173 | print("file not found") 174 | return false 175 | 176 | fileDict["magic"] = file.get_String(4) 177 | fileDict["version"] = file.get_32() 178 | fileDict["name"] = file.get_String(64) 179 | fileDict["size"] = file.get_32() 180 | fileDict["eyePosition"] = getVectorXZY(file) 181 | fileDict["min"] = getVectorXZY(file) 182 | fileDict["max"] = getVectorXZY(file) 183 | fileDict["bbmin"] = getVectorXZY(file) 184 | fileDict["bbmax"] = getVectorXZY(file) 185 | fileDict["flags"] = file.get_32() 186 | fileDict["numBones"] = file.get_32() 187 | fileDict["boneIndex"] = file.get_32() 188 | fileDict["numbonecontrollers"] = file.get_32() 189 | fileDict["bonecontrollerindex"] = file.get_32() 190 | fileDict["numhitboxes"] = file.get_32() 191 | fileDict["hitboxindex"] = file.get_32() 192 | fileDict["numseq"] = file.get_32() 193 | fileDict["seqindex"] = file.get_32() 194 | fileDict["numseqgroups"] = file.get_32() 195 | fileDict["seqgroupindex"] = file.get_32() 196 | fileDict["numTextures"] = file.get_32() 197 | fileDict["textureindex"] = file.get_32() 198 | fileDict["texturedataindex"] = file.get_32() 199 | 200 | 201 | file.seek(fileDict["textureindex"]) 202 | 203 | 204 | for i in fileDict["numTextures"]: 205 | textures.append(parseTexture()) 206 | 207 | return textures 208 | 209 | func saveScene(): 210 | var i = 0 211 | for c in get_children(): 212 | c.set_owner(self) 213 | 214 | var packed_scene = PackedScene.new() 215 | packed_scene.pack(self) 216 | ResourceSaver.save(String(i) + ".tscn", packed_scene) 217 | 218 | 219 | func parseTexture(): 220 | var textureDict = {} 221 | 222 | textureDict["name"] = file.get_String(64) 223 | textureDict["flags"] = file.get_32() 224 | textureDict["width"] = file.get_32() 225 | textureDict["height"] = file.get_32() 226 | textureDict["index"] = file.get_32() 227 | 228 | 229 | var chrome = false 230 | var additive = false 231 | var transparent = false 232 | var w = textureDict["width"] 233 | var h = textureDict["height"] 234 | 235 | if textureDict["flags"] & 2 > 0: chrome = true 236 | if textureDict["flags"] & 64 > 0: transparent = true 237 | if textureDict["flags"] & 32 > 0: additive = true 238 | 239 | 240 | 241 | var image = Image.new() 242 | image.create(w,h,false,Image.FORMAT_RGBA8) 243 | 244 | var pPos = file.get_position() 245 | 246 | file.seek(textureDict["index"]) 247 | 248 | var pallete = [] 249 | var colorArr = [] 250 | 251 | for y in h: 252 | for x in w: 253 | var colorIndex = file.get_8() 254 | colorArr.append(colorIndex) 255 | 256 | 257 | for c in 256: 258 | var r = file.get_8() / 255.0 259 | var g = file.get_8() / 255.0 260 | var b = file.get_8() / 255.0 261 | 262 | 263 | pallete.append(Color(r,g,b)) 264 | 265 | image.lock() 266 | 267 | 268 | for y in h: 269 | for x in w: 270 | var colorIndex = colorArr[x+(y*w)] 271 | 272 | var color = pallete[colorIndex] 273 | 274 | if colorIndex == pallete.size()-1 and transparent: 275 | color = Color(0,0,0,0) 276 | 277 | image.set_pixel(x,y,color) 278 | 279 | 280 | image.unlock() 281 | var texture = ImageTexture.new() 282 | texture.create_from_image(image) 283 | if !textureFiltering: 284 | texture.flags -= texture.FLAG_FILTER 285 | 286 | 287 | file.seek(pPos) 288 | 289 | return {"texture":texture,"chrome":chrome,"additive":additive,"transparent":transparent} 290 | 291 | 292 | func parseSequence(): 293 | var sequences = [] 294 | file.seek(fileDict["seqindex"]) 295 | for i in fileDict["numseq"]: 296 | var sequenceDict = {} 297 | sequenceDict["name"] = file.get_String(32) 298 | sequenceDict["fps"] = file.get_float32() 299 | sequenceDict["flags"] = file.get_32() 300 | sequenceDict["activity"] = file.get_32() 301 | sequenceDict["actweight"] = file.get_32() 302 | sequenceDict["numevents"] = file.get_32() 303 | sequenceDict["eventindex"] = file.get_32() 304 | sequenceDict["numframes"] = file.get_32() 305 | sequenceDict["numpivots"] = file.get_32() 306 | sequenceDict["pivotIndex"] = file.get_32() 307 | sequenceDict["motionType"] = file.get_32() 308 | sequenceDict["motionBone"] = file.get_32() 309 | sequenceDict["linearMovement"] = getVectorXZY(file) 310 | sequenceDict["autoMovePosIndex"] = file.get_32() 311 | sequenceDict["autoMovoeAngleIndex"] = file.get_32() 312 | sequenceDict["bbMin"] = getVectorXZY(file) 313 | sequenceDict["bbMax"] = getVectorXZY(file) 314 | sequenceDict["numBlends"] = file.get_32() 315 | sequenceDict["animIndex"] = file.get_32() 316 | sequenceDict["blendType0"] = file.get_32() 317 | sequenceDict["blendType1"] = file.get_32() 318 | sequenceDict["blendStart0"] = file.get_float32() 319 | sequenceDict["blendStart1"] = file.get_float32() 320 | sequenceDict["blendEnd0"] = file.get_float32() 321 | sequenceDict["blendEnd1"] = file.get_float32() 322 | sequenceDict["blendParent"] = file.get_float32() 323 | sequenceDict["seqGroup"] = file.get_32() 324 | sequenceDict["entryMode"] = file.get_32() 325 | sequenceDict["exitNode"] = file.get_32() 326 | sequenceDict["nodeFlags"] = file.get_32() 327 | sequenceDict["nextFlags"] = file.get_32() 328 | 329 | sequenceDict["blends"] = [] 330 | 331 | 332 | sequences.append(sequenceDict) 333 | var preOffset = file.get_position() 334 | for b in sequenceDict["numBlends"]: 335 | var blend = parseBlend(sequenceDict["animIndex"],sequenceDict["numBlends"],sequenceDict["numframes"],sequenceDict["seqGroup"]) 336 | sequenceDict["blends"].append(blend) 337 | file.seek(preOffset) 338 | 339 | 340 | return sequences 341 | 342 | func parseBlend(startOffset,numBlends,numFrames,group): 343 | var sFile = seqGroupFiles[group] 344 | var numBones = fileDict["numBones"] 345 | sFile.seek(startOffset) 346 | 347 | var boneToOffset = [] 348 | var blendOffsets = [] 349 | 350 | 351 | var blendLength = 6 * numBones#a pos and rot for each bone 352 | for o in numBlends*blendLength: 353 | blendOffsets.append(sFile.get_16())#an offset for each value xyz rxryryx 354 | 355 | 356 | var allFrames = [] 357 | for f in numFrames: 358 | allFrames.append({"pos":[],"rot":[]}) 359 | 360 | 361 | for boneIdx in numBones: 362 | var bone = bones[boneIdx] 363 | var boneFrameData = [] 364 | for i in 6: 365 | var offset = blendOffsets[boneIdx*6+i] 366 | if offset == 0: 367 | boneFrameData.append(createEmptyData(numFrames)) 368 | else: 369 | boneFrameData.append(parseAnimData(numFrames,sFile)) 370 | 371 | 372 | for f in numFrames: 373 | var bonePos = Vector3(boneFrameData[A.POSX][f],boneFrameData[A.POSY][f],boneFrameData[A.POSZ][f]) 374 | var boneRot = Vector3(boneFrameData[A.ROTX][f],boneFrameData[A.ROTY][f],boneFrameData[A.ROTZ][f]) 375 | 376 | allFrames[f]["pos"].append(bonePos* bone["scaleP"]) 377 | allFrames[f]["rot"].append(boneRot* bone["scaleR"]) 378 | 379 | return allFrames 380 | 381 | func createEmptyData(numFrames): 382 | var animData = [] 383 | for i in numFrames: 384 | animData.append(0) 385 | return animData 386 | 387 | func parseAnimData(numFrames,sFile): 388 | var animData = [] 389 | 390 | for i in numFrames: 391 | animData.append(0) 392 | 393 | var i = 0 394 | while i < numFrames: 395 | 396 | var compressedSize = sFile.get_8() 397 | var uncompressedSize = sFile.get_8() 398 | var compressedData = [] 399 | for c in compressedSize: 400 | compressedData.append(sFile.get_16u()) 401 | 402 | var j = 0 403 | 404 | while(j < uncompressedSize and i < numFrames): 405 | var index = min(compressedSize-1,j) 406 | animData[i] = compressedData[index] 407 | j+=1 408 | i+=1 409 | 410 | return animData 411 | 412 | 413 | func parseBones(): 414 | file.seek(fileDict["boneIndex"]) 415 | for b in fileDict["numBones"]: 416 | bones.append(parseBone()) 417 | 418 | func parseBone(): 419 | var boneDict = {} 420 | 421 | boneDict["name"] = file.get_String(32) 422 | boneDict["parentIndex"] = file.get_32() 423 | boneDict["unused"] = file.get_32() 424 | boneDict["x"] = file.get_32() 425 | boneDict["y"] = file.get_32() 426 | boneDict["z"] = file.get_32() 427 | boneDict["rotX"] = file.get_32() 428 | boneDict["rotY"] = file.get_32() 429 | boneDict["rotZ"] = file.get_32() 430 | boneDict["pos"] = getVectorXZY(file) 431 | boneDict["rot"] =getVectorRot(file) 432 | boneDict["scaleP"] = getVectorXZY(file) 433 | boneDict["scaleR"] = getVectorXZY(file) 434 | boneDict["index"] = String(boneIndex) 435 | boneDict["transform"] = Transform.IDENTITY 436 | 437 | if boneDict["name"] == "": boneDict["name"] = String(boneIndex) 438 | 439 | boneIndex += 1 440 | return boneDict 441 | 442 | 443 | 444 | func parseBodyParts(numBodyParts,modelName): 445 | file.seek(fileDict["bodypartindex"]) 446 | var bodyPartArr = [] 447 | var bodyPartEntry = [] 448 | var meshes = [] 449 | for i in numBodyParts: 450 | var bodyPart = {} 451 | bodyPart["name"] = file.get_String(64) 452 | bodyPart["numModels"] = file.get_32() 453 | bodyPart["base"] = file.get_32() 454 | bodyPart["modelIndex"] = file.get_32() 455 | bodyPartEntry.append(bodyPart) 456 | 457 | for b in bodyPartEntry: 458 | var bodyPartName = b["name"] 459 | file.seek(b["modelIndex"]) 460 | var isFirst = true 461 | 462 | for n in b["numModels"]: 463 | 464 | var bodyPartMesh = parseModel() 465 | if n != 0: bodyPartName += String(n) 466 | bodyPartArr.append({"mesh":bodyPartMesh,"meshName":bodyPartName,"visible":isFirst})# 467 | isFirst = false 468 | 469 | return bodyPartArr 470 | 471 | 472 | func parseModel(): 473 | var modelDict = {} 474 | modelDict["name"] = file.get_String(64) 475 | modelDict["type"] = file.get_32() 476 | modelDict["boundingRadius"] = file.get_32() 477 | modelDict["numMesh"] = file.get_32() 478 | modelDict["meshindex"] = file.get_32() 479 | modelDict["numverts"] = file.get_32() 480 | modelDict["vertinfoindex"] = file.get_32() 481 | modelDict["vertIndex"] = file.get_32() 482 | modelDict["numNorms"] = file.get_32() 483 | modelDict["normInfoIndex"] = file.get_32() 484 | modelDict["normIndex"] = file.get_32() 485 | modelDict["numGroups"] = file.get_32() 486 | modelDict["groupsIndex"] = file.get_32() 487 | var prePos = file.get_position() 488 | 489 | var verts = [] 490 | var norms = [] 491 | var boneMap = [] 492 | file.seek(modelDict["vertIndex"]) 493 | for i in range(0,modelDict["numverts"]): 494 | verts.append(getVectorXZY(file)) 495 | 496 | file.seek(modelDict["normIndex"]) 497 | for i in range(0,modelDict["numNorms"]): 498 | norms.append(getVectorXZY(file)) 499 | 500 | 501 | file.seek(modelDict["vertinfoindex"]) 502 | for i in range(0,modelDict["numverts"]): 503 | boneMap.append(file.get_8()) 504 | 505 | 506 | var meshs = [] 507 | file.seek(modelDict["meshindex"]) 508 | 509 | var runningMesh = null 510 | 511 | var lastTextureIdx = -1 512 | var totalMesh = ArrayMesh.new() 513 | 514 | for i in range(0,modelDict["numMesh"]): 515 | var meshDict = parseMesh() 516 | var polyIdx = 0 517 | for poly in meshDict["triVerts"]: 518 | var v = [] 519 | var n = [] 520 | var uv = [] 521 | var tex = [] 522 | var bones = [] 523 | 524 | for vertDict in poly: 525 | v.append(verts[vertDict["vertIndex"]]) 526 | n.append(norms[vertDict["normIndex"]]) 527 | uv.append(Vector2(vertDict["s"],vertDict["t"])) 528 | bones.append(boneMap[vertDict["vertIndex"]]) 529 | var type = poly[0]["type"] 530 | 531 | var textureIdx = meshDict["skinref"] 532 | 533 | 534 | if runningMesh == null: 535 | runningMesh = SurfaceTool.new() 536 | var mat = createMat(textureIdx) 537 | runningMesh.set_material(mat) 538 | runningMesh.begin(Mesh.PRIMITIVE_TRIANGLES) 539 | lastTextureIdx = textureIdx 540 | 541 | if lastTextureIdx != textureIdx:#if texture changed 542 | var mat = createMat(lastTextureIdx) 543 | runningMesh.set_material(mat) 544 | runningMesh.commit(totalMesh) 545 | #totalMesh.surface_set_material(textureIdx,mat) 546 | runningMesh = SurfaceTool.new() 547 | runningMesh.begin(Mesh.PRIMITIVE_TRIANGLES) 548 | 549 | runningMesh = createMesh(v,n,type,uv,bones,meshDict["skinref"],lastTextureIdx,runningMesh) 550 | 551 | 552 | if i == modelDict["numMesh"]-1 and polyIdx == meshDict["triVerts"].size()-1: 553 | var test = textureIdx 554 | var mat = createMat(textureIdx) 555 | runningMesh.set_material(mat) 556 | runningMesh.commit(totalMesh) 557 | 558 | 559 | lastTextureIdx = meshDict["skinref"] 560 | polyIdx += 1 561 | file.seek(prePos) 562 | return totalMesh 563 | 564 | func parseMesh(): 565 | var meshDict = {} 566 | 567 | meshDict["numTris"] = file.get_32() 568 | meshDict["triIndex"] = file.get_32() 569 | meshDict["skinref"] = file.get_32() 570 | meshDict["numNorms"] = file.get_32() 571 | meshDict["normIndex"] = file.get_32() 572 | meshDict["triVerts"] = [] 573 | 574 | var pPos = file.get_position() 575 | file.seek(meshDict["triIndex"]) 576 | 577 | 578 | var count = 0 579 | 580 | for i in range(0,meshDict["numTris"]): 581 | var t = parseTrivert() 582 | if t == null: 583 | break 584 | meshDict["triVerts"].append(t) 585 | 586 | 587 | file.seek(pPos) 588 | return meshDict 589 | 590 | 591 | 592 | func parseTrivert(): 593 | 594 | var count = file.get_16u() 595 | 596 | if count == 0: 597 | return null 598 | var tris = [] 599 | for i in abs(count): 600 | var vertDict = {} 601 | vertDict["vertIndex"] = file.get_16() 602 | vertDict["normIndex"] = file.get_16() 603 | vertDict["s"] = file.get_16() 604 | vertDict["t"] = file.get_16() 605 | vertDict["type"] = sign(count) 606 | tris.append(vertDict) 607 | 608 | return tris 609 | 610 | func createMeshFromFan(vertices): 611 | 612 | var texture 613 | var surf = SurfaceTool.new() 614 | var mesh = Mesh.new() 615 | 616 | 617 | surf.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP) 618 | 619 | var TL = Vector2(INF,INF) 620 | var triVerts = [] 621 | for v in vertices.size(): 622 | triVerts.append(vertices[v]) 623 | 624 | 625 | surf.add_triangle_fan(triVerts,[],[],[],[]) 626 | surf.commit(mesh) 627 | var meshNode = MeshInstance.new() 628 | meshNode.mesh = mesh 629 | 630 | return meshNode 631 | 632 | 633 | func createMesh(vertices,normals,type,uv,boneIndices,textureIndex,lastTextureIdx,runningMesh=null): 634 | var test = boneMap 635 | var seq = fileDict["sequences"] 636 | 637 | var surf = runningMesh 638 | 639 | 640 | var finalV = [] 641 | var finalN = [] 642 | var finalB = [] 643 | for v in vertices.size(): 644 | surf.add_normal(normals[v]) 645 | surf.add_uv(uv[v]) 646 | 647 | var boneIndex = boneIndices[v] 648 | var vert = vertices[v] 649 | vert = bones[boneIndices[v]]["transform"].xform(vert) 650 | finalV.append(vert) 651 | finalN.append(normals[v]) 652 | finalB.append(boneIndex) 653 | 654 | 655 | 656 | if type == -1: 657 | var ret = fanToTri(finalV,finalN,uv,finalB) 658 | var triVerts = ret["verts"] 659 | var triNomrals = ret["normals"] 660 | var triUvs = ret["uvs"] 661 | var triBones = ret["bones"] 662 | for v in triVerts.size(): 663 | surf.add_normal(triNomrals[v]) 664 | surf.add_uv(triUvs[v]) 665 | surf.add_bones([triBones[v],-1,-1,-1]) 666 | surf.add_weights([1,0,0,0]) 667 | surf.add_vertex(triVerts[v]) 668 | 669 | if type == 1: 670 | var ret = stripToTri(finalV,finalN,uv,finalB) 671 | var triVerts = ret["verts"] 672 | var triNormals = ret["normals"] 673 | var triUvs = ret["uvs"] 674 | var triBones = ret["bones"] 675 | for v in triVerts.size(): 676 | surf.add_normal(triNormals[v]) 677 | surf.add_uv(triUvs[v]) 678 | surf.add_bones([triBones[v],0,0,0]) 679 | surf.add_weights([1,0,0,0]) 680 | surf.add_vertex(triVerts[v]) 681 | 682 | return runningMesh 683 | 684 | func createMat(textureIndex): 685 | 686 | if !matCache.has(textureIndex): 687 | var mat = SpatialMaterial.new() 688 | if textures == null: 689 | return mat 690 | 691 | if textures.size() == 0: 692 | return mat 693 | 694 | var textDict = textures[textureIndex] 695 | var isChrome = textDict["chrome"] 696 | var text = textDict["texture"] 697 | var transparent = textDict["transparent"] 698 | if !isChrome: 699 | mat.albedo_texture = text 700 | mat.uv1_scale.x /= text.get_width() 701 | mat.uv1_scale.y /= text.get_height() 702 | if textDict["additive"]: mat.params_blend_mode = SpatialMaterial.BLEND_MODE_ADD 703 | 704 | if textDict["transparent"]: 705 | #mat.flags_transparent = true 706 | mat.params_use_alpha_scissor = true 707 | mat.params_alpha_scissor_threshold = 1 708 | 709 | matCache[textureIndex] = mat 710 | else: 711 | var shader = load(matCapShaderPath) 712 | var shaderMat = ShaderMaterial.new() 713 | shaderMat.shader = shader 714 | shaderMat.set_shader_param("matcap_texture",text) 715 | matCache[textureIndex] = shaderMat 716 | 717 | 718 | 719 | return(matCache[textureIndex]) 720 | 721 | 722 | func boneItt3(seq): 723 | var anim = seq 724 | var numBones = fileDict["numBones"] 725 | 726 | var boneLocalPos = [] 727 | 728 | for boneIdx in numBones: 729 | var bone = bones[boneIdx] 730 | var boneRot = bone["rot"] 731 | var bonePos = bone["pos"] 732 | var animPos = seq["pos"][boneIdx] 733 | var animRot = seq["rot"][boneIdx] 734 | 735 | var boneRestTransform = getTransform(bonePos,boneRot) 736 | 737 | bone["restTransform"] = boneRestTransform 738 | 739 | var pos = bonePos + animPos 740 | var rot = boneRot + animRot 741 | 742 | 743 | var t = getTransform(pos,rot) 744 | boneLocalTransforms.append(t) 745 | boneLocalTransformsInv.append(t.inverse()) 746 | 747 | 748 | 749 | for boneIdx in numBones: 750 | var bone = bones[boneIdx] 751 | var t = boneLocalTransforms[boneIdx] 752 | var parentIdx = bone["parentIndex"] 753 | var parentBone = bones[parentIdx] 754 | 755 | while parentIdx >= 0: 756 | var pT = boneLocalTransforms[parentIdx] 757 | t = pT * t 758 | 759 | parentIdx = parentBone["parentIndex"] 760 | parentBone = bones[parentIdx] 761 | 762 | 763 | bone["transform"] = t 764 | 765 | 766 | 767 | func getVectorXZY(file): 768 | var vec = file.get_Vector32() 769 | #return Vector3(-vec.x,vec.z,vec.y) 770 | return Vector3(vec.x,vec.y,vec.z) 771 | 772 | func getVectorRot(file): 773 | var vec = file.get_Vector32() 774 | return Vector3(vec.x,vec.y,vec.z) 775 | 776 | 777 | func getTransform(pos,rot): 778 | var t = Transform.IDENTITY 779 | t.origin = pos 780 | t.basis = t.basis.rotated(Vector3(1,0,0),rot.x) 781 | t.basis = t.basis.rotated(Vector3(0,1,0),rot.y) 782 | t.basis = t.basis.rotated(Vector3(0,0,1),rot.z) 783 | return t 784 | 785 | func getTransformQuat(pos,rot): 786 | var t = Transform.IDENTITY 787 | t.origin = pos 788 | t.basis = t.basis.rotated(Vector3(1,0,0),rot.x) 789 | t.basis = t.basis.rotated(Vector3(0,1,0),rot.y) 790 | t.basis = t.basis.rotated(Vector3(0,0,1),rot.z) 791 | return t.basis.get_rotation_quat() 792 | 793 | return t 794 | 795 | 796 | func stripToTri(verts,normals,uv,boneArr): 797 | var tris = [] 798 | var tNormals = [] 799 | var tUv = [] 800 | var tBones = [] 801 | var size = verts.size()-2 802 | 803 | for i in size: 804 | if i % 2: 805 | tris += rearrange(verts,i,0,2,1) 806 | tNormals += rearrange(normals,i,0,2,1) 807 | tUv += rearrange(uv,i,0,2,1) 808 | tBones += rearrange(boneArr,i,0,2,1) 809 | 810 | else: 811 | tris += rearrange(verts,i,0,1,2) 812 | tNormals += rearrange(normals,i,0,1,2) 813 | tUv += rearrange(uv,i,0,1,2) 814 | tBones += rearrange(boneArr,i,0,1,2) 815 | 816 | 817 | return{"verts":tris,"normals":tNormals,"uvs":tUv,"bones":tBones} 818 | 819 | func fanToTri(verts,normals,uv,boneArr): 820 | var tris = [] 821 | var tNormals = [] 822 | var tUv = [] 823 | var tBones = [] 824 | var size = verts.size()-2 825 | 826 | for i in size: 827 | tris += rearrange(verts,0,0,i+1,i+2) 828 | tNormals += rearrange(normals,0,0,i+1,i+2) 829 | tUv += rearrange(uv,0,0,i+1,i+2) 830 | tBones += rearrange(boneArr,0,0,i+1,i+2) 831 | 832 | 833 | return{"verts":tris,"normals":tNormals,"uvs":tUv,"bones":tBones} 834 | 835 | 836 | 837 | func getModelNameFromPath(path): 838 | path = path.replace(".mdl","") 839 | path = path.split("/") 840 | path = path[path.size()-1] 841 | return path 842 | 843 | func rearrange(arr,i,a,b,c): 844 | return [arr[i+a],arr[i+b],arr[i+c]] 845 | 846 | func initSkel(): 847 | var skel = Skeleton.new() 848 | for b in bones.size(): 849 | var bone = bones[b] 850 | 851 | skel.add_bone(bone["name"]) 852 | 853 | for b in bones.size(): 854 | var bone = bones[b] 855 | var sBoneIdx = skel.find_bone(bone["name"]) 856 | skel.set_bone_parent(sBoneIdx,bone["parentIndex"]) 857 | skel.set_bone_rest(sBoneIdx,boneLocalTransforms[b]) 858 | 859 | return skel 860 | 861 | func initSkelAnimations(skel): 862 | var animPlayer : AnimationPlayer = AnimationPlayer.new() 863 | animPlayer.name = "anims" 864 | var firstAnim = true 865 | for seq in fileDict["sequences"]: 866 | var anim = Animation.new() 867 | var fps = seq["fps"] 868 | var numFrames = seq["numframes"] 869 | var delta = 1/fps 870 | 871 | 872 | 873 | animPlayer.add_animation(seq["name"].to_lower(),anim) 874 | anim.length = delta*numFrames 875 | 876 | 877 | if firstAnim == true: 878 | animPlayer.set_autoplay(seq["name"].to_lower()) 879 | firstAnim = false 880 | 881 | if seq["flags"] == 1: anim.loop = true 882 | 883 | for boneIdx in bones.size(): 884 | var bone = bones[boneIdx] 885 | var animParentPath = "../" + skel.name + ":" + bone["name"] 886 | var trackIdx = anim.add_track(Animation.TYPE_TRANSFORM) 887 | anim.track_set_path(trackIdx, animParentPath) 888 | 889 | var prevKey = null 890 | 891 | for f in numFrames: 892 | var frameData = seq["blends"][0][f] 893 | var allPos = frameData["pos"] 894 | var allRot = frameData["rot"] 895 | var pos = bone["pos"] + allPos[boneIdx] 896 | var rot =bone["rot"] + allRot[boneIdx] 897 | 898 | var t = boneLocalTransformsInv[boneIdx] * getTransform(pos,rot) 899 | 900 | t.translated(pos) 901 | var rotQuat = t.basis.get_rotation_quat() 902 | 903 | 904 | var key = {"location":t.origin,"rotation":rotQuat,"scale":Vector3(1,1,1)} 905 | var keyHash = key.hash() 906 | if prevKey != keyHash: 907 | anim.track_insert_key(trackIdx,f*delta,key) 908 | prevKey = keyHash 909 | 910 | skel.add_child(animPlayer) 911 | animPlayer.set_owner(skel) 912 | return 913 | 914 | 915 | func doesTextureFileExist(path): 916 | 917 | var files = [] 918 | var searchPath = path.substr(0,path.find_last("/")) + "/" 919 | var fileName = path.substr(path.find_last("/"),-1).replace("/","") 920 | fileName = fileName.substr(0,fileName.find(".")) 921 | 922 | var toFind1 = fileName+"t.mdl" 923 | var toFind2 = fileName+"T.mdl" 924 | var dir = Directory.new() 925 | 926 | dir.open(searchPath) 927 | dir.list_dir_begin() 928 | 929 | while true: 930 | var file = dir.get_next() 931 | if file == "": 932 | break 933 | elif not file.begins_with("."): 934 | files.append(file) 935 | 936 | dir.list_dir_end() 937 | 938 | for f in files: 939 | if f == toFind1 or f == toFind2: 940 | return searchPath + f 941 | 942 | return null 943 | --------------------------------------------------------------------------------