Compare commits
10 Commits
dc76986cd3
...
cc2eba1352
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc2eba1352 | ||
|
|
cfee60d71a | ||
|
|
61665434ab | ||
|
|
d8795ec076 | ||
|
|
a47306533e | ||
|
|
f708843747 | ||
|
|
a589765b7e | ||
|
|
44dd09fcb5 | ||
|
|
f7e6f063d1 | ||
|
|
db8b9b7a18 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1,3 +1,4 @@
|
|||||||
*.bmp filter=lfs diff=lfs merge=lfs -text
|
*.bmp filter=lfs diff=lfs merge=lfs -text
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.anim filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
65
blender scripts/blender_join_anims.py
Normal file
65
blender scripts/blender_join_anims.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
def append_layered_action_5_0(source_obj_name, target_obj_name):
|
||||||
|
source_obj = bpy.data.objects.get(source_obj_name)
|
||||||
|
target_obj = bpy.data.objects.get(target_obj_name)
|
||||||
|
|
||||||
|
if not (source_obj and target_obj):
|
||||||
|
print("Ошибка: Объекты не найдены")
|
||||||
|
return
|
||||||
|
|
||||||
|
src_action = source_obj.animation_data.action
|
||||||
|
tgt_action = target_obj.animation_data.action
|
||||||
|
|
||||||
|
# 1. Получаем слои (обычно первый)
|
||||||
|
src_layer = src_action.layers[0]
|
||||||
|
tgt_layer = tgt_action.layers[0]
|
||||||
|
|
||||||
|
# 2. Получаем стрипы
|
||||||
|
src_strip = src_layer.strips[0]
|
||||||
|
tgt_strip = tgt_layer.strips[0]
|
||||||
|
|
||||||
|
# Смещение (опираемся на конец диапазона целевого экшена)
|
||||||
|
offset = tgt_action.frame_range[1]
|
||||||
|
|
||||||
|
# 3. Итерируемся по channelbags в исходном стрипе
|
||||||
|
for src_bag in src_strip.channelbags:
|
||||||
|
# Ищем или создаем соответствующий bag в целевом стрипе
|
||||||
|
# Обычно они сопоставляются по имени или типу (например, 'Keyframe Channel Bag')
|
||||||
|
# В простейшем случае берем первый или сопоставляем по индексу
|
||||||
|
dst_bag = None
|
||||||
|
if len(tgt_strip.channelbags) > 0:
|
||||||
|
# Пытаемся найти по названию (если оно есть) или берем тот же индекс
|
||||||
|
dst_bag = tgt_strip.channelbags[0]
|
||||||
|
|
||||||
|
if not dst_bag:
|
||||||
|
# Если в целевом стрипе нет сумок, это странно, но можно создать
|
||||||
|
# (Метод создания может зависеть от конкретного подтипа стрипа в 5.0)
|
||||||
|
continue
|
||||||
|
|
||||||
|
#print(f"Обработка channelbag: {src_bag.name}, кривых: {len(src_bag.fcurves)}")
|
||||||
|
|
||||||
|
# 4. Итерируемся по fcurves внутри сумки
|
||||||
|
for src_fcurve in src_bag.fcurves:
|
||||||
|
dst_fcurve = dst_bag.fcurves.find(src_fcurve.data_path, index=src_fcurve.array_index)
|
||||||
|
|
||||||
|
if not dst_fcurve:
|
||||||
|
dst_fcurve = dst_bag.fcurves.new(data_path=src_fcurve.data_path, index=src_fcurve.array_index)
|
||||||
|
|
||||||
|
# 5. Копируем ключи с офсетом
|
||||||
|
for keyframe in src_fcurve.keyframe_points:
|
||||||
|
new_frame = keyframe.co[0] + offset
|
||||||
|
new_value = keyframe.co[1]
|
||||||
|
|
||||||
|
new_kp = dst_fcurve.keyframe_points.insert(new_frame, new_value, options={'FAST'})
|
||||||
|
new_kp.interpolation = keyframe.interpolation
|
||||||
|
|
||||||
|
# Обновляем интерполяцию
|
||||||
|
for fc in dst_bag.fcurves:
|
||||||
|
fc.update()
|
||||||
|
|
||||||
|
print(f"Анимация успешно дозаписана. Новый конец: {tgt_action.frame_range[1]}")
|
||||||
|
|
||||||
|
append_layered_action_5_0('Armature.001', 'Armature')
|
||||||
|
|
||||||
228
blender scripts/export_anim_001.py
Normal file
228
blender scripts/export_anim_001.py
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import bpy
|
||||||
|
import bmesh
|
||||||
|
|
||||||
|
#!
|
||||||
|
|
||||||
|
# Имена mesh и арматуры
|
||||||
|
mesh_name = "Joined"
|
||||||
|
armature_name = "Armature"
|
||||||
|
|
||||||
|
# Находим объект mesh по имени
|
||||||
|
mesh_obj = bpy.data.objects.get(mesh_name)
|
||||||
|
|
||||||
|
# Находим объект арматуры по имени
|
||||||
|
armature_obj = bpy.data.objects.get(armature_name)
|
||||||
|
|
||||||
|
# Устанавливаем текущий кадр на 0
|
||||||
|
bpy.context.scene.frame_set(0)
|
||||||
|
|
||||||
|
# Принудительно обновляем сцену, чтобы применить анимацию
|
||||||
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
|
# Открываем файл для записи
|
||||||
|
with open("C:\\Work\\Media\\witcher\\2026-04-13\\output\\gg_stand_idle001.txt", "w") as file:
|
||||||
|
# Обработка арматуры и анимации
|
||||||
|
if armature_obj and armature_obj.type == 'ARMATURE':
|
||||||
|
file.write("=== Armature Matrix ===\n")
|
||||||
|
for row in armature_obj.matrix_world:
|
||||||
|
file.write(f"{row}\n")
|
||||||
|
file.write(f"=== Armature Bones: {len(armature_obj.data.bones)}\n")
|
||||||
|
for bone in armature_obj.data.bones:
|
||||||
|
# Записываем имя кости, длину и связи
|
||||||
|
file.write(f"Bone: {bone.name}\n")
|
||||||
|
file.write(f" HEAD_LOCAL: {bone.head_local}\n")
|
||||||
|
file.write(f" TAIL_LOCAL: {bone.tail_local}\n")
|
||||||
|
file.write(f" Length: {(bone.tail_local - bone.head_local).length}\n")
|
||||||
|
for row in bone.matrix:
|
||||||
|
file.write(f" {row}\n")
|
||||||
|
file.write(f" Parent: {bone.parent.name if bone.parent else 'None'}\n")
|
||||||
|
file.write(f" Children: {[child.name for child in bone.children]}\n")
|
||||||
|
|
||||||
|
# Обработка mesh
|
||||||
|
if mesh_obj and mesh_obj.type == 'MESH':
|
||||||
|
# Создаем копию mesh, чтобы не изменять оригинал
|
||||||
|
mesh_copy = mesh_obj.copy()
|
||||||
|
mesh_copy.data = mesh_obj.data.copy()
|
||||||
|
bpy.context.collection.objects.link(mesh_copy)
|
||||||
|
|
||||||
|
# Убедимся, что объект активен
|
||||||
|
bpy.context.view_layer.objects.active = mesh_copy
|
||||||
|
mesh_copy.select_set(True)
|
||||||
|
|
||||||
|
# Применяем модификатор Armature (если он есть)
|
||||||
|
for modifier in mesh_copy.modifiers:
|
||||||
|
if modifier.type == 'ARMATURE':
|
||||||
|
# Включаем модификатор, если он отключен
|
||||||
|
if not modifier.show_viewport:
|
||||||
|
modifier.show_viewport = True
|
||||||
|
if not modifier.show_render:
|
||||||
|
modifier.show_render = True
|
||||||
|
|
||||||
|
# Проверяем, что модификатор связан с арматурой
|
||||||
|
if modifier.object is None:
|
||||||
|
print(f"Модификатор Armature на объекте {mesh_copy.name} не связан с арматурой. Пропускаем.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Временно применяем модификатор, чтобы получить правильные координаты вершин
|
||||||
|
try:
|
||||||
|
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"Ошибка при применении модификатора Armature: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Переходим в режим редактирования
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
# Получаем BMesh представление mesh
|
||||||
|
bm = bmesh.from_edit_mesh(mesh_copy.data)
|
||||||
|
|
||||||
|
# Записываем список вершин
|
||||||
|
file.write(f"===Vertices: {len(bm.verts)}\n")
|
||||||
|
for vertex in bm.verts:
|
||||||
|
file.write(f"Vertex {vertex.index}: {vertex.co}\n")
|
||||||
|
|
||||||
|
# Убедимся, что у меша есть UV слой
|
||||||
|
uv_layer = bm.loops.layers.uv.active
|
||||||
|
if not uv_layer:
|
||||||
|
file.write("UV слой не найден.\n")
|
||||||
|
|
||||||
|
if uv_layer:
|
||||||
|
file.write(f"===UV Coordinates:\n")
|
||||||
|
file.write(f"Face count: {len(bm.faces)}\n")
|
||||||
|
|
||||||
|
for face in bm.faces:
|
||||||
|
file.write(f"Face {face.index}\n")
|
||||||
|
file.write(f"UV Count: {len(face.loops)}\n")
|
||||||
|
for loop in face.loops:
|
||||||
|
uv_coords = loop[uv_layer].uv
|
||||||
|
file.write(f" UV {uv_coords}\n")
|
||||||
|
|
||||||
|
# Записываем нормали
|
||||||
|
file.write(f"===Normals:\n")
|
||||||
|
for vertex in bm.verts:
|
||||||
|
file.write(f"Vertex {vertex.index}: Normal {vertex.normal}\n")
|
||||||
|
|
||||||
|
# Записываем треугольники (индексы вершин)
|
||||||
|
file.write(f"===Triangles: {len(bm.faces)}\n")
|
||||||
|
for face in bm.faces:
|
||||||
|
if len(face.verts) == 3: # Проверяем, что это треугольник
|
||||||
|
verts_indices = [vert.index for vert in face.verts]
|
||||||
|
file.write(f"Triangle: {verts_indices}\n")
|
||||||
|
|
||||||
|
# Возвращаемся в объектный режим
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
# Записываем веса вершин
|
||||||
|
file.write("=== Vertex Weights (Max 5 bones per vertex) ===\n")
|
||||||
|
|
||||||
|
MAX_BONES = 5
|
||||||
|
|
||||||
|
for vertex in mesh_copy.data.vertices:
|
||||||
|
# Извлекаем все группы и веса для текущей вершины
|
||||||
|
all_weights = []
|
||||||
|
for group_element in vertex.groups:
|
||||||
|
all_weights.append({
|
||||||
|
'index': group_element.group,
|
||||||
|
'weight': group_element.weight
|
||||||
|
})
|
||||||
|
|
||||||
|
# Если костей больше лимита, фильтруем и перераспределяем
|
||||||
|
if len(all_weights) > MAX_BONES:
|
||||||
|
# Сортируем по весу (от большего к меньшему)
|
||||||
|
all_weights.sort(key=lambda x: x['weight'], reverse=True)
|
||||||
|
|
||||||
|
# Берем только топ-5
|
||||||
|
kept_weights = all_weights[:MAX_BONES]
|
||||||
|
|
||||||
|
# Считаем сумму весов оставшихся костей для нормализации
|
||||||
|
total_weight = sum(gw['weight'] for gw in kept_weights)
|
||||||
|
|
||||||
|
if total_weight > 0:
|
||||||
|
for gw in kept_weights:
|
||||||
|
gw['weight'] /= total_weight
|
||||||
|
else:
|
||||||
|
# На случай, если у всех веса были по 0.0 (редкий баг меша)
|
||||||
|
kept_weights[0]['weight'] = 1.0
|
||||||
|
|
||||||
|
final_weights = kept_weights
|
||||||
|
else:
|
||||||
|
final_weights = all_weights
|
||||||
|
|
||||||
|
file.write(f"Vertex {vertex.index}:\n")
|
||||||
|
file.write(f"Vertex groups: {len(final_weights)}\n")
|
||||||
|
|
||||||
|
for gw in final_weights:
|
||||||
|
group_name = mesh_copy.vertex_groups[gw['index']].name
|
||||||
|
file.write(f" Group: '{group_name}', Weight: {gw['weight']:.6f}\n")
|
||||||
|
|
||||||
|
# Удаляем временную копию mesh
|
||||||
|
bpy.data.objects.remove(mesh_copy)
|
||||||
|
else:
|
||||||
|
file.write(f"Объект с именем '{mesh_name}' не найден или не является mesh.\n")
|
||||||
|
|
||||||
|
# Обработка арматуры и анимации
|
||||||
|
if armature_obj and armature_obj.type == 'ARMATURE':
|
||||||
|
|
||||||
|
# Получаем все ключевые кадры для арматуры
|
||||||
|
file.write("=== Animation Keyframes ===\n")
|
||||||
|
if armature_obj.animation_data and armature_obj.animation_data.action:
|
||||||
|
action = armature_obj.animation_data.action
|
||||||
|
|
||||||
|
# Собираем все уникальные ключевые кадры
|
||||||
|
keyframes = set()
|
||||||
|
|
||||||
|
# Логика для Blender 5.0 (Strip-based / ChannelBag structure)
|
||||||
|
if hasattr(action, "layers"):
|
||||||
|
for layer in action.layers:
|
||||||
|
if hasattr(layer, "strips"):
|
||||||
|
for strip in layer.strips:
|
||||||
|
# Проверяем наличие channelbags (согласно вашему dir(strip))
|
||||||
|
if hasattr(strip, "channelbags"):
|
||||||
|
for bag in strip.channelbags:
|
||||||
|
for fcurve in bag.fcurves:
|
||||||
|
for keyframe in fcurve.keyframe_points:
|
||||||
|
keyframes.add(int(keyframe.co[0]))
|
||||||
|
|
||||||
|
# На случай, если в этой версии используется единственное число
|
||||||
|
elif hasattr(strip, "channelbag") and strip.channelbag:
|
||||||
|
for fcurve in strip.channelbag.fcurves:
|
||||||
|
for keyframe in fcurve.keyframe_points:
|
||||||
|
keyframes.add(int(keyframe.co[0]))
|
||||||
|
|
||||||
|
# Фоллбек для Legacy экшенов
|
||||||
|
if not keyframes and hasattr(action, "fcurves"):
|
||||||
|
for fcurve in action.fcurves:
|
||||||
|
for keyframe in fcurve.keyframe_points:
|
||||||
|
keyframes.add(int(keyframe.co[0]))
|
||||||
|
|
||||||
|
keyframes = sorted(keyframes)
|
||||||
|
|
||||||
|
# Сортируем ключевые кадры
|
||||||
|
keyframes = sorted(keyframes)
|
||||||
|
|
||||||
|
# Сохраняем координаты и матрицы поворота для каждой кости на каждом ключевом кадре
|
||||||
|
file.write("=== Bone Transforms per Keyframe ===\n")
|
||||||
|
file.write(f"Keyframes: {len(keyframes)}\n")
|
||||||
|
for frame in keyframes:
|
||||||
|
# Устанавливаем текущий кадр
|
||||||
|
bpy.context.scene.frame_set(frame)
|
||||||
|
bpy.context.view_layer.update() # Обновляем сцену
|
||||||
|
|
||||||
|
file.write(f"Frame: {frame}\n")
|
||||||
|
for bone in armature_obj.pose.bones:
|
||||||
|
# Получаем координаты и матрицу поворота кости в мировом пространстве
|
||||||
|
matrix = bone.matrix
|
||||||
|
location = matrix.translation
|
||||||
|
rotation = matrix.to_euler()
|
||||||
|
|
||||||
|
# Записываем данные
|
||||||
|
file.write(f" Bone: {bone.name}\n")
|
||||||
|
file.write(f" Location: {location}\n")
|
||||||
|
file.write(f" Rotation: {rotation}\n")
|
||||||
|
file.write(f" Matrix:\n")
|
||||||
|
for row in matrix:
|
||||||
|
file.write(f" {row}\n")
|
||||||
|
else:
|
||||||
|
file.write(f"Объект с именем '{armature_name}' не найден или не является арматурой.\n")
|
||||||
|
|
||||||
|
print("Данные сохранены в файл 'mesh_armature_and_animation_data.txt'")
|
||||||
312
convert_anim_to_binary.py
Normal file
312
convert_anim_to_binary.py
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Convert a text-based bone animation file to the BSAF binary format.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python convert_anim_to_binary.py <input.txt> <output.bin>
|
||||||
|
|
||||||
|
Binary format (BSAF v1) -- all values little-endian:
|
||||||
|
|
||||||
|
HEADER
|
||||||
|
4 bytes magic "BSAF"
|
||||||
|
uint32 version (1)
|
||||||
|
|
||||||
|
BONES
|
||||||
|
uint32 numBones
|
||||||
|
per bone:
|
||||||
|
3 x float boneStartWorld (from HEAD_LOCAL)
|
||||||
|
float boneLength
|
||||||
|
9 x float 3x3 rotation matrix (row-major)
|
||||||
|
int32 parentIndex (-1 if none)
|
||||||
|
uint32 numChildren
|
||||||
|
numChildren x int32 childIndices
|
||||||
|
|
||||||
|
VERTICES
|
||||||
|
uint32 numVertices
|
||||||
|
numVertices x 3 x float positions
|
||||||
|
|
||||||
|
UV COORDINATES
|
||||||
|
uint32 numFaces
|
||||||
|
numFaces x 6 x float 3 UV pairs per face (u0,v0,u1,v1,u2,v2)
|
||||||
|
|
||||||
|
NORMALS
|
||||||
|
numVertices x 3 x float normals
|
||||||
|
|
||||||
|
TRIANGLES
|
||||||
|
uint32 numTriangles
|
||||||
|
numTriangles x 3 x int32 vertex indices
|
||||||
|
|
||||||
|
VERTEX WEIGHTS
|
||||||
|
per vertex (numVertices):
|
||||||
|
uint32 numGroups
|
||||||
|
numGroups x (int32 boneIndex, float weight)
|
||||||
|
|
||||||
|
ANIMATION KEYFRAMES
|
||||||
|
uint32 numKeyframes
|
||||||
|
per keyframe:
|
||||||
|
int32 frameNumber
|
||||||
|
per bone (numBones, in index order 0..N-1):
|
||||||
|
3 x float location
|
||||||
|
16 x float 4x4 matrix (row-major)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def parse_floats(line):
|
||||||
|
return [float(x) for x in re.findall(r'[-]?\d+\.\d+', line)]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_first_int(line):
|
||||||
|
m = re.search(r'\d+', line)
|
||||||
|
if m:
|
||||||
|
return int(m.group())
|
||||||
|
raise ValueError(f"No integer found in: {line}")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_children(line):
|
||||||
|
return re.findall(r"'([^']+)'", line)
|
||||||
|
|
||||||
|
|
||||||
|
def convert(input_path, output_path):
|
||||||
|
with open(input_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
def next_line():
|
||||||
|
nonlocal idx
|
||||||
|
line = lines[idx].rstrip()
|
||||||
|
idx += 1
|
||||||
|
return line
|
||||||
|
|
||||||
|
# --- Skip armature matrix (5 lines) ---
|
||||||
|
for _ in range(5):
|
||||||
|
next_line()
|
||||||
|
|
||||||
|
# --- Bone count ---
|
||||||
|
line = next_line() # "=== Armature Bones: 65"
|
||||||
|
num_bones = parse_first_int(line)
|
||||||
|
|
||||||
|
bone_names = []
|
||||||
|
bones = []
|
||||||
|
bone_parent_names = []
|
||||||
|
bone_children_names = []
|
||||||
|
|
||||||
|
for _ in range(num_bones):
|
||||||
|
bone = {}
|
||||||
|
|
||||||
|
# "Bone: mixamorig:Hips"
|
||||||
|
line = next_line()
|
||||||
|
bone_name = line[6:]
|
||||||
|
bone_names.append(bone_name)
|
||||||
|
|
||||||
|
# " HEAD_LOCAL: <Vector (x, y, z)>"
|
||||||
|
line = next_line()
|
||||||
|
bone['head'] = parse_floats(line)[:3]
|
||||||
|
|
||||||
|
# " TAIL_LOCAL: ..." -- skip
|
||||||
|
next_line()
|
||||||
|
|
||||||
|
# " Length: 0.123"
|
||||||
|
line = next_line()
|
||||||
|
bone['length'] = parse_floats(line)[0]
|
||||||
|
|
||||||
|
# 3x3 matrix (3 rows)
|
||||||
|
mat = []
|
||||||
|
for _ in range(3):
|
||||||
|
mat.extend(parse_floats(next_line()))
|
||||||
|
bone['matrix_3x3'] = mat
|
||||||
|
|
||||||
|
# " Parent: None" or " Parent: boneName"
|
||||||
|
line = next_line()
|
||||||
|
if line == " Parent: None":
|
||||||
|
bone_parent_names.append(None)
|
||||||
|
else:
|
||||||
|
bone_parent_names.append(line[10:])
|
||||||
|
|
||||||
|
# " Children: ['a', 'b'] or []"
|
||||||
|
line = next_line()
|
||||||
|
bone_children_names.append(parse_children(line))
|
||||||
|
|
||||||
|
bones.append(bone)
|
||||||
|
|
||||||
|
# Build name -> index map
|
||||||
|
name_to_idx = {name: i for i, name in enumerate(bone_names)}
|
||||||
|
|
||||||
|
# Resolve parent / child indices
|
||||||
|
for i in range(num_bones):
|
||||||
|
if bone_parent_names[i] is None:
|
||||||
|
bones[i]['parent'] = -1
|
||||||
|
else:
|
||||||
|
bones[i]['parent'] = name_to_idx[bone_parent_names[i]]
|
||||||
|
bones[i]['children'] = [name_to_idx[c] for c in bone_children_names[i]]
|
||||||
|
|
||||||
|
# --- Vertices ---
|
||||||
|
line = next_line() # "===Vertices: 5140"
|
||||||
|
num_vertices = parse_first_int(line)
|
||||||
|
|
||||||
|
vertices = []
|
||||||
|
for _ in range(num_vertices):
|
||||||
|
vertices.append(parse_floats(next_line())[:3])
|
||||||
|
|
||||||
|
# --- UV Coordinates ---
|
||||||
|
next_line() # "===UV Coordinates:"
|
||||||
|
line = next_line() # "Face count: 8602"
|
||||||
|
num_faces = parse_first_int(line)
|
||||||
|
|
||||||
|
uvs = []
|
||||||
|
for _ in range(num_faces):
|
||||||
|
next_line() # "Face N"
|
||||||
|
next_line() # "UV Count: 3"
|
||||||
|
face_uvs = []
|
||||||
|
for _ in range(3):
|
||||||
|
face_uvs.extend(parse_floats(next_line())[:2])
|
||||||
|
uvs.append(face_uvs) # 6 floats
|
||||||
|
|
||||||
|
# --- Normals ---
|
||||||
|
next_line() # "===Normals:"
|
||||||
|
|
||||||
|
normals = []
|
||||||
|
for _ in range(num_vertices):
|
||||||
|
normals.append(parse_floats(next_line())[:3])
|
||||||
|
|
||||||
|
# --- Triangles ---
|
||||||
|
line = next_line() # "===Triangles: 8602"
|
||||||
|
num_triangles = parse_first_int(line)
|
||||||
|
|
||||||
|
triangles = []
|
||||||
|
for _ in range(num_triangles):
|
||||||
|
line = next_line()
|
||||||
|
ints = [int(x) for x in re.findall(r'[-]?\d+', line)]
|
||||||
|
triangles.append(ints[:3])
|
||||||
|
|
||||||
|
# --- Vertex Weights ---
|
||||||
|
next_line() # "=== Vertex Weights ..."
|
||||||
|
|
||||||
|
vertex_weights = []
|
||||||
|
for _ in range(num_vertices):
|
||||||
|
next_line() # "Vertex N:"
|
||||||
|
line = next_line() # "Vertex groups: 2"
|
||||||
|
num_groups = parse_first_int(line)
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
for _ in range(num_groups):
|
||||||
|
line = next_line()
|
||||||
|
m = re.search(r"'([^']+)'.*?([-]?\d+\.\d+)", line)
|
||||||
|
bone_name = m.group(1)
|
||||||
|
weight = float(m.group(2))
|
||||||
|
groups.append((name_to_idx[bone_name], weight))
|
||||||
|
|
||||||
|
vertex_weights.append(groups)
|
||||||
|
|
||||||
|
# --- Animation Keyframes ---
|
||||||
|
next_line() # "=== Animation Keyframes ==="
|
||||||
|
next_line() # "=== Bone Transforms per Keyframe ==="
|
||||||
|
line = next_line() # "Keyframes: 32"
|
||||||
|
num_keyframes = parse_first_int(line)
|
||||||
|
|
||||||
|
keyframes = []
|
||||||
|
for _ in range(num_keyframes):
|
||||||
|
line = next_line() # "Frame: 0"
|
||||||
|
frame_number = parse_first_int(line)
|
||||||
|
|
||||||
|
bone_data = {}
|
||||||
|
for _ in range(num_bones):
|
||||||
|
line = next_line() # " Bone: mixamorig:Hips"
|
||||||
|
bone_name = line.strip()
|
||||||
|
if bone_name.startswith("Bone: "):
|
||||||
|
bone_name = bone_name[6:]
|
||||||
|
bone_idx = name_to_idx[bone_name]
|
||||||
|
|
||||||
|
# Location
|
||||||
|
location = parse_floats(next_line())[:3]
|
||||||
|
|
||||||
|
# Rotation (skip)
|
||||||
|
next_line()
|
||||||
|
|
||||||
|
# " Matrix:" (skip header)
|
||||||
|
next_line()
|
||||||
|
|
||||||
|
# 4 rows of 4 floats
|
||||||
|
matrix = []
|
||||||
|
for _ in range(4):
|
||||||
|
matrix.extend(parse_floats(next_line()))
|
||||||
|
|
||||||
|
bone_data[bone_idx] = {
|
||||||
|
'location': location,
|
||||||
|
'matrix': matrix,
|
||||||
|
}
|
||||||
|
|
||||||
|
keyframes.append((frame_number, bone_data))
|
||||||
|
|
||||||
|
# ================================================================
|
||||||
|
# Write binary file
|
||||||
|
# ================================================================
|
||||||
|
with open(output_path, 'wb') as out:
|
||||||
|
# Header
|
||||||
|
out.write(b'BSAF')
|
||||||
|
out.write(struct.pack('<I', 1))
|
||||||
|
|
||||||
|
# Bones
|
||||||
|
out.write(struct.pack('<I', num_bones))
|
||||||
|
for i in range(num_bones):
|
||||||
|
b = bones[i]
|
||||||
|
out.write(struct.pack('<3f', *b['head']))
|
||||||
|
out.write(struct.pack('<f', b['length']))
|
||||||
|
out.write(struct.pack('<9f', *b['matrix_3x3']))
|
||||||
|
out.write(struct.pack('<i', b['parent']))
|
||||||
|
out.write(struct.pack('<I', len(b['children'])))
|
||||||
|
for c in b['children']:
|
||||||
|
out.write(struct.pack('<i', c))
|
||||||
|
|
||||||
|
# Vertices
|
||||||
|
out.write(struct.pack('<I', num_vertices))
|
||||||
|
for v in vertices:
|
||||||
|
out.write(struct.pack('<3f', *v))
|
||||||
|
|
||||||
|
# UV Coordinates
|
||||||
|
out.write(struct.pack('<I', num_faces))
|
||||||
|
for uv in uvs:
|
||||||
|
out.write(struct.pack('<6f', *uv))
|
||||||
|
|
||||||
|
# Normals
|
||||||
|
for n in normals:
|
||||||
|
out.write(struct.pack('<3f', *n))
|
||||||
|
|
||||||
|
# Triangles
|
||||||
|
out.write(struct.pack('<I', num_triangles))
|
||||||
|
for t in triangles:
|
||||||
|
out.write(struct.pack('<3i', *t))
|
||||||
|
|
||||||
|
# Vertex Weights
|
||||||
|
for vw in vertex_weights:
|
||||||
|
out.write(struct.pack('<I', len(vw)))
|
||||||
|
for bone_idx, weight in vw:
|
||||||
|
out.write(struct.pack('<if', bone_idx, weight))
|
||||||
|
|
||||||
|
# Animation Keyframes
|
||||||
|
out.write(struct.pack('<I', num_keyframes))
|
||||||
|
for frame_num, bone_data in keyframes:
|
||||||
|
out.write(struct.pack('<i', frame_num))
|
||||||
|
for i in range(num_bones):
|
||||||
|
bd = bone_data[i]
|
||||||
|
out.write(struct.pack('<3f', *bd['location']))
|
||||||
|
out.write(struct.pack('<16f', *bd['matrix']))
|
||||||
|
|
||||||
|
input_size = sum(len(l) for l in lines)
|
||||||
|
import os
|
||||||
|
output_size = os.path.getsize(output_path)
|
||||||
|
print(f"Converted: {input_path} ({input_size:,} bytes text) -> {output_path} ({output_size:,} bytes binary)")
|
||||||
|
print(f" Bones: {num_bones}, Vertices: {num_vertices}, Faces: {num_faces}, "
|
||||||
|
f"Triangles: {num_triangles}, Keyframes: {num_keyframes}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print(f"Usage: {sys.argv[0]} <input.txt> <output.bin>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
convert(sys.argv[1], sys.argv[2])
|
||||||
@ -48,6 +48,8 @@ add_executable(space-game001
|
|||||||
# ../src/planet/StoneObject.h
|
# ../src/planet/StoneObject.h
|
||||||
../src/render/FrameBuffer.cpp
|
../src/render/FrameBuffer.cpp
|
||||||
../src/render/FrameBuffer.h
|
../src/render/FrameBuffer.h
|
||||||
|
../src/render/ShadowMap.cpp
|
||||||
|
../src/render/ShadowMap.h
|
||||||
../src/UiManager.cpp
|
../src/UiManager.cpp
|
||||||
../src/UiManager.h
|
../src/UiManager.h
|
||||||
../src/Projectile.h
|
../src/Projectile.h
|
||||||
@ -111,7 +113,8 @@ target_compile_definitions(space-game001 PRIVATE
|
|||||||
WIN32_LEAN_AND_MEAN
|
WIN32_LEAN_AND_MEAN
|
||||||
PNG_ENABLED
|
PNG_ENABLED
|
||||||
SDL_MAIN_HANDLED
|
SDL_MAIN_HANDLED
|
||||||
SHOW_PATH
|
# DEBUG_LIGHT
|
||||||
|
# SHOW_PATH
|
||||||
# NETWORK
|
# NETWORK
|
||||||
# SIMPLIFIED
|
# SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
"id": "npc_01_default",
|
"id": "npc_01_default",
|
||||||
"name": "NPC Default Guard",
|
"name": "NPC Default Guard",
|
||||||
"texturePath": "resources/w/default_skin001.png",
|
"texturePath": "resources/w/default_skin001.png",
|
||||||
"animationIdlePath": "resources/w/default_idle002.txt",
|
"animationIdlePath": "resources/w/default_idle002.anim",
|
||||||
"animationWalkPath": "resources/w/default_walk001.txt",
|
"animationWalkPath": "resources/w/default_walk001.anim",
|
||||||
"positionX": 0.0,
|
"positionX": 0.0,
|
||||||
"positionY": 0.0,
|
"positionY": 0.0,
|
||||||
"positionZ": -10.0,
|
"positionZ": -10.0,
|
||||||
@ -15,6 +15,7 @@
|
|||||||
"modelCorrectionRotX": 0.0,
|
"modelCorrectionRotX": 0.0,
|
||||||
"modelCorrectionRotY": 180.0,
|
"modelCorrectionRotY": 180.0,
|
||||||
"modelCorrectionRotZ": 0.0,
|
"modelCorrectionRotZ": 0.0,
|
||||||
|
"interactionRadius": 2.0,
|
||||||
"gift": {
|
"gift": {
|
||||||
"id": "guard_token",
|
"id": "guard_token",
|
||||||
"name": "Guard's Token",
|
"name": "Guard's Token",
|
||||||
@ -26,8 +27,8 @@
|
|||||||
"id": "npc_03_ghost",
|
"id": "npc_03_ghost",
|
||||||
"name": "NPC Floating Ghost",
|
"name": "NPC Floating Ghost",
|
||||||
"texturePath": "resources/w/ghost_skin001.png",
|
"texturePath": "resources/w/ghost_skin001.png",
|
||||||
"animationIdlePath": "resources/w/default_float001.txt",
|
"animationIdlePath": "resources/w/default_float001.anim",
|
||||||
"animationWalkPath": "resources/w/default_float001.txt",
|
"animationWalkPath": "resources/w/default_float001.anim",
|
||||||
"positionX": 0.0,
|
"positionX": 0.0,
|
||||||
"positionY": 0.0,
|
"positionY": 0.0,
|
||||||
"positionZ": -5.0,
|
"positionZ": -5.0,
|
||||||
@ -37,6 +38,7 @@
|
|||||||
"modelCorrectionRotX": 0.0,
|
"modelCorrectionRotX": 0.0,
|
||||||
"modelCorrectionRotY": 0.0,
|
"modelCorrectionRotY": 0.0,
|
||||||
"modelCorrectionRotZ": 0.0,
|
"modelCorrectionRotZ": 0.0,
|
||||||
|
"interactionRadius": 1.0,
|
||||||
"gift": {
|
"gift": {
|
||||||
"id": "ghost_essence",
|
"id": "ghost_essence",
|
||||||
"name": "Ghost's Essence",
|
"name": "Ghost's Essence",
|
||||||
|
|||||||
@ -26,18 +26,25 @@
|
|||||||
{
|
{
|
||||||
"type": "FrameLayout",
|
"type": "FrameLayout",
|
||||||
"name": "inventory_items_panel",
|
"name": "inventory_items_panel",
|
||||||
"x": 100.0,
|
"x": 50.0,
|
||||||
"y": 150.0,
|
"y": 150.0,
|
||||||
"width": 500.0,
|
"width": 250.0,
|
||||||
"height": 600.0,
|
"height": 300.0,
|
||||||
"children": [
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "StaticImage",
|
||||||
|
"name": "panel_background",
|
||||||
|
"width": 200,
|
||||||
|
"height": 400,
|
||||||
|
"texture": "resources/sand2.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "TextView",
|
"type": "TextView",
|
||||||
"name": "inventory_items_text",
|
"name": "inventory_items_text",
|
||||||
"x": 100.0,
|
"x": -100.0,
|
||||||
"y": 30.0,
|
"y": -100.0,
|
||||||
"width": 460.0,
|
"width": 250.0,
|
||||||
"height": 450.0,
|
"height": 300.0,
|
||||||
"text": "Inventory (Empty)",
|
"text": "Inventory (Empty)",
|
||||||
"fontSize": 18,
|
"fontSize": 18,
|
||||||
"fontPath": "resources/fonts/DroidSans.ttf",
|
"fontPath": "resources/fonts/DroidSans.ttf",
|
||||||
@ -47,8 +54,8 @@
|
|||||||
{
|
{
|
||||||
"type": "TextButton",
|
"type": "TextButton",
|
||||||
"name": "close_inventory_button",
|
"name": "close_inventory_button",
|
||||||
"x": 200.0,
|
"x": 165.0,
|
||||||
"y": 20.0,
|
"y": 0.0,
|
||||||
"width": 40.0,
|
"width": 40.0,
|
||||||
"height": 40.0,
|
"height": 40.0,
|
||||||
"text": "X",
|
"text": "X",
|
||||||
|
|||||||
@ -176,7 +176,8 @@
|
|||||||
"speaker": "Ghost",
|
"speaker": "Ghost",
|
||||||
"portrait": "resources/ghost_avatar.png",
|
"portrait": "resources/ghost_avatar.png",
|
||||||
"text": "Some memories never fade.",
|
"text": "Some memories never fade.",
|
||||||
"durationMs": 2600
|
"durationMs": 2600,
|
||||||
|
"background": "resources/loading.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
19
resources/shaders/default_shadow.vertex
Normal file
19
resources/shaders/default_shadow.vertex
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
attribute vec3 vPosition;
|
||||||
|
attribute vec2 vTexCoord;
|
||||||
|
attribute vec3 vNormal;
|
||||||
|
|
||||||
|
varying vec2 texCoord;
|
||||||
|
varying vec4 fragPosLightSpace;
|
||||||
|
varying vec3 fragNormal;
|
||||||
|
|
||||||
|
uniform mat4 ProjectionModelViewMatrix;
|
||||||
|
uniform mat4 ModelViewMatrix;
|
||||||
|
uniform mat4 uLightFromCamera;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = ProjectionModelViewMatrix * vec4(vPosition, 1.0);
|
||||||
|
texCoord = vTexCoord;
|
||||||
|
fragPosLightSpace = uLightFromCamera * ModelViewMatrix * vec4(vPosition, 1.0);
|
||||||
|
fragNormal = mat3(ModelViewMatrix) * vNormal;
|
||||||
|
}
|
||||||
70
resources/shaders/default_shadow_desktop.fragment
Normal file
70
resources/shaders/default_shadow_desktop.fragment
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
uniform sampler2D Texture;
|
||||||
|
uniform sampler2D uShadowMap;
|
||||||
|
uniform vec3 uLightDir;
|
||||||
|
|
||||||
|
varying vec2 texCoord;
|
||||||
|
varying vec4 fragPosLightSpace;
|
||||||
|
varying vec3 fragNormal;
|
||||||
|
|
||||||
|
float computeShadow(vec4 lightSpacePos, vec3 normal)
|
||||||
|
{
|
||||||
|
vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
|
||||||
|
projCoords = projCoords * 0.5 + 0.5;
|
||||||
|
|
||||||
|
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
||||||
|
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
||||||
|
projCoords.z > 1.0)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float currentDepth = projCoords.z;
|
||||||
|
|
||||||
|
// Slope-dependent bias: large for grazing angles, tiny for surfaces facing the light
|
||||||
|
float bias = 0.0004;
|
||||||
|
if (dot(normal, normal) > 0.001)
|
||||||
|
{
|
||||||
|
float cosTheta = dot(normalize(normal), -normalize(uLightDir));
|
||||||
|
bias = max(0.002 * (1.0 - cosTheta), 0.0002);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3x3 PCF (percentage-closer filtering) for softer shadows
|
||||||
|
float shadow = 0.0;
|
||||||
|
float texelSize = 1.0 / 2048.0;
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (int y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
float pcfDepth = texture2D(uShadowMap, projCoords.xy + vec2(float(x), float(y)) * texelSize).r;
|
||||||
|
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shadow /= 9.0;
|
||||||
|
|
||||||
|
return shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 color = texture2D(Texture, texCoord);
|
||||||
|
|
||||||
|
float ambient = 0.4;
|
||||||
|
float diffuseStrength = 0.6;
|
||||||
|
|
||||||
|
// Compute diffuse term; if normals are missing (zero-length) treat as fully lit
|
||||||
|
float diffuse = 1.0;
|
||||||
|
vec3 n = fragNormal;
|
||||||
|
if (dot(n, n) > 0.001)
|
||||||
|
{
|
||||||
|
n = normalize(n);
|
||||||
|
diffuse = max(dot(n, -normalize(uLightDir)), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float shadow = computeShadow(fragPosLightSpace, fragNormal);
|
||||||
|
|
||||||
|
// Shadow dims only the diffuse contribution; ambient is always present
|
||||||
|
float lighting = ambient + diffuseStrength * diffuse * (1.0 - shadow);
|
||||||
|
color.rgb *= lighting;
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
68
resources/shaders/default_shadow_web.fragment
Normal file
68
resources/shaders/default_shadow_web.fragment
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
uniform sampler2D uShadowMap;
|
||||||
|
uniform vec3 uLightDir;
|
||||||
|
|
||||||
|
varying vec2 texCoord;
|
||||||
|
varying vec4 fragPosLightSpace;
|
||||||
|
varying vec3 fragNormal;
|
||||||
|
|
||||||
|
float computeShadow(vec4 lightSpacePos, vec3 normal)
|
||||||
|
{
|
||||||
|
vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
|
||||||
|
projCoords = projCoords * 0.5 + 0.5;
|
||||||
|
|
||||||
|
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
|
||||||
|
projCoords.y < 0.0 || projCoords.y > 1.0 ||
|
||||||
|
projCoords.z > 1.0)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float currentDepth = projCoords.z;
|
||||||
|
|
||||||
|
float bias = 0.0004;
|
||||||
|
if (dot(normal, normal) > 0.001)
|
||||||
|
{
|
||||||
|
float cosTheta = dot(normalize(normal), -normalize(uLightDir));
|
||||||
|
bias = max(0.002 * (1.0 - cosTheta), 0.0002);
|
||||||
|
}
|
||||||
|
|
||||||
|
float shadow = 0.0;
|
||||||
|
float texelSize = 1.0 / 2048.0;
|
||||||
|
for (int x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (int y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
float pcfDepth = texture2D(uShadowMap, projCoords.xy + vec2(float(x), float(y)) * texelSize).r;
|
||||||
|
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shadow /= 9.0;
|
||||||
|
|
||||||
|
return shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 color = texture2D(Texture, texCoord);
|
||||||
|
|
||||||
|
float ambient = 0.4;
|
||||||
|
float diffuseStrength = 0.6;
|
||||||
|
|
||||||
|
float diffuse = 1.0;
|
||||||
|
vec3 n = fragNormal;
|
||||||
|
if (dot(n, n) > 0.001)
|
||||||
|
{
|
||||||
|
n = normalize(n);
|
||||||
|
diffuse = max(dot(n, -normalize(uLightDir)), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float shadow = computeShadow(fragPosLightSpace, fragNormal);
|
||||||
|
|
||||||
|
float lighting = ambient + diffuseStrength * diffuse * (1.0 - shadow);
|
||||||
|
color.rgb *= lighting;
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
|
}
|
||||||
9
resources/shaders/shadow_depth.vertex
Normal file
9
resources/shaders/shadow_depth.vertex
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
attribute vec3 vPosition;
|
||||||
|
attribute vec2 vTexCoord;
|
||||||
|
|
||||||
|
uniform mat4 ProjectionModelViewMatrix;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = ProjectionModelViewMatrix * vec4(vPosition, 1.0);
|
||||||
|
}
|
||||||
5
resources/shaders/shadow_depth_desktop.fragment
Normal file
5
resources/shaders/shadow_depth_desktop.fragment
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Depth is written automatically by the GPU.
|
||||||
|
// Nothing to do here.
|
||||||
|
}
|
||||||
47
resources/shaders/shadow_depth_skinning.vertex
Normal file
47
resources/shaders/shadow_depth_skinning.vertex
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
attribute vec3 vPosition;
|
||||||
|
attribute vec2 vTexCoord;
|
||||||
|
attribute vec4 aBoneIndices0;
|
||||||
|
attribute vec2 aBoneIndices1;
|
||||||
|
attribute vec4 aBoneWeights0;
|
||||||
|
attribute vec2 aBoneWeights1;
|
||||||
|
|
||||||
|
uniform mat4 ProjectionModelViewMatrix;
|
||||||
|
uniform mat4 uBoneMatrices[64];
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
vec4 originalPos = vec4(vPosition, 1.0);
|
||||||
|
float totalWeight = 0.0;
|
||||||
|
|
||||||
|
if (aBoneWeights0.x > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
|
||||||
|
totalWeight += aBoneWeights0.x;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.y > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
|
||||||
|
totalWeight += aBoneWeights0.y;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.z > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
|
||||||
|
totalWeight += aBoneWeights0.z;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.w > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
|
||||||
|
totalWeight += aBoneWeights0.w;
|
||||||
|
}
|
||||||
|
if (aBoneWeights1.x > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
|
||||||
|
totalWeight += aBoneWeights1.x;
|
||||||
|
}
|
||||||
|
if (aBoneWeights1.y > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
|
||||||
|
totalWeight += aBoneWeights1.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight < 0.001) {
|
||||||
|
skinnedPos = originalPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_Position = ProjectionModelViewMatrix * skinnedPos;
|
||||||
|
}
|
||||||
6
resources/shaders/shadow_depth_web.fragment
Normal file
6
resources/shaders/shadow_depth_web.fragment
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Depth is written automatically by the GPU.
|
||||||
|
}
|
||||||
50
resources/shaders/skinning.vertex
Normal file
50
resources/shaders/skinning.vertex
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
attribute vec3 vPosition;
|
||||||
|
attribute vec2 vTexCoord;
|
||||||
|
attribute vec4 aBoneIndices0;
|
||||||
|
attribute vec2 aBoneIndices1;
|
||||||
|
attribute vec4 aBoneWeights0;
|
||||||
|
attribute vec2 aBoneWeights1;
|
||||||
|
|
||||||
|
varying vec2 texCoord;
|
||||||
|
|
||||||
|
uniform mat4 ProjectionModelViewMatrix;
|
||||||
|
uniform mat4 uBoneMatrices[64];
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
vec4 originalPos = vec4(vPosition, 1.0);
|
||||||
|
float totalWeight = 0.0;
|
||||||
|
|
||||||
|
if (aBoneWeights0.x > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
|
||||||
|
totalWeight += aBoneWeights0.x;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.y > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
|
||||||
|
totalWeight += aBoneWeights0.y;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.z > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
|
||||||
|
totalWeight += aBoneWeights0.z;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.w > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
|
||||||
|
totalWeight += aBoneWeights0.w;
|
||||||
|
}
|
||||||
|
if (aBoneWeights1.x > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
|
||||||
|
totalWeight += aBoneWeights1.x;
|
||||||
|
}
|
||||||
|
if (aBoneWeights1.y > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
|
||||||
|
totalWeight += aBoneWeights1.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight < 0.001) {
|
||||||
|
skinnedPos = originalPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_Position = ProjectionModelViewMatrix * skinnedPos;
|
||||||
|
texCoord = vTexCoord;
|
||||||
|
}
|
||||||
65
resources/shaders/skinning_shadow.vertex
Normal file
65
resources/shaders/skinning_shadow.vertex
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
attribute vec3 vPosition;
|
||||||
|
attribute vec2 vTexCoord;
|
||||||
|
attribute vec3 vNormal;
|
||||||
|
attribute vec4 aBoneIndices0;
|
||||||
|
attribute vec2 aBoneIndices1;
|
||||||
|
attribute vec4 aBoneWeights0;
|
||||||
|
attribute vec2 aBoneWeights1;
|
||||||
|
|
||||||
|
varying vec2 texCoord;
|
||||||
|
varying vec4 fragPosLightSpace;
|
||||||
|
varying vec3 fragNormal;
|
||||||
|
|
||||||
|
uniform mat4 ProjectionModelViewMatrix;
|
||||||
|
uniform mat4 ModelViewMatrix;
|
||||||
|
uniform mat4 uLightFromCamera;
|
||||||
|
uniform mat4 uBoneMatrices[64];
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 skinnedPos = vec4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
vec3 skinnedNormal = vec3(0.0, 0.0, 0.0);
|
||||||
|
vec4 originalPos = vec4(vPosition, 1.0);
|
||||||
|
float totalWeight = 0.0;
|
||||||
|
|
||||||
|
if (aBoneWeights0.x > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.x)] * originalPos * aBoneWeights0.x;
|
||||||
|
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.x)]) * vNormal * aBoneWeights0.x;
|
||||||
|
totalWeight += aBoneWeights0.x;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.y > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.y)] * originalPos * aBoneWeights0.y;
|
||||||
|
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.y)]) * vNormal * aBoneWeights0.y;
|
||||||
|
totalWeight += aBoneWeights0.y;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.z > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.z)] * originalPos * aBoneWeights0.z;
|
||||||
|
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.z)]) * vNormal * aBoneWeights0.z;
|
||||||
|
totalWeight += aBoneWeights0.z;
|
||||||
|
}
|
||||||
|
if (aBoneWeights0.w > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices0.w)] * originalPos * aBoneWeights0.w;
|
||||||
|
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices0.w)]) * vNormal * aBoneWeights0.w;
|
||||||
|
totalWeight += aBoneWeights0.w;
|
||||||
|
}
|
||||||
|
if (aBoneWeights1.x > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices1.x)] * originalPos * aBoneWeights1.x;
|
||||||
|
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.x)]) * vNormal * aBoneWeights1.x;
|
||||||
|
totalWeight += aBoneWeights1.x;
|
||||||
|
}
|
||||||
|
if (aBoneWeights1.y > 0.0) {
|
||||||
|
skinnedPos += uBoneMatrices[int(aBoneIndices1.y)] * originalPos * aBoneWeights1.y;
|
||||||
|
skinnedNormal += mat3(uBoneMatrices[int(aBoneIndices1.y)]) * vNormal * aBoneWeights1.y;
|
||||||
|
totalWeight += aBoneWeights1.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight < 0.001) {
|
||||||
|
skinnedPos = originalPos;
|
||||||
|
skinnedNormal = vNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_Position = ProjectionModelViewMatrix * skinnedPos;
|
||||||
|
texCoord = vTexCoord;
|
||||||
|
fragPosLightSpace = uLightFromCamera * ModelViewMatrix * skinnedPos;
|
||||||
|
fragNormal = mat3(ModelViewMatrix) * skinnedNormal;
|
||||||
|
}
|
||||||
@ -32,7 +32,12 @@ end
|
|||||||
|
|
||||||
function on_npc_interact(npc_index)
|
function on_npc_interact(npc_index)
|
||||||
print("[Lua] NPC interaction! Index: " .. tostring(npc_index))
|
print("[Lua] NPC interaction! Index: " .. tostring(npc_index))
|
||||||
game_api.receive_npc_gift(npc_index)
|
|
||||||
|
if npc_index == 1 then
|
||||||
|
game_api.start_dialogue("test_line_dialogue")
|
||||||
|
else
|
||||||
|
game_api.receive_npc_gift(npc_index)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
print("Lua script loaded successfully!")
|
print("Lua script loaded successfully!")
|
||||||
BIN
resources/w/default_float001.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/default_float001.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/default_float001_cut.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/default_float001_cut.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
resources/w/default_idle002.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/default_idle002.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/default_walk001.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/default_walk001.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/float_attack003.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/float_attack003.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/float_attack003_cut.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/float_attack003_cut.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/gg/gg_action_attack001.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/gg/gg_action_attack001.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/gg/gg_action_idle001.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/gg/gg_action_idle001.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/gg/gg_action_to_stand001.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/gg/gg_action_to_stand001.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/gg/gg_stand_idle001.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/gg/gg_stand_idle001.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/gg/gg_stand_to_action002.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/gg/gg_stand_to_action002.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/w/gg/gg_walking001.anim
(Stored with Git LFS)
Normal file
BIN
resources/w/gg/gg_walking001.anim
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -4,6 +4,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace ZL
|
namespace ZL
|
||||||
{
|
{
|
||||||
@ -588,6 +589,264 @@ namespace ZL
|
|||||||
startMesh = mesh;
|
startMesh = mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BoneSystem::LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName)
|
||||||
|
{
|
||||||
|
std::vector<char> fileData;
|
||||||
|
|
||||||
|
if (!ZIPFileName.empty())
|
||||||
|
{
|
||||||
|
fileData = readFileFromZIP(fileName, ZIPFileName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::ifstream f(fileName, std::ios::binary | std::ios::ate);
|
||||||
|
if (!f.is_open()) throw std::runtime_error("Failed to open binary file: " + fileName);
|
||||||
|
std::streamsize fileSize = f.tellg();
|
||||||
|
f.seekg(0);
|
||||||
|
fileData.resize(static_cast<size_t>(fileSize));
|
||||||
|
f.read(fileData.data(), fileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ptr = fileData.data();
|
||||||
|
|
||||||
|
auto readRaw = [&](void* dst, size_t n) {
|
||||||
|
std::memcpy(dst, ptr, n);
|
||||||
|
ptr += n;
|
||||||
|
};
|
||||||
|
auto readUint32 = [&]() -> uint32_t { uint32_t v; readRaw(&v, 4); return v; };
|
||||||
|
auto readInt32 = [&]() -> int32_t { int32_t v; readRaw(&v, 4); return v; };
|
||||||
|
auto readFloat = [&]() -> float { float v; readRaw(&v, 4); return v; };
|
||||||
|
auto readVec3 = [&]() -> Vector3f { return Vector3f{readFloat(), readFloat(), readFloat()}; };
|
||||||
|
auto readVec2 = [&]() -> Vector2f { return Vector2f{readFloat(), readFloat()}; };
|
||||||
|
|
||||||
|
// Header
|
||||||
|
char magic[4];
|
||||||
|
readRaw(magic, 4);
|
||||||
|
if (std::memcmp(magic, "BSAF", 4) != 0)
|
||||||
|
throw std::runtime_error("Invalid binary animation file (bad magic)");
|
||||||
|
uint32_t version = readUint32();
|
||||||
|
if (version != 1)
|
||||||
|
throw std::runtime_error("Unsupported binary animation file version");
|
||||||
|
|
||||||
|
// ---- Bones ----
|
||||||
|
uint32_t numBones = readUint32();
|
||||||
|
std::vector<Bone> bones(numBones);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < numBones; i++)
|
||||||
|
{
|
||||||
|
bones[i].boneStartWorld = readVec3();
|
||||||
|
bones[i].boneLength = readFloat();
|
||||||
|
|
||||||
|
// 3x3 matrix (row-major in file).
|
||||||
|
// Stored with stride-3 into Matrix4f to match the text loader.
|
||||||
|
float m[9];
|
||||||
|
for (int j = 0; j < 9; j++) m[j] = readFloat();
|
||||||
|
|
||||||
|
bones[i].boneMatrixWorld = Matrix4f::Zero();
|
||||||
|
for (int r = 0; r < 3; r++)
|
||||||
|
for (int c = 0; c < 3; c++)
|
||||||
|
bones[i].boneMatrixWorld.data()[r + c * 3] = m[r * 3 + c];
|
||||||
|
|
||||||
|
bones[i].parent = readInt32();
|
||||||
|
|
||||||
|
uint32_t numChildren = readUint32();
|
||||||
|
bones[i].children.resize(numChildren);
|
||||||
|
for (uint32_t j = 0; j < numChildren; j++)
|
||||||
|
bones[i].children[j] = readInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
startBones = bones;
|
||||||
|
currentBones = bones;
|
||||||
|
|
||||||
|
// ---- Vertices ----
|
||||||
|
uint32_t numVertices = readUint32();
|
||||||
|
std::vector<Vector3f> vertices(numVertices);
|
||||||
|
for (uint32_t i = 0; i < numVertices; i++)
|
||||||
|
vertices[i] = readVec3();
|
||||||
|
|
||||||
|
// ---- UV Coordinates ----
|
||||||
|
uint32_t numFaces = readUint32();
|
||||||
|
std::vector<std::array<Vector2f, 3>> uvCoords(numFaces);
|
||||||
|
for (uint32_t i = 0; i < numFaces; i++)
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
uvCoords[i][j] = readVec2();
|
||||||
|
|
||||||
|
// ---- Normals (read but not currently used by mesh) ----
|
||||||
|
std::vector<Vector3f> normals(numVertices);
|
||||||
|
for (uint32_t i = 0; i < numVertices; i++)
|
||||||
|
normals[i] = readVec3();
|
||||||
|
|
||||||
|
// ---- Triangles ----
|
||||||
|
uint32_t numTriangles = readUint32();
|
||||||
|
std::vector<std::array<int, 3>> triangles(numTriangles);
|
||||||
|
for (uint32_t i = 0; i < numTriangles; i++)
|
||||||
|
triangles[i] = { readInt32(), readInt32(), readInt32() };
|
||||||
|
|
||||||
|
// ---- Vertex Weights ----
|
||||||
|
std::vector<std::array<BoneWeight, MAX_BONE_COUNT>> localVerticesBoneWeight(numVertices);
|
||||||
|
for (uint32_t i = 0; i < numVertices; i++)
|
||||||
|
{
|
||||||
|
uint32_t numGroups = readUint32();
|
||||||
|
float sumWeights = 0;
|
||||||
|
for (uint32_t j = 0; j < numGroups; j++)
|
||||||
|
{
|
||||||
|
int boneIdx = readInt32();
|
||||||
|
float weight = readFloat();
|
||||||
|
if (j < MAX_BONE_COUNT)
|
||||||
|
{
|
||||||
|
localVerticesBoneWeight[i][j].boneIndex = boneIdx;
|
||||||
|
localVerticesBoneWeight[i][j].weight = weight;
|
||||||
|
sumWeights += weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Normalize weights
|
||||||
|
uint32_t cap = (numGroups < MAX_BONE_COUNT) ? numGroups : MAX_BONE_COUNT;
|
||||||
|
for (uint32_t j = 0; j < cap; j++)
|
||||||
|
localVerticesBoneWeight[i][j].weight /= sumWeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Animation Keyframes ----
|
||||||
|
uint32_t numKeyframes = readUint32();
|
||||||
|
animations.resize(1);
|
||||||
|
animations[0].keyFrames.resize(numKeyframes);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < numKeyframes; i++)
|
||||||
|
{
|
||||||
|
animations[0].keyFrames[i].frame = readInt32();
|
||||||
|
animations[0].keyFrames[i].bones.resize(numBones);
|
||||||
|
|
||||||
|
for (uint32_t j = 0; j < numBones; j++)
|
||||||
|
{
|
||||||
|
animations[0].keyFrames[i].bones[j] = startBones[j];
|
||||||
|
animations[0].keyFrames[i].bones[j].boneStartWorld = readVec3();
|
||||||
|
|
||||||
|
// 4x4 matrix (row-major in file, stored with stride-4 into Matrix4f)
|
||||||
|
float m[16];
|
||||||
|
for (int k = 0; k < 16; k++) m[k] = readFloat();
|
||||||
|
|
||||||
|
for (int r = 0; r < 4; r++)
|
||||||
|
for (int c = 0; c < 4; c++)
|
||||||
|
animations[0].keyFrames[i].bones[j].boneMatrixWorld.data()[r + c * 4] = m[r * 4 + c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Build per-triangle mesh (same expansion as text loader) ----
|
||||||
|
for (uint32_t i = 0; i < numTriangles; i++)
|
||||||
|
{
|
||||||
|
mesh.PositionData.push_back(vertices[triangles[i][0]]);
|
||||||
|
mesh.PositionData.push_back(vertices[triangles[i][1]]);
|
||||||
|
mesh.PositionData.push_back(vertices[triangles[i][2]]);
|
||||||
|
|
||||||
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][0]]);
|
||||||
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][1]]);
|
||||||
|
verticesBoneWeight.push_back(localVerticesBoneWeight[triangles[i][2]]);
|
||||||
|
|
||||||
|
mesh.TexCoordData.push_back(uvCoords[i][0]);
|
||||||
|
mesh.TexCoordData.push_back(uvCoords[i][1]);
|
||||||
|
mesh.TexCoordData.push_back(uvCoords[i][2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
startMesh = mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoneSystem::PrepareGpuSkinningData()
|
||||||
|
{
|
||||||
|
size_t vertexCount = verticesBoneWeight.size();
|
||||||
|
gpuBoneData.boneIndices0.resize(vertexCount);
|
||||||
|
gpuBoneData.boneIndices1.resize(vertexCount);
|
||||||
|
gpuBoneData.boneWeights0.resize(vertexCount);
|
||||||
|
gpuBoneData.boneWeights1.resize(vertexCount);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < vertexCount; i++)
|
||||||
|
{
|
||||||
|
gpuBoneData.boneIndices0[i] = Vector4f(
|
||||||
|
static_cast<float>(max(0, verticesBoneWeight[i][0].boneIndex)),
|
||||||
|
static_cast<float>(max(0, verticesBoneWeight[i][1].boneIndex)),
|
||||||
|
static_cast<float>(max(0, verticesBoneWeight[i][2].boneIndex)),
|
||||||
|
static_cast<float>(max(0, verticesBoneWeight[i][3].boneIndex))
|
||||||
|
);
|
||||||
|
gpuBoneData.boneIndices1[i] = Vector2f(
|
||||||
|
static_cast<float>(max(0, verticesBoneWeight[i][4].boneIndex)),
|
||||||
|
static_cast<float>(max(0, verticesBoneWeight[i][5].boneIndex))
|
||||||
|
);
|
||||||
|
gpuBoneData.boneWeights0[i] = Vector4f(
|
||||||
|
verticesBoneWeight[i][0].weight,
|
||||||
|
verticesBoneWeight[i][1].weight,
|
||||||
|
verticesBoneWeight[i][2].weight,
|
||||||
|
verticesBoneWeight[i][3].weight
|
||||||
|
);
|
||||||
|
gpuBoneData.boneWeights1[i] = Vector2f(
|
||||||
|
verticesBoneWeight[i][4].weight,
|
||||||
|
verticesBoneWeight[i][5].weight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gpuBoneData.prepared = true;
|
||||||
|
|
||||||
|
if (startBones.size() > MAX_GPU_BONES)
|
||||||
|
{
|
||||||
|
std::cout << "Warning: model has " << startBones.size()
|
||||||
|
<< " bones, exceeding GPU skinning limit of " << MAX_GPU_BONES << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoneSystem::ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const
|
||||||
|
{
|
||||||
|
int startingKeyFrame = -1;
|
||||||
|
for (size_t i = 0; i < animations[0].keyFrames.size() - 1; i++)
|
||||||
|
{
|
||||||
|
int oldFrame = animations[0].keyFrames[i].frame;
|
||||||
|
int nextFrame = animations[0].keyFrames[i + 1].frame;
|
||||||
|
if (frame >= oldFrame && frame < nextFrame)
|
||||||
|
{
|
||||||
|
startingKeyFrame = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startingKeyFrame == -1)
|
||||||
|
{
|
||||||
|
outMatrices.resize(startBones.size());
|
||||||
|
for (auto& m : outMatrices) m = Matrix4f::Identity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int modifiedFrameNumber = frame - animations[0].keyFrames[startingKeyFrame].frame;
|
||||||
|
int diffFrames = animations[0].keyFrames[startingKeyFrame + 1].frame - animations[0].keyFrames[startingKeyFrame].frame;
|
||||||
|
float t = (modifiedFrameNumber + 0.f) / diffFrames;
|
||||||
|
|
||||||
|
const std::vector<Bone>& oneFrameBones = animations[0].keyFrames[startingKeyFrame].bones;
|
||||||
|
const std::vector<Bone>& nextFrameBones = animations[0].keyFrames[startingKeyFrame + 1].bones;
|
||||||
|
|
||||||
|
outMatrices.resize(startBones.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < startBones.size(); i++)
|
||||||
|
{
|
||||||
|
Vector3f interpPos;
|
||||||
|
interpPos(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
|
||||||
|
interpPos(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
|
||||||
|
interpPos(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));
|
||||||
|
|
||||||
|
Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
||||||
|
Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
|
||||||
|
|
||||||
|
Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
|
||||||
|
Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
|
||||||
|
Eigen::Quaternionf result = q1.slerp(t, q2);
|
||||||
|
|
||||||
|
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
|
||||||
|
|
||||||
|
Matrix4f currentBoneMatrixWorld4 = Eigen::Matrix4f::Identity();
|
||||||
|
currentBoneMatrixWorld4.block<3, 3>(0, 0) = boneMatrixWorld3;
|
||||||
|
currentBoneMatrixWorld4.block<3, 1>(0, 3) = interpPos;
|
||||||
|
|
||||||
|
Matrix4f startBoneMatrixWorld4 = animations[0].keyFrames[0].bones[i].boneMatrixWorld;
|
||||||
|
Matrix4f invertedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
|
||||||
|
|
||||||
|
outMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BoneSystem::Interpolate(int frame)
|
void BoneSystem::Interpolate(int frame)
|
||||||
{
|
{
|
||||||
int startingFrame = -1;
|
int startingFrame = -1;
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
namespace ZL
|
namespace ZL
|
||||||
{
|
{
|
||||||
constexpr int MAX_BONE_COUNT = 6;
|
constexpr int MAX_BONE_COUNT = 6;
|
||||||
|
constexpr int MAX_GPU_BONES = 64;
|
||||||
struct Bone
|
struct Bone
|
||||||
{
|
{
|
||||||
Vector3f boneStartWorld;
|
Vector3f boneStartWorld;
|
||||||
@ -35,6 +36,14 @@ namespace ZL
|
|||||||
std::vector<AnimationKeyFrame> keyFrames;
|
std::vector<AnimationKeyFrame> keyFrames;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GpuBoneData {
|
||||||
|
std::vector<Vector4f> boneIndices0; // bone indices 0-3 per vertex (as float)
|
||||||
|
std::vector<Vector2f> boneIndices1; // bone indices 4-5 per vertex
|
||||||
|
std::vector<Vector4f> boneWeights0; // bone weights 0-3 per vertex
|
||||||
|
std::vector<Vector2f> boneWeights1; // bone weights 4-5 per vertex
|
||||||
|
bool prepared = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct BoneSystem
|
struct BoneSystem
|
||||||
{
|
{
|
||||||
VertexDataStruct mesh;
|
VertexDataStruct mesh;
|
||||||
@ -49,9 +58,17 @@ namespace ZL
|
|||||||
std::vector<Animation> animations;
|
std::vector<Animation> animations;
|
||||||
int startingFrame = 0;
|
int startingFrame = 0;
|
||||||
|
|
||||||
|
GpuBoneData gpuBoneData;
|
||||||
|
|
||||||
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
||||||
|
void LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName = "");
|
||||||
|
|
||||||
void Interpolate(int frame);
|
void Interpolate(int frame);
|
||||||
|
|
||||||
|
// GPU skinning: prepare per-vertex bone data for VBO upload
|
||||||
|
void PrepareGpuSkinningData();
|
||||||
|
// GPU skinning: compute skinning matrices without modifying the mesh
|
||||||
|
void ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include "GameConstants.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -15,7 +17,16 @@ void Character::loadAnimation(AnimationState state, const std::string& filename,
|
|||||||
data.totalFrames = 1;
|
data.totalFrames = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void Character::loadBinaryAnimation(AnimationState state, const std::string& filename, const std::string& zipFile) {
|
||||||
|
auto& data = animations[state];
|
||||||
|
data.model.LoadFromBinaryFile(filename, zipFile);
|
||||||
|
if (!data.model.animations.empty() && !data.model.animations[0].keyFrames.empty()) {
|
||||||
|
data.totalFrames = data.model.animations[0].keyFrames.back().frame + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data.totalFrames = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
/*void Character::setTexture(std::shared_ptr<Texture> tex) {
|
/*void Character::setTexture(std::shared_ptr<Texture> tex) {
|
||||||
texture = tex;
|
texture = tex;
|
||||||
}*/
|
}*/
|
||||||
@ -176,38 +187,6 @@ void Character::update(int64_t deltaMs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
if (isPlayer) //Player should decide only by himself
|
|
||||||
{
|
|
||||||
if (attackTarget != nullptr)
|
|
||||||
{
|
|
||||||
auto pos = attackTarget->position;
|
|
||||||
float distToTarget = (position - pos).norm();
|
|
||||||
|
|
||||||
if (distToTarget > 1.0)
|
|
||||||
{
|
|
||||||
setTarget(Eigen::Vector3f(pos.x(), 0.f, pos.z()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (battle_state != 1)
|
|
||||||
{
|
|
||||||
setTarget(position);
|
|
||||||
battle_state = 1;
|
|
||||||
//player->attack = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (battle_state != 0)
|
|
||||||
{
|
|
||||||
battle_state = 0;
|
|
||||||
attack = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (battle_state == 1)
|
if (battle_state == 1)
|
||||||
{
|
{
|
||||||
if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) {
|
if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) {
|
||||||
@ -279,17 +258,32 @@ void Character::update(int64_t deltaMs) {
|
|||||||
|
|
||||||
|
|
||||||
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
|
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
|
||||||
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
|
if (useGpuSkinning) {
|
||||||
|
anim.model.ComputeSkinningMatrices(static_cast<int>(anim.currentFrame), anim.skinningMatrices);
|
||||||
|
} else {
|
||||||
|
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
|
||||||
|
}
|
||||||
anim.lastFrame = static_cast<int>(anim.currentFrame);
|
anim.lastFrame = static_cast<int>(anim.currentFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Character::draw(Renderer& renderer) {
|
void Character::draw(Renderer& renderer) {
|
||||||
//std::cout << "draw called for Character at position: " << position.transpose() << std::endl;
|
if (useGpuSkinning) {
|
||||||
|
drawGpuSkinning(renderer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AnimationState drawState = resolveActiveState();
|
AnimationState drawState = resolveActiveState();
|
||||||
auto it = animations.find(drawState);
|
auto it = animations.find(drawState);
|
||||||
if (it == animations.end() || !texture) return;
|
if (it == animations.end() || !texture) return;
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
|
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
|
||||||
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
@ -303,6 +297,349 @@ void Character::draw(Renderer& renderer) {
|
|||||||
renderer.DrawVertexRenderStruct(anim.modelMutable);
|
renderer.DrawVertexRenderStruct(anim.modelMutable);
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::prepareGpuSkinningVBOs(AnimationData& anim) {
|
||||||
|
if (anim.gpuSkinningPrepared)
|
||||||
|
{
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
anim.model.PrepareGpuSkinningData();
|
||||||
|
|
||||||
|
// Upload bind-pose mesh (static, done once)
|
||||||
|
anim.bindPoseMutable.AssignFrom(anim.model.startMesh);
|
||||||
|
|
||||||
|
auto& gpu = anim.model.gpuBoneData;
|
||||||
|
|
||||||
|
anim.boneIndices0VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices0.size() * sizeof(Eigen::Vector4f),
|
||||||
|
gpu.boneIndices0.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
anim.boneIndices1VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneIndices1.size() * sizeof(Eigen::Vector2f),
|
||||||
|
gpu.boneIndices1.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
anim.boneWeights0VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights0.size() * sizeof(Eigen::Vector4f),
|
||||||
|
gpu.boneWeights0.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
anim.boneWeights1VBO = std::make_shared<VBOHolder>();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, gpu.boneWeights1.size() * sizeof(Eigen::Vector2f),
|
||||||
|
gpu.boneWeights1.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
anim.gpuSkinningPrepared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::drawGpuSkinning(Renderer& renderer) {
|
||||||
|
AnimationState drawState = resolveActiveState();
|
||||||
|
auto it = animations.find(drawState);
|
||||||
|
if (it == animations.end() || !texture)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& anim = it->second;
|
||||||
|
prepareGpuSkinningVBOs(anim);
|
||||||
|
|
||||||
|
if (anim.skinningMatrices.empty())
|
||||||
|
{
|
||||||
|
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
|
||||||
|
anim.model.ComputeSkinningMatrices(
|
||||||
|
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
|
||||||
|
if (anim.skinningMatrices.empty()) return;
|
||||||
|
}
|
||||||
|
static const std::string skinningShaderName = "skinning";
|
||||||
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(skinningShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
renderer.ScaleMatrix(modelScale);
|
||||||
|
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
||||||
|
|
||||||
|
// Upload bone skinning matrices
|
||||||
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
||||||
|
static_cast<int>(anim.skinningMatrices.size()), false,
|
||||||
|
anim.skinningMatrices[0].data());
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||||
|
|
||||||
|
// Bind VAO (desktop only)
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
if (anim.bindPoseMutable.vao) {
|
||||||
|
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
|
||||||
|
renderer.shaderManager.EnableVertexAttribArrays();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Bind position and texcoord VBOs
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
||||||
|
|
||||||
|
if (anim.bindPoseMutable.texCoordVBO) {
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind bone index VBOs
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
||||||
|
|
||||||
|
// Bind bone weight VBOs
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Shadow depth pass ====================
|
||||||
|
|
||||||
|
void Character::drawShadowDepth(Renderer& renderer) {
|
||||||
|
if (useGpuSkinning) {
|
||||||
|
drawShadowDepthGpuSkinning(renderer);
|
||||||
|
} else {
|
||||||
|
drawShadowDepthCpu(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::drawShadowDepthCpu(Renderer& renderer) {
|
||||||
|
AnimationState drawState = resolveActiveState();
|
||||||
|
auto it = animations.find(drawState);
|
||||||
|
if (it == animations.end()) return;
|
||||||
|
|
||||||
|
// The caller has already pushed the shadow_depth shader and the
|
||||||
|
// light's projection + view onto the renderer stacks.
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
renderer.ScaleMatrix(modelScale);
|
||||||
|
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
||||||
|
|
||||||
|
auto& anim = it->second;
|
||||||
|
anim.modelMutable.AssignFrom(anim.model.mesh);
|
||||||
|
anim.modelMutable.RefreshVBO();
|
||||||
|
renderer.DrawVertexRenderStruct(anim.modelMutable);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
|
||||||
|
AnimationState drawState = resolveActiveState();
|
||||||
|
auto it = animations.find(drawState);
|
||||||
|
if (it == animations.end()) return;
|
||||||
|
|
||||||
|
auto& anim = it->second;
|
||||||
|
prepareGpuSkinningVBOs(anim);
|
||||||
|
if (anim.skinningMatrices.empty()) {
|
||||||
|
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
|
||||||
|
anim.model.ComputeSkinningMatrices(
|
||||||
|
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
|
||||||
|
if (anim.skinningMatrices.empty()) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string shadowSkinningShader = "shadow_depth_skinning";
|
||||||
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
||||||
|
|
||||||
|
// Switch to the skinning depth shader (caller has the static depth shader active).
|
||||||
|
renderer.shaderManager.PushShader(shadowSkinningShader);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
renderer.ScaleMatrix(modelScale);
|
||||||
|
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
||||||
|
|
||||||
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
||||||
|
static_cast<int>(anim.skinningMatrices.size()), false,
|
||||||
|
anim.skinningMatrices[0].data());
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
if (anim.bindPoseMutable.vao) {
|
||||||
|
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
|
||||||
|
renderer.shaderManager.EnableVertexAttribArrays();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
||||||
|
|
||||||
|
if (anim.bindPoseMutable.texCoordVBO) {
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Main pass with shadows ====================
|
||||||
|
|
||||||
|
void Character::drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
|
||||||
|
if (useGpuSkinning) {
|
||||||
|
drawGpuSkinningWithShadow(renderer, lightFromCamera, shadowMapTex, lightDirCamera);
|
||||||
|
} else {
|
||||||
|
drawCpuWithShadow(renderer, lightFromCamera, shadowMapTex, lightDirCamera);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
|
||||||
|
AnimationState drawState = resolveActiveState();
|
||||||
|
auto it = animations.find(drawState);
|
||||||
|
if (it == animations.end() || !texture) return;
|
||||||
|
|
||||||
|
static const std::string shadowShader = "default_shadow";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(shadowShader);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.RenderUniform1i("uShadowMap", 1);
|
||||||
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
||||||
|
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
renderer.ScaleMatrix(modelScale);
|
||||||
|
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
||||||
|
|
||||||
|
auto& anim = it->second;
|
||||||
|
anim.modelMutable.AssignFrom(anim.model.mesh);
|
||||||
|
anim.modelMutable.RefreshVBO();
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(anim.modelMutable);
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
|
||||||
|
AnimationState drawState = resolveActiveState();
|
||||||
|
auto it = animations.find(drawState);
|
||||||
|
if (it == animations.end() || !texture) return;
|
||||||
|
|
||||||
|
auto& anim = it->second;
|
||||||
|
prepareGpuSkinningVBOs(anim);
|
||||||
|
if (anim.skinningMatrices.empty()) {
|
||||||
|
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
|
||||||
|
anim.model.ComputeSkinningMatrices(
|
||||||
|
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
|
||||||
|
if (anim.skinningMatrices.empty()) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::string skinningShadowShader = "skinning_shadow";
|
||||||
|
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(skinningShadowShader);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.RenderUniform1i("uShadowMap", 1);
|
||||||
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
||||||
|
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, shadowMapTex);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix({ position.x(), position.y(), position.z() });
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
renderer.ScaleMatrix(modelScale);
|
||||||
|
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
|
||||||
|
|
||||||
|
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
|
||||||
|
static_cast<int>(anim.skinningMatrices.size()), false,
|
||||||
|
anim.skinningMatrices[0].data());
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
|
||||||
|
|
||||||
|
#ifndef EMSCRIPTEN
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
if (anim.bindPoseMutable.vao) {
|
||||||
|
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
|
||||||
|
renderer.shaderManager.EnableVertexAttribArrays();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
|
||||||
|
|
||||||
|
if (anim.bindPoseMutable.texCoordVBO) {
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind normals for diffuse lighting (skinning_shadow shader skins them)
|
||||||
|
if (anim.bindPoseMutable.normalVBO) {
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.normalVBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer3fv("vNormal", 0, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
|
||||||
|
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
@ -28,6 +28,7 @@ public:
|
|||||||
const Eigen::Vector3f& end)>;
|
const Eigen::Vector3f& end)>;
|
||||||
|
|
||||||
void loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
|
void loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
|
||||||
|
void loadBinaryAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
|
||||||
// void setTexture(std::shared_ptr<Texture> texture);
|
// void setTexture(std::shared_ptr<Texture> texture);
|
||||||
void setTexture(std::shared_ptr<Texture> texture) {
|
void setTexture(std::shared_ptr<Texture> texture) {
|
||||||
this->texture = texture;
|
this->texture = texture;
|
||||||
@ -39,6 +40,8 @@ public:
|
|||||||
std::function<void()> onArrived = nullptr);
|
std::function<void()> onArrived = nullptr);
|
||||||
void setPathPlanner(PathPlanner planner);
|
void setPathPlanner(PathPlanner planner);
|
||||||
void draw(Renderer& renderer);
|
void draw(Renderer& renderer);
|
||||||
|
void drawShadowDepth(Renderer& renderer);
|
||||||
|
void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
|
||||||
|
|
||||||
// Public: read by Game for camera tracking and ray-cast origin
|
// Public: read by Game for camera tracking and ray-cast origin
|
||||||
Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
|
Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
|
||||||
@ -57,6 +60,7 @@ public:
|
|||||||
std::string npcName;
|
std::string npcName;
|
||||||
bool giftReceived = false;
|
bool giftReceived = false;
|
||||||
|
|
||||||
|
|
||||||
int battle_state = 0;
|
int battle_state = 0;
|
||||||
bool resetAnim = false;
|
bool resetAnim = false;
|
||||||
int attack = 0;
|
int attack = 0;
|
||||||
@ -65,6 +69,10 @@ public:
|
|||||||
bool canAttack = false;
|
bool canAttack = false;
|
||||||
Character* attackTarget = nullptr;
|
Character* attackTarget = nullptr;
|
||||||
bool isPlayer = false;
|
bool isPlayer = false;
|
||||||
|
bool useGpuSkinning = true;
|
||||||
|
|
||||||
|
float interactionRadius = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct AnimationData {
|
struct AnimationData {
|
||||||
@ -73,6 +81,15 @@ private:
|
|||||||
float currentFrame = 0.f;
|
float currentFrame = 0.f;
|
||||||
int lastFrame = -1;
|
int lastFrame = -1;
|
||||||
int totalFrames = 1;
|
int totalFrames = 1;
|
||||||
|
|
||||||
|
// GPU skinning data (lazily initialized)
|
||||||
|
VertexRenderStruct bindPoseMutable;
|
||||||
|
std::shared_ptr<VBOHolder> boneIndices0VBO;
|
||||||
|
std::shared_ptr<VBOHolder> boneIndices1VBO;
|
||||||
|
std::shared_ptr<VBOHolder> boneWeights0VBO;
|
||||||
|
std::shared_ptr<VBOHolder> boneWeights1VBO;
|
||||||
|
bool gpuSkinningPrepared = false;
|
||||||
|
std::vector<Eigen::Matrix4f> skinningMatrices;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::map<AnimationState, AnimationData> animations;
|
std::map<AnimationState, AnimationData> animations;
|
||||||
@ -92,6 +109,17 @@ private:
|
|||||||
// Returns the animation state to actually play/draw, falling back to IDLE
|
// Returns the animation state to actually play/draw, falling back to IDLE
|
||||||
// if the requested state has no loaded animation.
|
// if the requested state has no loaded animation.
|
||||||
AnimationState resolveActiveState() const;
|
AnimationState resolveActiveState() const;
|
||||||
|
|
||||||
|
// GPU skinning: prepare per-animation VBOs (called lazily on first draw)
|
||||||
|
void prepareGpuSkinningVBOs(AnimationData& anim);
|
||||||
|
// GPU skinning: draw using shader-based skinning
|
||||||
|
void drawGpuSkinning(Renderer& renderer);
|
||||||
|
// Shadow: draw into depth map (no texture, no projection push)
|
||||||
|
void drawShadowDepthGpuSkinning(Renderer& renderer);
|
||||||
|
void drawShadowDepthCpu(Renderer& renderer);
|
||||||
|
// Shadow: draw with shadow-aware shaders
|
||||||
|
void drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
|
||||||
|
void drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
|
|||||||
253
src/Game.cpp
253
src/Game.cpp
@ -130,6 +130,7 @@ namespace ZL
|
|||||||
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE);
|
||||||
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE);
|
||||||
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_web.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_desktop.fragment", CONST_ZIP_FILE);
|
||||||
@ -138,6 +139,7 @@ namespace ZL
|
|||||||
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_desktop.fragment", CONST_ZIP_FILE);
|
||||||
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_desktop.fragment", CONST_ZIP_FILE);
|
||||||
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE);
|
renderer.shaderManager.AddShaderFromFiles("spark", "resources/shaders/spark.vertex", "resources/shaders/spark_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("skinning", "resources/shaders/skinning.vertex", "resources/shaders/default_desktop.fragment", CONST_ZIP_FILE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::cout << "Load resurces step 4" << std::endl;
|
std::cout << "Load resurces step 4" << std::endl;
|
||||||
@ -158,12 +160,20 @@ namespace ZL
|
|||||||
|
|
||||||
// Player (Viola)
|
// Player (Viola)
|
||||||
player = std::make_unique<Character>();
|
player = std::make_unique<Character>();
|
||||||
|
/*
|
||||||
player->loadAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.txt", CONST_ZIP_FILE);
|
player->loadAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.txt", CONST_ZIP_FILE);
|
||||||
player->loadAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.txt", CONST_ZIP_FILE);
|
player->loadAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.txt", CONST_ZIP_FILE);
|
||||||
player->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.txt", CONST_ZIP_FILE);
|
player->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.txt", CONST_ZIP_FILE);
|
||||||
player->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.txt", CONST_ZIP_FILE);
|
player->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.txt", CONST_ZIP_FILE);
|
||||||
player->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.txt", CONST_ZIP_FILE);
|
player->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.txt", CONST_ZIP_FILE);
|
||||||
player->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.txt", CONST_ZIP_FILE);
|
player->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.txt", CONST_ZIP_FILE);
|
||||||
|
*/
|
||||||
|
player->loadBinaryAnimation(AnimationState::STAND, "resources/w/gg/gg_stand_idle001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::WALK, "resources/w/gg/gg_walking001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/gg/gg_stand_to_action002.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/gg/gg_action_attack001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/gg/gg_action_idle001.anim", CONST_ZIP_FILE);
|
||||||
|
player->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/gg/gg_action_to_stand001.anim", CONST_ZIP_FILE);
|
||||||
|
|
||||||
player->setTexture(violaTexture);
|
player->setTexture(violaTexture);
|
||||||
player->walkSpeed = 3.0f;
|
player->walkSpeed = 3.0f;
|
||||||
@ -209,13 +219,20 @@ namespace ZL
|
|||||||
|
|
||||||
std::cout << "Load resurces step 11" << std::endl;
|
std::cout << "Load resurces step 11" << std::endl;
|
||||||
auto npc02 = std::make_unique<Character>();
|
auto npc02 = std::make_unique<Character>();
|
||||||
|
/*
|
||||||
npc02->loadAnimation(AnimationState::STAND, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
npc02->loadAnimation(AnimationState::STAND, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
||||||
npc02->loadAnimation(AnimationState::WALK, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
npc02->loadAnimation(AnimationState::WALK, "resources/w/default_float001.txt", CONST_ZIP_FILE);
|
||||||
npc02->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.txt", CONST_ZIP_FILE);
|
npc02->loadAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.txt", CONST_ZIP_FILE);
|
||||||
npc02->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
npc02->loadAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
||||||
npc02->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
npc02->loadAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
||||||
npc02->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
npc02->loadAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.txt", CONST_ZIP_FILE);
|
||||||
|
*/
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
|
||||||
|
npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
|
||||||
//npc02->loadAnimation(AnimationState::STAND, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
//npc02->loadAnimation(AnimationState::STAND, "resources/w/float_attack003.txt", CONST_ZIP_FILE);
|
||||||
//npc02->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE);
|
//npc02->loadAnimation(AnimationState::STAND, "resources/idleviola_uv010.txt", CONST_ZIP_FILE);
|
||||||
npc02->setTexture(ghostTexture);
|
npc02->setTexture(ghostTexture);
|
||||||
@ -234,20 +251,38 @@ namespace ZL
|
|||||||
|
|
||||||
std::cout << "Load resurces step 12" << std::endl;
|
std::cout << "Load resurces step 12" << std::endl;
|
||||||
|
|
||||||
|
// Shadow mapping shaders
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_web.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_web.fragment", CONST_ZIP_FILE);
|
||||||
|
#else
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth", "resources/shaders/shadow_depth.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("shadow_depth_skinning", "resources/shaders/shadow_depth_skinning.vertex", "resources/shaders/shadow_depth_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("default_shadow", "resources/shaders/default_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
renderer.shaderManager.AddShaderFromFiles("skinning_shadow", "resources/shaders/skinning_shadow.vertex", "resources/shaders/default_shadow_desktop.fragment", CONST_ZIP_FILE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Create shadow map (2048x2048, ortho size 40, near 0.1, far 100)
|
||||||
|
shadowMap = std::make_unique<ShadowMap>(2048, 40.0f, 0.1f, 100.0f);
|
||||||
|
shadowMap->setLightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f));
|
||||||
|
std::cout << "Shadow map initialized" << std::endl;
|
||||||
|
|
||||||
setupNavigation();
|
setupNavigation();
|
||||||
|
|
||||||
loadingCompleted = true;
|
loadingCompleted = true;
|
||||||
|
|
||||||
dialogueSystem.init(renderer, CONST_ZIP_FILE);
|
dialogueSystem.init(renderer, CONST_ZIP_FILE);
|
||||||
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
|
dialogueSystem.loadDatabase("resources/dialogue/sample_dialogues.json");
|
||||||
dialogueSystem.addTriggerZone({
|
/*dialogueSystem.addTriggerZone({
|
||||||
"ghost_room_trigger",
|
"ghost_room_trigger",
|
||||||
"test_line_dialogue",
|
"test_line_dialogue",
|
||||||
Eigen::Vector3f(0.0f, 0.0f, -8.5f),
|
Eigen::Vector3f(0.0f, 0.0f, -8.5f),
|
||||||
2.0f,
|
2.0f,
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
});
|
});*/
|
||||||
|
|
||||||
scriptEngine.init(this);
|
scriptEngine.init(this);
|
||||||
|
|
||||||
@ -510,6 +545,161 @@ namespace ZL
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Game::drawShadowDepthPass()
|
||||||
|
{
|
||||||
|
if (!shadowMap) return;
|
||||||
|
|
||||||
|
const Eigen::Vector3f& sceneCenter = player ? player->position : Eigen::Vector3f::Zero();
|
||||||
|
shadowMap->updateLightSpaceMatrix(sceneCenter);
|
||||||
|
|
||||||
|
shadowMap->bind();
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
|
||||||
|
// Use front-face culling during depth pass to reduce shadow acne on lit faces
|
||||||
|
//glCullFace(GL_FRONT);
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader("shadow_depth");
|
||||||
|
|
||||||
|
// Set up light's orthographic projection
|
||||||
|
const Eigen::Matrix4f& lightProj = shadowMap->getLightProjectionMatrix();
|
||||||
|
const Eigen::Matrix4f& lightView = shadowMap->getLightViewMatrix();
|
||||||
|
|
||||||
|
// Push the light's projection matrix via the 6-param ortho overload won't
|
||||||
|
// match our pre-computed matrix. Instead, use the raw stack approach:
|
||||||
|
// push a dummy projection then overwrite via PushSpecialMatrix-style.
|
||||||
|
// Simpler: push ortho then push the light view as modelview.
|
||||||
|
renderer.PushProjectionMatrix(
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
0.1f, 100.0f);
|
||||||
|
|
||||||
|
const Eigen::Vector3f& lightDir = shadowMap->getLightDirection();
|
||||||
|
Eigen::Vector3f lightPos = sceneCenter - lightDir * 50.0f;
|
||||||
|
Eigen::Vector3f up(0.0f, 1.0f, 0.0f);
|
||||||
|
if (std::abs(lightDir.dot(up)) > 0.99f) {
|
||||||
|
up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the light view matrix and push it
|
||||||
|
renderer.PushSpecialMatrix(lightView);
|
||||||
|
|
||||||
|
// Draw static geometry
|
||||||
|
renderer.DrawVertexRenderStruct(roomMesh);
|
||||||
|
|
||||||
|
for (auto& [name, gameObj] : gameObjects) {
|
||||||
|
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.isActive && intObj.texture) {
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.TranslateMatrix(intObj.position);
|
||||||
|
renderer.DrawVertexRenderStruct(intObj.mesh);
|
||||||
|
renderer.PopMatrix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw characters (they handle their own skinning shader switch internally)
|
||||||
|
if (player) player->drawShadowDepth(renderer);
|
||||||
|
for (auto& npc : npcs) npc->drawShadowDepth(renderer);
|
||||||
|
|
||||||
|
renderer.PopMatrix(); // light view
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
|
||||||
|
//glCullFace(GL_BACK);
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
shadowMap->unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::drawGameWithShadows()
|
||||||
|
{
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
#ifdef DEBUG_LIGHT
|
||||||
|
// Debug mode: render from the light's point of view using the plain
|
||||||
|
// textured shader so we can see what the shadow map "sees".
|
||||||
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
|
||||||
|
renderer.PushProjectionMatrix(
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
-40.0f, 40.0f,
|
||||||
|
0.1f, 1000.0f);
|
||||||
|
|
||||||
|
renderer.PushSpecialMatrix(shadowMap->getLightViewMatrix());
|
||||||
|
|
||||||
|
#else
|
||||||
|
static const std::string shadowShaderName = "default_shadow";
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader(shadowShaderName);
|
||||||
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
renderer.RenderUniform1i("uShadowMap", 1);
|
||||||
|
|
||||||
|
// Bind shadow map texture to unit 1
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthTexture());
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|
||||||
|
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
|
||||||
|
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
|
||||||
|
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
|
||||||
|
renderer.PushMatrix();
|
||||||
|
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
|
||||||
|
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraInclination, Eigen::Vector3f::UnitX())).toRotationMatrix());
|
||||||
|
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(cameraAzimuth, Eigen::Vector3f::UnitY())).toRotationMatrix());
|
||||||
|
const Eigen::Vector3f& camTarget = player ? player->position : Eigen::Vector3f::Zero();
|
||||||
|
renderer.TranslateMatrix({ -camTarget.x(), -camTarget.y(), -camTarget.z() });
|
||||||
|
|
||||||
|
// Capture the camera view matrix and compute uLightFromCamera
|
||||||
|
cameraViewMatrix = renderer.GetCurrentModelViewMatrix();
|
||||||
|
Eigen::Matrix4f cameraViewInverse = cameraViewMatrix.inverse();
|
||||||
|
Eigen::Matrix4f lightFromCamera = shadowMap->getLightSpaceMatrix() * cameraViewInverse;
|
||||||
|
renderer.RenderUniformMatrix4fv("uLightFromCamera", false, lightFromCamera.data());
|
||||||
|
|
||||||
|
// Light direction in camera space for diffuse lighting
|
||||||
|
Eigen::Vector3f lightDirCamera = cameraViewMatrix.block<3,3>(0,0) * shadowMap->getLightDirection();
|
||||||
|
renderer.RenderUniform3fv("uLightDir", lightDirCamera.data());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, roomTexture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(roomMesh);
|
||||||
|
|
||||||
|
for (auto& [name, gameObj] : gameObjects) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, gameObj.texture->getTexID());
|
||||||
|
renderer.DrawVertexRenderStruct(gameObj.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& intObj : interactiveObjects) {
|
||||||
|
if (intObj.isActive) {
|
||||||
|
intObj.draw(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_LIGHT
|
||||||
|
// In debug-light mode characters use the plain shaders (draw normally
|
||||||
|
// but from the light's viewpoint — projection/view already on stack).
|
||||||
|
if (player) player->draw(renderer);
|
||||||
|
for (auto& npc : npcs) npc->draw(renderer);
|
||||||
|
#else
|
||||||
|
// Characters use their own shadow-aware shaders
|
||||||
|
if (player) player->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
|
for (auto& npc : npcs) npc->drawWithShadow(renderer, lightFromCamera, shadowMap->getDepthTexture(), lightDirCamera);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
}
|
||||||
|
|
||||||
void Game::drawScene() {
|
void Game::drawScene() {
|
||||||
glViewport(0, 0, Environment::width, Environment::height);
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
if (!loadingCompleted) {
|
if (!loadingCompleted) {
|
||||||
@ -517,7 +707,12 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
drawGame();
|
if (shadowMap) {
|
||||||
|
drawShadowDepthPass();
|
||||||
|
drawGameWithShadows();
|
||||||
|
} else {
|
||||||
|
drawGame();
|
||||||
|
}
|
||||||
drawUI();
|
drawUI();
|
||||||
}
|
}
|
||||||
CheckGlError();
|
CheckGlError();
|
||||||
@ -578,7 +773,11 @@ namespace ZL
|
|||||||
|
|
||||||
lastTickCount = newTickCount;
|
lastTickCount = newTickCount;
|
||||||
|
|
||||||
if (player) player->update(delta);
|
//if (player) player->update(delta);
|
||||||
|
if (player) {
|
||||||
|
player->update(delta);
|
||||||
|
dialogueSystem.update(static_cast<int>(delta), player->position);
|
||||||
|
}
|
||||||
for (auto& npc : npcs) npc->update(delta);
|
for (auto& npc : npcs) npc->update(delta);
|
||||||
|
|
||||||
|
|
||||||
@ -611,9 +810,9 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
//for (auto& npc : npcs) npc->update(delta);
|
//for (auto& npc : npcs) npc->update(delta);
|
||||||
if (player) {
|
//if (player) {
|
||||||
dialogueSystem.update(static_cast<int>(delta), player->position);
|
// dialogueSystem.update(static_cast<int>(delta), player->position);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some AI stuff
|
// Some AI stuff
|
||||||
@ -709,6 +908,10 @@ namespace ZL
|
|||||||
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
handleDown(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
||||||
|
|
||||||
player->attackTarget = nullptr;
|
player->attackTarget = nullptr;
|
||||||
|
if (menuManager.uiManager.isUiInteractionForFinger(ZL::UiManager::MOUSE_FINGER_ID)) {
|
||||||
|
std::cout << "[CLICK] UI handled, skipping character movement" << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate ray for picking
|
// Calculate ray for picking
|
||||||
if (dialogueSystem.blocksGameplayInput()) {
|
if (dialogueSystem.blocksGameplayInput()) {
|
||||||
@ -753,22 +956,28 @@ namespace ZL
|
|||||||
// Check if we clicked on an NPC
|
// Check if we clicked on an NPC
|
||||||
Character* clickedNpc = raycastNpcs(camPos, rayDir);
|
Character* clickedNpc = raycastNpcs(camPos, rayDir);
|
||||||
if (clickedNpc && player) {
|
if (clickedNpc && player) {
|
||||||
int npcIndex = -1;
|
float distance = (player->position - clickedNpc->position).norm();
|
||||||
for (size_t i = 0; i < npcs.size(); ++i) {
|
if (distance <= clickedNpc->interactionRadius) {
|
||||||
if (npcs[i].get() == clickedNpc) {
|
int npcIndex = -1;
|
||||||
npcIndex = static_cast<int>(i);
|
for (size_t i = 0; i < npcs.size(); ++i) {
|
||||||
break;
|
if (npcs[i].get() == clickedNpc) {
|
||||||
|
npcIndex = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (npcIndex != -1) {
|
||||||
|
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl;
|
||||||
|
scriptEngine.callNpcInteractCallback(npcIndex);
|
||||||
|
|
||||||
|
if (clickedNpc->canAttack)
|
||||||
|
{
|
||||||
|
player->attackTarget = clickedNpc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (npcIndex != -1) {
|
else {
|
||||||
std::cout << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " ***" << std::endl;
|
std::cout << "[CLICK] Too far from NPC (distance " << distance
|
||||||
scriptEngine.callNpcInteractCallback(npcIndex);
|
<< " > " << clickedNpc->interactionRadius << ")" << std::endl;
|
||||||
|
|
||||||
if (clickedNpc->canAttack)
|
|
||||||
{
|
|
||||||
player->attackTarget = clickedNpc;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (rayDir.y() < -0.001f && player) {
|
else if (rayDir.y() < -0.001f && player) {
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
#include "ScriptEngine.h"
|
#include "ScriptEngine.h"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "dialogue/DialogueSystem.h"
|
#include "dialogue/DialogueSystem.h"
|
||||||
|
#include "render/ShadowMap.h"
|
||||||
#include "navigation/PathFinder.h"
|
#include "navigation/PathFinder.h"
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
@ -105,6 +106,8 @@ namespace ZL {
|
|||||||
void drawUI();
|
void drawUI();
|
||||||
void drawGame();
|
void drawGame();
|
||||||
void drawLoading();
|
void drawLoading();
|
||||||
|
void drawShadowDepthPass();
|
||||||
|
void drawGameWithShadows();
|
||||||
void setupNavigation();
|
void setupNavigation();
|
||||||
void handleDown(int64_t fingerId, int mx, int my);
|
void handleDown(int64_t fingerId, int mx, int my);
|
||||||
void handleUp(int64_t fingerId, int mx, int my);
|
void handleUp(int64_t fingerId, int mx, int my);
|
||||||
@ -134,6 +137,9 @@ namespace ZL {
|
|||||||
//MenuManager menuManager;
|
//MenuManager menuManager;
|
||||||
Dialogue::DialogueSystem dialogueSystem;
|
Dialogue::DialogueSystem dialogueSystem;
|
||||||
//ScriptEngine scriptEngine;
|
//ScriptEngine scriptEngine;
|
||||||
|
|
||||||
|
std::unique_ptr<ShadowMap> shadowMap;
|
||||||
|
Eigen::Matrix4f cameraViewMatrix = Eigen::Matrix4f::Identity();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -121,6 +121,7 @@ CutsceneLine DialogueDatabase::parseCutsceneLine(const json& j) {
|
|||||||
line.text = j.value("text", "");
|
line.text = j.value("text", "");
|
||||||
line.portrait = j.value("portrait", "");
|
line.portrait = j.value("portrait", "");
|
||||||
line.sfx = j.value("sfx", "");
|
line.sfx = j.value("sfx", "");
|
||||||
|
line.background = j.value("background", "");
|
||||||
line.durationMs = j.value("durationMs", 0);
|
line.durationMs = j.value("durationMs", 0);
|
||||||
line.waitForConfirm = j.value("waitForConfirm", false);
|
line.waitForConfirm = j.value("waitForConfirm", false);
|
||||||
return line;
|
return line;
|
||||||
|
|||||||
@ -325,6 +325,7 @@ void DialogueRuntime::startCutscene(const std::string& cutsceneId, const std::st
|
|||||||
|
|
||||||
activeCutscene = cutscene;
|
activeCutscene = cutscene;
|
||||||
pendingNodeAfterCutscene = nextNodeAfterCutscene;
|
pendingNodeAfterCutscene = nextNodeAfterCutscene;
|
||||||
|
currentCutsceneBackground = cutscene->background;
|
||||||
mode = Mode::PlayingCutscene;
|
mode = Mode::PlayingCutscene;
|
||||||
currentCutsceneLine = -1;
|
currentCutsceneLine = -1;
|
||||||
cutsceneTimerMs = 0;
|
cutsceneTimerMs = 0;
|
||||||
@ -363,12 +364,18 @@ void DialogueRuntime::refreshCutscenePresentation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CutsceneLine& line = activeCutscene->lines[currentCutsceneLine];
|
const CutsceneLine& line = activeCutscene->lines[currentCutsceneLine];
|
||||||
|
|
||||||
|
if (!line.background.empty()) {
|
||||||
|
currentCutsceneBackground = line.background;
|
||||||
|
}
|
||||||
|
|
||||||
presentation.mode = PresentationMode::Cutscene;
|
presentation.mode = PresentationMode::Cutscene;
|
||||||
presentation.speaker = line.speaker;
|
presentation.speaker = line.speaker;
|
||||||
presentation.fullText = line.text;
|
presentation.fullText = line.text;
|
||||||
presentation.visibleText = line.text;
|
presentation.visibleText = line.text;
|
||||||
presentation.portraitPath = line.portrait;
|
presentation.portraitPath = line.portrait;
|
||||||
presentation.backgroundPath = activeCutscene->background;
|
//presentation.backgroundPath = activeCutscene->background;
|
||||||
|
presentation.backgroundPath = currentCutsceneBackground;
|
||||||
presentation.choices.clear();
|
presentation.choices.clear();
|
||||||
presentation.selectedChoice = 0;
|
presentation.selectedChoice = 0;
|
||||||
presentation.revealCompleted = true;
|
presentation.revealCompleted = true;
|
||||||
|
|||||||
@ -64,6 +64,8 @@ private:
|
|||||||
int currentCutsceneLine = -1;
|
int currentCutsceneLine = -1;
|
||||||
int cutsceneTimerMs = 0;
|
int cutsceneTimerMs = 0;
|
||||||
|
|
||||||
|
std::string currentCutsceneBackground;
|
||||||
|
|
||||||
bool evaluateConditions(const std::vector<Condition>& conditions) const;
|
bool evaluateConditions(const std::vector<Condition>& conditions) const;
|
||||||
void applyEffects(const std::vector<Effect>& effects);
|
void applyEffects(const std::vector<Effect>& effects);
|
||||||
|
|
||||||
|
|||||||
@ -87,6 +87,7 @@ struct CutsceneLine {
|
|||||||
std::string text;
|
std::string text;
|
||||||
std::string portrait;
|
std::string portrait;
|
||||||
std::string sfx;
|
std::string sfx;
|
||||||
|
std::string background;
|
||||||
int durationMs = 0;
|
int durationMs = 0;
|
||||||
bool waitForConfirm = false;
|
bool waitForConfirm = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -280,6 +280,7 @@ namespace ZL {
|
|||||||
data.id = item.value("id", "npc_unknown");
|
data.id = item.value("id", "npc_unknown");
|
||||||
data.name = item.value("name", "Unknown NPC");
|
data.name = item.value("name", "Unknown NPC");
|
||||||
data.texturePath = item.value("texturePath", "");
|
data.texturePath = item.value("texturePath", "");
|
||||||
|
data.interactionRadius = item.value("interactionRadius", 0.0f);
|
||||||
data.animationIdlePath = item.value("animationIdlePath", "");
|
data.animationIdlePath = item.value("animationIdlePath", "");
|
||||||
data.animationWalkPath = item.value("animationWalkPath", "");
|
data.animationWalkPath = item.value("animationWalkPath", "");
|
||||||
|
|
||||||
@ -331,7 +332,14 @@ namespace ZL {
|
|||||||
|
|
||||||
// Load animations
|
// Load animations
|
||||||
try {
|
try {
|
||||||
npc->loadAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath);
|
if (npcData.animationIdlePath.substr(npcData.animationIdlePath.size() - 5) == ".anim")
|
||||||
|
{
|
||||||
|
npc->loadBinaryAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
npc->loadAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath);
|
||||||
|
}
|
||||||
std::cout << " Loaded IDLE animation: " << npcData.animationIdlePath << std::endl;
|
std::cout << " Loaded IDLE animation: " << npcData.animationIdlePath << std::endl;
|
||||||
}
|
}
|
||||||
catch (const std::exception& e) {
|
catch (const std::exception& e) {
|
||||||
@ -340,7 +348,15 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath);
|
if (npcData.animationIdlePath.substr(npcData.animationIdlePath.size() - 5) == ".anim")
|
||||||
|
{
|
||||||
|
npc->loadBinaryAnimation(AnimationState::WALK, npcData.animationIdlePath, zipPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath);
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << " Loaded WALK animation: " << npcData.animationWalkPath << std::endl;
|
std::cout << " Loaded WALK animation: " << npcData.animationWalkPath << std::endl;
|
||||||
}
|
}
|
||||||
catch (const std::exception& e) {
|
catch (const std::exception& e) {
|
||||||
@ -364,6 +380,7 @@ namespace ZL {
|
|||||||
npc->rotationSpeed = npcData.rotationSpeed;
|
npc->rotationSpeed = npcData.rotationSpeed;
|
||||||
npc->modelScale = npcData.modelScale;
|
npc->modelScale = npcData.modelScale;
|
||||||
npc->position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ);
|
npc->position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ);
|
||||||
|
npc->interactionRadius = npcData.interactionRadius;
|
||||||
|
|
||||||
// Set NPC metadata
|
// Set NPC metadata
|
||||||
npc->npcId = npcData.id;
|
npc->npcId = npcData.id;
|
||||||
|
|||||||
@ -57,6 +57,7 @@ namespace ZL {
|
|||||||
float modelCorrectionRotY = 0.0f;
|
float modelCorrectionRotY = 0.0f;
|
||||||
float modelCorrectionRotZ = 0.0f;
|
float modelCorrectionRotZ = 0.0f;
|
||||||
GiftData gift;
|
GiftData gift;
|
||||||
|
float interactionRadius = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LoadedGameObject {
|
struct LoadedGameObject {
|
||||||
|
|||||||
@ -801,6 +801,18 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::RenderUniformMatrix4fvArray(const std::string& uniformName, int count, bool transpose, const float* value)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
|
||||||
|
auto uniform = shader->uniformList.find(uniformName);
|
||||||
|
|
||||||
|
if (uniform != shader->uniformList.end())
|
||||||
|
{
|
||||||
|
glUniformMatrix4fv(uniform->second, count, transpose, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::RenderUniform3fv(const std::string& uniformName, const float* value)
|
void Renderer::RenderUniform3fv(const std::string& uniformName, const float* value)
|
||||||
{
|
{
|
||||||
auto shader = shaderManager.GetCurrentShader();
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
@ -865,6 +877,13 @@ namespace ZL {
|
|||||||
glVertexAttribPointer(shader->attribList[attribName], 3, GL_FLOAT, GL_FALSE, stride, pointer);
|
glVertexAttribPointer(shader->attribList[attribName], 3, GL_FLOAT, GL_FALSE, stride, pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::VertexAttribPointer4fv(const std::string& attribName, int stride, const char* pointer)
|
||||||
|
{
|
||||||
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
if (shader->attribList.find(attribName) != shader->attribList.end())
|
||||||
|
glVertexAttribPointer(shader->attribList[attribName], 4, GL_FLOAT, GL_FALSE, stride, pointer);
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::DisableVertexAttribArray(const std::string& attribName)
|
void Renderer::DisableVertexAttribArray(const std::string& attribName)
|
||||||
{
|
{
|
||||||
auto shader = shaderManager.GetCurrentShader();
|
auto shader = shaderManager.GetCurrentShader();
|
||||||
|
|||||||
@ -130,6 +130,7 @@ namespace ZL {
|
|||||||
|
|
||||||
void RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value);
|
void RenderUniformMatrix3fv(const std::string& uniformName, bool transpose, const float* value);
|
||||||
void RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value);
|
void RenderUniformMatrix4fv(const std::string& uniformName, bool transpose, const float* value);
|
||||||
|
void RenderUniformMatrix4fvArray(const std::string& uniformName, int count, bool transpose, const float* value);
|
||||||
void RenderUniform1i(const std::string& uniformName, const int value);
|
void RenderUniform1i(const std::string& uniformName, const int value);
|
||||||
void RenderUniform3fv(const std::string& uniformName, const float* value);
|
void RenderUniform3fv(const std::string& uniformName, const float* value);
|
||||||
void RenderUniform4fv(const std::string& uniformName, const float* value);
|
void RenderUniform4fv(const std::string& uniformName, const float* value);
|
||||||
@ -139,6 +140,8 @@ namespace ZL {
|
|||||||
|
|
||||||
void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer);
|
void VertexAttribPointer3fv(const std::string& attribName, int stride, const char* pointer);
|
||||||
|
|
||||||
|
void VertexAttribPointer4fv(const std::string& attribName, int stride, const char* pointer);
|
||||||
|
|
||||||
void DisableVertexAttribArray(const std::string& attribName);
|
void DisableVertexAttribArray(const std::string& attribName);
|
||||||
|
|
||||||
void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct);
|
void DrawVertexRenderStruct(const VertexRenderStruct& VertexRenderStruct);
|
||||||
|
|||||||
124
src/render/ShadowMap.cpp
Normal file
124
src/render/ShadowMap.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#include "ShadowMap.h"
|
||||||
|
#include "Environment.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
// Build a look-at view matrix (column-major, same convention as the engine).
|
||||||
|
static Eigen::Matrix4f lookAt(const Eigen::Vector3f& eye,
|
||||||
|
const Eigen::Vector3f& center,
|
||||||
|
const Eigen::Vector3f& up) {
|
||||||
|
Eigen::Vector3f f = (center - eye).normalized();
|
||||||
|
Eigen::Vector3f s = f.cross(up).normalized();
|
||||||
|
Eigen::Vector3f u = s.cross(f);
|
||||||
|
|
||||||
|
Eigen::Matrix4f m = Eigen::Matrix4f::Identity();
|
||||||
|
m(0, 0) = s.x(); m(0, 1) = s.y(); m(0, 2) = s.z();
|
||||||
|
m(1, 0) = u.x(); m(1, 1) = u.y(); m(1, 2) = u.z();
|
||||||
|
m(2, 0) = -f.x(); m(2, 1) = -f.y(); m(2, 2) = -f.z();
|
||||||
|
|
||||||
|
m(0, 3) = -s.dot(eye);
|
||||||
|
m(1, 3) = -u.dot(eye);
|
||||||
|
m(2, 3) = f.dot(eye);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an orthographic projection matrix that matches the engine's
|
||||||
|
// MakeOrthoMatrix(xmin, xmax, ymin, ymax, zNear, zFar) exactly.
|
||||||
|
static Eigen::Matrix4f ortho(float left, float right,
|
||||||
|
float bottom, float top,
|
||||||
|
float zNear, float zFar) {
|
||||||
|
float width = right - left;
|
||||||
|
float height = top - bottom;
|
||||||
|
float depthRange = zFar - zNear;
|
||||||
|
|
||||||
|
Eigen::Matrix4f m = Eigen::Matrix4f::Zero();
|
||||||
|
m(0, 0) = 2.0f / width;
|
||||||
|
m(1, 1) = 2.0f / height;
|
||||||
|
m(2, 2) = -1.0f / depthRange;
|
||||||
|
m(0, 3) = -(right + left) / width;
|
||||||
|
m(1, 3) = -(top + bottom) / height;
|
||||||
|
m(2, 3) = zNear / depthRange;
|
||||||
|
m(3, 3) = 1.0f;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowMap::ShadowMap(int res, float orthoSz, float zNear, float zFar)
|
||||||
|
: resolution(res)
|
||||||
|
, orthoSize(orthoSz)
|
||||||
|
, nearPlane(zNear)
|
||||||
|
, farPlane(zFar)
|
||||||
|
, lightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f).normalized())
|
||||||
|
, lightViewMatrix(Eigen::Matrix4f::Identity())
|
||||||
|
, lightProjectionMatrix(Eigen::Matrix4f::Identity())
|
||||||
|
, lightSpaceMatrix(Eigen::Matrix4f::Identity())
|
||||||
|
{
|
||||||
|
glGenFramebuffers(1, &fbo);
|
||||||
|
|
||||||
|
glGenTextures(1, &depthTexture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, depthTexture);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
|
||||||
|
resolution, resolution, 0,
|
||||||
|
GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||||
|
GL_TEXTURE_2D, depthTexture, 0);
|
||||||
|
|
||||||
|
// No color buffer for this FBO — depth only.
|
||||||
|
glDrawBuffer(GL_NONE);
|
||||||
|
glReadBuffer(GL_NONE);
|
||||||
|
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
std::cerr << "Error: Shadow map framebuffer is not complete!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShadowMap::~ShadowMap() {
|
||||||
|
if (fbo) glDeleteFramebuffers(1, &fbo);
|
||||||
|
if (depthTexture) glDeleteTextures(1, &depthTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowMap::setLightDirection(const Eigen::Vector3f& dir) {
|
||||||
|
lightDirection = dir.normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowMap::updateLightSpaceMatrix(const Eigen::Vector3f& sceneCenter) {
|
||||||
|
// Place the light "camera" looking at the scene center from the light direction.
|
||||||
|
Eigen::Vector3f lightPos = sceneCenter - lightDirection * (farPlane * 0.5f);
|
||||||
|
|
||||||
|
// Choose an up vector that isn't parallel to the light direction.
|
||||||
|
Eigen::Vector3f up(0.0f, 1.0f, 0.0f);
|
||||||
|
if (std::abs(lightDirection.dot(up)) > 0.99f) {
|
||||||
|
up = Eigen::Vector3f(0.0f, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
lightViewMatrix = lookAt(lightPos, sceneCenter, up);
|
||||||
|
|
||||||
|
lightProjectionMatrix = ortho(
|
||||||
|
-orthoSize, orthoSize,
|
||||||
|
-orthoSize, orthoSize,
|
||||||
|
nearPlane, farPlane);
|
||||||
|
|
||||||
|
lightSpaceMatrix = lightProjectionMatrix * lightViewMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowMap::bind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glViewport(0, 0, resolution, resolution);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowMap::unbind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
44
src/render/ShadowMap.h
Normal file
44
src/render/ShadowMap.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "render/OpenGlExtensions.h"
|
||||||
|
#include <Eigen/Dense>
|
||||||
|
|
||||||
|
namespace ZL {
|
||||||
|
|
||||||
|
class ShadowMap {
|
||||||
|
private:
|
||||||
|
GLuint fbo = 0;
|
||||||
|
GLuint depthTexture = 0;
|
||||||
|
int resolution;
|
||||||
|
|
||||||
|
Eigen::Vector3f lightDirection;
|
||||||
|
Eigen::Matrix4f lightViewMatrix;
|
||||||
|
Eigen::Matrix4f lightProjectionMatrix;
|
||||||
|
Eigen::Matrix4f lightSpaceMatrix;
|
||||||
|
|
||||||
|
float orthoSize;
|
||||||
|
float nearPlane;
|
||||||
|
float farPlane;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ShadowMap(int resolution = 2048, float orthoSize = 40.0f,
|
||||||
|
float zNear = 0.1f, float zFar = 100.0f);
|
||||||
|
~ShadowMap();
|
||||||
|
|
||||||
|
ShadowMap(const ShadowMap&) = delete;
|
||||||
|
ShadowMap& operator=(const ShadowMap&) = delete;
|
||||||
|
|
||||||
|
void setLightDirection(const Eigen::Vector3f& dir);
|
||||||
|
void updateLightSpaceMatrix(const Eigen::Vector3f& sceneCenter);
|
||||||
|
|
||||||
|
void bind();
|
||||||
|
void unbind();
|
||||||
|
|
||||||
|
GLuint getDepthTexture() const { return depthTexture; }
|
||||||
|
int getResolution() const { return resolution; }
|
||||||
|
const Eigen::Matrix4f& getLightSpaceMatrix() const { return lightSpaceMatrix; }
|
||||||
|
const Eigen::Matrix4f& getLightViewMatrix() const { return lightViewMatrix; }
|
||||||
|
const Eigen::Matrix4f& getLightProjectionMatrix() const { return lightProjectionMatrix; }
|
||||||
|
const Eigen::Vector3f& getLightDirection() const { return lightDirection; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZL
|
||||||
Loading…
Reference in New Issue
Block a user