init_code
This commit is contained in:
116
pygarment/mayaqltools/scan_imitation.py
Normal file
116
pygarment/mayaqltools/scan_imitation.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Maya script for removing faces from 3D garement model that are not visible from the outside cameras
|
||||
The goal is to imitate scanning artifacts that result in missing geometry
|
||||
* Maya 2022+
|
||||
"""
|
||||
|
||||
from maya import OpenMaya
|
||||
from maya import cmds
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
|
||||
# My modules
|
||||
from pygarment.mayaqltools import utils
|
||||
|
||||
|
||||
def _sample_on_sphere(rad):
|
||||
"""Uniformly sample a point on a sphere with radious rad. Return as Maya-compatible floating-point vector"""
|
||||
# Using method of (Muller 1959, Marsaglia 1972)
|
||||
# see the last one here https://mathworld.wolfram.com/SpherePointPicking.html
|
||||
|
||||
uni_array = np.random.normal(size=3)
|
||||
|
||||
uni_array = uni_array / np.linalg.norm(uni_array) * rad
|
||||
|
||||
return OpenMaya.MFloatVector(uni_array[0], uni_array[1], uni_array[2])
|
||||
|
||||
|
||||
def _camera_surface(target, obstacles=[], vertical_scaling_factor=1.5, ground_scaling_factor=1.2):
|
||||
"""Generate a (3D scanning) camera surface around provided scene"""
|
||||
|
||||
# basically, draw a bounding box around the target
|
||||
bbox = np.array(cmds.exactWorldBoundingBox(obstacles + [target])) # [xmin, ymin, zmin, xmax, ymax, zmax]
|
||||
|
||||
top = bbox[3:]
|
||||
bottom = bbox[:3]
|
||||
center = (top + bottom) / 2
|
||||
dims = top - bottom
|
||||
dims = [max(dims[0], dims[2]) * ground_scaling_factor, dims[1] * vertical_scaling_factor]
|
||||
|
||||
cube = cmds.polyCube(height=dims[1], depth=dims[0], width=dims[0], name='camera_surface')
|
||||
|
||||
# align with center
|
||||
cmds.move(center[0], center[1], center[2], cube, absolute=True)
|
||||
|
||||
# remove bottom face -- as if no cameras there
|
||||
# adding '.f[1]' would also remove the ceiling
|
||||
cmds.polyDelFacet( cube[0] + '.f[3]') # we know exact structure of default polyCube in Maya2018 & Maya2020
|
||||
|
||||
return cube[0], np.max(dims)
|
||||
|
||||
|
||||
def remove_invisible(target, obstacles=[], num_rays=30, visibile_rays=4):
|
||||
"""Update target 3D mesh: remove faces that are not visible from camera_surface
|
||||
* due to self-occlusion or occlusion by an obstacle
|
||||
* Camera surface is generated aroung the target as a small "room" with empty floor and ceiling
|
||||
|
||||
In my context, target is usually a garment mesh, and obstacle is a body surface
|
||||
Noise control:
|
||||
* num_rays -- number of random rays to emit from each face -- the less rays, the more noisy the output is
|
||||
* visibile_rays -- number of rays to hit camera surface without obstacles to consider the face to be visible
|
||||
BUT at least one ray is always required to consider face as visible!
|
||||
"""
|
||||
# Follows the idea of self_intersect_3D() checks used in simulation pipeline
|
||||
print('Performing scanning imitation on {} with obstacles {}'.format(target, obstacles))
|
||||
|
||||
# generate apropriate camera surface
|
||||
camera_surface_obj, ray_dist = _camera_surface(target, obstacles)
|
||||
|
||||
start_time = datetime.now()
|
||||
|
||||
# get mesh objects as OpenMaya object
|
||||
target_mesh, target_dag = utils.get_mesh_dag(target)
|
||||
camera_surface_mesh, _ = utils.get_mesh_dag(camera_surface_obj)
|
||||
obstacles_meshes = [utils.get_mesh_dag(name)[0] for name in obstacles]
|
||||
|
||||
# search for intersections
|
||||
target_accelerator = target_mesh.autoUniformGridParams()
|
||||
cam_surface_accelerator = camera_surface_mesh.autoUniformGridParams()
|
||||
obstacles_accs = [mesh.autoUniformGridParams() for mesh in obstacles_meshes]
|
||||
to_delete = []
|
||||
|
||||
target_face_iterator = OpenMaya.MItMeshPolygon(target_dag)
|
||||
while not target_face_iterator.isDone(): # https://stackoverflow.com/questions/40422082/how-to-find-face-neighbours-in-maya
|
||||
# midpoint of the current face -- start of all the rays
|
||||
face_mean = OpenMaya.MFloatPoint(target_face_iterator.center(OpenMaya.MSpace.kWorld))
|
||||
face_id = target_face_iterator.index()
|
||||
|
||||
visible_count = 0
|
||||
visible = False
|
||||
# Send rays in all directions from the currect vertex
|
||||
for _ in range(num_rays):
|
||||
rayDir = _sample_on_sphere(ray_dist)
|
||||
# Case when face is visible from camera surface
|
||||
if (utils.test_ray_intersect(camera_surface_mesh, face_mean, rayDir, cam_surface_accelerator) # intesection with camera surface
|
||||
and not any([utils.test_ray_intersect(mesh, face_mean, rayDir, acc,) for mesh, acc in zip(obstacles_meshes, obstacles_accs)]) # intesects any of the obstacles
|
||||
and not utils.test_ray_intersect(target_mesh, face_mean, rayDir, target_accelerator, hit_tol=1e-5)): # intersects itself
|
||||
visible_count += 1
|
||||
if visible_count >= visibile_rays: # enough rays are visible -- no need to test more
|
||||
visible = True
|
||||
|
||||
if not visible:
|
||||
to_delete.append(face_id)
|
||||
target_face_iterator.next() # iterate!
|
||||
|
||||
cmds.delete(camera_surface_obj) # clean-up the scene
|
||||
|
||||
# Remove invisible faces
|
||||
delete_strs = [target + '.f[{}]'.format(face_id) for face_id in to_delete]
|
||||
if len(delete_strs) > 0:
|
||||
cmds.polyDelFacet(tuple(delete_strs)) # as this is the last command to execute, it could be undone with Ctrl-Z once
|
||||
|
||||
passed = datetime.now() - start_time
|
||||
print('{}::Removed {} faces after {}. Press Ctrl-Z to undo the changes'.format(target, len(to_delete), passed))
|
||||
|
||||
return len(to_delete), passed.total_seconds()
|
||||
|
||||
Reference in New Issue
Block a user