File size: 3,374 Bytes
381de33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import bpy
import numpy as np
from mathutils import Vector
from mathutils.bvhtree import BVHTree
import argparse
import sys
import json
import bmesh
from pathlib import Path

p = argparse.ArgumentParser()
p.add_argument("--input", required=True)
p.add_argument("--output", required=True)
a = p.parse_args(sys.argv[sys.argv.index("--") + 1 :])
out = Path(a.output)
out.mkdir(parents=True, exist_ok=True)
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
bpy.ops.import_scene.gltf(filepath=str(Path(a.input).resolve()))
for obj in bpy.context.scene.objects:
    if obj.type == "MESH":
        bpy.context.view_layer.objects.active = obj
        obj.select_set(True)
        bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
        obj.select_set(False)

objs = [o for o in bpy.context.scene.objects if o.type == "MESH"]
v = np.array([tuple(v.co) for o in objs for v in o.data.vertices])
u, inv = np.unique(np.round(v / 1e-5).astype(int), axis=0, return_inverse=True)
u = u * 1e-5
adj = [set() for _ in u]
off = 0
faces = []
for o in objs:
    for p in o.data.polygons:
        faces.append(inv[off + np.array(p.vertices)])
    for e in o.data.edges:
        a, b = inv[off + np.array(e.vertices)]
        adj[a].add(b)
        adj[b].add(a)
    off += len(o.data.vertices)
r = set(range(len(u)))
comps = []
while r:
    a = r.pop()
    c = [a]
    q = [a]
    while q:
        for b in adj[q.pop()]:
            if b in r:
                r.remove(b)
                q.append(b)
                c.append(b)
    comps.append(c)
comps.sort(key=len, reverse=True)
main = set(comps[0])
tree = BVHTree.FromPolygons(u, [f for f in faces if f[0] in main], all_triangles=True)
removed = set()
report = []
for c in comps[1:]:
    votes = []
    for vertex in u[c]:
        hitvotes = []
        for d in [(1, 0.231, 0.451), (-0.21, 1, 0.323), (0.371, 0.143, 1)]:
            origin = Vector(vertex)
            direction = Vector(d).normalized()
            count = 0
            for i in range(50):
                hit, normal, index, dist = tree.ray_cast(origin, direction)
                if hit is None:
                    break
                count += 1
                origin = hit + direction * 1e-6
            hitvotes.append(count % 2)
        votes.append(sum(hitvotes) >= 2)
    inside = float(np.mean(votes))
    if inside == 1.0:
        removed.update(c)
    report.append({"vertices": len(c), "inside_fraction": inside, "removed": inside == 1.0})
print("REMOVING", len(removed), "internal vertices", flush=True)
off = 0
for obj in objs:
    ids = {i for i in range(len(obj.data.vertices)) if inv[off + i] in removed}
    off += len(obj.data.vertices)
    bm = bmesh.new()
    bm.from_mesh(obj.data)
    bm.verts.ensure_lookup_table()
    bmesh.ops.delete(bm, geom=[bm.verts[i] for i in ids], context="VERTS")
    bm.to_mesh(obj.data)
    bm.free()
bpy.ops.object.select_all(action="SELECT")
bpy.ops.export_scene.gltf(filepath=str(out / "character-static.glb"), export_format="GLB", use_selection=True)
(out / "internal-shell-cleanup.json").write_text(
    json.dumps(
        {
            "removed_vertices": len(removed),
            "components": report,
            "criterion": "Every vertex inside the largest outer shell by majority parity of three ray directions",
        },
        indent=2,
    )
)