File size: 3,781 Bytes
876fa4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Unlit orthographic raster evidence; bounding-box alignment is not registration."""
import numpy as np


def rasterize(parts, bounds, shape, front):
    height, width = shape
    front = np.asarray(front, dtype=float)
    right = -np.cross(front, [0, 0, 1])
    all_vertices = np.concatenate([p['vertices'] for p in parts])
    lo = np.array([(all_vertices @ right).min(), all_vertices[:, 2].min()])
    span = np.array([np.ptp(all_vertices @ right), np.ptp(all_vertices[:, 2])])
    if np.any(span <= 0):
        raise ValueError('Degenerate projection bounds')
    x0, y0, x1, y1 = bounds
    rgb = np.full((height, width, 3), 220, dtype=np.uint8)
    depth = np.full((height, width), -np.inf)
    front_facing = np.zeros((height, width), dtype=bool)
    covered = np.zeros((height, width), dtype=bool)
    occluded = np.zeros((height, width), dtype=bool)
    for part in parts:
        vertices, faces, uvs, texture = [part[k] for k in ('vertices', 'faces', 'uvs', 'texture')]
        mode = part.get('alpha_mode', 'OPAQUE')
        if mode not in {'OPAQUE', 'MASK'}:
            raise ValueError('Unsupported diagnostic alpha mode: ' + mode)
        factor = np.asarray(part.get('base_color_factor', [1, 1, 1, 1]), dtype=float)
        screen = np.c_[(vertices @ right - lo[0]) / span[0] * (x1-x0) + x0,
                       y1 - (vertices[:, 2] - lo[1]) / span[1] * (y1-y0)]
        for face, uv in zip(faces, uvs, strict=True):
            p = screen[face]
            xmin, ymin = np.maximum(np.floor(p.min(0)).astype(int), 0)
            xmax, ymax = np.minimum(np.ceil(p.max(0)).astype(int), [width-1, height-1])
            if xmin > xmax or ymin > ymax:
                continue
            xx, yy = np.meshgrid(np.arange(xmin, xmax+1), np.arange(ymin, ymax+1))
            d = (p[1,1]-p[2,1])*(p[0,0]-p[2,0])+(p[2,0]-p[1,0])*(p[0,1]-p[2,1])
            if abs(d) < 1e-12:
                continue
            a = ((p[1,1]-p[2,1])*(xx-p[2,0])+(p[2,0]-p[1,0])*(yy-p[2,1]))/d
            b = ((p[2,1]-p[0,1])*(xx-p[2,0])+(p[0,0]-p[2,0])*(yy-p[2,1]))/d
            bary = np.stack([a,b,1-a-b], axis=-1)
            inside = (bary >= -1e-7).all(-1)
            xx, yy, bary = xx[inside], yy[inside], bary[inside]
            z = bary @ (vertices[face] @ front)
            coords = bary @ uv
            tx = np.clip(np.rint(coords[:,0]*(texture.shape[1]-1)).astype(int),0,texture.shape[1]-1)
            ty = np.clip(np.rint((1-coords[:,1])*(texture.shape[0]-1)).astype(int),0,texture.shape[0]-1)
            if mode == 'MASK':
                visible = texture[ty, tx, 3] / 255 * factor[3] >= part.get('alpha_cutoff', .5)
                xx, yy, z, tx, ty = xx[visible], yy[visible], z[visible], tx[visible], ty[visible]
            previous = depth[yy, xx]
            occluded[yy, xx] |= np.isfinite(previous) & (np.abs(previous-z)>1e-6)
            keep = z > previous
            xx, yy, z, tx, ty = xx[keep], yy[keep], z[keep], tx[keep], ty[keep]
            # Convert only visible sampled texels, never the full potentially 16K atlas.
            srgb = texture[ty, tx, :3].astype(float) / 255
            linear = np.where(srgb <= .04045, srgb / 12.92, ((srgb + .055) / 1.055) ** 2.4)
            linear *= factor[:3]
            corrected = np.where(linear <= .0031308, 12.92 * linear,
                                 1.055 * np.maximum(linear, 0) ** (1/2.4) - .055)
            rgb[yy,xx] = np.clip(np.rint(corrected * 255), 0, 255).astype(np.uint8)
            depth[yy,xx] = z
            normal = np.cross(vertices[face[1]]-vertices[face[0]],vertices[face[2]]-vertices[face[0]])
            front_facing[yy,xx] = normal @ front > 0
            covered[yy,xx] = True
    return dict(rgb=rgb, coverage=covered, front_facing=front_facing, occluded=occluded)