Дано

base_mesh.glb

Untitled

[point_cloud.ply](<https://prod-files-secure.s3.us-west-2.amazonaws.com/05dec818-e1ed-4b8f-8983-84fb0682f601/2d2b5235-0091-48d0-aa4e-7f73b0390fff/point_cloud.ply>)

Untitled

Если координаты правильно преобразовать, то получится что-то такое (возможно это другая голова, но смысл такой). Этот файл можно открыть в сплат вьюерах.

pc_kolya_for_viewer.ply

Untitled

Код на питоне для пересчета

import torch

def dot(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
    return torch.sum(x * y, -1, keepdim=True)

def length(x: torch.Tensor, eps: float = 1e-20) -> torch.Tensor:
    return torch.sqrt(torch.clamp(dot(x, x), min=eps))  # Clamp to avoid nan gradients because grad(sqrt(0)) = NaN

def safe_normalize(x: torch.Tensor, eps: float = 1e-20) -> torch.Tensor:
    return x / length(x, eps)

def compute_face_orientation(verts, faces, return_scale=False):
    i0 = faces[..., 0].long()
    i1 = faces[..., 1].long()
    i2 = faces[..., 2].long()

    v0 = verts[..., i0, :]
    v1 = verts[..., i1, :]
    v2 = verts[..., i2, :]
		
		# basis for local coord system 
    a0 = safe_normalize(v1 - v0)
    a1 = safe_normalize(torch.cross(a0, v2 - v0))
    a2 = -safe_normalize(torch.cross(a1, a0))  # will have artifacts without negation
		
    orientation = torch.cat([a0[..., None], a1[..., None], a2[..., None]], dim=-1)

    if return_scale:
        s0 = length(v1 - v0)
        s1 = dot(a2, (v2 - v0)).abs()
        scale = (s0 + s1) / 2
        
    return orientation, scale

def get_face_centers(verts, face_ids):
        triangles = verts[:, faces]

        # just average between 3 verts
        return triangles.mean(dim=-2)

def rel_to_abs(rel_xyz, face_binding, face_orien_mat, face_scaling, face_centers):

    # rotate, bmm=batch mat mul 
    xyz = torch.bmm( face_orien_mat[face_binding], rel_xyz )

    # scale and translate
    return xyz * face_scaling[face_binding] + face_centers[face_binding] 

ply = read_ply("path/to/ply") # splat
glb = read_glb("path/to/glb") # reference tringle mesh

rel_xyz =  ply[['x','y','z']]
face_binding = ply['binding_0'] 

# prepare
face_centers = get_face_centers(glb.verts, glb.face_ids)
face_orien_mat, face_scaling = compute_face_orientation(glb.verts, glb.faces, return_scale=True)
face_orien_quat = quat_xyzw_to_wxyz(rotmat_to_unitquat(self.face_orien_mat) ) 

# 1. Now we are ready to compute absolute coordinates of the splats
abs_xyz = rel_to_abs(rel_xyz, face_binding, face_orien_mat, face_scaling, face_centers)

# 2. Recompute scale 
rel_scale = ply[['scale_0', 'scale_1', 'scale_2']]
abs_scale= math.exp(rel_scale) * face_scaling[face_binding]

# 3. Recompute splat SH rotation
rel_rotation = ply[['rot_0', 'rot_1', 'rot_2','rot_3' ]]  # quaternion, looks like it is in wxyz format
rel_rotation = normalize(rel_rotation) 
face_quat = normalize(face_orien_quat[face_binding])
abs_rotation = quat_xyzw_to_wxyz(quat_product(quat_wxyz_to_xyzw(rel_rotation), quat_wxyz_to_xyzw(face_orien_quat)))

если abs_* переменные записать обратно в ply вместо отностительных, то получится обычный ply файл который откроет любой вьюер

Важно