modify 3d view to sync / identical to 2d view

This commit is contained in:
ykcheungab
2026-09-27 10:42:38 +08:00
parent 3daee6d407
commit bcab2790bf
24 changed files with 1971 additions and 530 deletions
@@ -0,0 +1,620 @@
"""Region agreement: does the 3D pane paint exactly the 2D island?
The user-visible claim this file checks: the set of fragments the 3D surface paints as
*valid* must be the 2D island's inverse image under the mesh's UV map, to within one
texel of the field domain, with **no** disagreement in the island's interior.
Before the texture-first path it was not. The colour came from a per-vertex gather
(``sampling.SurfaceSampler``), one invalid tap made a vertex ``NaN``, the shader's
``isnan`` rule painted every triangle touching it grey, and on the shipped finger asset
that measured as 5763 of the 27227 mesh-covered island texels painted grey (**21.17 %**),
5476 of them strictly *inside* the island, with 26840 wrong pixels on screen. The same
measurement after the change is 0, 0 and 0.
How it is measured, and why this is not a tautology
---------------------------------------------------
The pane is drawn by a real GL context and the framebuffer is read back. The *reference*
is computed on the CPU from the same camera matrices, viewport, vertex positions and
UVs: every triangle is rasterised, ``a_uv`` is interpolated perspective-correctly exactly
as a GPU varying is, and the texel it addresses is the one the fragment shader's
``floor(v_uv + 0.5)`` selects. So the comparison is between the GPU's answer and an
independent CPU derivation of the same UV map. A wrong UV convention, a wrong v flip, an
off-by-one texel, a stale ``a_scalar`` attribute or a leftover per-vertex path all show
up as a large difference — which is exactly how the numbers above were produced.
The tolerance is the design's, and it is asserted in *domain* space, which is where
"one texel of the 300x280 domain" is defined:
* every island texel the mesh covers is painted valid, and no texel outside the island's
one-texel dilation is (``residual <= 1 texel``);
* the island's one-texel *erosion* is painted valid everywhere and invalid nowhere
(``zero interior disagreement``);
* no texel is painted both ways (a fragment-level disagreement).
The mesh's UV footprint is a sub-rectangle of the domain (the shipped finger mesh spans
x 0.03..279.0, y 42.6..256.4 of a 300x280 grid), so 5710 island texels have no triangle
over them at all. "The island's inverse image under the mesh UV map" is therefore
*island ∩ covered*, and every interior/outside assertion is restricted to that set: a
texel no triangle reaches cannot be evidence about the shader.
There is deliberately no ``pytest.skip`` for a missing context once a real platform is
in use: this is the only coverage of this property on actual hardware, and a silent skip
is how it stayed broken. The module is not collected under the offscreen plugin because
that plugin cannot create a platform GL context *at all* — a property of the plugin, not
of the machine — which is the same rule ``test_scene3d_gl_smoke.py`` follows.
"""
from __future__ import annotations
import os
import numpy as np
import pytest
os.environ.setdefault("QT_OPENGL", "desktop")
os.environ.setdefault("PYQTGRAPH_QT_LIB", "PySide6")
pytestmark = pytest.mark.skipif(
os.environ.get("QT_QPA_PLATFORM", "").lower() in ("offscreen", "minimal"),
reason="the offscreen/minimal plugin cannot create a platform GL context",
)
#: The 2D canvas's clear colour and the round-trip sentinels, in 8-bit.
BACKGROUND_RGB = (15, 18, 23)
INVALID_SENTINEL = (0, 255, 0) # patches gl_backend.INVALID_COLOR
#: The shipped domain, when the real package is the subject.
REAL_VIEWPORT = (900, 900)
#: A dense synthetic domain: one vertex per texel over a 320x240 viewport, so a texel is
#: about five pixels and a per-vertex path cannot look like a per-fragment one by luck.
DENSE_DOMAIN_HW = (40, 48)
DENSE_VIEWPORT = (320, 240)
DENSE_PLATE_M = 0.06
# ------------------------------------------------------------------------ helpers
def adjacent_to(mask: np.ndarray) -> np.ndarray:
"""Everything 8-adjacent to ``mask``: its one-texel dilation, without itself."""
out = np.zeros_like(mask)
for dy in (-1, 0, 1):
for dx in (-1, 0, 1):
if dy == 0 and dx == 0:
continue
out |= np.roll(np.roll(mask, dy, axis=0), dx, axis=1)
return out
def dilate1(mask: np.ndarray) -> np.ndarray:
return mask | adjacent_to(mask)
def erode1(mask: np.ndarray) -> np.ndarray:
return mask & ~adjacent_to(~mask)
def reference_texels(positions, uvs, triangles, camera, viewport):
"""Which texel every framebuffer pixel reads, derived independently of the GPU."""
view = np.asarray(camera.view, dtype=np.float64).reshape(4, 4)
projection = np.asarray(camera.projection, dtype=np.float64).reshape(4, 4)
width, height = int(viewport[0]), int(viewport[1])
homo = np.hstack([np.asarray(positions, dtype=np.float64), np.ones((len(positions), 1))])
clip = (projection @ view @ homo.T).T
w = clip[:, 3]
ndc = clip[:, :3] / w[:, None]
x = (ndc[:, 0] * 0.5 + 0.5) * width
y = (ndc[:, 1] * 0.5 + 0.5) * height
depth = np.full((height, width), np.inf)
texel = np.full((height, width, 2), -1, dtype=np.int64)
for tri in np.asarray(triangles, dtype=np.int64):
i0, i1, i2 = int(tri[0]), int(tri[1]), int(tri[2])
xs = np.array([x[i0], x[i1], x[i2]])
ys = np.array([y[i0], y[i1], y[i2]])
area = (xs[1] - xs[0]) * (ys[2] - ys[0]) - (xs[2] - xs[0]) * (ys[1] - ys[0])
if area == 0.0:
continue
min_x = max(int(np.floor(xs.min() - 0.5)), 0)
max_x = min(int(np.ceil(xs.max() + 0.5)), width)
min_y = max(int(np.floor(ys.min() - 0.5)), 0)
max_y = min(int(np.ceil(ys.max() + 0.5)), height)
if min_x >= max_x or min_y >= max_y:
continue
gx, gy = np.meshgrid(
np.arange(min_x, max_x) + 0.5, np.arange(min_y, max_y) + 0.5
)
l0 = ((xs[1] - gx) * (ys[2] - gy) - (xs[2] - gx) * (ys[1] - gy)) / area
l1 = ((xs[2] - gx) * (ys[0] - gy) - (xs[0] - gx) * (ys[2] - gy)) / area
l2 = 1.0 - l0 - l1
inside = (l0 >= 0.0) & (l1 >= 0.0) & (l2 >= 0.0)
if not inside.any():
continue
zs = np.array([ndc[i0, 2], ndc[i1, 2], ndc[i2, 2]])
ws = np.array([w[i0], w[i1], w[i2]])
zi = l0 * zs[0] + l1 * zs[1] + l2 * zs[2]
window = depth[min_y:max_y, min_x:max_x]
take = inside & (zi < window)
if not take.any():
continue
inv_w = l0 / ws[0] + l1 / ws[1] + l2 / ws[2]
uu = (
l0 * uvs[i0, 0] / ws[0] + l1 * uvs[i1, 0] / ws[1] + l2 * uvs[i2, 0] / ws[2]
) / inv_w
vv = (
l0 * uvs[i0, 1] / ws[0] + l1 * uvs[i1, 1] / ws[1] + l2 * uvs[i2, 1] / ws[2]
) / inv_w
window[take] = zi[take]
rows, cols = np.nonzero(take)
view_x = texel[min_y:max_y, min_x:max_x, 0]
view_y = texel[min_y:max_y, min_x:max_x, 1]
view_x[rows, cols] = np.floor(uu[take] + 0.5)
view_y[rows, cols] = np.floor(vv[take] + 0.5)
return texel, np.isfinite(depth)
def classify(pixels, target, tolerance):
flat = pixels.reshape(-1, 3).astype(np.int16)
return (
np.abs(flat - np.asarray(target, dtype=np.int16)).max(axis=1) <= tolerance
).reshape(pixels.shape[:2])
def measure(island, texel, covered, painted_valid, painted_invalid):
"""The domain-space numbers, and the assertions, in one place."""
shape = island.shape
# The GPU's own coverage, not this rasteriser's: the reference's barycentric test
# includes triangle and silhhouette edge pixels that GL's fill rule rejects, and a
# texel only this reference reaches would otherwise read as "the pane failed to paint
# it". A texel is covered when a pixel the pane *did* paint resolves to it.
painted = painted_valid | painted_invalid
covered = covered & painted
height, width = int(shape[0]), int(shape[1])
flat = texel[..., 1] * width + texel[..., 0]
valid_texel = np.zeros(shape[0] * shape[1], dtype=bool)
invalid_texel = np.zeros(shape[0] * shape[1], dtype=bool)
covered_texel = np.zeros(shape[0] * shape[1], dtype=bool)
covered_texel[flat[covered]] = True
valid_texel[flat[covered & painted_valid]] = True
invalid_texel[flat[covered & painted_invalid]] = True
valid_texel = valid_texel.reshape(shape)
invalid_texel = invalid_texel.reshape(shape)
covered_texel = covered_texel.reshape(shape)
island_covered = island & covered_texel
grey = island_covered & ~valid_texel
extra = valid_texel & ~island
interior = erode1(island) & covered_texel
outside = ~dilate1(island) & covered_texel
mixed = valid_texel & invalid_texel
# The one-texel rim of the island, in screen terms: a pixel is in the band when its
# own texel has a 4-neighbour with the opposite validity.
tx = np.clip(texel[..., 0], 0, width - 1)
ty = np.clip(texel[..., 1], 0, height - 1)
here = island[ty, tx]
band = np.zeros_like(here)
for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)):
band |= island[np.clip(ty + dy, 0, height - 1), np.clip(tx + dx, 0, width - 1)] != here
band &= covered
expected = np.zeros(here.shape, dtype=bool)
expected[covered] = here[covered]
missing_pixels = expected & ~painted_valid
extra_pixels = painted_valid & covered & ~expected
return {
"island_texels": int(island.sum()),
"rim_texels": int((island & adjacent_to(~island)).sum()),
"island_covered_texels": int(island_covered.sum()),
"island_uncovered_texels": int((island & ~covered_texel).sum()),
"painted_valid_texels": int(valid_texel.sum()),
"island_grey_texels": int(grey.sum()),
"island_grey_percent": round(
100.0 * float(grey.sum()) / max(int(island_covered.sum()), 1), 4
),
"extra_valid_texels": int(extra.sum()),
"mixed_texels": int(mixed.sum()),
"mixed_inside_interior": int((mixed & erode1(island)).sum()),
"covered_pixels": int(covered.sum()),
"interior_texels": int(interior.sum()),
"interior_not_painted_valid": int((interior & ~valid_texel).sum()),
"interior_painted_invalid": int((interior & invalid_texel).sum()),
"outside_dilated_painted_valid": int((outside & valid_texel).sum()),
"grey_more_than_one_texel_inside": int((grey & erode1(island)).sum()),
"grey_more_than_two_texels_inside": int((grey & erode1(erode1(island))).sum()),
"extra_more_than_one_texel_outside": int((extra & ~dilate1(island)).sum()),
"extra_more_than_two_texels_outside": int(
(extra & ~dilate1(dilate1(island))).sum()
),
"band_pixels": int(band.sum()),
"missing_pixels": int(missing_pixels.sum()),
"extra_pixels": int(extra_pixels.sum()),
"missing_pixels_outside_band": int((missing_pixels & ~band).sum()),
"extra_pixels_outside_band": int((extra_pixels & ~band).sum()),
}
def assert_agreement(numbers: dict, label: str) -> None:
"""Every assertion, with the label so a failure says which subject it was."""
assert numbers["covered_pixels"] > 5000, (label, numbers)
# A render that covers almost none of the island would make the rest vacuous.
assert numbers["island_covered_texels"] > 0.5 * numbers["island_texels"], (label, numbers)
# The island's inverse image: every covered island texel is a measurement.
#
# Two residues are bounded here rather than waved at, because they have different
# causes and only one of them is avoidable:
#
# * a *rim* texel can be missed when a fragment's interpolated UV sits within float32
# rounding of the neighbouring texel and the GPU rounds the other way — the
# "residual <= 1 texel" of the acceptance, and it may not reach deeper than the rim;
# * a texel can be *skipped entirely* when the plate is minified: NEAREST sampling of a
# 300x280 domain onto a plate of roughly half that many pixels cannot show every
# texel, and a validity channel deliberately cannot be averaged (a blended validity
# is the partly-valid triangle this change removed). One texel two steps inside the
# rim is the measured worst case at the shipped asset's own default view, so the
# band is bounded at two texels and the *fraction* at 0.05 %.
assert numbers["grey_more_than_one_texel_inside"] <= numbers["rim_texels"], (
f"{label}: the unpainted residue is deeper than the island's rim"
)
assert numbers["grey_more_than_two_texels_inside"] == 0, (label, numbers)
assert numbers["island_grey_percent"] <= 0.05, (
f"{label}: {numbers['island_grey_percent']} % of the island is painted as no "
"measurement; at most a hairline of the rim may be"
)
# The same tolerance in the other direction: a texel just outside the island may be
# resolved as valid when a fragment's UV rounds the other way, but never deeper than
# the rim and never in a way that grows with the island.
assert numbers["extra_valid_texels"] <= numbers["rim_texels"], (label, numbers)
assert numbers["extra_more_than_one_texel_outside"] == 0, (label, numbers)
# Zero interior disagreement, and no disagreement more than one texel out.
assert numbers["interior_not_painted_valid"] == 0, (label, numbers)
assert numbers["interior_painted_invalid"] == 0, (label, numbers)
assert numbers["outside_dilated_painted_valid"] == 0, (label, numbers)
assert numbers["grey_more_than_two_texels_inside"] == 0, (label, numbers)
assert numbers["extra_more_than_two_texels_outside"] == 0, (label, numbers)
# A texel painted both ways can only be a half-texel rounding tie at the rim: a
# fragment whose interpolated UV sits within float32 rounding of a texel boundary is
# rounded to a different texel by the GPU than by this float64 reference. That is the
# residue the one-texel tolerance exists for, and it may never reach the interior.
assert numbers["mixed_inside_interior"] == 0, (label, numbers)
# ...and every pixel that disagrees is inside that one-texel band.
assert numbers["missing_pixels_outside_band"] == 0, (label, numbers)
assert numbers["extra_pixels_outside_band"] == 0, (label, numbers)
# ---------------------------------------------------------------------- subjects
def _dense_scene():
"""A flat plate with one vertex per texel, and its island: the controlled case."""
from orisys_app_shared.scene3d.contracts import (
CameraPreset,
Material,
MeshPart,
SceneAssets,
SurfaceBinding,
freeze,
)
from orisys_app_shared.scene3d.digest import field_domain_id, geometry_id
height, width = DENSE_DOMAIN_HW
step = DENSE_PLATE_M / (max(height, width) - 1)
rows, cols = np.meshgrid(np.arange(height), np.arange(width), indexing="ij")
positions = np.stack(
[
(cols - (width - 1) / 2.0) * step,
-(rows - (height - 1) / 2.0) * step,
np.zeros_like(rows, dtype=np.float64),
],
axis=-1,
).astype(np.float32)
flat = positions.reshape(-1, 3)
index = (rows * width + cols).astype(np.int64)
a = index[:-1, :-1].reshape(-1)
b = index[:-1, 1:].reshape(-1)
c = index[1:, :-1].reshape(-1)
d = index[1:, 1:].reshape(-1)
triangles = np.concatenate(
[np.stack([a, b, d], axis=1), np.stack([a, d, c], axis=1)], axis=0
).astype(np.uint32)
part = MeshPart(
part_id="sensing_surface",
role="sensing",
positions_sensor_m=freeze(flat, dtype=np.float32),
normals_sensor=freeze(
np.tile(np.array([0.0, 0.0, 1.0], dtype=np.float32), (len(flat), 1)),
dtype=np.float32,
),
triangles=freeze(triangles, dtype=np.uint32),
material=Material((0.85, 0.85, 0.88, 1.0), "unlit_scalar", True),
lod_id="region",
)
geom = geometry_id([part])
domain = field_domain_id(shape_hw=DENSE_DOMAIN_HW, flip_v=False)
uvs = np.stack(
[cols.reshape(-1).astype(np.float32), rows.reshape(-1).astype(np.float32)], axis=1
)
count = len(flat)
indices = np.full((count, 4), -1, dtype=np.int32)
weights = np.zeros((count, 4), dtype=np.float32)
indices[:, 0] = np.arange(count, dtype=np.int32)
weights[:, 0] = 1.0
assets = SceneAssets(
asset_id="region-dense",
asset_version="0.0.0",
asset_hash="region-hash",
geometry_id=geom,
parts=(part,),
binding=SurfaceBinding(
part_id="sensing_surface",
geometry_id=geom,
field_domain_id=domain,
shape_hw=DENSE_DOMAIN_HW,
uvs=freeze(uvs, dtype=np.float32),
sample_indices=freeze(indices, dtype=np.int32),
sample_weights=freeze(weights, dtype=np.float32),
),
camera_presets=(
CameraPreset(
"front", (0.0, 0.0, DENSE_PLATE_M * 2.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0), 0.6
),
),
bounds_sensor_m=freeze(
np.stack([flat.min(axis=0), flat.max(axis=0)]).astype(np.float32),
dtype=np.float32,
),
field_domain_id=domain,
shape_hw=DENSE_DOMAIN_HW,
)
island = np.zeros(DENSE_DOMAIN_HW, dtype=bool)
island[6:34, 8:40] = True
island[12:20, 20:26] = False # a hole: an interior the shader must still colour
island[24:30, 30:36] = False # a notch: a concave corner on the boundary
return assets, island, uvs, "front"
def _shipped_scene(preset_name: str = "front"):
"""The real finger package, with the island it carries.
``surface_valid.npy`` is byte-identical to the unroll map's ``valid_mask`` (measured:
32937 texels, 0 differing), so the island is the 2D canvas's own island and no path
outside this repository is needed to state it.
"""
from pathlib import Path
from orisys_app_shared.scene3d.package import read_package, scene_from_contents
package = (
Path(__file__).resolve().parents[1]
/ "config" / "sensors" / "finger" / "scene" / "finger-display-1.0.0.oryscene"
)
assert package.is_file(), f"the shipped scene package is missing: {package}"
assets = scene_from_contents(read_package(package), None)
mapping = assets.surface_mapping
assert mapping is not None, "the shipped package carries no surface mapping"
names = {preset.name for preset in assets.camera_presets}
assert preset_name in names, f"the shipped package has no {preset_name!r} preset: {names}"
return (
assets,
np.asarray(mapping.valid, dtype=bool),
np.asarray(assets.binding.uvs, dtype=np.float32),
preset_name,
)
def _render_region(qt_app, assets, island, uvs, preset_name, viewport):
"""Draw the island through the real GL path and measure the painted region."""
from orisys_app_shared.scene3d.contracts import (
ColorScale,
DisplayField,
DisplaySettings,
FieldDescriptor,
FrameIdentity,
RenderFrame,
freeze,
)
from PySide6.QtGui import QImage
from orisys_studio.scene3d import gl_backend
from orisys_studio.scene3d.camera import OrbitCamera
from orisys_studio.scene3d.gl_backend import (
GLDispatch,
GLRenderer,
configure_surface_format,
lut_rgba,
)
from orisys_studio.scene3d.widget import Sensor3DWidget
configure_surface_format("desktop33")
renderer = GLRenderer()
widget = Sensor3DWidget(renderer, OrbitCamera(), dispatch=GLDispatch())
seen: list = []
widget.diagnosticRaised.connect(seen.append)
widget.resize(*viewport)
widget.show()
widget.set_scene(assets)
def pump(predicate, timeout_s=12.0):
import time
deadline = time.perf_counter() + timeout_s
while time.perf_counter() < deadline:
widget.repaint()
qt_app.processEvents()
if predicate():
return True
time.sleep(0.01)
return False
if not pump(lambda: bool(renderer.part_ids)):
widget.release_resources()
widget.close()
pytest.fail(
"no usable OpenGL context on a real platform: "
f"{widget.diagnostic() or seen or 'the widget never initialized'}"
)
preset = next(item for item in assets.camera_presets if item.name == preset_name)
widget.camera().set_preset(preset)
widget.camera().fit(assets.bounds_sensor_m, viewport[0] / max(viewport[1], 1))
widget.repaint()
qt_app.processEvents()
descriptor = FieldDescriptor("flow_magnitude", "px", "approximate", False)
frame = RenderFrame(
identity=FrameIdentity("region", 1, 1, 0.0, generation=1),
asset_hash=assets.asset_hash,
geometry_id=assets.geometry_id,
sensing_part_id=assets.sensing_part().part_id,
positions_sensor_m=assets.sensing_part().positions_sensor_m,
normals_sensor=assets.sensing_part().normals_sensor,
field=DisplayField(
domain_id=assets.field_domain_id,
shape_hw=(int(island.shape[0]), int(island.shape[1])),
scalar=freeze(
np.where(island, 1.0, 0.0).astype(np.float32), dtype=np.float32
),
valid=freeze(np.array(island, dtype=bool), dtype=np.bool_),
descriptor=descriptor,
),
)
widget.set_settings(
DisplaySettings(
housing_visible=False,
color_scale=ColorScale(
descriptor=descriptor, lower=0.0, upper=1.0, lut_id="sequential"
),
)
)
# The invalid colour is the sentinel the readback is classified by. ``draw`` reads it
# from the module, so patching the module is what the shader is given.
gl_backend.INVALID_COLOR = (0.0, 1.0, 0.0, 1.0)
# The colour a *valid* fragment gets comes from the tree's own lookup table, not from
# a hard-coded constant: the 2D canvas and the 3D surface must be coloured by one
# table, and a test that hard-coded the colour would stop noticing if they diverged.
valid_rgb = tuple(int(v) for v in lut_rgba("sequential")[-1, 0, :3])
widget.present(frame)
try:
assert pump(
lambda: getattr(renderer, "field_shape", None)
== (int(island.shape[0]), int(island.shape[1]))
), "the field texture was never uploaded"
for _ in range(8):
widget.repaint()
qt_app.processEvents()
image = widget.grabFramebuffer()
assert not image.isNull(), "Qt returned no framebuffer"
image = image.convertToFormat(QImage.Format.Format_RGB888)
width, height = image.width(), image.height()
buffer = np.frombuffer(image.constBits(), dtype=np.uint8)
pixels = (
np.frombuffer(buffer, dtype=np.uint8)
.reshape(height, image.bytesPerLine())[:, : width * 3]
.reshape(height, width, 3)
.copy()
)
background = classify(pixels, BACKGROUND_RGB, 6)
painted_valid = classify(pixels, valid_rgb, 14) & ~background
painted_invalid = classify(pixels, INVALID_SENTINEL, 14) & ~background
texel, covered = reference_texels(
np.asarray(frame.positions_sensor_m, dtype=np.float64),
uvs,
assets.sensing_part().triangles,
widget.camera().matrices(widget.viewport_pixels()),
widget.viewport_pixels(),
)
# The reference is built in GL window coordinates (y up); the grab is top-down.
return (
measure(
island,
texel[::-1],
covered[::-1],
painted_valid,
painted_invalid,
),
renderer,
)
finally:
widget.release_resources()
widget.close()
# ------------------------------------------------------------------------ the test
@pytest.fixture(scope="module")
def dense_region(qt_app):
assets, island, uvs, preset = _dense_scene()
numbers, _ = _render_region(
qt_app, assets, island, uvs, preset, DENSE_VIEWPORT
)
return numbers
@pytest.fixture(scope="module")
def shipped_region(qt_app):
"""The shipped asset, seen face-on and magnified: the exact case."""
assets, island, uvs, preset = _shipped_scene("front")
numbers, _ = _render_region(qt_app, assets, island, uvs, preset, REAL_VIEWPORT)
return numbers
@pytest.fixture(scope="module")
def shipped_default_region(qt_app):
"""The shipped asset at *its own* default preset: a three-quarter, minified view."""
assets, island, uvs, _ = _shipped_scene("front")
default = assets.camera_presets[0].name
numbers, _ = _render_region(qt_app, assets, island, uvs, default, REAL_VIEWPORT)
return numbers
def test_the_dense_scene_paints_exactly_the_island(dense_region):
"""The controlled case: one vertex per texel, a hole and a notch in the island.
Nothing is tolerated here. The plate is seen face-on and every texel is magnified,
so neither a rounding tie nor a skipped texel can arise: the painted region must be
the island exactly.
"""
assert_agreement(dense_region, "dense")
assert dense_region["island_grey_texels"] == 0, dense_region
assert dense_region["extra_valid_texels"] == 0, dense_region
assert dense_region["grey_more_than_one_texel_inside"] == 0, dense_region
assert dense_region["mixed_texels"] == 0, dense_region
assert dense_region["missing_pixels"] == 0, dense_region
assert dense_region["extra_pixels"] == 0, dense_region
# Nothing may be left uncovered here: the mesh spans the whole domain.
assert dense_region["island_uncovered_texels"] == 0, dense_region
assert dense_region["painted_valid_texels"] == dense_region["island_texels"]
def test_the_shipped_package_paints_exactly_the_island(shipped_region):
"""The real acceptance: the shipped asset, face-on, every texel magnified.
Zero tolerance, as the design asks: no island texel is grey, no texel outside the
island is painted as a measurement, and no pixel disagrees at all. Before the change
the same measurement was 5763 grey island texels — 5476 of them strictly inside the
island — and 26840 wrong pixels.
"""
numbers = shipped_region
assert_agreement(numbers, "shipped")
assert numbers["island_grey_texels"] == 0, numbers
assert numbers["extra_valid_texels"] == 0, numbers
assert numbers["mixed_texels"] == 0, numbers
assert numbers["missing_pixels"] == 0, numbers
assert numbers["extra_pixels"] == 0, numbers
assert numbers["painted_valid_texels"] == numbers["island_covered_texels"], numbers
# Reported, not asserted: the mesh's UV footprint is a sub-rectangle of the domain,
# so these island texels have no triangle over them at all.
assert numbers["island_uncovered_texels"] > 0, numbers
def test_the_shipped_default_view_keeps_the_residue_on_the_rim(shipped_default_region):
"""The view an operator actually opens: three-quarter, and minified.
Nothing may be grey except within two texels of the island's rim, and almost nothing
may be: measured as 1 texel of 25539 (0.0039 %) — the one texel the GPU never resolved
as itself, because NEAREST sampling of a 300x280 domain onto a plate of about half
that many pixels cannot show every texel, and a validity channel may not be averaged.
(The old per-vertex path, on this same view, left 5763 texels grey with 5476 of them
strictly inside the island.)
"""
numbers = shipped_default_region
assert_agreement(numbers, "shipped-default")
assert numbers["island_grey_percent"] < 0.05, numbers
assert numbers["interior_painted_invalid"] == 0, numbers
# The residual at this view is a rim rounding tie and nothing more: at most one texel
# of the island missed and at most one texel just outside it claimed, both adjacent to
# the boundary, and no disagreement anywhere but the one-texel band.
assert numbers["island_grey_texels"] <= 1, numbers
assert numbers["extra_valid_texels"] <= 1, numbers
assert numbers["grey_more_than_one_texel_inside"] == 0, numbers
assert numbers["extra_more_than_one_texel_outside"] == 0, numbers