├── .gitattributes ├── .gitignore ├── README.md ├── assets ├── Montserrat-Regular.ttf ├── blsmpht.glb ├── dancer.bin ├── joy.bin ├── mixamo.bin └── tracks.bin ├── blender ├── defold_mesh_bin.py └── nla.py ├── def-mesh ├── animator.lua ├── binary.go ├── binary.lua ├── binary.script ├── bone.go ├── buffers │ └── dummy.buffer ├── checker_256_32.png ├── dummy.go ├── dummy_trans.go ├── editor │ ├── meta.editor_script │ └── reader.lua ├── materials │ ├── model.fp │ ├── model.material │ ├── model.vp │ └── transparent.material └── mesh_utils │ ├── ext.manifest │ └── src │ ├── animation.cpp │ ├── animation.h │ ├── armature.cpp │ ├── armature.h │ ├── binary.cpp │ ├── binary.h │ ├── bonego.cpp │ ├── bonego.h │ ├── instance.cpp │ ├── instance.h │ ├── mesh.cpp │ ├── mesh.h │ ├── mesh_utils.cpp │ ├── model.cpp │ ├── model.h │ ├── model_instance.cpp │ ├── model_instance.h │ ├── reader.cpp │ ├── reader.h │ ├── track.cpp │ ├── track.h │ ├── utils.cpp │ └── utils.h ├── demo ├── complex │ ├── complex.collection │ └── complex.script ├── dancer │ ├── dancer.collection │ ├── dancer.script │ ├── pants.fp │ └── pants.material ├── layers │ ├── layers.collection │ ├── layers.script │ └── mask.lua ├── mixamo │ ├── animations.lua │ ├── mixamo.collection │ └── mixamo.script └── root │ ├── root.collection │ └── root.script ├── game.project ├── imgui.ini ├── input └── game.input_binding ├── main ├── custom.render ├── custom.render_script ├── demo.script ├── main.collection └── main.script ├── manifest.private.der ├── manifest.public.der ├── sample.png ├── textures ├── complex │ ├── arm_d.png │ ├── arm_n.png │ ├── bikini_n.png │ ├── bikini_r.png │ ├── body_d.png │ ├── body_n.png │ ├── body_r.png │ ├── eye_d.png │ ├── eye_n.png │ ├── eyelash.png │ ├── hair_d.png │ ├── hair_n.png │ ├── head_d.png │ ├── head_n.png │ └── head_r.png └── mixamo │ ├── Pearl_Body_Diffuse.png │ ├── Pearl_Body_Normal.png │ ├── Pearl_Bottom_Diffuse.png │ ├── Pearl_Bottom_Normal.png │ ├── Pearl_Hair_Diffuse.png │ ├── Pearl_Hair_Gloss.png │ ├── Pearl_Hair_Normal.png │ ├── Pearl_Hair_Specular.png │ ├── Pearl_Shoes_Diffuse.png │ ├── Pearl_Top_Diffuse.png │ └── Pearl_Top_Normal.png └── todo /.gitattributes: -------------------------------------------------------------------------------- 1 | # Defold Protocol Buffer Text Files (https://github.com/github/linguist/issues/5091) 2 | *.animationset linguist-language=JSON5 3 | *.atlas linguist-language=JSON5 4 | *.camera linguist-language=JSON5 5 | *.collection linguist-language=JSON5 6 | *.collectionfactory linguist-language=JSON5 7 | *.collectionproxy linguist-language=JSON5 8 | *.collisionobject linguist-language=JSON5 9 | *.cubemap linguist-language=JSON5 10 | *.display_profiles linguist-language=JSON5 11 | *.factory linguist-language=JSON5 12 | *.font linguist-language=JSON5 13 | *.gamepads linguist-language=JSON5 14 | *.go linguist-language=JSON5 15 | *.gui linguist-language=JSON5 16 | *.input_binding linguist-language=JSON5 17 | *.label linguist-language=JSON5 18 | *.material linguist-language=JSON5 19 | *.mesh linguist-language=JSON5 20 | *.model linguist-language=JSON5 21 | *.particlefx linguist-language=JSON5 22 | *.render linguist-language=JSON5 23 | *.sound linguist-language=JSON5 24 | *.sprite linguist-language=JSON5 25 | *.spinemodel linguist-language=JSON5 26 | *.spinescene linguist-language=JSON5 27 | *.texture_profiles linguist-language=JSON5 28 | *.tilemap linguist-language=JSON5 29 | *.tilesource linguist-language=JSON5 30 | 31 | # Defold JSON Files 32 | *.buffer linguist-language=JSON 33 | 34 | # Defold GLSL Shaders 35 | *.fp linguist-language=GLSL 36 | *.vp linguist-language=GLSL 37 | 38 | # Defold Lua Files 39 | *.editor_script linguist-language=Lua 40 | *.render_script linguist-language=Lua 41 | *.script linguist-language=Lua 42 | *.gui_script linguist-language=Lua 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.internal 2 | /build 3 | .externalToolBuilders 4 | .DS_Store 5 | Thumbs.db 6 | .lock-wscript 7 | *.pyc 8 | .project 9 | .cproject 10 | builtins 11 | blender/sample.blend1 12 | models/girl.bin 13 | /.editor_settings -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # defold-mesh-binary 2 | Export mesh of any complexity from Blender to Defold game engine. 3 | 4 | **The code was rewritten with C++ for better performance, previous version is in** [lua-mesh branch](https://github.com/abadonna/defold-mesh-binary/tree/lua-mesh) 5 | 6 | [demo on github pages](https://abadonna.github.io/defold-mesh-binary/) 7 | 8 | ## Usage 9 | 1. Use defold_mesh_bin.py to export scene from Blender (tested only in 3.0.0) 10 | 2. Add def-mesh/binary.go into the scene 11 | 3. Require module "def-mesh.binary" and call binary.load(url_to_binary_go, path_to_bin_file_in_resources) 12 | 4. For transparent materials, add "trans" predicate and render it after "model" predicate: 13 | ```` 14 | render.set_depth_mask(false) 15 | render.disable_state(render.STATE_CULL_FACE) 16 | render.draw(self.trans_pred) 17 | render.set_depth_mask(true) 18 | ```` 19 | 20 | ![pcss](https://github.com/abadonna/defold-mesh-binary/blob/main/sample.png) 21 | 22 | ## Features 23 | * Binary format 24 | * Half precision floats 25 | * Exports all the meshes 26 | * Base color, specular power, roughness and texture for every material (so far only "Principled BSDF") 27 | * Normal map, reflection map, roughness map 28 | * Blend shapes 29 | * Multiple materials per mesh 30 | * Bones 31 | * Bone animations on GPU 32 | * Blend shape animations 33 | * Transparent materials 34 | * Animation tracks 35 | * Animations baked in textures (for performance) 36 | * Editor script to generate animation masks 37 | * Editor script to extract material list 38 | 39 | ## Experimental 40 | * Root Motion (only on based track) 41 | * GameObjects can be attached to bones 42 | 43 | ## Drawbacks 44 | * Meshes are not visible in Editor, only in runtime 45 | There is a dummy model in binary.go, so any mesh can be set on it to make scene more obvious in editor. Dummy model is disabled in runtime. 46 | 47 | * It looks like animations (mostly Root Motion ones) should be (optionally) interpolated between frames to avoid jitter. 48 | 49 | 50 | --- -------------------------------------------------------------------------------- /assets/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/assets/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /assets/blsmpht.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/assets/blsmpht.glb -------------------------------------------------------------------------------- /assets/dancer.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/assets/dancer.bin -------------------------------------------------------------------------------- /assets/joy.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/assets/joy.bin -------------------------------------------------------------------------------- /assets/mixamo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/assets/mixamo.bin -------------------------------------------------------------------------------- /assets/tracks.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/assets/tracks.bin -------------------------------------------------------------------------------- /blender/defold_mesh_bin.py: -------------------------------------------------------------------------------- 1 | # See https://docs.blender.org/api/blender_python_api_2_78_0/info_tutorial_addon.html for more info 2 | 3 | bl_info = { 4 | "name": "Defold Mesh Binary Export", 5 | "author": "", 6 | "version": (2, 5), 7 | "blender": (3, 0, 0), 8 | "location": "File > Export > Defold Binary Mesh (.bin)", 9 | "description": "Export to Defold .mesh format", 10 | "warning": "", 11 | "wiki_url": "", 12 | "tracker_url": "", 13 | "category": "Import-Export" 14 | } 15 | 16 | import bpy, sys, struct, time, mathutils 17 | from pathlib import Path 18 | 19 | def write_shape_values(mesh, shapes, f, ff): 20 | for shape in shapes: 21 | s = mesh.shape_keys.key_blocks.get(shape['name']) 22 | value = float("{:.2f}".format(s.value)) 23 | f.write(struct.pack('i', len(shape['name']))) 24 | f.write(bytes(shape['name'], "ascii")) 25 | f.write(struct.pack(ff, value)) 26 | 27 | def sort_weights(vg): 28 | return -vg.weight 29 | 30 | def set_frame(context, frame): 31 | context.scene.frame_set(frame) 32 | #there is an issue with IK bones (and maybe other simulations?) 33 | #looks like Blender needs some time to compute it 34 | context.scene.frame_set(frame) #twice to make sure IK computations is done? 35 | context.view_layer.update() #not enough? 36 | depsgraph = context.evaluated_depsgraph_get() 37 | 38 | 39 | def write_some_data(context, filepath, export_anim_setting, export_hidden_settings, export_hp_settings): 40 | 41 | f = open(filepath, 'wb') 42 | 43 | f.write(struct.pack('i', 1 if export_hp_settings else 0)) 44 | 45 | float_format = 'e' if export_hp_settings else 'f' 46 | 47 | armatures = [] 48 | armature_map = {} 49 | objects = [] 50 | 51 | #---------------------find-armatures----------------------- 52 | for obj in context.scene.objects: 53 | if obj.type != 'MESH': 54 | continue 55 | 56 | if obj.mode == 'EDIT': 57 | obj.mode_set(mode='OBJECT', toggle=False) 58 | 59 | mesh = obj.data 60 | 61 | mesh.calc_loop_triangles() 62 | mesh.calc_normals_split() 63 | 64 | if len(mesh.loop_triangles) == 0: 65 | continue 66 | 67 | if not obj.visible_get() and not export_hidden_settings: 68 | continue 69 | 70 | objects.append(obj) 71 | 72 | armature = obj.find_armature() 73 | 74 | if armature and (armature.name not in armature_map): 75 | armatures.append({'armature': armature, 'bones': []}) 76 | armature_map[armature.name] = len(armatures) - 1 77 | 78 | #---------------------optimize-armatures-------------------- 79 | #sometimes armatures contain a lot of not used bones! 80 | for proxy in armatures: 81 | bones_map = {bone.name: False for bone in proxy['armature'].pose.bones} 82 | 83 | def add_parent(bone): 84 | if bone.parent: 85 | bones_map[bone.parent.name] = True 86 | add_parent(bone.parent) 87 | 88 | for obj in objects: 89 | if obj.find_armature() != proxy['armature']: 90 | continue 91 | 92 | for vert in obj.data.vertices: 93 | fixed_groups = [] 94 | for wgrp in vert.groups: 95 | group = obj.vertex_groups[wgrp.group] 96 | if wgrp.weight > 0 and group.name in bones_map: 97 | fixed_groups.append(wgrp) 98 | #bones_map[group.name] = True 99 | 100 | fixed_groups.sort(key = sort_weights) 101 | fixed_groups = fixed_groups[:4] 102 | 103 | for wgrp in fixed_groups: 104 | group = obj.vertex_groups[wgrp.group] 105 | bones_map[group.name] = True 106 | 107 | 108 | for bone in proxy['armature'].pose.bones: 109 | if bones_map[bone.name]: 110 | add_parent(bone) 111 | 112 | for bone in proxy['armature'].pose.bones: 113 | if bones_map[bone.name]: 114 | proxy['bones'].append(bone) 115 | 116 | #-----------------------get-frames------------------------- 117 | 118 | # def collect_frame_data(): 119 | # for proxy in armatures: 120 | # data = [] 121 | # for pbone in proxy['bones']: 122 | # matrix = pbone.matrix_basis 123 | # data.append(matrix) 124 | # proxy['frames'].append(data) 125 | # 126 | # current_frame = context.scene.frame_current 127 | # if export_anim_setting: 128 | # for frame in range(1, context.scene.frame_end): 129 | # set_frame(context, frame) 130 | # collect_frame_data() 131 | # 132 | # set_frame(context, current_frame) 133 | # else: 134 | # collect_frame_data() 135 | 136 | 137 | #---------------------write-armatures----------------------- 138 | 139 | f.write(struct.pack('i', len(armatures))) 140 | 141 | current_frame = context.scene.frame_current 142 | f4 = float_format * 4 143 | 144 | for proxy in armatures: 145 | 146 | f.write(struct.pack('i', len(proxy['bones']))) 147 | 148 | for pbone in proxy['bones']: 149 | f.write(struct.pack('i', len(pbone.name))) 150 | f.write(bytes(pbone.name, "ascii")) 151 | parent = -1 152 | for idx, b in enumerate(proxy['bones']): 153 | if b == pbone.parent: 154 | parent = idx 155 | break 156 | f.write(struct.pack('i', parent)) 157 | 158 | matrix = pbone.bone.matrix_local 159 | f.write(struct.pack(f4, *matrix[0])) 160 | f.write(struct.pack(f4, *matrix[1])) 161 | f.write(struct.pack(f4, *matrix[2])) 162 | 163 | if export_anim_setting: 164 | f.write(struct.pack('i', context.scene.frame_end - 1)) 165 | 166 | for frame in range(1, context.scene.frame_end): 167 | set_frame(context, frame) 168 | for pbone in proxy['bones']: 169 | matrix = pbone.matrix_basis 170 | f.write(struct.pack(f4, *matrix[0])) 171 | f.write(struct.pack(f4, *matrix[1])) 172 | f.write(struct.pack(f4, *matrix[2])) 173 | 174 | set_frame(context, current_frame) #as we work with object's global matrix in particular frame 175 | 176 | else: 177 | f.write(struct.pack('i', 1)) #single frame flag 178 | for pbone in proxy['bones']: 179 | matrix = pbone.matrix_basis 180 | f.write(struct.pack(f4, *matrix[0])) 181 | f.write(struct.pack(f4, *matrix[1])) 182 | f.write(struct.pack(f4, *matrix[2])) 183 | 184 | 185 | #---------------------write-models----------------------- 186 | 187 | f3 = float_format * 3 188 | 189 | for obj in objects: 190 | print(obj.name) 191 | 192 | mesh = obj.data 193 | 194 | f.write(struct.pack('i', len(obj.name))) 195 | f.write(bytes(obj.name, "ascii")) 196 | 197 | if obj.parent: 198 | f.write(struct.pack('i', len(obj.parent.name))) 199 | f.write(bytes(obj.parent.name, "ascii")) 200 | else: 201 | f.write(struct.pack('i', 0)) #no parent flag 202 | 203 | 204 | t = obj.matrix_world @ obj.matrix_local.inverted() #to remove local transfrom - we will apply it to vertices 205 | 206 | (translation, rotation, scale) = t.decompose() 207 | f.write(struct.pack(f3, *translation)) 208 | f.write(struct.pack(f3, *rotation.to_euler())) 209 | f.write(struct.pack(f3, *scale)) 210 | 211 | 212 | #---------------------read-materials----------------------- 213 | def find_nodes_to(socket, type): 214 | for link in socket.links: 215 | node = link.from_node 216 | if node.bl_static_type == type: 217 | return [node] 218 | else: 219 | for input in node.inputs: 220 | n = find_nodes_to(input, type) 221 | if n: 222 | n.append(node) 223 | return n 224 | return None 225 | 226 | def find_node(socket, type): 227 | nodes = find_nodes_to(socket, type) 228 | if nodes: 229 | #print([n.bl_static_type for n in nodes]) 230 | return nodes[0] 231 | return None 232 | 233 | def find_texture(socket): 234 | node = find_node(socket, 'TEX_IMAGE') 235 | if node != None: 236 | texture = Path(node.image.filepath).name 237 | return texture 238 | 239 | return None 240 | 241 | def find_ramp(socket): 242 | node = find_node(socket, 'VALTORGB') 243 | if node and node.color_ramp: 244 | ramp = node.color_ramp 245 | e1 = ramp.elements[0] 246 | e2 = ramp.elements[len(ramp.elements)-1] 247 | return {'p1': e1.position, 'v1': e1.color[0], 'p2': e2.position, 'v2': e2.color[0]} 248 | #simplified ramp, just intensity of first and last element, enough for specular\roughness 249 | 250 | materials = [] 251 | for m in mesh.materials: 252 | #print("--------------------------") 253 | #print("material", m.name, m.blend_method) 254 | 255 | if m == None: #create default material 256 | material = {'name': 'default', 'col': [0.7, 0.7, 0.7, 1], 'method': 'OPAQUE', 'spec_power': 0.0, 'roughness': 0.0, 'normal_strength': 1.0} 257 | materials.append(material) 258 | continue 259 | 260 | material = {'name': m.name, 'col': m.diffuse_color, 'method': m.blend_method, 'spec_power': 0.0, 'roughness': 0.0, 'normal_strength': 1.0} 261 | materials.append(material) 262 | 263 | if m.node_tree: 264 | 265 | for principled in m.node_tree.nodes: 266 | if principled.bl_static_type != 'BSDF_PRINCIPLED': 267 | continue 268 | 269 | specular = principled.inputs['Specular'] 270 | material['spec_power'] = specular.default_value 271 | material['specular'] = find_texture(specular) 272 | 273 | if material.get('specular'): 274 | material['specular_invert'] = 1 if find_node(specular, 'INVERT') else 0 275 | material['specular_ramp'] = find_ramp(specular) 276 | 277 | roughness = principled.inputs['Roughness'] 278 | material['roughness'] = roughness.default_value 279 | material['roughness_tex'] = find_texture(roughness) 280 | material['roughness_ramp'] = find_ramp(roughness) 281 | 282 | normal_map = find_node(principled.inputs['Normal'], 'NORMAL_MAP') 283 | if normal_map: 284 | material['normal'] = find_texture(normal_map.inputs['Color']) 285 | material['normal_strength'] = normal_map.inputs['Strength'].default_value 286 | else: #Look in the group 287 | group = find_node(principled.inputs['Normal'], 'GROUP') 288 | if group: 289 | normal_map = group.inputs.get("Normal Map") 290 | if normal_map: 291 | material['normal'] = find_texture(normal_map) 292 | str = group.inputs.get('Normal Strength') 293 | material['normal_strength'] = str and str.default_value or 1. 294 | 295 | 296 | base_color = principled.inputs['Base Color'] 297 | value = base_color.default_value 298 | 299 | is_subsurface = principled.inputs['Subsurface'].default_value == 1. 300 | 301 | material['col'] = [value[0], value[1], value[2], principled.inputs['Alpha'].default_value] 302 | 303 | if is_subsurface: 304 | material['texture'] = find_texture(principled.inputs['Subsurface Color']) 305 | else: 306 | material['texture'] = find_texture(base_color) 307 | 308 | if material.get('texture') != None: 309 | #print(material['texture']) 310 | break #TODO objects with combined shaders 311 | 312 | 313 | if len(materials) == 0: #create default material 314 | material = {'name': 'default', 'col': [0.7, 0.7, 0.7, 1], 'method': 'OPAQUE', 'spec_power': 0.0, 'roughness': 0.0, 'normal_strength': 1.0} 315 | materials.append(material) 316 | 317 | #---------------------write-geometry------------------------ 318 | 319 | f.write(struct.pack('i', len(mesh.vertices))) 320 | 321 | for vert in mesh.vertices: 322 | v = obj.matrix_local @ vert.co #apply local transform, 323 | #or we have to deal with it calculating bones 324 | 325 | f.write(struct.pack(f3, *v)) 326 | 327 | v = (obj.matrix_local @ vert.normal).normalized() 328 | f.write(struct.pack(f3, *v)) 329 | 330 | shapes = [] 331 | if mesh.shape_keys and len(mesh.shape_keys.key_blocks) > 0: 332 | for shape in mesh.shape_keys.key_blocks: 333 | s = {'name': shape.name, 'deltas': [], 'value':shape.value} 334 | normals = shape.normals_vertex_get() 335 | for i in range(len(shape.data)): 336 | vert = mesh.vertices[i] 337 | if (shape.data[i].co - vert.co).length > 0.001: 338 | dpos = (obj.matrix_local @ shape.data[i].co) - (obj.matrix_local @ vert.co) 339 | s['deltas'].append({'idx': i, 'p': dpos, 'n': obj.matrix_local @ mathutils.Vector((normals[i*3] - vert.normal.x, normals[i*3 + 1] - vert.normal.y, normals[i*3 + 2]- vert.normal.z))}) 340 | 341 | if len(s['deltas']) > 0: 342 | shapes.append(s) 343 | 344 | f.write(struct.pack('i', len(shapes))) 345 | for shape in shapes: 346 | f.write(struct.pack('i', len(shape['name']))) 347 | f.write(bytes(shape['name'], "ascii")) 348 | f.write(struct.pack('i', len(shape['deltas']))) 349 | for vert in shape['deltas']: 350 | f.write(struct.pack('i', vert['idx'])) 351 | f.write(struct.pack(f3, *vert['p'])) 352 | f.write(struct.pack(f3, *vert['n'])) 353 | 354 | f.write(struct.pack('i', len(mesh.loop_triangles))) 355 | 356 | uv = [] 357 | for face in mesh.loop_triangles: 358 | f.write(struct.pack('iii', *face.vertices)) 359 | f.write(struct.pack('i', face.material_index)) 360 | 361 | for loop_idx in face.loops: 362 | uv_cords = mesh.uv_layers.active.data[loop_idx].uv if mesh.uv_layers.active else (0, 0) 363 | uv.extend(uv_cords) 364 | 365 | if not face.use_smooth: 366 | f.write(struct.pack('i', 1)) 367 | v = (obj.matrix_local @ face.normal).normalized() 368 | f.write(struct.pack(f3, *v)) 369 | else: 370 | f.write(struct.pack('i', 0)) 371 | 372 | f.write(struct.pack(float_format * len(uv), *uv)) 373 | 374 | f.write(struct.pack('i', len(materials))) 375 | 376 | #---------------------write-materials------------------------ 377 | for material in materials: 378 | print("MATERIAL " + material['name']) 379 | 380 | f.write(struct.pack('i', len(material['name']))) 381 | f.write(bytes(material['name'], "ascii")) 382 | 383 | method = 1 384 | if material['method'] == 'OPAQUE': 385 | method = 0 386 | elif material['method'] == 'HASHED': 387 | method = 2 388 | 389 | f.write(struct.pack('i', method)) 390 | f.write(struct.pack(float_format * 4, *material['col'])) 391 | f.write(struct.pack(float_format, material['spec_power'])) 392 | f.write(struct.pack(float_format, material['roughness'])) 393 | 394 | if material.get('texture') == None: 395 | f.write(struct.pack('i', 0)) #no texture flag 396 | else: 397 | f.write(struct.pack('i', len(material['texture']))) 398 | f.write(bytes(material['texture'], "ascii")) 399 | 400 | if material.get('normal') == None: 401 | f.write(struct.pack('i', 0)) #no normal texture flag 402 | else: 403 | f.write(struct.pack('i', len(material['normal']))) 404 | f.write(bytes(material['normal'], "ascii")) 405 | f.write(struct.pack(float_format, material['normal_strength'])) 406 | 407 | if material.get('specular') == None: 408 | f.write(struct.pack('i', 0)) #no texture flag 409 | else: 410 | f.write(struct.pack('i', len(material['specular']))) 411 | f.write(bytes(material['specular'], "ascii")) 412 | f.write(struct.pack('i', material['specular_invert'])) 413 | if material.get('specular_ramp') == None: 414 | f.write(struct.pack('i', 0)) #no ramp flag 415 | else: 416 | f.write(struct.pack('i', 1)) 417 | f.write(struct.pack(float_format, material['specular_ramp']['p1'])) 418 | f.write(struct.pack(float_format, material['specular_ramp']['v1'])) 419 | f.write(struct.pack(float_format, material['specular_ramp']['p2'])) 420 | f.write(struct.pack(float_format, material['specular_ramp']['v2'])) 421 | 422 | if material.get('roughness_tex') == None: 423 | f.write(struct.pack('i', 0)) #no roughbess texture flag 424 | else: 425 | f.write(struct.pack('i', len(material['roughness_tex']))) 426 | f.write(bytes(material['roughness_tex'], "ascii")) 427 | if material.get('roughness_ramp') == None: 428 | f.write(struct.pack('i', 0)) #no ramp flag 429 | else: 430 | f.write(struct.pack('i', 1)) 431 | f.write(struct.pack(float_format, material['roughness_ramp']['p1'])) 432 | f.write(struct.pack(float_format, material['roughness_ramp']['v1'])) 433 | f.write(struct.pack(float_format, material['roughness_ramp']['p2'])) 434 | f.write(struct.pack(float_format, material['roughness_ramp']['v2'])) 435 | 436 | 437 | #f.close() 438 | #return {'FINISHED'} 439 | 440 | #---------------------write-bones------------------------ 441 | 442 | armature = obj.find_armature() 443 | max_bones_per_vertex = 4 444 | 445 | if armature: 446 | pose = armature.pose 447 | proxy = armatures[armature_map[armature.name]] 448 | 449 | f.write(struct.pack('i', armature_map[armature.name])) #id of saved armature 450 | 451 | bones_map = {bone.name: i for i, bone in enumerate(proxy['bones'])} 452 | for vert in mesh.vertices: 453 | fixed_groups = [] 454 | for wgrp in vert.groups: 455 | group = obj.vertex_groups[wgrp.group] 456 | if wgrp.weight > 0 and group.name in bones_map: 457 | fixed_groups.append(wgrp) 458 | 459 | fixed_groups.sort(key = sort_weights) 460 | fixed_groups = fixed_groups[:max_bones_per_vertex] 461 | 462 | total = 0 463 | for vg in fixed_groups: 464 | total = total + vg.weight 465 | for vg in fixed_groups: 466 | vg.weight = vg.weight / total 467 | 468 | f.write(struct.pack('i', len(fixed_groups))) 469 | 470 | for wgrp in fixed_groups: 471 | group = obj.vertex_groups[wgrp.group] 472 | bone_idx = bones_map[group.name] 473 | f.write(struct.pack('i', bone_idx)) 474 | f.write(struct.pack(float_format, wgrp.weight)) 475 | 476 | #f.write(struct.pack('i', 1 if export_precompute_setting else 0)) 477 | else: 478 | f.write(struct.pack('i', -1)) #no bones flag 479 | 480 | 481 | if export_anim_setting and len(shapes) > 0: 482 | f.write(struct.pack('i', context.scene.frame_end - 1)) 483 | 484 | for frame in range(1, context.scene.frame_end): 485 | set_frame(context, frame) 486 | 487 | write_shape_values(mesh, shapes, f, float_format) 488 | 489 | set_frame(context, current_frame) #as we work with object's global matrix in particular frame 490 | 491 | elif len(shapes) > 0: 492 | f.write(struct.pack('i', 1)) #single frame flag 493 | write_shape_values(mesh, shapes, f, float_format) 494 | 495 | else: 496 | f.write(struct.pack('i', 0)) #no shape animations 497 | 498 | f.close() 499 | 500 | return {'FINISHED'} 501 | 502 | 503 | # ExportHelper is a helper class, defines filename and 504 | # invoke() function which calls the file selector. 505 | from bpy_extras.io_utils import ExportHelper 506 | from bpy.props import StringProperty, BoolProperty, EnumProperty 507 | from bpy.types import Operator 508 | 509 | class DefoldExport(Operator, ExportHelper): 510 | """This appears in the tooltip of the operator and in the generated docs""" 511 | bl_idname = "export_mesh.defold_binary" # important since its how bpy.ops.import_test.some_data is constructed 512 | bl_label = "Export binary" 513 | bl_description = "Export Defold binary mesh data" 514 | 515 | # ExportHelper mixin class uses this 516 | filename_ext = ".bin" 517 | 518 | filter_glob: StringProperty( 519 | default="*.bin", 520 | options={'HIDDEN'}, 521 | maxlen=255, # Max internal buffer length, longer would be clamped. 522 | ) 523 | 524 | export_anim: BoolProperty( 525 | name="Export animations", 526 | description="Only for armatures", 527 | default=False, 528 | ) 529 | 530 | export_hidden: BoolProperty( 531 | name="Export hidden meshes", 532 | description="", 533 | default=False, 534 | ) 535 | 536 | export_halfprecision: BoolProperty( 537 | name="Half precision floats", 538 | description="2 bytes float numbers", 539 | default=False, 540 | ) 541 | 542 | 543 | def execute(self, context): 544 | return write_some_data(context, self.filepath, self.export_anim, self.export_hidden, self.export_halfprecision) 545 | 546 | 547 | # Only needed if you want to add into a dynamic menu 548 | def menu_func_export(self, context): 549 | self.layout.operator(DefoldExport.bl_idname, text="Defold Binary Mesh (.bin)") 550 | 551 | # Register and add to the "file selector" menu (required to use F3 search "Text Export Operator" for quick access) 552 | def register(): 553 | bpy.utils.register_class(DefoldExport) 554 | bpy.types.TOPBAR_MT_file_export.append(menu_func_export) 555 | 556 | 557 | def unregister(): 558 | bpy.utils.unregister_class(DefoldExport) 559 | bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) 560 | 561 | 562 | if __name__ == "__main__": 563 | register() 564 | 565 | # test call 566 | bpy.ops.export_mesh.defold_binary('INVOKE_DEFAULT') 567 | -------------------------------------------------------------------------------- /blender/nla.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | def export_animations(context, filepath): 5 | obj = context.active_object 6 | if obj.type != 'ARMATURE': 7 | print("Not an armature") 8 | return 9 | 10 | file = open(filepath, 'w') 11 | file.write('return {\n') 12 | 13 | animation_data = {} 14 | scene = bpy.context.scene 15 | 16 | if obj.animation_data and obj.animation_data.nla_tracks: 17 | for track in obj.animation_data.nla_tracks: 18 | for strip in track.strips: 19 | file.write('\t' + strip.name + ' = {start = ' + str(strip.frame_start-1) + ', finish = ' + str(strip.frame_end-1) + '},\n') 20 | 21 | file.write('}') 22 | file.close() 23 | return {'FINISHED'} 24 | 25 | # ExportHelper is a helper class, defines filename and 26 | # invoke() function which calls the file selector. 27 | from bpy_extras.io_utils import ExportHelper 28 | from bpy.types import Operator 29 | 30 | class NLAExport(Operator, ExportHelper): 31 | """This appears in the tooltip of the operator and in the generated docs""" 32 | bl_idname = "export_nla.defold" # important since its how bpy.ops.import_test.some_data is constructed 33 | bl_label = "Export nla" 34 | bl_description = "Export nla tracks data" 35 | 36 | # ExportHelper mixin class uses this 37 | filename_ext = ".lua" 38 | 39 | def execute(self, context): 40 | return export_animations(context, self.filepath) 41 | 42 | 43 | bpy.utils.register_class(NLAExport) 44 | bpy.ops.export_nla.defold('INVOKE_DEFAULT') 45 | -------------------------------------------------------------------------------- /def-mesh/animator.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | RM_NONE = 0 4 | RM_ROTATION = 1 5 | RM_POSITION = 2 6 | RM_BOTH = 3 7 | RM_FORWARD = 4 8 | 9 | local function update_blend(self, dt) 10 | 11 | if self.blend then 12 | self.blend.factor = math.max(0, 1.0 - self.total_time / self.blend.duration) 13 | if self.blend.factor == 0 then 14 | if self.blend.animation and self.blend.animation.callback and not self.blend.animation.is_completed then 15 | self.blend.animation.callback(self.blend.animation.frame) 16 | end 17 | self.blend = nil 18 | elseif self.blend.animation then 19 | self.blend.animation:update(dt) 20 | end 21 | self.total_time = self.total_time + dt 22 | end 23 | 24 | end 25 | 26 | local function single_frame_update(self, dt) 27 | if self.is_completed then return end 28 | 29 | update_blend(self, dt) 30 | 31 | self.changed = true 32 | 33 | if not self.blend then 34 | self.is_completed = true 35 | end 36 | 37 | if (self.time == 0) and (self.motion > RM_NONE) then 38 | self.reset_root_motion() 39 | end 40 | 41 | self.time = self.time + dt 42 | end 43 | 44 | local function animation_update(self, dt) 45 | if self.is_completed then return end 46 | 47 | update_blend(self, dt) 48 | 49 | local a = self.time / self.duration 50 | local full, part = math.modf(a) 51 | 52 | local frame = self.start + math.floor(self.length * part) 53 | 54 | if full >= 1 then --TODO loop 55 | frame = self.start + 1 -- skip first frame on loop 56 | self.time = 2 / self.fps -- self.time - self.duration * full 57 | end 58 | 59 | 60 | if (frame == self.finish or full >= 1) and self.playback == go.PLAYBACK_ONCE_FORWARD then 61 | self.is_completed = true 62 | end 63 | 64 | if frame ~= self.frame then 65 | self.frame = frame 66 | self.changed = true 67 | end 68 | 69 | if ((frame == self.start) or (full >=1) ) and (self.changed) and (self.motion > RM_NONE) then 70 | self.reset_root_motion() 71 | end 72 | 73 | self.time = self.time + dt 74 | end 75 | 76 | M.create = function(binary) 77 | local animator = { 78 | binary = binary, 79 | list = {}, 80 | animations = {}, 81 | frame = {[0]= {idx = 0, motion = RM_NONE} }, 82 | tracks = {[0]={weight=1}} 83 | } 84 | 85 | animator.set_frame = function(track, frame1, frame2, blend, rm1, rm2) 86 | animator.frame[track] = {idx = frame1, motion = rm1 or RM_NONE} 87 | --pprint(frame1 .. ":" .. frame2 .. ", rm: " .. rm1 .. ", " .. rm2) 88 | animator.binary:set_frame(track, frame1, frame2 == nil and -1 or frame2, blend or 0, rm1 or RM_NONE, rm2 or RM_NONE) 89 | end 90 | 91 | animator.update_tracks = function() 92 | animator.binary:update() 93 | end 94 | 95 | animator.stop = function(track) 96 | for i, a in ipairs(animator.animations) do 97 | if a.track == track then 98 | if a.callback then 99 | a.callback(false) 100 | end 101 | table.remove(animator.animations, i) 102 | break 103 | end 104 | end 105 | end 106 | 107 | animator.play = function(animation, config, callback) 108 | --[[ 109 | config: 110 | track - track id 111 | blend_duration - seconds to blend with previous animation 112 | fps - frames per second 113 | playback - once or looped 114 | root_motion- RM_ROTATION, RM_POSITION, RM_BOTH, RM_FORWARD 115 | 116 | --]] 117 | if type(animation) == "string" then 118 | animation = { 119 | start = animator.list[animation].start, 120 | finish = animator.list[animation].finish 121 | } 122 | else 123 | animation = { 124 | start = animation.start, 125 | finish = animation.finish 126 | } 127 | end 128 | 129 | config = config or {} 130 | local blend_duration = config.blend_duration or 0 131 | 132 | animation.track = config.track or 0 133 | animation.fps = config.fps or 30 134 | animation.callback = callback 135 | animation.length = animation.finish - animation.start + 1 136 | animation.duration = animation.length / animation.fps 137 | animation.playback = config.playback or go.PLAYBACK_ONCE_FORWARD 138 | --so far supported only PLAYBACK_LOOP_FORWARD & PLAYBACK_ONCE_FORWARD 139 | animation.motion = config.root_motion or RM_NONE 140 | animation.primary = true 141 | 142 | animation.reset_root_motion = function() 143 | if animation.track > 0 then return end -- TODO 144 | animator.binary:reset_root_motion(animation.primary, animation.start) 145 | end 146 | 147 | animation.time = 0 148 | animation.total_time = 0 149 | animation.update = animation_update 150 | 151 | if animation.start == animation.finish then -- just set or blend to frame 152 | animation.frame = animation.start 153 | animation.update = single_frame_update 154 | if animation.track == 0 then 155 | animator.binary:switch_root_motion() 156 | end 157 | end 158 | 159 | for i, a in ipairs(animator.animations) do 160 | if a.track == animation.track then 161 | a.blend = nil -- blend only 2 animations 162 | a.primary = false 163 | if animation.track == 0 then 164 | animator.binary:switch_root_motion() 165 | end 166 | if blend_duration > 0 then 167 | animation.blend = { 168 | animation = a, 169 | duration = blend_duration 170 | } 171 | end 172 | 173 | table.remove(animator.animations, i) 174 | break 175 | end 176 | end 177 | 178 | if blend_duration > 0 and not animation.blend then --blend with current frame 179 | if animation.track == 0 then 180 | animator.binary:switch_root_motion() 181 | end 182 | animation.blend = { 183 | frame = animator.frame[animation.track] or animator.frame[0], 184 | duration = blend_duration 185 | } 186 | end 187 | 188 | table.insert(animator.animations, animation) 189 | end 190 | 191 | animator.update = function(dt) 192 | local completed = {} 193 | 194 | for i = #animator.animations, 1, -1 do 195 | local a = animator.animations[i] 196 | a:update(dt) 197 | if a.changed then 198 | a.changed = false 199 | local blend_frame_idx = -1 200 | local blend_motion = RM_NONE 201 | if a.blend then 202 | blend_frame_idx = a.blend.frame and a.blend.frame.idx or a.blend.animation.frame 203 | blend_motion = a.blend.frame and a.blend.frame.motion or a.blend.animation.motion 204 | end 205 | animator.set_frame(a.track, a.frame, blend_frame_idx, 206 | a.blend and a.blend.factor or 0, 207 | a.motion, blend_motion) 208 | end 209 | if a.is_completed then 210 | table.remove(animator.animations, i) 211 | table.insert(completed, a) 212 | end 213 | end 214 | 215 | for track_id, data in pairs(animator.tracks) do 216 | if data.duration then --animate weight 217 | data.time = data.time + dt 218 | local a = data.time / data.duration 219 | local full, part = math.modf(a) 220 | local length = data.finish - data.start 221 | local weight = data.start + length * part 222 | 223 | if full > 0 then 224 | data.duration = nil 225 | animator.set_weight(track_id, data.finish) 226 | else 227 | animator.set_weight(track_id, weight) 228 | end 229 | end 230 | end 231 | 232 | animator.update_tracks() 233 | 234 | for _, a in ipairs(completed) do 235 | if a.callback then a.callback(a.frame) end 236 | end 237 | 238 | end 239 | 240 | animator.add_track = function(mask, weight) 241 | local id = animator.binary:add_animation_track(mask) 242 | animator.tracks[id] = {} 243 | if weight and weight < 1 then 244 | animator.set_weight(id, weight) 245 | end 246 | return id 247 | end 248 | 249 | animator.set_weight = function(track_id, weight, duration) 250 | if duration then 251 | animator.tracks[track_id] = { 252 | start = animator.tracks[track_id].weight or 1, 253 | duration = duration, 254 | finish = weight, 255 | time = 0 256 | } 257 | 258 | return 259 | end 260 | animator.tracks[track_id].weight = weight 261 | animator.binary:set_animation_track_weight(track_id, weight) 262 | end 263 | 264 | return animator 265 | end 266 | 267 | 268 | return M -------------------------------------------------------------------------------- /def-mesh/binary.go: -------------------------------------------------------------------------------- 1 | components { 2 | id: "binary" 3 | component: "/def-mesh/binary.script" 4 | } 5 | embedded_components { 6 | id: "factory" 7 | type: "factory" 8 | data: "prototype: \"/def-mesh/dummy.go\"\n" 9 | "" 10 | } 11 | embedded_components { 12 | id: "dummy" 13 | type: "model" 14 | data: "mesh: \"/builtins/assets/meshes/sphere.dae\"\n" 15 | "name: \"{{NAME}}\"\n" 16 | "materials {\n" 17 | " name: \"default\"\n" 18 | " material: \"/builtins/materials/model.material\"\n" 19 | " textures {\n" 20 | " sampler: \"tex0\"\n" 21 | " texture: \"/def-mesh/checker_256_32.png\"\n" 22 | " }\n" 23 | "}\n" 24 | "" 25 | } 26 | embedded_components { 27 | id: "factory_bone" 28 | type: "factory" 29 | data: "prototype: \"/def-mesh/bone.go\"\n" 30 | "" 31 | } 32 | embedded_components { 33 | id: "factory_trans" 34 | type: "factory" 35 | data: "prototype: \"/def-mesh/dummy_trans.go\"\n" 36 | "" 37 | } 38 | -------------------------------------------------------------------------------- /def-mesh/binary.lua: -------------------------------------------------------------------------------- 1 | local ANIMATOR = require "def-mesh.animator" 2 | 3 | local M = {} 4 | 5 | M.BUFFER_COUNT = 0 6 | 7 | local function create_buffer(buf) 8 | M.BUFFER_COUNT = M.BUFFER_COUNT and M.BUFFER_COUNT + 1 or 1 9 | local path = "/def-mesh/buffers/buffer" .. M.BUFFER_COUNT .. ".bufferc" 10 | return resource.create_buffer(path, {buffer = buf, transfer_ownership = false}), path 11 | end 12 | 13 | function native_update_buffer(mesh_url, buffer) 14 | resource.set_buffer(go.get(mesh_url, "vertices"), buffer, {transfer_ownership = false}) 15 | end 16 | 17 | function native_runtime_texture(tpath, width, height, buffer) 18 | local tparams = { 19 | width = width, 20 | height = height, 21 | type = resource.TEXTURE_TYPE_2D, 22 | format = resource.TEXTURE_FORMAT_RGBA32F, 23 | } 24 | resource.set_texture(tpath, tparams, buffer) 25 | end 26 | 27 | local function get_animation_texture(path, model, runtime) 28 | 29 | local tpath = (runtime and path or string.match(path, "(%a-)[$%.]")) .. "_" .. model.armature 30 | tpath = "/__anim_" .. tpath:gsub("[%./]", "") ..".texturec" 31 | 32 | if not pcall(function() 33 | resource.get_texture_info(tpath) 34 | end) then 35 | local twidth, theight, tbuffer = model:get_animation_buffer(runtime) 36 | local tparams = { 37 | width = twidth, 38 | height = theight, 39 | type = resource.TEXTURE_TYPE_2D, 40 | format = resource.TEXTURE_FORMAT_RGBA32F, 41 | } 42 | 43 | return resource.create_texture(tpath, tparams, tbuffer) 44 | else 45 | return hash(tpath) 46 | end 47 | end 48 | 49 | local function set_texture(self, url, slot, file, texel) 50 | if not file then 51 | return false 52 | end 53 | local data = sys.load_resource(self.texture_folder .. file) 54 | if not data then 55 | pprint(self.texture_folder .. file .. " not found") 56 | return false 57 | end 58 | 59 | local path = self.texture_folder .. file ..".texturec" 60 | local img = imageloader.load{data = data} 61 | 62 | local texture_id = hash(path) 63 | if not pcall(function() 64 | texture_id = resource.create_texture(path, img.header, img.buffer) 65 | end) then 66 | --already exists? 67 | end 68 | self.textures[texture_id] = true 69 | go.set(url, slot, texture_id) 70 | if texel then 71 | pcall(function() 72 | go.set(url, texel, vmath.vector4(1./img.header.width, 1./img.header.height, 0, 0)) 73 | end) 74 | end 75 | return true 76 | end 77 | 78 | local function get_bone_go(self, bone) 79 | if self.bones_go[bone] then 80 | return self.bones_go[bone] 81 | end 82 | 83 | local id = factory.create(msg.url(self.url.socket, self.url.path, "factory_bone")) 84 | 85 | local parent = self.binary:attach_bone_go(id, bone) 86 | if parent then 87 | go.set_parent(id, parent) 88 | self.bones_go[bone] = id 89 | 90 | return id 91 | end 92 | 93 | return nil 94 | end 95 | 96 | --[[ 97 | url - url to binary.go instance 98 | path - path to .bin file in custom assets 99 | config = { 100 | verbose - true to get models info 101 | textures - path to folder with textures 102 | bake - true to bake animations into texture 103 | aabb - float, scale factor - force aabb creation scaled with this value 104 | (for frustum culling skinned meshes) 105 | materials - table of materials to replace, 106 | use editor script "Add materials from model" to generate properties 107 | } 108 | --]] 109 | M.load = function(url, path, config) 110 | url = type(url) == "string" and msg.url(nil, url, nil) or url 111 | 112 | local instance = { 113 | meshes = {}, 114 | attaches = {}, 115 | bones_go = {}, 116 | textures = {}, 117 | game_objects = {}, 118 | url = url, 119 | uid = math.random(0, 10000000) 120 | } 121 | 122 | config = config or {} 123 | 124 | instance.texture_folder = config.textures or "/assets/" 125 | if string.find(instance.texture_folder, "/") ~= 1 then 126 | instance.texture_folder = "/" .. instance.texture_folder 127 | end 128 | if string.find(instance.texture_folder, "/$") == nil then 129 | instance.texture_folder = instance.texture_folder .. "/" 130 | end 131 | 132 | local models 133 | local data = sys.load_resource(path) 134 | 135 | instance.binary, models = mesh_utils.load(path, data, url, config.bake or false, config.verbose or false, config.aabb or 0) 136 | instance.animator = ANIMATOR.create(instance.binary) 137 | instance.models = {} 138 | 139 | for name, model in pairs(models) do 140 | 141 | instance.models[name] = {} 142 | instance.total_frames = model.frames 143 | 144 | local anim_texture, runtime_texture 145 | 146 | if model.frames > 1 and config.bake then 147 | anim_texture = get_animation_texture(path, model) 148 | runtime_texture = get_animation_texture(instance.uid, model, true) 149 | model:set_runtime_texture(runtime_texture) 150 | end 151 | 152 | for i, mesh in ipairs(model.meshes) do 153 | 154 | --local f = mesh.material.type == 0 and "#factory" or "#factory_trans" 155 | --local id = factory.create(url .. f, model.position, model.rotation, {}, model.scale) 156 | 157 | local id = factory.create(msg.url(nil, url.path, "factory"), model.position, model.rotation, {}, model.scale) 158 | local mesh_url = msg.url(nil, id, "mesh") 159 | mesh:set_url(mesh_url); 160 | 161 | local material = nil 162 | local materials = msg.url(nil, url.path, "binary") 163 | pcall(function() material = go.get(materials, mesh.material.name) end) 164 | 165 | if config.materials and config.materials[mesh.material.name] then 166 | go.set(mesh_url, "material", config.materials[mesh.material.name]) 167 | elseif material then 168 | go.set(mesh_url, "material", material) 169 | elseif mesh.material.type == 0 then 170 | go.set(mesh_url, "material", go.get(materials, "opaque")) 171 | else 172 | go.set(mesh_url, "material", go.get(materials, "transparent")) 173 | end 174 | 175 | local v, bpath = create_buffer(mesh.buffer) 176 | go.set(mesh_url, "vertices", v) 177 | 178 | instance.game_objects[i == 1 and name or (name .. "_" .. i)] = 179 | { 180 | id = id, 181 | url = mesh_url, 182 | path = bpath, 183 | parent = model.parent 184 | } 185 | 186 | table.insert(instance.models[name], id) 187 | 188 | local options = vmath.vector4(0.0) 189 | local options_specular = vmath.vector4(0.0) 190 | options_specular.y = mesh.material.specular.value 191 | options_specular.z = mesh.material.roughness.value 192 | 193 | options.z = mesh.material.type 194 | 195 | if anim_texture then 196 | instance.textures[anim_texture] = true; 197 | instance.textures[runtime_texture] = true; 198 | go.set(mesh_url, "texture0", anim_texture) 199 | go.set(mesh_url, "texture4", runtime_texture) 200 | end 201 | 202 | if mesh.material.texture then 203 | set_texture(instance, mesh_url, "texture1", mesh.material.texture, "texel") 204 | options.x = 1.0 205 | end 206 | 207 | if mesh.material.normal and 208 | set_texture(instance, mesh_url, "texture2", mesh.material.normal.texture) 209 | then 210 | options.y = mesh.material.normal.value 211 | end 212 | 213 | if mesh.material.specular.texture and 214 | set_texture(instance, mesh_url, "texture3", mesh.material.specular.texture) 215 | then 216 | options_specular.x = 1.0 + mesh.material.specular.invert 217 | if mesh.material.specular.ramp then 218 | local r = mesh.material.specular.ramp 219 | go.set(mesh_url, "spec_ramp", vmath.vector4(r.p1, r.v1, r.p2, r.v2)) 220 | end 221 | end 222 | 223 | if mesh.material.roughness.texture and 224 | set_texture(instance, mesh_url, "texture3", mesh.material.roughness.texture) 225 | then 226 | -- roughness and specular are usually the same? 227 | options_specular.w = 1.0 228 | if mesh.material.roughness.ramp then 229 | local r = mesh.material.roughness.ramp 230 | go.set(mesh_url, "rough_ramp", vmath.vector4(r.p1, r.v1, r.p2, r.v2)) 231 | end 232 | end 233 | 234 | pcall(function() 235 | go.set(mesh_url, "base_color", mesh.material and mesh.material.color or vmath.vector4(0.8,0.8,0.8,1)) 236 | go.set(mesh_url, "options", options) 237 | go.set(mesh_url, "options_specular", options_specular) 238 | end) 239 | end 240 | 241 | end 242 | 243 | 244 | --set hierarchy 245 | for _, mesh in pairs(instance.game_objects) do 246 | if mesh.parent and instance.game_objects[mesh.parent] then 247 | go.set_parent(mesh.id, instance.game_objects[mesh.parent].id, false) 248 | else 249 | go.set_parent(mesh.id, url, false) 250 | end 251 | end 252 | 253 | instance.binary:set_frame(0, 0); 254 | instance.binary:update(); 255 | 256 | 257 | --------------------------------------------------------------- 258 | instance.hide = function(name) 259 | for _, id in ipairs(instance.models[name]) do 260 | msg.post(id, "disable") 261 | end 262 | end 263 | 264 | --------------------------------------------------------------- 265 | instance.show = function(name) 266 | for _, id in ipairs(instance.models[name]) do 267 | msg.post(id, "enable") 268 | end 269 | end 270 | 271 | --------------------------------------------------------------- 272 | 273 | instance.delete = function(with_objects) 274 | if with_objects then 275 | go.delete(instance.url, true) 276 | end 277 | 278 | for key, mesh in pairs(instance.game_objects) do 279 | resource.release(mesh.path) 280 | end 281 | 282 | if instance.binary then 283 | if instance.binary:delete() then --clean up textures, no more instances 284 | for texture, _ in pairs(instance.textures) do 285 | resource.release(texture) 286 | end 287 | end 288 | end 289 | 290 | end 291 | 292 | instance.set_frame = function(frame1, frame2, blend) -- use animator directly for more flexible approach 293 | instance.animator.set_frame(0, frame1, frame2, blend) 294 | instance.animator.update_tracks() 295 | end 296 | 297 | instance.attach = function(bone, target_url) 298 | local id = get_bone_go(instance, bone) 299 | go.set_parent(target_url, id, true) 300 | end 301 | 302 | instance.set_shapes = function(shapes) 303 | instance.binary:set_shapes(shapes) 304 | end 305 | 306 | instance.set = function(property, value) 307 | for _, mesh in pairs(instance.game_objects) do 308 | go.set(mesh.url, property, value) 309 | end 310 | end 311 | 312 | return instance 313 | end 314 | 315 | 316 | 317 | 318 | return M -------------------------------------------------------------------------------- /def-mesh/binary.script: -------------------------------------------------------------------------------- 1 | go.property('opaque', resource.material("/def-mesh/materials/model.material")) 2 | go.property('transparent', resource.material("/def-mesh/materials/transparent.material")) 3 | 4 | function init(self) 5 | msg.post("#dummy", "disable") 6 | end 7 | -------------------------------------------------------------------------------- /def-mesh/bone.go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/def-mesh/bone.go -------------------------------------------------------------------------------- /def-mesh/buffers/dummy.buffer: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "position", 4 | "type": "float32", 5 | "count": 3, 6 | "data": [] 7 | }, 8 | { 9 | "name": "normal", 10 | "type": "float32", 11 | "count": 3, 12 | "data": [] 13 | }, 14 | { 15 | "name": "texcoord0", 16 | "type": "float32", 17 | "count": 2, 18 | "data": [] 19 | }, 20 | { 21 | "name": "weight", 22 | "type": "float32", 23 | "count": 4, 24 | "data": [] 25 | }, 26 | { 27 | "name": "bone", 28 | "type": "uint8", 29 | "count": 4, 30 | "data": [] 31 | } 32 | ] -------------------------------------------------------------------------------- /def-mesh/checker_256_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/def-mesh/checker_256_32.png -------------------------------------------------------------------------------- /def-mesh/dummy.go: -------------------------------------------------------------------------------- 1 | embedded_components { 2 | id: "mesh" 3 | type: "mesh" 4 | data: "material: \"/def-mesh/materials/model.material\"\n" 5 | "vertices: \"/def-mesh/buffers/dummy.buffer\"\n" 6 | "textures: \"/def-mesh/checker_256_32.png\"\n" 7 | "textures: \"/def-mesh/checker_256_32.png\"\n" 8 | "textures: \"/def-mesh/checker_256_32.png\"\n" 9 | "textures: \"/def-mesh/checker_256_32.png\"\n" 10 | "primitive_type: PRIMITIVE_TRIANGLES\n" 11 | "position_stream: \"position\"\n" 12 | "normal_stream: \"normal\"\n" 13 | "" 14 | position { 15 | x: 0.0 16 | y: 0.0 17 | z: 0.0 18 | } 19 | rotation { 20 | x: 0.0 21 | y: 0.0 22 | z: 0.0 23 | w: 1.0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /def-mesh/dummy_trans.go: -------------------------------------------------------------------------------- 1 | embedded_components { 2 | id: "mesh" 3 | type: "mesh" 4 | data: "material: \"/def-mesh/materials/transparent.material\"\n" 5 | "vertices: \"/def-mesh/buffers/dummy.buffer\"\n" 6 | "textures: \"/def-mesh/checker_256_32.png\"\n" 7 | "primitive_type: PRIMITIVE_TRIANGLES\n" 8 | "position_stream: \"position\"\n" 9 | "normal_stream: \"normal\"\n" 10 | "" 11 | position { 12 | x: 0.0 13 | y: 0.0 14 | z: 0.0 15 | } 16 | rotation { 17 | x: 0.0 18 | y: 0.0 19 | z: 0.0 20 | w: 1.0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /def-mesh/editor/meta.editor_script: -------------------------------------------------------------------------------- 1 | reader = require "def-mesh.editor.reader" 2 | 3 | local M = {} 4 | 5 | local function ends_with(str, ending) 6 | return ending == "" or str:sub(-#ending) == ending 7 | end 8 | 9 | local function get_filename(path) 10 | local main, filename, _ = path:match("(.-)([^\\/]-%.?([^%.\\/]*))$") 11 | return main, filename 12 | end 13 | 14 | function M.get_language_servers() 15 | return {} 16 | end 17 | 18 | function M.get_commands() 19 | return { 20 | { 21 | label = "Add materials from model", 22 | locations = {"Edit", "Assets", "Outline"}, 23 | query = { 24 | selection = {type = "resource", cardinality = "one"} 25 | }, 26 | active = function(opts) 27 | local path = editor.get(opts.selection, "path") 28 | return ends_with(path, ".script") 29 | end, 30 | run = function(opts) 31 | local text = editor.get(opts.selection, "text") 32 | local path = editor.ui.show_resource_dialog({title = "Select binary", extensions = {"bin"}}) 33 | 34 | if path then 35 | reader.init(path:sub(2)) 36 | local materials = reader.read_materials() 37 | for _, material in ipairs(materials) do 38 | text = "go.property('" .. material.name .. "', resource.material('/def-mesh/materials/" .. material.type .. ".material'))\n" .. text 39 | end 40 | editor.transact({ 41 | editor.tx.set(opts.selection, "text", text) 42 | }) 43 | editor.ui.open_resource(editor.get(opts.selection, "path")) 44 | end 45 | end 46 | }, 47 | 48 | { 49 | label = "New animation mask", 50 | locations = {"Assets"}, 51 | query = { 52 | selection = {type = "resource", cardinality = "one"} 53 | }, 54 | active = function(opts) 55 | return ends_with(editor.get(opts.selection, "path"), ".bin") 56 | end, 57 | run = function(opts) 58 | local path = editor.get(opts.selection, "path"):sub(2) 59 | reader.init(path) 60 | local bones = reader.read_bones() 61 | 62 | local children = {} 63 | for i, bone in ipairs(bones) do 64 | table.insert(children, editor.ui.check_box( 65 | { 66 | value = true, 67 | text = bone.name, 68 | on_value_changed = function(value) 69 | bone.enabled = value 70 | end 71 | })) 72 | end 73 | 74 | file_name = path:gsub(".bin", ".lua") 75 | 76 | local file = editor.ui.horizontal({ 77 | padding = editor.ui.PADDING.SMALL, 78 | spacing = editor.ui.SPACING.SMALL, 79 | children = { 80 | editor.ui.label({ 81 | text = "File Name", 82 | alignment = editor.ui.ALIGNMENT.CENTER 83 | }), 84 | editor.ui.string_field({ 85 | grow = true, 86 | value = file_name, 87 | -- Typing callback: 88 | on_value_changed = function(new_text) 89 | file_name = new_text 90 | end 91 | }) 92 | } 93 | }) 94 | 95 | 96 | local result = editor.ui.show_dialog(editor.ui.dialog({ 97 | title = "Animation Mask", 98 | 99 | content = editor.ui.vertical({ 100 | padding = editor.ui.PADDING.SMALL, 101 | children = { 102 | editor.ui.scroll({ 103 | content = editor.ui.vertical({ 104 | padding = editor.ui.PADDING.SMALL, -- add padding around dialog edges 105 | children = children 106 | }), 107 | }), 108 | editor.ui.separator({}), 109 | file, 110 | 111 | } 112 | }), 113 | 114 | 115 | buttons = { 116 | editor.ui.dialog_button({ 117 | text = "Cancel", 118 | cancel = true, 119 | result = false 120 | }), 121 | editor.ui.dialog_button({ 122 | text = "Create", 123 | default = true, 124 | result = true 125 | }) 126 | } 127 | })) 128 | 129 | if result then 130 | local f = io.open(file_name, "w+") 131 | f:write("return {\n\tmask = {\n") 132 | 133 | for _, bone in ipairs(bones) do 134 | f:write(bone.enabled and "\t\t" or "\t\t--", "\"" .. bone.name .. "\",\n") 135 | end 136 | f:write("\t}\n}") 137 | io.close(f) 138 | end 139 | 140 | end 141 | } 142 | 143 | } 144 | end 145 | 146 | return M 147 | -------------------------------------------------------------------------------- /def-mesh/editor/reader.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | 4 | M.init = function(path) 5 | local f = io.open(path, "r") 6 | M.content = f:read("*a") 7 | M.index = 1 8 | io.close(f) 9 | end 10 | 11 | M.read_materials = function() 12 | local materials = {} 13 | 14 | M.half_precision = M.read_int() == 1 15 | 16 | --skip armatures 17 | for i = 1, M.read_int() do 18 | local count = M.read_int() 19 | for bone = 1, count do 20 | M.read_string() 21 | M.read_int() 22 | M.read_vec4() 23 | M.read_vec4() 24 | M.read_vec4() 25 | end 26 | 27 | for frame = 1, M.read_int() do 28 | for bone = 1, count do 29 | M.read_vec4() 30 | M.read_vec4() 31 | M.read_vec4() 32 | end 33 | end 34 | end 35 | 36 | while not M.eof() do 37 | M.read_model(materials) 38 | end 39 | return materials 40 | end 41 | 42 | M.read_model = function(materials) 43 | M.read_string() -- model name 44 | local parent = M.read_int() 45 | if parent > 0 then 46 | M.read_string(parent) 47 | end 48 | 49 | M.read_vec3() -- transform 50 | M.read_vec3() 51 | M.read_vec3() 52 | 53 | local vert_count = M.read_int() 54 | for vertex = 1, vert_count do --vertices 55 | M.read_vec3() 56 | M.read_vec3() 57 | end 58 | 59 | local shape_count = M.read_int() 60 | for shape = 1, shape_count do --shapes 61 | M.read_string() 62 | for delta = 1, M.read_int() do 63 | M.read_int() 64 | M.read_vec3() 65 | M.read_vec3() 66 | end 67 | end 68 | 69 | local count = M.read_int() 70 | for face = 1, count do --faces 71 | M.read_int() 72 | M.read_int() 73 | M.read_int() 74 | M.read_int() 75 | local flat = M.read_int() 76 | if flat == 1 then 77 | M.read_vec3() 78 | end 79 | end 80 | 81 | for face = 1, count * 6 do --uv 82 | M.read_float() 83 | end 84 | 85 | for material = 1, M.read_int() do --materials 86 | local name = M.read_string() 87 | local type = M.read_int() 88 | table.insert(materials, {name = name, type = (type == 0 and "model" or "transparent")}) 89 | M.read_vec4() -- color 90 | M.read_float() -- specular 91 | M.read_float() -- roughness 92 | local flag = M.read_int() 93 | if flag > 0 then -- texture 94 | M.read_string(flag) 95 | end 96 | flag = M.read_int() 97 | if flag > 0 then -- normal texture 98 | M.read_string(flag) 99 | M.read_float() 100 | end 101 | flag = M.read_int() 102 | if flag > 0 then -- specular texture 103 | M.read_string(flag) 104 | M.read_int() 105 | flag = M.read_int() 106 | if flag > 0 then --ramp 107 | M.read_float() 108 | M.read_float() 109 | M.read_float() 110 | M.read_float() 111 | end 112 | end 113 | flag = M.read_int() 114 | if flag > 0 then -- roughness texture 115 | M.read_string(flag) 116 | M.read_int() 117 | flag = M.read_int() 118 | if flag > 0 then --ramp 119 | M.read_float() 120 | M.read_float() 121 | M.read_float() 122 | M.read_float() 123 | end 124 | end 125 | 126 | end 127 | 128 | if M.read_int() > -1 then --skin 129 | for vertex = 1, vert_count do 130 | for w = 1, M.read_int() do 131 | M.read_int() 132 | M.read_float() 133 | end 134 | end 135 | end 136 | 137 | for frame = 1, M.read_int() do --frames 138 | for i = 1, shape_count do 139 | M.read_string() 140 | M.read_float() 141 | end 142 | end 143 | 144 | end 145 | 146 | M.read_bones = function() 147 | M.half_precision = M.read_int() == 1 148 | 149 | local count = M.read_int() -- number of armatures 150 | count = M.read_int() -- number of bones in first armature 151 | 152 | local bones = {} 153 | for i = 1, count do 154 | table.insert(bones, {name = M.read_string(), enabled = true}) 155 | M.read_int() --parent 156 | M.read_vec4() 157 | M.read_vec4() 158 | M.read_vec4() 159 | end 160 | 161 | return bones 162 | 163 | end 164 | 165 | M.eof = function() 166 | return M.index > #M.content 167 | end 168 | 169 | M.read_string = function(size) 170 | local res = "" 171 | size = size or M.read_int() 172 | for i = 1, size do 173 | local b = string.byte(M.content, M.index) 174 | res = res .. string.char(b) 175 | M.index = M.index + 1 176 | end 177 | return res 178 | end 179 | 180 | M.read_int = function() 181 | local b4 = string.byte(M.content, M.index) 182 | local b3 = string.byte(M.content, M.index + 1) 183 | local b2 = string.byte(M.content, M.index + 2) 184 | local b1 = string.byte(M.content, M.index + 3) 185 | 186 | local n = b1 * 16777216 + b2 * 65536 + b3 * 256 + b4 187 | 188 | M.index = M.index + 4 189 | n = (n > 2147483647) and (n - 4294967296) or n 190 | return n 191 | end 192 | 193 | M.read_float_hp = function() 194 | M.index = M.index + 2 195 | return 0 196 | end 197 | 198 | --http://lua-users.org/lists/lua-l/2010-03/msg00910.html 199 | M.read_float = function() 200 | if M.half_precision then return M.read_float_hp() end 201 | 202 | local sign = 1 203 | 204 | local mantissa = string.byte(M.content, M.index + 2) % 128 205 | for i = 1, 0, -1 do mantissa = mantissa * 256 + string.byte(M.content, M.index + i) end 206 | if string.byte(M.content, M.index + 3) > 127 then sign = -1 end 207 | local exponent = (string.byte(M.content, M.index + 3) % 128) * 2 + math.floor(string.byte(M.content, M.index + 2) / 128) 208 | M.index = M.index + 4 209 | if exponent == 0 then return 0 end 210 | mantissa = (math.ldexp(mantissa, -23) + 1) * sign 211 | return math.ldexp(mantissa, exponent - 127) 212 | end 213 | 214 | M.read_vec3 = function() 215 | return {M.read_float(), M.read_float(), M.read_float()} 216 | end 217 | 218 | M.read_vec4 = function() 219 | return {M.read_float(), M.read_float(), M.read_float(), M.read_float()} 220 | end 221 | 222 | 223 | return M -------------------------------------------------------------------------------- /def-mesh/materials/model.fp: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in highp vec4 var_position; 4 | in highp vec3 var_normal; 5 | in highp vec2 var_texcoord0; 6 | 7 | in vec3 var_light_dir; 8 | in vec3 var_vh; 9 | 10 | uniform sampler2D tex_anim; 11 | uniform sampler2D tex_diffuse; 12 | uniform sampler2D tex_normal; 13 | uniform sampler2D tex_rough; 14 | 15 | uniform uniforms_fp { 16 | vec4 texel; 17 | vec4 options; 18 | //x - texture, 19 | //y - normal map strength, 20 | //z - alpha mode, 0 - opaque, 1 - blend, 2 - hashed 21 | 22 | vec4 base_color; 23 | 24 | vec4 options_specular; 25 | //x - 1: specular map, 2: specular map inverted 26 | //y - specular power 27 | //z - roughness 28 | //w - roughness map 29 | 30 | vec4 spec_ramp; 31 | vec4 rough_ramp; 32 | //x - p1, y - v1, z = p2, w = v2 33 | // if x == -1 - no ramp 34 | }; 35 | 36 | out vec4 frag_color; 37 | 38 | float ramp(float factor, vec4 data) 39 | { 40 | if (data.x == -1.) {return factor;} 41 | if (factor <= data.x) {return data.y;} 42 | if (factor >= data.z) {return data.w;} 43 | float f = (factor - data.x) / (data.z - data.x); 44 | return mix(data.y, data.w, f); 45 | } 46 | 47 | vec4 pcf_4x4(vec2 proj) { 48 | vec4 sum = vec4(0.); 49 | float x, y; 50 | 51 | for (y = -1.5; y <= 1.5; y += 1.0) { 52 | for (x = -1.5; x <= 1.5; x += 1.0) { 53 | vec2 uv = proj.xy + texel.xy * vec2(x, y); 54 | if (uv.x < 0. ||uv.x > 1. || uv.y < 0. ||uv.y > 1.) {continue;} 55 | sum += texture(tex_diffuse, uv); 56 | } 57 | } 58 | return sum / 16; 59 | } 60 | 61 | //http://www.thetenthplanet.de/archives/1180 62 | highp mat3 cotangent_frame(highp vec3 N, highp vec3 p, highp vec2 uv) 63 | { 64 | // get edge vectors of the pixel triangle 65 | highp vec3 dp1 = dFdx(p); 66 | highp vec3 dp2 = dFdy(p); 67 | highp vec2 duv1 = dFdx(uv); 68 | highp vec2 duv2 = dFdy(uv); 69 | // solve the linear system 70 | highp vec3 dp2perp = cross(dp2, N); 71 | highp vec3 dp1perp = cross(N, dp1); 72 | highp vec3 T = dp2perp * duv1.x + dp1perp * duv2.x; 73 | highp vec3 B = dp2perp * duv1.y + dp1perp * duv2.y; 74 | // construct a scale-invariant frame 75 | highp float invmax = inversesqrt(max(dot(T,T), dot(B,B))); 76 | return mat3(T * invmax, B * invmax, N); 77 | } 78 | 79 | void main() 80 | { 81 | vec4 color = base_color; 82 | 83 | if (options.x == 1.) {color = texture(tex_diffuse, var_texcoord0);} 84 | 85 | if (options.z == 2. && color.w > 0 && color.w < 1) { 86 | color = pcf_4x4(var_texcoord0); 87 | } 88 | 89 | //----------------------- 90 | // Diffuse light calculations 91 | vec3 ambient = vec3(0.2); 92 | vec3 specular = vec3(0.0); 93 | highp vec3 n = var_normal; 94 | 95 | if (options.y > 0.0) { 96 | n = texture(tex_normal, var_texcoord0).xyz * 255./127. - 128./127.; 97 | n.xy *= options.y; 98 | highp mat3 TBN = cotangent_frame(normalize(var_normal), var_position.xyz, var_texcoord0); 99 | n = TBN * n; 100 | } 101 | n = normalize(n); 102 | 103 | float light = max(dot(n, var_light_dir), 0.0); 104 | if (light > 0.0) { 105 | float roughness = options_specular.w > 0.0 ? texture(tex_rough, var_texcoord0).x : options_specular.z; 106 | roughness = ramp(roughness, rough_ramp); 107 | float k = mix(1.0, 0.4, roughness); 108 | roughness = 32. / (roughness * roughness); 109 | roughness = min(500.0, roughness); 110 | float sp = options_specular.x > 0.0 ? texture(tex_rough, var_texcoord0).x : options_specular.y; 111 | vec3 spec_power = vec3(ramp(sp, spec_ramp)); 112 | if (options_specular.x > 1.0) {spec_power = 1.0 - spec_power;} ///invert flag 113 | 114 | specular = k * k * spec_power * pow(max(dot(n, var_vh), 0.0), roughness); 115 | } 116 | 117 | vec3 diffuse = light + ambient; 118 | diffuse = clamp(diffuse, 0.0, 1.0); 119 | 120 | frag_color = vec4(color.xyz * diffuse + specular, color.w); 121 | } 122 | 123 | -------------------------------------------------------------------------------- /def-mesh/materials/model.material: -------------------------------------------------------------------------------- 1 | name: "model" 2 | tags: "model" 3 | vertex_program: "/def-mesh/materials/model.vp" 4 | fragment_program: "/def-mesh/materials/model.fp" 5 | vertex_space: VERTEX_SPACE_LOCAL 6 | vertex_constants { 7 | name: "mtx_worldview" 8 | type: CONSTANT_TYPE_WORLDVIEW 9 | } 10 | vertex_constants { 11 | name: "mtx_view" 12 | type: CONSTANT_TYPE_VIEW 13 | } 14 | vertex_constants { 15 | name: "mtx_proj" 16 | type: CONSTANT_TYPE_PROJECTION 17 | } 18 | vertex_constants { 19 | name: "mtx_normal" 20 | type: CONSTANT_TYPE_NORMAL 21 | } 22 | vertex_constants { 23 | name: "light" 24 | type: CONSTANT_TYPE_USER 25 | value { 26 | x: 2.0 27 | y: 2.0 28 | z: 2.0 29 | w: 1.0 30 | } 31 | } 32 | vertex_constants { 33 | name: "animation" 34 | type: CONSTANT_TYPE_USER 35 | value { 36 | } 37 | } 38 | fragment_constants { 39 | name: "texel" 40 | type: CONSTANT_TYPE_USER 41 | value { 42 | x: 1.0 43 | y: 1.0 44 | z: 1.0 45 | w: 1.0 46 | } 47 | } 48 | fragment_constants { 49 | name: "options" 50 | type: CONSTANT_TYPE_USER 51 | value { 52 | } 53 | } 54 | fragment_constants { 55 | name: "options_specular" 56 | type: CONSTANT_TYPE_USER 57 | value { 58 | } 59 | } 60 | fragment_constants { 61 | name: "base_color" 62 | type: CONSTANT_TYPE_USER 63 | value { 64 | x: 0.8 65 | y: 0.8 66 | z: 0.8 67 | w: 1.0 68 | } 69 | } 70 | fragment_constants { 71 | name: "rough_ramp" 72 | type: CONSTANT_TYPE_USER 73 | value { 74 | x: -1.0 75 | } 76 | } 77 | fragment_constants { 78 | name: "spec_ramp" 79 | type: CONSTANT_TYPE_USER 80 | value { 81 | x: -1.0 82 | } 83 | } 84 | samplers { 85 | name: "tex_anim" 86 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 87 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 88 | filter_min: FILTER_MODE_MIN_NEAREST 89 | filter_mag: FILTER_MODE_MAG_NEAREST 90 | } 91 | samplers { 92 | name: "tex_diffuse" 93 | wrap_u: WRAP_MODE_REPEAT 94 | wrap_v: WRAP_MODE_REPEAT 95 | filter_min: FILTER_MODE_MIN_LINEAR 96 | filter_mag: FILTER_MODE_MAG_LINEAR 97 | } 98 | samplers { 99 | name: "tex_normal" 100 | wrap_u: WRAP_MODE_REPEAT 101 | wrap_v: WRAP_MODE_REPEAT 102 | filter_min: FILTER_MODE_MIN_LINEAR 103 | filter_mag: FILTER_MODE_MAG_LINEAR 104 | } 105 | samplers { 106 | name: "tex_rough" 107 | wrap_u: WRAP_MODE_REPEAT 108 | wrap_v: WRAP_MODE_REPEAT 109 | filter_min: FILTER_MODE_MIN_LINEAR 110 | filter_mag: FILTER_MODE_MAG_LINEAR 111 | } 112 | samplers { 113 | name: "tex_runtime" 114 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 115 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 116 | filter_min: FILTER_MODE_MIN_NEAREST 117 | filter_mag: FILTER_MODE_MAG_NEAREST 118 | } 119 | -------------------------------------------------------------------------------- /def-mesh/materials/model.vp: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in highp vec4 position; 4 | in highp vec2 texcoord0; 5 | in highp vec3 normal; 6 | in vec4 weight; 7 | in vec4 bone; 8 | 9 | uniform sampler2D tex_anim; 10 | uniform sampler2D tex_runtime; 11 | 12 | uniform uniforms_vp { 13 | highp vec4 animation; //w - baked or runtime baked 14 | 15 | highp mat4 mtx_worldview; 16 | highp mat4 mtx_view; 17 | highp mat4 mtx_proj; 18 | highp mat4 mtx_normal; 19 | mediump vec4 light; 20 | 21 | #ifdef GL_ES 22 | vec4 bones[450]; 23 | #else 24 | vec4 bones[800]; 25 | #endif 26 | }; 27 | 28 | out highp vec4 var_position; 29 | out highp vec3 var_normal; 30 | out highp vec2 var_texcoord0; 31 | out vec3 var_light_dir; 32 | out vec3 var_vh; 33 | 34 | mat4 get_bone_matrix(int idx) { 35 | highp mat4 mtx_bone; 36 | int offset = idx * 3; 37 | 38 | if (animation.x == 0.) { 39 | mtx_bone[0] = bones[offset]; 40 | mtx_bone[1] = bones[offset + 1]; 41 | mtx_bone[2] = bones[offset + 2]; 42 | }else if (animation.w == 0.) { 43 | mtx_bone[0] = texture(tex_anim, vec2(offset * animation.x, animation.y)); 44 | mtx_bone[1] = texture(tex_anim, vec2((offset + 1) * animation.x, animation.y)); 45 | mtx_bone[2] = texture(tex_anim, vec2((offset + 2) * animation.x, animation.y)); 46 | }else 47 | { 48 | mtx_bone[0] = texture(tex_runtime, vec2(offset * animation.x, animation.y)); 49 | mtx_bone[1] = texture(tex_runtime, vec2((offset + 1) * animation.x, animation.y)); 50 | mtx_bone[2] = texture(tex_runtime, vec2((offset + 2) * animation.x, animation.y)); 51 | } 52 | 53 | mtx_bone = transpose(mtx_bone); 54 | 55 | //transposed matrix simplifies reading 4x3 matrix, 56 | //using 4x3 lets us to use more bones per mesh 57 | //and increase performance while sending data to shader 58 | return mtx_bone; 59 | } 60 | 61 | vec4 apply_bones(vec3 pos, out vec3 n) { 62 | vec4 result = vec4(0.0); 63 | vec3 no_trans = vec3(0.0); 64 | 65 | for (int i = 0; i < 4; i++) { 66 | if (weight[i] == 0.0) {break;} 67 | mat4 m = get_bone_matrix(int(bone[i])); 68 | vec4 v = m * vec4(pos.x, pos.y, pos.z, 1.0); 69 | result += v * weight[i]; 70 | 71 | vec3 v2 = mat3(m) * normal; 72 | no_trans += v2 * weight[i]; 73 | } 74 | n = vec3(no_trans.x, no_trans.z, -no_trans.y); 75 | return vec4(result.x, result.z, -result.y, 1.0); //to fix blender coordinates 76 | } 77 | 78 | void main() 79 | { 80 | highp vec4 p = vec4(position.x, position.z, -position.y, 1.0); 81 | highp vec3 n = vec3(normal.x, normal.z, -normal.y); 82 | 83 | if (weight.x > 0.0) { 84 | p = apply_bones(position.xyz, n); 85 | } 86 | 87 | p = mtx_worldview * p; 88 | 89 | vec4 view_light = mtx_view * vec4(light.xyz, 1.0); 90 | var_light_dir = normalize(view_light.xyz - p.xyz); 91 | vec3 eye_dir = normalize(-p.xyz); 92 | var_vh = normalize(var_light_dir + eye_dir); 93 | 94 | var_position = p; 95 | var_texcoord0 = texcoord0; 96 | 97 | var_normal = normalize((mtx_normal * vec4(n, 1.0)).xyz); 98 | gl_Position = mtx_proj * p; 99 | } 100 | 101 | -------------------------------------------------------------------------------- /def-mesh/materials/transparent.material: -------------------------------------------------------------------------------- 1 | name: "transparent" 2 | tags: "trans" 3 | vertex_program: "/def-mesh/materials/model.vp" 4 | fragment_program: "/def-mesh/materials/model.fp" 5 | vertex_space: VERTEX_SPACE_LOCAL 6 | vertex_constants { 7 | name: "mtx_worldview" 8 | type: CONSTANT_TYPE_WORLDVIEW 9 | } 10 | vertex_constants { 11 | name: "mtx_view" 12 | type: CONSTANT_TYPE_VIEW 13 | } 14 | vertex_constants { 15 | name: "mtx_proj" 16 | type: CONSTANT_TYPE_PROJECTION 17 | } 18 | vertex_constants { 19 | name: "mtx_normal" 20 | type: CONSTANT_TYPE_NORMAL 21 | } 22 | vertex_constants { 23 | name: "light" 24 | type: CONSTANT_TYPE_USER 25 | value { 26 | x: 2.0 27 | y: 2.0 28 | z: 2.0 29 | w: 1.0 30 | } 31 | } 32 | vertex_constants { 33 | name: "animation" 34 | type: CONSTANT_TYPE_USER 35 | value { 36 | } 37 | } 38 | fragment_constants { 39 | name: "texel" 40 | type: CONSTANT_TYPE_USER 41 | value { 42 | x: 1.0 43 | y: 1.0 44 | z: 1.0 45 | w: 1.0 46 | } 47 | } 48 | fragment_constants { 49 | name: "options" 50 | type: CONSTANT_TYPE_USER 51 | value { 52 | } 53 | } 54 | fragment_constants { 55 | name: "options_specular" 56 | type: CONSTANT_TYPE_USER 57 | value { 58 | } 59 | } 60 | fragment_constants { 61 | name: "spec_ramp" 62 | type: CONSTANT_TYPE_USER 63 | value { 64 | x: -1.0 65 | } 66 | } 67 | fragment_constants { 68 | name: "rough_ramp" 69 | type: CONSTANT_TYPE_USER 70 | value { 71 | x: -1.0 72 | } 73 | } 74 | samplers { 75 | name: "tex_anim" 76 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 77 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 78 | filter_min: FILTER_MODE_MIN_NEAREST 79 | filter_mag: FILTER_MODE_MAG_NEAREST 80 | } 81 | samplers { 82 | name: "tex_diffuse" 83 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 84 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 85 | filter_min: FILTER_MODE_MIN_LINEAR 86 | filter_mag: FILTER_MODE_MAG_LINEAR 87 | } 88 | samplers { 89 | name: "tex_normal" 90 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 91 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 92 | filter_min: FILTER_MODE_MIN_LINEAR 93 | filter_mag: FILTER_MODE_MAG_LINEAR 94 | } 95 | samplers { 96 | name: "tex_rough" 97 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 98 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 99 | filter_min: FILTER_MODE_MIN_LINEAR 100 | filter_mag: FILTER_MODE_MAG_LINEAR 101 | } 102 | samplers { 103 | name: "tex_runtime" 104 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 105 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 106 | filter_min: FILTER_MODE_MIN_NEAREST 107 | filter_mag: FILTER_MODE_MAG_NEAREST 108 | } 109 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/ext.manifest: -------------------------------------------------------------------------------- 1 | name: "mesh_utils" -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/animation.cpp: -------------------------------------------------------------------------------- 1 | #include "animation.h" 2 | 3 | Animation::Animation(Armature* armature, dmGameObject::HInstance obj) { 4 | this->armature = armature; 5 | this->root = obj; 6 | AnimationTrack base = AnimationTrack(armature, NULL); 7 | this->tracks.reserve(8); //to avoid losing pointers to calculated bones 8 | this->tracks.push_back(base); 9 | this->SetFrame(0, 0, -1, 0, RootMotionType::None, RootMotionType::None); 10 | this->Update(); 11 | 12 | } 13 | 14 | void Animation::SwitchRootMotion() { 15 | this->tracks[0].SwitchRootMotion(); 16 | } 17 | 18 | void Animation::ResetRootMotion(int frameIdx, bool isPrimary) { 19 | this->tracks[0].ResetRootMotion(frameIdx, isPrimary); 20 | } 21 | 22 | void Animation::SetTransform(Matrix4 matrix) { 23 | this->transform = matrix; 24 | this->tracks[0].SetTransform(matrix); 25 | } 26 | 27 | bool Animation::IsBlending() { 28 | int count = 0; 29 | for (AnimationTrack & track : this->tracks) { 30 | if (track.weight > 0) count++; 31 | if (track.frame2 > -1) return true; 32 | } 33 | return count > 1; 34 | } 35 | 36 | bool Animation::HasRootMotion() { 37 | return this->hasRootMotion; 38 | } 39 | 40 | void Animation::Update() { 41 | if (!this->needUpdate) return; 42 | 43 | this->bones = this->tracks[0].bones; 44 | 45 | int count = 0; 46 | for (AnimationTrack & track : this->tracks) { 47 | if ((track.weight > 0) && (track.frame1 > -1)) { 48 | count ++; 49 | if (count == 1) { 50 | this->bones = track.bones; 51 | continue; 52 | } 53 | 54 | if (count == 2) {//copy bones from base track 55 | this->cumulative = *this->bones; 56 | this->bones = &this->cumulative; 57 | } 58 | 59 | for (int & idx : track.mask) { 60 | if (track.weight == 1.0) { 61 | this->cumulative[idx] = track.bones->at(idx); 62 | }else { 63 | MatrixBlend(&this->cumulative[idx], &track.bones->at(idx), &this->cumulative[idx], track.weight); 64 | } 65 | } 66 | 67 | } 68 | } 69 | 70 | this->CalculateBones(); 71 | 72 | for(auto & obj : this->boneObjects) { 73 | obj.ApplyTransform(this->bones); 74 | } 75 | } 76 | 77 | 78 | void Animation::CalculateBones() { 79 | if (this->bones == NULL) return; 80 | 81 | int size = this->bones->size(); 82 | if (this->cumulative.size() < size) { //never acumulated, just copy to expand 83 | this->cumulative = *this->bones; 84 | } 85 | 86 | bool has_parent_transforms[size]; 87 | 88 | for (int idx = 0; idx < size; idx ++) { //precalculate parent transforms 89 | Matrix4 local = this->armature->localBones[idx]; 90 | this->cumulative[idx] = local * this->bones->at(idx) * Inverse(local); 91 | 92 | has_parent_transforms[idx] = false; 93 | } 94 | 95 | 96 | for (int idx = 0; idx < size; idx ++) { 97 | Matrix4 bone = this->cumulative[idx]; 98 | 99 | int parent = this->armature->boneParents[idx]; 100 | while (parent > -1) { 101 | bone = this->cumulative[parent] * bone; 102 | if (has_parent_transforms[parent]) break; //optimization 103 | parent = this->armature->boneParents[parent]; 104 | } 105 | 106 | has_parent_transforms[idx] = true; 107 | this->cumulative[idx] = bone; 108 | } 109 | 110 | this->bones = &this->cumulative; 111 | this->needUpdate = false; 112 | } 113 | 114 | void Animation::SetFrame(int trackIdx, int idx1, int idx2, float factor, RootMotionType rm1, RootMotionType rm2) { 115 | if (trackIdx >= this->tracks.size()) { return; } 116 | 117 | int last_frame = this->armature->frames.size() - 1; 118 | AnimationTrack* track = &this->tracks[trackIdx]; 119 | 120 | idx1 = (idx1 < last_frame) ? idx1 : last_frame; 121 | idx2 = (idx2 < last_frame) ? idx2 : last_frame; 122 | 123 | factor = (idx2 > -1) ? factor : 0; 124 | 125 | bool hasChanged = ((track->frame1 != idx1) || (track->frame2 != idx2) || fabs(track->factor - factor) > 0.00001); 126 | 127 | track->frame1 = idx1; 128 | track->frame2 = idx2; 129 | track->factor = factor; 130 | 131 | if (hasChanged) { 132 | this->needUpdate = true; 133 | 134 | if (idx2 > -1) { 135 | track->Interpolate(); 136 | } else { 137 | track->bones = &this->armature->frames[idx1]; 138 | } 139 | 140 | if (trackIdx == 0) { 141 | track->ExtractRootMotion(this->root, rm1, rm2); 142 | this->hasRootMotion = (rm1 != RootMotionType::None) || (rm2 != RootMotionType::None); 143 | } //TODO: for all tracks 144 | } 145 | } 146 | 147 | 148 | int Animation::GetRuntimeBuffer(lua_State* L) { 149 | 150 | int width = this->armature->animationTextureWidth; 151 | int height = 2; 152 | 153 | const dmBuffer::StreamDeclaration streams_decl[] = { 154 | {dmHashString64("rgba"), dmBuffer::VALUE_TYPE_FLOAT32, 4} 155 | }; 156 | 157 | dmBuffer::HBuffer buffer = 0x0; 158 | dmBuffer::Create(width * height, streams_decl, 1, &buffer); 159 | 160 | float* stream = 0x0; 161 | 162 | uint32_t components = 0; 163 | uint32_t stride = 0; 164 | uint32_t items_count = 0; 165 | 166 | dmBuffer::GetStream(buffer, dmHashString64("rgba"), (void**)&stream, &items_count, &components, &stride); 167 | 168 | if (this->bones == NULL) { 169 | this->bones = &this->armature->frames[0]; 170 | this->CalculateBones(); 171 | } 172 | 173 | for(auto & bone : *this->bones) { 174 | Vector4 data[3] = {bone.getRow(0), bone.getRow(1), bone.getRow(2)}; 175 | for (int i = 0; i < 3; i++) { 176 | stream[0] = data[i].getX(); 177 | stream[1] = data[i].getY(); 178 | stream[2] = data[i].getZ(); 179 | stream[3] = data[i].getW(); 180 | stream += stride; 181 | } 182 | } 183 | 184 | lua_pushnumber(L, width); 185 | lua_pushnumber(L, height); 186 | 187 | dmScript::LuaHBuffer luabuf(buffer, dmScript::OWNER_LUA); 188 | dmScript::PushBuffer(L, luabuf); 189 | 190 | return 3; 191 | } 192 | 193 | int Animation::GetTextureBuffer(lua_State* L) { 194 | int frameCount = this->armature->frames.size(); 195 | 196 | int width = this->armature->animationTextureWidth; 197 | int height = this->armature->animationTextureHeight; 198 | 199 | const dmBuffer::StreamDeclaration streams_decl[] = { 200 | {dmHashString64("rgba"), dmBuffer::VALUE_TYPE_FLOAT32, 4} 201 | }; 202 | 203 | dmBuffer::HBuffer buffer = 0x0; 204 | dmBuffer::Create(width * height, streams_decl, 1, &buffer); 205 | 206 | float* stream = 0x0; 207 | 208 | uint32_t components = 0; 209 | uint32_t stride = 0; 210 | uint32_t items_count = 0; 211 | 212 | dmBuffer::GetStream(buffer, dmHashString64("rgba"), (void**)&stream, &items_count, &components, &stride); 213 | 214 | for (int f = 0; f < height; f++) { 215 | if (f < frameCount) { 216 | this->bones = &this->armature->frames[f]; 217 | this->CalculateBones(); 218 | 219 | for(auto & bone : *this->bones) { 220 | Vector4 data[3] = {bone.getRow(0), bone.getRow(1), bone.getRow(2)}; 221 | for (int i = 0; i < 3; i++) { 222 | stream[0] = data[i].getX(); 223 | stream[1] = data[i].getY(); 224 | stream[2] = data[i].getZ(); 225 | stream[3] = data[i].getW(); 226 | stream += stride; 227 | } 228 | } 229 | 230 | stream += stride * (width - this->bones->size() * 3); 231 | } 232 | } 233 | 234 | this->bones = &this->armature->frames[0]; 235 | this->CalculateBones(); // return to first frame 236 | 237 | lua_pushnumber(L, width); 238 | lua_pushnumber(L, height); 239 | 240 | dmScript::LuaHBuffer luabuf(buffer, dmScript::OWNER_LUA); 241 | dmScript::PushBuffer(L, luabuf); 242 | 243 | return 3; 244 | } 245 | 246 | Vector4 Animation::GetBakedUniform() { 247 | if (this->IsBlending()) { 248 | return Vector4(1.0 / this->armature->animationTextureWidth, 0, 0, 1); 249 | } 250 | return Vector4(1.0 / this->armature->animationTextureWidth, (float)this->tracks[0].frame1 / this->armature->animationTextureHeight, 0, 0); 251 | } 252 | 253 | int Animation::FindBone(string bone) { 254 | return this->armature->FindBone(bone); 255 | } 256 | 257 | int Animation::GetFramesCount() { 258 | return this->armature->GetFramesCount(); 259 | } 260 | 261 | int Animation::AddAnimationTrack(vector* mask) { 262 | AnimationTrack track = AnimationTrack(this->armature, mask); 263 | this->tracks.push_back(track); 264 | return this->tracks.size() - 1; 265 | } 266 | 267 | void Animation::SetTrackWeight(int idx, float weight) { 268 | if (this->tracks.size() > idx) { 269 | this->needUpdate = true; 270 | this->tracks[idx].weight = weight; 271 | } 272 | } 273 | 274 | int Animation::GetFrameIdx() { 275 | return this->tracks[0].frame1; 276 | } 277 | 278 | bool Animation::HasAttachments() { 279 | return this->boneObjects.size() > 0; 280 | } 281 | 282 | 283 | void Animation::CreateBoneGO(dmGameObject::HInstance go, int idx) { 284 | BoneGameObject obj = BoneGameObject(go, idx); 285 | this->boneObjects.push_back(obj); 286 | obj.ApplyTransform(this->bones); 287 | } -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/animation.h: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include "armature.h" 3 | #include "track.h" 4 | #include "bonego.h" 5 | 6 | using namespace std; 7 | 8 | class Animation 9 | { 10 | private: 11 | bool needUpdate = true; 12 | bool hasRootMotion = false; 13 | Armature* armature; 14 | vector boneNames; 15 | vector cumulative; 16 | vector tracks; 17 | 18 | Matrix4 transform; 19 | dmGameObject::HInstance root = 0; 20 | 21 | vector boneObjects; 22 | 23 | void CalculateBones(); 24 | 25 | public: 26 | vector* bones = NULL; 27 | 28 | void SetFrame(int trackIdx, int idx1, int idx2, float factor, RootMotionType rm1, RootMotionType rm2); 29 | void Update(); 30 | int AddAnimationTrack(vector* mask); 31 | void SetTrackWeight(int idx, float weight); 32 | int GetTextureBuffer(lua_State* L); 33 | int GetRuntimeBuffer(lua_State* L); 34 | int GetFramesCount(); 35 | Vector4 GetBakedUniform(); 36 | int FindBone(string bone); 37 | int GetFrameIdx(); 38 | bool IsBlending(); 39 | bool HasAttachments(); 40 | bool HasRootMotion(); 41 | void SetTransform(Matrix4 matrix); 42 | void SwitchRootMotion(); 43 | void ResetRootMotion(int frameIdx, bool isPrimary); 44 | void CreateBoneGO(dmGameObject::HInstance go, int idx); 45 | 46 | Animation(Armature* armature, dmGameObject::HInstance obj); 47 | }; 48 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/armature.cpp: -------------------------------------------------------------------------------- 1 | #include "armature.h" 2 | #include "reader.h" 3 | 4 | unsigned nextPOT(unsigned x) { 5 | if (x <= 1) return 2; 6 | int power = 2; 7 | x--; 8 | while (x >>= 1) power <<= 1; 9 | return power; 10 | } 11 | 12 | Armature::Armature(Reader* reader, bool verbose) { 13 | 14 | int boneCount = reader->ReadInt(); 15 | if (verbose) dmLogInfo("reading armature, bones: %d", boneCount); 16 | 17 | this->boneNames.reserve(boneCount); 18 | this->boneParents.reserve(boneCount); 19 | this->localBones.reserve(boneCount); 20 | 21 | for (int i = 0; i < boneCount; i++) { 22 | this->boneNames.push_back(reader->ReadString()); 23 | int parentIdx = reader->ReadInt(); 24 | this->boneParents.push_back(parentIdx); 25 | this->localBones.push_back(reader->ReadMatrix()); 26 | if (parentIdx == -1) { 27 | this->rootBoneIdx = i; 28 | } 29 | } 30 | 31 | int frameCount = reader->ReadInt(); 32 | if (verbose) dmLogInfo("frames: %d", frameCount); 33 | 34 | this->frames.reserve(frameCount); 35 | 36 | for (int i = 0; i < frameCount; i++) { 37 | vector bones; 38 | bones.reserve(boneCount); 39 | for (int j = 0; j < boneCount; j++) { 40 | //3x4 transform matrix 41 | bones.push_back(reader->ReadMatrix()); 42 | } 43 | 44 | this->frames.push_back(bones); 45 | } 46 | 47 | this->animationTextureWidth = nextPOT(this->frames[0].size() * 3); 48 | this->animationTextureHeight = nextPOT(frameCount); 49 | 50 | } 51 | 52 | Armature::~Armature(){ 53 | 54 | } 55 | 56 | int Armature::GetFramesCount() { 57 | return this->frames.size(); 58 | } 59 | 60 | int Armature::FindBone(string bone) { 61 | for (int i = 0; i < this->boneNames.size(); i++) { 62 | if (bone == this->boneNames[i]) 63 | return i; 64 | } 65 | 66 | return -1; 67 | } 68 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/armature.h: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | class Reader; 4 | 5 | using namespace std; 6 | 7 | class Armature 8 | { 9 | private: 10 | vector boneNames; 11 | 12 | public: 13 | vector< vector > frames; 14 | vector localBones; 15 | vector boneParents; 16 | 17 | int rootBoneIdx = 0; 18 | 19 | int animationTextureWidth = 0; 20 | int animationTextureHeight = 0; 21 | 22 | int FindBone(string bone); 23 | int GetFramesCount(); 24 | 25 | Armature(Reader* reader, bool verbose); 26 | ~Armature(); 27 | }; 28 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/binary.cpp: -------------------------------------------------------------------------------- 1 | #include "binary.h" 2 | 3 | BinaryFile::BinaryFile(const char* file, bool verbose) { 4 | Reader* reader = new Reader(file); 5 | 6 | int count = reader->ReadInt(); 7 | this->armatures.reserve(count); 8 | for (int i = 0; i < count; i++) { 9 | Armature* armature = new Armature(reader, verbose); 10 | this->armatures.push_back(armature); 11 | } 12 | 13 | while (!reader->IsEOF()) { 14 | Model* model = new Model(reader, verbose); 15 | this->models.push_back(model); 16 | } 17 | 18 | delete reader; 19 | } 20 | 21 | BinaryFile::~BinaryFile() { 22 | for (auto & model : this->models) { 23 | delete model; 24 | } 25 | 26 | for (auto & armature : this->armatures) { 27 | delete armature; 28 | } 29 | } 30 | 31 | Instance* BinaryFile::CreateInstance(dmGameObject::HInstance obj, bool useBakedAnimations, float scaleAABB) { 32 | Instance* instance = new Instance(&this->models, &this->armatures, obj, useBakedAnimations, scaleAABB); 33 | this->instances ++; 34 | return instance; 35 | } 36 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/binary.h: -------------------------------------------------------------------------------- 1 | #include "model.h" 2 | #include "instance.h" 3 | 4 | using namespace std; 5 | 6 | class BinaryFile 7 | { 8 | private: 9 | vector models; 10 | vector armatures; 11 | 12 | public: 13 | int instances = 0; 14 | 15 | Instance* CreateInstance(dmGameObject::HInstance obj, bool useBakedAnimations, float scaleAABB); 16 | 17 | BinaryFile(const char* file, bool verbose); 18 | ~BinaryFile(); 19 | }; -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/bonego.cpp: -------------------------------------------------------------------------------- 1 | #include "bonego.h" 2 | 3 | BoneGameObject::BoneGameObject(dmGameObject::HInstance obj, int idx) { 4 | this->gameObject = obj; 5 | this->boneIdx = idx; 6 | } 7 | 8 | void BoneGameObject::ApplyTransform(vector* bones) { 9 | 10 | Matrix4 m = bones->at(this->boneIdx); 11 | //m.setRow(3, Vector4(1)); 12 | 13 | Vector4 v1 = m.getRow(0); 14 | Vector4 v2 = m.getRow(1); 15 | Vector4 v3 = m.getRow(2); 16 | 17 | Quat q = Quat(m.getUpper3x3()); 18 | dmGameObject::SetRotation(this->gameObject, Quat(q.getX(), q.getZ(), -q.getY(), q.getW())); 19 | dmGameObject::SetPosition(this->gameObject, dmVMath::Point3(v1.getW(), v3.getW(), -v2.getW())); 20 | } -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/bonego.h: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | using namespace std; 4 | 5 | class BoneGameObject { 6 | private: 7 | int boneIdx; 8 | dmGameObject::HInstance gameObject; 9 | 10 | public: 11 | BoneGameObject(dmGameObject::HInstance obj, int idx); 12 | void ApplyTransform(vector* bones); 13 | }; -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/instance.cpp: -------------------------------------------------------------------------------- 1 | #include "instance.h" 2 | #include "model.h" 3 | 4 | Instance::Instance(vector* models, vector* armatures, dmGameObject::HInstance obj, bool useBakedAnimations, float scaleAABB) { 5 | this->useBakedAnimations = useBakedAnimations; 6 | 7 | this->animations.reserve(armatures->size()); 8 | this->models.reserve(models->size()); 9 | 10 | for (auto & armature : *armatures) { 11 | Animation* a = new Animation(armature, obj); 12 | this->animations.push_back(a); 13 | } 14 | 15 | for(auto & model : *models) { 16 | Animation* animation = NULL; 17 | if(model->armatureIdx > -1) { 18 | animation = this->animations[model->armatureIdx]; 19 | animation->SetTransform(model->world.matrix); 20 | } 21 | ModelInstance* mi = new ModelInstance(model, animation, useBakedAnimations, scaleAABB); 22 | this->models.push_back(mi); 23 | } 24 | } 25 | 26 | Instance::~Instance() { 27 | for(auto & model : this->models) { 28 | delete model; 29 | } 30 | 31 | for(auto & animation : this->animations) { 32 | delete animation; 33 | } 34 | } 35 | 36 | void Instance::CreateLuaProxy(lua_State* L) { 37 | lua_newtable(L); 38 | 39 | for(auto & model : this->models){ 40 | model->CreateLuaProxy(L); 41 | } 42 | } 43 | 44 | void Instance::SetFrame(int trackIdx, int idx1, int idx2, float factor, RootMotionType rm1, RootMotionType rm2) { 45 | for(auto & animation : this->animations) { 46 | animation->SetFrame(trackIdx, idx1, idx2, factor, rm1, rm2); 47 | } 48 | } 49 | 50 | void Instance::ResetRootMotion(bool isPrimary, int frame) { 51 | if (this->animations.size() > 0) { 52 | this->animations[0]->ResetRootMotion(frame, isPrimary); 53 | } 54 | } 55 | 56 | void Instance::SwitchRootMotion() { 57 | if (this->animations.size() > 0) { 58 | this->animations[0]->SwitchRootMotion(); 59 | } 60 | } 61 | 62 | void Instance::Update(lua_State* L) { 63 | for(auto & animation : this->animations) { 64 | if (!this->useBakedAnimations || animation->IsBlending() || animation->HasAttachments()) { 65 | animation->Update(); 66 | } 67 | } 68 | 69 | for(auto & mi : this->models) { 70 | mi->Update(L); 71 | } 72 | } 73 | 74 | void Instance::SetShapes(lua_State* L, unordered_map* values) { 75 | for(auto & model : this->models) { 76 | model->SetShapes(L, values); 77 | } 78 | } 79 | 80 | URL* Instance::AttachGameObject(dmGameObject::HInstance go, string bone) { 81 | 82 | for(auto & mi : this->models) { 83 | URL* url = mi->AttachGameObject(go, bone); 84 | if (url != NULL) { 85 | return url; 86 | } 87 | } 88 | dmLogInfo("Bone \"%s\" not found!", bone.c_str()); 89 | return NULL; 90 | } 91 | 92 | 93 | int Instance::AddAnimationTrack(vector* mask) { 94 | int id = 0; 95 | for(auto & animation : this->animations) { 96 | id = animation->AddAnimationTrack(mask); 97 | } 98 | return id; 99 | } 100 | 101 | void Instance::SetAnimationTrackWeight(int idx, float weight) { 102 | for(auto & animation : this->animations) { 103 | animation->SetTrackWeight(idx, weight); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/instance.h: -------------------------------------------------------------------------------- 1 | #include "model_instance.h" 2 | 3 | using namespace std; 4 | using namespace dmMessage; 5 | 6 | class Model; 7 | 8 | class Instance{ 9 | private: 10 | vector models; 11 | vector animations; 12 | bool useBakedAnimations = false; 13 | 14 | public: 15 | void SetShapes(lua_State* L, unordered_map* values); 16 | void CreateLuaProxy(lua_State* L); 17 | void SetFrame(int trackIdx, int idx1, int idx2, float factor, RootMotionType rm1, RootMotionType rm2); 18 | void Update(lua_State* L); 19 | URL* AttachGameObject(dmGameObject::HInstance go, string bone); 20 | int AddAnimationTrack(vector* mask); 21 | void SetAnimationTrackWeight(int idx, float weight); 22 | 23 | void ResetRootMotion(bool isPrimary, int frame); 24 | void SwitchRootMotion(); 25 | 26 | Instance(vector* models, vector* armatures, dmGameObject::HInstance obj, bool useBakedAnimations, float scaleAABB); 27 | ~Instance(); 28 | }; 29 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "model.h" 2 | #include "instance.h" 3 | #include 4 | #include 5 | 6 | 7 | Mesh::Mesh() { 8 | } 9 | 10 | Mesh::~Mesh() { 11 | } 12 | 13 | dmScript::LuaHBuffer Mesh::CreateBuffer(ModelInstance* mi, float scaleAABB) { 14 | 15 | bool hasNormalMap = !this->material.normal.texture.empty(); 16 | 17 | const dmBuffer::StreamDeclaration streams_decl[] = { 18 | {dmHashString64("position"), dmBuffer::VALUE_TYPE_FLOAT32, 3}, 19 | {dmHashString64("normal"), dmBuffer::VALUE_TYPE_FLOAT32, 3}, 20 | {dmHashString64("texcoord0"), dmBuffer::VALUE_TYPE_FLOAT32, 2}, 21 | {dmHashString64("weight"), dmBuffer::VALUE_TYPE_FLOAT32, 4}, 22 | {dmHashString64("bone"), dmBuffer::VALUE_TYPE_UINT8, 4} 23 | }; 24 | dmBuffer::HBuffer buffer = 0x0; 25 | dmBuffer::Result r = dmBuffer::Create(this->faces.size() * 3, streams_decl, 5, &buffer); 26 | 27 | 28 | float* positions = 0x0; 29 | float* normals = 0x0; 30 | float* weights = 0x0; 31 | 32 | uint8_t* bones = 0x0; 33 | 34 | uint32_t components = 0; 35 | uint32_t stride = 0; 36 | uint32_t items_count = 0; 37 | 38 | dmBuffer::GetStream(buffer, dmHashString64("position"), (void**)&positions, &items_count, &components, &stride); 39 | dmBuffer::GetStream(buffer, dmHashString64("normal"), (void**)&normals, &items_count, &components, &stride); 40 | 41 | uint32_t stride_weight = 0; 42 | dmBuffer::GetStream(buffer, dmHashString64("weight"), (void**)&weights, &items_count, &components, &stride_weight); 43 | uint32_t stride_bone = 0; 44 | dmBuffer::GetStream(buffer, dmHashString64("bone"), (void**)&bones, &items_count, &components, &stride_bone); 45 | 46 | 47 | 48 | Vector4 min = Vector4(FLT_MAX, FLT_MAX, FLT_MAX, 1); 49 | Vector4 max = Vector4(-FLT_MAX, -FLT_MAX, -FLT_MAX, 1); 50 | 51 | for(auto & face : this->faces) { 52 | for (int i = 0; i < 3; i++) { 53 | int idx = face.v[i]; 54 | 55 | Vertex* vertex = &mi->model->vertices[idx]; 56 | Vector3* n = face.isFlat ? &face.n : &vertex->n; 57 | 58 | //dmLogInfo("N %d : %f, %f, %f", face.isFlat, n-getX(), n.getY(), n.getZ()); 59 | 60 | positions[0] = vertex->p.getX(); 61 | positions[1] = vertex->p.getY(); 62 | positions[2] = vertex->p.getZ(); 63 | 64 | if (positions[0] < min[0]) min[0] = positions[0]; 65 | if (positions[1] < min[1]) min[1] = positions[1]; 66 | if (positions[2] < min[2]) min[2] = positions[2]; 67 | 68 | if (positions[0] > max[0]) max[0] = positions[0]; 69 | if (positions[1] > max[1]) max[1] = positions[1]; 70 | if (positions[2] > max[2]) max[2] = positions[2]; 71 | 72 | normals[0] = n->getX(); 73 | normals[1] = n->getY(); 74 | normals[2] = n->getZ(); 75 | 76 | int boneCount = 0; 77 | vector* skin; 78 | 79 | if (mi->model->skin != NULL) { 80 | skin = &mi->model->skin[idx]; 81 | boneCount = skin->size(); 82 | 83 | for (int j = 0; j < boneCount; j++) { 84 | this->usedBonesIndex.insert(skin->at(j).idx); 85 | } 86 | 87 | } 88 | 89 | weights[0] = boneCount > 0 ? skin->at(0).weight : 0; 90 | weights[1] = boneCount > 1 ? skin->at(1).weight : 0; 91 | weights[2] = boneCount > 2 ? skin->at(2).weight : 0; 92 | weights[3] = boneCount > 3 ? skin->at(3).weight : 0; 93 | 94 | bones[0] = boneCount > 0 ? skin->at(0).idx : 0; 95 | bones[1] = boneCount > 1 ? skin->at(1).idx : 0; 96 | bones[2] = boneCount > 2 ? skin->at(2).idx : 0; 97 | bones[3] = boneCount > 3 ? skin->at(3).idx : 0; 98 | 99 | //dmLogInfo("mesh bone: %d", bones[0]); 100 | 101 | 102 | positions += stride; 103 | normals += stride; 104 | 105 | weights += stride_weight; 106 | bones += stride_bone; 107 | 108 | } 109 | } 110 | 111 | float* tc = 0x0; 112 | dmBuffer::GetStream(buffer, dmHashString64("texcoord0"), (void**)&tc, &items_count, &components, &stride); 113 | int count = 0; 114 | 115 | for (int i = 0; i < items_count; ++i) 116 | { 117 | tc[0] = this->texcoords[count++]; 118 | tc[1] = this->texcoords[count++]; 119 | tc += stride; 120 | } 121 | 122 | if ( (mi->model->armatureIdx == -1) || (scaleAABB > 0)) { 123 | 124 | scaleAABB = (scaleAABB > 0) ? scaleAABB : 1; 125 | //min = mi->model->world.matrix * min; 126 | //max = mi->model->world.matrix * max; 127 | 128 | if (scaleAABB != 1) { 129 | Vector4 center = Vector4( (max[0] + min[0]) / 2, (max[1] + min[1]) / 2, (max[2] + min[2]) / 2, 0); 130 | Vector4 offset = (max - center) * scaleAABB; 131 | min -= offset; 132 | max += offset; 133 | } 134 | 135 | float aabb[6]; 136 | 137 | aabb[0] = min[0]; 138 | aabb[1] = min[2]; 139 | aabb[2] = -min[1]; 140 | aabb[3] = max[0]; 141 | aabb[4] = max[2]; 142 | aabb[5] = -max[1]; 143 | 144 | 145 | dmBuffer::SetMetaData(buffer, dmHashString64("AABB"), &aabb, 6, dmBuffer::VALUE_TYPE_FLOAT32); 146 | } 147 | 148 | return dmScript::LuaHBuffer(buffer, dmScript::OWNER_C); 149 | } -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/mesh.h: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include 3 | 4 | using namespace std; 5 | using namespace dmMessage; 6 | 7 | class ModelInstance; 8 | 9 | class Mesh 10 | { 11 | private: 12 | 13 | public: 14 | vector faces; 15 | vector texcoords; 16 | set usedBonesIndex; 17 | unordered_map< int, vector > vertexMap; 18 | Material material; 19 | 20 | dmScript::LuaHBuffer CreateBuffer(ModelInstance* model, float scaleAABB); 21 | 22 | Mesh(); 23 | ~Mesh(); 24 | }; -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/mesh_utils.cpp: -------------------------------------------------------------------------------- 1 | #define EXTENSION_NAME mesh_utils 2 | #define LIB_NAME "mesh_utils" 3 | #define MODULE_NAME "mesh_utils" 4 | #define DLIB_LOG_DOMAIN LIB_NAME 5 | 6 | #include 7 | #include 8 | #include 9 | #include "binary.h" 10 | 11 | using namespace Vectormath::Aos; 12 | 13 | std::unordered_map files; 14 | 15 | static int SwitchRootMotion(lua_State* L) { 16 | lua_getfield(L, 1, "instance"); 17 | Instance* instance = (Instance*)lua_touserdata(L, -1); 18 | instance->SwitchRootMotion(); 19 | return 0; 20 | } 21 | 22 | static int ResetRootMotion(lua_State* L) { 23 | lua_getfield(L, 1, "instance"); 24 | Instance* instance = (Instance*)lua_touserdata(L, -1); 25 | bool isPrimary = lua_toboolean(L, 2); 26 | int frame = luaL_checknumber(L, 3); 27 | instance->ResetRootMotion(isPrimary, frame); 28 | return 0; 29 | } 30 | 31 | static int AddAnimationTrack(lua_State* L) { 32 | lua_getfield(L, 1, "instance"); 33 | Instance* instance = (Instance*)lua_touserdata(L, -1); 34 | 35 | vector bones; 36 | 37 | for (int i = 0; i < lua_objlen(L, 2); i++) { 38 | lua_rawgeti(L, 2, i + 1); 39 | string bone = string(luaL_checkstring(L, -1)); 40 | bones.push_back(bone); 41 | } 42 | 43 | int id = instance->AddAnimationTrack(&bones); 44 | 45 | lua_pushnumber(L, id); 46 | return 1; 47 | } 48 | 49 | static int SetAnimationTrackWeight(lua_State* L) { 50 | lua_getfield(L, 1, "instance"); 51 | Instance* instance = (Instance*)lua_touserdata(L, -1); 52 | int track = luaL_checknumber(L, 2); 53 | float weight = luaL_checknumber(L, 3); 54 | instance->SetAnimationTrackWeight(track, weight); 55 | return 0; 56 | } 57 | 58 | static int Update(lua_State* L) { 59 | lua_getfield(L, 1, "instance"); 60 | Instance* instance = (Instance*)lua_touserdata(L, -1); 61 | instance->Update(L); 62 | return 0; 63 | } 64 | 65 | static int AttachBoneGO(lua_State* L) { 66 | int count = lua_gettop(L); 67 | 68 | lua_getfield(L, 1, "instance"); 69 | Instance* instance = (Instance*)lua_touserdata(L, -1); 70 | 71 | dmGameObject::HInstance go = dmScript::CheckGOInstance(L, 2); 72 | string bone = string(luaL_checkstring(L, 3)); 73 | 74 | URL* url = instance->AttachGameObject(go, bone); 75 | if (url != NULL) { 76 | dmScript::PushURL(L, *url); 77 | return 1; 78 | } 79 | 80 | return 0; 81 | } 82 | 83 | static int SetShapes(lua_State* L) { 84 | int count = lua_gettop(L); 85 | 86 | lua_getfield(L, 1, "instance"); 87 | Instance* instance = (Instance*)lua_touserdata(L, -1); 88 | 89 | lua_pop(L, 1); 90 | lua_pushnil(L); 91 | 92 | unordered_map values; 93 | 94 | while (lua_next(L, -2) != 0) 95 | { 96 | string name = string(luaL_checkstring(L, -2)); 97 | float value = luaL_checknumber(L, -1); 98 | values[name] = value; 99 | lua_pop(L, 1); 100 | } 101 | 102 | instance->SetShapes(L, &values); 103 | 104 | return 0; 105 | } 106 | 107 | static int SetFrame(lua_State* L) { 108 | int count = lua_gettop(L); 109 | 110 | lua_getfield(L, 1, "instance"); 111 | Instance* instance = (Instance*)lua_touserdata(L, -1); 112 | 113 | int track = luaL_checknumber(L, 2); 114 | int frame1 = luaL_checknumber(L, 3); 115 | int frame2 = (count > 3) ? luaL_checknumber(L, 4) : -1; 116 | float factor = (count > 4) ? luaL_checknumber(L, 5) : 0; 117 | RootMotionType rm1 = (count > 5) ? static_cast(luaL_checknumber(L, 6)) : RootMotionType::None; 118 | RootMotionType rm2 = (count > 6) ? static_cast(luaL_checknumber(L, 7)) : RootMotionType::None; 119 | 120 | instance->SetFrame(track, frame1, frame2, factor, rm1, rm2); 121 | return 0; 122 | } 123 | 124 | static int Delete(lua_State* L) { 125 | int count = lua_gettop(L); 126 | 127 | lua_getfield(L, 1, "instance"); 128 | Instance* instance = (Instance*)lua_touserdata(L, -1); 129 | lua_getfield(L, 1, "path"); 130 | string path = string(luaL_checkstring(L, -1)); 131 | 132 | BinaryFile* binary = files[path]; 133 | delete instance; 134 | binary->instances --; 135 | if (binary->instances == 0) { 136 | files.erase(path); 137 | delete binary; 138 | lua_pushnumber(L, 1); 139 | return 1; 140 | } 141 | 142 | return 0; 143 | } 144 | 145 | static int Load(lua_State* L) { 146 | std::string path = string(luaL_checkstring(L, 1)); 147 | const char* content = luaL_checkstring(L, 2); 148 | dmGameObject::HInstance obj = dmScript::CheckGOInstance(L, 3); 149 | bool useBakedAnimations = lua_toboolean(L, 4); 150 | bool verbose = lua_toboolean(L, 5); 151 | float scaleAABB = luaL_checknumber(L, 6); 152 | 153 | BinaryFile* binary; 154 | if (auto search = files.find(path); search != files.end()) { 155 | binary = search->second; 156 | } else { 157 | files[path] = new BinaryFile(content, verbose); 158 | binary = files[path]; 159 | } 160 | 161 | Instance* instance = binary->CreateInstance(obj, useBakedAnimations, scaleAABB); 162 | lua_newtable(L); 163 | lua_pushstring(L, "instance"); 164 | lua_pushlightuserdata(L, instance); 165 | lua_settable(L, -3); 166 | 167 | lua_pushstring(L, "path"); 168 | lua_pushstring(L, path.c_str()); 169 | lua_settable(L, -3); 170 | 171 | static const luaL_Reg f[] = 172 | { 173 | {"switch_root_motion", SwitchRootMotion}, 174 | {"reset_root_motion", ResetRootMotion}, 175 | {"add_animation_track", AddAnimationTrack}, 176 | {"set_animation_track_weight", SetAnimationTrackWeight}, 177 | {"attach_bone_go", AttachBoneGO}, 178 | {"set_shapes", SetShapes}, 179 | {"set_frame", SetFrame}, 180 | {"update", Update}, 181 | {"delete", Delete}, 182 | {0, 0} 183 | }; 184 | luaL_register(L, NULL, f); 185 | 186 | instance->CreateLuaProxy(L); 187 | 188 | return 2; 189 | } 190 | 191 | // Functions exposed to Lua 192 | static const luaL_reg Module_methods[] = 193 | { 194 | {"load", Load}, 195 | {0, 0} 196 | }; 197 | 198 | static void LuaInit(lua_State* L) 199 | { 200 | int top = lua_gettop(L); 201 | 202 | // Register lua names 203 | luaL_register(L, MODULE_NAME, Module_methods); 204 | 205 | lua_pop(L, 1); 206 | assert(top == lua_gettop(L)); 207 | } 208 | 209 | dmExtension::Result InitializeUtils(dmExtension::Params* params) 210 | { 211 | // Init Lua 212 | LuaInit(params->m_L); 213 | printf("Registered %s Extension\n", MODULE_NAME); 214 | return dmExtension::RESULT_OK; 215 | } 216 | 217 | dmExtension::Result FinalizeUtils(dmExtension::Params* params) 218 | { 219 | return dmExtension::RESULT_OK; 220 | } 221 | 222 | DM_DECLARE_EXTENSION(EXTENSION_NAME, LIB_NAME, 0, 0, InitializeUtils, 0, 0, FinalizeUtils) 223 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/model.cpp: -------------------------------------------------------------------------------- 1 | #include "model.h" 2 | 3 | Model::Model(Reader* reader, bool verbose){ 4 | this->meshes.emplace_back(); 5 | 6 | this->name = reader->ReadString(); 7 | 8 | if (verbose) { 9 | dmLogInfo("-------------"); 10 | dmLogInfo("%s", this->name.c_str()); 11 | } 12 | 13 | int parentFlag = reader->ReadInt(); 14 | 15 | if (parentFlag > 0) { 16 | this->parent = reader->ReadString(parentFlag); 17 | } 18 | 19 | this->world = reader->ReadTransform(); 20 | 21 | this->vertexCount = reader->ReadInt(); 22 | if (verbose) dmLogInfo("vertices: %d", this->vertexCount); 23 | 24 | this->vertices = new Vertex[vertexCount]; 25 | for (int i = 0; i < vertexCount; i++) { 26 | this->vertices[i].p = reader->ReadVector3(); 27 | this->vertices[i].n = reader->ReadVector3(); 28 | } 29 | 30 | int shapeCount = reader->ReadInt(); 31 | if (verbose) dmLogInfo("shapes: %d", shapeCount); 32 | 33 | for (int j = 0; j < shapeCount; j++) { 34 | string name = reader->ReadString(); 35 | unordered_map shape; 36 | 37 | int deltaCount = reader->ReadInt(); 38 | 39 | for (int i = 0; i < deltaCount; i++) { 40 | int idx = reader->ReadInt(); 41 | ShapeData data; 42 | data.p = reader->ReadVector3(); 43 | data.n = reader->ReadVector3(); 44 | shape[idx] = data; 45 | } 46 | 47 | this->shapes[name] = shape; 48 | } 49 | 50 | int faceCount = reader->ReadInt(); 51 | int faceMap[faceCount]; 52 | 53 | if (verbose) dmLogInfo("faces: %d", faceCount); 54 | 55 | for (int i = 0; i < faceCount; i++) { 56 | Face face; 57 | face.v[0] = reader->ReadInt(); 58 | face.v[1] = reader->ReadInt(); 59 | face.v[2] = reader->ReadInt(); 60 | 61 | int mi = reader->ReadInt(); 62 | faceMap[i] = mi; 63 | 64 | int flatFlag = reader->ReadInt(); 65 | if (flatFlag == 1) { 66 | face.isFlat = true; 67 | face.n = reader->ReadVector3(); 68 | } 69 | 70 | while (this->meshes.size() <= mi) { 71 | this->meshes.emplace_back(); 72 | } 73 | this->meshes[mi].faces.push_back(face); 74 | 75 | if (shapeCount > 0) { 76 | int idx = (this->meshes[mi].faces.size() - 1) * 3; 77 | this->meshes[mi].vertexMap[face.v[0]].push_back(idx); 78 | this->meshes[mi].vertexMap[face.v[1]].push_back(idx + 1); 79 | this->meshes[mi].vertexMap[face.v[2]].push_back(idx + 2); 80 | } 81 | } 82 | 83 | for (int i = 0; i < faceCount; i++) { 84 | Mesh* mesh = &this->meshes[faceMap[i]]; 85 | for (int j = 0; j < 6; j++) { 86 | mesh->texcoords.push_back(reader->ReadFloat()); 87 | } 88 | } 89 | 90 | int materialCount = reader->ReadInt(); 91 | 92 | if (verbose) dmLogInfo("materials: %d", materialCount); 93 | 94 | Mesh notUsedMaterialMesh; //? still needed ? 95 | 96 | for (int i = 0; i < materialCount; i ++) { 97 | Mesh* mesh = (this->meshes.size() > i) ? &this->meshes[i] : ¬UsedMaterialMesh; 98 | mesh->material.name = reader->ReadString(); 99 | mesh->material.type = reader->ReadInt(); // 0 - opaque, 1 - blend, 2 - hashed 100 | mesh->material.color = reader->ReadVector4(); 101 | mesh->material.specular.value = reader->ReadFloat(); 102 | mesh->material.roughness.value = reader->ReadFloat(); 103 | 104 | int textureFlag = reader->ReadInt(); 105 | int rampFlag = 0; 106 | 107 | if (textureFlag > 0) { 108 | mesh->material.texture = reader->ReadString(textureFlag); 109 | } 110 | 111 | textureFlag = reader->ReadInt(); //normal texture 112 | if (textureFlag > 0) { 113 | mesh->material.normal.texture = reader->ReadString(textureFlag); 114 | mesh->material.normal.value = reader->ReadFloat(); 115 | } 116 | 117 | textureFlag = reader->ReadInt(); //specular texture 118 | if (textureFlag > 0) { 119 | mesh->material.specular.texture = reader->ReadString(textureFlag); 120 | mesh->material.specular.invert = reader->ReadInt(); 121 | rampFlag = reader->ReadInt(); 122 | if (rampFlag > 0) { 123 | mesh->material.specular.ramp.p1 = reader->ReadFloat(); 124 | mesh->material.specular.ramp.v1 = reader->ReadFloat(); 125 | mesh->material.specular.ramp.p2 = reader->ReadFloat(); 126 | mesh->material.specular.ramp.v2 = reader->ReadFloat(); 127 | } 128 | } 129 | 130 | textureFlag = reader->ReadInt(); //roughness texture 131 | if (textureFlag > 0) { 132 | mesh->material.roughness.texture = reader->ReadString(textureFlag); 133 | rampFlag = reader->ReadInt(); 134 | if (rampFlag > 0) { 135 | mesh->material.roughness.ramp.p1 = reader->ReadFloat(); 136 | mesh->material.roughness.ramp.v1 = reader->ReadFloat(); 137 | mesh->material.roughness.ramp.p2 = reader->ReadFloat(); 138 | mesh->material.roughness.ramp.v2 = reader->ReadFloat(); 139 | } 140 | } 141 | } 142 | 143 | this->armatureIdx = reader->ReadInt(); 144 | if (verbose) dmLogInfo("armature: %d", armatureIdx); 145 | 146 | if (armatureIdx > -1) { 147 | 148 | this->skin = new vector[vertexCount]; 149 | for (int i = 0; i < vertexCount; i++) { 150 | this->skin[i].reserve(4); 151 | int weightCount = reader->ReadInt(); 152 | for (int j = 0; j < weightCount; j++) { 153 | SkinData data; 154 | data.idx = reader->ReadInt(); 155 | data.weight = reader->ReadFloat(); 156 | this->skin[i].push_back(data); 157 | } 158 | } 159 | } 160 | 161 | int frameCount = reader->ReadInt(); 162 | if (verbose) dmLogInfo("frames: %d", frameCount); 163 | 164 | this->shapeFrames.reserve(frameCount); 165 | 166 | for (int i = 0; i < frameCount; i++) { 167 | unordered_map shapes; 168 | for (int j = 0; j < shapeCount; j++) { 169 | string key = reader->ReadString(); 170 | float value = reader->ReadFloat(); 171 | shapes[key] = value; 172 | } 173 | 174 | this->shapeFrames.push_back(shapes); 175 | } 176 | 177 | } 178 | 179 | Model::~Model(){ 180 | delete [] this->vertices; 181 | delete [] this->skin; 182 | } 183 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/model.h: -------------------------------------------------------------------------------- 1 | #include "reader.h" 2 | #include 3 | 4 | using namespace std; 5 | 6 | class Model 7 | { 8 | private: 9 | 10 | public: 11 | string name; 12 | string parent; 13 | Transform3d world; 14 | int vertexCount = 0; 15 | int armatureIdx = 0; 16 | Vertex* vertices = NULL; 17 | vectormeshes; 18 | 19 | unordered_map > shapes; 20 | vector< unordered_map > shapeFrames; 21 | vector* skin = NULL; 22 | 23 | Model(Reader* reader, bool verbose); 24 | ~Model(); 25 | 26 | vector boneParents; 27 | }; 28 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/model_instance.cpp: -------------------------------------------------------------------------------- 1 | #include "model_instance.h" 2 | #include "model.h" 3 | 4 | static int SetURL(lua_State* L) { 5 | lua_getfield(L, 1, "instance"); 6 | ModelInstance* mi = (ModelInstance* )lua_touserdata(L, -1); 7 | 8 | URL url = *dmScript::CheckURL(L, 2); 9 | 10 | int idx = mi->urls.size(); 11 | mi->urls.push_back(url); 12 | 13 | return 0; 14 | } 15 | 16 | static int GetAnimationTextureBuffer(lua_State* L) { 17 | lua_getfield(L, 1, "instance"); 18 | ModelInstance* mi = (ModelInstance* )lua_touserdata(L, -1); 19 | 20 | if (mi->animation == NULL) return 0; 21 | 22 | bool runtime = lua_toboolean(L, 2); 23 | if (runtime) return mi->animation->GetRuntimeBuffer(L); 24 | 25 | return mi->animation->GetTextureBuffer(L); 26 | } 27 | 28 | static int SetRuntimeTexture(lua_State* L) { 29 | lua_getfield(L, 1, "instance"); 30 | ModelInstance* mi = (ModelInstance* )lua_touserdata(L, -1); 31 | 32 | mi->runtimeTexture = dmScript::CheckHash(L, 2); 33 | 34 | return 0; 35 | } 36 | 37 | 38 | ModelInstance::ModelInstance(Model* model, Animation* animation, bool useBakedAnimations, float scaleAABB) { 39 | this->useBakedAnimations = useBakedAnimations && (animation != NULL) && (animation->GetFramesCount() > 1); 40 | this->model = model; 41 | this->animation = animation; 42 | 43 | this->urls.reserve(this->model->meshes.size()); 44 | this->buffers.reserve(this->model->meshes.size()); 45 | 46 | for(auto & mesh : this->model->meshes) { 47 | auto buffer = mesh.CreateBuffer(this, scaleAABB); 48 | this->buffers.push_back(buffer); 49 | } 50 | 51 | } 52 | 53 | ModelInstance::~ModelInstance() { 54 | /* 55 | for (auto& buffer : this->buffers) { 56 | dmBuffer::Destroy(buffer.m_Buffer); 57 | }*/ 58 | //resource will destroy buffer anyway 59 | } 60 | 61 | void ModelInstance::CreateLuaProxy(lua_State* L) { 62 | lua_pushstring(L, this->model->name.c_str()); 63 | 64 | lua_newtable(L); 65 | 66 | static const luaL_Reg f[] = 67 | { 68 | {"get_animation_buffer", GetAnimationTextureBuffer}, 69 | {"set_runtime_texture", SetRuntimeTexture}, 70 | {0, 0} 71 | }; 72 | luaL_register(L, NULL, f); 73 | 74 | lua_pushstring(L, "instance"); 75 | lua_pushlightuserdata(L, this); 76 | lua_settable(L, -3); 77 | 78 | if (!this->model->parent.empty()) { 79 | lua_pushstring(L, "parent"); 80 | lua_pushstring(L, this->model->parent.c_str()); 81 | lua_settable(L, -3); 82 | } 83 | 84 | lua_pushstring(L, "frames"); 85 | lua_pushnumber(L, this->animation != NULL ? this->animation->GetFramesCount() : 0); //refactor 86 | lua_settable(L, -3); 87 | 88 | lua_pushstring(L, "armature"); 89 | lua_pushnumber(L, this->model->armatureIdx); 90 | lua_settable(L, -3); 91 | 92 | lua_pushstring(L, "position"); 93 | dmScript::PushVector3(L, this->model->world.position); 94 | lua_settable(L, -3); 95 | 96 | lua_pushstring(L, "rotation"); 97 | dmScript::PushQuat(L, this->model->world.rotation); 98 | lua_settable(L, -3); 99 | 100 | lua_pushstring(L, "scale"); 101 | dmScript::PushVector3(L, this->model->world.scale); 102 | lua_settable(L, -3); 103 | 104 | lua_pushstring(L, "meshes"); 105 | lua_newtable(L); 106 | 107 | int idx = 1; 108 | for(auto & mesh : this->model->meshes) { 109 | lua_newtable(L); 110 | 111 | static const luaL_Reg f[] = 112 | { 113 | {"set_url", SetURL}, 114 | {0, 0} 115 | }; 116 | luaL_register(L, NULL, f); 117 | 118 | lua_pushstring(L, "instance"); 119 | lua_pushlightuserdata(L, this); 120 | lua_settable(L, -3); 121 | 122 | lua_pushstring(L, "buffer"); 123 | dmScript::PushBuffer(L, this->buffers[idx-1]); 124 | lua_settable(L, -3); 125 | 126 | mesh.material.CreateLuaProxy(L); 127 | 128 | lua_rawseti(L, -2, idx++); 129 | 130 | } 131 | lua_settable(L, -3); 132 | lua_settable(L, -3); 133 | 134 | } 135 | 136 | bool ModelInstance::UseBakedAnimations() { 137 | if ((this->animation != NULL) && (this->animation->HasRootMotion())) return false; 138 | return this->useBakedAnimations; 139 | } 140 | 141 | void ModelInstance::Update(lua_State* L) { 142 | if (this->animation == NULL) return; 143 | 144 | int meshCount = this->model->meshes.size(); 145 | 146 | this->SetShapeFrame(L, this->animation->GetFrameIdx()); //TODO blending, multi tracks 147 | 148 | if (this->UseBakedAnimations()) { 149 | if (this->animation->IsBlending()) { 150 | //set runtime texture 151 | lua_getglobal(L, "native_runtime_texture"); 152 | 153 | dmScript::PushHash(L, this->runtimeTexture); 154 | this->animation->GetRuntimeBuffer(L); 155 | lua_call(L, 4, 0); 156 | } 157 | 158 | for (int i = 0; i < meshCount; i++) { 159 | lua_getglobal(L, "go"); 160 | lua_getfield(L, -1, "set"); 161 | lua_remove(L, -2); 162 | 163 | dmScript::PushURL(L, this->urls[i]); 164 | lua_pushstring(L, "animation"); 165 | dmScript::PushVector4(L, this->animation->GetBakedUniform()); 166 | lua_call(L, 3, 0); 167 | } 168 | 169 | return; 170 | } 171 | 172 | for (int i = 0; i < meshCount; i++) { 173 | this->ApplyArmature(L, i); 174 | } 175 | 176 | } 177 | 178 | void ModelInstance::ApplyArmature(lua_State* L, int meshIdx) { 179 | if (this->animation->bones == NULL) return; 180 | 181 | if (this->useBakedAnimations) { // overrride baked settings, e.g. we have root motion 182 | lua_getglobal(L, "go"); 183 | lua_getfield(L, -1, "set"); 184 | lua_remove(L, -2); 185 | 186 | dmScript::PushURL(L, this->urls[meshIdx]); 187 | lua_pushstring(L, "animation"); 188 | dmScript::PushVector4(L, Vector4(0,0,0,0)); 189 | lua_call(L, 3, 0); 190 | } 191 | 192 | for (int idx : this->model->meshes[meshIdx].usedBonesIndex) { // set only used bones, critical for performance 193 | int offset = idx * 3; 194 | 195 | Matrix4* m = &this->animation->bones->at(idx); 196 | 197 | Vector4 data[3] = { 198 | m->getRow(0), 199 | m->getRow(1), 200 | m->getRow(2) 201 | }; 202 | 203 | //alternative - dmGameObject::SetProperty - defold/engine/gameobject/src/gameobject/gameobject.h 204 | 205 | for (int i = 0; i < 3; i ++) { 206 | lua_getglobal(L, "go"); 207 | lua_getfield(L, -1, "set"); 208 | lua_remove(L, -2); 209 | 210 | dmScript::PushURL(L, this->urls[meshIdx]); 211 | lua_pushstring(L, "bones"); 212 | dmScript::PushVector4(L, data[i]); 213 | 214 | lua_newtable(L); 215 | lua_pushstring(L, "index"); 216 | lua_pushnumber(L, offset + i + 1); 217 | lua_settable(L, -3); 218 | 219 | lua_call(L, 4, 0); 220 | } 221 | } 222 | } 223 | 224 | void ModelInstance::SetShapes(lua_State* L, unordered_map* values) { 225 | if (this->model->shapes.empty()) return; 226 | vector modified; 227 | 228 | for (auto & it : *values) { 229 | string name = it.first; 230 | float value = it.second; 231 | directShapeValues[name] = value; 232 | if (CONTAINS(&this->model->shapes, name) && (this->shapeValues[name] != value)) { 233 | this->shapeValues[name] = value; 234 | modified.emplace_back(name); 235 | } 236 | } 237 | 238 | if (modified.size() > 0) { 239 | this->CalculateShapes(&modified); 240 | this->ApplyShapes(L); 241 | } 242 | 243 | for (auto it = this->directShapeValues.begin(); it != this->directShapeValues.end();) { 244 | if (it->second == 0) { 245 | it = this->directShapeValues.erase(it); 246 | } else ++it; 247 | } 248 | } 249 | 250 | void ModelInstance::SetShapeFrame(lua_State* L, int idx1) { 251 | //TODO: blending 252 | 253 | if (this->model->shapes.empty() || this->model->shapeFrames.size() < idx1) return; 254 | 255 | vector modified; 256 | 257 | for (auto & it : this->model->shapeFrames[idx1]) { 258 | string name = it.first; 259 | float value = it.second; 260 | if (CONTAINS(&this->directShapeValues, name)) continue; 261 | if (this->shapeValues[name] != value 262 | /*&& fabs(this->shapeValues[name] - value) > this->threshold*/) { 263 | this->shapeValues[name] = value; 264 | modified.emplace_back(name); 265 | } 266 | } 267 | 268 | if (modified.size() > 0) { 269 | this->CalculateShapes(&modified); 270 | this->ApplyShapes(L); 271 | } 272 | } 273 | 274 | void ModelInstance::CalculateShapes(vector* shapeNames) { 275 | Quat iq = Quat::identity(); 276 | this->blended.clear(); 277 | unordered_map modified; 278 | 279 | for (auto &name : *shapeNames) { 280 | for (auto &it: this->model->shapes[name]) { 281 | modified[it.first] = true; 282 | } 283 | } 284 | 285 | for (auto &it : modified) { 286 | int idx = it.first; 287 | ShapeData v; 288 | v.p = Vector3(0); 289 | v.n = Vector3(0); 290 | //v.q = Quat::identity(); 291 | float weight = 0; 292 | 293 | for (auto &s : this->shapeValues) { 294 | float value = s.second; 295 | if (value == 0) continue; 296 | 297 | auto shape = &this->model->shapes[s.first]; 298 | if (CONTAINS(shape, idx)) { 299 | ShapeData* delta = &shape->at(idx); 300 | weight += value; 301 | v.p += delta->p * value; 302 | v.n += delta->n * value; 303 | //v.q *= Lerp(value, iq, delta->q); 304 | } 305 | } 306 | 307 | Vertex vertex = this->model->vertices[idx]; 308 | 309 | if (weight > 1) { 310 | v.p = vertex.p + v.p / weight; 311 | v.n = vertex.n + v.n / weight; 312 | } 313 | else if (weight > 0) { 314 | v.p = vertex.p + v.p; 315 | v.n = vertex.n + v.n; 316 | } else { 317 | v.p = vertex.p; 318 | v.n = vertex.n; 319 | //v.q = iq; 320 | } 321 | 322 | this->blended[idx] = v; 323 | } 324 | 325 | for (auto it = this->shapeValues.begin(); it != this->shapeValues.end();) { 326 | if (it->second == 0) { 327 | it = this->shapeValues.erase(it); 328 | } else ++it; 329 | } 330 | } 331 | 332 | void ModelInstance::ApplyShapes(lua_State* L) { 333 | int size = this->model->meshes.size(); 334 | for (int i = 0; i < size; i++) { 335 | Mesh* mesh = &this->model->meshes[i]; 336 | 337 | bool needUpdate = false; 338 | 339 | float* positions = 0x0; 340 | float* normals = 0x0; 341 | 342 | uint32_t components = 0; 343 | uint32_t stride = 0; 344 | uint32_t items_count = 0; 345 | 346 | dmBuffer::GetStream(this->buffers[i].m_Buffer, dmHashString64("position"), (void**)&positions, &items_count, &components, &stride); 347 | dmBuffer::GetStream(this->buffers[i].m_Buffer, dmHashString64("normal"), (void**)&normals, &items_count, &components, &stride); 348 | 349 | for (auto & it : this->blended) { 350 | ShapeData vertex = it.second; 351 | if (!CONTAINS(&mesh->vertexMap, it.first)) continue; 352 | needUpdate = true; 353 | 354 | for (auto & count : mesh->vertexMap[it.first]) { 355 | 356 | int idx = stride * count; 357 | 358 | positions[idx] = vertex.p.getX(); 359 | positions[idx + 1] = vertex.p.getY(); 360 | positions[idx + 2] = vertex.p.getZ(); 361 | 362 | normals[idx] = vertex.n.getX(); 363 | normals[idx + 1] = vertex.n.getY(); 364 | normals[idx + 2] = vertex.n.getZ(); 365 | 366 | } 367 | } 368 | 369 | if (needUpdate) { 370 | lua_getglobal(L, "native_update_buffer"); 371 | 372 | dmScript::PushURL(L, this->urls[i]); 373 | dmScript::PushBuffer(L, this->buffers[i]); 374 | lua_call(L, 2, 0); 375 | } 376 | } 377 | } 378 | 379 | URL* ModelInstance::AttachGameObject(dmGameObject::HInstance go, string bone) { 380 | int idx = this->animation->FindBone(bone); 381 | if (idx > -1) { 382 | this->animation->CreateBoneGO(go, idx); 383 | return &this->urls[0]; 384 | } 385 | return NULL; 386 | } 387 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/model_instance.h: -------------------------------------------------------------------------------- 1 | #include "animation.h" 2 | 3 | namespace dmScript { 4 | dmMessage::URL* CheckURL(lua_State* L, int index); 5 | void PushURL(lua_State* L, dmMessage::URL const& url ); 6 | } 7 | 8 | using namespace std; 9 | using namespace dmMessage; 10 | 11 | class Model; 12 | 13 | class ModelInstance { 14 | private: 15 | unordered_map shapeValues; 16 | unordered_map directShapeValues; 17 | float threshold = 0; 18 | 19 | void CalculateShapes(vector* shapeNames); 20 | void ApplyShapes(lua_State* L); 21 | void ApplyArmature(lua_State* L, int meshIdx); 22 | void SetShapeFrame(lua_State* L, int idx1); 23 | 24 | bool UseBakedAnimations(); 25 | 26 | public: 27 | Model* model; 28 | Animation* animation = NULL; 29 | bool useBakedAnimations = false; 30 | unordered_map blended; 31 | vector buffers; 32 | vector urls; 33 | dmhash_t runtimeTexture; 34 | 35 | ModelInstance(Model* model, Animation* animation, bool useBakedAnimations, float scaleAABB); 36 | ~ModelInstance(); 37 | 38 | void CreateLuaProxy(lua_State* L); 39 | void SetFrame(int trackIdx, int idx1, int idx2, float factor, RootMotionType rm1, RootMotionType rm2); 40 | void Update(lua_State* L); 41 | void SetShapes(lua_State* L, unordered_map* values); 42 | 43 | URL* AttachGameObject(dmGameObject::HInstance go, string bone); 44 | }; -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/reader.cpp: -------------------------------------------------------------------------------- 1 | #include "reader.h" 2 | 3 | Reader::Reader(const char* file) { 4 | this->data = file; 5 | this->halfPrecision = (this->ReadInt() == 1); 6 | } 7 | 8 | Reader::~Reader() { 9 | } 10 | 11 | //----------------------------------------------------------------- 12 | 13 | bool Reader::IsEOF() { 14 | return *this->data == '\0'; 15 | } 16 | 17 | int Reader::ReadInt() { 18 | const unsigned char *b = (const unsigned char *)this->data; 19 | int value = b[0] | ((int)b[1] << 8) | ((int)b[2] << 16) | ((int)b[3] << 24); 20 | 21 | this->data += 4; 22 | return value; 23 | } 24 | 25 | string Reader::ReadString(int size) { 26 | string res = string(this->data, size); 27 | this->data += size; 28 | return res; 29 | } 30 | 31 | string Reader::ReadString() { 32 | int size = ReadInt(); 33 | return ReadString(size); 34 | } 35 | 36 | //https://stackoverflow.com/questions/3991478/building-a-32-bit-float-out-of-its-4-composite-bytes 37 | float Reader::ReadFloat() { 38 | if (this->halfPrecision) return this->ReadFloatHP(); 39 | 40 | const unsigned char *b = (const unsigned char *)this->data; 41 | uint32_t temp = 0; 42 | 43 | temp = ((b[3] << 24) | 44 | (b[2] << 16) | 45 | (b[1] << 8) | 46 | b[0]); 47 | 48 | this->data += 4; 49 | return *((float *) &temp); 50 | } 51 | 52 | float Reader::ReadFloatHP() { 53 | /* 54 | short* b = (short *)this->data; 55 | 56 | int sign = (*b >> 15) & 1; 57 | int exponent = (*b >> 10) & 0x1f; 58 | int fraction = *b & 0x3ff; 59 | 60 | float sum = 1.0; 61 | int mul = sign == 1 ? -1 : 1; 62 | exponent = exponent - (pow(2, 4) - 1); 63 | 64 | for (int i = 9; i >= -1; i--) { 65 | if ((fraction >> i) & 1 > 0) { 66 | sum += 1.0 / pow(2 , (10 - i)); 67 | } 68 | } 69 | 70 | this->data += 2; 71 | return mul * sum * pow(2, exponent);*/ 72 | 73 | uint16_t* x = (uint16_t *)this->data; 74 | this->data += 2; 75 | 76 | const uint32_t e = (*x&0x7C00)>>10; // exponent 77 | const uint32_t m = (*x&0x03FF)<<13; // mantissa 78 | const float f = (float)m; 79 | const uint32_t v = *(uint32_t*)&f >>23; // evil log2 bit hack to count leading zeros in denormalized format 80 | 81 | const uint32_t result = (*x&0x8000)<<16 | (e!=0)*((e+112)<<23|m) | ((e==0)&(m!=0))*((v-37)<<23|((m<<(150-v))&0x007FE000)); 82 | return *(float*)&result; 83 | } 84 | 85 | Vector3 Reader::ReadVector3() { 86 | float x = ReadFloat(); 87 | float y = ReadFloat(); 88 | float z = ReadFloat(); 89 | return Vector3(x, y, z); 90 | } 91 | 92 | Vector4 Reader::ReadVector4() { 93 | float x = ReadFloat(); 94 | float y = ReadFloat(); 95 | float z = ReadFloat(); 96 | float w = ReadFloat(); 97 | return Vector4(x, y, z, w); 98 | } 99 | 100 | Matrix4 Reader::ReadMatrix() { //3x4 101 | Matrix4 m = Matrix4::identity(); 102 | m.setRow(0, this->ReadVector4()); 103 | m.setRow(1, this->ReadVector4()); 104 | m.setRow(2, this->ReadVector4()); 105 | return m; 106 | } 107 | 108 | Transform3d Reader::ReadTransform() { 109 | Transform3d res; 110 | Vector3 p = ReadVector3(); 111 | res.position = Vector3(p.getX(), p.getZ(), -p.getY()); // blender coords fix 112 | 113 | Vector3 euler = ReadVector3(); 114 | Quat qx = Quat::rotationX(euler.getX()); 115 | Quat qy = Quat::rotationY(euler.getZ()); 116 | Quat qz = Quat::rotationZ(-euler.getY()); 117 | res.rotation = qy * qz * qx; 118 | 119 | Vector3 s = ReadVector3(); 120 | res.scale = Vector3(s.getX(), s.getZ(), s.getY()); 121 | 122 | Matrix4 mR = Matrix4::rotationZYX(euler); 123 | Matrix4 mT = Matrix4::translation(p); 124 | Matrix4 mS = Matrix4::identity(); 125 | mS[0][0] = s.getX(); 126 | mS[1][1] = s.getY(); 127 | mS[2][2] = s.getZ(); 128 | 129 | res.matrix = mT * mR * mS; 130 | 131 | return res; 132 | } 133 | 134 | -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/reader.h: -------------------------------------------------------------------------------- 1 | #include "mesh.h" 2 | #include 3 | 4 | class Reader 5 | { 6 | private: 7 | const char* data; 8 | bool halfPrecision; 9 | 10 | public: 11 | bool IsEOF(); 12 | int ReadInt(); 13 | float ReadFloat(); 14 | float ReadFloatHP(); 15 | Vector3 ReadVector3(); 16 | Vector4 ReadVector4(); 17 | Matrix4 ReadMatrix(); 18 | string ReadString(); 19 | string ReadString(int size); 20 | Transform3d ReadTransform(); 21 | Reader(const char* file); 22 | ~Reader(); 23 | }; -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/track.cpp: -------------------------------------------------------------------------------- 1 | #include "track.h" 2 | #include "model.h" 3 | #include "armature.h" 4 | 5 | AnimationTrack::AnimationTrack(Armature* armature, vector* bones) { 6 | this->armature = armature; 7 | this->transform = Matrix4::identity(); 8 | this->inversed = Matrix4::identity(); 9 | 10 | if (bones != NULL) { 11 | for (auto & bone : *bones) { 12 | int idx = armature->FindBone(bone); 13 | if (idx > -1) { 14 | this->mask.push_back(idx); 15 | } 16 | } 17 | } 18 | } 19 | 20 | void AnimationTrack::Interpolate() { 21 | int size = this->armature->frames[this->frame1].size(); 22 | 23 | if (this->interpolated.size() != size) { 24 | this->interpolated = this->armature->frames[this->frame1]; 25 | } 26 | 27 | for (int i = 0; i < size; i ++) { //TODO: optimize, interpolate using track mask 28 | MatrixBlend( 29 | &this->armature->frames[this->frame1].at(i), 30 | &this->armature->frames[this->frame2].at(i), 31 | &this->interpolated[i], 32 | this->factor); 33 | } 34 | 35 | this->bones = &interpolated; 36 | } 37 | 38 | void AnimationTrack::SwitchRootMotion() { 39 | std::swap(this->rmdata1, this->rmdata2); 40 | } 41 | 42 | void AnimationTrack::ResetRootMotion(int frameIdx, bool isPrimary) { 43 | RootMotionData* data = isPrimary ? &this->rmdata1 : &this->rmdata2; 44 | 45 | int bi = this->armature->rootBoneIdx; 46 | Matrix4 local = this->armature->localBones[bi]; 47 | 48 | Matrix4 m = this->armature->frames[frameIdx][bi]; 49 | m = local * m * Inverse(local); 50 | data->rotation = m; 51 | 52 | Vector4 v = this->transform * m.getCol3(); 53 | data->position = Vector3(v[0], v[2], -v[1]); 54 | 55 | data->offset = m.getCol3(); 56 | 57 | //m = this->transform * m * Inverse(this->transform); 58 | data->angle = 0; // QuatToEuler(Quat(m.getUpper3x3())).getZ(); 59 | } 60 | 61 | void AnimationTrack::SetTransform(Matrix4 matrix) { 62 | this->transform = matrix; 63 | this->inversed = Inverse(this->transform); 64 | 65 | this->ResetRootMotion(0, true); 66 | this->ResetRootMotion(0, false); 67 | } 68 | 69 | 70 | //TODO: refactoring 71 | void AnimationTrack::GetRootMotionForFrame(int idx, RootMotionData* data, RootMotionType rm, Matrix4& rootBone, Vector3& position, float& angle) { 72 | int bi = this->armature->rootBoneIdx; 73 | 74 | Matrix4 local = this->armature->localBones[bi]; 75 | rootBone = local * armature->frames[idx][bi] * Inverse(local); 76 | 77 | Vector4 posePosition = rootBone.getCol3(); 78 | Matrix4 worldRootBone = this->transform * rootBone * Inverse(data->rotation) * this->inversed; 79 | 80 | if ((rm == RootMotionType::Rotation) || (rm == RootMotionType::Both)) { 81 | 82 | Quat q = Quat(worldRootBone.getUpper3x3()); 83 | Vector3 eulers = QuatToEuler(q); 84 | angle = eulers.getZ(); 85 | 86 | Matrix4 inv = Matrix4::rotationZ(-angle); 87 | 88 | Matrix4 mm = inv * worldRootBone; 89 | /* 90 | Matrix4 mX = Matrix4::rotationX(eulers.getX()); 91 | Matrix4 mY = Matrix4::rotationY(eulers.getY()); 92 | Matrix4 mm = mX * mY; //probably the wrong order?*/ 93 | 94 | mm = this->inversed * mm * this->transform; 95 | 96 | mm = mm * (data->rotation); 97 | 98 | Matrix3 mXZ = mm.getUpper3x3(); 99 | rootBone.setUpper3x3(mXZ); 100 | 101 | mm = this->inversed * inv * this->transform; 102 | 103 | Vector4 pPosition = mm * posePosition; 104 | 105 | rootBone.setCol3(pPosition); 106 | } 107 | 108 | if ((rm == RootMotionType::Position) || (rm == RootMotionType::Both) || (rm == RootMotionType::Forward)) { 109 | 110 | Vector4 v = this->transform * posePosition; 111 | position = Vector3(v.getX(), v.getZ(), -v.getY()); 112 | 113 | if (rm == RootMotionType::Forward) { // bake Y & X into pose, move game object only along Z 114 | v = this->inversed * Vector4(v[0], 0, v[2], 1) + Vector4(0, data->offset[1], 0, 0); 115 | Vector4 t = this->transform * data->offset; 116 | position[0] = t[0]; 117 | position[1] = t[2]; 118 | } else { 119 | v = data->offset; 120 | } 121 | 122 | rootBone.setCol3(v); 123 | } 124 | 125 | rootBone = Inverse(local) * rootBone * local; 126 | } 127 | 128 | void AnimationTrack::ExtractRootMotion(dmGameObject::HInstance root, RootMotionType rm1, RootMotionType rm2) { 129 | if (rm1 == RootMotionType::None && rm2 == RootMotionType::None) return; 130 | 131 | int bi = this->armature->rootBoneIdx; 132 | 133 | Matrix4 rootBone1, rootBone2; 134 | Vector3 position1 = Vector3(0); 135 | Vector3 position2 = Vector3(0); 136 | float angle1 = this->rmdata1.angle; 137 | float angle2 = this->rmdata2.angle; 138 | 139 | bool applyRotation1 = (rm1 == RootMotionType::Rotation) || (rm1 == RootMotionType::Both); 140 | bool applyPosition1 = (rm1 == RootMotionType::Position) || (rm1 == RootMotionType::Both) || (rm1 == RootMotionType::Forward); 141 | bool applyRotation2 = (this->frame2 > -1) && ((rm2 == RootMotionType::Rotation) || (rm2 == RootMotionType::Both)); 142 | bool applyPosition2 = (this->frame2 > -1) && ((rm2 == RootMotionType::Position) || (rm2 == RootMotionType::Both) || (rm2 == RootMotionType::Forward)); 143 | 144 | 145 | Matrix4* bone1 = &this->armature->frames[this->frame1][bi]; 146 | Matrix4* bone2 = this->frame2 > -1 ? &this->armature->frames[this->frame2][bi] : NULL; 147 | 148 | Quat r1 = Quat::identity(); 149 | Quat r2 = Quat::identity(); 150 | 151 | if (rm1 != RootMotionType::None) { 152 | this->GetRootMotionForFrame(this->frame1, &this->rmdata1, rm1, rootBone1, position1, angle1); 153 | bone1 = &rootBone1; 154 | 155 | r1 = Quat::rotationY(angle1 - this->rmdata1.angle); 156 | this->rmdata1.angle = angle1; 157 | 158 | } 159 | 160 | if (applyPosition2 || applyRotation2) { 161 | this->GetRootMotionForFrame(this->frame2, &this->rmdata2, rm2, rootBone2, position2, angle2); 162 | bone2 = &rootBone2; 163 | 164 | r2 = Quat::rotationY(angle2 - this->rmdata2.angle); 165 | this->rmdata2.angle = angle2; 166 | } 167 | 168 | Quat rotation = dmGameObject::GetRotation(root); 169 | 170 | if (applyRotation1 || applyRotation2) 171 | { 172 | Quat diff = Slerp(this->factor, r1, r2); 173 | rotation = diff * rotation; 174 | dmGameObject::SetRotation(root, rotation); 175 | } 176 | 177 | 178 | Matrix4 local = this->armature->localBones[bi]; 179 | 180 | if (applyPosition1) { 181 | 182 | Vector3 velocity = position1 - this->rmdata1.position; 183 | this->rmdata1.position = position1; 184 | 185 | if (applyRotation1) { 186 | Matrix3 mm = Matrix3::rotationY(-angle1); 187 | velocity = mm * velocity; 188 | } 189 | 190 | position1 = dmVMath::Rotate(rotation, velocity); 191 | //dmLogInfo("%d, %f, %f, %f", this->GetFrameIdx(), position1[0], position1[1], position1[2]); 192 | 193 | } 194 | 195 | 196 | if (applyPosition2) { 197 | Vector3 velocity = position2 - this->rmdata2.position; 198 | this->rmdata2.position = position2; 199 | 200 | if (applyRotation2) { 201 | Matrix3 mm = Matrix3::rotationY(-angle2); 202 | velocity = mm * velocity; 203 | } 204 | 205 | position2 = dmVMath::Rotate(rotation, velocity); 206 | } 207 | 208 | 209 | if (applyPosition1 || applyPosition2) 210 | { 211 | Point3 p = dmGameObject::GetPosition(root); 212 | Vector3 position = Slerp(this->factor, position1, position2); 213 | 214 | dmGameObject::SetPosition(root, p + position); 215 | } 216 | 217 | if (this->frame2 == -1) { 218 | this->interpolated = armature->frames[this->frame1]; //copy 219 | this->bones = &this->interpolated; 220 | this->interpolated[bi] = *bone1; 221 | Vector4 v = bone1->getRow(3); 222 | 223 | }else { 224 | MatrixBlend(bone1, bone2, &this->interpolated[bi], this->factor); 225 | } 226 | 227 | } -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/track.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "utils.h" 3 | #include 4 | 5 | using namespace std; 6 | 7 | class Model; 8 | class Armature; 9 | 10 | enum class RootMotionType { None, Rotation, Position, Both, Forward }; 11 | 12 | class AnimationTrack { 13 | private: 14 | Armature* armature; 15 | Matrix4 transform; 16 | Matrix4 inversed; 17 | RootMotionData rmdata1; 18 | RootMotionData rmdata2; 19 | void GetRootMotionForFrame(int idx, RootMotionData* data, RootMotionType rm, Matrix4& rootBone, Vector3& position, float& angle); 20 | 21 | public: 22 | 23 | vector interpolated; 24 | 25 | int frame1 = -1; 26 | int frame2 = -1; 27 | float factor = 0.0; 28 | float weight = 1.0; 29 | bool additive = false; // blending mode: override, TODO: additive 30 | vector mask; 31 | 32 | vector* bones = NULL; 33 | 34 | void Interpolate(); 35 | void ExtractRootMotion(dmGameObject::HInstance root, RootMotionType rm1, RootMotionType rm2); 36 | void ResetRootMotion(int frameIdx, bool isPrimary); 37 | void SetTransform(Matrix4 matrix); 38 | void SwitchRootMotion(); 39 | 40 | AnimationTrack(Armature* armature, vector* bones); 41 | }; -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | void MatrixBlend (Matrix4* m1, Matrix4* m2, Matrix4* result, float factor) { 4 | 5 | //dual quats? 6 | //https://github.com/PacktPublishing/OpenGL-Build-High-Performance-Graphics/blob/master/Module%201/Chapter08/DualQuaternionSkinning/main.cpp 7 | //https://subscription.packtpub.com/book/application_development/9781788296724/1/ch01lvl1sec09/8-skeletal-and-physically-based-simulation-on-the-gpu 8 | //https://xbdev.net/misc_demos/demos/dual_quaternions_beyond/paper.pdf 9 | //https://github.com/chinedufn/skeletal-animation-system/blob/master/src/blend-dual-quaternions.js 10 | //https://github.com/Achllle/dual_quaternions/blob/master/src/dual_quaternions/dual_quaternions.py 11 | 12 | Vector4 t = Lerp(factor, m1->getCol3(), m2->getCol3()); 13 | 14 | Quat q1 = Quat(m1->getUpper3x3()); 15 | Quat q2 = Quat(m2->getUpper3x3()); 16 | Quat q = Slerp(factor, q1, q2); 17 | 18 | result->setUpper3x3(Matrix3::rotation(q)); 19 | result->setCol3(t); 20 | } 21 | 22 | Vector3 QuatToEuler(Quat q) { 23 | Vector3 angles; 24 | const auto x = q.getX(); 25 | const auto y = q.getY(); 26 | const auto z = q.getZ(); 27 | const auto w = q.getW(); 28 | 29 | // roll (x-axis rotation) 30 | double sinr_cosp = 2 * (w * x + y * z); 31 | double cosr_cosp = 1 - 2 * (x * x + y * y); 32 | angles[0] = std::atan2(sinr_cosp, cosr_cosp); 33 | 34 | // pitch (y-axis rotation) 35 | double sinp = 2 * (w * y - z * x); 36 | if (std::abs(sinp) >= 1) 37 | angles[1] = std::copysign(M_PI / 2, sinp); // use 90 degrees if out of range 38 | else 39 | angles[1] = std::asin(sinp); 40 | 41 | // yaw (z-axis rotation) 42 | double siny_cosp = 2 * (w * z + x * y); 43 | double cosy_cosp = 1 - 2 * (y * y + z * z); 44 | angles[2] = std::atan2(siny_cosp, cosy_cosp); 45 | 46 | return angles; 47 | } -------------------------------------------------------------------------------- /def-mesh/mesh_utils/src/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef M_PI 9 | #define M_PI 3.14159265358979323846 10 | #endif 11 | 12 | using namespace std; 13 | using namespace dmVMath; 14 | 15 | struct RootMotionData { 16 | float angle = 0; 17 | Vector3 position = Vector3(0); 18 | Vector4 offset = Vector4(0); 19 | Matrix4 rotation = Matrix4::identity(); 20 | }; 21 | 22 | struct Face { 23 | int v[3]; 24 | bool isFlat = false; 25 | Vector3 n; 26 | }; 27 | 28 | struct Vertex { 29 | Vector3 p; 30 | Vector3 n; 31 | }; 32 | 33 | struct ShapeData : Vertex { 34 | //Quat q; 35 | }; 36 | 37 | 38 | struct SkinData { 39 | int idx = -1; 40 | float weight = 0; 41 | }; 42 | 43 | struct Transform3d { 44 | Vector3 position; 45 | Vector3 scale; 46 | Quat rotation; 47 | Matrix4 matrix; 48 | }; 49 | 50 | struct Ramp { 51 | float p1 = 0; 52 | float v1 = 0; 53 | float p2 = 0; 54 | float v2 = 0; 55 | }; 56 | 57 | struct NormalMap { 58 | string texture; 59 | float value = 0; 60 | }; 61 | 62 | struct SpecularMap { 63 | string texture; 64 | float value = 0; 65 | int invert = 0; 66 | Ramp ramp; 67 | }; 68 | 69 | struct RoughnessMap { 70 | string texture; 71 | float value = 0; 72 | Ramp ramp; 73 | }; 74 | 75 | inline template 76 | bool CONTAINS(unordered_map* map, K key){ 77 | return map->find(key) != map->end(); 78 | } 79 | 80 | Vector3 QuatToEuler(Quat q); 81 | 82 | void MatrixBlend (Matrix4* m1, Matrix4* m2, Matrix4* result, float factor); 83 | 84 | struct Material { 85 | string name; 86 | int type; // 0 - opaque, 1 - blend, 2 - hashed 87 | Vector4 color; 88 | string texture; 89 | NormalMap normal; 90 | SpecularMap specular; 91 | RoughnessMap roughness; 92 | 93 | void CreateLuaProxy(lua_State* L) { 94 | lua_pushstring(L, "material"); 95 | lua_newtable(L); 96 | 97 | lua_pushstring(L, "name"); 98 | lua_pushstring(L, this->name.c_str()); 99 | lua_settable(L, -3); 100 | 101 | lua_pushstring(L, "type"); 102 | lua_pushnumber(L, this->type); 103 | lua_settable(L, -3); 104 | 105 | lua_pushstring(L, "color"); 106 | dmScript::PushVector4(L, this->color); 107 | lua_settable(L, -3); 108 | 109 | if (!this->texture.empty()) { 110 | lua_pushstring(L, "texture"); 111 | lua_pushstring(L, this->texture.c_str()); 112 | lua_settable(L, -3); 113 | } 114 | 115 | lua_pushstring(L, "normal"); 116 | lua_newtable(L); 117 | lua_pushstring(L, "value"); 118 | lua_pushnumber(L, this->normal.value); 119 | lua_settable(L, -3); 120 | if (!this->normal.texture.empty()) { 121 | lua_pushstring(L, "texture"); 122 | lua_pushstring(L, this->normal.texture.c_str()); 123 | lua_settable(L, -3); 124 | } 125 | lua_settable(L, -3); 126 | 127 | lua_pushstring(L, "specular"); 128 | lua_newtable(L); 129 | lua_pushstring(L, "value"); 130 | lua_pushnumber(L, this->specular.value); 131 | lua_settable(L, -3); 132 | lua_pushstring(L, "invert"); 133 | lua_pushnumber(L, this->specular.invert); 134 | lua_settable(L, -3); 135 | if (!this->specular.texture.empty()) { 136 | lua_pushstring(L, "texture"); 137 | lua_pushstring(L, this->specular.texture.c_str()); 138 | lua_settable(L, -3); 139 | } 140 | lua_pushstring(L, "ramp"); 141 | lua_newtable(L); 142 | lua_pushstring(L, "p1"); 143 | lua_pushnumber(L, this->specular.ramp.p1); 144 | lua_settable(L, -3); 145 | lua_pushstring(L, "v1"); 146 | lua_pushnumber(L, this->specular.ramp.v1); 147 | lua_settable(L, -3); 148 | lua_pushstring(L, "p2"); 149 | lua_pushnumber(L, this->specular.ramp.p2); 150 | lua_settable(L, -3); 151 | lua_pushstring(L, "v2"); 152 | lua_pushnumber(L, this->specular.ramp.v2); 153 | lua_settable(L, -3); 154 | lua_settable(L, -3); 155 | lua_settable(L, -3); 156 | 157 | lua_pushstring(L, "roughness"); 158 | lua_newtable(L); 159 | lua_pushstring(L, "value"); 160 | lua_pushnumber(L, this->roughness.value); 161 | lua_settable(L, -3); 162 | if (!this->roughness.texture.empty()) { 163 | lua_pushstring(L, "texture"); 164 | lua_pushstring(L, this->roughness.texture.c_str()); 165 | lua_settable(L, -3); 166 | } 167 | lua_pushstring(L, "ramp"); 168 | lua_newtable(L); 169 | lua_pushstring(L, "p1"); 170 | lua_pushnumber(L, this->roughness.ramp.p1); 171 | lua_settable(L, -3); 172 | lua_pushstring(L, "v1"); 173 | lua_pushnumber(L, this->roughness.ramp.v1); 174 | lua_settable(L, -3); 175 | lua_pushstring(L, "p2"); 176 | lua_pushnumber(L, this->roughness.ramp.p2); 177 | lua_settable(L, -3); 178 | lua_pushstring(L, "v2"); 179 | lua_pushnumber(L, this->roughness.ramp.v2); 180 | lua_settable(L, -3); 181 | lua_settable(L, -3); 182 | lua_settable(L, -3); 183 | 184 | lua_settable(L, -3); 185 | } 186 | }; 187 | -------------------------------------------------------------------------------- /demo/complex/complex.collection: -------------------------------------------------------------------------------- 1 | name: "complex" 2 | instances { 3 | id: "model" 4 | prototype: "/def-mesh/binary.go" 5 | position { 6 | y: -12.0 7 | } 8 | scale3 { 9 | x: 8.0 10 | y: 8.0 11 | z: 8.0 12 | } 13 | } 14 | scale_along_z: 1 15 | embedded_instances { 16 | id: "go" 17 | children: "model" 18 | data: "components {\n" 19 | " id: \"demo\"\n" 20 | " component: \"/main/demo.script\"\n" 21 | "}\n" 22 | "components {\n" 23 | " id: \"complex\"\n" 24 | " component: \"/demo/complex/complex.script\"\n" 25 | "}\n" 26 | "" 27 | } 28 | -------------------------------------------------------------------------------- /demo/complex/complex.script: -------------------------------------------------------------------------------- 1 | local binary = require "def-mesh.binary" 2 | 3 | local function blink(self) 4 | self.instance.set_shapes({["blink_upper_down.R"] = 1, ["blink_upper_down.L"] = 1}) 5 | timer.delay(0.3, false, function() 6 | self.instance.set_shapes({["blink_upper_down.R"] = 0, ["blink_upper_down.L"] = 0}) 7 | timer.delay(math.random(2, 4), false, blink) 8 | end) 9 | end 10 | 11 | function init(self) 12 | math.randomseed(os.time() * 10000) 13 | math.random() 14 | math.random() 15 | math.random() 16 | 17 | self.instance = binary.load("/model", "/assets/joy.bin", {textures = "textures/complex", verbose = true}) 18 | blink(self) 19 | 20 | end 21 | 22 | function final(self) 23 | self.instance.delete() 24 | end 25 | 26 | 27 | --[[ 28 | ----------------------------------performance test----------------------------------- 29 | local shapes = {"lip_roll_upper_in.L", "lip_roll_upper_in.R", "lip_smile.L", "lip_smile.R", "cheek_puff_out.R", "cheek_puff_out.L", "blink_upper_down.R", "blink_upper_down.L", "brow_upper_up.L", "brow_upper_up.R"} 30 | 31 | function on_message(self, message_id, message, sender) 32 | if message_id == hash("mesh_loaded") then 33 | self.loaded = true 34 | end 35 | end 36 | 37 | function update(self, dt) 38 | if self.loaded then 39 | local res = {} 40 | for _, name in ipairs(shapes) do 41 | res[name] = math.random() 42 | end 43 | msg.post("/model", "set_shapes", {shapes = res}) 44 | 45 | end 46 | end 47 | --]] -------------------------------------------------------------------------------- /demo/dancer/dancer.collection: -------------------------------------------------------------------------------- 1 | name: "dancer" 2 | instances { 3 | id: "model" 4 | prototype: "/def-mesh/binary.go" 5 | } 6 | scale_along_z: 1 7 | embedded_instances { 8 | id: "go" 9 | children: "model" 10 | data: "components {\n" 11 | " id: \"script\"\n" 12 | " component: \"/demo/dancer/dancer.script\"\n" 13 | "}\n" 14 | "components {\n" 15 | " id: \"demo\"\n" 16 | " component: \"/main/demo.script\"\n" 17 | "}\n" 18 | "" 19 | } 20 | -------------------------------------------------------------------------------- /demo/dancer/dancer.script: -------------------------------------------------------------------------------- 1 | go.property('Pants', resource.material('/demo/dancer/pants.material')) 2 | 3 | local binary = require "def-mesh.binary" 4 | 5 | function init(self) 6 | self.instance = binary.load("/model", "/assets/dancer.bin", 7 | { 8 | verbose = true, 9 | bake = true, 10 | materials = {["Pants"] = self.Pants} 11 | }) -- override material example 12 | 13 | self.instance.set("light", vmath.vector4(0,.5,1,0)) 14 | 15 | self.count = 1 16 | self.time = 0 17 | end 18 | 19 | function update(self, dt) 20 | if self.instance then 21 | self.time = self.time + dt 22 | if self.time > .03 then 23 | self.time = 0.0 24 | self.instance.set_frame(self.count) 25 | 26 | if self.count > self.instance.total_frames then 27 | self.count = 1 28 | else 29 | self.count = self.count + 1 30 | end 31 | end 32 | 33 | end 34 | end 35 | 36 | function final(self) 37 | self.instance.delete() 38 | end 39 | -------------------------------------------------------------------------------- /demo/dancer/pants.fp: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in highp vec4 var_position; 4 | in highp vec3 var_normal; 5 | in highp vec2 var_texcoord0; 6 | 7 | in vec3 var_light_dir; 8 | in vec3 var_vh; 9 | 10 | uniform sampler2D tex_diffuse; 11 | uniform sampler2D tex_normal; 12 | uniform sampler2D tex_rough; 13 | uniform sampler2D tex_anim; 14 | 15 | uniform sampler2D tex4; 16 | uniform sampler2D tex5; 17 | uniform sampler2D tex6; 18 | uniform sampler2D tex7; 19 | 20 | uniform uniforms_fp { 21 | vec4 texel; 22 | vec4 options; 23 | //x - texture, 24 | //y - normal map strength, 25 | //z - alpha mode, 0 - opaque, 1 - blend, 2 - hashed 26 | 27 | vec4 base_color; 28 | 29 | vec4 options_specular; 30 | //x - 1: specular map, 2: specular map inverted 31 | //y - specular power 32 | //z - roughness 33 | //w - roughness map 34 | 35 | vec4 spec_ramp; 36 | vec4 rough_ramp; 37 | //x - p1, y - v1, z = p2, w = v2 38 | // if x == -1 - no ramp 39 | }; 40 | 41 | out vec4 frag_color; 42 | 43 | float ramp(float factor, vec4 data) 44 | { 45 | if (data.x == -1.) {return factor;} 46 | if (factor <= data.x) {return data.y;} 47 | if (factor >= data.z) {return data.w;} 48 | float f = (factor - data.x) / (data.z - data.x); 49 | return mix(data.y, data.w, f); 50 | } 51 | 52 | vec4 pcf_4x4(vec2 proj) { 53 | vec4 sum = vec4(0.); 54 | float x, y; 55 | 56 | for (y = -1.5; y <= 1.5; y += 1.0) { 57 | for (x = -1.5; x <= 1.5; x += 1.0) { 58 | vec2 uv = proj.xy + texel.xy * vec2(x, y); 59 | if (uv.x < 0. ||uv.x > 1. || uv.y < 0. ||uv.y > 1.) {continue;} 60 | sum += texture(tex_diffuse, uv); 61 | } 62 | } 63 | return sum / 16; 64 | } 65 | 66 | //http://www.thetenthplanet.de/archives/1180 67 | highp mat3 cotangent_frame(highp vec3 N, highp vec3 p, highp vec2 uv) 68 | { 69 | // get edge vectors of the pixel triangle 70 | highp vec3 dp1 = dFdx(p); 71 | highp vec3 dp2 = dFdy(p); 72 | highp vec2 duv1 = dFdx(uv); 73 | highp vec2 duv2 = dFdy(uv); 74 | // solve the linear system 75 | highp vec3 dp2perp = cross(dp2, N); 76 | highp vec3 dp1perp = cross(N, dp1); 77 | highp vec3 T = dp2perp * duv1.x + dp1perp * duv2.x; 78 | highp vec3 B = dp2perp * duv1.y + dp1perp * duv2.y; 79 | // construct a scale-invariant frame 80 | highp float invmax = inversesqrt(max(dot(T,T), dot(B,B))); 81 | return mat3(T * invmax, B * invmax, N); 82 | } 83 | 84 | void main() 85 | { 86 | vec4 color = vec4(0., 0., 1., 1.); 87 | 88 | if (options.x == 1.) {color = texture(tex_diffuse, var_texcoord0);} 89 | 90 | if (options.z == 2. && color.w > 0 && color.w < 1) { 91 | color = pcf_4x4(var_texcoord0); 92 | } 93 | 94 | //----------------------- 95 | // Diffuse light calculations 96 | vec3 ambient = vec3(0.2); 97 | vec3 specular = vec3(0.0); 98 | highp vec3 n = var_normal; 99 | 100 | if (options.y > 0.0) { 101 | n = texture(tex_normal, var_texcoord0).xyz * 255./127. - 128./127.; 102 | n.xy *= options.y; 103 | highp mat3 TBN = cotangent_frame(normalize(var_normal), var_position.xyz, var_texcoord0); 104 | n = TBN * n; 105 | } 106 | n = normalize(n); 107 | 108 | float light = max(dot(n, var_light_dir), 0.0); 109 | if (light > 0.0) { 110 | float roughness = options_specular.w > 0.0 ? texture(tex_rough, var_texcoord0).x : options_specular.z; 111 | roughness = ramp(roughness, rough_ramp); 112 | float k = mix(1.0, 0.4, roughness); 113 | roughness = 32. / (roughness * roughness); 114 | roughness = min(500.0, roughness); 115 | float sp = options_specular.x > 0.0 ? texture(tex_rough, var_texcoord0).x : options_specular.y; 116 | vec3 spec_power = vec3(ramp(sp, spec_ramp)); 117 | if (options_specular.x > 1.0) {spec_power = 1.0 - spec_power;} ///invert flag 118 | 119 | specular = k * k * spec_power * pow(max(dot(n, var_vh), 0.0), roughness); 120 | } 121 | 122 | vec3 diffuse = light + ambient; 123 | diffuse = clamp(diffuse, 0.0, 1.0); 124 | 125 | frag_color = vec4(color.xyz * diffuse + specular, color.w); 126 | } 127 | 128 | -------------------------------------------------------------------------------- /demo/dancer/pants.material: -------------------------------------------------------------------------------- 1 | name: "model" 2 | tags: "model" 3 | vertex_program: "/def-mesh/materials/model.vp" 4 | fragment_program: "/demo/dancer/pants.fp" 5 | vertex_space: VERTEX_SPACE_LOCAL 6 | vertex_constants { 7 | name: "mtx_worldview" 8 | type: CONSTANT_TYPE_WORLDVIEW 9 | } 10 | vertex_constants { 11 | name: "mtx_view" 12 | type: CONSTANT_TYPE_VIEW 13 | } 14 | vertex_constants { 15 | name: "mtx_proj" 16 | type: CONSTANT_TYPE_PROJECTION 17 | } 18 | vertex_constants { 19 | name: "mtx_normal" 20 | type: CONSTANT_TYPE_NORMAL 21 | } 22 | vertex_constants { 23 | name: "light" 24 | type: CONSTANT_TYPE_USER 25 | value { 26 | x: 2.0 27 | y: 2.0 28 | z: 2.0 29 | w: 1.0 30 | } 31 | } 32 | vertex_constants { 33 | name: "animation" 34 | type: CONSTANT_TYPE_USER 35 | value { 36 | } 37 | } 38 | fragment_constants { 39 | name: "texel" 40 | type: CONSTANT_TYPE_USER 41 | value { 42 | x: 1.0 43 | y: 1.0 44 | z: 1.0 45 | w: 1.0 46 | } 47 | } 48 | fragment_constants { 49 | name: "options" 50 | type: CONSTANT_TYPE_USER 51 | value { 52 | } 53 | } 54 | fragment_constants { 55 | name: "options_specular" 56 | type: CONSTANT_TYPE_USER 57 | value { 58 | } 59 | } 60 | fragment_constants { 61 | name: "base_color" 62 | type: CONSTANT_TYPE_USER 63 | value { 64 | x: 0.8 65 | y: 0.8 66 | z: 0.8 67 | w: 1.0 68 | } 69 | } 70 | fragment_constants { 71 | name: "rough_ramp" 72 | type: CONSTANT_TYPE_USER 73 | value { 74 | x: -1.0 75 | } 76 | } 77 | fragment_constants { 78 | name: "spec_ramp" 79 | type: CONSTANT_TYPE_USER 80 | value { 81 | x: -1.0 82 | } 83 | } 84 | 85 | samplers { 86 | name: "tex_anim" 87 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 88 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 89 | filter_min: FILTER_MODE_MIN_NEAREST 90 | filter_mag: FILTER_MODE_MAG_NEAREST 91 | } 92 | samplers { 93 | name: "tex_diffuse" 94 | wrap_u: WRAP_MODE_REPEAT 95 | wrap_v: WRAP_MODE_REPEAT 96 | filter_min: FILTER_MODE_MIN_LINEAR 97 | filter_mag: FILTER_MODE_MAG_LINEAR 98 | } 99 | samplers { 100 | name: "tex_normal" 101 | wrap_u: WRAP_MODE_REPEAT 102 | wrap_v: WRAP_MODE_REPEAT 103 | filter_min: FILTER_MODE_MIN_LINEAR 104 | filter_mag: FILTER_MODE_MAG_LINEAR 105 | } 106 | samplers { 107 | name: "tex_rough" 108 | wrap_u: WRAP_MODE_REPEAT 109 | wrap_v: WRAP_MODE_REPEAT 110 | filter_min: FILTER_MODE_MIN_LINEAR 111 | filter_mag: FILTER_MODE_MAG_LINEAR 112 | } 113 | 114 | samplers { 115 | name: "tex_runtime" 116 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 117 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 118 | filter_min: FILTER_MODE_MIN_NEAREST 119 | filter_mag: FILTER_MODE_MAG_NEAREST 120 | } -------------------------------------------------------------------------------- /demo/layers/layers.collection: -------------------------------------------------------------------------------- 1 | name: "layers" 2 | instances { 3 | id: "model" 4 | prototype: "/def-mesh/binary.go" 5 | position { 6 | y: -1.0 7 | } 8 | } 9 | scale_along_z: 1 10 | embedded_instances { 11 | id: "go" 12 | children: "model" 13 | data: "components {\n" 14 | " id: \"script\"\n" 15 | " component: \"/demo/layers/layers.script\"\n" 16 | "}\n" 17 | "components {\n" 18 | " id: \"demo\"\n" 19 | " component: \"/main/demo.script\"\n" 20 | "}\n" 21 | "" 22 | } 23 | -------------------------------------------------------------------------------- /demo/layers/layers.script: -------------------------------------------------------------------------------- 1 | local tracks = require "demo.layers.mask" 2 | local binary = require "def-mesh.binary" 3 | 4 | function init(self) 5 | self.instance = binary.load("/model", "/assets/tracks.bin") 6 | 7 | self.track = self.instance.animator.add_track(tracks.mask1) 8 | self.instance.animator.set_weight(self.track, 0) 9 | 10 | self.instance.animator.play({start = 0, finish = 41}, {playback = go.PLAYBACK_LOOP_FORWARD}) 11 | self.instance.animator.play({start = 42, finish = 77}, {track = 1, playback = go.PLAYBACK_LOOP_FORWARD}) 12 | end 13 | 14 | 15 | function update(self, dt) 16 | if self.instance then self.instance.animator.update(dt) end 17 | 18 | imgui.set_next_window_size(400, 300) 19 | imgui.set_next_window_pos(1400, 900) 20 | imgui.begin_window("tracks") 21 | 22 | local changed, checked = imgui.checkbox("shoot", self.shoot) 23 | if changed then 24 | self.shoot = checked 25 | self.instance.animator.set_weight(self.track, checked and 1 or 0, .3) 26 | end 27 | 28 | imgui.end_window() 29 | end -------------------------------------------------------------------------------- /demo/layers/mask.lua: -------------------------------------------------------------------------------- 1 | return { 2 | mask1 = { 3 | --"mixamorig:Hips", 4 | --"mixamorig:Spine", 5 | --"mixamorig:Spine1", 6 | "mixamorig:Spine2", 7 | "mixamorig:Neck", 8 | "mixamorig:Head", 9 | "mixamorig:RightShoulder", 10 | "mixamorig:RightArm", 11 | "mixamorig:RightForeArm", 12 | "mixamorig:RightHand", 13 | "mixamorig:RightHandThumb1", 14 | "mixamorig:RightHandThumb2", 15 | "mixamorig:RightHandThumb3", 16 | "mixamorig:RightHandIndex1", 17 | "mixamorig:RightHandIndex2", 18 | "mixamorig:RightHandIndex3", 19 | "mixamorig:RightHandMiddle1", 20 | "mixamorig:RightHandMiddle2", 21 | "mixamorig:RightHandMiddle3", 22 | "mixamorig:RightHandRing1", 23 | "mixamorig:RightHandRing2", 24 | "mixamorig:RightHandRing3", 25 | "mixamorig:RightHandPinky1", 26 | "mixamorig:RightHandPinky2", 27 | "mixamorig:RightHandPinky3", 28 | "mixamorig:LeftShoulder", 29 | "mixamorig:LeftArm", 30 | "mixamorig:LeftForeArm", 31 | "mixamorig:LeftHand", 32 | "mixamorig:LeftHandThumb1", 33 | "mixamorig:LeftHandThumb2", 34 | "mixamorig:LeftHandThumb3", 35 | "mixamorig:LeftHandIndex1", 36 | "mixamorig:LeftHandIndex2", 37 | "mixamorig:LeftHandIndex3", 38 | "mixamorig:LeftHandMiddle1", 39 | "mixamorig:LeftHandMiddle2", 40 | "mixamorig:LeftHandMiddle3", 41 | "mixamorig:LeftHandRing1", 42 | "mixamorig:LeftHandRing2", 43 | "mixamorig:LeftHandRing3", 44 | "mixamorig:LeftHandPinky1", 45 | "mixamorig:LeftHandPinky2", 46 | "mixamorig:LeftHandPinky3", 47 | --"mixamorig:RightUpLeg", 48 | --"mixamorig:RightLeg", 49 | --"mixamorig:RightFoot", 50 | --"mixamorig:RightToeBase", 51 | --"mixamorig:LeftUpLeg", 52 | --"mixamorig:LeftLeg", 53 | --"mixamorig:LeftFoot" 54 | } 55 | } -------------------------------------------------------------------------------- /demo/mixamo/animations.lua: -------------------------------------------------------------------------------- 1 | return 2 | { 3 | move1 = { 4 | start = 0, 5 | finish = 220 6 | }, 7 | move2 = { 8 | start = 221, 9 | finish = 570 10 | }, 11 | move3 = { 12 | start = 571, 13 | finish = 759 14 | }, 15 | move4 = { 16 | start = 770, 17 | finish = 1405 18 | } 19 | } -------------------------------------------------------------------------------- /demo/mixamo/mixamo.collection: -------------------------------------------------------------------------------- 1 | name: "mixamo" 2 | instances { 3 | id: "model" 4 | prototype: "/def-mesh/binary.go" 5 | position { 6 | y: -2.5 7 | } 8 | scale3 { 9 | x: 2.5 10 | y: 2.5 11 | z: 2.5 12 | } 13 | } 14 | scale_along_z: 1 15 | embedded_instances { 16 | id: "go" 17 | children: "hat" 18 | children: "model" 19 | data: "components {\n" 20 | " id: \"script\"\n" 21 | " component: \"/demo/mixamo/mixamo.script\"\n" 22 | "}\n" 23 | "components {\n" 24 | " id: \"demo\"\n" 25 | " component: \"/main/demo.script\"\n" 26 | "}\n" 27 | "" 28 | } 29 | embedded_instances { 30 | id: "hat" 31 | data: "embedded_components {\n" 32 | " id: \"model\"\n" 33 | " type: \"model\"\n" 34 | " data: \"mesh: \\\"/assets/blsmpht.glb\\\"\\n" 35 | "name: \\\"{{NAME}}\\\"\\n" 36 | "materials {\\n" 37 | " name: \\\"Material.001\\\"\\n" 38 | " material: \\\"/builtins/materials/model.material\\\"\\n" 39 | " textures {\\n" 40 | " sampler: \\\"tex0\\\"\\n" 41 | " texture: \\\"/def-mesh/checker_256_32.png\\\"\\n" 42 | " }\\n" 43 | "}\\n" 44 | "\"\n" 45 | "}\n" 46 | "" 47 | position { 48 | x: 0.035495 49 | y: 1.225984 50 | z: -0.099935 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /demo/mixamo/mixamo.script: -------------------------------------------------------------------------------- 1 | local binary = require "def-mesh.binary" 2 | local list = require "demo.mixamo.animations" 3 | 4 | function init(self) 5 | self.instance = binary.load("/model", "/assets/mixamo.bin", {verbose = true, textures = "/textures/mixamo/"}) 6 | self.instance.attach("mixamorig:Head", "/hat") 7 | self.instance.animator.list = list 8 | 9 | self.mouth = 0 10 | self.body = 0 11 | end 12 | 13 | local function set_anim (self, anim) 14 | self.anim = anim 15 | self.instance.animator.play(self.anim, {blend_duration = 2}) 16 | end 17 | 18 | function update(self, dt) 19 | 20 | if self.instance then self.instance.animator.update(dt) end 21 | 22 | imgui.set_next_window_size(400, 300) 23 | imgui.set_next_window_pos(1400, 900) 24 | imgui.begin_window("animations") 25 | 26 | local changed, checked = imgui.checkbox("move1", self.anim == "move1") 27 | if changed and checked then 28 | set_anim(self, "move1") 29 | end 30 | 31 | changed, checked = imgui.checkbox("move2", self.anim == "move2") 32 | if changed and checked then 33 | set_anim(self, "move2") 34 | end 35 | 36 | changed, checked = imgui.checkbox("move3", self.anim == "move3") 37 | if changed and checked then 38 | set_anim(self, "move3") 39 | end 40 | 41 | changed, checked = imgui.checkbox("move4", self.anim == "move4") 42 | if changed and checked then 43 | set_anim(self, "move4") 44 | end 45 | 46 | imgui.end_window() 47 | 48 | imgui.set_next_window_size(400, 300) 49 | imgui.set_next_window_pos(200, 900) 50 | imgui.begin_window("blend shapes") 51 | 52 | local value = 0 53 | changed, value = imgui.input_float("mouth", self.mouth, 0.1 ) 54 | if changed then 55 | self.mouth = math.min(math.max(value,0), 1) 56 | self.instance.set_shapes({MouthOpen = self.mouth}) 57 | end 58 | 59 | changed, value = imgui.input_float("body", self.body, 0.1 ) 60 | if changed then 61 | self.body = math.min(math.max(value,0), 1) 62 | self.instance.set_shapes({body = self.body}) 63 | end 64 | 65 | imgui.end_window() 66 | end -------------------------------------------------------------------------------- /demo/root/root.collection: -------------------------------------------------------------------------------- 1 | name: "root" 2 | instances { 3 | id: "model" 4 | prototype: "/def-mesh/binary.go" 5 | children: "go2" 6 | position { 7 | y: -1.5 8 | } 9 | } 10 | scale_along_z: 1 11 | embedded_instances { 12 | id: "go" 13 | children: "base" 14 | children: "model" 15 | data: "components {\n" 16 | " id: \"script\"\n" 17 | " component: \"/demo/root/root.script\"\n" 18 | "}\n" 19 | "components {\n" 20 | " id: \"demo\"\n" 21 | " component: \"/main/demo.script\"\n" 22 | "}\n" 23 | "" 24 | scale3 { 25 | x: 0.7 26 | y: 0.7 27 | z: 0.7 28 | } 29 | } 30 | embedded_instances { 31 | id: "go2" 32 | data: "embedded_components {\n" 33 | " id: \"model\"\n" 34 | " type: \"model\"\n" 35 | " data: \"mesh: \\\"/builtins/assets/meshes/cube.dae\\\"\\n" 36 | "name: \\\"{{NAME}}\\\"\\n" 37 | "materials {\\n" 38 | " name: \\\"default\\\"\\n" 39 | " material: \\\"/builtins/materials/model.material\\\"\\n" 40 | " textures {\\n" 41 | " sampler: \\\"tex0\\\"\\n" 42 | " texture: \\\"/builtins/assets/images/logo/logo_256.png\\\"\\n" 43 | " }\\n" 44 | "}\\n" 45 | "\"\n" 46 | "}\n" 47 | "" 48 | position { 49 | y: 1.0 50 | } 51 | scale3 { 52 | x: 0.4 53 | y: 0.4 54 | z: 0.4 55 | } 56 | } 57 | embedded_instances { 58 | id: "base" 59 | data: "embedded_components {\n" 60 | " id: \"model\"\n" 61 | " type: \"model\"\n" 62 | " data: \"mesh: \\\"/builtins/assets/meshes/cube.dae\\\"\\n" 63 | "name: \\\"{{NAME}}\\\"\\n" 64 | "materials {\\n" 65 | " name: \\\"default\\\"\\n" 66 | " material: \\\"/builtins/materials/model.material\\\"\\n" 67 | " textures {\\n" 68 | " sampler: \\\"tex0\\\"\\n" 69 | " texture: \\\"/def-mesh/checker_256_32.png\\\"\\n" 70 | " }\\n" 71 | "}\\n" 72 | "\"\n" 73 | "}\n" 74 | "" 75 | position { 76 | x: 0.05 77 | y: -1.5 78 | } 79 | scale3 { 80 | x: 10.0 81 | y: 0.1 82 | z: 10.0 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /demo/root/root.script: -------------------------------------------------------------------------------- 1 | local binary = require "def-mesh.binary" 2 | 3 | local WALK = {start = 78, finish = 107} 4 | local TURN = {start = 108, finish = 138} 5 | 6 | 7 | function init(self) 8 | 9 | 10 | self.model = binary.load("/model", "/assets/tracks.bin") 11 | 12 | --self.model.hide("Beta_Joints") 13 | 14 | self.walk = function() 15 | self.model.animator.play(WALK, {root_motion = RM_POSITION, blend_duration= .2}, self.turn) 16 | end 17 | 18 | self.turn = function() 19 | self.model.animator.play(TURN, {root_motion = RM_BOTH, blend_duration = .2}, self.walk) 20 | end 21 | 22 | self.walk() 23 | 24 | --self.model.animator.play(WALK, {root_motion = RM_FORWARD, fps=15, playback = go.PLAYBACK_LOOP_FORWARD}) 25 | 26 | end 27 | 28 | 29 | function update(self, dt) 30 | if self.model then self.model.animator.update(dt) end 31 | 32 | 33 | 34 | end -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [bootstrap] 2 | main_collection = /main/main.collectionc 3 | render = /main/custom.renderc 4 | 5 | [script] 6 | shared_state = 1 7 | 8 | [display] 9 | width = 960 10 | height = 640 11 | high_dpi = 1 12 | vsync = 0 13 | update_frequency = 0 14 | 15 | [android] 16 | input_method = HiddenInputField 17 | 18 | [project] 19 | title = defold-mesh-binary 20 | custom_resources = /assets, /textures 21 | version = 1.2.0 22 | dependencies#0 = https://github.com/rgrams/rendercam/archive/v1.0.1.zip 23 | dependencies#1 = https://github.com/britzl/extension-imgui/archive/master.zip 24 | dependencies#2 = https://github.com/britzl/defold-metrics/archive/master.zip 25 | dependencies#3 = https://github.com/Lerg/extension-imageloader/archive/master.zip 26 | 27 | [library] 28 | include_dirs = def-mesh 29 | 30 | [html5] 31 | cssfile = /builtins/manifests/web/dark_theme.css 32 | 33 | [input] 34 | game_binding = /imgui/bindings/imgui.input_bindingc 35 | 36 | -------------------------------------------------------------------------------- /imgui.ini: -------------------------------------------------------------------------------- 1 | [Window][Debug##Default] 2 | Pos=60,60 3 | Size=400,400 4 | Collapsed=0 5 | 6 | [Window][Hello, world!] 7 | Pos=60,60 8 | Size=191,48 9 | Collapsed=0 10 | 11 | -------------------------------------------------------------------------------- /input/game.input_binding: -------------------------------------------------------------------------------- 1 | mouse_trigger { 2 | input: MOUSE_BUTTON_1 3 | action: "touch" 4 | } 5 | -------------------------------------------------------------------------------- /main/custom.render: -------------------------------------------------------------------------------- 1 | script: "/main/custom.render_script" 2 | -------------------------------------------------------------------------------- /main/custom.render_script: -------------------------------------------------------------------------------- 1 | local rendercam = require "rendercam.rendercam" 2 | local vp = rendercam.viewport 3 | 4 | local IDENTITY_MATRIX = vmath.matrix4() 5 | local CLEAR_COLOR = hash("clear_color") 6 | local WINDOW_RESIZED = hash("window_resized") 7 | local UPDATE_WINDOW = hash("update window") 8 | 9 | local function update_window(self) 10 | rendercam.update_window(render.get_window_width(), render.get_window_height()) 11 | self.gui_proj = vmath.matrix4_orthographic(0, rendercam.window.x, 0, rendercam.window.y, -1, 1) 12 | end 13 | 14 | function init(self) 15 | self.tile_pred = render.predicate({"tile"}) 16 | self.gui_pred = render.predicate({"gui"}) 17 | self.text_pred = render.predicate({"text"}) 18 | self.model_pred = render.predicate({"model"}) 19 | self.particle_pred = render.predicate({"particle"}) 20 | self.trans_pred = render.predicate({"trans"}) 21 | 22 | self.clear_color = vmath.vector4(0) 23 | 24 | rendercam.configWin.x = render.get_width(); rendercam.configWin.y = render.get_height() 25 | rendercam.update_window_size(render.get_window_width(), render.get_window_height()) 26 | update_window(self) 27 | end 28 | 29 | function update(self) 30 | render.set_depth_mask(true) 31 | render.set_stencil_mask(0xff) 32 | render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0}) 33 | 34 | render.set_viewport(vp.x, vp.y, vp.width, vp.height) 35 | 36 | render.set_view(rendercam.calculate_view()) 37 | render.set_projection(rendercam.calculate_proj()) 38 | 39 | -- Sprite and particle rendering 40 | render.set_depth_mask(false) 41 | render.disable_state(render.STATE_DEPTH_TEST) 42 | render.disable_state(render.STATE_STENCIL_TEST) 43 | render.enable_state(render.STATE_BLEND) 44 | render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA) 45 | render.disable_state(render.STATE_CULL_FACE) 46 | 47 | render.draw(self.tile_pred) 48 | render.draw(self.particle_pred) 49 | 50 | -- Model rendering 51 | render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA) 52 | render.enable_state(render.STATE_CULL_FACE) 53 | render.enable_state(render.STATE_DEPTH_TEST) 54 | render.set_depth_mask(true) 55 | render.draw(self.model_pred) 56 | 57 | render.set_depth_mask(false) 58 | render.disable_state(render.STATE_CULL_FACE) 59 | render.draw(self.trans_pred) 60 | render.set_depth_mask(true) 61 | 62 | -- Debug rendering - physics debug, draw_line 63 | render.disable_state(render.STATE_DEPTH_TEST) 64 | 65 | render.draw_debug3d() 66 | 67 | -- GUI Rendering 68 | render.set_viewport(0, 0, rendercam.window.x, rendercam.window.y) 69 | render.set_view(IDENTITY_MATRIX) 70 | render.set_projection(self.gui_proj) -- gui_proj only calculated on update_window 71 | 72 | render.enable_state(render.STATE_STENCIL_TEST) 73 | render.draw(self.gui_pred) 74 | render.draw(self.text_pred) -- Includes debug text from "draw_text" messages. 75 | render.disable_state(render.STATE_STENCIL_TEST) 76 | end 77 | 78 | function on_message(self, message_id, message) 79 | if message_id == CLEAR_COLOR then 80 | self.clear_color = message.color 81 | elseif message_id == WINDOW_RESIZED then -- sent by engine 82 | update_window(self) 83 | elseif message_id == UPDATE_WINDOW then -- sent by rendercam when a camera is activated ("window_resized" engine message requires data) 84 | update_window(self) 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /main/demo.script: -------------------------------------------------------------------------------- 1 | 2 | function init(self) 3 | 4 | msg.post(".", "acquire_input_focus") 5 | self.touch_down = false 6 | end 7 | 8 | function on_input(self, action_id, action) 9 | 10 | if action_id == hash("mouse_button_left") then 11 | self.touch_down = true 12 | if action.released then 13 | self.touch_down = false 14 | end 15 | end 16 | 17 | if self.touch_down and action_id == nil then 18 | go.set(".", "euler.y", go.get(".", "euler.y") + action.dx * 0.5) 19 | go.set(".", "euler.x", go.get(".", "euler.x") - action.dy * 0.5) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /main/main.collection: -------------------------------------------------------------------------------- 1 | name: "main" 2 | instances { 3 | id: "camera" 4 | prototype: "/rendercam/camera.go" 5 | position { 6 | z: 4.0 7 | } 8 | component_properties { 9 | id: "script" 10 | properties { 11 | id: "orthographic" 12 | value: "false" 13 | type: PROPERTY_TYPE_BOOLEAN 14 | } 15 | properties { 16 | id: "nearZ" 17 | value: "0.1" 18 | type: PROPERTY_TYPE_NUMBER 19 | } 20 | properties { 21 | id: "farZ" 22 | value: "100.0" 23 | type: PROPERTY_TYPE_NUMBER 24 | } 25 | properties { 26 | id: "viewDistance" 27 | value: "1500.0" 28 | type: PROPERTY_TYPE_NUMBER 29 | } 30 | properties { 31 | id: "fov" 32 | value: "-1.0" 33 | type: PROPERTY_TYPE_NUMBER 34 | } 35 | } 36 | } 37 | instances { 38 | id: "fps" 39 | prototype: "/metrics/fps.go" 40 | position { 41 | x: 100.0 42 | y: 100.0 43 | } 44 | } 45 | scale_along_z: 1 46 | embedded_instances { 47 | id: "go" 48 | data: "components {\n" 49 | " id: \"main\"\n" 50 | " component: \"/main/main.script\"\n" 51 | "}\n" 52 | "components {\n" 53 | " id: \"imgui\"\n" 54 | " component: \"/imgui/imgui.script\"\n" 55 | "}\n" 56 | "embedded_components {\n" 57 | " id: \"dancer\"\n" 58 | " type: \"collectionproxy\"\n" 59 | " data: \"collection: \\\"/demo/dancer/dancer.collection\\\"\\n" 60 | "\"\n" 61 | "}\n" 62 | "embedded_components {\n" 63 | " id: \"complex\"\n" 64 | " type: \"collectionproxy\"\n" 65 | " data: \"collection: \\\"/demo/complex/complex.collection\\\"\\n" 66 | "\"\n" 67 | "}\n" 68 | "embedded_components {\n" 69 | " id: \"mixamo\"\n" 70 | " type: \"collectionproxy\"\n" 71 | " data: \"collection: \\\"/demo/mixamo/mixamo.collection\\\"\\n" 72 | "\"\n" 73 | "}\n" 74 | "embedded_components {\n" 75 | " id: \"layers\"\n" 76 | " type: \"collectionproxy\"\n" 77 | " data: \"collection: \\\"/demo/layers/layers.collection\\\"\\n" 78 | "\"\n" 79 | "}\n" 80 | "embedded_components {\n" 81 | " id: \"root\"\n" 82 | " type: \"collectionproxy\"\n" 83 | " data: \"collection: \\\"/demo/root/root.collection\\\"\\n" 84 | "\"\n" 85 | "}\n" 86 | "" 87 | } 88 | -------------------------------------------------------------------------------- /main/main.script: -------------------------------------------------------------------------------- 1 | 2 | local function load_scene(self, id) 3 | if self.demo then 4 | msg.post("#" .. self.demo, "unload") 5 | self.demo = id 6 | return 7 | end 8 | self.demo = id 9 | msg.post("#" .. self.demo, "async_load") 10 | end 11 | 12 | function init(self) 13 | msg.post("@render:", "clear_color", {color = vmath.vector4(0.5, 0.5, 0.5, 1)}) 14 | 15 | local data, error = sys.load_resource("/assets/Montserrat-Regular.ttf") 16 | self.font = imgui.font_add_ttf_data(data, #data, 32, 32) 17 | imgui.set_ini_filename() 18 | 19 | load_scene(self, "root") 20 | end 21 | 22 | function update(self, dt) 23 | imgui.set_next_window_size(500, 270) 24 | 25 | imgui.begin_window("Blender export demo") 26 | imgui.font_push(self.font) 27 | 28 | local changed, checked = imgui.checkbox("baked animation", self.demo == "dancer") 29 | if changed and checked then 30 | load_scene(self, "dancer") 31 | end 32 | 33 | changed, checked = imgui.checkbox("complex model", self.demo == "complex") 34 | if changed and checked then 35 | load_scene(self, "complex") 36 | end 37 | 38 | changed, checked = imgui.checkbox("shapes & blending", self.demo == "mixamo") 39 | if changed and checked then 40 | load_scene(self, "mixamo") 41 | end 42 | 43 | changed, checked = imgui.checkbox("tracks", self.demo == "layers") 44 | if changed and checked then 45 | load_scene(self, "layers") 46 | end 47 | 48 | changed, checked = imgui.checkbox("root motion", self.demo == "root") 49 | if changed and checked then 50 | load_scene(self, "root") 51 | end 52 | 53 | imgui.font_pop() 54 | imgui.end_window() 55 | end 56 | 57 | function on_message(self, message_id, message, sender) 58 | if message_id == hash("proxy_loaded") then 59 | msg.post(sender, "init") 60 | msg.post(sender, "enable") 61 | elseif message_id == hash("proxy_unloaded") then 62 | msg.post("#" .. self.demo, "async_load") 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /manifest.private.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/manifest.private.der -------------------------------------------------------------------------------- /manifest.public.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/manifest.public.der -------------------------------------------------------------------------------- /sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/sample.png -------------------------------------------------------------------------------- /textures/complex/arm_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/arm_d.png -------------------------------------------------------------------------------- /textures/complex/arm_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/arm_n.png -------------------------------------------------------------------------------- /textures/complex/bikini_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/bikini_n.png -------------------------------------------------------------------------------- /textures/complex/bikini_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/bikini_r.png -------------------------------------------------------------------------------- /textures/complex/body_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/body_d.png -------------------------------------------------------------------------------- /textures/complex/body_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/body_n.png -------------------------------------------------------------------------------- /textures/complex/body_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/body_r.png -------------------------------------------------------------------------------- /textures/complex/eye_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/eye_d.png -------------------------------------------------------------------------------- /textures/complex/eye_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/eye_n.png -------------------------------------------------------------------------------- /textures/complex/eyelash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/eyelash.png -------------------------------------------------------------------------------- /textures/complex/hair_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/hair_d.png -------------------------------------------------------------------------------- /textures/complex/hair_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/hair_n.png -------------------------------------------------------------------------------- /textures/complex/head_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/head_d.png -------------------------------------------------------------------------------- /textures/complex/head_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/head_n.png -------------------------------------------------------------------------------- /textures/complex/head_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/complex/head_r.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Body_Diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Body_Diffuse.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Body_Normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Body_Normal.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Bottom_Diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Bottom_Diffuse.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Bottom_Normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Bottom_Normal.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Hair_Diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Hair_Diffuse.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Hair_Gloss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Hair_Gloss.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Hair_Normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Hair_Normal.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Hair_Specular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Hair_Specular.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Shoes_Diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Shoes_Diffuse.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Top_Diffuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Top_Diffuse.png -------------------------------------------------------------------------------- /textures/mixamo/Pearl_Top_Normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abadonna/defold-mesh-binary/1a326d530a3697643d6491e2b1517b76bd4b54a5/textures/mixamo/Pearl_Top_Normal.png -------------------------------------------------------------------------------- /todo: -------------------------------------------------------------------------------- 1 | - half precision для всех float 2 | - для статичных мешей - linked 3 | - экспорт вторых UVMap 4 | - блендинг в треке переделать, сабтреки? 5 | - интерполяция анимаций на большее количество кадров 6 | - проверить производительность текстур при блендинге, может стоит выкинуть рантайм текстуры 7 | - блендить root motion по всем трекам 8 | - убрать трансформы с арматур.. 9 | - https://www.youtube.com/watch?v=Jkv0pbp0ckQ&t=6007s 10 | - https://github.com/jkuhlmann/cgltf/blob/master/cgltf.h 11 | - additive треки? --------------------------------------------------------------------------------