mirror of
https://github.com/AxioDL/metaforce.git
synced 2025-12-08 13:44:56 +00:00
Blender 2.8 refactor
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# Node Grid Arranger Class
|
||||
NODE_PADDING = 80
|
||||
FRAME_NAMES = ['Dynamics','Textures','Combiners','Output']
|
||||
FRAME_WIDTHS = [250, 250, 800, 180]
|
||||
FRAME_NAMES = ['Textures','Output']
|
||||
FRAME_WIDTHS = [400, 180]
|
||||
TOTAL_WIDTH = 0.0
|
||||
for width in FRAME_WIDTHS:
|
||||
TOTAL_WIDTH += width + NODE_PADDING
|
||||
FRAME_COLORS = [(0.6,0.46,0.6),(0.6,0.48,0.44),(0.33,0.48,0.6),(0.53,0.6,0.47)]
|
||||
FRAME_COLORS = [(0.6,0.48,0.44),(0.53,0.6,0.47)]
|
||||
class Nodegrid:
|
||||
|
||||
def __init__(self, nodetree, cycles=False):
|
||||
|
||||
@@ -134,11 +134,11 @@ def load_func(self, context):
|
||||
def register():
|
||||
bpy.utils.register_class(FILE_OT_hecl_patching_save)
|
||||
bpy.utils.register_class(FILE_OT_hecl_patching_load)
|
||||
bpy.types.INFO_MT_file_external_data.append(load_func)
|
||||
bpy.types.INFO_MT_file_external_data.append(save_func)
|
||||
bpy.types.TOPBAR_MT_file_external_data.append(load_func)
|
||||
bpy.types.TOPBAR_MT_file_external_data.append(save_func)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(FILE_OT_hecl_patching_save)
|
||||
bpy.utils.unregister_class(FILE_OT_hecl_patching_load)
|
||||
bpy.types.INFO_MT_file_external_data.remove(load_func)
|
||||
bpy.types.INFO_MT_file_external_data.remove(save_func)
|
||||
bpy.types.TOPBAR_MT_file_external_data.remove(load_func)
|
||||
bpy.types.TOPBAR_MT_file_external_data.remove(save_func)
|
||||
|
||||
@@ -2,7 +2,7 @@ bl_info = {
|
||||
"name": "HECL",
|
||||
"author": "Jack Andersen <jackoalan@gmail.com>",
|
||||
"version": (1, 0),
|
||||
"blender": (2, 78),
|
||||
"blender": (2, 80, 0),
|
||||
"tracker_url": "https://github.com/AxioDL/hecl/issues/new",
|
||||
"location": "Properties > Scene > HECL",
|
||||
"description": "Enables blender to gather meshes, materials, and textures for hecl",
|
||||
@@ -11,11 +11,10 @@ bl_info = {
|
||||
# Package import
|
||||
from . import hmdl, sact, srea, swld, mapa, mapu, frme, path, Nodegrid, Patching
|
||||
Nodegrid = Nodegrid.Nodegrid
|
||||
import bpy, os, sys, struct
|
||||
from bpy.app.handlers import persistent
|
||||
parent_armature = sact.SACTSubtype.parent_armature
|
||||
import bpy, os, sys, struct, math
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
# Appendable list allowing external addons to register additional resource types
|
||||
hecl_typeS = [
|
||||
('NONE', "None", "Active scene not using HECL", None),
|
||||
@@ -46,11 +45,33 @@ class hecl_scene_panel(bpy.types.Panel):
|
||||
type_row = layout.row(align=True)
|
||||
type_row.prop_menu_enum(context.scene, 'hecl_type', text='Export Type')
|
||||
|
||||
if context.scene.hecl_type == 'MESH' or context.scene.hecl_type == 'AREA' or context.scene.hecl_type == 'ACTOR':
|
||||
sm_row = layout.row(align=True)
|
||||
sm_row.prop_enum(context.scene, 'hecl_shader_model', 'ORIGINAL')
|
||||
sm_row.prop_enum(context.scene, 'hecl_shader_model', 'PBR')
|
||||
|
||||
for exp_type in hecl_typeS:
|
||||
if exp_type[0] == context.scene.hecl_type and callable(exp_type[3]):
|
||||
exp_type[3](self.layout, context)
|
||||
break
|
||||
|
||||
# Light Panel
|
||||
class hecl_light_panel(bpy.types.Panel):
|
||||
bl_idname = "DATA_PT_hecl_light"
|
||||
bl_label = "HECL"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "data"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.light
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(context.light, 'hecl_falloff_constant')
|
||||
layout.prop(context.light, 'hecl_falloff_linear')
|
||||
layout.prop(context.light, 'hecl_falloff_quadratic')
|
||||
|
||||
# Blender export-type registration
|
||||
def register_export_type_enum():
|
||||
@@ -107,6 +128,14 @@ def mesh_aabb(writepipebuf):
|
||||
writepipebuf(struct.pack('fff', total_min[0], total_min[1], total_min[2]))
|
||||
writepipebuf(struct.pack('fff', total_max[0], total_max[1], total_max[2]))
|
||||
|
||||
def shader_model_update(self, context):
|
||||
value = 0.0
|
||||
if self.hecl_shader_model == 'PBR':
|
||||
value = 1.0
|
||||
for shad in ('RetroShader', 'RetroDynamicShader', 'RetroDynamicAlphaShader', 'RetroDynamicCharacterShader'):
|
||||
if shad in bpy.data.node_groups and 'NewShaderModel' in bpy.data.node_groups[shad].nodes:
|
||||
bpy.data.node_groups[shad].nodes['NewShaderModel'].outputs[0].default_value = value
|
||||
|
||||
# Load scene callback
|
||||
from bpy.app.handlers import persistent
|
||||
@persistent
|
||||
@@ -114,13 +143,13 @@ def scene_loaded(dummy):
|
||||
# Hide everything from an external library
|
||||
for o in bpy.context.scene.objects:
|
||||
if o.library:
|
||||
o.hide = True
|
||||
o.hide_set(True)
|
||||
|
||||
# Show PATH library objects as wireframes
|
||||
if bpy.context.scene.hecl_type == 'PATH':
|
||||
if bpy.context.scene.background_set:
|
||||
for o in bpy.context.scene.background_set.objects:
|
||||
o.draw_type = 'WIRE'
|
||||
o.display_type = 'WIRE'
|
||||
if bpy.context.scene.hecl_path_obj in bpy.context.scene.objects:
|
||||
path_obj = bpy.context.scene.objects[bpy.context.scene.hecl_path_obj]
|
||||
path_obj.show_wire = True
|
||||
@@ -134,14 +163,11 @@ def scene_loaded(dummy):
|
||||
mesh_obj = bpy.data.objects[subtype.linked_mesh]
|
||||
if subtype.linked_armature in bpy.data.objects:
|
||||
arm_obj = bpy.data.objects[subtype.linked_armature]
|
||||
mesh_obj.parent = arm_obj
|
||||
mesh_obj.parent_type = 'ARMATURE'
|
||||
parent_armature(mesh_obj, arm_obj)
|
||||
for overlay in subtype.overlays:
|
||||
if overlay.linked_mesh in bpy.data.objects:
|
||||
mesh_obj = bpy.data.objects[overlay.linked_mesh]
|
||||
mesh_obj.parent = arm_obj
|
||||
mesh_obj.parent_type = 'ARMATURE'
|
||||
|
||||
parent_armature(mesh_obj, arm_obj)
|
||||
|
||||
# Show only the active mesh and action
|
||||
if sact.SACTSubtype.SACTSubtype_load.poll(bpy.context):
|
||||
@@ -149,6 +175,28 @@ def scene_loaded(dummy):
|
||||
if sact.SACTAction.SACTAction_load.poll(bpy.context):
|
||||
bpy.ops.scene.sactaction_load()
|
||||
|
||||
shader_model_update(bpy.context.scene, bpy.context)
|
||||
|
||||
def power_of_distance(context, light, dist):
|
||||
color = light.color
|
||||
return dist * dist * context.scene.eevee.light_threshold / max(color[0], max(color[1], color[2]))
|
||||
|
||||
def power_of_coefficients(context, light):
|
||||
epsilon = 1.19e-07
|
||||
if light.hecl_falloff_linear < epsilon and light.hecl_falloff_quadratic < epsilon:
|
||||
return 0.0
|
||||
color = light.color
|
||||
intens = max(color[0], max(color[1], color[2]))
|
||||
if light.hecl_falloff_quadratic > epsilon:
|
||||
if intens <= epsilon:
|
||||
return 0.0
|
||||
return power_of_distance(context, light, math.sqrt(intens / (0.0588235 * light.hecl_falloff_quadratic)))
|
||||
if light.hecl_falloff_linear > epsilon:
|
||||
return power_of_distance(context, light, intens / (0.0588235 * light.hecl_falloff_linear))
|
||||
return 0.0
|
||||
|
||||
def set_light_falloff(self, context):
|
||||
self.energy = power_of_coefficients(context, self)
|
||||
|
||||
# Registration
|
||||
def register():
|
||||
@@ -161,7 +209,33 @@ def register():
|
||||
mapu.register()
|
||||
path.register()
|
||||
bpy.utils.register_class(hecl_scene_panel)
|
||||
bpy.utils.register_class(hecl_light_panel)
|
||||
bpy.types.Scene.hecl_auto_select = bpy.props.BoolProperty(name='HECL Auto Select', default=True)
|
||||
bpy.types.Light.hecl_falloff_constant = bpy.props.FloatProperty(
|
||||
name="HECL Falloff Constant",
|
||||
description="Constant falloff coefficient",
|
||||
update=set_light_falloff,
|
||||
default=1.0,
|
||||
min=0.0)
|
||||
bpy.types.Light.hecl_falloff_linear = bpy.props.FloatProperty(
|
||||
name="HECL Falloff Linear",
|
||||
description="Linear falloff coefficient",
|
||||
update=set_light_falloff,
|
||||
default=0.0,
|
||||
min=0.0)
|
||||
bpy.types.Light.hecl_falloff_quadratic = bpy.props.FloatProperty(
|
||||
name="HECL Falloff Quadratic",
|
||||
description="Quadratic falloff coefficient",
|
||||
update=set_light_falloff,
|
||||
default=0.0,
|
||||
min=0.0)
|
||||
bpy.types.Scene.hecl_shader_model = bpy.props.EnumProperty(name="HECL Shader Model",
|
||||
description="Which shader model to use for rendering",
|
||||
items=[
|
||||
('ORIGINAL', "Original", "Close approximation of GameCube materials"),
|
||||
('PBR', "PBR", "Hybrid PBR materials replacing original reflection")],
|
||||
update=shader_model_update,
|
||||
default='ORIGINAL')
|
||||
bpy.app.handlers.load_post.append(scene_loaded)
|
||||
Patching.register()
|
||||
|
||||
@@ -172,6 +246,7 @@ def unregister():
|
||||
srea.unregister()
|
||||
path.unregister()
|
||||
bpy.utils.unregister_class(hecl_scene_panel)
|
||||
bpy.utils.unregister_class(hecl_light_panel)
|
||||
Patching.unregister()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,7 +4,7 @@ from mathutils import Quaternion
|
||||
def draw(layout, context):
|
||||
if bpy.context.active_object:
|
||||
obj = bpy.context.active_object
|
||||
layout.label("Widget Settings:", icon='OBJECT_DATA')
|
||||
layout.label(text="Widget Settings:", icon='OBJECT_DATA')
|
||||
layout.prop_menu_enum(obj, 'retro_widget_type', text='Widget Type')
|
||||
#layout.prop_search(obj, 'retro_widget_parent', context.scene, 'objects', text='Widget Parent')
|
||||
row = layout.row(align=True)
|
||||
@@ -56,9 +56,9 @@ def draw(layout, context):
|
||||
layout.prop(obj, 'retro_meter_max_capacity', text='Max Capacity')
|
||||
layout.prop(obj, 'retro_meter_worker_count', text='Worker Count')
|
||||
elif obj.retro_widget_type == 'RETRO_LITE':
|
||||
if obj.data and obj.type == 'LAMP':
|
||||
if obj.data and obj.type == 'LIGHT':
|
||||
layout.prop(obj.data, 'retro_light_index', text='Index')
|
||||
layout.label("Angular Falloff:", icon='LAMP_SPOT')
|
||||
layout.label(text="Angular Falloff:", icon='LIGHT')
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.data, 'retro_light_angle_constant', text='Constant')
|
||||
row.prop(obj.data, 'retro_light_angle_linear', text='Linear')
|
||||
@@ -201,7 +201,7 @@ def recursive_cook(buffer, obj, version, path_hasher, parent_name):
|
||||
cutoff = 0.0
|
||||
if obj.data.type == 'POINT':
|
||||
type_enum = 4
|
||||
elif obj.data.type == 'HEMI':
|
||||
elif obj.data.type == 'SUN':
|
||||
type_enum = 2
|
||||
elif obj.data.type == 'SPOT':
|
||||
type_enum = 0
|
||||
@@ -231,10 +231,10 @@ def recursive_cook(buffer, obj, version, path_hasher, parent_name):
|
||||
path_hash = 0xffffffff
|
||||
if len(obj.data.materials):
|
||||
material = obj.data.materials[0]
|
||||
if len(material.texture_slots) and material.texture_slots[0]:
|
||||
tex_slot = material.texture_slots[0]
|
||||
if tex_slot.texture.type == 'IMAGE' and tex_slot.texture.image:
|
||||
image = tex_slot.texture.image
|
||||
if 'Image Texture' in material.node_tree.nodes:
|
||||
image_node = material.node_tree.nodes['Image Texture']
|
||||
if image_node.image:
|
||||
image = image_node.image
|
||||
path = bpy.path.abspath(image.filepath)
|
||||
path_hash = path_hasher.hashpath32(path)
|
||||
|
||||
@@ -254,7 +254,7 @@ def recursive_cook(buffer, obj, version, path_hasher, parent_name):
|
||||
else:
|
||||
buffer += struct.pack('>b', False)
|
||||
|
||||
angMtx = angle.to_matrix() * obj.matrix_local.to_3x3()
|
||||
angMtx = angle.to_matrix() @ obj.matrix_local.to_3x3()
|
||||
buffer += struct.pack('>fffffffffffffffIH',
|
||||
obj.matrix_local[0][3],
|
||||
obj.matrix_local[1][3],
|
||||
@@ -384,8 +384,8 @@ def register():
|
||||
bpy.types.Object.retro_meter_max_capacity = bpy.props.IntProperty(name='Retro: Max Capacity', min=0, default=100)
|
||||
bpy.types.Object.retro_meter_worker_count = bpy.props.IntProperty(name='Retro: Worker Count', min=0, default=1)
|
||||
|
||||
bpy.types.Lamp.retro_light_index = bpy.props.IntProperty(name='Retro: Light Index', min=0, default=0)
|
||||
bpy.types.Lamp.retro_light_angle_constant = bpy.props.FloatProperty(name='Retro: Light Angle Constant', min=0.0, default=0.0)
|
||||
bpy.types.Lamp.retro_light_angle_linear = bpy.props.FloatProperty(name='Retro: Light Angle Linear', min=0.0, default=0.0)
|
||||
bpy.types.Lamp.retro_light_angle_quadratic = bpy.props.FloatProperty(name='Retro: Light Angle Quadratic', min=0.0, default=0.0)
|
||||
bpy.types.Light.retro_light_index = bpy.props.IntProperty(name='Retro: Light Index', min=0, default=0)
|
||||
bpy.types.Light.retro_light_angle_constant = bpy.props.FloatProperty(name='Retro: Light Angle Constant', min=0.0, default=0.0)
|
||||
bpy.types.Light.retro_light_angle_linear = bpy.props.FloatProperty(name='Retro: Light Angle Linear', min=0.0, default=0.0)
|
||||
bpy.types.Light.retro_light_angle_quadratic = bpy.props.FloatProperty(name='Retro: Light Angle Quadratic', min=0.0, default=0.0)
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import bpy, bmesh, operator, struct
|
||||
from mathutils import Vector
|
||||
|
||||
# Function to quantize normals to 15-bit precision
|
||||
def quant_norm(n):
|
||||
@@ -15,348 +14,87 @@ def quant_luv(n):
|
||||
uf[i] = int(uf[i] * 32768) / 32768.0
|
||||
return uf.freeze()
|
||||
|
||||
# Class for building unique sets of vertex attributes for VBO generation
|
||||
class VertPool:
|
||||
# Function to output all mesh attribute values
|
||||
def write_mesh_attrs(writebuf, bm, rna_loops, use_luv, material_slots):
|
||||
dlay = None
|
||||
if len(bm.verts.layers.deform):
|
||||
dlay = bm.verts.layers.deform[0]
|
||||
|
||||
# Initialize hash-unique index for each available attribute
|
||||
def __init__(self, bm, rna_loops, use_luv, material_slots):
|
||||
self.bm = bm
|
||||
self.rna_loops = rna_loops
|
||||
self.material_slots = material_slots
|
||||
self.pos = {}
|
||||
self.norm = {}
|
||||
self.skin = {}
|
||||
self.color = {}
|
||||
self.uv = {}
|
||||
self.luv = {}
|
||||
self.dlay = None
|
||||
self.clays = []
|
||||
self.ulays = []
|
||||
self.luvlay = None
|
||||
clays = []
|
||||
for cl in range(len(bm.loops.layers.color)):
|
||||
clays.append(bm.loops.layers.color[cl])
|
||||
writebuf(struct.pack('I', len(clays)))
|
||||
|
||||
dlay = None
|
||||
if len(bm.verts.layers.deform):
|
||||
dlay = bm.verts.layers.deform[0]
|
||||
self.dlay = dlay
|
||||
luvlay = None
|
||||
if use_luv:
|
||||
luvlay = bm.loops.layers.uv[0]
|
||||
ulays = []
|
||||
for ul in range(len(bm.loops.layers.uv)):
|
||||
ulays.append(bm.loops.layers.uv[ul])
|
||||
writebuf(struct.pack('I', len(ulays)))
|
||||
|
||||
clays = []
|
||||
for cl in range(len(bm.loops.layers.color)):
|
||||
clays.append(bm.loops.layers.color[cl])
|
||||
self.clays = clays
|
||||
|
||||
luvlay = None
|
||||
if use_luv:
|
||||
luvlay = bm.loops.layers.uv[0]
|
||||
self.luvlay = luvlay
|
||||
ulays = []
|
||||
for ul in range(len(bm.loops.layers.uv)):
|
||||
ulays.append(bm.loops.layers.uv[ul])
|
||||
self.ulays = ulays
|
||||
|
||||
# Per-vert pool attributes
|
||||
for v in bm.verts:
|
||||
pf = v.co.copy().freeze()
|
||||
if pf not in self.pos:
|
||||
self.pos[pf] = len(self.pos)
|
||||
if not rna_loops:
|
||||
nf = quant_norm(v.normal)
|
||||
if nf not in self.norm:
|
||||
self.norm[nf] = len(self.norm)
|
||||
if dlay:
|
||||
sf = tuple(sorted(v[dlay].items()))
|
||||
if sf not in self.skin:
|
||||
self.skin[sf] = len(self.skin)
|
||||
|
||||
# Per-loop pool attributes
|
||||
for f in bm.faces:
|
||||
lightmapped = f.material_index < len(material_slots) and \
|
||||
material_slots[f.material_index].material['retro_lightmapped']
|
||||
for l in f.loops:
|
||||
if rna_loops:
|
||||
nf = quant_norm(rna_loops[l.index].normal)
|
||||
if nf not in self.norm:
|
||||
self.norm[nf] = len(self.norm)
|
||||
for cl in range(len(clays)):
|
||||
cf = l[clays[cl]].copy().freeze()
|
||||
if cf not in self.color:
|
||||
self.color[cf] = len(self.color)
|
||||
start_uvlay = 0
|
||||
if use_luv and lightmapped:
|
||||
start_uvlay = 1
|
||||
uf = quant_luv(l[luvlay].uv)
|
||||
if uf not in self.luv:
|
||||
self.luv[uf] = len(self.luv)
|
||||
for ul in range(start_uvlay, len(ulays)):
|
||||
uf = l[ulays[ul]].uv.copy().freeze()
|
||||
if uf not in self.uv:
|
||||
self.uv[uf] = len(self.uv)
|
||||
|
||||
def write_out(self, writebuf, vert_groups):
|
||||
writebuf(struct.pack('I', len(self.pos)))
|
||||
for p in sorted(self.pos.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', p[0][0], p[0][1], p[0][2]))
|
||||
|
||||
writebuf(struct.pack('I', len(self.norm)))
|
||||
for n in sorted(self.norm.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', n[0][0], n[0][1], n[0][2]))
|
||||
|
||||
writebuf(struct.pack('II', len(self.clays), len(self.color)))
|
||||
for c in sorted(self.color.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', c[0][0], c[0][1], c[0][2]))
|
||||
|
||||
writebuf(struct.pack('II', len(self.ulays), len(self.uv)))
|
||||
for u in sorted(self.uv.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('ff', u[0][0], u[0][1]))
|
||||
|
||||
luv_count = 0
|
||||
if self.luvlay is not None:
|
||||
luv_count = 1
|
||||
writebuf(struct.pack('II', luv_count, len(self.luv)))
|
||||
for u in sorted(self.luv.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('ff', u[0][0], u[0][1]))
|
||||
|
||||
writebuf(struct.pack('I', len(vert_groups)))
|
||||
for vgrp in vert_groups:
|
||||
writebuf(struct.pack('I', len(vgrp.name)))
|
||||
writebuf(vgrp.name.encode())
|
||||
|
||||
writebuf(struct.pack('I', len(self.skin)))
|
||||
for s in sorted(self.skin.items(), key=operator.itemgetter(1)):
|
||||
entries = s[0]
|
||||
writebuf(struct.pack('I', len(entries)))
|
||||
if len(entries):
|
||||
total_len = 0.0
|
||||
for ent in entries:
|
||||
total_len += ent[1]
|
||||
for ent in entries:
|
||||
writebuf(struct.pack('If', ent[0], ent[1] / total_len))
|
||||
|
||||
def write_out_map(self, writebuf):
|
||||
writebuf(struct.pack('I', len(self.pos)))
|
||||
for p in sorted(self.pos.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', p[0][0], p[0][1], p[0][2]))
|
||||
|
||||
def get_pos_idx(self, vert):
|
||||
pf = vert.co.copy().freeze()
|
||||
return self.pos[pf]
|
||||
|
||||
def get_norm_idx(self, loop):
|
||||
if self.rna_loops:
|
||||
nf = quant_norm(self.rna_loops[loop.index].normal)
|
||||
# Verts
|
||||
writebuf(struct.pack('I', len(bm.verts)))
|
||||
for v in bm.verts:
|
||||
writebuf(struct.pack('fff', v.co[0], v.co[1], v.co[2]))
|
||||
if dlay:
|
||||
sf = tuple(sorted(v[dlay].items()))
|
||||
writebuf(struct.pack('I', len(sf)))
|
||||
total_len = 0.0
|
||||
for ent in sf:
|
||||
total_len += ent[1]
|
||||
for ent in sf:
|
||||
writebuf(struct.pack('If', ent[0], ent[1] / total_len))
|
||||
else:
|
||||
nf = quant_norm(loop.vert.normal)
|
||||
return self.norm[nf]
|
||||
writebuf(struct.pack('I', 0))
|
||||
|
||||
def get_skin_idx(self, vert):
|
||||
if not self.dlay:
|
||||
return 0
|
||||
sf = tuple(sorted(vert[self.dlay].items()))
|
||||
return self.skin[sf]
|
||||
# Loops
|
||||
loop_count = 0
|
||||
for f in bm.faces:
|
||||
for l in f.loops:
|
||||
loop_count += 1
|
||||
writebuf(struct.pack('I', loop_count))
|
||||
for f in bm.faces:
|
||||
for l in f.loops:
|
||||
if rna_loops:
|
||||
nf = quant_norm(rna_loops[l.index].normal)
|
||||
else:
|
||||
nf = quant_norm(l.vert.normal)
|
||||
writebuf(struct.pack('fff', nf[0], nf[1], nf[2]))
|
||||
for cl in range(len(clays)):
|
||||
col = l[clays[cl]]
|
||||
writebuf(struct.pack('fff', col[0], col[1], col[2]))
|
||||
for cl in range(len(ulays)):
|
||||
if luvlay and cl == 0 and material_slots[l.face.material_index].material['retro_lightmapped']:
|
||||
uv = quant_luv(l[luvlay].uv)
|
||||
else:
|
||||
uv = l[ulays[cl]].uv
|
||||
writebuf(struct.pack('ff', uv[0], uv[1]))
|
||||
writebuf(struct.pack('IIIII', l.vert.index, l.edge.index, l.face.index,
|
||||
l.link_loop_next.index, l.link_loop_prev.index))
|
||||
if l.edge.is_contiguous:
|
||||
writebuf(struct.pack('II', l.link_loop_radial_next.index, l.link_loop_radial_prev.index))
|
||||
else:
|
||||
writebuf(struct.pack('II', 0xffffffff, 0xffffffff))
|
||||
|
||||
def get_color_idx(self, loop, cidx):
|
||||
cf = loop[self.clays[cidx]].copy().freeze()
|
||||
return self.color[cf]
|
||||
|
||||
def get_uv_idx(self, loop, uidx):
|
||||
if self.luvlay is not None and uidx == 0:
|
||||
if self.material_slots[loop.face.material_index].material['retro_lightmapped']:
|
||||
uf = quant_luv(loop[self.luvlay].uv)
|
||||
return self.luv[uf]
|
||||
uf = loop[self.ulays[uidx]].uv.copy().freeze()
|
||||
return self.uv[uf]
|
||||
|
||||
def loops_contiguous(self, la, lb):
|
||||
if la.vert != lb.vert:
|
||||
return False
|
||||
if self.get_norm_idx(la) != self.get_norm_idx(lb):
|
||||
return False
|
||||
for cl in range(len(self.clays)):
|
||||
if self.get_color_idx(la, cl) != self.get_color_idx(lb, cl):
|
||||
return False
|
||||
for ul in range(len(self.ulays)):
|
||||
if self.get_uv_idx(la, ul) != self.get_uv_idx(lb, ul):
|
||||
return False
|
||||
return True
|
||||
|
||||
def splitable_edge(self, edge):
|
||||
if len(edge.link_faces) < 2:
|
||||
return False
|
||||
for v in edge.verts:
|
||||
found = None
|
||||
for f in edge.link_faces:
|
||||
for l in f.loops:
|
||||
if l.vert == v:
|
||||
if not found:
|
||||
found = l
|
||||
break
|
||||
else:
|
||||
if not self.loops_contiguous(found, l):
|
||||
return True
|
||||
break
|
||||
return False
|
||||
|
||||
def loop_out(self, writebuf, loop):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('II', self.get_pos_idx(loop.vert), self.get_norm_idx(loop)))
|
||||
for cl in range(len(self.clays)):
|
||||
writebuf(struct.pack('I', self.get_color_idx(loop, cl)))
|
||||
for ul in range(len(self.ulays)):
|
||||
writebuf(struct.pack('I', self.get_uv_idx(loop, ul)))
|
||||
sp = struct.pack('I', self.get_skin_idx(loop.vert))
|
||||
writebuf(sp)
|
||||
|
||||
def null_loop_out(self, writebuf):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('I', 0xffffffff))
|
||||
|
||||
def loop_out_map(self, writebuf, loop):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('I', self.get_pos_idx(loop.vert)))
|
||||
|
||||
def vert_out_map(self, writebuf, vert):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('I', self.get_pos_idx(vert)))
|
||||
|
||||
|
||||
def sort_faces_by_skin_group(dlay, faces):
|
||||
faces_out = []
|
||||
done_sg = set()
|
||||
ref_sg = None
|
||||
while len(faces_out) < len(faces):
|
||||
for f in faces:
|
||||
found = False
|
||||
for v in f.verts:
|
||||
sg = tuple(sorted(v[dlay].items()))
|
||||
if sg not in done_sg:
|
||||
ref_sg = sg
|
||||
done_sg.add(ref_sg)
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
break
|
||||
|
||||
for f in faces:
|
||||
if f in faces_out:
|
||||
continue
|
||||
for v in f.verts:
|
||||
sg = tuple(sorted(v[dlay].items()))
|
||||
if sg == ref_sg:
|
||||
faces_out.append(f)
|
||||
break
|
||||
|
||||
return faces_out
|
||||
|
||||
def recursive_faces_islands(dlay, list_out, rem_list, skin_slot_set, skin_slot_count, face):
|
||||
if face not in rem_list:
|
||||
return []
|
||||
|
||||
if dlay:
|
||||
for v in face.verts:
|
||||
sg = tuple(sorted(v[dlay].items()))
|
||||
if sg not in skin_slot_set:
|
||||
if skin_slot_count > 0 and len(skin_slot_set) == skin_slot_count:
|
||||
return False
|
||||
skin_slot_set.add(sg)
|
||||
|
||||
list_out.append(face)
|
||||
rem_list.remove(face)
|
||||
next_faces = []
|
||||
for e in face.edges:
|
||||
if not e.is_contiguous:
|
||||
continue
|
||||
# Edges
|
||||
writebuf(struct.pack('I', len(bm.edges)))
|
||||
for e in bm.edges:
|
||||
for v in e.verts:
|
||||
writebuf(struct.pack('I', v.index))
|
||||
writebuf(struct.pack('I', len(e.link_faces)))
|
||||
for f in e.link_faces:
|
||||
if f == face:
|
||||
continue
|
||||
next_faces.append(f)
|
||||
return next_faces
|
||||
writebuf(struct.pack('I', f.index))
|
||||
writebuf(struct.pack('b', e.is_contiguous))
|
||||
|
||||
def strip_next_loop(prev_loop, out_count):
|
||||
if out_count & 1:
|
||||
radial_loop = prev_loop.link_loop_radial_next
|
||||
loop = radial_loop.link_loop_prev
|
||||
return loop, loop
|
||||
else:
|
||||
radial_loop = prev_loop.link_loop_radial_prev
|
||||
loop = radial_loop.link_loop_next
|
||||
return loop.link_loop_next, loop
|
||||
|
||||
def write_out_surface(writebuf, output_mode, vert_pool, island_faces, mat_idx):
|
||||
|
||||
# Centroid of surface
|
||||
centroid = Vector()
|
||||
for f in island_faces:
|
||||
centroid += f.calc_center_bounds()
|
||||
centroid /= len(island_faces)
|
||||
writebuf(struct.pack('fff', centroid[0], centroid[1], centroid[2]))
|
||||
|
||||
# Material index
|
||||
writebuf(struct.pack('I', mat_idx))
|
||||
|
||||
# AABB of surface
|
||||
aabb_min = Vector((9999999, 9999999, 9999999))
|
||||
aabb_max = Vector((-9999999, -9999999, -9999999))
|
||||
for f in island_faces:
|
||||
for v in f.verts:
|
||||
for c in range(3):
|
||||
if v.co[c] < aabb_min[c]:
|
||||
aabb_min[c] = v.co[c]
|
||||
if v.co[c] > aabb_max[c]:
|
||||
aabb_max[c] = v.co[c]
|
||||
writebuf(struct.pack('fff', aabb_min[0], aabb_min[1], aabb_min[2]))
|
||||
writebuf(struct.pack('fff', aabb_max[0], aabb_max[1], aabb_max[2]))
|
||||
|
||||
# Average normal of surface
|
||||
avg_norm = Vector()
|
||||
for f in island_faces:
|
||||
avg_norm += f.normal
|
||||
avg_norm.normalize()
|
||||
writebuf(struct.pack('fff', avg_norm[0], avg_norm[1], avg_norm[2]))
|
||||
|
||||
# Count estimate (as raw triangles)
|
||||
writebuf(struct.pack('I', len(island_faces) * 3))
|
||||
|
||||
# Verts themselves
|
||||
if output_mode == 'TRIANGLES':
|
||||
for f in island_faces:
|
||||
for l in f.loops:
|
||||
vert_pool.loop_out(writebuf, l)
|
||||
|
||||
elif output_mode == 'TRISTRIPS':
|
||||
prev_loop_emit = None
|
||||
while len(island_faces):
|
||||
sel_lists_local = []
|
||||
for start_face in island_faces:
|
||||
for l in start_face.loops:
|
||||
island_local = list(island_faces)
|
||||
prev_loop = l.link_loop_next
|
||||
loop = prev_loop.link_loop_next
|
||||
sel_list = [l, prev_loop, loop]
|
||||
island_local.remove(start_face)
|
||||
while True:
|
||||
if not prev_loop.edge.is_contiguous or prev_loop.edge.tag:
|
||||
break
|
||||
loop, prev_loop = strip_next_loop(prev_loop, len(sel_list))
|
||||
face = loop.face
|
||||
if face not in island_local:
|
||||
break
|
||||
sel_list.append(loop)
|
||||
island_local.remove(face)
|
||||
sel_lists_local.append((sel_list, island_local))
|
||||
|
||||
max_count = 0
|
||||
max_sl = None
|
||||
max_island_faces = None
|
||||
for sl in sel_lists_local:
|
||||
if len(sl[0]) > max_count:
|
||||
max_count = len(sl[0])
|
||||
max_sl = sl[0]
|
||||
max_island_faces = sl[1]
|
||||
island_faces = max_island_faces
|
||||
if prev_loop_emit:
|
||||
vert_pool.null_loop_out(writebuf)
|
||||
for loop in max_sl:
|
||||
vert_pool.loop_out(writebuf, loop)
|
||||
prev_loop_emit = loop
|
||||
|
||||
writebuf(struct.pack('B', 0))
|
||||
# Faces
|
||||
writebuf(struct.pack('I', len(bm.faces)))
|
||||
for f in bm.faces:
|
||||
norm = f.normal
|
||||
writebuf(struct.pack('fff', norm[0], norm[1], norm[2]))
|
||||
centroid = f.calc_center_bounds()
|
||||
writebuf(struct.pack('fff', centroid[0], centroid[1], centroid[2]))
|
||||
writebuf(struct.pack('I', f.material_index))
|
||||
for l in f.loops:
|
||||
writebuf(struct.pack('I', l.index))
|
||||
|
||||
|
||||
@@ -1,394 +1,162 @@
|
||||
import bpy, bpy.path, os.path
|
||||
import bpy, bpy.path, os.path, struct
|
||||
|
||||
def get_texmap_idx(tex_list, name):
|
||||
for i in range(len(tex_list)):
|
||||
if tex_list[i] == name:
|
||||
return i
|
||||
retval = len(tex_list)
|
||||
tex_list.append(name)
|
||||
return retval
|
||||
def get_texture_path(image):
|
||||
return os.path.normpath(bpy.path.abspath(image.filepath))
|
||||
|
||||
def get_texture_path(name):
|
||||
if name not in bpy.data.textures:
|
||||
raise RuntimeError('unable to find %s texture' % name)
|
||||
tex = bpy.data.textures[name]
|
||||
if tex.type != 'IMAGE':
|
||||
raise RuntimeError('%s texture unsupported for %s, please save as IMAGE' % (tex.type, name))
|
||||
img = tex.image
|
||||
if not img:
|
||||
raise RuntimeError('image not set in %s' % name)
|
||||
return os.path.normpath(bpy.path.abspath(img.filepath))
|
||||
SHADER_TYPES = {
|
||||
'RetroShader': b'RSHD',
|
||||
'RetroDynamicShader': b'RDYN',
|
||||
'RetroDynamicAlphaShader': b'RDAL',
|
||||
'RetroDynamicCharacterShader': b'RCHR',
|
||||
}
|
||||
|
||||
# Trace color node structure
|
||||
def recursive_color_trace(mat_obj, mesh_obj, tex_list, node, socket=None):
|
||||
PASS_TYPE = {
|
||||
'Lightmap': b'LMAP',
|
||||
'Diffuse': b'DIFF',
|
||||
'Emissive': b'EMIS',
|
||||
'Specular': b'SPEC',
|
||||
'ExtendedSpecular': b'ESPC',
|
||||
'Reflection': b'REFL',
|
||||
'IndirectTex': b'INDR',
|
||||
'Alpha': b'ALPH',
|
||||
}
|
||||
|
||||
if node.type == 'OUTPUT':
|
||||
if node.inputs['Color'].is_linked:
|
||||
return recursive_color_trace(mat_obj, mesh_obj, tex_list, node.inputs['Color'].links[0].from_node, node.inputs['Color'].links[0].from_socket)
|
||||
else:
|
||||
return 'vec3(%g,%g,%g)' % (node.inputs['Color'].default_value[0],
|
||||
node.inputs['Color'].default_value[1],
|
||||
node.inputs['Color'].default_value[2])
|
||||
|
||||
elif node.type == 'MIX_RGB':
|
||||
|
||||
if node.inputs[1].is_linked:
|
||||
a_input = recursive_color_trace(mat_obj, mesh_obj, tex_list, node.inputs[1].links[0].from_node, node.inputs[1].links[0].from_socket)
|
||||
else:
|
||||
a_input = 'vec3(%g,%g,%g)' % (node.inputs[1].default_value[0],
|
||||
node.inputs[1].default_value[1],
|
||||
node.inputs[1].default_value[2])
|
||||
|
||||
if node.inputs[2].is_linked:
|
||||
b_input = recursive_color_trace(mat_obj, mesh_obj, tex_list, node.inputs[2].links[0].from_node, node.inputs[2].links[0].from_socket)
|
||||
else:
|
||||
b_input = 'vec3(%g,%g,%g)' % (node.inputs[2].default_value[0],
|
||||
node.inputs[2].default_value[1],
|
||||
node.inputs[2].default_value[2])
|
||||
|
||||
if node.blend_type == 'MULTIPLY':
|
||||
if a_input == 'vec3(1,1,1)':
|
||||
return b_input
|
||||
elif b_input == 'vec3(1,1,1)':
|
||||
return a_input
|
||||
return '(%s * %s)' % (a_input, b_input)
|
||||
elif node.blend_type == 'ADD':
|
||||
if a_input == 'vec3(0,0,0)':
|
||||
return b_input
|
||||
elif b_input == 'vec3(0,0,0)':
|
||||
return a_input
|
||||
return '(%s + %s)' % (a_input, b_input)
|
||||
else:
|
||||
raise RuntimeError("HMDL does not support shaders with '{0}' blending modes".format(node.blend_type))
|
||||
|
||||
elif node.type == 'TEXTURE':
|
||||
|
||||
if not node.texture or not hasattr(node.texture, 'name'):
|
||||
raise RuntimeError("HMDL texture nodes must specify a texture object")
|
||||
|
||||
if not node.inputs['Vector'].is_linked:
|
||||
raise RuntimeError("HMDL texture nodes must have a 'Geometry' or 'Group' UV modifier node linked")
|
||||
|
||||
# Determine matrix generator type
|
||||
matrix_str = None
|
||||
soc_from = node.inputs['Vector'].links[0].from_socket
|
||||
|
||||
if soc_from.node.type == 'GROUP':
|
||||
matrix_str = '%s(%%s' % soc_from.node.node_tree.name
|
||||
if len(soc_from.node.inputs)-1:
|
||||
matrix_str += ', '
|
||||
for s in range(len(soc_from.node.inputs)-1):
|
||||
soc = soc_from.node.inputs[s+1]
|
||||
if len(soc.links):
|
||||
raise RuntimeError("UV Modifier nodes may not have parameter links (default values only)")
|
||||
if soc.type == 'VALUE':
|
||||
matrix_str += '%g' % soc.default_value
|
||||
else:
|
||||
ncomps = len(soc.default_value)
|
||||
matrix_str += 'vec%d(' % ncomps
|
||||
for c in range(ncomps-1):
|
||||
matrix_str += '%g,' % soc.default_value[c]
|
||||
matrix_str += '%g)' % soc.default_value[ncomps-1]
|
||||
|
||||
if s == len(soc_from.node.inputs)-2:
|
||||
matrix_str += ')'
|
||||
else:
|
||||
matrix_str += ', '
|
||||
else:
|
||||
matrix_str += ')'
|
||||
|
||||
soc_from = soc_from.node.inputs[0].links[0].from_socket
|
||||
|
||||
elif soc_from.node.type == 'GEOMETRY':
|
||||
pass
|
||||
|
||||
else:
|
||||
raise RuntimeError("HMDL texture nodes must have a 'Geometry', 'Group' UV modifier node linked")
|
||||
|
||||
if soc_from.node.type != 'GEOMETRY':
|
||||
raise RuntimeError("Matrix animator nodes must connect to 'Geometry' node")
|
||||
|
||||
|
||||
# Resolve map and matrix index
|
||||
node_label = soc_from.node.label
|
||||
if not matrix_str and node_label.startswith('MTX_'):
|
||||
matrix_str = 'TexMtx(%%s, %d)' % int(node_label[4:])
|
||||
|
||||
if soc_from.name == 'UV':
|
||||
uv_name = soc_from.node.uv_layer
|
||||
uv_idx = mesh_obj.data.uv_layers.find(uv_name)
|
||||
if uv_idx == -1:
|
||||
raise RuntimeError('UV Layer "%s" doesn\'t exist' % uv_name)
|
||||
uvsource_str = 'UV(%d)' % uv_idx
|
||||
|
||||
elif soc_from.name == 'Normal':
|
||||
uvsource_str = 'Normal()'
|
||||
|
||||
elif soc_from.name == 'View':
|
||||
uvsource_str = 'View()'
|
||||
|
||||
else:
|
||||
raise RuntimeError("Only the 'UV', 'Normal' and 'View' sockets may be used from 'Geometry' nodes")
|
||||
|
||||
call_name = 'Texture'
|
||||
if node.label.startswith('Diffuse'):
|
||||
call_name = 'TextureD'
|
||||
|
||||
if socket.name == 'Value':
|
||||
if matrix_str:
|
||||
uvsource_str = matrix_str % uvsource_str
|
||||
return '%s(%d, %s).aaa' % (call_name, get_texmap_idx(tex_list, node.texture.name), uvsource_str)
|
||||
if socket.name == 'Color':
|
||||
if matrix_str:
|
||||
uvsource_str = matrix_str % uvsource_str
|
||||
return '%s(%d, %s)' % (call_name, get_texmap_idx(tex_list, node.texture.name), uvsource_str)
|
||||
else:
|
||||
raise RuntimeError("Only the 'Value' or 'Color' output sockets may be used from Texture nodes")
|
||||
|
||||
elif node.type == 'GROUP':
|
||||
|
||||
group_str = '%s(' % node.node_tree.name
|
||||
did_first = False
|
||||
for input in node.inputs:
|
||||
if input.type == 'RGBA':
|
||||
if did_first:
|
||||
group_str += ', '
|
||||
if input.is_linked:
|
||||
group_str += recursive_color_trace(mat_obj, mesh_obj, tex_list, input.links[0].from_node, input.links[0].from_socket)
|
||||
else:
|
||||
group_str += 'vec3(%g,%g,%g)' % (input.default_value[0],
|
||||
input.default_value[1],
|
||||
input.default_value[2])
|
||||
did_first = True
|
||||
group_str += ')'
|
||||
return group_str
|
||||
|
||||
elif node.type == 'RGB':
|
||||
|
||||
if node.label.startswith('DYNAMIC_'):
|
||||
dynamic_index = int(node.label[8:])
|
||||
return 'ColorReg(%d)' % dynamic_index
|
||||
|
||||
return 'vec3(%g,%g,%g)' % (node.outputs['Color'].default_value[0],
|
||||
node.outputs['Color'].default_value[1],
|
||||
node.outputs['Color'].default_value[2])
|
||||
|
||||
elif node.type == 'MATERIAL':
|
||||
|
||||
if mat_obj.use_shadeless:
|
||||
return 'vec3(1.0)'
|
||||
else:
|
||||
return 'Lighting()'
|
||||
|
||||
else:
|
||||
raise RuntimeError("HMDL is unable to process '{0}' shader nodes in '{1}'".format(node.type, mat_obj.name))
|
||||
|
||||
|
||||
|
||||
# Trace alpha node structure
|
||||
def recursive_alpha_trace(mat_obj, mesh_obj, tex_list, node, socket=None):
|
||||
|
||||
if node.type == 'OUTPUT':
|
||||
if node.inputs['Alpha'].is_linked:
|
||||
return recursive_alpha_trace(mat_obj, mesh_obj, tex_list, node.inputs['Alpha'].links[0].from_node, node.inputs['Alpha'].links[0].from_socket)
|
||||
else:
|
||||
return '%g' % node.inputs['Alpha'].default_value
|
||||
|
||||
elif node.type == 'MATH':
|
||||
|
||||
if node.inputs[0].is_linked:
|
||||
a_input = recursive_alpha_trace(mat_obj, mesh_obj, tex_list, node.inputs[0].links[0].from_node, node.inputs[0].links[0].from_socket)
|
||||
else:
|
||||
a_input = '%g' % node.inputs[0].default_value
|
||||
|
||||
if node.inputs[1].is_linked:
|
||||
b_input = recursive_alpha_trace(mat_obj, mesh_obj, tex_list, node.inputs[1].links[0].from_node, node.inputs[1].links[0].from_socket)
|
||||
else:
|
||||
b_input = '%g' % node.inputs[1].default_value
|
||||
|
||||
if node.operation == 'MULTIPLY':
|
||||
if a_input == '1':
|
||||
return b_input
|
||||
elif b_input == '1':
|
||||
return a_input
|
||||
return '(%s * %s)' % (a_input, b_input)
|
||||
elif node.operation == 'ADD':
|
||||
if a_input == '0':
|
||||
return b_input
|
||||
elif b_input == '0':
|
||||
return a_input
|
||||
return '(%s + %s)' % (a_input, b_input)
|
||||
else:
|
||||
raise RuntimeError("HMDL does not support shaders with '{0}' blending modes".format(node.operation))
|
||||
|
||||
elif node.type == 'TEXTURE':
|
||||
|
||||
if not node.texture or not hasattr(node.texture, 'name'):
|
||||
raise RuntimeError("HMDL texture nodes must specify a texture object")
|
||||
|
||||
if not node.inputs['Vector'].is_linked:
|
||||
raise RuntimeError("HMDL texture nodes must have a 'Geometry' or 'Group' UV modifier node linked")
|
||||
|
||||
# Determine matrix generator type
|
||||
matrix_str = None
|
||||
soc_from = node.inputs['Vector'].links[0].from_socket
|
||||
|
||||
if soc_from.node.type == 'GROUP':
|
||||
matrix_str = '%s(%%s' % soc_from.node.node_tree.name
|
||||
if len(soc_from.node.inputs)-1:
|
||||
matrix_str += ', '
|
||||
for s in range(len(soc_from.node.inputs)-1):
|
||||
soc = soc_from.node.inputs[s+1]
|
||||
if len(soc.links):
|
||||
raise RuntimeError("UV Modifier nodes may not have parameter links (default values only)")
|
||||
if soc.type == 'VALUE':
|
||||
matrix_str += '%g' % soc.default_value
|
||||
else:
|
||||
ncomps = len(soc.default_value)
|
||||
matrix_str += 'vec%d(' % ncomps
|
||||
for c in range(ncomps-1):
|
||||
matrix_str += '%g,' % soc.default_value[c]
|
||||
matrix_str += '%g)' % soc.default_value[ncomps-1]
|
||||
|
||||
if s == len(soc_from.node.inputs)-2:
|
||||
matrix_str += ')'
|
||||
else:
|
||||
matrix_str += ', '
|
||||
else:
|
||||
matrix_str += ')'
|
||||
|
||||
soc_from = soc_from.node.inputs[0].links[0].from_socket
|
||||
|
||||
elif soc_from.node.type == 'GEOMETRY':
|
||||
pass
|
||||
|
||||
else:
|
||||
raise RuntimeError("HMDL texture nodes must have a 'Geometry', 'Group' UV modifier node linked")
|
||||
|
||||
if soc_from.node.type != 'GEOMETRY':
|
||||
raise RuntimeError("Matrix animator nodes must connect to 'Geometry' node")
|
||||
|
||||
|
||||
# Resolve map and matrix index
|
||||
node_label = soc_from.node.label
|
||||
if not matrix_str and node_label.startswith('MTX_'):
|
||||
matrix_str = 'TexMtx(%%s, %d)' % int(node_label[4:])
|
||||
|
||||
if soc_from.name == 'UV':
|
||||
uv_name = soc_from.node.uv_layer
|
||||
uv_idx = mesh_obj.data.uv_layers.find(uv_name)
|
||||
if uv_idx == -1:
|
||||
raise RuntimeError('UV Layer "%s" doesn\'t exist' % uv_name)
|
||||
uvsource_str = 'UV(%d)' % uv_idx
|
||||
|
||||
elif soc_from.name == 'Normal':
|
||||
uvsource_str = 'Normal()'
|
||||
|
||||
elif soc_from.name == 'View':
|
||||
uvsource_str = 'View()'
|
||||
|
||||
else:
|
||||
raise RuntimeError("Only the 'UV', 'Normal' and 'View' sockets may be used from 'Geometry' nodes")
|
||||
|
||||
if socket.name == 'Value':
|
||||
if matrix_str:
|
||||
uvsource_str = matrix_str % uvsource_str
|
||||
return 'Texture(%d, %s).aaa' % (get_texmap_idx(tex_list, node.texture.name), uvsource_str)
|
||||
else:
|
||||
raise RuntimeError("Only the 'Value' output sockets may be used from Texture nodes")
|
||||
|
||||
elif node.type == 'GROUP':
|
||||
|
||||
group_str = '%s(' % node.node_tree.name
|
||||
did_first = False
|
||||
for input in node.inputs:
|
||||
if input.type == 'VALUE':
|
||||
if did_first:
|
||||
group_str += ', '
|
||||
if input.is_linked:
|
||||
group_str += recursive_alpha_trace(mat_obj, mesh_obj, tex_list, input.links[0].from_node, input.links[0].from_socket)
|
||||
else:
|
||||
group_str += '%g' % input.default_value
|
||||
did_first = True
|
||||
group_str += ')'
|
||||
return group_str
|
||||
|
||||
elif node.type == 'VALUE':
|
||||
|
||||
if node.label.startswith('DYNAMIC_'):
|
||||
dynamic_index = int(node.label[8:])
|
||||
return 'ColorReg(%d).aaa' % dynamic_index
|
||||
|
||||
return '%g' % node.outputs['Value'].default_value
|
||||
|
||||
elif node.type == 'MATERIAL':
|
||||
|
||||
return '1.0'
|
||||
|
||||
else:
|
||||
raise RuntimeError("HMDL is unable to process '{0}' shader nodes in '{1}'".format(node.type, mat_obj.name))
|
||||
|
||||
|
||||
# Trace indirect node structure
|
||||
def indirect_trace(mat_obj, mesh_obj, tex_list, node):
|
||||
if node.type == 'OUTPUT':
|
||||
if node.inputs['Color'].is_linked:
|
||||
tex_node = node.inputs['Color'].links[0].from_node
|
||||
if tex_node.type != 'TEXTURE':
|
||||
raise RuntimeError("HMDL *requires* that an indirect output node is directly connected to TEXTURE")
|
||||
if not tex_node.texture or not hasattr(tex_node.texture, 'name'):
|
||||
raise RuntimeError("HMDL texture nodes must specify a texture object")
|
||||
get_texmap_idx(tex_list, tex_node.texture.name)
|
||||
|
||||
|
||||
def shader(mat_obj, mesh_obj):
|
||||
def write_chunks(writebuf, mat_obj, mesh_obj):
|
||||
|
||||
if not mat_obj.use_nodes:
|
||||
raise RuntimeError("HMDL *requires* that shader nodes are used; '{0}' does not".format(mat_obj.name))
|
||||
|
||||
if 'Output' not in mat_obj.node_tree.nodes or mat_obj.node_tree.nodes['Output'].type != 'OUTPUT':
|
||||
raise RuntimeError("HMDL *requires* that an OUTPUT shader node named 'Output' is present")
|
||||
if 'Output' not in mat_obj.node_tree.nodes or mat_obj.node_tree.nodes['Output'].type != 'GROUP':
|
||||
raise RuntimeError("HMDL *requires* that an group shader node named 'Output' is present")
|
||||
|
||||
# Root (output) node
|
||||
output_node = mat_obj.node_tree.nodes['Output']
|
||||
if output_node.node_tree.name not in SHADER_TYPES:
|
||||
raise RuntimeError("HMDL *requires* one of the RetroShader group nodes for the 'Output' node")
|
||||
writebuf(SHADER_TYPES[output_node.node_tree.name])
|
||||
|
||||
# Trace nodes and build result
|
||||
tex_list = []
|
||||
color_trace_result = recursive_color_trace(mat_obj, mesh_obj, tex_list, output_node)
|
||||
alpha_trace_result = recursive_alpha_trace(mat_obj, mesh_obj, tex_list, output_node)
|
||||
# Count sockets
|
||||
chunk_count = 0
|
||||
for inp in output_node.inputs:
|
||||
if inp.name in PASS_TYPE:
|
||||
if inp.is_linked:
|
||||
chunk_count += 1
|
||||
else:
|
||||
# Color pass
|
||||
color_set = False
|
||||
if inp.type == 'VALUE':
|
||||
color_set = bool(inp.default_value)
|
||||
else:
|
||||
for comp in inp.default_value:
|
||||
color_set |= bool(comp)
|
||||
if color_set:
|
||||
chunk_count += 1
|
||||
|
||||
# Trace indirect reflection texture
|
||||
if 'IndirectOutput' in mat_obj.node_tree.nodes:
|
||||
ind_out_node = mat_obj.node_tree.nodes['IndirectOutput']
|
||||
indirect_trace(mat_obj, mesh_obj, tex_list, ind_out_node)
|
||||
writebuf(struct.pack('I', chunk_count))
|
||||
|
||||
# Resolve texture paths
|
||||
tex_paths = [get_texture_path(name) for name in tex_list]
|
||||
# Enumerate sockets
|
||||
for inp in output_node.inputs:
|
||||
if inp.name in PASS_TYPE:
|
||||
pass_fourcc = PASS_TYPE[inp.name]
|
||||
if inp.is_linked:
|
||||
socket = inp.links[0].from_socket
|
||||
node = socket.node
|
||||
if node.type != 'TEX_IMAGE':
|
||||
raise RuntimeError("HMDL requires all group node inputs connect to Image Texture nodes")
|
||||
|
||||
if mat_obj.game_settings.alpha_blend == 'ALPHA' or mat_obj.game_settings.alpha_blend == 'ALPHA_SORT':
|
||||
return "HECLAlpha(%s, %s)" % (color_trace_result, alpha_trace_result), tex_paths
|
||||
elif mat_obj.game_settings.alpha_blend == 'ADD':
|
||||
return "HECLAdditive(%s, %s)" % (color_trace_result, alpha_trace_result), tex_paths
|
||||
else:
|
||||
return "HECLOpaque(%s, %s)" % (color_trace_result, alpha_trace_result), tex_paths
|
||||
if not node.image:
|
||||
raise RuntimeError("HMDL texture nodes must specify an image object")
|
||||
|
||||
# DEBUG operator
|
||||
import bpy
|
||||
class hecl_shader_operator(bpy.types.Operator):
|
||||
bl_idname = "scene.hecl_shader"
|
||||
bl_label = "DEBUG HECL shader maker"
|
||||
bl_description = "Test shader generation utility"
|
||||
if not node.inputs['Vector'].is_linked:
|
||||
raise RuntimeError("HMDL texture nodes must have a 'Texture Coordinate', 'UV Map' or 'Group' UV modifier node linked")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'MESH'
|
||||
# Determine matrix generator type
|
||||
tex_coord_source = 0xff
|
||||
uv_anim_type = 0xff
|
||||
uv_anim_args = []
|
||||
soc_from = node.inputs['Vector'].links[0].from_socket
|
||||
|
||||
def execute(self, context):
|
||||
shad, texs = shader(context.object.active_material, context.object)
|
||||
|
||||
vs = bpy.data.texts.new('HECL SHADER')
|
||||
vs.write((shad + '\n'))
|
||||
for tex in texs:
|
||||
vs.write(tex + '\n')
|
||||
if soc_from.node.type == 'GROUP':
|
||||
if soc_from.node.node_tree.name.startswith('RetroUVMode'):
|
||||
uv_anim_type = int(soc_from.node.node_tree.name[11:12])
|
||||
if len(soc_from.node.inputs)-1:
|
||||
for s in range(len(soc_from.node.inputs)-1):
|
||||
soc = soc_from.node.inputs[s+1]
|
||||
if len(soc.links):
|
||||
raise RuntimeError("UV Modifier nodes may not have parameter links (default values only)")
|
||||
if soc.type == 'VALUE':
|
||||
uv_anim_args.append(soc.default_value)
|
||||
else:
|
||||
uv_anim_args.append(soc.default_value[0])
|
||||
uv_anim_args.append(soc.default_value[1])
|
||||
soc_from = soc_from.node.inputs[0].links[0].from_socket
|
||||
|
||||
return {'FINISHED'}
|
||||
elif soc_from.node.type == 'UVMAP' or soc_from.node.type == 'TEX_COORD':
|
||||
pass
|
||||
|
||||
else:
|
||||
raise RuntimeError("HMDL texture nodes must have a 'Texture Coordinate', 'UV Map' or 'Group' UV modifier node linked")
|
||||
|
||||
if soc_from.node.type != 'UVMAP' and soc_from.node.type != 'TEX_COORD':
|
||||
raise RuntimeError("Matrix animator nodes must connect to 'Texture Coordinate' or 'UV Map' node")
|
||||
|
||||
|
||||
# Resolve map and matrix index
|
||||
node_label = soc_from.node.label
|
||||
matrix_idx = None
|
||||
if node_label.startswith('MTX_'):
|
||||
matrix_idx = int(node_label[4:])
|
||||
|
||||
if soc_from.name == 'UV':
|
||||
if hasattr(soc_from.node, 'uv_map'):
|
||||
uv_name = soc_from.node.uv_map
|
||||
uv_idx = mesh_obj.data.uv_layers.find(uv_name)
|
||||
if uv_idx == -1:
|
||||
raise RuntimeError('UV Layer "%s" doesn\'t exist' % uv_name)
|
||||
tex_coord_source = uv_idx + 2
|
||||
else:
|
||||
tex_coord_source = 2
|
||||
|
||||
elif soc_from.name == 'Normal':
|
||||
tex_coord_source = 1
|
||||
|
||||
elif soc_from.name == 'Window':
|
||||
tex_coord_source = 0
|
||||
|
||||
else:
|
||||
raise RuntimeError("Only the 'UV', 'Normal' and 'Window' sockets may be used from 'Texture Coordinate' nodes")
|
||||
|
||||
alpha = False
|
||||
if socket.name == 'Alpha':
|
||||
alpha = True
|
||||
|
||||
writebuf(b'PASS')
|
||||
writebuf(pass_fourcc)
|
||||
path = get_texture_path(node.image)
|
||||
writebuf(struct.pack('I', len(path)))
|
||||
writebuf(path.encode())
|
||||
writebuf(struct.pack('B', tex_coord_source))
|
||||
writebuf(struct.pack('B', uv_anim_type))
|
||||
writebuf(struct.pack('I', len(uv_anim_args)))
|
||||
for arg in uv_anim_args:
|
||||
writebuf(struct.pack('f', arg))
|
||||
writebuf(struct.pack('B', alpha))
|
||||
|
||||
else:
|
||||
# Color pass
|
||||
color_set = False
|
||||
if inp.type == 'VALUE':
|
||||
color_set = bool(inp.default_value)
|
||||
else:
|
||||
for comp in inp.default_value:
|
||||
color_set |= bool(comp)
|
||||
|
||||
if color_set:
|
||||
writebuf(b'CLR ')
|
||||
writebuf(pass_fourcc)
|
||||
if inp.type == 'VALUE':
|
||||
writebuf(struct.pack('ffff', inp.default_value, inp.default_value,
|
||||
inp.default_value, inp.default_value))
|
||||
else:
|
||||
writebuf(struct.pack('ffff', inp.default_value[0], inp.default_value[1],
|
||||
inp.default_value[2], inp.default_value[3]))
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import struct, bpy, bmesh
|
||||
from mathutils import Vector
|
||||
from . import HMDLShader, HMDLMesh
|
||||
|
||||
def write_out_material(writebuf, mat, mesh_obj):
|
||||
hecl_str, texs = HMDLShader.shader(mat, mesh_obj)
|
||||
writebuf(struct.pack('I', len(mat.name)))
|
||||
writebuf(mat.name.encode())
|
||||
writebuf(struct.pack('I', len(hecl_str)))
|
||||
writebuf(hecl_str.encode())
|
||||
writebuf(struct.pack('I', len(texs)))
|
||||
for tex in texs:
|
||||
writebuf(struct.pack('I', len(tex)))
|
||||
writebuf(tex.encode())
|
||||
|
||||
writebuf(struct.pack('I', mat.pass_index))
|
||||
|
||||
HMDLShader.write_chunks(writebuf, mat, mesh_obj)
|
||||
|
||||
prop_count = 0
|
||||
for prop in mat.items():
|
||||
@@ -24,22 +20,16 @@ def write_out_material(writebuf, mat, mesh_obj):
|
||||
writebuf(prop[0].encode())
|
||||
writebuf(struct.pack('i', prop[1]))
|
||||
|
||||
transparent = False
|
||||
if mat.game_settings.alpha_blend == 'ALPHA' or mat.game_settings.alpha_blend == 'ALPHA_SORT':
|
||||
transparent = True
|
||||
elif mat.game_settings.alpha_blend == 'ADD':
|
||||
transparent = True
|
||||
writebuf(struct.pack('b', int(transparent)))
|
||||
|
||||
# If this returns true, the material geometry will be split into contiguous faces
|
||||
def should_split_into_contiguous_faces(mat):
|
||||
return False
|
||||
#return mat.game_settings.alpha_blend != 'OPAQUE' and \
|
||||
# 'retro_depth_sort' in mat and mat['retro_depth_sort']
|
||||
blend = 0
|
||||
if mat.blend_method == 'BLEND':
|
||||
blend = 1
|
||||
elif mat.blend_method == 'ADD':
|
||||
blend = 2
|
||||
writebuf(struct.pack('I', blend))
|
||||
|
||||
# Takes a Blender 'Mesh' object (not the datablock)
|
||||
# and performs a one-shot conversion process to HMDL
|
||||
def cook(writebuf, mesh_obj, output_mode, max_skin_banks, use_luv=False):
|
||||
def cook(writebuf, mesh_obj, use_luv=False):
|
||||
if mesh_obj.type != 'MESH':
|
||||
raise RuntimeError("%s is not a mesh" % mesh_obj.name)
|
||||
|
||||
@@ -47,13 +37,13 @@ def cook(writebuf, mesh_obj, output_mode, max_skin_banks, use_luv=False):
|
||||
copy_name = mesh_obj.name + "_hmdltri"
|
||||
copy_mesh = bpy.data.meshes.new(copy_name)
|
||||
copy_obj = bpy.data.objects.new(copy_name, copy_mesh)
|
||||
copy_obj.data = mesh_obj.to_mesh(bpy.context.scene, True, 'RENDER')
|
||||
copy_obj.data = mesh_obj.to_mesh(bpy.context.depsgraph, True)
|
||||
copy_mesh = copy_obj.data
|
||||
copy_obj.scale = mesh_obj.scale
|
||||
bpy.context.scene.objects.link(copy_obj)
|
||||
bpy.context.scene.collection.objects.link(copy_obj)
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.context.scene.objects.active = copy_obj
|
||||
copy_obj.select = True
|
||||
bpy.context.view_layer.objects.active = copy_obj
|
||||
copy_obj.select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris()
|
||||
@@ -77,26 +67,9 @@ def cook(writebuf, mesh_obj, output_mode, max_skin_banks, use_luv=False):
|
||||
pt = copy_obj.bound_box[6]
|
||||
writebuf(struct.pack('fff', pt[0], pt[1], pt[2]))
|
||||
|
||||
# Create master BMesh and VertPool
|
||||
# Create master BMesh
|
||||
bm_master = bmesh.new()
|
||||
bm_master.from_mesh(copy_mesh)
|
||||
vert_pool = HMDLMesh.VertPool(bm_master, rna_loops, use_luv, mesh_obj.material_slots)
|
||||
|
||||
# Tag edges where there are distinctive loops
|
||||
for e in bm_master.edges:
|
||||
e.tag = vert_pool.splitable_edge(e)
|
||||
|
||||
# Sort materials by pass index first
|
||||
sorted_material_idxs = []
|
||||
source_mat_set = set(range(len(mesh_obj.data.materials)))
|
||||
while len(source_mat_set):
|
||||
min_mat_idx = source_mat_set.pop()
|
||||
source_mat_set.add(min_mat_idx)
|
||||
for mat_idx in source_mat_set:
|
||||
if mesh_obj.data.materials[mat_idx].pass_index < mesh_obj.data.materials[min_mat_idx].pass_index:
|
||||
min_mat_idx = mat_idx
|
||||
sorted_material_idxs.append(min_mat_idx)
|
||||
source_mat_set.discard(min_mat_idx)
|
||||
|
||||
# Generate shaders
|
||||
if mesh_obj.data.hecl_material_count > 0:
|
||||
@@ -121,84 +94,14 @@ def cook(writebuf, mesh_obj, output_mode, max_skin_banks, use_luv=False):
|
||||
for mat in mesh_obj.data.materials:
|
||||
write_out_material(writebuf, mat, mesh_obj)
|
||||
|
||||
# Output vert pool
|
||||
vert_pool.write_out(writebuf, mesh_obj.vertex_groups)
|
||||
# Output attribute lists
|
||||
HMDLMesh.write_mesh_attrs(writebuf, bm_master, rna_loops, use_luv, mesh_obj.material_slots)
|
||||
|
||||
dlay = None
|
||||
if len(bm_master.verts.layers.deform):
|
||||
dlay = bm_master.verts.layers.deform[0]
|
||||
|
||||
# Generate material meshes (if opaque)
|
||||
for mat_idx in sorted_material_idxs:
|
||||
mat = mesh_obj.data.materials[mat_idx]
|
||||
if should_split_into_contiguous_faces(mat):
|
||||
continue
|
||||
mat_faces_rem = []
|
||||
for face in bm_master.faces:
|
||||
if face.material_index == mat_idx:
|
||||
mat_faces_rem.append(face)
|
||||
if dlay:
|
||||
mat_faces_rem = HMDLMesh.sort_faces_by_skin_group(dlay, mat_faces_rem)
|
||||
while len(mat_faces_rem):
|
||||
the_list = []
|
||||
skin_slot_set = set()
|
||||
faces = list(mat_faces_rem)
|
||||
for f in faces:
|
||||
if dlay:
|
||||
ret_faces = None
|
||||
for v in f.verts:
|
||||
sg = tuple(sorted(v[dlay].items()))
|
||||
if sg not in skin_slot_set:
|
||||
if max_skin_banks > 0 and len(skin_slot_set) == max_skin_banks:
|
||||
ret_faces = False
|
||||
break
|
||||
skin_slot_set.add(sg)
|
||||
|
||||
if ret_faces == False:
|
||||
break
|
||||
|
||||
the_list.append(f)
|
||||
mat_faces_rem.remove(f)
|
||||
|
||||
writebuf(struct.pack('B', 1))
|
||||
HMDLMesh.write_out_surface(writebuf, output_mode, vert_pool, the_list, mat_idx)
|
||||
|
||||
|
||||
# Generate island meshes (if transparent)
|
||||
for mat_idx in sorted_material_idxs:
|
||||
mat = mesh_obj.data.materials[mat_idx]
|
||||
if not should_split_into_contiguous_faces(mat):
|
||||
continue
|
||||
mat_faces_rem = []
|
||||
for face in bm_master.faces:
|
||||
if face.material_index == mat_idx:
|
||||
mat_faces_rem.append(face)
|
||||
if dlay:
|
||||
mat_faces_rem = HMDLMesh.sort_faces_by_skin_group(dlay, mat_faces_rem)
|
||||
while len(mat_faces_rem):
|
||||
the_list = []
|
||||
skin_slot_set = set()
|
||||
faces = [mat_faces_rem[0]]
|
||||
while len(faces):
|
||||
next_faces = []
|
||||
ret_faces = None
|
||||
for f in faces:
|
||||
ret_faces = HMDLMesh.recursive_faces_islands(dlay, the_list,
|
||||
mat_faces_rem,
|
||||
skin_slot_set,
|
||||
max_skin_banks, f)
|
||||
if ret_faces == False:
|
||||
break
|
||||
next_faces.extend(ret_faces)
|
||||
if ret_faces == False:
|
||||
break
|
||||
faces = next_faces
|
||||
|
||||
writebuf(struct.pack('B', 1))
|
||||
HMDLMesh.write_out_surface(writebuf, output_mode, vert_pool, the_list, mat_idx)
|
||||
|
||||
# No more surfaces
|
||||
writebuf(struct.pack('B', 0))
|
||||
# Vertex groups
|
||||
writebuf(struct.pack('I', len(mesh_obj.vertex_groups)))
|
||||
for vgrp in mesh_obj.vertex_groups:
|
||||
writebuf(struct.pack('I', len(vgrp.name)))
|
||||
writebuf(vgrp.name.encode())
|
||||
|
||||
# Enumerate custom props
|
||||
writebuf(struct.pack('I', len(mesh_obj.keys())))
|
||||
@@ -211,7 +114,7 @@ def cook(writebuf, mesh_obj, output_mode, max_skin_banks, use_luv=False):
|
||||
|
||||
# Delete copied mesh from scene
|
||||
bm_master.free()
|
||||
bpy.context.scene.objects.unlink(copy_obj)
|
||||
#bpy.context.scene.objects.unlink(copy_obj)
|
||||
bpy.data.objects.remove(copy_obj)
|
||||
bpy.data.meshes.remove(copy_mesh)
|
||||
|
||||
@@ -231,13 +134,13 @@ def cookcol(writebuf, mesh_obj):
|
||||
copy_name = mesh_obj.name + "_hmdltri"
|
||||
copy_mesh = bpy.data.meshes.new(copy_name)
|
||||
copy_obj = bpy.data.objects.new(copy_name, copy_mesh)
|
||||
copy_obj.data = mesh_obj.to_mesh(bpy.context.scene, True, 'RENDER')
|
||||
copy_obj.data = mesh_obj.to_mesh(bpy.context.depsgraph, True)
|
||||
copy_mesh = copy_obj.data
|
||||
copy_obj.scale = mesh_obj.scale
|
||||
bpy.context.scene.objects.link(copy_obj)
|
||||
bpy.context.scene.collection.objects.link(copy_obj)
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.context.scene.objects.active = copy_obj
|
||||
copy_obj.select = True
|
||||
bpy.context.view_layer.objects.active = copy_obj
|
||||
copy_obj.select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris()
|
||||
@@ -320,7 +223,7 @@ def cookcol(writebuf, mesh_obj):
|
||||
# Send verts
|
||||
writebuf(struct.pack('I', len(copy_mesh.vertices)))
|
||||
for v in copy_mesh.vertices:
|
||||
xfVert = wmtx * v.co
|
||||
xfVert = wmtx @ v.co
|
||||
writebuf(struct.pack('fff', xfVert[0], xfVert[1], xfVert[2]))
|
||||
|
||||
# Send edges
|
||||
@@ -340,7 +243,7 @@ def cookcol(writebuf, mesh_obj):
|
||||
writebuf(struct.pack('IIIIb', edge_idxs[0], edge_idxs[1], edge_idxs[2], p.material_index, flip))
|
||||
|
||||
# Delete copied mesh from scene
|
||||
bpy.context.scene.objects.unlink(copy_obj)
|
||||
#bpy.context.scene.objects.unlink(copy_obj)
|
||||
bpy.data.objects.remove(copy_obj)
|
||||
bpy.data.meshes.remove(copy_mesh)
|
||||
|
||||
@@ -348,13 +251,13 @@ def cookcol(writebuf, mesh_obj):
|
||||
def draw(layout, context):
|
||||
layout.prop_search(context.scene, 'hecl_mesh_obj', context.scene, 'objects')
|
||||
if not len(context.scene.hecl_mesh_obj):
|
||||
layout.label("Mesh not specified", icon='ERROR')
|
||||
layout.label(text="Mesh not specified", icon='ERROR')
|
||||
elif context.scene.hecl_mesh_obj not in context.scene.objects:
|
||||
layout.label("'"+context.scene.hecl_mesh_obj+"' not in scene", icon='ERROR')
|
||||
layout.label(text="'"+context.scene.hecl_mesh_obj+"' not in scene", icon='ERROR')
|
||||
else:
|
||||
obj = context.scene.objects[context.scene.hecl_mesh_obj]
|
||||
if obj.type != 'MESH':
|
||||
layout.label("'"+context.scene.hecl_mesh_obj+"' not a 'MESH'", icon='ERROR')
|
||||
layout.label(text="'"+context.scene.hecl_mesh_obj+"' not a 'MESH'", icon='ERROR')
|
||||
layout.prop(obj.data, 'hecl_active_material')
|
||||
layout.prop(obj.data, 'hecl_material_count')
|
||||
|
||||
@@ -398,10 +301,8 @@ def register():
|
||||
description='Blender Empty Object to export during HECL\'s cook process')
|
||||
bpy.types.Mesh.hecl_material_count = bpy.props.IntProperty(name='HECL Material Count', default=0, min=0)
|
||||
bpy.types.Mesh.hecl_active_material = bpy.props.IntProperty(name='HECL Active Material', default=0, min=0, update=material_update)
|
||||
bpy.utils.register_class(HMDLShader.hecl_shader_operator)
|
||||
bpy.utils.register_class(hecl_mesh_operator)
|
||||
pass
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(HMDLShader.hecl_shader_operator)
|
||||
bpy.utils.unregister_class(hecl_mesh_operator)
|
||||
pass
|
||||
|
||||
@@ -1,8 +1,233 @@
|
||||
import bpy, struct, bmesh
|
||||
from . import hmdl
|
||||
import bpy, struct, bmesh, operator
|
||||
from mathutils import Vector
|
||||
VertPool = hmdl.HMDLMesh.VertPool
|
||||
strip_next_loop = hmdl.HMDLMesh.strip_next_loop
|
||||
|
||||
# Function to quantize normals to 15-bit precision
|
||||
def quant_norm(n):
|
||||
nf = n.copy()
|
||||
for i in range(3):
|
||||
nf[i] = int(nf[i] * 16384) / 16384.0
|
||||
return nf.freeze()
|
||||
|
||||
# Function to quantize lightmap UVs to 15-bit precision
|
||||
def quant_luv(n):
|
||||
uf = n.copy()
|
||||
for i in range(2):
|
||||
uf[i] = int(uf[i] * 32768) / 32768.0
|
||||
return uf.freeze()
|
||||
|
||||
# Class for building unique sets of vertex attributes for VBO generation
|
||||
class VertPool:
|
||||
|
||||
# Initialize hash-unique index for each available attribute
|
||||
def __init__(self, bm, rna_loops, use_luv, material_slots):
|
||||
self.bm = bm
|
||||
self.rna_loops = rna_loops
|
||||
self.material_slots = material_slots
|
||||
self.pos = {}
|
||||
self.norm = {}
|
||||
self.skin = {}
|
||||
self.color = {}
|
||||
self.uv = {}
|
||||
self.luv = {}
|
||||
self.dlay = None
|
||||
self.clays = []
|
||||
self.ulays = []
|
||||
self.luvlay = None
|
||||
|
||||
dlay = None
|
||||
if len(bm.verts.layers.deform):
|
||||
dlay = bm.verts.layers.deform[0]
|
||||
self.dlay = dlay
|
||||
|
||||
clays = []
|
||||
for cl in range(len(bm.loops.layers.color)):
|
||||
clays.append(bm.loops.layers.color[cl])
|
||||
self.clays = clays
|
||||
|
||||
luvlay = None
|
||||
if use_luv:
|
||||
luvlay = bm.loops.layers.uv[0]
|
||||
self.luvlay = luvlay
|
||||
ulays = []
|
||||
for ul in range(len(bm.loops.layers.uv)):
|
||||
ulays.append(bm.loops.layers.uv[ul])
|
||||
self.ulays = ulays
|
||||
|
||||
# Per-vert pool attributes
|
||||
for v in bm.verts:
|
||||
pf = v.co.copy().freeze()
|
||||
if pf not in self.pos:
|
||||
self.pos[pf] = len(self.pos)
|
||||
if not rna_loops:
|
||||
nf = quant_norm(v.normal)
|
||||
if nf not in self.norm:
|
||||
self.norm[nf] = len(self.norm)
|
||||
if dlay:
|
||||
sf = tuple(sorted(v[dlay].items()))
|
||||
if sf not in self.skin:
|
||||
self.skin[sf] = len(self.skin)
|
||||
|
||||
# Per-loop pool attributes
|
||||
for f in bm.faces:
|
||||
lightmapped = f.material_index < len(material_slots) and \
|
||||
material_slots[f.material_index].material['retro_lightmapped']
|
||||
for l in f.loops:
|
||||
if rna_loops:
|
||||
nf = quant_norm(rna_loops[l.index].normal)
|
||||
if nf not in self.norm:
|
||||
self.norm[nf] = len(self.norm)
|
||||
for cl in range(len(clays)):
|
||||
cf = l[clays[cl]].copy().freeze()
|
||||
if cf not in self.color:
|
||||
self.color[cf] = len(self.color)
|
||||
start_uvlay = 0
|
||||
if use_luv and lightmapped:
|
||||
start_uvlay = 1
|
||||
uf = quant_luv(l[luvlay].uv)
|
||||
if uf not in self.luv:
|
||||
self.luv[uf] = len(self.luv)
|
||||
for ul in range(start_uvlay, len(ulays)):
|
||||
uf = l[ulays[ul]].uv.copy().freeze()
|
||||
if uf not in self.uv:
|
||||
self.uv[uf] = len(self.uv)
|
||||
|
||||
def write_out(self, writebuf, vert_groups):
|
||||
writebuf(struct.pack('I', len(self.pos)))
|
||||
for p in sorted(self.pos.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', p[0][0], p[0][1], p[0][2]))
|
||||
|
||||
writebuf(struct.pack('I', len(self.norm)))
|
||||
for n in sorted(self.norm.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', n[0][0], n[0][1], n[0][2]))
|
||||
|
||||
writebuf(struct.pack('II', len(self.clays), len(self.color)))
|
||||
for c in sorted(self.color.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', c[0][0], c[0][1], c[0][2]))
|
||||
|
||||
writebuf(struct.pack('II', len(self.ulays), len(self.uv)))
|
||||
for u in sorted(self.uv.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('ff', u[0][0], u[0][1]))
|
||||
|
||||
luv_count = 0
|
||||
if self.luvlay is not None:
|
||||
luv_count = 1
|
||||
writebuf(struct.pack('II', luv_count, len(self.luv)))
|
||||
for u in sorted(self.luv.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('ff', u[0][0], u[0][1]))
|
||||
|
||||
writebuf(struct.pack('I', len(vert_groups)))
|
||||
for vgrp in vert_groups:
|
||||
writebuf(struct.pack('I', len(vgrp.name)))
|
||||
writebuf(vgrp.name.encode())
|
||||
|
||||
writebuf(struct.pack('I', len(self.skin)))
|
||||
for s in sorted(self.skin.items(), key=operator.itemgetter(1)):
|
||||
entries = s[0]
|
||||
writebuf(struct.pack('I', len(entries)))
|
||||
if len(entries):
|
||||
total_len = 0.0
|
||||
for ent in entries:
|
||||
total_len += ent[1]
|
||||
for ent in entries:
|
||||
writebuf(struct.pack('If', ent[0], ent[1] / total_len))
|
||||
|
||||
def write_out_map(self, writebuf):
|
||||
writebuf(struct.pack('I', len(self.pos)))
|
||||
for p in sorted(self.pos.items(), key=operator.itemgetter(1)):
|
||||
writebuf(struct.pack('fff', p[0][0], p[0][1], p[0][2]))
|
||||
|
||||
def get_pos_idx(self, vert):
|
||||
pf = vert.co.copy().freeze()
|
||||
return self.pos[pf]
|
||||
|
||||
def get_norm_idx(self, loop):
|
||||
if self.rna_loops:
|
||||
nf = quant_norm(self.rna_loops[loop.index].normal)
|
||||
else:
|
||||
nf = quant_norm(loop.vert.normal)
|
||||
return self.norm[nf]
|
||||
|
||||
def get_skin_idx(self, vert):
|
||||
if not self.dlay:
|
||||
return 0
|
||||
sf = tuple(sorted(vert[self.dlay].items()))
|
||||
return self.skin[sf]
|
||||
|
||||
def get_color_idx(self, loop, cidx):
|
||||
cf = loop[self.clays[cidx]].copy().freeze()
|
||||
return self.color[cf]
|
||||
|
||||
def get_uv_idx(self, loop, uidx):
|
||||
if self.luvlay is not None and uidx == 0:
|
||||
if self.material_slots[loop.face.material_index].material['retro_lightmapped']:
|
||||
uf = quant_luv(loop[self.luvlay].uv)
|
||||
return self.luv[uf]
|
||||
uf = loop[self.ulays[uidx]].uv.copy().freeze()
|
||||
return self.uv[uf]
|
||||
|
||||
def loops_contiguous(self, la, lb):
|
||||
if la.vert != lb.vert:
|
||||
return False
|
||||
if self.get_norm_idx(la) != self.get_norm_idx(lb):
|
||||
return False
|
||||
for cl in range(len(self.clays)):
|
||||
if self.get_color_idx(la, cl) != self.get_color_idx(lb, cl):
|
||||
return False
|
||||
for ul in range(len(self.ulays)):
|
||||
if self.get_uv_idx(la, ul) != self.get_uv_idx(lb, ul):
|
||||
return False
|
||||
return True
|
||||
|
||||
def splitable_edge(self, edge):
|
||||
if len(edge.link_faces) < 2:
|
||||
return False
|
||||
for v in edge.verts:
|
||||
found = None
|
||||
for f in edge.link_faces:
|
||||
for l in f.loops:
|
||||
if l.vert == v:
|
||||
if not found:
|
||||
found = l
|
||||
break
|
||||
else:
|
||||
if not self.loops_contiguous(found, l):
|
||||
return True
|
||||
break
|
||||
return False
|
||||
|
||||
def loop_out(self, writebuf, loop):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('II', self.get_pos_idx(loop.vert), self.get_norm_idx(loop)))
|
||||
for cl in range(len(self.clays)):
|
||||
writebuf(struct.pack('I', self.get_color_idx(loop, cl)))
|
||||
for ul in range(len(self.ulays)):
|
||||
writebuf(struct.pack('I', self.get_uv_idx(loop, ul)))
|
||||
sp = struct.pack('I', self.get_skin_idx(loop.vert))
|
||||
writebuf(sp)
|
||||
|
||||
def null_loop_out(self, writebuf):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('I', 0xffffffff))
|
||||
|
||||
def loop_out_map(self, writebuf, loop):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('I', self.get_pos_idx(loop.vert)))
|
||||
|
||||
def vert_out_map(self, writebuf, vert):
|
||||
writebuf(struct.pack('B', 1))
|
||||
writebuf(struct.pack('I', self.get_pos_idx(vert)))
|
||||
|
||||
|
||||
def strip_next_loop(prev_loop, out_count):
|
||||
if out_count & 1:
|
||||
radial_loop = prev_loop.link_loop_radial_next
|
||||
loop = radial_loop.link_loop_prev
|
||||
return loop, loop
|
||||
else:
|
||||
radial_loop = prev_loop.link_loop_radial_prev
|
||||
loop = radial_loop.link_loop_next
|
||||
return loop.link_loop_next, loop
|
||||
|
||||
|
||||
def recursive_faces_islands(list_out, rem_list, face):
|
||||
if face not in rem_list:
|
||||
@@ -34,13 +259,13 @@ def cook(writebuf, mesh_obj):
|
||||
copy_name = mesh_obj.name + "_hmdltri"
|
||||
copy_mesh = bpy.data.meshes.new(copy_name)
|
||||
copy_obj = bpy.data.objects.new(copy_name, copy_mesh)
|
||||
copy_obj.data = mesh_obj.to_mesh(bpy.context.scene, True, 'RENDER')
|
||||
copy_obj.data = mesh_obj.to_mesh(bpy.context.depsgraph, True)
|
||||
copy_mesh = copy_obj.data
|
||||
copy_obj.scale = mesh_obj.scale
|
||||
bpy.context.scene.objects.link(copy_obj)
|
||||
bpy.context.scene.collection.objects.link(copy_obj)
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.context.scene.objects.active = copy_obj
|
||||
copy_obj.select = True
|
||||
bpy.context.view_layer.objects.active = copy_obj
|
||||
copy_obj.select_set(True)
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import bpy, bgl, sys, bmesh, struct
|
||||
import bpy, gpu, sys, bmesh, struct
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
# Convenience class that automatically brings active edit mesh's face into scope for get/set
|
||||
class HeightRef:
|
||||
@@ -44,14 +45,6 @@ def set_height(self, val):
|
||||
if ar.type == 'VIEW_3D':
|
||||
ar.tag_redraw()
|
||||
|
||||
# Edit panel
|
||||
def draw(layout, context):
|
||||
layout.prop_search(context.scene, 'hecl_path_obj', context.scene, 'objects')
|
||||
layout.operator('view3d.toggle_path_background_wireframe', text='Toggle Background Wire', icon='WIRE')
|
||||
layout.operator('view3d.toggle_path_height_visualization', text='Toggle Height Viz', icon='MANIPUL')
|
||||
if HeightRef().ready:
|
||||
layout.prop(context.window_manager, 'hecl_height_prop', text='Height')
|
||||
|
||||
# Simple AABB class
|
||||
class AABB:
|
||||
def __init__(self):
|
||||
@@ -226,34 +219,39 @@ def cook(writebuf, mesh_obj):
|
||||
writebuf(struct.pack('I', len(ba)))
|
||||
writebuf(ba)
|
||||
|
||||
try:
|
||||
line_shader = gpu.shader.from_builtin('3D_FLAT_COLOR')
|
||||
except:
|
||||
pass
|
||||
|
||||
# Line draw helper
|
||||
def draw_line_3d(color, start, end):
|
||||
bgl.glColor4f(*color)
|
||||
bgl.glBegin(bgl.GL_LINES)
|
||||
bgl.glVertex3f(*start)
|
||||
bgl.glVertex3f(*end)
|
||||
def draw_line_3d(pos_vbo, color_vbo, color, start, end):
|
||||
pos_vbo.append(start)
|
||||
pos_vbo.append(end)
|
||||
color_vbo.append(color)
|
||||
color_vbo.append(color)
|
||||
|
||||
# Draw RNA polygon
|
||||
def draw_poly(p, obj, obj_mtx, height, top_color, side_color):
|
||||
def draw_poly(pos_vbo, color_vbo, p, obj, obj_mtx, height, top_color, side_color):
|
||||
for ek in p.edge_keys:
|
||||
co0 = obj_mtx * obj.data.vertices[ek[0]].co
|
||||
co1 = obj_mtx * obj.data.vertices[ek[1]].co
|
||||
draw_line_3d(top_color, co0 + Vector((0.0, 0.0, height)),
|
||||
co0 = obj_mtx @ obj.data.vertices[ek[0]].co
|
||||
co1 = obj_mtx @ obj.data.vertices[ek[1]].co
|
||||
draw_line_3d(pos_vbo, color_vbo, top_color, co0 + Vector((0.0, 0.0, height)),
|
||||
co1 + Vector((0.0, 0.0, height)))
|
||||
for vk in p.vertices:
|
||||
co = obj_mtx * obj.data.vertices[vk].co
|
||||
draw_line_3d(side_color, co, co + Vector((0.0, 0.0, height)))
|
||||
co = obj_mtx @ obj.data.vertices[vk].co
|
||||
draw_line_3d(pos_vbo, color_vbo, side_color, co, co + Vector((0.0, 0.0, height)))
|
||||
|
||||
# Draw bmesh face
|
||||
def draw_face(f, obj_mtx, height, top_color, side_color):
|
||||
def draw_face(pos_vbo, color_vbo, f, obj_mtx, height, top_color, side_color):
|
||||
for e in f.edges:
|
||||
co0 = obj_mtx * e.verts[0].co
|
||||
co1 = obj_mtx * e.verts[1].co
|
||||
draw_line_3d(top_color, co0 + Vector((0.0, 0.0, height)),
|
||||
co0 = obj_mtx @ e.verts[0].co
|
||||
co1 = obj_mtx @ e.verts[1].co
|
||||
draw_line_3d(pos_vbo, color_vbo, top_color, co0 + Vector((0.0, 0.0, height)),
|
||||
co1 + Vector((0.0, 0.0, height)))
|
||||
for v in f.verts:
|
||||
co = obj_mtx * v.co
|
||||
draw_line_3d(side_color, co, co + Vector((0.0, 0.0, height)))
|
||||
co = obj_mtx @ v.co
|
||||
draw_line_3d(pos_vbo, color_vbo, side_color, co, co + Vector((0.0, 0.0, height)))
|
||||
|
||||
# Viewport hook callback
|
||||
def draw_callback_3d(self, context):
|
||||
@@ -275,6 +273,9 @@ def draw_callback_3d(self, context):
|
||||
if 'Height' in obj.data.polygon_layers_float:
|
||||
height_lay = obj.data.polygon_layers_float['Height']
|
||||
|
||||
pos_vbo = []
|
||||
color_vbo = []
|
||||
|
||||
# Deselected colors
|
||||
top_color = (0.0, 0.0, 1.0, 0.7)
|
||||
side_color = (1.0, 0.0, 0.0, 0.7)
|
||||
@@ -286,33 +287,34 @@ def draw_callback_3d(self, context):
|
||||
if selected:
|
||||
continue
|
||||
height = f[height_lay]
|
||||
draw_face(f, obj_mtx, height, top_color, side_color)
|
||||
draw_face(pos_vbo, color_vbo, f, obj_mtx, height, top_color, side_color)
|
||||
else:
|
||||
for p in obj.data.polygons:
|
||||
height = 1.0
|
||||
if height_lay is not None:
|
||||
height = height_lay.data[p.index].value
|
||||
draw_poly(p, obj, obj_mtx, height, top_color, side_color)
|
||||
bgl.glEnd()
|
||||
draw_poly(pos_vbo, color_vbo, p, obj, obj_mtx, height, top_color, side_color)
|
||||
|
||||
# Selected colors
|
||||
if bm is not None:
|
||||
top_color = (1.0, 0.0, 1.0, 0.7)
|
||||
side_color = (0.0, 1.0, 0.0, 0.7)
|
||||
# Avoid z-fighting on selected lines
|
||||
bgl.glDepthRange(-0.001, 0.999)
|
||||
#bgl.glDepthRange(-0.001, 0.999)
|
||||
for f in bm.faces:
|
||||
if height_lay is not None:
|
||||
selected = f.select
|
||||
if not selected:
|
||||
continue
|
||||
height = f[height_lay]
|
||||
draw_face(f, obj_mtx, height, top_color, side_color)
|
||||
bgl.glEnd()
|
||||
bgl.glDepthRange(0.0, 1.0)
|
||||
draw_face(pos_vbo, color_vbo, f, obj_mtx, height, top_color, side_color)
|
||||
#bgl.glEnd()
|
||||
#bgl.glDepthRange(0.0, 1.0)
|
||||
|
||||
line_shader.bind()
|
||||
batch = batch_for_shader(line_shader, 'LINES', {"pos": pos_vbo, "color": color_vbo})
|
||||
batch.draw(line_shader)
|
||||
|
||||
# restore opengl defaults
|
||||
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
|
||||
|
||||
# Toggle height viz button
|
||||
class PathHeightDrawOperator(bpy.types.Operator):
|
||||
@@ -346,17 +348,29 @@ class PathBackgroundWireframeOperator(bpy.types.Operator):
|
||||
if context.scene.background_set:
|
||||
to_wire = False
|
||||
for o in context.scene.background_set.objects:
|
||||
if o.draw_type != 'WIRE':
|
||||
if o.display_type != 'WIRE':
|
||||
to_wire = True
|
||||
break
|
||||
if to_wire:
|
||||
for o in context.scene.background_set.objects:
|
||||
o.draw_type = 'WIRE'
|
||||
o.display_type = 'WIRE'
|
||||
else:
|
||||
for o in context.scene.background_set.objects:
|
||||
o.draw_type = 'TEXTURED'
|
||||
o.display_type = 'TEXTURED'
|
||||
return {'FINISHED'}
|
||||
|
||||
# Edit panel
|
||||
def draw(layout, context):
|
||||
layout.prop_search(context.scene, 'hecl_path_obj', context.scene, 'objects')
|
||||
layout.operator('view3d.toggle_path_background_wireframe', text='Toggle Background Wire', icon='SHADING_WIRE')
|
||||
if PathHeightDrawOperator._handle_3d:
|
||||
icon = 'HIDE_OFF'
|
||||
else:
|
||||
icon = 'HIDE_ON'
|
||||
layout.operator('view3d.toggle_path_height_visualization', text='Toggle Height Viz', icon=icon)
|
||||
if HeightRef().ready:
|
||||
layout.prop(context.window_manager, 'hecl_height_prop', text='Height')
|
||||
|
||||
# Registration
|
||||
def register():
|
||||
bpy.types.Material.retro_path_idx_mask = bpy.props.IntProperty(name='Retro: Path Index Mask')
|
||||
|
||||
@@ -4,7 +4,6 @@ in an interleaved, sparse array for use by the runtime
|
||||
'''
|
||||
|
||||
import re
|
||||
import hashlib
|
||||
import struct
|
||||
import mathutils
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ def action_type_update(self, context):
|
||||
|
||||
# Actor action class
|
||||
class SACTAction(bpy.types.PropertyGroup):
|
||||
name = bpy.props.StringProperty(name="Action Name")
|
||||
name: bpy.props.StringProperty(name="Action Name")
|
||||
|
||||
# Panel draw
|
||||
def draw(layout, context):
|
||||
@@ -30,8 +30,8 @@ def draw(layout, context):
|
||||
row.template_list("UI_UL_list", "SCENE_UL_SACTActions",
|
||||
actor_data, 'actions', actor_data, 'active_action')
|
||||
col = row.column(align=True)
|
||||
col.operator("scene.sactaction_add", icon="ZOOMIN", text="")
|
||||
col.operator("scene.sactaction_remove", icon="ZOOMOUT", text="")
|
||||
col.operator("scene.sactaction_add", icon="ADD", text="")
|
||||
col.operator("scene.sactaction_remove", icon="REMOVE", text="")
|
||||
|
||||
if len(actor_data.actions) and actor_data.active_action >= 0:
|
||||
action = actor_data.actions[actor_data.active_action]
|
||||
@@ -48,7 +48,7 @@ def draw(layout, context):
|
||||
|
||||
# Validate
|
||||
if linked_action is None:
|
||||
layout.label("Source action not set", icon='ERROR')
|
||||
layout.label(text="Source action not set", icon='ERROR')
|
||||
else:
|
||||
#layout.prop(linked_action, 'hecl_index', text="Index")
|
||||
#layout.prop(linked_action, 'hecl_anim_props', text="Props")
|
||||
|
||||
@@ -9,28 +9,26 @@ def active_subtype_update(self, context):
|
||||
|
||||
# Actor subtype overlay class
|
||||
class SACTSubtypeOverlay(bpy.types.PropertyGroup):
|
||||
name = bpy.props.StringProperty(name="Overlay Name")
|
||||
linked_mesh = bpy.props.StringProperty(name="Linked Mesh Object Source", update=active_subtype_update)
|
||||
show_overlay = bpy.props.BoolProperty(name="Show Overlay Mesh", update=active_subtype_update)
|
||||
name: bpy.props.StringProperty(name="Overlay Name")
|
||||
linked_mesh: bpy.props.StringProperty(name="Linked Mesh Object Source", update=active_subtype_update)
|
||||
show_overlay: bpy.props.BoolProperty(name="Show Overlay Mesh", update=active_subtype_update)
|
||||
|
||||
# Actor attachment class
|
||||
class SACTAttachment(bpy.types.PropertyGroup):
|
||||
name = bpy.props.StringProperty(name="Attachment Name")
|
||||
linked_armature = bpy.props.StringProperty(name="Linked Armature Object Source", update=active_subtype_update)
|
||||
linked_mesh = bpy.props.StringProperty(name="Linked Mesh Object Source", update=active_subtype_update)
|
||||
show_attachment = bpy.props.BoolProperty(name="Show Attachment Mesh", update=active_subtype_update)
|
||||
name: bpy.props.StringProperty(name="Attachment Name")
|
||||
linked_armature: bpy.props.StringProperty(name="Linked Armature Object Source", update=active_subtype_update)
|
||||
linked_mesh: bpy.props.StringProperty(name="Linked Mesh Object Source", update=active_subtype_update)
|
||||
show_attachment: bpy.props.BoolProperty(name="Show Attachment Mesh", update=active_subtype_update)
|
||||
|
||||
# Actor subtype class
|
||||
class SACTSubtype(bpy.types.PropertyGroup):
|
||||
name = bpy.props.StringProperty(name="Actor Mesh Name")
|
||||
linked_armature = bpy.props.StringProperty(name="Linked Armature Object Source", update=active_subtype_update)
|
||||
linked_mesh = bpy.props.StringProperty(name="Linked Mesh Object Source", update=active_subtype_update)
|
||||
show_mesh = bpy.props.BoolProperty(name="Show Mesh", default=True, update=active_subtype_update)
|
||||
name: bpy.props.StringProperty(name="Actor Mesh Name")
|
||||
linked_armature: bpy.props.StringProperty(name="Linked Armature Object Source", update=active_subtype_update)
|
||||
linked_mesh: bpy.props.StringProperty(name="Linked Mesh Object Source", update=active_subtype_update)
|
||||
show_mesh: bpy.props.BoolProperty(name="Show Mesh", default=True, update=active_subtype_update)
|
||||
|
||||
overlays =\
|
||||
bpy.props.CollectionProperty(type=SACTSubtypeOverlay, name="Subtype Overlay List")
|
||||
active_overlay =\
|
||||
bpy.props.IntProperty(name="Active Subtype Overlay", default=0, update=active_subtype_update)
|
||||
overlays: bpy.props.CollectionProperty(type=SACTSubtypeOverlay, name="Subtype Overlay List")
|
||||
active_overlay: bpy.props.IntProperty(name="Active Subtype Overlay", default=0, update=active_subtype_update)
|
||||
|
||||
# Panel draw
|
||||
def draw(layout, context):
|
||||
@@ -45,8 +43,8 @@ def draw(layout, context):
|
||||
row.template_list("UI_UL_list", "SCENE_UL_SACTSubtypes",
|
||||
actor_data, 'subtypes', actor_data, 'active_subtype')
|
||||
col = row.column(align=True)
|
||||
col.operator("scene.sactsubtype_add", icon="ZOOMIN", text="")
|
||||
col.operator("scene.sactsubtype_remove", icon="ZOOMOUT", text="")
|
||||
col.operator("scene.sactsubtype_add", icon="ADD", text="")
|
||||
col.operator("scene.sactsubtype_remove", icon="REMOVE", text="")
|
||||
|
||||
if len(actor_data.subtypes) and actor_data.active_subtype >= 0:
|
||||
subtype = actor_data.subtypes[actor_data.active_subtype]
|
||||
@@ -67,9 +65,9 @@ def draw(layout, context):
|
||||
|
||||
# Validate
|
||||
if linked_armature is None:
|
||||
layout.label("Source armature not set", icon='ERROR')
|
||||
layout.label(text="Source armature not set", icon='ERROR')
|
||||
elif linked_armature is not None and linked_armature.type != 'ARMATURE':
|
||||
layout.label("Source armature is not an 'ARMATURE'", icon='ERROR')
|
||||
layout.label(text="Source armature is not an 'ARMATURE'", icon='ERROR')
|
||||
|
||||
|
||||
# Link external mesh search
|
||||
@@ -80,13 +78,13 @@ def draw(layout, context):
|
||||
layout.prop(subtype, 'show_mesh', text="Show Mesh")
|
||||
|
||||
# Mesh overlays
|
||||
layout.label("Overlay Meshes:")
|
||||
layout.label(text="Overlay Meshes:")
|
||||
row = layout.row()
|
||||
row.template_list("UI_UL_list", "SCENE_UL_SACTSubtypeOverlays",
|
||||
subtype, 'overlays', subtype, 'active_overlay')
|
||||
col = row.column(align=True)
|
||||
col.operator("scene.sactsubtypeoverlay_add", icon="ZOOMIN", text="")
|
||||
col.operator("scene.sactsubtypeoverlay_remove", icon="ZOOMOUT", text="")
|
||||
col.operator("scene.sactsubtypeoverlay_add", icon="ADD", text="")
|
||||
col.operator("scene.sactsubtypeoverlay_remove", icon="REMOVE", text="")
|
||||
|
||||
overlay_mesh = None
|
||||
if len(subtype.overlays) and subtype.active_overlay >= 0:
|
||||
@@ -98,13 +96,13 @@ def draw(layout, context):
|
||||
layout.prop(overlay, 'show_overlay', text="Show Overlay")
|
||||
|
||||
# Mesh attachments
|
||||
layout.label("Attachment Meshes:")
|
||||
layout.label(text="Attachment Meshes:")
|
||||
row = layout.row()
|
||||
row.template_list("UI_UL_list", "SCENE_UL_SACTAttachments",
|
||||
actor_data, 'attachments', actor_data, 'active_attachment')
|
||||
col = row.column(align=True)
|
||||
col.operator("scene.sactattachment_add", icon="ZOOMIN", text="")
|
||||
col.operator("scene.sactattachment_remove", icon="ZOOMOUT", text="")
|
||||
col.operator("scene.sactattachment_add", icon="ADD", text="")
|
||||
col.operator("scene.sactattachment_remove", icon="REMOVE", text="")
|
||||
|
||||
attachment_armature = linked_armature
|
||||
attachment_mesh = None
|
||||
@@ -121,27 +119,27 @@ def draw(layout, context):
|
||||
|
||||
# Validate
|
||||
if linked_mesh is None:
|
||||
layout.label("Source mesh not set", icon='ERROR')
|
||||
layout.label(text="Source mesh not set", icon='ERROR')
|
||||
elif linked_mesh.type != 'MESH':
|
||||
layout.label("Source mesh not 'MESH'", icon='ERROR')
|
||||
layout.label(text="Source mesh not 'MESH'", icon='ERROR')
|
||||
elif linked_armature is not None and linked_mesh not in linked_armature.children:
|
||||
layout.label(linked_mesh.name+" not a child of "+linked_armature.name, icon='ERROR')
|
||||
elif linked_mesh.parent_type != 'ARMATURE':
|
||||
layout.label("Source mesh not 'ARMATURE' parent type", icon='ERROR')
|
||||
layout.label(text="Source mesh not 'ARMATURE' parent type", icon='ERROR')
|
||||
|
||||
if overlay_mesh:
|
||||
if overlay_mesh.type != 'MESH':
|
||||
layout.label("Overlay mesh not 'MESH'", icon='ERROR')
|
||||
layout.label(text="Overlay mesh not 'MESH'", icon='ERROR')
|
||||
elif overlay_mesh.parent_type != 'ARMATURE':
|
||||
layout.label("Overlay mesh not 'ARMATURE' parent type", icon='ERROR')
|
||||
layout.label(text="Overlay mesh not 'ARMATURE' parent type", icon='ERROR')
|
||||
|
||||
if attachment_mesh:
|
||||
if attachment_mesh.type != 'MESH':
|
||||
layout.label("Attachment mesh not 'MESH'", icon='ERROR')
|
||||
layout.label(text="Attachment mesh not 'MESH'", icon='ERROR')
|
||||
elif attachment_armature is not None and attachment_mesh not in attachment_armature.children:
|
||||
layout.label(attachment_mesh.name+" not a child of "+attachment_armature.name, icon='ERROR')
|
||||
elif attachment_mesh.parent_type != 'ARMATURE':
|
||||
layout.label("Attachment mesh not 'ARMATURE' parent type", icon='ERROR')
|
||||
layout.label(text="Attachment mesh not 'ARMATURE' parent type", icon='ERROR')
|
||||
|
||||
|
||||
# Subtype 'add' operator
|
||||
@@ -195,6 +193,17 @@ class SACTSubtype_remove(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def parent_armature(mesh_obj, arm_obj):
|
||||
mesh_obj.parent = None
|
||||
for mod in mesh_obj.modifiers:
|
||||
if mod.type == 'ARMATURE':
|
||||
mod.object = arm_obj
|
||||
return
|
||||
mod = mesh_obj.modifiers.new('Parent', 'ARMATURE')
|
||||
mod.object = arm_obj
|
||||
#mesh_obj.parent = arm_obj
|
||||
#mesh_obj.parent_type = 'ARMATURE'
|
||||
|
||||
# Subtype 'load' operator
|
||||
class SACTSubtype_load(bpy.types.Operator):
|
||||
bl_idname = "scene.sactsubtype_load"
|
||||
@@ -222,49 +231,46 @@ class SACTSubtype_load(bpy.types.Operator):
|
||||
# Hide armature children
|
||||
for object in linked_armature.children:
|
||||
if object.name in context.scene.objects:
|
||||
object.hide = True
|
||||
object.hide_set(True)
|
||||
|
||||
# Hide all meshes (incl overlays)
|
||||
for subtype_data in actor_data.subtypes:
|
||||
if subtype_data.linked_mesh in bpy.data.objects:
|
||||
mesh = bpy.data.objects[subtype_data.linked_mesh]
|
||||
if mesh.name in context.scene.objects:
|
||||
mesh.hide = True
|
||||
mesh.hide_set(True)
|
||||
for overlay in subtype_data.overlays:
|
||||
if overlay.linked_mesh in bpy.data.objects:
|
||||
mesh = bpy.data.objects[overlay.linked_mesh]
|
||||
if mesh.name in context.scene.objects:
|
||||
mesh.hide = True
|
||||
mesh.hide_set(True)
|
||||
|
||||
# Hide/Show selected attachment meshes
|
||||
for attachment in actor_data.attachments:
|
||||
if attachment.linked_mesh in bpy.data.objects:
|
||||
mesh_obj = bpy.data.objects[attachment.linked_mesh]
|
||||
if mesh_obj.name in context.scene.objects:
|
||||
mesh_obj.hide = not attachment.show_attachment
|
||||
mesh_obj.hide_set(not attachment.show_attachment)
|
||||
attachment_armature = linked_armature
|
||||
if attachment.linked_armature in bpy.data.objects:
|
||||
attachment_armature = bpy.data.objects[attachment.linked_armature]
|
||||
if mesh_obj != attachment_armature:
|
||||
mesh_obj.parent = attachment_armature
|
||||
mesh_obj.parent_type = 'ARMATURE'
|
||||
parent_armature(mesh_obj, attachment_armature)
|
||||
|
||||
# Show only the chosen subtype (and selected overlays)
|
||||
if subtype.linked_mesh in bpy.data.objects:
|
||||
mesh_obj = bpy.data.objects[subtype.linked_mesh]
|
||||
if subtype.show_mesh:
|
||||
mesh_obj.hide = False
|
||||
mesh_obj.hide_set(False)
|
||||
if mesh_obj != linked_armature:
|
||||
mesh_obj.parent = linked_armature
|
||||
mesh_obj.parent_type = 'ARMATURE'
|
||||
parent_armature(mesh_obj, linked_armature)
|
||||
for overlay in subtype.overlays:
|
||||
if overlay.linked_mesh in bpy.data.objects:
|
||||
mesh_obj = bpy.data.objects[overlay.linked_mesh]
|
||||
if overlay.show_overlay:
|
||||
mesh_obj.hide = False
|
||||
mesh_obj.hide_set(False)
|
||||
if mesh_obj != linked_armature:
|
||||
mesh_obj.parent = linked_armature
|
||||
mesh_obj.parent_type = 'ARMATURE'
|
||||
parent_armature(mesh_obj, linked_armature)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
from . import SACTSubtype, SACTAction, ANIM
|
||||
from .. import hmdl
|
||||
|
||||
import bpy
|
||||
import bpy.path
|
||||
import re
|
||||
import os.path
|
||||
import posixpath
|
||||
import struct
|
||||
from mathutils import Vector, Quaternion, Euler
|
||||
|
||||
# Actor data class
|
||||
class SACTData(bpy.types.PropertyGroup):
|
||||
|
||||
subtypes =\
|
||||
bpy.props.CollectionProperty(type=SACTSubtype.SACTSubtype, name="Actor Subtype List")
|
||||
active_subtype =\
|
||||
bpy.props.IntProperty(name="Active Actor Subtype", default=0, update=SACTSubtype.active_subtype_update)
|
||||
show_subtypes =\
|
||||
bpy.props.BoolProperty()
|
||||
subtypes: bpy.props.CollectionProperty(type=SACTSubtype.SACTSubtype, name="Actor Subtype List")
|
||||
active_subtype: bpy.props.IntProperty(name="Active Actor Subtype", default=0, update=SACTSubtype.active_subtype_update)
|
||||
show_subtypes: bpy.props.BoolProperty()
|
||||
|
||||
attachments = \
|
||||
bpy.props.CollectionProperty(type=SACTSubtype.SACTAttachment, name="Attachment List")
|
||||
active_attachment = \
|
||||
bpy.props.IntProperty(name="Active Attachment", default=0, update=SACTSubtype.active_subtype_update)
|
||||
attachments: bpy.props.CollectionProperty(type=SACTSubtype.SACTAttachment, name="Attachment List")
|
||||
active_attachment: bpy.props.IntProperty(name="Active Attachment", default=0, update=SACTSubtype.active_subtype_update)
|
||||
|
||||
actions =\
|
||||
bpy.props.CollectionProperty(type=SACTAction.SACTAction, name="Actor Action List")
|
||||
active_action =\
|
||||
bpy.props.IntProperty(name="Active Actor Action", default=0, update=SACTAction.active_action_update)
|
||||
show_actions =\
|
||||
bpy.props.BoolProperty()
|
||||
actions: bpy.props.CollectionProperty(type=SACTAction.SACTAction, name="Actor Action List")
|
||||
active_action: bpy.props.IntProperty(name="Active Actor Action", default=0, update=SACTAction.active_action_update)
|
||||
show_actions: bpy.props.BoolProperty()
|
||||
|
||||
# Regex RNA path matchers
|
||||
scale_matcher = re.compile(r'pose.bones\["(\S+)"\].scale')
|
||||
|
||||
@@ -3,7 +3,7 @@ from bpy.app.handlers import persistent
|
||||
from mathutils import Quaternion, Color
|
||||
import math
|
||||
import os.path
|
||||
from .. import Nodegrid, swld
|
||||
from .. import swld
|
||||
|
||||
# Preview update func (for lighting preview)
|
||||
def preview_update(self, context):
|
||||
@@ -13,30 +13,26 @@ def preview_update(self, context):
|
||||
# Original Lightmaps
|
||||
if area_data.lightmap_mode == 'ORIGINAL':
|
||||
for material in bpy.data.materials:
|
||||
if material.hecl_lightmap:
|
||||
material.use_shadeless = False
|
||||
|
||||
if material.hecl_lightmap and 'Lightmap' in material.node_tree.nodes:
|
||||
lm_node = material.node_tree.nodes['Lightmap']
|
||||
# Reference original game lightmaps
|
||||
if material.hecl_lightmap in bpy.data.textures:
|
||||
img_name = material.hecl_lightmap
|
||||
if img_name in bpy.data.images:
|
||||
bpy.data.textures[material.hecl_lightmap].image = bpy.data.images[img_name]
|
||||
else:
|
||||
bpy.data.textures[material.hecl_lightmap].image = None
|
||||
if material.hecl_lightmap in bpy.data.images:
|
||||
lm_node.image = bpy.data.images[material.hecl_lightmap]
|
||||
else:
|
||||
lm_node.image = None
|
||||
|
||||
# Cycles Lightmaps
|
||||
elif area_data.lightmap_mode == 'CYCLES':
|
||||
for material in bpy.data.materials:
|
||||
if material.hecl_lightmap:
|
||||
material.use_shadeless = False
|
||||
|
||||
if material.hecl_lightmap and 'Lightmap' in material.node_tree.nodes:
|
||||
lm_node = material.node_tree.nodes['Lightmap']
|
||||
# Reference newly-generated lightmaps
|
||||
if material.hecl_lightmap in bpy.data.textures:
|
||||
img_name = material.hecl_lightmap + '_CYCLES'
|
||||
if img_name in bpy.data.images:
|
||||
bpy.data.textures[material.hecl_lightmap].image = bpy.data.images[img_name]
|
||||
else:
|
||||
bpy.data.textures[material.hecl_lightmap].image = None
|
||||
img_name = material.hecl_lightmap + '_CYCLES'
|
||||
if img_name in bpy.data.images:
|
||||
lm_node.image = bpy.data.images[img_name]
|
||||
else:
|
||||
lm_node.image = None
|
||||
|
||||
# White Lightmaps
|
||||
elif area_data.lightmap_mode == 'NONE':
|
||||
img_name = 'NONE'
|
||||
@@ -51,13 +47,11 @@ def preview_update(self, context):
|
||||
img.file_format = 'PNG'
|
||||
|
||||
for material in bpy.data.materials:
|
||||
if material.hecl_lightmap:
|
||||
material.use_shadeless = False
|
||||
|
||||
if material.hecl_lightmap and 'Lightmap' in material.node_tree.nodes:
|
||||
lm_node = material.node_tree.nodes['Lightmap']
|
||||
# Reference NONE
|
||||
if material.hecl_lightmap in bpy.data.textures:
|
||||
bpy.data.textures[material.hecl_lightmap].image = img
|
||||
|
||||
lm_node.image = img
|
||||
|
||||
|
||||
# Update lightmap output-resolution
|
||||
def set_lightmap_resolution(self, context):
|
||||
@@ -153,17 +147,17 @@ def set_adjacent_area(self, context):
|
||||
|
||||
adjacent = dock_idx >= 0
|
||||
if len(context.scene.render.layers):
|
||||
context.scene.render.layers[0].use_sky = not adjacent
|
||||
context.scene.view_layers[0].use_sky = not adjacent
|
||||
|
||||
# Remove linked lamps and show/hide locals
|
||||
for obj in bpy.data.objects:
|
||||
if obj.library is not None and (obj.type == 'LAMP' or obj.type == 'MESH'):
|
||||
if obj.library is not None and (obj.type == 'LIGHT' or obj.type == 'MESH'):
|
||||
try:
|
||||
context.scene.objects.unlink(obj)
|
||||
context.scene.collection.children.unlink(obj)
|
||||
except:
|
||||
pass
|
||||
continue
|
||||
if obj.type == 'LAMP':
|
||||
if obj.type == 'LIGHT':
|
||||
obj.hide_render = adjacent
|
||||
|
||||
# Remove linked scenes
|
||||
@@ -200,8 +194,8 @@ def set_adjacent_area(self, context):
|
||||
bpy.data.scenes.remove(other_scene)
|
||||
return
|
||||
for obj in other_scene.objects:
|
||||
if (obj.type == 'LAMP' or obj.type == 'MESH') and obj.layers[0]:
|
||||
context.scene.objects.link(obj)
|
||||
if (obj.type == 'LIGHT' or obj.type == 'MESH') and obj.layers[0]:
|
||||
context.scene.collection.objects.link(obj)
|
||||
obj.hide_render = False
|
||||
|
||||
# Ensure filepaths target the current dock index
|
||||
@@ -212,7 +206,7 @@ def set_adjacent_area(self, context):
|
||||
|
||||
# Area data class
|
||||
class SREAData(bpy.types.PropertyGroup):
|
||||
lightmap_resolution = bpy.props.EnumProperty(name="HECL Area Lightmap Resolution",
|
||||
lightmap_resolution: bpy.props.EnumProperty(name="HECL Area Lightmap Resolution",
|
||||
description="Set square resolution to use when rendering new lightmaps",
|
||||
items=[
|
||||
('256', "256", "256x256 (original quality)"),
|
||||
@@ -223,7 +217,7 @@ class SREAData(bpy.types.PropertyGroup):
|
||||
update=set_lightmap_resolution,
|
||||
default='1024')
|
||||
|
||||
lightmap_mode = bpy.props.EnumProperty(name="HECL Area Lightmap Mode",
|
||||
lightmap_mode: bpy.props.EnumProperty(name="HECL Area Lightmap Mode",
|
||||
description="Simple way to manipulate all lightmap-using materials",
|
||||
items=[
|
||||
('NONE', "None", "Pure white lightmaps"),
|
||||
@@ -232,7 +226,7 @@ class SREAData(bpy.types.PropertyGroup):
|
||||
update=preview_update,
|
||||
default='ORIGINAL')
|
||||
|
||||
adjacent_area = bpy.props.IntProperty(name="HECL Adjacent Area Lightmap",
|
||||
adjacent_area: bpy.props.IntProperty(name="HECL Adjacent Area Lightmap",
|
||||
description="Dock index of adjacent area to render, or -1 for local lights",
|
||||
update=set_adjacent_area,
|
||||
default=-1,
|
||||
@@ -296,179 +290,6 @@ def get_de_sockets(chain):
|
||||
|
||||
return found_mul, found_add
|
||||
|
||||
# Get texture node from node
|
||||
def tex_node_from_node(node):
|
||||
if node.type == 'TEXTURE':
|
||||
return node
|
||||
elif node.type == 'MIX_RGB':
|
||||
if node.inputs[1].is_linked and node.inputs[1].links[0].from_node.type == 'TEXTURE':
|
||||
return node.inputs[1].links[0].from_node
|
||||
if node.inputs[2].is_linked and node.inputs[2].links[0].from_node.type == 'TEXTURE':
|
||||
return node.inputs[2].links[0].from_node
|
||||
return None
|
||||
|
||||
# Delete existing cycles nodes and convert from GLSL nodes
|
||||
CYCLES_TYPES = {'OUTPUT_MATERIAL', 'ADD_SHADER', 'BSDF_DIFFUSE', 'BSDF_TRANSPARENT',
|
||||
'EMISSION', 'MIX_SHADER', 'TEX_IMAGE'}
|
||||
def initialize_nodetree_cycles(mat, area_data):
|
||||
nt = mat.node_tree
|
||||
to_remove = set()
|
||||
for node in nt.nodes:
|
||||
if node.type in CYCLES_TYPES:
|
||||
to_remove.add(node)
|
||||
if node.parent:
|
||||
to_remove.add(node.parent)
|
||||
for node in to_remove:
|
||||
nt.nodes.remove(node)
|
||||
|
||||
gridder = Nodegrid.Nodegrid(nt, cycles=True)
|
||||
|
||||
if mat.hecl_lightmap and not mat.library:
|
||||
# Get name of lightmap texture
|
||||
if mat.hecl_lightmap in bpy.data.textures:
|
||||
img_name = mat.hecl_lightmap
|
||||
if img_name in bpy.data.images:
|
||||
bpy.data.textures[mat.hecl_lightmap].image = bpy.data.images[img_name]
|
||||
else:
|
||||
bpy.data.textures[mat.hecl_lightmap].image = None
|
||||
|
||||
# Get image already established or make new one
|
||||
new_image = make_or_load_cycles_image(mat, area_data)
|
||||
|
||||
image_out_node = nt.nodes.new('ShaderNodeTexImage')
|
||||
image_out_node.name = 'CYCLES_OUT'
|
||||
gridder.place_node(image_out_node, 3)
|
||||
image_out_node.image = new_image
|
||||
|
||||
if mat.game_settings.alpha_blend == 'ADD':
|
||||
transp = nt.nodes.new('ShaderNodeBsdfTransparent')
|
||||
gridder.place_node(transp, 2)
|
||||
material_output = nt.nodes.new('ShaderNodeOutputMaterial')
|
||||
gridder.place_node(material_output, 3)
|
||||
nt.links.new(transp.outputs[0], material_output.inputs[0])
|
||||
|
||||
elif mat.game_settings.alpha_blend == 'ALPHA':
|
||||
diffuse = nt.nodes.new('ShaderNodeBsdfDiffuse')
|
||||
gridder.place_node(diffuse, 2)
|
||||
transp = nt.nodes.new('ShaderNodeBsdfTransparent')
|
||||
gridder.place_node(transp, 2)
|
||||
mix_shader = nt.nodes.new('ShaderNodeMixShader')
|
||||
gridder.place_node(mix_shader, 2)
|
||||
nt.links.new(transp.outputs[0], mix_shader.inputs[1])
|
||||
nt.links.new(diffuse.outputs[0], mix_shader.inputs[2])
|
||||
material_output = nt.nodes.new('ShaderNodeOutputMaterial')
|
||||
gridder.place_node(material_output, 3)
|
||||
nt.links.new(mix_shader.outputs[0], material_output.inputs[0])
|
||||
|
||||
# Classify connected transparent textures
|
||||
chain = recursive_build_material_chain(nt.nodes['Output'])
|
||||
if chain:
|
||||
diffuse_soc, emissive_soc = get_de_sockets(chain)
|
||||
tex_node = tex_node_from_node(diffuse_soc.node)
|
||||
if tex_node and tex_node.inputs[0].links[0].from_socket.name == 'UV':
|
||||
diffuse_image_node = nt.nodes.new('ShaderNodeTexImage')
|
||||
gridder.place_node(diffuse_image_node, 1)
|
||||
diffuse_image_node.image = tex_node.texture.image
|
||||
mixrgb_node = nt.nodes.new('ShaderNodeMixRGB')
|
||||
gridder.place_node(mixrgb_node, 1)
|
||||
mixrgb_node.inputs[1].default_value = (1.0,1.0,1.0,1.0)
|
||||
mapping = nt.nodes.new('ShaderNodeUVMap')
|
||||
gridder.place_node(mapping, 1)
|
||||
mapping.uv_map = tex_node.inputs[0].links[0].from_node.uv_layer
|
||||
nt.links.new(mapping.outputs[0], diffuse_image_node.inputs[0])
|
||||
light_path = nt.nodes.new('ShaderNodeLightPath')
|
||||
gridder.place_node(light_path, 2)
|
||||
mixrgb_reflect = nt.nodes.new('ShaderNodeMixRGB')
|
||||
gridder.place_node(mixrgb_reflect, 2)
|
||||
nt.links.new(light_path.outputs['Is Reflection Ray'], mixrgb_reflect.inputs[0])
|
||||
mixrgb_reflect.inputs[1].default_value = (1.0,1.0,1.0,1.0)
|
||||
nt.links.new(diffuse_image_node.outputs[0], mixrgb_reflect.inputs[2])
|
||||
nt.links.new(mixrgb_reflect.outputs[0], diffuse.inputs[0])
|
||||
nt.links.new(mixrgb_reflect.outputs[0], mixrgb_node.inputs[2])
|
||||
if nt.nodes['Output'].inputs[1].is_linked:
|
||||
nt.links.new(nt.nodes['Output'].inputs[1].links[0].from_socket, mix_shader.inputs[0])
|
||||
nt.links.new(nt.nodes['Output'].inputs[1].links[0].from_socket, mixrgb_node.inputs[0])
|
||||
nt.links.new(mixrgb_node.outputs[0], transp.inputs[0])
|
||||
|
||||
else:
|
||||
# Classify connected opaque textures
|
||||
chain = recursive_build_material_chain(nt.nodes['Output'])
|
||||
if chain:
|
||||
diffuse = None
|
||||
emissive = None
|
||||
diffuse_soc, emissive_soc = get_de_sockets(chain)
|
||||
if diffuse_soc:
|
||||
tex_node = tex_node_from_node(diffuse_soc.node)
|
||||
if tex_node and tex_node.inputs[0].links[0].from_socket.name == 'UV':
|
||||
diffuse_image_node = nt.nodes.new('ShaderNodeTexImage')
|
||||
gridder.place_node(diffuse_image_node, 1)
|
||||
diffuse_image_node.image = tex_node.texture.image
|
||||
mapping = nt.nodes.new('ShaderNodeUVMap')
|
||||
gridder.place_node(mapping, 1)
|
||||
mapping.uv_map = tex_node.inputs[0].links[0].from_node.uv_layer
|
||||
nt.links.new(mapping.outputs[0], diffuse_image_node.inputs[0])
|
||||
light_path = nt.nodes.new('ShaderNodeLightPath')
|
||||
gridder.place_node(light_path, 2)
|
||||
mixrgb_reflect = nt.nodes.new('ShaderNodeMixRGB')
|
||||
gridder.place_node(mixrgb_reflect, 2)
|
||||
nt.links.new(light_path.outputs['Is Reflection Ray'], mixrgb_reflect.inputs[0])
|
||||
mixrgb_reflect.inputs[1].default_value = (1.0,1.0,1.0,1.0)
|
||||
nt.links.new(diffuse_image_node.outputs[0], mixrgb_reflect.inputs[2])
|
||||
diffuse = nt.nodes.new('ShaderNodeBsdfDiffuse')
|
||||
gridder.place_node(diffuse, 2)
|
||||
nt.links.new(mixrgb_reflect.outputs[0], diffuse.inputs[0])
|
||||
else:
|
||||
diffuse = nt.nodes.new('ShaderNodeBsdfDiffuse')
|
||||
gridder.place_node(diffuse, 2)
|
||||
if emissive_soc:
|
||||
tex_node = tex_node_from_node(emissive_soc.node)
|
||||
if tex_node and tex_node.inputs[0].links[0].from_socket.name == 'UV':
|
||||
emissive_image_node = nt.nodes.new('ShaderNodeTexImage')
|
||||
gridder.place_node(emissive_image_node, 1)
|
||||
emissive_image_node.image = tex_node.texture.image
|
||||
mapping = nt.nodes.new('ShaderNodeUVMap')
|
||||
gridder.place_node(mapping, 1)
|
||||
mapping.uv_map = tex_node.inputs[0].links[0].from_node.uv_layer
|
||||
nt.links.new(mapping.outputs[0], emissive_image_node.inputs[0])
|
||||
emissive = nt.nodes.new('ShaderNodeEmission')
|
||||
gridder.place_node(emissive, 2)
|
||||
nt.links.new(emissive_image_node.outputs[0], emissive.inputs[0])
|
||||
|
||||
material_output = nt.nodes.new('ShaderNodeOutputMaterial')
|
||||
gridder.place_node(material_output, 3)
|
||||
if diffuse and emissive:
|
||||
shader_add = nt.nodes.new('ShaderNodeAddShader')
|
||||
gridder.place_node(shader_add, 2)
|
||||
nt.links.new(diffuse.outputs[0], shader_add.inputs[0])
|
||||
nt.links.new(emissive.outputs[0], shader_add.inputs[1])
|
||||
nt.links.new(shader_add.outputs[0], material_output.inputs[0])
|
||||
elif diffuse_soc:
|
||||
nt.links.new(diffuse.outputs[0], material_output.inputs[0])
|
||||
elif emissive_soc:
|
||||
nt.links.new(emissive.outputs[0], material_output.inputs[0])
|
||||
|
||||
# Lightmap setup operator
|
||||
class SREAInitializeCycles(bpy.types.Operator):
|
||||
bl_idname = "scene.hecl_area_initialize_cycles"
|
||||
bl_label = "HECL Initialize Cycles"
|
||||
bl_description = "Initialize Cycles nodes for lightmap baking (WILL DELETE EXISTING CYCLES NODES!)"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None and context.scene.hecl_type == 'AREA'
|
||||
|
||||
def execute(self, context):
|
||||
area_data = context.scene.hecl_srea_data
|
||||
|
||||
# Iterate materials and setup cycles
|
||||
for mat in bpy.data.materials:
|
||||
if mat.use_nodes:
|
||||
initialize_nodetree_cycles(mat, area_data)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
|
||||
# Lookup the directory name of other area via dock link
|
||||
def get_other_area_name(op, bg_scene, dock_idx):
|
||||
dock_conns = swld.build_dock_connections(bg_scene)
|
||||
@@ -501,7 +322,6 @@ def render_lightmaps(context):
|
||||
pixel_size = int(area_data.lightmap_resolution)
|
||||
|
||||
# Mmm Cycles
|
||||
context.scene.render.engine = 'CYCLES'
|
||||
context.scene.render.bake.margin = pixel_size // 256
|
||||
|
||||
# Iterate materials and setup cycles
|
||||
@@ -547,8 +367,8 @@ class SREARenderLightmaps(bpy.types.Operator):
|
||||
if not context.selected_objects:
|
||||
for obj in context.scene.objects:
|
||||
if obj.type == 'MESH' and not obj.library:
|
||||
obj.select = True
|
||||
context.scene.objects.active = obj
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = obj
|
||||
|
||||
render_lightmaps(context)
|
||||
|
||||
@@ -559,7 +379,6 @@ def shadeless_material(idx):
|
||||
if name in bpy.data.materials:
|
||||
return bpy.data.materials[name]
|
||||
mat = bpy.data.materials.new(name)
|
||||
mat.use_shadeless = True
|
||||
r = idx % 256
|
||||
g = (idx % 65536) // 256
|
||||
b = idx // 65536
|
||||
@@ -567,11 +386,11 @@ def shadeless_material(idx):
|
||||
return mat
|
||||
|
||||
look_forward = Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
|
||||
look_backward = Quaternion((0.0, 0.0, 1.0), math.radians(180.0)) * Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
|
||||
look_backward = Quaternion((0.0, 0.0, 1.0), math.radians(180.0)) @ Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
|
||||
look_up = Quaternion((1.0, 0.0, 0.0), math.radians(180.0))
|
||||
look_down = Quaternion((1.0, 0.0, 0.0), math.radians(0.0))
|
||||
look_left = Quaternion((0.0, 0.0, 1.0), math.radians(90.0)) * Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
|
||||
look_right = Quaternion((0.0, 0.0, 1.0), math.radians(-90.0)) * Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
|
||||
look_left = Quaternion((0.0, 0.0, 1.0), math.radians(90.0)) @ Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
|
||||
look_right = Quaternion((0.0, 0.0, 1.0), math.radians(-90.0)) @ Quaternion((1.0, 0.0, 0.0), math.radians(90.0))
|
||||
look_list = (look_forward, look_backward, look_up, look_down, look_left, look_right)
|
||||
|
||||
# Render PVS for location
|
||||
@@ -585,7 +404,6 @@ def render_pvs(pathOut, location):
|
||||
bpy.context.scene.render.use_sss = False
|
||||
bpy.context.scene.render.use_envmaps = False
|
||||
bpy.context.scene.render.use_raytrace = False
|
||||
bpy.context.scene.render.engine = 'BLENDER_RENDER'
|
||||
bpy.context.scene.display_settings.display_device = 'None'
|
||||
bpy.context.scene.render.image_settings.file_format = 'PNG'
|
||||
bpy.context.scene.world.horizon_color = Color((1.0, 1.0, 1.0))
|
||||
@@ -593,7 +411,7 @@ def render_pvs(pathOut, location):
|
||||
|
||||
cam = bpy.data.cameras.new('CUBIC_CAM')
|
||||
cam_obj = bpy.data.objects.new('CUBIC_CAM', cam)
|
||||
bpy.context.scene.objects.link(cam_obj)
|
||||
bpy.context.scene.collection.objects.link(cam_obj)
|
||||
bpy.context.scene.camera = cam_obj
|
||||
cam.lens_unit = 'FOV'
|
||||
cam.angle = math.radians(90.0)
|
||||
@@ -617,7 +435,7 @@ def render_pvs(pathOut, location):
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
bpy.context.scene.camera = None
|
||||
bpy.context.scene.objects.unlink(cam_obj)
|
||||
#bpy.context.scene.objects.unlink(cam_obj)
|
||||
bpy.data.objects.remove(cam_obj)
|
||||
bpy.data.cameras.remove(cam)
|
||||
|
||||
@@ -634,16 +452,15 @@ def cook(writebuffunc, platform, endianchar):
|
||||
# Panel draw
|
||||
def draw(layout, context):
|
||||
area_data = context.scene.hecl_srea_data
|
||||
layout.label("Lighting:", icon='LAMP_SPOT')
|
||||
layout.label(text="Lighting:", icon='LIGHT')
|
||||
light_row = layout.row(align=True)
|
||||
light_row.prop_enum(area_data, 'lightmap_mode', 'NONE')
|
||||
light_row.prop_enum(area_data, 'lightmap_mode', 'ORIGINAL')
|
||||
light_row.prop_enum(area_data, 'lightmap_mode', 'CYCLES')
|
||||
layout.prop(area_data, 'lightmap_resolution', text="Resolution")
|
||||
layout.menu("CYCLES_MT_sampling_presets", text=bpy.types.CYCLES_MT_sampling_presets.bl_label)
|
||||
layout.popover("CYCLES_PT_sampling_presets", text=bpy.types.CYCLES_PT_sampling_presets.bl_label)
|
||||
layout.prop(context.scene.render.bake, "use_clear", text="Clear Before Baking")
|
||||
layout.prop(area_data, 'adjacent_area', text='Adjacent Dock Index', icon='OOPS')
|
||||
layout.operator("scene.hecl_area_initialize_cycles", text="Initialize Cycles Nodes", icon='NODETREE')
|
||||
layout.prop(area_data, 'adjacent_area', text='Adjacent Dock Index', icon='MOD_OPACITY')
|
||||
layout.operator("scene.hecl_area_render_lightmaps", text="Bake Cycles Lightmaps", icon='RENDER_STILL')
|
||||
layout.operator("image.save_dirty", text="Save Lightmaps", icon='FILE_TICK')
|
||||
|
||||
@@ -656,7 +473,6 @@ def scene_loaded(dummy):
|
||||
# Registration
|
||||
def register():
|
||||
bpy.utils.register_class(SREAData)
|
||||
bpy.utils.register_class(SREAInitializeCycles)
|
||||
bpy.utils.register_class(SREARenderLightmaps)
|
||||
bpy.types.Scene.hecl_srea_data = bpy.props.PointerProperty(type=SREAData)
|
||||
bpy.types.Material.hecl_lightmap = bpy.props.StringProperty(name='HECL: Lightmap Base Name')
|
||||
@@ -664,6 +480,5 @@ def register():
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(SREAData)
|
||||
bpy.utils.unregister_class(SREAInitializeCycles)
|
||||
bpy.utils.unregister_class(SREARenderLightmaps)
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ def cook(writebuf):
|
||||
for ch in dock_list:
|
||||
if len(ch.data.vertices) < 4:
|
||||
raise RuntimeError('Not enough vertices in dock %s' % ch.name)
|
||||
wmtx = wmtx_inv * ch.matrix_world
|
||||
wmtx = wmtx_inv @ ch.matrix_world
|
||||
for vi in range(4):
|
||||
v = wmtx * ch.data.vertices[vi].co
|
||||
v = wmtx @ ch.data.vertices[vi].co
|
||||
writebuf(struct.pack('fff', v[0], v[1], v[2]))
|
||||
if ch.name in dock_conns:
|
||||
conn_dock = dock_conns[ch.name]
|
||||
|
||||
@@ -65,14 +65,14 @@ class PathHasher:
|
||||
# If there's a third argument, use it as the .zip path containing the addon
|
||||
did_install = False
|
||||
if len(args) >= 4 and args[3] != 'SKIPINSTALL':
|
||||
bpy.ops.wm.addon_install(overwrite=True, target='DEFAULT', filepath=args[3])
|
||||
bpy.ops.wm.addon_refresh()
|
||||
bpy.ops.preferences.addon_install(overwrite=True, target='DEFAULT', filepath=args[3])
|
||||
bpy.ops.preferences.addon_refresh()
|
||||
did_install = True
|
||||
|
||||
# Make addon available to commands
|
||||
if bpy.context.user_preferences.addons.find('hecl') == -1:
|
||||
if bpy.context.preferences.addons.find('hecl') == -1:
|
||||
try:
|
||||
bpy.ops.wm.addon_enable(module='hecl')
|
||||
bpy.ops.preferences.addon_enable(module='hecl')
|
||||
bpy.ops.wm.save_userpref()
|
||||
except:
|
||||
pass
|
||||
@@ -93,16 +93,6 @@ ackbytes = readpipestr()
|
||||
if ackbytes != b'ACK':
|
||||
quitblender()
|
||||
|
||||
# slerp branch check
|
||||
bpy.ops.mesh.primitive_cube_add()
|
||||
orig_rot = bpy.context.object.rotation_mode
|
||||
try:
|
||||
bpy.context.object.rotation_mode = 'QUATERNION_SLERP'
|
||||
writepipestr(b'SLERP1')
|
||||
except:
|
||||
writepipestr(b'SLERP0')
|
||||
bpy.context.object.rotation_mode = orig_rot
|
||||
|
||||
# Count brackets
|
||||
def count_brackets(linestr):
|
||||
bracket_count = 0
|
||||
@@ -184,15 +174,15 @@ def writelight(obj):
|
||||
if obj.data.type == 'POINT':
|
||||
type = 2
|
||||
hasFalloff = True
|
||||
castShadow = obj.data.shadow_method != 'NOSHADOW'
|
||||
castShadow = obj.data.use_shadow
|
||||
elif obj.data.type == 'SPOT':
|
||||
type = 3
|
||||
hasFalloff = True
|
||||
spotCutoff = obj.data.spot_size
|
||||
castShadow = obj.data.shadow_method != 'NOSHADOW'
|
||||
castShadow = obj.data.use_shadow
|
||||
elif obj.data.type == 'SUN':
|
||||
type = 1
|
||||
castShadow = obj.data.shadow_method != 'NOSHADOW'
|
||||
castShadow = obj.data.use_shadow
|
||||
|
||||
constant = 1.0
|
||||
linear = 0.0
|
||||
@@ -236,11 +226,11 @@ def dataout_loop():
|
||||
elif cmdargs[0] == 'LIGHTLIST':
|
||||
lightCount = 0
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.type == 'LAMP' and not obj.library:
|
||||
if obj.type == 'LIGHT' and not obj.library:
|
||||
lightCount += 1
|
||||
writepipebuf(struct.pack('I', lightCount))
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.type == 'LAMP' and not obj.library:
|
||||
if obj.type == 'LIGHT' and not obj.library:
|
||||
writepipestr(obj.name.encode())
|
||||
|
||||
elif cmdargs[0] == 'MESHAABB':
|
||||
@@ -248,27 +238,24 @@ def dataout_loop():
|
||||
hecl.mesh_aabb(writepipebuf)
|
||||
|
||||
elif cmdargs[0] == 'MESHCOMPILE':
|
||||
maxSkinBanks = int(cmdargs[2])
|
||||
|
||||
meshName = bpy.context.scene.hecl_mesh_obj
|
||||
if meshName not in bpy.data.objects:
|
||||
writepipestr(('mesh %s not found' % meshName).encode())
|
||||
continue
|
||||
|
||||
writepipestr(b'OK')
|
||||
hecl.hmdl.cook(writepipebuf, bpy.data.objects[meshName], cmdargs[1], maxSkinBanks)
|
||||
hecl.hmdl.cook(writepipebuf, bpy.data.objects[meshName])
|
||||
|
||||
elif cmdargs[0] == 'MESHCOMPILENAME':
|
||||
meshName = cmdargs[1]
|
||||
maxSkinBanks = int(cmdargs[3])
|
||||
useLuv = int(cmdargs[4])
|
||||
useLuv = int(cmdargs[2])
|
||||
|
||||
if meshName not in bpy.data.objects:
|
||||
writepipestr(('mesh %s not found' % meshName).encode())
|
||||
continue
|
||||
|
||||
writepipestr(b'OK')
|
||||
hecl.hmdl.cook(writepipebuf, bpy.data.objects[meshName], cmdargs[2], maxSkinBanks, useLuv)
|
||||
hecl.hmdl.cook(writepipebuf, bpy.data.objects[meshName], useLuv)
|
||||
|
||||
elif cmdargs[0] == 'MESHCOMPILENAMECOLLISION':
|
||||
meshName = cmdargs[1]
|
||||
@@ -293,25 +280,6 @@ def dataout_loop():
|
||||
if obj.type == 'MESH' and not obj.library:
|
||||
hecl.hmdl.cookcol(writepipebuf, obj)
|
||||
|
||||
elif cmdargs[0] == 'MESHCOMPILEALL':
|
||||
maxSkinBanks = int(cmdargs[2])
|
||||
maxOctantLength = float(cmdargs[3])
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
join_mesh = bpy.data.meshes.new('JOIN_MESH')
|
||||
join_obj = bpy.data.object.new(join_mesh.name, join_mesh)
|
||||
bpy.context.scene.objects.link(join_obj)
|
||||
bpy.ops.object.select_by_type(type='MESH')
|
||||
bpy.context.scene.objects.active = join_obj
|
||||
bpy.ops.object.join()
|
||||
|
||||
writepipestr(b'OK')
|
||||
hecl.hmdl.cook(writepipebuf, join_obj, cmdargs[1], maxSkinBanks, maxOctantLength)
|
||||
|
||||
bpy.context.scene.objects.unlink(join_obj)
|
||||
bpy.data.objects.remove(join_obj)
|
||||
bpy.data.meshes.remove(join_mesh)
|
||||
|
||||
elif cmdargs[0] == 'MESHCOMPILEPATH':
|
||||
meshName = bpy.context.scene.hecl_path_obj
|
||||
if meshName not in bpy.data.objects:
|
||||
@@ -342,7 +310,7 @@ def dataout_loop():
|
||||
lampCount = 0
|
||||
firstSpot = None
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.type == 'LAMP':
|
||||
if obj.type == 'LIGHT':
|
||||
lampCount += 1
|
||||
if firstSpot is None and obj.data.type == 'SPOT':
|
||||
firstSpot = obj
|
||||
@@ -375,7 +343,7 @@ def dataout_loop():
|
||||
|
||||
# Lamp objects
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj != firstSpot and obj.type == 'LAMP':
|
||||
if obj != firstSpot and obj.type == 'LIGHT':
|
||||
writelight(obj)
|
||||
|
||||
elif cmdargs[0] == 'GETTEXTURES':
|
||||
@@ -505,7 +473,7 @@ try:
|
||||
else:
|
||||
bpy.ops.wm.read_homefile()
|
||||
loaded_blend = None
|
||||
bpy.context.user_preferences.filepaths.save_version = 0
|
||||
bpy.context.preferences.filepaths.save_version = 0
|
||||
if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=cmdargs[1]):
|
||||
bpy.ops.file.hecl_patching_load()
|
||||
bpy.context.scene.hecl_type = cmdargs[2]
|
||||
@@ -527,7 +495,7 @@ try:
|
||||
writepipestr(b'FALSE')
|
||||
|
||||
elif cmdargs[0] == 'SAVE':
|
||||
bpy.context.user_preferences.filepaths.save_version = 0
|
||||
bpy.context.preferences.filepaths.save_version = 0
|
||||
print('SAVING %s' % loaded_blend)
|
||||
if loaded_blend:
|
||||
if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=loaded_blend, check_existing=False, compress=True):
|
||||
|
||||
Reference in New Issue
Block a user