Skip to content

Commit

Permalink
Release - 1.8
Browse files Browse the repository at this point in the history
  • Loading branch information
simon50keda committed Apr 13, 2017
1 parent 06d7ffc commit 22b4ac4
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 60 deletions.
2 changes: 1 addition & 1 deletion addon/io_scs_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"name": "SCS Tools",
"description": "Setup models, Import-Export SCS data format",
"author": "Simon Lusenc (50keda), Milos Zajic (4museman)",
"version": (1, 7, "40fb83b"),
"version": (1, 8, "2926820"),
"blender": (2, 78, 0),
"location": "File > Import-Export",
"wiki_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools",
Expand Down
108 changes: 83 additions & 25 deletions addon/io_scs_tools/exp/pim/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from io_scs_tools.utils.convert import change_to_scs_uv_coordinates as _change_to_scs_uv_coordinates
from io_scs_tools.utils.convert import get_scs_transformation_components as _get_scs_transformation_components
from io_scs_tools.utils.convert import scs_to_blend_matrix as _scs_to_blend_matrix
from io_scs_tools.utils.convert import hookup_name_to_hookup_id as _hookup_name_to_hookup_id
from io_scs_tools.utils.printout import lprint


Expand Down Expand Up @@ -74,7 +75,7 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec

print("\n************************************")
print("** SCS PIM Exporter **")
print("** (c)2015 SCS Software **")
print("** (c)2017 SCS Software **")
print("************************************\n")

scs_globals = _get_scs_globals()
Expand Down Expand Up @@ -102,6 +103,7 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec

objects_with_default_material = {} # stores object names which has no material set
missing_mappings_data = {} # indicates if material doesn't have set any uv layer for export
invalid_objects_for_tangents = set() # stores object names which tangents calculation failed because of N-gons existence

bones = skin = skin_stream = None
if is_skin_used:
Expand Down Expand Up @@ -150,17 +152,34 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec

# calculate transformation matrix for current object (root object transforms are always subtracted!)
mesh_transf_mat = root_object.matrix_world.inverted() * mesh_obj.matrix_world
""":type: mathutils.Matrix"""

# calculate transformation matrices for this object
# calculate vertex position transformation matrix for this object
pos_transf_mat = (Matrix.Scale(scs_globals.export_scale, 4) *
_scs_to_blend_matrix().inverted())

nor_transf_mat = _scs_to_blend_matrix().inverted()
""":type: mathutils.Matrix"""

# calculate vertex normals transformation matrix for this object
# NOTE: as normals will be read from none export prepared mesh we have to add rotation and scale from mesh transformation matrix
_, rot, scale = mesh_transf_mat.decompose()
scale_matrix_x = Matrix.Scale(scale.x, 3, Vector((1, 0, 0))).to_4x4()
scale_matrix_y = Matrix.Scale(scale.y, 3, Vector((0, 1, 0))).to_4x4()
scale_matrix_z = Matrix.Scale(scale.z, 3, Vector((0, 0, 1))).to_4x4()
nor_transf_mat = (_scs_to_blend_matrix().inverted() *
rot.to_matrix().to_4x4() *
scale_matrix_x * scale_matrix_y * scale_matrix_z)
""":type: mathutils.Matrix"""

tangent_transf_mat = _scs_to_blend_matrix().inverted()
""":type: mathutils.Matrix"""

# get initial mesh and vertex groups for it
mesh = _object_utils.get_mesh(mesh_obj)
_mesh_utils.bm_prepare_mesh_for_export(mesh, mesh_transf_mat, face_flip)
mesh.calc_normals_split()
_mesh_utils.bm_prepare_mesh_for_export(mesh, mesh_transf_mat)

# get extra mesh only for normals
mesh_for_normals = _object_utils.get_mesh(mesh_obj)
mesh_for_normals.calc_normals_split()

missing_uv_layers = {} # stores missing uvs specified by materials of this object
missing_vcolor = False # indicates if object is missing vertex color layer
Expand Down Expand Up @@ -197,7 +216,10 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec
if nmap_uv_layer:

