Découvrez ma chaine YouTube pour d’autres applications et jeux pour votre NUMWORKS
import kandinsky import math W, H = 320, 222 CAM = (0.0, 1.2, -4.0) LOOK = (0.0, 0.0, 1.0) UP = (0.0, 1.0, 0.0) FOV = 0.45 FLOOR_Y = 0.0 SPHERES = [ (-2, 1.5, 1.2, 1.5, 200, 30, 30, 0.3), ( 1.4, 1.80, -0.6, 0.90, 30, 200, 30, 0.25), ( 0.0, 0.60, -0.9, 0.40, 30, 30, 200, 0.25), ] LIGHT = (2.0, 6.0, -2.0) AMB = 0.6 MAX_BOUNCE = 2 def add(a, b): return (a[0]+b[0], a[1]+b[1], a[2]+b[2]) def sub(a, b): return (a[0]-b[0], a[1]-b[1], a[2]-b[2]) def mul(a, t): return (a[0]*t, a[1]*t, a[2]*t) def dot(a, b): return a[0]*b[0] + a[1]*b[1] + a[2]*b[2] def norm(a): n = math.sqrt(dot(a, a)) return (a[0]/n, a[1]/n, a[2]/n) if n > 1e-9 else a def reflect(d, n): c = 2.0 * dot(d, n) return sub(d, mul(n, c)) def hit_floor(ro, rd): if abs(rd[1]) < 1e-6: return -1.0 t = (FLOOR_Y - ro[1]) / rd[1] return t if t > 0.001 else -1.0 def hit_sphere(ro, rd, s): cx, cy, cz, r = s[0], s[1], s[2], s[3] oc = sub(ro, (cx, cy, cz)) b = dot(oc, rd) c = dot(oc, oc) - r*r disc = b*b - c if disc < 0: return -1.0 sq = math.sqrt(disc) t = -b - sq if t > 0.001: return t t = -b + sq return t if t > 0.001 else -1.0 def nearest(ro, rd, exclude=-1): best_t = 1e18 best = ('none', -1, -1.0) # Sol t = hit_floor(ro, rd) if 0 < t < best_t: best_t = t best = ('floor', 0, t) for i, s in enumerate(SPHERES): if i == exclude: continue t = hit_sphere(ro, rd, s) if 0 < t < best_t: best_t = t best = ('sphere', i, t) return best def checker(px, pz): ix = int(math.floor(px)) iz = int(math.floor(pz)) if (ix + iz) % 2 == 0: return (210, 210, 50) else: return ( 30, 140, 30) def in_shadow(pos, nrm): ldir = norm(sub(LIGHT, pos)) orig = add(pos, mul(nrm, 0.01)) kind, _, _ = nearest(orig, ldir) return kind != 'none' def shade_floor(pos, rd, depth): nrm = (0.0, 1.0, 0.0) base = checker(pos[0], pos[2]) ldir = norm(sub(LIGHT, pos)) diff = max(0.0, dot(nrm, ldir)) shadow = in_shadow(pos, nrm) if shadow: diff *= 0.4 lit = tuple(int(min(255, b * (AMB + diff * 0.82))) for b in base) if depth < MAX_BOUNCE: ref_dir = reflect(rd, nrm) rc = trace(add(pos, mul(nrm, 0.01)), ref_dir, depth+1) r = int(min(255, lit[0]*0.85 + rc[0]*0.15)) g = int(min(255, lit[1]*0.85 + rc[1]*0.15)) b = int(min(255, lit[2]*0.85 + rc[2]*0.15)) return (r, g, b) return lit def shade_sphere(pos, rd, idx, depth): s = SPHERES[idx] cx, cy, cz, r, sr, sg, sb, refl = s nrm = norm(sub(pos, (cx, cy, cz))) ldir = norm(sub(LIGHT, pos)) diff = max(0.0, dot(nrm, ldir)) hv = norm(sub(ldir, rd)) spec = max(0.0, dot(nrm, hv)) ** 60 shadow = in_shadow(pos, nrm) if shadow: diff *= 0.12 spec = 0.0 dr = int(min(255, sr * (AMB + diff * (1-refl)))) dg = int(min(255, sg * (AMB + diff * (1-refl)))) db = int(min(255, sb * (AMB + diff * (1-refl)))) if depth < MAX_BOUNCE and refl > 0: ref_dir = reflect(rd, nrm) rc = trace(add(pos, mul(nrm, 0.001)), ref_dir, depth+1, exclude=idx) r2 = int(min(255, dr*(1-refl) + rc[0]*refl + spec*255)) g2 = int(min(255, dg*(1-refl) + rc[1]*refl + spec*255)) b2 = int(min(255, db*(1-refl) + rc[2]*refl + spec*255)) return (r2, g2, b2) sp = int(spec * 255) return (min(255, dr+sp), min(255, dg+sp), min(255, db+sp)) def sky(rd): t = max(0.0, rd[1]) r = int(t * 10) g = int(t * 10) b = int(10 + t * 40) return (r, g, b) def trace(ro, rd, depth=0, exclude=-1): kind, idx, t = nearest(ro, rd, exclude) if kind == 'none': return sky(rd) pos = add(ro, mul(rd, t)) if kind == 'floor': return shade_floor(pos, rd, depth) else: return shade_sphere(pos, rd, idx, depth) right = norm((LOOK[2], 0.0, -LOOK[0])) up = UP def make_ray(px, py): nx = (px / W - 0.5) * 2.0 ny = -(py / H - 0.5) * 2.0 aspect = W / H dx = nx * aspect * FOV dy = ny * FOV d = add(LOOK, add(mul(right, dx), mul(up, dy))) return norm(d) def render(): for x in range(W): for y in range(H): rd = make_ray(x, y) c = trace(CAM, rd) kandinsky.set_pixel(x, y, kandinsky.color(c[0], c[1], c[2])) render()