Files
P2EP_Export/p2coll_importer.py
2025-12-08 20:28:56 +05:00

250 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import bpy
import struct
import math
from bpy_extras.io_utils import ImportHelper
from bpy.props import StringProperty
from bpy.types import Operator
bl_info = {
"name": "Persona2 PSX Scene Collisions Importer",
"author": "Sergey Shemet",
"version": (1, 0),
"blender": (4, 5, 0),
"location": "File > Import",
"description": "Imports Persona 2 Eternal Punishment PSX 3D scenes Collisions (.p2coll)",
"category": "Import-Export",
}
class P2CollisionImporter(Operator, ImportHelper):
"""Import Persona 2 PSX Collisions"""
bl_idname = "import_scene.p2coll"
bl_label = "Import P2Coll"
bl_options = {'PRESET', 'UNDO'}
filename_ext = ".p2coll"
filter_glob: StringProperty(
default="*.p2coll",
options={'HIDDEN'},
)
def execute(self, context):
return read_p2coll_file(context, self.filepath)
def create_cylinder_at_location(location, radius, height, vertices=16, name="cylinder"):
"""Создает цилиндр с заданными параметрами в указанной позиции"""
# Создаем цилиндр
bpy.ops.mesh.primitive_cylinder_add(
vertices=vertices,
radius=radius,
depth=height,
location=location
)
# Получаем созданный объект
cylinder = context.active_object
cylinder.name = name
return cylinder
def read_p2coll_file(context, filepath):
"""Чтение файла .p2coll и создание объектов в Blender"""
with open(filepath, 'rb') as file:
# Чтение всего файла
data = file.read()
# Чтение блоков из заголовка
# Первый блок: vertexMap
vertex_map_size = struct.unpack_from('<I', data, 0)[0]
vertex_map_block_offset = struct.unpack_from('<I', data, 4)[0]
# Второй блок: collisionLines
collision_lines_size = struct.unpack_from('<I', data, 8)[0]
collision_lines_block_offset = struct.unpack_from('<I', data, 12)[0]
# Третий блок: arrow (теперь cylCollision)
cyl_collision_size = struct.unpack_from('<I', data, 16)[0]
cyl_collision_block_offset = struct.unpack_from('<I', data, 20)[0]
print(f"Vertex count: {vertex_map_size}")
print(f"Wall collisions count: {collision_lines_size}")
print(f"Cylinder collisions count: {cyl_collision_size}")
# Чтение массива вершин
vertices = []
vertex_data_offset = vertex_map_block_offset
for i in range(vertex_map_size):
offset = vertex_data_offset + i * 8 # 8 bytes per coord struct (4 x short)
if offset + 8 <= len(data):
x, y, z, d = struct.unpack_from('<4h', data, offset)
# Преобразование координат: y=z, z=-y, деление на 100
vertices.append((
x / 100.0,
z / 100.0,
-y / 100.0
))
# Создание коллекции
coll_collection_name = "PSX_Collisions"
# Проверяем, существует ли коллекция
if coll_collection_name in bpy.data.collections:
coll_collection = bpy.data.collections[coll_collection_name]
else:
coll_collection = bpy.data.collections.new(coll_collection_name)
context.scene.collection.children.link(coll_collection)
# Сохраняем активную коллекцию для возврата
current_collection = context.collection
# Чтение и создание объектов коллизий (стен)
collision_data_offset = collision_lines_block_offset
for i in range(collision_lines_size):
offset = collision_data_offset + i * 8 # 8 bytes per collisionObj struct
if offset + 8 <= len(data):
# Чтение данных коллизии
vert_id1 = struct.unpack_from('<H', data, offset)[0]
vert_id2 = struct.unpack_from('<H', data, offset + 2)[0]
col_id = struct.unpack_from('<H', data, offset + 4)[0]
collis_height = struct.unpack_from('<B', data, offset + 6)[0]
collis_flag = struct.unpack_from('<B', data, offset + 7)[0]
print(f"Wall Collision {i}: v1={vert_id1}, v2={vert_id2}, height={collis_height}, flag={collis_flag}")
# Проверка, что индексы вершин валидны
if vert_id1 < len(vertices) and vert_id2 < len(vertices):
# Создание меша для стены
mesh = bpy.data.meshes.new(f"wall_collision_{i}")
obj = bpy.data.objects.new(f"wall_collision_{i}", mesh)
# Получаем координаты вершин
v1 = vertices[vert_id1]
v2 = vertices[vert_id2]
# Высота стены
height = collis_height / 100.0
# Создаём вершины для прямоугольника (стены)
verts = [
(v1[0], v1[1], v1[2]), # Нижняя левая
(v2[0], v2[1], v2[2]), # Нижняя правая
(v2[0], v2[1], v2[2] + height), # Верхняя правая
(v1[0], v1[1], v1[2] + height), # Верхняя левая
]
# Грани (два треугольника для прямоугольника)
faces = [(0, 1, 2, 3)]
# Создание меша
mesh.from_pydata(verts, [], faces)
mesh.update()
# Добавляем метаданные
obj["collisHeight"] = collis_height
obj["collisFlag"] = collis_flag
obj["collisionId"] = col_id
obj["vertexId1"] = vert_id1
obj["vertexId2"] = vert_id2
obj["collisionType"] = "wall"
# Добавляем объект в коллекцию
coll_collection.objects.link(obj)
obj.update_tag()
# Отключаем отображение в основной сцене
if obj.name in context.scene.collection.objects:
context.scene.collection.objects.unlink(obj)
else:
print(f"Warning: Invalid vertex indices in wall collision {i}: {vert_id1}, {vert_id2}")
# Чтение и создание цилиндрических коллизий
cyl_collision_data_offset = cyl_collision_block_offset
for i in range(cyl_collision_size):
offset = cyl_collision_data_offset + i * 8 # 8 bytes per cylCollisionObj struct
if offset + 8 <= len(data):
# Чтение данных цилиндрической коллизии
coords_vertex = struct.unpack_from('<H', data, offset)[0]
radius = struct.unpack_from('<H', data, offset + 2)[0] # Радиус вместо вращения
object_id = struct.unpack_from('<H', data, offset + 4)[0]
collis_height = struct.unpack_from('<B', data, offset + 6)[0]
collis_flag = struct.unpack_from('<B', data, offset + 7)[0]
print(f"Cylinder Collision {i}: vertex={coords_vertex}, radius={radius}, height={collis_height}, flag={collis_flag}")
# Проверка, что индекс вершины валиден
if coords_vertex < len(vertices):
# Устанавливаем активную коллекцию для создания объектов
context.view_layer.active_layer_collection = context.view_layer.layer_collection.children[coll_collection.name]
# Получаем позицию для цилиндра
pos = vertices[coords_vertex]
# Преобразуем радиус и высоту (делим на 100)
cyl_radius = radius / 100.0
cyl_height = collis_height / 100.0
# Создаем цилиндр
bpy.ops.mesh.primitive_cylinder_add(
vertices=16, # Количество вершин для гладкости
radius=cyl_radius,
depth=cyl_height,
location=pos,
rotation=(0, 0, 0) # Без вращения
)
# Получаем созданный объект
cyl_obj = context.active_object
cyl_obj.name = f"cyl_collision_{i}"
# Смещаем цилиндр так, чтобы его основание было в позиции вершины
cyl_obj.location.z += cyl_height / 2.0
# Добавляем метаданные
cyl_obj["collisHeight"] = collis_height
cyl_obj["collisFlag"] = collis_flag
cyl_obj["objectId"] = object_id
cyl_obj["vertexId"] = coords_vertex
cyl_obj["radius"] = radius
cyl_obj["collisionType"] = "cylinder"
# Обновляем объект
cyl_obj.update_tag()
# Убеждаемся, что объект в правильной коллекции
if coll_collection.name not in cyl_obj.users_collection:
# Удаляем из всех текущих коллекций
for coll in cyl_obj.users_collection:
coll.objects.unlink(cyl_obj)
# Добавляем в нашу коллекцию
coll_collection.objects.link(cyl_obj)
else:
print(f"Warning: Invalid vertex index in cylinder collision {i}: {coords_vertex}")
# Возвращаем активную коллекцию
context.view_layer.active_layer_collection = context.view_layer.layer_collection
# Выводим информацию о результате
print(f"Импортировано {len(vertices)} вершин")
print(f"Создано {collision_lines_size} стен-коллизий")
print(f"Создано {cyl_collision_size} цилиндрических коллизий")
return {'FINISHED'}
def menu_func_import(self, context):
self.layout.operator(P2CollisionImporter.bl_idname, text="Persona 2 Collisions (.p2coll)")
def register():
bpy.utils.register_class(P2CollisionImporter)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
bpy.utils.unregister_class(P2CollisionImporter)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
if __name__ == "__main__":
register()