mirror of https://github.com/AxioDL/metaforce.git
Initial PATH cooking support
This commit is contained in:
parent
2f6d9d2509
commit
3acb9c9e3d
|
@ -1,45 +1,234 @@
|
||||||
import bpy
|
import bpy, bgl, sys, bmesh, struct
|
||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
# Convenience class that automatically brings active edit mesh's face into scope for get/set
|
||||||
|
class HeightRef:
|
||||||
|
def __init__(self):
|
||||||
|
self.bm = None
|
||||||
|
context = bpy.context
|
||||||
|
obj = context.scene.objects[context.scene.hecl_path_obj]
|
||||||
|
if obj.type != 'MESH':
|
||||||
|
return
|
||||||
|
if context.edit_object != obj:
|
||||||
|
return
|
||||||
|
bm = bmesh.from_edit_mesh(obj.data)
|
||||||
|
if 'Height' not in bm.faces.layers.float:
|
||||||
|
return
|
||||||
|
self.height_lay = bm.faces.layers.float['Height']
|
||||||
|
self.bm = bm
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ready(self):
|
||||||
|
return self.bm is not None and self.bm.faces.active is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
if self.ready:
|
||||||
|
return self.bm.faces.active[self.height_lay]
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, value):
|
||||||
|
if self.ready:
|
||||||
|
for f in self.bm.faces:
|
||||||
|
if f.select:
|
||||||
|
f[self.height_lay] = value
|
||||||
|
|
||||||
|
# Active edit face height get
|
||||||
|
def get_height(self):
|
||||||
|
return HeightRef().value
|
||||||
|
|
||||||
|
# Selected edit face(s) height set
|
||||||
|
def set_height(self, val):
|
||||||
|
HeightRef().value = val
|
||||||
|
for ar in bpy.context.screen.areas:
|
||||||
|
if ar.type == 'VIEW_3D':
|
||||||
|
ar.tag_redraw()
|
||||||
|
|
||||||
|
# Edit panel
|
||||||
def draw(layout, context):
|
def draw(layout, context):
|
||||||
layout.prop_search(context.scene, 'hecl_path_obj', context.scene, 'objects')
|
layout.prop_search(context.scene, 'hecl_path_obj', context.scene, 'objects')
|
||||||
layout.operator('view3d.toggle_path_height_visualization', text='Toggle Height Viz', icon='MANIPUL')
|
layout.operator('view3d.toggle_path_height_visualization', text='Toggle Height Viz', icon='MANIPUL')
|
||||||
layout.operator('view3d.toggle_path_background_wireframe', text='Toggle Background Wire', icon='WIRE')
|
layout.operator('view3d.toggle_path_background_wireframe', text='Toggle Background Wire', icon='WIRE')
|
||||||
|
hr = HeightRef()
|
||||||
|
if hr.ready:
|
||||||
|
layout.prop(context.window_manager, 'hecl_height_prop', text='Height')
|
||||||
|
|
||||||
|
# Simple AABB class
|
||||||
|
class AABB:
|
||||||
|
def __init__(self):
|
||||||
|
self.min = Vector((999999.0, 999999.0, 999999.0))
|
||||||
|
self.max = Vector((-999999.0, -999999.0, -999999.0))
|
||||||
|
def accumulate(self, vec):
|
||||||
|
for i in range(3):
|
||||||
|
if vec[i] < self.min[i]:
|
||||||
|
self.min[i] = vec[i]
|
||||||
|
if vec[i] > self.max[i]:
|
||||||
|
self.max[i] = vec[i]
|
||||||
|
def isValid(self):
|
||||||
|
for i in range(3):
|
||||||
|
if self.min[i] > self.max[i]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Simple adjacency calculator
|
||||||
|
class AdjacencySet:
|
||||||
|
def __init__(self, bm, mesh_obj, type_mask):
|
||||||
|
self.faces = {}
|
||||||
|
for f in bm.faces:
|
||||||
|
material = mesh_obj.material_slots[f.material_index].material
|
||||||
|
if (material.retro_path_type_mask & type_mask) == 0:
|
||||||
|
continue
|
||||||
|
face_set = set()
|
||||||
|
face_set.add(f)
|
||||||
|
|
||||||
|
# Breadth-first loop to avoid crashing python with large recursion
|
||||||
|
next_level = [f]
|
||||||
|
while len(next_level):
|
||||||
|
next_next_level = []
|
||||||
|
for of in next_level:
|
||||||
|
for e in of.edges:
|
||||||
|
for of2 in e.link_faces:
|
||||||
|
if of2 == of:
|
||||||
|
continue
|
||||||
|
if of2 not in face_set:
|
||||||
|
material = mesh_obj.material_slots[of2.material_index].material
|
||||||
|
if material.retro_path_type_mask & type_mask:
|
||||||
|
face_set.add(of2)
|
||||||
|
next_next_level.append(of2)
|
||||||
|
next_level = next_next_level
|
||||||
|
self.faces[f] = face_set
|
||||||
|
|
||||||
|
def has_adjacency(self, face_a, face_b):
|
||||||
|
if face_a not in self.faces:
|
||||||
|
return False
|
||||||
|
return face_b in self.faces[face_a]
|
||||||
|
|
||||||
|
# Cooking entry point
|
||||||
def cook(writebuf, mesh_obj):
|
def cook(writebuf, mesh_obj):
|
||||||
pass
|
ba = bytearray()
|
||||||
|
# Version 4
|
||||||
|
ba += struct.pack('>I', 4)
|
||||||
|
|
||||||
import bpy, bgl
|
bm = bmesh.new()
|
||||||
from mathutils import Vector, Matrix
|
bm.from_mesh(mesh_obj.data)
|
||||||
from mathutils.geometry import intersect_ray_tri, tessellate_polygon
|
bm.faces.ensure_lookup_table()
|
||||||
|
height_lay = None
|
||||||
|
if 'Height' in bm.faces.layers.float:
|
||||||
|
height_lay = bm.faces.layers.float['Height']
|
||||||
|
|
||||||
def correlate_polygon_heights(context, obj):
|
# Gather immediate adjacencies
|
||||||
ret = {}
|
node_list = []
|
||||||
if obj.type != 'MESH':
|
link_list = []
|
||||||
return ret
|
region_list = []
|
||||||
for p in obj.data.polygons:
|
for f in bm.faces:
|
||||||
pl = [obj.matrix_world * obj.data.vertices[vert].co for vert in p.vertices]
|
start_loop = f.loops[0]
|
||||||
tpl = tessellate_polygon((pl,))
|
cur_loop = start_loop
|
||||||
found = False
|
node_idx = 0
|
||||||
for eobj in context.scene.objects:
|
start_node = len(node_list)
|
||||||
for tri in tpl:
|
start_link = len(link_list)
|
||||||
if eobj.type == 'EMPTY' and eobj.library is None:
|
while cur_loop != start_loop:
|
||||||
intersect = intersect_ray_tri(pl[tri[0]], pl[tri[1]], pl[tri[2]],
|
node_list.append(cur_loop)
|
||||||
Vector((0.0, 0.0, -999.0)),
|
cur_loop = cur_loop.link_loop_prev
|
||||||
eobj.location)
|
for other_face in cur_loop.edge.link_faces:
|
||||||
if intersect is not None:
|
if other_face == f:
|
||||||
ret[p] = abs(intersect.z - eobj.location.z)
|
continue
|
||||||
found = True
|
link_list.append((node_idx, other_face.index, cur_loop.edge.calc_length()))
|
||||||
break
|
node_idx += 1
|
||||||
if found:
|
region_list.append((f, range(start_node, len(node_list)), range(start_link, len(link_list))))
|
||||||
break
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
# Emit nodes
|
||||||
|
ba += struct.pack('>I', len(node_list))
|
||||||
|
for n in node_list:
|
||||||
|
v = n.vert
|
||||||
|
normal = (n.edge.other_vert(v).co - v.co).normalized()
|
||||||
|
ba += struct.pack('>ffffff', v.co[0], v.co[1], v.co[2], normal[0], normal[1], normal[2])
|
||||||
|
|
||||||
|
# Emit links
|
||||||
|
ba += struct.pack('>I', len(link_list))
|
||||||
|
for l in link_list:
|
||||||
|
ba += struct.pack('>IIff', l[0], l[1], l[2], 1.0 / l[2])
|
||||||
|
|
||||||
|
# Emit regions
|
||||||
|
ba += struct.pack('>I', len(region_list))
|
||||||
|
for r in region_list:
|
||||||
|
material = mesh_obj.material_slots[r[0].material_index].material
|
||||||
|
height = 1.0
|
||||||
|
if height_lay is not None:
|
||||||
|
height = r[0][height_lay]
|
||||||
|
center = r[0].calc_center_median_weighted()
|
||||||
|
aabb = AABB()
|
||||||
|
for v in r[0].verts:
|
||||||
|
aabb.accumulate(v.co)
|
||||||
|
aabb.accumulate(v.co + Vector(0.0, 0.0, height))
|
||||||
|
ba += struct.pack('>IIIIHHffffIfffffffffI', len(r[1]), r[1].start, len(r[2]), r[2].start,
|
||||||
|
material.retro_path_idx_mask, material.retro_path_type_mask,
|
||||||
|
height, r[0].normal[0], r[0].normal[1], r[0].normal[2],
|
||||||
|
r[0].index, center[0], center[1], center[2],
|
||||||
|
aabb.min[0], aabb.min[1], aabb.min[2],
|
||||||
|
aabb.max[0], aabb.max[1], aabb.max[2],
|
||||||
|
r[0].index)
|
||||||
|
|
||||||
|
num_regions = len(region_list)
|
||||||
|
total_adjacencies = num_regions * (num_regions - 1) // 2
|
||||||
|
num_words = (total_adjacencies + 31) / 32
|
||||||
|
|
||||||
|
# Find ground adjacencies
|
||||||
|
words = [0] * num_words
|
||||||
|
ground_adjacencies = AdjacencySet(bm, mesh_obj, 0x1)
|
||||||
|
for i in range(num_regions):
|
||||||
|
for j in range(num_regions):
|
||||||
|
if i == j:
|
||||||
|
continue
|
||||||
|
i1 = i
|
||||||
|
i2 = j
|
||||||
|
if i1 > i2:
|
||||||
|
continue
|
||||||
|
rem_regions = num_regions - i1
|
||||||
|
rem_connections = rem_regions * (rem_regions - 1) // 2
|
||||||
|
if ground_adjacencies.has_adjacency(bm.faces[i], bm.faces[j]):
|
||||||
|
bit = total_adjacencies - rem_connections + i2 - (i1 + 1)
|
||||||
|
words[bit / 32] |= 1 << (bit % 32)
|
||||||
|
for w in words:
|
||||||
|
ba += struct.pack('>I', w)
|
||||||
|
|
||||||
|
# Find flyer adjacencies
|
||||||
|
words = [0] * num_words
|
||||||
|
flyer_adjacencies = AdjacencySet(bm, mesh_obj, 0x3)
|
||||||
|
for i in range(num_regions):
|
||||||
|
for j in range(num_regions):
|
||||||
|
if i == j:
|
||||||
|
continue
|
||||||
|
i1 = i
|
||||||
|
i2 = j
|
||||||
|
if i1 > i2:
|
||||||
|
continue
|
||||||
|
rem_regions = num_regions - i1
|
||||||
|
rem_connections = rem_regions * (rem_regions - 1) // 2
|
||||||
|
if flyer_adjacencies.has_adjacency(bm.faces[i], bm.faces[j]):
|
||||||
|
bit = total_adjacencies - rem_connections + i2 - (i1 + 1)
|
||||||
|
words[bit / 32] |= 1 << (bit % 32)
|
||||||
|
for w in words:
|
||||||
|
ba += struct.pack('>I', w)
|
||||||
|
|
||||||
|
# Unused zero bits
|
||||||
|
for i in range((((num_regions * num_regions) + 31) / 32 - num_words) * 2):
|
||||||
|
ba += struct.pack('>I', 0)
|
||||||
|
|
||||||
|
# Empty octree (to be generated by hecl)
|
||||||
|
ba += struct.pack('>II', 0, 0)
|
||||||
|
|
||||||
|
# Write out
|
||||||
|
writebuf(struct.pack('I', len(ba)))
|
||||||
|
writebuf(ba)
|
||||||
|
|
||||||
|
# Line draw helper
|
||||||
def draw_line_3d(color, start, end):
|
def draw_line_3d(color, start, end):
|
||||||
bgl.glColor4f(*color)
|
bgl.glColor4f(*color)
|
||||||
bgl.glBegin(bgl.GL_LINES)
|
bgl.glBegin(bgl.GL_LINES)
|
||||||
bgl.glVertex3f(*start)
|
bgl.glVertex3f(*start)
|
||||||
bgl.glVertex3f(*end)
|
bgl.glVertex3f(*end)
|
||||||
|
|
||||||
|
# Viewport hook callback
|
||||||
def draw_callback_3d(self, context):
|
def draw_callback_3d(self, context):
|
||||||
# object locations
|
# object locations
|
||||||
if context.scene.hecl_path_obj not in context.scene.objects:
|
if context.scene.hecl_path_obj not in context.scene.objects:
|
||||||
|
@ -47,13 +236,24 @@ def draw_callback_3d(self, context):
|
||||||
obj = context.scene.objects[context.scene.hecl_path_obj]
|
obj = context.scene.objects[context.scene.hecl_path_obj]
|
||||||
if obj.type != 'MESH':
|
if obj.type != 'MESH':
|
||||||
return
|
return
|
||||||
heights = correlate_polygon_heights(context, obj)
|
|
||||||
obj_mtx = obj.matrix_world
|
obj_mtx = obj.matrix_world
|
||||||
|
|
||||||
|
height_lay = None
|
||||||
|
height_lambda = None
|
||||||
|
if obj == context.edit_object:
|
||||||
|
bm = bmesh.from_edit_mesh(obj.data)
|
||||||
|
if 'Height' in bm.faces.layers.float:
|
||||||
|
height_lay = bm.faces.layers.float['Height']
|
||||||
|
height_lambda = lambda i: bm.faces[i][height_lay]
|
||||||
|
else:
|
||||||
|
if 'Height' in obj.data.polygon_layers_float:
|
||||||
|
height_lay = obj.data.polygon_layers_float['Height']
|
||||||
|
height_lambda = lambda i: height_lay.data[i].value
|
||||||
|
|
||||||
for p in obj.data.polygons:
|
for p in obj.data.polygons:
|
||||||
height = 1.0
|
height = 1.0
|
||||||
if p in heights:
|
if height_lay is not None:
|
||||||
height = heights[p]
|
height = height_lambda(p.index)
|
||||||
for ek in p.edge_keys:
|
for ek in p.edge_keys:
|
||||||
co0 = obj_mtx * obj.data.vertices[ek[0]].co
|
co0 = obj_mtx * obj.data.vertices[ek[0]].co
|
||||||
co1 = obj_mtx * obj.data.vertices[ek[1]].co
|
co1 = obj_mtx * obj.data.vertices[ek[1]].co
|
||||||
|
@ -67,27 +267,30 @@ def draw_callback_3d(self, context):
|
||||||
# restore opengl defaults
|
# restore opengl defaults
|
||||||
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
|
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
# Toggle height viz button
|
||||||
class PathHeightDrawOperator(bpy.types.Operator):
|
class PathHeightDrawOperator(bpy.types.Operator):
|
||||||
bl_idname = "view3d.toggle_path_height_visualization"
|
bl_idname = "view3d.toggle_path_height_visualization"
|
||||||
bl_label = "Toggle PATH height visualization"
|
bl_label = "Toggle PATH height visualization"
|
||||||
_handle_3d = None
|
_handle_3d = None
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
#heights = correlate_polygon_heights(context, bpy.data.objects['Plane'])
|
|
||||||
# the arguments we pass the the callback
|
# the arguments we pass the the callback
|
||||||
args = (self, context)
|
args = (self, context)
|
||||||
# Add the region OpenGL drawing callback
|
# Add the region OpenGL drawing callback
|
||||||
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
|
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
|
||||||
if self._handle_3d is None:
|
if self._handle_3d is None:
|
||||||
PathHeightDrawOperator._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
|
PathHeightDrawOperator._handle_3d = \
|
||||||
|
bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
|
||||||
else:
|
else:
|
||||||
bpy.types.SpaceView3D.draw_handler_remove(PathHeightDrawOperator._handle_3d, 'WINDOW')
|
bpy.types.SpaceView3D.draw_handler_remove(PathHeightDrawOperator._handle_3d, 'WINDOW')
|
||||||
PathHeightDrawOperator._handle_3d = None
|
PathHeightDrawOperator._handle_3d = None
|
||||||
|
|
||||||
for ar in bpy.context.screen.areas:
|
for ar in bpy.context.screen.areas:
|
||||||
ar.tag_redraw()
|
if ar.type == 'VIEW_3D':
|
||||||
|
ar.tag_redraw()
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
# Toggle background wire button
|
||||||
class PathBackgroundWireframeOperator(bpy.types.Operator):
|
class PathBackgroundWireframeOperator(bpy.types.Operator):
|
||||||
bl_idname = "view3d.toggle_path_background_wireframe"
|
bl_idname = "view3d.toggle_path_background_wireframe"
|
||||||
bl_label = "Toggle PATH background wireframe"
|
bl_label = "Toggle PATH background wireframe"
|
||||||
|
@ -110,6 +313,10 @@ class PathBackgroundWireframeOperator(bpy.types.Operator):
|
||||||
|
|
||||||
# Registration
|
# Registration
|
||||||
def register():
|
def register():
|
||||||
|
bpy.types.Material.retro_path_idx_mask = bpy.props.IntProperty(name='Retro: Path Index Mask')
|
||||||
|
bpy.types.Material.retro_path_type_mask = bpy.props.IntProperty(name='Retro: Path Type Mask')
|
||||||
|
bpy.types.WindowManager.hecl_height_prop = bpy.props.FloatProperty(
|
||||||
|
get=get_height, set=set_height, options={'HIDDEN'})
|
||||||
bpy.types.Scene.hecl_path_obj = bpy.props.StringProperty(
|
bpy.types.Scene.hecl_path_obj = bpy.props.StringProperty(
|
||||||
name='HECL Path Object',
|
name='HECL Path Object',
|
||||||
description='Blender Mesh Object to export during PATH\'s cook process')
|
description='Blender Mesh Object to export during PATH\'s cook process')
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 17a0959dbd0a79f9f9630dc616adc79fff65ad80
|
Subproject commit 73a0ae0d0056c1ad3a0eb9cc3a50d284e320ec2e
|
|
@ -512,6 +512,7 @@ struct Actor
|
||||||
/** Intermediate pathfinding representation prepared by blender */
|
/** Intermediate pathfinding representation prepared by blender */
|
||||||
struct PathMesh
|
struct PathMesh
|
||||||
{
|
{
|
||||||
|
std::vector<uint8_t> data;
|
||||||
PathMesh(Connection& conn);
|
PathMesh(Connection& conn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -598,6 +599,7 @@ class Connection
|
||||||
friend struct Armature;
|
friend struct Armature;
|
||||||
friend struct Action;
|
friend struct Action;
|
||||||
friend struct Bone;
|
friend struct Bone;
|
||||||
|
friend struct PathMesh;
|
||||||
friend struct Vector2f;
|
friend struct Vector2f;
|
||||||
friend struct Vector3f;
|
friend struct Vector3f;
|
||||||
friend struct Vector4f;
|
friend struct Vector4f;
|
||||||
|
|
|
@ -49,7 +49,7 @@ struct CVarContainer : public athena::io::DNA<athena::Big>
|
||||||
AT_DECL_DNA
|
AT_DECL_DNA
|
||||||
Value<atUint32> magic = 'CVAR';
|
Value<atUint32> magic = 'CVAR';
|
||||||
Value<atUint32> cvarCount;
|
Value<atUint32> cvarCount;
|
||||||
Vector<CVar, DNA_COUNT(cvarCount)> cvars;
|
Vector<CVar, AT_DNA_COUNT(cvarCount)> cvars;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,7 +313,7 @@ struct IR : BigDNA
|
||||||
AT_DECL_DNA
|
AT_DECL_DNA
|
||||||
String<-1> m_name;
|
String<-1> m_name;
|
||||||
Value<atUint16> m_argInstCount;
|
Value<atUint16> m_argInstCount;
|
||||||
Vector<atUint16, DNA_COUNT(m_argInstCount)> m_argInstIdxs;
|
Vector<atUint16, AT_DNA_COUNT(m_argInstCount)> m_argInstIdxs;
|
||||||
} m_call;
|
} m_call;
|
||||||
|
|
||||||
struct LoadImm : BigDNA
|
struct LoadImm : BigDNA
|
||||||
|
|
|
@ -1522,7 +1522,10 @@ Actor::Actor(Connection& conn)
|
||||||
|
|
||||||
PathMesh::PathMesh(Connection& conn)
|
PathMesh::PathMesh(Connection& conn)
|
||||||
{
|
{
|
||||||
|
uint32_t dataSize;
|
||||||
|
conn._readBuf(&dataSize, 4);
|
||||||
|
data.resize(dataSize);
|
||||||
|
conn._readBuf(data.data(), dataSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Bone* Armature::lookupBone(const char* name) const
|
const Bone* Armature::lookupBone(const char* name) const
|
||||||
|
|
Loading…
Reference in New Issue