mirror of
https://github.com/kuhyx/WUT_Computer_Science.git
synced 2026-11-10 18:47:11 +01:00
The archive reached zero ruff findings this morning, so the standing rule in
.pre-commit-config.yaml -- a hook lands only once the thing it gates is clean
-- is finally satisfied for ruff. `ruff check` and `ruff format --check` are
hooks now, pinned at v0.16.5 per rules/dependency-freshness, plus
`no-suppressions`, which rejects `# noqa` and `# type: ignore`. CLAUDE.md has
always claimed a hook did that; until now the only thing stopping them was
whoever was reading ruff's output, and I typed `# noqa-free` three times in
this session by accident. mypy stays out: six of its errors are third-party
typing limits rather than our code.
The first attempt used `language: system`, which passed `pre-commit run
--all-files` and then failed the actual commit with "Executable `ruff` not
found" -- git hooks do not get ~/.local/bin on PATH. Taking the hooks from
astral-sh/ruff-pre-commit means pre-commit installs ruff itself, which is also
what makes the pin mean anything.
`ruff format --check` failed the moment it was wired up, on four TRAK files I
wrote today and never formatted. Formatted, and the ray_tracing0 render is
still 02484bdb6b6c1ddeff317d7e5566580f.
Then the run guard from f780d023 needed proving on more than TRAK, since it
now applies to every course kind. make, cpp and python courses pass it; node
and target and obj are all properly gitignored, so npm, maven and dotnet
builds cannot trip it either. But NotProgramming/MOM failed, and not because
of the guard:
`_first_file '*.tex'` used `find -print -quit`, which returns whatever readdir
hands back. After the 19 reports were split into sections, that was
`report_three/sections/02-model-dwukryterialny.tex`, and latexmk faithfully
tried to typeset a bare `\section` as a document. DOCS-runnability.md has
recorded MOM as runnable throughout. The tex branch now picks a file that
actually contains `\documentclass`.
Deliberately not sorted, and that is the interesting part. Sorting is the
obvious fix and it made things worse: it swapped ELAC's pick to projectA and
AIS's to report/final/ver1, both of which fail with latexmk exit 12. Those two
documents are broken independently, and sorting merely surfaced them; turning
two green courses red to fix a third is a bad trade. Recorded in
TODO-refactor-remaining.md instead. MOM, ELAC, AIS, SPD, ZPOB, SDM, DPZ and
ESOEN all exit 0 now.
`_works` also grew a `timeout 5`, because `unityhub --version` never returns
on this machine and a probe without one would hang the status sweep.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121 lines
4.0 KiB
Python
Executable File
121 lines
4.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Vectors, photons and the two surfaces the standalone photon mapper traces.
|
|
|
|
Split out of `main.py` so each file stays under this repo's 250-line cap; the
|
|
classes are unchanged.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
# A ray whose direction is this close to parallel with a plane never hits it.
|
|
_PARALLEL_EPSILON = 1e-6
|
|
|
|
|
|
# Define basic vector operations
|
|
class Vector3:
|
|
"""A 3D vector, with just the operations this renderer needs."""
|
|
|
|
def __init__(self, x: float, y: float, z: float) -> None:
|
|
"""Store the three components."""
|
|
self.x = x
|
|
self.y = y
|
|
self.z = z
|
|
|
|
def __add__(self, other: Vector3) -> Vector3:
|
|
"""Return the component-wise sum."""
|
|
return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)
|
|
|
|
def __sub__(self, other: Vector3) -> Vector3:
|
|
"""Return the component-wise difference."""
|
|
return Vector3(self.x - other.x, self.y - other.y, self.z - other.z)
|
|
|
|
def __mul__(self, scalar: float) -> Vector3:
|
|
"""Return this vector scaled by `scalar`."""
|
|
return Vector3(self.x * scalar, self.y * scalar, self.z * scalar)
|
|
|
|
def dot(self, other: Vector3) -> float:
|
|
"""Return the dot product with `other`."""
|
|
return self.x * other.x + self.y * other.y + self.z * other.z
|
|
|
|
def norm(self) -> float:
|
|
"""Return the length of this vector."""
|
|
return float(np.sqrt(self.dot(self)))
|
|
|
|
def normalize(self) -> Vector3:
|
|
"""Return a new vector of unit length pointing the same way."""
|
|
n = self.norm()
|
|
return Vector3(self.x / n, self.y / n, self.z / n)
|
|
|
|
|
|
# Define the photon
|
|
class Photon:
|
|
"""A packet of light energy travelling in a straight line."""
|
|
|
|
def __init__(
|
|
self, position: Vector3, direction: Vector3, power: np.ndarray
|
|
) -> None:
|
|
"""Store where the photon is, where it is going, and what it carries."""
|
|
self.position = position
|
|
self.direction = direction
|
|
self.power = power
|
|
|
|
|
|
# Define a simple sphere
|
|
class Sphere:
|
|
"""A sphere of uniform colour."""
|
|
|
|
def __init__(self, center: Vector3, radius: float, color: np.ndarray) -> None:
|
|
"""Store the sphere's geometry and colour."""
|
|
self.center = center
|
|
self.radius = radius
|
|
self.color = color
|
|
|
|
def intersect(
|
|
self, ray_origin: Vector3, ray_direction: Vector3
|
|
) -> tuple[float, Vector3, Vector3] | None:
|
|
"""Return the nearest hit as (distance, point, normal), or None."""
|
|
# Solve quadratic equation for intersection
|
|
oc = ray_origin - self.center
|
|
a = ray_direction.dot(ray_direction)
|
|
b = 2.0 * oc.dot(ray_direction)
|
|
c = oc.dot(oc) - self.radius * self.radius
|
|
discriminant = b * b - 4 * a * c
|
|
if discriminant < 0:
|
|
return None # No intersection
|
|
t = (-b - np.sqrt(discriminant)) / (2.0 * a)
|
|
if t < 0:
|
|
t = (-b + np.sqrt(discriminant)) / (2.0 * a)
|
|
if t < 0:
|
|
return None
|
|
hit_point = ray_origin + ray_direction * t
|
|
normal = (hit_point - self.center).normalize()
|
|
return (t, hit_point, normal)
|
|
|
|
|
|
# Define a simple plane
|
|
class Plane:
|
|
"""An infinite plane of uniform colour."""
|
|
|
|
def __init__(self, point: Vector3, normal: Vector3, color: np.ndarray) -> None:
|
|
"""Store a point on the plane, its (normalised) normal, and its colour."""
|
|
self.point = point
|
|
self.normal = normal.normalize()
|
|
self.color = color
|
|
|
|
def intersect(
|
|
self, ray_origin: Vector3, ray_direction: Vector3
|
|
) -> tuple[float, Vector3, Vector3] | None:
|
|
"""Return the nearest hit as (distance, point, normal), or None."""
|
|
denom = self.normal.dot(ray_direction)
|
|
if abs(denom) > _PARALLEL_EPSILON:
|
|
t = (self.point - ray_origin).dot(self.normal) / denom
|
|
if t >= 0:
|
|
hit_point = ray_origin + ray_direction * t
|
|
return (t, hit_point, self.normal)
|
|
return None
|
|
|
|
|
|
Surface = Sphere | Plane
|