mirror of
https://github.com/open-goal/jak-project
synced 2026-09-08 03:46:51 -04:00
improvements to custom level blender import (#1649)
This commit is contained in:
@@ -0,0 +1,621 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2018-2021 The glTF-Blender-IO authors.
|
||||
|
||||
# Modified for OpenGOAL GLTF extraction.
|
||||
# Make sure that no meshes have face corner colors. All colors must be vertex colors (float).
|
||||
# This should replace <blender>/3.2/scripts/addons/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
|
||||
|
||||
import numpy as np
|
||||
from mathutils import Vector
|
||||
|
||||
from . import gltf2_blender_export_keys
|
||||
from ...io.com.gltf2_io_debug import print_console
|
||||
from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
|
||||
|
||||
|
||||
def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings):
|
||||
"""Extract primitives from a mesh."""
|
||||
print_console('INFO', 'Extracting primitive: ' + blender_mesh.name)
|
||||
|
||||
blender_object = None
|
||||
if uuid_for_skined_data:
|
||||
blender_object = export_settings['vtree'].nodes[uuid_for_skined_data].blender_object
|
||||
|
||||
use_normals = export_settings[gltf2_blender_export_keys.NORMALS]
|
||||
if use_normals:
|
||||
blender_mesh.calc_normals_split()
|
||||
|
||||
use_tangents = False
|
||||
if use_normals and export_settings[gltf2_blender_export_keys.TANGENTS]:
|
||||
if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0:
|
||||
try:
|
||||
blender_mesh.calc_tangents()
|
||||
use_tangents = True
|
||||
except Exception:
|
||||
print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.')
|
||||
|
||||
tex_coord_max = 0
|
||||
if export_settings[gltf2_blender_export_keys.TEX_COORDS]:
|
||||
if blender_mesh.uv_layers.active:
|
||||
tex_coord_max = len(blender_mesh.uv_layers)
|
||||
|
||||
color_max = 0
|
||||
if export_settings[gltf2_blender_export_keys.COLORS]:
|
||||
# changed: now reading from color attributes
|
||||
color_max = len(blender_mesh.color_attributes)
|
||||
|
||||
armature = None
|
||||
skin = None
|
||||
if blender_vertex_groups and export_settings[gltf2_blender_export_keys.SKINS]:
|
||||
if modifiers is not None:
|
||||
modifiers_dict = {m.type: m for m in modifiers}
|
||||
if "ARMATURE" in modifiers_dict:
|
||||
modifier = modifiers_dict["ARMATURE"]
|
||||
armature = modifier.object
|
||||
|
||||
# Skin must be ignored if the object is parented to a bone of the armature
|
||||
# (This creates an infinite recursive error)
|
||||
# So ignoring skin in that case
|
||||
is_child_of_arma = (
|
||||
armature and
|
||||
blender_object and
|
||||
blender_object.parent_type == "BONE" and
|
||||
blender_object.parent.name == armature.name
|
||||
)
|
||||
if is_child_of_arma:
|
||||
armature = None
|
||||
|
||||
if armature:
|
||||
skin = gltf2_blender_gather_skins.gather_skin(export_settings['vtree'].nodes[uuid_for_skined_data].armature, export_settings)
|
||||
if not skin:
|
||||
armature = None
|
||||
|
||||
use_morph_normals = use_normals and export_settings[gltf2_blender_export_keys.MORPH_NORMAL]
|
||||
use_morph_tangents = use_morph_normals and use_tangents and export_settings[gltf2_blender_export_keys.MORPH_TANGENT]
|
||||
|
||||
key_blocks = []
|
||||
if blender_mesh.shape_keys and export_settings[gltf2_blender_export_keys.MORPH]:
|
||||
key_blocks = [
|
||||
key_block
|
||||
for key_block in blender_mesh.shape_keys.key_blocks
|
||||
if not (key_block == key_block.relative_key or key_block.mute)
|
||||
]
|
||||
|
||||
use_materials = export_settings[gltf2_blender_export_keys.MATERIALS]
|
||||
|
||||
# Fetch vert positions and bone data (joint,weights)
|
||||
|
||||
locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings)
|
||||
if skin:
|
||||
vert_bones, num_joint_sets, need_neutral_bone = __get_bone_data(blender_mesh, skin, blender_vertex_groups)
|
||||
if need_neutral_bone is True:
|
||||
# Need to create a fake joint at root of armature
|
||||
# In order to assign not assigned vertices to it
|
||||
# But for now, this is not yet possible, we need to wait the armature node is created
|
||||
# Just store this, to be used later
|
||||
armature_uuid = export_settings['vtree'].nodes[uuid_for_skined_data].armature
|
||||
export_settings['vtree'].nodes[armature_uuid].need_neutral_bone = True
|
||||
|
||||
# In Blender there is both per-vert data, like position, and also per-loop
|
||||
# (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert
|
||||
# data, so we need to split Blender verts up into potentially-multiple glTF
|
||||
# verts.
|
||||
#
|
||||
# First, we'll collect a "dot" for every loop: a struct that stores all the
|
||||
# attributes at that loop, namely the vertex index (which determines all
|
||||
# per-vert data), and all the per-loop data like UVs, etc.
|
||||
#
|
||||
# Each unique dot will become one unique glTF vert.
|
||||
|
||||
# List all fields the dot struct needs.
|
||||
dot_fields = [('vertex_index', np.uint32)]
|
||||
if use_normals:
|
||||
dot_fields += [('nx', np.float32), ('ny', np.float32), ('nz', np.float32)]
|
||||
if use_tangents:
|
||||
dot_fields += [('tx', np.float32), ('ty', np.float32), ('tz', np.float32), ('tw', np.float32)]
|
||||
for uv_i in range(tex_coord_max):
|
||||
dot_fields += [('uv%dx' % uv_i, np.float32), ('uv%dy' % uv_i, np.float32)]
|
||||
# for col_i in range(color_max):
|
||||
# dot_fields += [
|
||||
# ('color%dr' % col_i, np.float32),
|
||||
# ('color%dg' % col_i, np.float32),
|
||||
# ('color%db' % col_i, np.float32),
|
||||
# ('color%da' % col_i, np.float32),
|
||||
# ]
|
||||
if use_morph_normals:
|
||||
for morph_i, _ in enumerate(key_blocks):
|
||||
dot_fields += [
|
||||
('morph%dnx' % morph_i, np.float32),
|
||||
('morph%dny' % morph_i, np.float32),
|
||||
('morph%dnz' % morph_i, np.float32),
|
||||
]
|
||||
|
||||
dots = np.empty(len(blender_mesh.loops), dtype=np.dtype(dot_fields))
|
||||
|
||||
vidxs = np.empty(len(blender_mesh.loops))
|
||||
blender_mesh.loops.foreach_get('vertex_index', vidxs)
|
||||
dots['vertex_index'] = vidxs
|
||||
del vidxs
|
||||
|
||||
if use_normals:
|
||||
kbs = key_blocks if use_morph_normals else []
|
||||
normals, morph_normals = __get_normals(
|
||||
blender_mesh, kbs, armature, blender_object, export_settings
|
||||
)
|
||||
dots['nx'] = normals[:, 0]
|
||||
dots['ny'] = normals[:, 1]
|
||||
dots['nz'] = normals[:, 2]
|
||||
del normals
|
||||
for morph_i, ns in enumerate(morph_normals):
|
||||
dots['morph%dnx' % morph_i] = ns[:, 0]
|
||||
dots['morph%dny' % morph_i] = ns[:, 1]
|
||||
dots['morph%dnz' % morph_i] = ns[:, 2]
|
||||
del morph_normals
|
||||
|
||||
if use_tangents:
|
||||
tangents = __get_tangents(blender_mesh, armature, blender_object, export_settings)
|
||||
dots['tx'] = tangents[:, 0]
|
||||
dots['ty'] = tangents[:, 1]
|
||||
dots['tz'] = tangents[:, 2]
|
||||
del tangents
|
||||
signs = __get_bitangent_signs(blender_mesh, armature, blender_object, export_settings)
|
||||
dots['tw'] = signs
|
||||
del signs
|
||||
|
||||
for uv_i in range(tex_coord_max):
|
||||
uvs = __get_uvs(blender_mesh, uv_i)
|
||||
dots['uv%dx' % uv_i] = uvs[:, 0]
|
||||
dots['uv%dy' % uv_i] = uvs[:, 1]
|
||||
del uvs
|
||||
|
||||
vertex_colors = [__get_colors(blender_mesh, i) for i in range(color_max)]
|
||||
#for col_i in range(color_max):
|
||||
# colors = __get_colors(blender_mesh, col_i)
|
||||
# dots['color%dr' % col_i] = colors[:, 0]
|
||||
# dots['color%dg' % col_i] = colors[:, 1]
|
||||
# dots['color%db' % col_i] = colors[:, 2]
|
||||
# dots['color%da' % col_i] = colors[:, 3]
|
||||
# del colors
|
||||
|
||||
# Calculate triangles and sort them into primitives.
|
||||
|
||||
blender_mesh.calc_loop_triangles()
|
||||
loop_indices = np.empty(len(blender_mesh.loop_triangles) * 3, dtype=np.uint32)
|
||||
blender_mesh.loop_triangles.foreach_get('loops', loop_indices)
|
||||
|
||||
prim_indices = {} # maps material index to TRIANGLES-style indices into dots
|
||||
|
||||
if use_materials == "NONE": # Only for None. For placeholder and export, keep primitives
|
||||
# Put all vertices into one primitive
|
||||
prim_indices[-1] = loop_indices
|
||||
|
||||
else:
|
||||
# Bucket by material index.
|
||||
|
||||
tri_material_idxs = np.empty(len(blender_mesh.loop_triangles), dtype=np.uint32)
|
||||
blender_mesh.loop_triangles.foreach_get('material_index', tri_material_idxs)
|
||||
loop_material_idxs = np.repeat(tri_material_idxs, 3) # material index for every loop
|
||||
unique_material_idxs = np.unique(tri_material_idxs)
|
||||
del tri_material_idxs
|
||||
|
||||
for material_idx in unique_material_idxs:
|
||||
prim_indices[material_idx] = loop_indices[loop_material_idxs == material_idx]
|
||||
|
||||
# Create all the primitives.
|
||||
|
||||
primitives = []
|
||||
|
||||
for material_idx, dot_indices in prim_indices.items():
|
||||
# Extract just dots used by this primitive, deduplicate them, and
|
||||
# calculate indices into this deduplicated list.
|
||||
prim_dots = dots[dot_indices]
|
||||
prim_dots, indices = np.unique(prim_dots, return_inverse=True)
|
||||
|
||||
if len(prim_dots) == 0:
|
||||
continue
|
||||
|
||||
# Now just move all the data for prim_dots into attribute arrays
|
||||
|
||||
attributes = {}
|
||||
|
||||
blender_idxs = prim_dots['vertex_index']
|
||||
|
||||
attributes['POSITION'] = locs[blender_idxs]
|
||||
for i in range(color_max):
|
||||
attributes['COLOR_%d' % i] = vertex_colors[i][blender_idxs]
|
||||
|
||||
for morph_i, vs in enumerate(morph_locs):
|
||||
attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs]
|
||||
|
||||
if use_normals:
|
||||
normals = np.empty((len(prim_dots), 3), dtype=np.float32)
|
||||
normals[:, 0] = prim_dots['nx']
|
||||
normals[:, 1] = prim_dots['ny']
|
||||
normals[:, 2] = prim_dots['nz']
|
||||
attributes['NORMAL'] = normals
|
||||
|
||||
if use_tangents:
|
||||
tangents = np.empty((len(prim_dots), 4), dtype=np.float32)
|
||||
tangents[:, 0] = prim_dots['tx']
|
||||
tangents[:, 1] = prim_dots['ty']
|
||||
tangents[:, 2] = prim_dots['tz']
|
||||
tangents[:, 3] = prim_dots['tw']
|
||||
attributes['TANGENT'] = tangents
|
||||
|
||||
if use_morph_normals:
|
||||
for morph_i, _ in enumerate(key_blocks):
|
||||
ns = np.empty((len(prim_dots), 3), dtype=np.float32)
|
||||
ns[:, 0] = prim_dots['morph%dnx' % morph_i]
|
||||
ns[:, 1] = prim_dots['morph%dny' % morph_i]
|
||||
ns[:, 2] = prim_dots['morph%dnz' % morph_i]
|
||||
attributes['MORPH_NORMAL_%d' % morph_i] = ns
|
||||
|
||||
if use_morph_tangents:
|
||||
attributes['MORPH_TANGENT_%d' % morph_i] = __calc_morph_tangents(normals, ns, tangents)
|
||||
|
||||
for tex_coord_i in range(tex_coord_max):
|
||||
uvs = np.empty((len(prim_dots), 2), dtype=np.float32)
|
||||
uvs[:, 0] = prim_dots['uv%dx' % tex_coord_i]
|
||||
uvs[:, 1] = prim_dots['uv%dy' % tex_coord_i]
|
||||
attributes['TEXCOORD_%d' % tex_coord_i] = uvs
|
||||
|
||||
#for color_i in range(color_max):
|
||||
# colors = np.empty((len(prim_dots), 4), dtype=np.float32)
|
||||
# colors[:, 0] = prim_dots['color%dr' % color_i]
|
||||
# colors[:, 1] = prim_dots['color%dg' % color_i]
|
||||
# colors[:, 2] = prim_dots['color%db' % color_i]
|
||||
# colors[:, 3] = prim_dots['color%da' % color_i]
|
||||
# attributes['COLOR_%d' % color_i] = colors
|
||||
|
||||
if skin:
|
||||
joints = [[] for _ in range(num_joint_sets)]
|
||||
weights = [[] for _ in range(num_joint_sets)]
|
||||
|
||||
for vi in blender_idxs:
|
||||
bones = vert_bones[vi]
|
||||
for j in range(0, 4 * num_joint_sets):
|
||||
if j < len(bones):
|
||||
joint, weight = bones[j]
|
||||
else:
|
||||
joint, weight = 0, 0.0
|
||||
joints[j//4].append(joint)
|
||||
weights[j//4].append(weight)
|
||||
|
||||
for i, (js, ws) in enumerate(zip(joints, weights)):
|
||||
attributes['JOINTS_%d' % i] = js
|
||||
attributes['WEIGHTS_%d' % i] = ws
|
||||
|
||||
primitives.append({
|
||||
'attributes': attributes,
|
||||
'indices': indices,
|
||||
'material': material_idx,
|
||||
})
|
||||
|
||||
if export_settings['gltf_loose_edges']:
|
||||
# Find loose edges
|
||||
loose_edges = [e for e in blender_mesh.edges if e.is_loose]
|
||||
blender_idxs = [vi for e in loose_edges for vi in e.vertices]
|
||||
|
||||
if blender_idxs:
|
||||
# Export one glTF vert per unique Blender vert in a loose edge
|
||||
blender_idxs = np.array(blender_idxs, dtype=np.uint32)
|
||||
blender_idxs, indices = np.unique(blender_idxs, return_inverse=True)
|
||||
|
||||
attributes = {}
|
||||
|
||||
attributes['POSITION'] = locs[blender_idxs]
|
||||
|
||||
for morph_i, vs in enumerate(morph_locs):
|
||||
attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs]
|
||||
|
||||
if skin:
|
||||
joints = [[] for _ in range(num_joint_sets)]
|
||||
weights = [[] for _ in range(num_joint_sets)]
|
||||
|
||||
for vi in blender_idxs:
|
||||
bones = vert_bones[vi]
|
||||
for j in range(0, 4 * num_joint_sets):
|
||||
if j < len(bones):
|
||||
joint, weight = bones[j]
|
||||
else:
|
||||
joint, weight = 0, 0.0
|
||||
joints[j//4].append(joint)
|
||||
weights[j//4].append(weight)
|
||||
|
||||
for i, (js, ws) in enumerate(zip(joints, weights)):
|
||||
attributes['JOINTS_%d' % i] = js
|
||||
attributes['WEIGHTS_%d' % i] = ws
|
||||
|
||||
primitives.append({
|
||||
'attributes': attributes,
|
||||
'indices': indices,
|
||||
'mode': 1, # LINES
|
||||
'material': 0,
|
||||
})
|
||||
|
||||
if export_settings['gltf_loose_points']:
|
||||
# Find loose points
|
||||
verts_in_edge = set(vi for e in blender_mesh.edges for vi in e.vertices)
|
||||
blender_idxs = [
|
||||
vi for vi, _ in enumerate(blender_mesh.vertices)
|
||||
if vi not in verts_in_edge
|
||||
]
|
||||
|
||||
if blender_idxs:
|
||||
blender_idxs = np.array(blender_idxs, dtype=np.uint32)
|
||||
|
||||
attributes = {}
|
||||
|
||||
attributes['POSITION'] = locs[blender_idxs]
|
||||
|
||||
for morph_i, vs in enumerate(morph_locs):
|
||||
attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs]
|
||||
|
||||
if skin:
|
||||
joints = [[] for _ in range(num_joint_sets)]
|
||||
weights = [[] for _ in range(num_joint_sets)]
|
||||
|
||||
for vi in blender_idxs:
|
||||
bones = vert_bones[vi]
|
||||
for j in range(0, 4 * num_joint_sets):
|
||||
if j < len(bones):
|
||||
joint, weight = bones[j]
|
||||
else:
|
||||
joint, weight = 0, 0.0
|
||||
joints[j//4].append(joint)
|
||||
weights[j//4].append(weight)
|
||||
|
||||
for i, (js, ws) in enumerate(zip(joints, weights)):
|
||||
attributes['JOINTS_%d' % i] = js
|
||||
attributes['WEIGHTS_%d' % i] = ws
|
||||
|
||||
primitives.append({
|
||||
'attributes': attributes,
|
||||
'mode': 0, # POINTS
|
||||
'material': 0,
|
||||
})
|
||||
|
||||
print_console('INFO', 'Primitives created: %d' % len(primitives))
|
||||
|
||||
return primitives
|
||||
|
||||
|
||||
def __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings):
|
||||
locs = np.empty(len(blender_mesh.vertices) * 3, dtype=np.float32)
|
||||
source = key_blocks[0].relative_key.data if key_blocks else blender_mesh.vertices
|
||||
source.foreach_get('co', locs)
|
||||
locs = locs.reshape(len(blender_mesh.vertices), 3)
|
||||
|
||||
morph_locs = []
|
||||
for key_block in key_blocks:
|
||||
vs = np.empty(len(blender_mesh.vertices) * 3, dtype=np.float32)
|
||||
key_block.data.foreach_get('co', vs)
|
||||
vs = vs.reshape(len(blender_mesh.vertices), 3)
|
||||
morph_locs.append(vs)
|
||||
|
||||
# Transform for skinning
|
||||
if armature and blender_object:
|
||||
# apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world
|
||||
# loc_transform = armature.matrix_world @ apply_matrix
|
||||
|
||||
loc_transform = blender_object.matrix_world
|
||||
locs[:] = __apply_mat_to_all(loc_transform, locs)
|
||||
for vs in morph_locs:
|
||||
vs[:] = __apply_mat_to_all(loc_transform, vs)
|
||||
|
||||
# glTF stores deltas in morph targets
|
||||
for vs in morph_locs:
|
||||
vs -= locs
|
||||
|
||||
if export_settings[gltf2_blender_export_keys.YUP]:
|
||||
__zup2yup(locs)
|
||||
for vs in morph_locs:
|
||||
__zup2yup(vs)
|
||||
|
||||
return locs, morph_locs
|
||||
|
||||
|
||||
def __get_normals(blender_mesh, key_blocks, armature, blender_object, export_settings):
|
||||
"""Get normal for each loop."""
|
||||
if key_blocks:
|
||||
normals = key_blocks[0].relative_key.normals_split_get()
|
||||
normals = np.array(normals, dtype=np.float32)
|
||||
else:
|
||||
normals = np.empty(len(blender_mesh.loops) * 3, dtype=np.float32)
|
||||
blender_mesh.calc_normals_split()
|
||||
blender_mesh.loops.foreach_get('normal', normals)
|
||||
|
||||
normals = normals.reshape(len(blender_mesh.loops), 3)
|
||||
|
||||
morph_normals = []
|
||||
for key_block in key_blocks:
|
||||
ns = np.array(key_block.normals_split_get(), dtype=np.float32)
|
||||
ns = ns.reshape(len(blender_mesh.loops), 3)
|
||||
morph_normals.append(ns)
|
||||
|
||||
# Transform for skinning
|
||||
if armature and blender_object:
|
||||
apply_matrix = (armature.matrix_world.inverted_safe() @ blender_object.matrix_world)
|
||||
apply_matrix = apply_matrix.to_3x3().inverted_safe().transposed()
|
||||
normal_transform = armature.matrix_world.to_3x3() @ apply_matrix
|
||||
|
||||
normals[:] = __apply_mat_to_all(normal_transform, normals)
|
||||
__normalize_vecs(normals)
|
||||
for ns in morph_normals:
|
||||
ns[:] = __apply_mat_to_all(normal_transform, ns)
|
||||
__normalize_vecs(ns)
|
||||
|
||||
for ns in [normals, *morph_normals]:
|
||||
# Replace zero normals with the unit UP vector.
|
||||
# Seems to happen sometimes with degenerate tris?
|
||||
is_zero = ~ns.any(axis=1)
|
||||
ns[is_zero, 2] = 1
|
||||
|
||||
# glTF stores deltas in morph targets
|
||||
for ns in morph_normals:
|
||||
ns -= normals
|
||||
|
||||
if export_settings[gltf2_blender_export_keys.YUP]:
|
||||
__zup2yup(normals)
|
||||
for ns in morph_normals:
|
||||
__zup2yup(ns)
|
||||
|
||||
return normals, morph_normals
|
||||
|
||||
|
||||
def __get_tangents(blender_mesh, armature, blender_object, export_settings):
|
||||
"""Get an array of the tangent for each loop."""
|
||||
tangents = np.empty(len(blender_mesh.loops) * 3, dtype=np.float32)
|
||||
blender_mesh.loops.foreach_get('tangent', tangents)
|
||||
tangents = tangents.reshape(len(blender_mesh.loops), 3)
|
||||
|
||||
# Transform for skinning
|
||||
if armature and blender_object:
|
||||
apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world
|
||||
tangent_transform = apply_matrix.to_quaternion().to_matrix()
|
||||
tangents = __apply_mat_to_all(tangent_transform, tangents)
|
||||
__normalize_vecs(tangents)
|
||||
|
||||
if export_settings[gltf2_blender_export_keys.YUP]:
|
||||
__zup2yup(tangents)
|
||||
|
||||
return tangents
|
||||
|
||||
|
||||
def __get_bitangent_signs(blender_mesh, armature, blender_object, export_settings):
|
||||
signs = np.empty(len(blender_mesh.loops), dtype=np.float32)
|
||||
blender_mesh.loops.foreach_get('bitangent_sign', signs)
|
||||
|
||||
# Transform for skinning
|
||||
if armature and blender_object:
|
||||
# Bitangent signs should flip when handedness changes
|
||||
# TODO: confirm
|
||||
apply_matrix = armature.matrix_world.inverted_safe() @ blender_object.matrix_world
|
||||
tangent_transform = apply_matrix.to_quaternion().to_matrix()
|
||||
flipped = tangent_transform.determinant() < 0
|
||||
if flipped:
|
||||
signs *= -1
|
||||
|
||||
# No change for Zup -> Yup
|
||||
|
||||
return signs
|
||||
|
||||
|
||||
def __calc_morph_tangents(normals, morph_normal_deltas, tangents):
|
||||
# TODO: check if this works
|
||||
morph_tangent_deltas = np.empty((len(normals), 3), dtype=np.float32)
|
||||
|
||||
for i in range(len(normals)):
|
||||
n = Vector(normals[i])
|
||||
morph_n = n + Vector(morph_normal_deltas[i]) # convert back to non-delta
|
||||
t = Vector(tangents[i, :3])
|
||||
|
||||
rotation = morph_n.rotation_difference(n)
|
||||
|
||||
t_morph = Vector(t)
|
||||
t_morph.rotate(rotation)
|
||||
morph_tangent_deltas[i] = t_morph - t # back to delta
|
||||
|
||||
return morph_tangent_deltas
|
||||
|
||||
|
||||
def __get_uvs(blender_mesh, uv_i):
|
||||
layer = blender_mesh.uv_layers[uv_i]
|
||||
uvs = np.empty(len(blender_mesh.loops) * 2, dtype=np.float32)
|
||||
layer.data.foreach_get('uv', uvs)
|
||||
uvs = uvs.reshape(len(blender_mesh.loops), 2)
|
||||
|
||||
# Blender UV space -> glTF UV space
|
||||
# u,v -> u,1-v
|
||||
uvs[:, 1] *= -1
|
||||
uvs[:, 1] += 1
|
||||
|
||||
return uvs
|
||||
|
||||
|
||||
def __get_colors(blender_mesh, color_i):
|
||||
colors = np.empty(len(blender_mesh.vertices) * 4, dtype=np.float32)
|
||||
#layer = blender_mesh.vertex_colors[color_i]
|
||||
blender_mesh.color_attributes[color_i].data.foreach_get('color', colors)
|
||||
colors = colors.reshape(len(blender_mesh.vertices), 4)
|
||||
# somehow the colors from blender are > 1.0 sometimes, so clamp here.
|
||||
colors = np.clip(colors, 0.0, 1.0)
|
||||
# colors are already linear, no need to switch color space
|
||||
return colors
|
||||
|
||||
#def __get_colors(blender_mesh, color_i):
|
||||
# layer = blender_mesh.vertex_colors[color_i]
|
||||
# colors = np.empty(len(blender_mesh.loops) * 4, dtype=np.float32)
|
||||
# layer.data.foreach_get('color', colors)
|
||||
# colors = colors.reshape(len(blender_mesh.loops), 4)
|
||||
#
|
||||
# # sRGB -> Linear
|
||||
# rgb = colors[:, :-1]
|
||||
# not_small = rgb >= 0.04045
|
||||
# small_result = np.where(rgb < 0.0, 0.0, rgb * (1.0 / 12.92))
|
||||
# large_result = np.power((rgb + 0.055) * (1.0 / 1.055), 2.4, where=not_small)
|
||||
# rgb[:] = np.where(not_small, large_result, small_result)
|
||||
# return colors
|
||||
|
||||
|
||||
def __get_bone_data(blender_mesh, skin, blender_vertex_groups):
|
||||
|
||||
need_neutral_bone = False
|
||||
min_influence = 0.0001
|
||||
|
||||
joint_name_to_index = {joint.name: index for index, joint in enumerate(skin.joints)}
|
||||
group_to_joint = [joint_name_to_index.get(g.name) for g in blender_vertex_groups]
|
||||
|
||||
# List of (joint, weight) pairs for each vert
|
||||
vert_bones = []
|
||||
max_num_influences = 0
|
||||
|
||||
for vertex in blender_mesh.vertices:
|
||||
bones = []
|
||||
if vertex.groups:
|
||||
for group_element in vertex.groups:
|
||||
weight = group_element.weight
|
||||
if weight <= min_influence:
|
||||
continue
|
||||
try:
|
||||
joint = group_to_joint[group_element.group]
|
||||
except Exception:
|
||||
continue
|
||||
if joint is None:
|
||||
continue
|
||||
bones.append((joint, weight))
|
||||
bones.sort(key=lambda x: x[1], reverse=True)
|
||||
if not bones:
|
||||
# Is not assign to any bone
|
||||
bones = ((len(skin.joints), 1.0),) # Assign to a joint that will be created later
|
||||
need_neutral_bone = True
|
||||
vert_bones.append(bones)
|
||||
if len(bones) > max_num_influences:
|
||||
max_num_influences = len(bones)
|
||||
|
||||
# How many joint sets do we need? 1 set = 4 influences
|
||||
num_joint_sets = (max_num_influences + 3) // 4
|
||||
|
||||
return vert_bones, num_joint_sets, need_neutral_bone
|
||||
|
||||
|
||||
def __zup2yup(array):
|
||||
# x,y,z -> x,z,-y
|
||||
array[:, [1,2]] = array[:, [2,1]] # x,z,y
|
||||
array[:, 2] *= -1 # x,z,-y
|
||||
|
||||
|
||||
def __apply_mat_to_all(matrix, vectors):
|
||||
"""Given matrix m and vectors [v1,v2,...], computes [m@v1,m@v2,...]"""
|
||||
# Linear part
|
||||
m = matrix.to_3x3() if len(matrix) == 4 else matrix
|
||||
res = np.matmul(vectors, np.array(m.transposed()))
|
||||
# Translation part
|
||||
if len(matrix) == 4:
|
||||
res += np.array(matrix.translation)
|
||||
return res
|
||||
|
||||
|
||||
def __normalize_vecs(vectors):
|
||||
norms = np.linalg.norm(vectors, axis=1, keepdims=True)
|
||||
np.divide(vectors, norms, out=vectors, where=norms != 0)
|
||||
@@ -0,0 +1,119 @@
|
||||
bl_info = {
|
||||
"name": "OpenGOAL Mesh",
|
||||
"author": "water111",
|
||||
"version": (0, 0, 1),
|
||||
"blender": (2, 83, 0),
|
||||
"location": "3D View",
|
||||
"description": "OpenGOAL Mesh tools",
|
||||
"category": "Development"
|
||||
}
|
||||
|
||||
import bpy
|
||||
import colorsys
|
||||
import bmesh
|
||||
|
||||
from bpy.props import (StringProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
EnumProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
from bpy.types import (Panel,
|
||||
Menu,
|
||||
Operator,
|
||||
PropertyGroup,
|
||||
)
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
pat_surfaces = [
|
||||
("stone", "stone", "", 0),
|
||||
("ice", "ice", "", 1),
|
||||
("quicksand", "quicksand", "", 2),
|
||||
("waterbottom", "waterbottom", "", 3),
|
||||
("tar", "tar", "", 4),
|
||||
("sand", "sand", "", 5),
|
||||
("wood", "wood", "", 6),
|
||||
("grass", "grass", "", 7),
|
||||
("pcmetal", "pcmetal", "", 8),
|
||||
("snow", "snow", "", 9),
|
||||
("deepsnow", "deepsnow", "", 10),
|
||||
("hotcoals", "hotcoals", "", 11),
|
||||
("lava", "lava", "", 12),
|
||||
("crwood", "crwood", "", 13),
|
||||
("gravel", "gravel", "", 14),
|
||||
("dirt", "dirt", "", 15),
|
||||
("metal", "metal", "", 16),
|
||||
("straw", "straw", "", 17),
|
||||
("tube", "tube", "", 18),
|
||||
("swamp", "swamp", "", 19),
|
||||
("stopproj", "stopproj", "", 20),
|
||||
("rotate", "rotate", "", 21),
|
||||
("neutral", "neutral", "", 22),
|
||||
]
|
||||
|
||||
pat_events = [
|
||||
("none", "none", "", 0),
|
||||
("deadly", "deadly", "", 1),
|
||||
("endlessfall", "endlessfall", "", 2),
|
||||
("burn", "burn", "", 3),
|
||||
("deadlyup", "deadlyup", "", 4),
|
||||
("burnup", "burnup", "", 5),
|
||||
("melt", "melt", "", 6),
|
||||
]
|
||||
|
||||
def draw_func(self, context):
|
||||
layout = self.layout
|
||||
ob = context.object
|
||||
layout.prop(ob.active_material, "set_collision")
|
||||
if (ob.active_material.set_collision):
|
||||
layout.prop(ob.active_material, "ignore")
|
||||
layout.prop(ob.active_material, "collide_material")
|
||||
layout.prop(ob.active_material, "collide_event")
|
||||
layout.prop(ob.active_material, "noedge")
|
||||
layout.prop(ob.active_material, "noentity")
|
||||
layout.prop(ob.active_material, "nolineofsight")
|
||||
layout.prop(ob.active_material, "nocamera")
|
||||
|
||||
def draw_func_ob(self, context):
|
||||
layout = self.layout
|
||||
ob = context.object
|
||||
layout.prop(ob, "set_collision")
|
||||
if (ob.set_collision):
|
||||
layout.prop(ob, "ignore")
|
||||
layout.prop(ob, "collide_material")
|
||||
layout.prop(ob, "collide_event")
|
||||
layout.prop(ob, "noedge")
|
||||
layout.prop(ob, "noentity")
|
||||
layout.prop(ob, "nolineofsight")
|
||||
layout.prop(ob, "nocamera")
|
||||
|
||||
def register():
|
||||
bpy.types.Material.set_collision = bpy.props.BoolProperty(name="Apply Collision Properties")
|
||||
bpy.types.Material.ignore = bpy.props.BoolProperty(name="ignore")
|
||||
bpy.types.Material.noedge = bpy.props.BoolProperty(name="No-Edge")
|
||||
bpy.types.Material.noentity = bpy.props.BoolProperty(name="No-Entity")
|
||||
bpy.types.Material.nolineofsight = bpy.props.BoolProperty(name="No-LOS")
|
||||
bpy.types.Material.nocamera = bpy.props.BoolProperty(name="No-Camera")
|
||||
bpy.types.Material.collide_material = bpy.props.EnumProperty(items = pat_surfaces, name = "Material")
|
||||
bpy.types.Material.collide_event = bpy.props.EnumProperty(items = pat_events, name = "Event")
|
||||
bpy.types.MATERIAL_PT_custom_props.prepend(draw_func)
|
||||
|
||||
bpy.types.Object.set_collision = bpy.props.BoolProperty(name="Apply Collision Properties")
|
||||
bpy.types.Object.ignore = bpy.props.BoolProperty(name="ignore")
|
||||
bpy.types.Object.noedge = bpy.props.BoolProperty(name="No-Edge")
|
||||
bpy.types.Object.noentity = bpy.props.BoolProperty(name="No-Entity")
|
||||
bpy.types.Object.nolineofsight = bpy.props.BoolProperty(name="No-LOS")
|
||||
bpy.types.Object.nocamera = bpy.props.BoolProperty(name="No-Camera")
|
||||
bpy.types.Object.collide_material = bpy.props.EnumProperty(items = pat_surfaces, name = "Material")
|
||||
bpy.types.Object.collide_event = bpy.props.EnumProperty(items = pat_events, name = "Event")
|
||||
bpy.types.OBJECT_PT_custom_props.prepend(draw_func_ob)
|
||||
|
||||
def unregister():
|
||||
bpy.types.MATERIAL_PT_custom_props.remove(draw_func)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "game/kernel/jak1/kscheme.h"
|
||||
#include "game/mips2c/mips2c_private.h"
|
||||
using namespace jak1;
|
||||
|
||||
const uint32_t* max_tri_count = nullptr;
|
||||
namespace {
|
||||
u32 vu0_buffer[1024]; // todo, maybe can be 512.
|
||||
u32 vi1 = 0;
|
||||
@@ -507,6 +509,7 @@ u64 execute(void* ctxt) {
|
||||
void link() {
|
||||
cache.fake_scratchpad_data = intern_from_c("*fake-scratchpad-data*").c();
|
||||
gLinkedFunctionTable.reg("(method 32 collide-cache)", execute, 128);
|
||||
max_tri_count = intern_from_c("*collide-cache-max-tris*").cast<u32>().c();
|
||||
}
|
||||
|
||||
} // namespace method_32_collide_cache
|
||||
@@ -542,7 +545,7 @@ u64 execute(void* ctxt) {
|
||||
c->sq(s5, 80, sp); // sq s5, 80(sp)
|
||||
c->sq(gp, 96, sp); // sq gp, 96(sp)
|
||||
// nop // sll r0, r0, 0
|
||||
c->addiu(v1, r0, 460); // addiu v1, r0, 460
|
||||
c->addiu(v1, r0, *max_tri_count); // addiu v1, r0, 460
|
||||
c->lwu(a2, 0, a0); // lwu a2, 0(a0)
|
||||
c->dsubu(t0, v1, a2); // dsubu t0, v1, a2
|
||||
c->addiu(a3, r0, 64); // addiu a3, r0, 64
|
||||
@@ -825,7 +828,7 @@ u64 execute(void* ctxt) {
|
||||
c->sq(s5, 80, sp); // sq s5, 80(sp)
|
||||
c->sq(gp, 96, sp); // sq gp, 96(sp)
|
||||
// nop // sll r0, r0, 0
|
||||
c->addiu(v1, r0, 460); // addiu v1, r0, 460
|
||||
c->addiu(v1, r0, *max_tri_count); // addiu v1, r0, 460
|
||||
c->lwu(a2, 0, a0); // lwu a2, 0(a0)
|
||||
c->dsubu(t0, v1, a2); // dsubu t0, v1, a2
|
||||
c->addiu(a3, r0, 64); // addiu a3, r0, 64
|
||||
@@ -1120,7 +1123,7 @@ u64 execute(void* ctxt) {
|
||||
c->mov64(v1, v0); // or v1, v0, r0
|
||||
// nop // sll r0, r0, 0
|
||||
c->load_symbol(t0, cache.collide_work); // lw t0, *collide-work*(s7)
|
||||
c->addiu(v1, r0, 460); // addiu v1, r0, 460
|
||||
c->addiu(v1, r0, *max_tri_count); // addiu v1, r0, 460
|
||||
c->lwu(a0, 0, gp); // lwu a0, 0(gp)
|
||||
c->dsubu(a2, v1, a0); // dsubu a2, v1, a0
|
||||
c->dsll(a1, a0, 6); // dsll a1, a0, 6
|
||||
@@ -1629,7 +1632,7 @@ u64 execute(void* ctxt) {
|
||||
// nop // sll r0, r0, 0
|
||||
// nop // sll r0, r0, 0
|
||||
c->lwu(a0, 4, gp); // lwu a0, 4(gp)
|
||||
c->addiu(a1, r0, 460); // addiu a1, r0, 460
|
||||
c->addiu(a1, r0, *max_tri_count); // addiu a1, r0, 460
|
||||
c->lwu(v1, 0, gp); // lwu v1, 0(gp)
|
||||
c->dsll32(a0, a0, 0); // dsll32 a0, a0, 0
|
||||
// nop // sll r0, r0, 0
|
||||
@@ -1875,7 +1878,7 @@ u64 execute(void* ctxt) {
|
||||
// nop // sll r0, r0, 0
|
||||
// nop // sll r0, r0, 0
|
||||
c->lwu(a0, 4, gp); // lwu a0, 4(gp)
|
||||
c->addiu(a1, r0, 460); // addiu a1, r0, 460
|
||||
c->addiu(a1, r0, *max_tri_count); // addiu a1, r0, 460
|
||||
c->lwu(v1, 0, gp); // lwu v1, 0(gp)
|
||||
c->dsll32(a0, a0, 0); // dsll32 a0, a0, 0
|
||||
// nop // sll r0, r0, 0
|
||||
@@ -2123,7 +2126,7 @@ u64 execute(void* ctxt) {
|
||||
c->load_symbol(t2, cache.collide_work); // lw t2, *collide-work*(s7)
|
||||
// nop // sll r0, r0, 0
|
||||
c->lwu(a0, 4, gp); // lwu a0, 4(gp)
|
||||
c->addiu(a1, r0, 460); // addiu a1, r0, 460
|
||||
c->addiu(a1, r0, *max_tri_count); // addiu a1, r0, 460
|
||||
c->lwu(v1, 0, gp); // lwu v1, 0(gp)
|
||||
c->dsll32(a0, a0, 0); // dsll32 a0, a0, 0
|
||||
// nop // sll r0, r0, 0
|
||||
|
||||
@@ -191,8 +191,23 @@
|
||||
|
||||
(define-perm *collide-work* collide-work (new 'global 'collide-work))
|
||||
|
||||
(defglobalconstant BIG_COLLIDE_CACHE_SIZE 5000)
|
||||
(define-perm *collide-cache* collide-cache (new 'global 'collide-cache))
|
||||
|
||||
(#when PC_PORT
|
||||
;; disgusting hack to let us overflow the collide cache up to 5k triangles
|
||||
(malloc 'global (* (size-of collide-cache-tri) BIG_COLLIDE_CACHE_SIZE))
|
||||
;; but start it at the default.
|
||||
(define *collide-cache-max-tris* 460)
|
||||
(defmacro activate-big-collide-cache! ()
|
||||
`(set! *collide-cache-max-tris* BIG_COLLIDE_CACHE_SIZE)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
(define-perm *collide-list* collide-list (new 'global 'collide-list))
|
||||
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
)
|
||||
(return #f)
|
||||
)
|
||||
(when (< 460 (+ (-> obj num-tris) 2))
|
||||
(when (< *collide-cache-max-tris* (+ (-> obj num-tris) 2))
|
||||
(when (not *already-printed-exeeded-max-cache-tris*)
|
||||
(set! *already-printed-exeeded-max-cache-tris* #t)
|
||||
(if (= *cheat-mode* 'debug)
|
||||
|
||||
@@ -28,6 +28,7 @@ struct PatSurface {
|
||||
STOPPROJ = 20,
|
||||
ROTATE = 21,
|
||||
NEUTRAL = 22,
|
||||
MAX_MATERIAL = 23
|
||||
};
|
||||
|
||||
enum class Event {
|
||||
@@ -38,6 +39,7 @@ struct PatSurface {
|
||||
DEADLYUP = 4,
|
||||
BURNUP = 5,
|
||||
MELT = 6,
|
||||
MAX_EVENT = 7,
|
||||
};
|
||||
|
||||
void set_noentity(bool x) {
|
||||
@@ -90,7 +92,7 @@ struct PatSurface {
|
||||
|
||||
void set_event(Event ev) {
|
||||
val &= ~(0b111111 << 14);
|
||||
val |= ((u32)ev << 6);
|
||||
val |= ((u32)ev << 14);
|
||||
}
|
||||
Event get_event() const { return (Event)(0b111111 & (val >> 14)); }
|
||||
|
||||
|
||||
@@ -665,6 +665,55 @@ std::optional<std::vector<CollideFace>> subdivide_face_if_needed(CollideFace fac
|
||||
}
|
||||
}
|
||||
|
||||
struct PatResult {
|
||||
bool set = false;
|
||||
bool ignore = false;
|
||||
PatSurface pat;
|
||||
};
|
||||
|
||||
PatResult custom_props_to_pat(const tinygltf::Value& val, const std::string& debug_name) {
|
||||
PatResult result;
|
||||
if (!val.IsObject() || !val.Has("set_collision") || !val.Get("set_collision").Get<int>()) {
|
||||
// unset.
|
||||
result.set = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.set = true;
|
||||
|
||||
if (val.Get("ignore").Get<int>()) {
|
||||
result.ignore = true;
|
||||
return result;
|
||||
}
|
||||
result.ignore = false;
|
||||
|
||||
int mat = val.Get("collide_material").Get<int>();
|
||||
ASSERT(mat < (int)PatSurface::Material::MAX_MATERIAL);
|
||||
result.pat.set_material(PatSurface::Material(mat));
|
||||
|
||||
int evt = val.Get("collide_event").Get<int>();
|
||||
ASSERT(evt < (int)PatSurface::Event::MAX_EVENT);
|
||||
result.pat.set_event(PatSurface::Event(evt));
|
||||
|
||||
if (val.Get("nolineofsight").Get<int>()) {
|
||||
result.pat.set_nolineofsight(true);
|
||||
}
|
||||
|
||||
if (val.Get("noedge").Get<int>()) {
|
||||
result.pat.set_noedge(true);
|
||||
}
|
||||
|
||||
if (val.Get("nocamera").Get<int>()) {
|
||||
result.pat.set_nocamera(true);
|
||||
}
|
||||
|
||||
if (val.Get("noentity").Get<int>()) {
|
||||
result.pat.set_noentity(true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void extract(const Input& in,
|
||||
CollideOutput& out,
|
||||
const tinygltf::Model& model,
|
||||
@@ -675,14 +724,25 @@ void extract(const Input& in,
|
||||
|
||||
for (const auto& n : all_nodes) {
|
||||
const auto& node = model.nodes[n.node_idx];
|
||||
PatResult mesh_default_collide = custom_props_to_pat(node.extras, node.name);
|
||||
if (node.mesh >= 0) {
|
||||
const auto& mesh = model.meshes[node.mesh];
|
||||
if (!mesh.extras.Has("collide")) {
|
||||
// fmt::print("skip collide: {}\n", mesh.name);
|
||||
// continue;
|
||||
}
|
||||
mesh_count++;
|
||||
for (const auto& prim : mesh.primitives) {
|
||||
// get material
|
||||
const auto& mat_idx = prim.material;
|
||||
PatResult pat = mesh_default_collide;
|
||||
if (mat_idx != -1) {
|
||||
const auto& mat = model.materials[mat_idx];
|
||||
auto mat_pat = custom_props_to_pat(mat.extras, mat.name);
|
||||
if (mat_pat.set) {
|
||||
pat = mat_pat;
|
||||
}
|
||||
}
|
||||
|
||||
if (pat.set && pat.ignore) {
|
||||
continue; // skip, no collide here
|
||||
}
|
||||
prim_count++;
|
||||
// extract index buffer
|
||||
std::vector<u32> prim_indices = gltf_index_buffer(model, prim.indices, 0);
|
||||
@@ -728,7 +788,7 @@ void extract(const Input& in,
|
||||
fmt::print("bsphere: {}\n", face.bsphere.to_string_aligned());
|
||||
}
|
||||
}
|
||||
|
||||
face.pat = pat.pat;
|
||||
out.faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user