if nmap_uv_layer in mesh.uv_layers:
mesh.calc_tangents(uvmap=nmap_uv_layer)
try:
mesh.calc_tangents(uvmap=nmap_uv_layer)
except RuntimeError:
invalid_objects_for_tangents.add(mesh_obj.name)
else:
lprint("W Unable to calculate normal map tangents for object %r,\n\t "
"as it's missing UV layer with name: %r, expect problems!",
Expand All @@ -206,8 +228,9 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec
mesh_piece = mesh_pieces[pim_mat_name]
""":type: Piece"""

piece_vert_indices = []
for loop_i in poly.loop_indices:
first_loop_pvert_i = None # storing first loop piece vertex index for usage as first vertex of each triangle of the polygon
prev_tris_last_pvert_i = None # storing last piece vertex index for usage as second vertex of each next triangle of the polygon
for i, loop_i in enumerate(poly.loop_indices):

loop = mesh.loops[loop_i]
""":type: bpy.types.MeshLoop"""
Expand All @@ -218,7 +241,8 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec
position = tuple(pos_transf_mat * mesh.vertices[vert_i].co)

# 2. normal -> loop.normal -> calc_normals_split() has to be called before
normal = nor_transf_mat * loop.normal
# NOTE: we are using normals from original mesh
normal = nor_transf_mat * mesh_for_normals.loops[loop_i].normal
normal = tuple(Vector(normal).normalized())

# 3. uvs -> uv_lay = mesh.uv_layers[0].data; uv_lay[loop_i].uv
Expand Down Expand Up @@ -277,18 +301,17 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec

# 5. tangent -> loop.tangent; loop.bitangent_sign -> calc_tangents() has to be called before
if pim_materials[pim_mat_name].get_nmap_uv_name(): # calculate tangents only if needed
tangent = tuple(nor_transf_mat * loop.tangent)
tangent = tuple(tangent_transf_mat * loop.tangent)
tangent = tuple(Vector(tangent).normalized())
tangent = (tangent[0], tangent[1], tangent[2], loop.bitangent_sign)
else:
tangent = None

# save internal vertex index to array to be able to construct triangle afterwards
# 6. There we go, vertex data collected! Now create internal vertex index, for triangle and skin stream construction
piece_vert_index = mesh_piece.add_vertex(vert_i, position, normal, uvs, uvs_aliases, vcol, tangent)
piece_vert_indices.append(piece_vert_index)

# 7. Get skinning data for vertex and save it to skin stream
if is_skin_used:
# get skinning data for vertex and save it to skin stream
bone_weights = {}
bone_weights_sum = 0
for v_group_entry in mesh.vertices[vert_i].groups:
Expand All @@ -307,7 +330,33 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec
if bone_weights_sum <= 0:
missing_skinned_verts.add(vert_i)

# save to terrain points storage if present in correct vertex group
# 8. Triangle construction!
#
# We are using totally naive method for triangulation, taking polygon loops one by one
# and once we have enough vertices, we create triangle.
#
if i < 2: # on start only save first two loops piece vertex indices

if i == 0:
first_loop_pvert_i = piece_vert_index
else:
prev_tris_last_pvert_i = piece_vert_index

else: # each next loop requires triangle creation

# 1. construct vertices of triangle:
tris_pvert_indices = [first_loop_pvert_i, prev_tris_last_pvert_i, piece_vert_index]

# 2. save current piece vertex index as last triangle vertex for possible next triangles
prev_tris_last_pvert_i = piece_vert_index

# 3. Triangle creation, at last!
if face_flip:
mesh_piece.add_triangle(tuple(tris_pvert_indices))
else:
mesh_piece.add_triangle(tuple(tris_pvert_indices[::-1])) # yep it's weird but it simply works vice versa

# Addition - Terrain Points: save vertex to terrain points storage, if present in correct vertex group
for group in mesh.vertices[vert_i].groups:

# if current object doesn't have vertex group found in mesh data, then ignore that group
Expand Down Expand Up @@ -348,10 +397,9 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec
used_terrain_points.add(variant_i, node_index, position, normal)
break

mesh_piece.add_triangle(tuple(piece_vert_indices[::-1])) # invert indices because of normals flip

# free normals calculations
_mesh_utils.cleanup_mesh(mesh)
_mesh_utils.cleanup_mesh(mesh_for_normals)

# create part if it doesn't exists yet
part_name = used_parts.ensure_part(mesh_obj)
Expand All @@ -360,6 +408,12 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec

mesh_pieces = mesh_pieces.values()
for piece in mesh_pieces:

# now as pieces are created we can check for it's flaws
if piece.get_vertex_count() > 65536:
lprint("E Object %r has exceeded maximum vertex count (65536), expect errors during conversion!",
(mesh_obj.name,))

# put pieces of current mesh to global list
pim_pieces.append(piece)

Expand All @@ -370,7 +424,7 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec
# report missing data for each object
if len(missing_uv_layers) > 0:
for uv_lay_name in missing_uv_layers:
lprint("W Object '%s' is missing UV layer '%s' specified by materials: %s\n",
lprint("W Object %r is missing UV layer %r specified by materials: %r",
(mesh_obj.name, uv_lay_name, missing_uv_layers[uv_lay_name]))
if missing_vcolor:
lprint("W Object %r is missing vertex color layer with name %r! Default RGB color will be exported (0.5, 0.5, 0.5)!",
Expand All @@ -391,6 +445,11 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec
lprint("W Some objects don't use any material. Default material and UV mapping is used on them:\n\t %s",
(list(objects_with_default_material.keys()),))

if len(invalid_objects_for_tangents) > 0:
lprint("E N-gons present in some objects, thus normal map tangent calculation failed.\n\t "
"Visualization in game will be distorted for this objects:\n\t %s",
(list(invalid_objects_for_tangents),))

# create locators data sections
for loc_obj in model_locators:

Expand All @@ -403,15 +462,14 @@ def execute(dirpath, root_object, armature_object, skeleton_filepath, mesh_objec

name = _name_utils.tokenize_name(loc_obj.name)
hookup_string = loc_obj.scs_props.locator_model_hookup
if hookup_string != "" and ":" in hookup_string:
hookup = hookup_string.split(':', 1)[1].strip()
else:
if hookup_string != "":
lprint("W The Hookup %r has no expected value!", hookup_string)
hookup = None
hookup_id = None
if hookup_string != "":
hookup_id = _hookup_name_to_hookup_id(hookup_string)
if hookup_id is None:
lprint("W Model locator %r has unexpected hookup value %r.", (loc_obj.name, loc_obj.scs_props.locator_model_hookup))

# create locator object for export
locator = Locator(len(pim_locators), name, hookup)
locator = Locator(len(pim_locators), name, hookup_id)
locator.set_position(pos)
locator.set_rotation(qua)
locator.set_scale(sca)
Expand Down
18 changes: 15 additions & 3 deletions addon/io_scs_tools/exp/pim/piece.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,16 @@ def get_global_triangle_count():
return Piece.__global_triangle_count

@staticmethod
def __calc_vertex_hash(index, uvs, rgba):
def __calc_vertex_hash(index, uvs, rgba, tangent):
"""Calculates vertex hash from original vertex index, uvs components and vertex color.
:param index: original index from Blender mesh
:type index: int
:param uvs: list of uvs used on vertex (each uv must be in SCS coordinates)
:type uvs: list of (tuple | mathutils.Vector)
:param rgba: rgba representation of vertex color in SCS values
:type rgba: tuple | mathutils.Color
:param tangent: vertex tangent in SCS coordinates or none, if piece doesn't have tangents
:type tangent: tuple | None
:return: calculated vertex hash
:rtype: str
"""
Expand All @@ -79,6 +81,9 @@ def __calc_vertex_hash(index, uvs, rgba):

vertex_hash += frmt % rgba[0] + frmt % rgba[1] + frmt % rgba[2] + frmt % rgba[3]

if tangent:
vertex_hash += frmt % tangent[0] + frmt % tangent[1] + frmt % tangent[2] + frmt % tangent[3]

return vertex_hash

def __init__(self, index, material):
Expand Down Expand Up @@ -140,16 +145,20 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange
:type normal: tuple | mathutils.Vector
:param uvs: list of uvs used on vertex (each uv must be in SCS coordinates)
:type uvs: list of (tuple | mathutils.Vector)
:param uvs_aliases: list of uv aliases names per uv layer
:type uvs_aliases: list[list[str]]
:param rgba: rgba representation of vertex color in SCS values
:type rgba: tuple | mathutils.Color
:param tangent: tuple representation of vertex tangent in SCS values or None if piece doesn't have tangents
:type tangent: tuple | None
:return: vertex index inside piece streams ( use it for adding triangles )
:rtype: int
"""

vertex_hash = self.__calc_vertex_hash(vert_index, uvs, rgba)
vertex_hash = self.__calc_vertex_hash(vert_index, uvs, rgba, tangent)

# save vertex if the vertex with the same properties doesn't exists yet in streams
if not vertex_hash in self.__vertices_hash:
if vertex_hash not in self.__vertices_hash:

stream = self.__streams[Stream.Types.POSITION]
stream.add_entry(position)
Expand Down Expand Up @@ -195,6 +204,9 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange
def get_index(self):
return self.__index

def get_vertex_count(self):
return self.__streams[Stream.Types.POSITION].get_size()

def get_as_section(self):
"""Gets piece represented with SectionData structure class.
:return: packed piece as section data
Expand Down
49 changes: 24 additions & 25 deletions addon/io_scs_tools/imp/pim.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import bpy
import bmesh
import array
from re import match
from mathutils import Vector
from bpy_extras import object_utils as bpy_object_utils
from io_scs_tools.consts import Mesh as _MESH_consts
Expand Down Expand Up @@ -380,7 +381,7 @@ def _get_locator_properties(section):
elif prop[0] == "Name":
loc_name = prop[1]
elif prop[0] == "Hookup":
loc_hookup = _search_hookup_name(prop[1])
loc_hookup = _convert_utils.hookup_id_to_hookup_name(prop[1])
elif prop[0] == "Position":
loc_position = prop[1]
elif prop[0] == "Rotation":
Expand Down Expand Up @@ -459,24 +460,6 @@ def _get_skin_properties(section):
return skin_stream_cnt, skin_streams


def _search_hookup_name(hookup_id):
"""Takes a Hookup ID string and returns the whole Hookup Name
or original ID string if it doesn't exists in Hookup inventory.
:param hookup_id: Hookup ID (as saved in PIM)
:type hookup_id: str
:return: Hookup Name (as used in Blender UI)
:rtype: str
"""
hookup_name = hookup_id
for rec in _get_scs_globals().scs_hookup_inventory:
rec_id = rec.name.split(':', 1)[1].strip()
if rec_id == hookup_id:
hookup_name = rec.name
break
return hookup_name


def _create_5_piece(
context,
preview_model,
Expand Down Expand Up @@ -1119,6 +1102,26 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa
objects = []
skinned_objects = []
for obj_i in objects_data:

# PARTS - search part first so preview model can possibly ignore objects with prescribed variant
part_name = None
for part in parts_data:
if parts_data[part][0] is not None and obj_i in parts_data[part][0]:
part_name = part.lower()

if preview_model:

# ignore pieces with "coll" parts
if match(r'^coll([0-9]?|_.*)$', part_name):
lprint("I Ignoring piece with part collision part name %r for preview model!", (part_name,))
continue

# ignore pieces with shadow and none material effects
used_mat_effect = materials_data[objects_data[obj_i][2]][1]
if ".shadowonly" in used_mat_effect or ".fakeshadow" in used_mat_effect or used_mat_effect.startswith("eut2.none"):
lprint("I Ignoring piece with material having shadow or none effect for preview model!")
continue

# print('objects_data[obj_i]: %s' % str(objects_data[obj_i]))
if format_version in (5, 6):
obj = _create_5_piece(
Expand Down Expand Up @@ -1179,12 +1182,8 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa
lprint('I Created Object "%s"...', (obj.name,))

# PARTS
for part in parts_data:
# print('parts_data["%s"]: %s' % (str(part), str(parts_data[part])))
if parts_data[part][0] is not None:
if obj_i in parts_data[part][0]:
# print(' obj_i: %s - part: %s - parts_data[part][0]: %s' % (obj_i, part, parts_data[part][0]))
obj.scs_props.scs_part = part.lower()
if part_name:
obj.scs_props.scs_part = part_name
else:
lprint('E "%s" - Object creation FAILED!', piece_name)

Expand Down
Loading

0 comments on commit 22b4ac4

Please sign in to comment.