A point in a box¶
Whether a point, in a solid's own frame, lies in the box of half-extents `half` about the frame's origin, its faces across the frame's axes: |p.x| <= h.x, |p.y| <= h.y and |p.z| <= h.z, the faces inside.
A CSG primitive (lusine.render.solid); a line on the ground is a long, thin box straddling it. Source: Requicha, Representations for Rigid Solids (1980), point membership classification. Checked against a reference in numpy, which has its own tests.
- Source
- Requicha, Representations for Rigid Solids (1980), point membership classification.
- Proof
- reference, invariant, limit.
in_box.trait
namespace lusine.render;
bool in_box(vector<float<m>, 3> point, vector<float<m>, 3> half)
{
return abs(point.x) <= half.x && abs(point.y) <= half.y && abs(point.z) <= half.z;
}
Whether a point in a solid's own frame lies within the box of half-extents half about its origin, the faces inside. A primitive of the library's constructive solid geometry (lusine.render.solid), which a shape's steps place and combine and lusine.render.table's paints use to mark the ground.
import os, sys
root = os.getcwd()
while not os.path.isfile(os.path.join(root, 'docsite.json')):
root = os.path.dirname(root)
sys.path.insert(0, os.path.join(root, 'tools'))
import numpy as np
import matplotlib.pyplot as plt
from lusine import trait_run
F32 = np.float32
rng = np.random.default_rng(2026)
def law(name, point, **kw):
# the kernel over many points, a few hundred a run (a step's host queue holds that many)
return np.concatenate([trait_run.law(f'render/{name}.trait', name, point=point[k:k + 500], **kw)
for k in range(0, len(point), 500)])
The kernel¶
The law as the library states it.
print(trait_run.source('render/in_box.trait'))
// lusine.render.in_box: whether a point, in a solid's own frame, lies in the box of half-extents `half` about the
// frame's origin, its faces across the frame's axes: |p.x| <= h.x, |p.y| <= h.y and |p.z| <= h.z, the faces inside. A
// CSG primitive (lusine.render.solid); a line on the ground is a long, thin box straddling it.
// Source: Requicha, Representations for Rigid Solids (1980), point membership classification.
// Checked against a reference in numpy, which has its own tests.
namespace lusine.render;
bool in_box(vector<float<m>, 3> point, vector<float<m>, 3> half)
{
return abs(point.x) <= half.x && abs(point.y) <= half.y && abs(point.z) <= half.z;
}
The program¶
Each section runs the kernel over a lattice of items, one a case: the host sets every parameter, and main() binds the kernel over the items.
print(trait_run.law_program('render/in_box.trait', 'in_box'))
from lusine.kernels.render.in_box import in_box as reference
namespace probe;
lusine.version version = { { number = 1 } };
struct item {
host vector<float<m>, 3> point;
host vector<float<m>, 3> half;
bool result_0;
};
lattice<item, 1> items(spacing = 1 m, boundary = wall);
void main() { items.result_0 = lusine.render.in_box(items.point, items.half); }
Reference: a boundary line¶
Points about a terrain's boundary line, 15 m long and 2 cm wide, straddling the ground a centimetre each way: the kernel inside exactly where the reference is, apart from those within a few binary32 roundings of a face.
half = np.array([7.5, 0.01, 0.01])
p = rng.uniform(-1.0, 1.0, size=(3000, 3)) * np.array([8.0, 0.02, 0.02])
got = law('in_box', p, half=half)
want = reference(p, half)
near = np.min(np.abs(np.abs(p) - half), axis=1) < 1e-6
assert np.array_equal(got[~near], want[~near])
print(f'{int(got.sum())} of {len(p)} inside, the same as the reference ({int(near.sum())} on a face left out)')
700 of 3000 inside, the same as the reference (0 on a face left out)
Invariant: the share inside, and the reflections¶
The share of a cube's points inside the box is the box's share of the cube's volume, and a point is inside where its reflection through the centre is.
p = rng.uniform(-1.0, 1.0, size=(6000, 3))
half = np.array([0.3, 0.6, 0.9])
inside = law('in_box', p, half=half)
mirror = law('in_box', -p, half=half)
assert abs(inside.mean() - 0.162) < 0.02 and np.array_equal(inside, mirror)
print(f'{inside.mean():.4f} of the cube inside, 0.162 the volume\'s share; every reflection agrees')
0.1660 of the cube inside, 0.162 the volume's share; every reflection agrees
Limit: the faces and the corner¶
A corner and a point on each face are inside; a point past any single face is outside.
half = np.array([0.5, 0.25, 0.125])
on = law('in_box', np.array([[0.5, 0.25, 0.125], [0.5, 0.0, 0.0], [0.0, -0.25, 0.0], [0.0, 0.0, 0.125]]), half=half)
off = law('in_box', np.array([[0.51, 0.0, 0.0], [0.0, 0.26, 0.0], [0.0, 0.0, -0.13]]), half=half)
assert on.all() and not off.any()
print('the corner and the faces inside; past any one face, outside')
the corner and the faces inside; past any one face, outside