+ #{
+ _.dims[0] = obj.data.cv_data.v0[0]
+ _.dims[1] = obj.data.cv_data.v0[1]
+ _.dims[2] = obj.data.cv_data.v0[2]
+ #}
+ else:
+ #{
+ _.dims[0] = obj.cv_data.v0[0]
+ _.dims[1] = obj.cv_data.v0[1]
+ _.dims[2] = obj.cv_data.v0[2]
+ #}
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+
+ if obj.type == 'MESH':
+ dims = obj.data.cv_data.v0
+ else:
+ dims = obj.cv_data.v0
+
+ vs = [None]*9
+ c = Vector((0,0,dims[2]))
+
+ vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
+ vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
+ vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
+ vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
+ vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
+ vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
+ vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
+ vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
+ vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
+
+ indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
+
+ for l in indices:
+ #{
+ v0 = vs[l[0]]
+ v1 = vs[l[1]]
+ cv_view_verts += [(v0[0],v0[1],v0[2])]
+ cv_view_verts += [(v1[0],v1[1],v1[2])]
+ cv_view_colours += [(1,1,0,1),(1,1,0,1)]
+ #}
+
+ sw = (0.4,0.4,0.4,0.2)
+ if obj.cv_data.target != None:
+ cv_draw_arrow( obj.location, obj.cv_data.target.location, sw )
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "target" )
+
+ mesh = obj.data
+ layout.label( text=F"(i) Data is stored in {mesh.name}" )
+ layout.prop( mesh.cv_data, "v0", text="Gate dimensions" )
+ #}
+#}
+
+# Classtype 3
+#
+# Purpose: player can reset here, its a safe place
+# spawns can share the same name, the closest one will be picked
+#
+# when the world loads it will pick the one named 'start' first.
+#
+class classtype_spawn(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("pstr_alias",c_uint32)]
+
+ def encode_obj(_, node,node_def):
+ #{
+ node.classtype = 3
+ _.pstr_alias = encoder_process_pstr( node_def['obj'].cv_data.strp )
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+
+ vs = [None]*4
+ vs[0] = obj.matrix_world @ Vector((0,0,0))
+ vs[1] = obj.matrix_world @ Vector((0,2,0))
+ vs[2] = obj.matrix_world @ Vector((0.5,1,0))
+ vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
+ indices = [(0,1),(1,2),(1,3)]
+
+ for l in indices:
+ #{
+ v0 = vs[l[0]]
+ v1 = vs[l[1]]
+
+ cv_view_verts += [(v0[0],v0[1],v0[2])]
+ cv_view_verts += [(v1[0],v1[1],v1[2])]
+ cv_view_colours += [(0,1,1,1),(0,1,1,1)]
+ #}
+
+ cv_draw_sphere( obj.location, 20.0, [0.1,0,0.9,0.4] )
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "strp", text="Alias" )
+ #}
+#}
+
+# Classtype 4
+#
+# Purpose: Tells the game to draw water HERE, at this entity.
+#
+class classtype_water(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("temp",c_uint32)]
+
+ def encode_obj(_, node,node_def):
+ #{
+ node.classtype = 4
+ # no data, spooky
+ #}
+#}
+
+# Classtype 8
+#
+# Purpose: Defines a route node and links to up to two more nodes
+#
+class classtype_route_node(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("target",c_uint32),
+ ("target1",c_uint32)]
+
+ def encode_obj(_, node,node_def):
+ #{
+ node.classtype = 8
+ obj = node_def['obj']
+
+ if obj.cv_data.target != None:
+ _.target = obj.cv_data.target.cv_data.uid
+ if obj.cv_data.target1 != None:
+ _.target1 = obj.cv_data.target1.cv_data.uid
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+
+ sw = Vector((0.4,0.4,0.4,0.2))
+ sw2 = Vector((1.5,0.2,0.2,0.0))
+ if obj.cv_data.target != None:
+ cv_draw_bpath( obj, obj.cv_data.target, sw, sw )
+ if obj.cv_data.target1 != None:
+ cv_draw_bpath( obj, obj.cv_data.target1, sw, sw )
+
+ cv_draw_bhandle( obj, 1.0, (0.8,0.8,0.8,1.0) )
+ cv_draw_bhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
+
+ p1 = obj.location+ \
+ obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
+ cv_draw_arrow( obj.location, p1, sw )
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "target", text="Left" )
+ layout.prop( obj.cv_data, "target1", text="Right" )
+ #}
+#}
+
+# Classtype 9
+#
+# Purpose: Defines a route, its 'starting' point, and the colour to use for it
+#
+class classtype_route(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("id_start",c_uint32),
+ ("pstr_name",c_uint32),
+ ("colour",c_float*3)]
+
+ def encode_obj(_, node,node_def):
+ #{
+ node.classtype = 9
+ obj = node_def['obj']
+
+ _.colour[0] = obj.cv_data.colour[0]
+ _.colour[1] = obj.cv_data.colour[1]
+ _.colour[2] = obj.cv_data.colour[2]
+ _.pstr_name = encoder_process_pstr( obj.cv_data.strp )
+
+ if obj.cv_data.target != None:
+ _.id_start = obj.cv_data.target.cv_data.uid
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours, cv_view_course_i
+
+ if obj.cv_data.target:
+ cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
+
+ # Tries to simulate how we do it in the game
+ #
+ stack = [None]*64
+ stack_i = [0]*64
+ stack[0] = obj.cv_data.target
+ si = 1
+ loop_complete = False
+
+ while si > 0:
+ #{
+ if stack_i[si-1] == 2:
+ #{
+ si -= 1
+ continue
+
+ if si == 0: # Loop failed to complete
+ break
+ #}
+
+ node = stack[si-1]
+
+ targets = [None,None]
+ targets[0] = node.cv_data.target
+
+ if node.cv_data.classtype == 'classtype_route_node':
+ #{
+ targets[1] = node.cv_data.target1
+ #}
+
+ nextnode = targets[stack_i[si-1]]
+ stack_i[si-1] += 1
+
+ if nextnode != None: # branch
+ #{
+ if nextnode == stack[0]: # Loop completed
+ #{
+ loop_complete = True
+ break
+ #}
+
+ valid=True
+ for sj in range(si):
+ #{
+ if stack[sj] == nextnode: # invalidated path
+ #{
+ valid=False
+ break
+ #}
+ #}
+
+ if valid:
+ #{
+ stack_i[si] = 0
+ stack[si] = nextnode
+ si += 1
+ continue
+ #}
+ #}
+ #}
+
+ if loop_complete:
+ #{
+ cc = Vector((obj.cv_data.colour[0],\
+ obj.cv_data.colour[1],\
+ obj.cv_data.colour[2],\
+ 1.0))
+
+ for sj in range(si):
+ #{
+ sk = (sj+1)%si
+
+ if stack[sj].cv_data.classtype == 'classtype_gate' and \
+ stack[sk].cv_data.classtype == 'classtype_gate':
+ #{
+ dist = (stack[sj].location-stack[sk].location).magnitude
+ cv_draw_sbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
+ #}
+ else:
+ cv_draw_bpath( stack[sj], stack[sk], cc, cc )
+ #}
+
+ cv_view_course_i += 1
+ #}
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "target", text="'Start' from" )
+ layout.prop( obj.cv_data, "colour" )
+ layout.prop( obj.cv_data, "strp", text="Name" )
+ #}
+#}
+
+# Classtype 12
+#
+# Purpose: links an mesh node to a type 11
+#
+class classtype_skin(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("skeleton",c_uint32)]
+
+ def encode_obj(_, node,node_def):
+ #{
+ node.classtype = 12
+
+ armature_def = node_def['linked_armature']
+ _.skeleton = armature_def['obj'].cv_data.uid
+ #}
+#}
+
+# Classtype 11
+#
+# Purpose: defines the allocation requirements for a skeleton
+#
+class classtype_skeleton(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("channels",c_uint32),
+ ("ik_count",c_uint32),
+ ("collider_count",c_uint32),
+ ("anim_start",c_uint32),
+ ("anim_count",c_uint32)]
+
+ def encode_obj(_, node,node_def):
+ #{
+ node.classtype = 11
+
+ _.channels = len( node_def['bones'] )
+ _.ik_count = node_def['ik_count']
+ _.collider_count = node_def['collider_count']
+ _.anim_start = node_def['anim_start']
+ _.anim_count = node_def['anim_count']
+ #}
+#}
+
+
+# Classtype 10
+#
+# Purpose: intrinsic bone type, stores collision information and limits too
+#
+class classtype_bone(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("deform",c_uint32),
+ ("ik_target",c_uint32),
+ ("ik_pole",c_uint32),
+ ("collider",c_uint32),
+ ("use_limits",c_uint32),
+ ("angle_limits",(c_float*3)*2),
+ ("hitbox",(c_float*3)*2)]
+
+ def encode_obj(_, node,node_def):
+ #{
+ node.classtype = 10
+
+ armature_def = node_def['linked_armature']
+ obj = node_def['bone']
+
+ _.deform = node_def['deform']
+
+ if 'ik_target' in node_def:
+ #{
+ _.ik_target = armature_def['bones'].index( node_def['ik_target'] )
+ _.ik_pole = armature_def['bones'].index( node_def['ik_pole'] )
+ #}
+
+ # For ragdolls
+ #
+ if obj.cv_data.collider:
+ #{
+ _.collider = 1
+ _.hitbox[0][0] = obj.cv_data.v0[0]
+ _.hitbox[0][1] = obj.cv_data.v0[2]
+ _.hitbox[0][2] = -obj.cv_data.v1[1]
+ _.hitbox[1][0] = obj.cv_data.v1[0]
+ _.hitbox[1][1] = obj.cv_data.v1[2]
+ _.hitbox[1][2] = -obj.cv_data.v0[1]
+ #}
+
+ if obj.cv_data.con0:
+ #{
+ _.use_limits = 1
+ _.angle_limits[0][0] = obj.cv_data.mins[0]
+ _.angle_limits[0][1] = obj.cv_data.mins[2]
+ _.angle_limits[0][2] = -obj.cv_data.maxs[1]
+ _.angle_limits[1][0] = obj.cv_data.maxs[0]
+ _.angle_limits[1][1] = obj.cv_data.maxs[2]
+ _.angle_limits[1][2] = -obj.cv_data.mins[1]
+ #}
+ #}
+#}
+
+# Classtype 100
+#
+# Purpose: sends a signal to another entity
+#
+class classtype_trigger(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("target",c_uint32)]
+
+ def encode_obj(_, node,node_def ):
+ #{
+ node.classtype = 100
+ if node_def['obj'].cv_data.target:
+ _.target = node_def['obj'].cv_data.target.cv_data.uid
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+ cv_draw_ucube( obj.matrix_world, [0,1,0,1] )
+
+ if obj.cv_data.target:
+ cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "target", text="Triggers" )
+ #}
+#}
+
+# Classtype 101
+#
+# Purpose: Gives the player an achievement.
+# No cheating! You shouldn't use this entity anyway, since only ME can
+# add achievements to the steam ;)
+#
+class classtype_logic_achievement(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("pstr_name",c_uint32)]
+
+ def encode_obj(_, node,node_def ):
+ #{
+ node.classtype = 101
+ _.pstr_name = encoder_process_pstr( node_def['obj'].cv_data.strp )
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "strp", text="Achievement ID" )
+ #}
+#}
+
+# Classtype 102
+#
+# Purpose: sends a signal to another entity
+#
+class classtype_logic_relay(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("targets",c_uint32*4)]
+
+ def encode_obj(_, node,node_def ):
+ #{
+ node.classtype = 102
+ obj = node_def['obj']
+ if obj.cv_data.target:
+ _.targets[0] = obj.cv_data.target.cv_data.uid
+ if obj.cv_data.target1:
+ _.targets[1] = obj.cv_data.target1.cv_data.uid
+ if obj.cv_data.target2:
+ _.targets[2] = obj.cv_data.target2.cv_data.uid
+ if obj.cv_data.target3:
+ _.targets[3] = obj.cv_data.target3.cv_data.uid
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+
+ if obj.cv_data.target:
+ cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
+ if obj.cv_data.target1:
+ cv_draw_arrow( obj.location, obj.cv_data.target1.location, [1,1,1,1] )
+ if obj.cv_data.target2:
+ cv_draw_arrow( obj.location, obj.cv_data.target2.location, [1,1,1,1] )
+ if obj.cv_data.target3:
+ cv_draw_arrow( obj.location, obj.cv_data.target3.location, [1,1,1,1] )
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "target", text="Triggers" )
+ layout.prop( obj.cv_data, "target1", text="Triggers" )
+ layout.prop( obj.cv_data, "target2", text="Triggers" )
+ layout.prop( obj.cv_data, "target3", text="Triggers" )
+ #}
+#}
+
+# Classtype 14
+#
+# Purpose: Plays some audio (44100hz .ogg vorbis only)
+# NOTE: There is a 32mb limit on the audio buffer, world audio is
+# decompressed and stored in signed 16 bit integers (2 bytes)
+# per sample.
+#
+# volume: not used if has 3D flag
+# flags:
+# AUDIO_FLAG_LOOP 0x1
+# AUDIO_FLAG_ONESHOT 0x2 (DONT USE THIS, it breaks semaphores)
+# AUDIO_FLAG_SPACIAL_3D 0x4 (Probably what you want)
+# AUDIO_FLAG_AUTO_START 0x8 (Play when the world starts)
+# ......
+# the rest are just internal flags, only use the above 3.
+#
+class classtype_audio(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("pstr_file",c_uint32),
+ ("flags",c_uint32),
+ ("volume",c_float)]
+
+ def encode_obj(_, node,node_def ):
+ #{
+ node.classtype = 14
+
+ obj = node_def['obj']
+
+ _.pstr_file = encoder_process_pstr( obj.cv_data.strp )
+
+ flags = 0x00
+ if obj.cv_data.bp0: flags |= 0x1
+ if obj.cv_data.bp1: flags |= 0x4
+ if obj.cv_data.bp2: flags |= 0x8
+
+ _.flags = flags
+ _.volume = obj.cv_data.fltp
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ layout.prop( obj.cv_data, "strp" )
+
+ layout.prop( obj.cv_data, "bp0", text = "Looping" )
+ layout.prop( obj.cv_data, "bp1", text = "3D Audio" )
+ layout.prop( obj.cv_data, "bp2", text = "Auto Start" )
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+
+ cv_draw_sphere( obj.location, obj.scale[0], [1,1,0,1] )
+ #}
+#}
+
+class classtype_spawn_link(Structure):
+#{
+ _pack_ = 1
+ _fields_ = [("connections",c_uint32*4)]
+
+ def encode_obj(_, node,node_def ):
+ #{
+ node.classtype = 0
+ #}
+
+ @staticmethod
+ def editor_interface( layout, obj ):
+ #{
+ pass
+ #}
+
+ @staticmethod
+ def draw_scene_helpers( obj ):
+ #{
+ global cv_view_verts, cv_view_colours
+
+ count = 0
+
+ for obj1 in bpy.context.collection.objects:
+ #{
+ if (obj1.cv_data.classtype != 'classtype_spawn_link') and \
+ (obj1.cv_data.classtype != 'classtype_spawn') :
+ continue
+
+ if (obj1.location - obj.location).length < 40.0:
+ #{
+ cv_draw_line( obj.location, obj1.location, [1,1,1,1] )
+ count +=1
+ #}
+
+ if count == 4:
+ break
+ #}
+
+ cv_draw_sphere( obj.location, 20.0, [0.5,0,0.2,0.4] )
+ #}
+#}
+
+# ---------------------------------------------------------------------------- #
+# #
+# Compiler section #
+# #
+# ---------------------------------------------------------------------------- #
+
+# Current encoder state
+#
+g_encoder = None
+
+# Reset encoder
+#
+def encoder_init( collection ):
+#{
+ global g_encoder
+
+ g_encoder = \
+ {
+ # The actual file header
+ #
+ 'header': mdl_header(),
+
+ # Options
+ #
+ 'pack_textures': collection.cv_data.pack_textures,
+
+ # Compiled data chunks (each can be read optionally by the client)
+ #
+ 'data':
+ {
+ #1---------------------------------
+ 'node': [], # Metadata 'chunk'
+ 'submesh': [],
+ 'material': [],
+ 'texture': [],
+ 'anim': [],
+ 'entdata': bytearray(), # variable width
+ 'strings': bytearray(), # .
+ #2---------------------------------
+ 'keyframe': [], # Animations
+ #3---------------------------------
+ 'vertex': [], # Mesh data
+ 'indice': [],
+ #4---------------------------------
+ 'pack': bytearray() # Other generic packed data
+ },
+
+ # All objects of the model in their final heirachy
+ #
+ "uid_count": 1,
+ "scene_graph":{},
+ "graph_lookup":{},
+
+ # Allows us to reuse definitions
+ #
+ 'string_cache':{},
+ 'mesh_cache': {},
+ 'material_cache': {},
+ 'texture_cache': {}
+ }
+
+ g_encoder['header'].identifier = 0xABCD0000
+ g_encoder['header'].version = 1
+
+ # Add fake NoneID material and texture
+ #
+ none_material = mdl_material()
+ none_material.pstr_name = encoder_process_pstr( "" )
+ none_material.texture_id = 0
+
+ none_texture = mdl_texture()
+ none_texture.pstr_name = encoder_process_pstr( "" )
+ none_texture.pack_offset = 0
+ none_texture.pack_length = 0
+
+ g_encoder['data']['material'] += [none_material]
+ g_encoder['data']['texture'] += [none_texture]
+
+ g_encoder['data']['pack'].extend( b'datapack\0\0\0\0\0\0\0\0' )
+
+ # Add root node
+ #
+ root = mdl_node()
+ root.co[0] = 0
+ root.co[1] = 0
+ root.co[2] = 0
+ root.q[0] = 0
+ root.q[1] = 0
+ root.q[2] = 0
+ root.q[3] = 1
+ root.s[0] = 1
+ root.s[1] = 1
+ root.s[2] = 1
+ root.pstr_name = encoder_process_pstr('')
+ root.submesh_start = 0
+ root.submesh_count = 0
+ root.offset = 0
+ root.classtype = 0
+ root.parent = 0xffffffff
+
+ g_encoder['data']['node'] += [root]
+#}
+
+
+# fill with 0x00 until a multiple of align. Returns how many bytes it added
+#
+def bytearray_align_to( buffer, align, offset=0 ):
+#{
+ count = 0
+
+ while ((len(buffer)+offset) % align) != 0:
+ #{
+ buffer.extend( b'\0' )
+ count += 1
+ #}
+
+ return count
+#}
+
+# Add a string to the string buffer except if it already exists there then we
+# just return its ID.
+#
+def encoder_process_pstr( s ):
+#{
+ global g_encoder
+
+ cache = g_encoder['string_cache']
+
+ if s in cache:
+ return cache[s]
+
+ cache[s] = len( g_encoder['data']['strings'] )
+
+ buffer = g_encoder['data']['strings']
+ buffer.extend( s.encode('utf-8') )
+ buffer.extend( b'\0' )
+
+ bytearray_align_to( buffer, 4 )
+ return cache[s]
+#}
+
+def get_texture_resource_name( img ):
+#{
+ return os.path.splitext( img.name )[0]
+#}
+
+# Pack a texture
+#
+def encoder_process_texture( img ):
+#{
+ global g_encoder
+
+ if img == None:
+ return 0
+
+ cache = g_encoder['texture_cache']
+ buffer = g_encoder['data']['texture']
+ pack = g_encoder['data']['pack']
+
+ name = get_texture_resource_name( img )
+
+ if name in cache:
+ return cache[name]
+
+ cache[name] = len( buffer )
+
+ tex = mdl_texture()
+ tex.pstr_name = encoder_process_pstr( name )
+
+ if g_encoder['pack_textures']:
+ #{
+ tex.pack_offset = len( pack )
+ pack.extend( qoi_encode( img ) )
+ tex.pack_length = len( pack ) - tex.pack_offset
+ #}
+ else:
+ tex.pack_offset = 0
+
+ buffer += [ tex ]
+ return cache[name]
+#}
+
+def material_tex_image(v):
+#{
+ return {
+ "Image Texture":
+ {
+ "image": F"{v}"
+ }
+ }
+#}
+
+cxr_graph_mapping = \
+{
+ # Default shader setup
+ "Principled BSDF":
+ {
+ "Base Color":
+ {
+ "Image Texture":
+ {
+ "image": "tex_diffuse"
+ },
+ "Mix":
+ {
+ "A": material_tex_image("tex_diffuse"),
+ "B": material_tex_image("tex_decal")
+ },
+ },
+ "Normal":
+ {
+ "Normal Map":
+ {
+ "Color": material_tex_image("tex_normal")
+ }
+ }
+ }
+}
+
+# https://harrygodden.com/git/?p=convexer.git;a=blob;f=__init__.py;#l1164
+#
+def material_info(mat):
+#{
+ info = {}
+
+ # Using the cv_graph_mapping as a reference, go through the shader
+ # graph and gather all $props from it.
+ #
+ def _graph_read( node_def, node=None, depth=0 ):
+ #{
+ nonlocal mat
+ nonlocal info
+
+ # Find rootnodes
+ #
+ if node == None:
+ #{
+ _graph_read.extracted = []
+
+ for node_idname in node_def:
+ #{
+ for n in mat.node_tree.nodes:
+ #{
+ if n.name == node_idname:
+ #{
+ node_def = node_def[node_idname]
+ node = n
+ break
+ #}
+ #}
+ #}
+ #}
+
+ for link in node_def:
+ #{
+ link_def = node_def[link]
+
+ if isinstance( link_def, dict ):
+ #{
+ node_link = None
+ for x in node.inputs:
+ #{
+ if isinstance( x, bpy.types.NodeSocketColor ):
+ #{
+ if link == x.name:
+ #{
+ node_link = x
+ break
+ #}
+ #}
+ #}
+
+ if node_link and node_link.is_linked:
+ #{
+ # look for definitions for the connected node type
+ #
+ from_node = node_link.links[0].from_node
+
+ node_name = from_node.name.split('.')[0]
+ if node_name in link_def:
+ #{
+ from_node_def = link_def[ node_name ]
+
+ _graph_read( from_node_def, from_node, depth+1 )
+ #}
+
+ # No definition! :(
+ # TODO: Make a warning for this?
+ #}
+ else:
+ #{
+ if "default" in link_def:
+ #{
+ prop = link_def['default']
+ info[prop] = node_link.default_value
+ #}
+ #}
+ #}
+ else:
+ #{
+ prop = link_def
+ info[prop] = getattr( node, link )
+ #}
+ #}
+ #}
+
+ _graph_read( cxr_graph_mapping )
+ return info
+#}
+
+# Add a material to the material buffer. Returns 0 (None ID) if invalid
+#
+def encoder_process_material( mat ):
+#{
+ global g_encoder
+
+ if mat == None:
+ return 0
+
+ cache = g_encoder['material_cache']
+ buffer = g_encoder['data']['material']
+
+ if mat.name in cache:
+ return cache[mat.name]
+
+ cache[mat.name] = len( buffer )
+
+ dest = mdl_material()
+ dest.pstr_name = encoder_process_pstr( mat.name )
+
+ flags = 0x00
+ if mat.cv_data.collision:
+ flags |= 0x2
+ if mat.cv_data.skate_surface: flags |= 0x1
+ if mat.cv_data.grind_surface: flags |= (0x8|0x1)
+
+ if mat.cv_data.grow_grass: flags |= 0x4
+ dest.flags = flags
+
+ if mat.cv_data.surface_prop == 'concrete': dest.surface_prop = 0
+ if mat.cv_data.surface_prop == 'wood': dest.surface_prop = 1
+ if mat.cv_data.surface_prop == 'grass': dest.surface_prop = 2
+
+ if mat.cv_data.shader == 'standard': dest.shader = 0
+ if mat.cv_data.shader == 'standard_cutout': dest.shader = 1
+ if mat.cv_data.shader == 'terrain_blend':
+ #{
+ dest.shader = 2
+
+ dest.colour[0] = pow( mat.cv_data.sand_colour[0], 1.0/2.2 )
+ dest.colour[1] = pow( mat.cv_data.sand_colour[1], 1.0/2.2 )
+ dest.colour[2] = pow( mat.cv_data.sand_colour[2], 1.0/2.2 )
+ dest.colour[3] = 1.0
+
+ dest.colour1[0] = mat.cv_data.blend_offset[0]
+ dest.colour1[1] = mat.cv_data.blend_offset[1]
+ #}
+
+ if mat.cv_data.shader == 'vertex_blend':
+ #{
+ dest.shader = 3
+
+ dest.colour1[0] = mat.cv_data.blend_offset[0]
+ dest.colour1[1] = mat.cv_data.blend_offset[1]
+ #}
+
+ if mat.cv_data.shader == 'water':
+ #{
+ dest.shader = 4
+
+ dest.colour[0] = pow( mat.cv_data.shore_colour[0], 1.0/2.2 )
+ dest.colour[1] = pow( mat.cv_data.shore_colour[1], 1.0/2.2 )
+ dest.colour[2] = pow( mat.cv_data.shore_colour[2], 1.0/2.2 )
+ dest.colour[3] = 1.0
+ dest.colour1[0] = pow( mat.cv_data.ocean_colour[0], 1.0/2.2 )
+ dest.colour1[1] = pow( mat.cv_data.ocean_colour[1], 1.0/2.2 )
+ dest.colour1[2] = pow( mat.cv_data.ocean_colour[2], 1.0/2.2 )
+ dest.colour1[3] = 1.0
+ #}
+
+ inf = material_info( mat )
+
+ if mat.cv_data.shader == 'standard' or \
+ mat.cv_data.shader == 'standard_cutout' or \
+ mat.cv_data.shader == 'terrain_blend' or \
+ mat.cv_data.shader == 'vertex_blend':
+ #{
+ if 'tex_diffuse' in inf:
+ dest.tex_diffuse = encoder_process_texture(inf['tex_diffuse'])
+ #}
+
+ buffer += [dest]
+ return cache[mat.name]
+#}
+
+# Create a tree structure containing all the objects in the collection
+#
+def encoder_build_scene_graph( collection ):
+#{
+ global g_encoder
+
+ print( " creating scene graph" )
+
+ # initialize root
+ #
+ graph = g_encoder['scene_graph']
+ graph_lookup = g_encoder['graph_lookup']
+ graph["obj"] = None
+ graph["depth"] = 0
+ graph["children"] = []
+ graph["uid"] = 0
+ graph["parent"] = None
+
+ def _new_uid():
+ #{
+ global g_encoder
+ uid = g_encoder['uid_count']
+ g_encoder['uid_count'] += 1
+ return uid
+ #}
+
+ for obj in collection.all_objects:
+ #{
+ if obj.parent: continue
+
+ def _extend( p, n, d ):
+ #{
+ uid = _new_uid()
+ tree = {}
+ tree["uid"] = uid
+ tree["children"] = []
+ tree["depth"] = d
+ tree["obj"] = n
+ tree["parent"] = p
+ n.cv_data.uid = uid