250 lines
12 KiB
Python
250 lines
12 KiB
Python
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() |