package tiny_libs
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/tiny_libs.graphics_3d_geometry/Vec3.ml.html
Source file Vec3.ml
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) (* See Vec3.mli *) type t = float * float * float let add ((ax, ay, az) : t) ((bx, by, bz) : t) : t = (ax +. bx, ay +. by, az +. bz) let sub ((ax, ay, az) : t) ((bx, by, bz) : t) : t = (ax -. bx, ay -. by, az -. bz) let scale (s : float) ((x, y, z) : t) : t = (s *. x, s *. y, s *. z) let dot ((ax, ay, az) : t) ((bx, by, bz) : t) : float = (ax *. bx) +. (ay *. by) +. (az *. bz) let cross ((ax, ay, az) : t) ((bx, by, bz) : t) : t = ((ay *. bz) -. (az *. by), (az *. bx) -. (ax *. bz), (ax *. by) -. (ay *. bx)) let length (v : t) : float = sqrt (dot v v) let normalize (v : t) : t = let n = length v in if n = 0. then v else scale (1. /. n) v let centroid (points : t list) : t = let n = float_of_int (List.length points) in scale (1. /. n) (List.fold_left add (0., 0., 0.) points) (* claude: bugfix: spheres drawn "cut" at the top. * * The symptom: every sphere was missing its top cap, the ring of faces * around the north pole; you could see through it to the white * background. On the software and web backends; the bottom cap was * fine, the OpenGL backend was fine, and it didn't depend on the * shading mode. * * The cause: the face normal used to be computed from the polygon's * first three points, as the cross product of its first two edges: * * normalize (cross (sub p1 p0) (sub p2 p0)) * * That's right for any real triangle, but Playground3d.sphere builds * its faces as latitude/longitude quads, and at the pole a "quad" has * two corners at the very same point, the pole itself: * * p0 = p1 = pole (0, 1, 0) the top row of faces: * /\ each quad's first two * / \ corners are both the * / \ pole, so it's really a * p3------p2 triangle p0 p2 p3 * * so p1 - p0 = (0, 0, 0), and the cross product is (0, 0, 0). And * [normalize] leaves the zero vector as it is (it checks for a zero * length, to not divide by 0): the "normal" is (0, 0, 0). The bottom * row is fine because there the pole comes *last* (p2 = p3), after * three distinct points. * * Why the faces vanished instead of, say, being drawn black: the * backface test is "draw if dot normal (eye - centroid) > 0.", and the * dot product of anything with (0, 0, 0) is exactly 0, which is not * > 0: every top face was culled, silently -- no exception, no warning, * just missing faces. The OpenGL backend didn't have the bug: the GPU * culls each *triangle* by the order its corners appear on screen, * clockwise or not, and never computes a face normal. * * The fix: Newell's method. Each coordinate of the normal is computed * from *all* the edges, going around the polygon: * * nx = sum over the edges (p, q) of (p.y - q.y) * (p.z + q.z) * ny = sum over the edges (p, q) of (p.z - q.z) * (p.x + q.x) * nz = sum over the edges (p, q) of (p.x - q.x) * (p.y + q.y) * * Each sum is the shoelace formula (see graphics/2d/Stroke.signed_area): * twice the signed area of the polygon's shadow on one of the three * coordinate planes (nz: on the xy plane, looking down z), and those * three areas together are the plane's direction, scaled by the * polygon's area. An edge from a point to the same point adds exactly * 0 to every sum, so a repeated pole changes nothing, and a triangle * with a repeated corner gets the normal of the triangle it really is. * For a counterclockwise triangle it agrees with the cross product, * e.g. (0,0,0), (1,0,0), (0,1,0): * * nz = (0-1)*(0+0) + (1-0)*(0+1) + (0-0)*(1+0) = 1, so (0, 0, 1) * * and for a quad that isn't quite flat (4 points need not lie in one * plane) it gives a sensible average normal, where the first three * points would only see one corner of it. * * The lesson, i.e. why such a bug survives: the old code is textbook * code, and it *looks* obviously right. Its hidden assumption is that * the first three points are distinct and not on one line. That's true * of every polygon written by hand (a cube's faces, a triangle), which * is how it got tested, and false for polygons generated by formulas, * where degenerate cases come naturally: a sphere's pole, a cylinder's * or cone's tip, or a quad with a corner in the middle of an edge (the * first three points in a straight line: their cross product is * (0, 0, 0) too). And the failure is silent, made so by a guard meant * to be "safe": without [normalize]'s zero check, the division by 0 * would have given NaNs, just as silent here; a [failwith] on a zero * normal would have pointed right at it. So: a degenerate case that * "can't happen" deserves an error, not a quiet default. And when a * picture looks slightly off, find out why: the notch was visible in * screenshots for a while and was dismissed as "some rendering * artifact" before anyone asked. (Even this comment first said * the normal was NaN, until someone read [normalize] again.) * * Found by eye, on a screenshot of Spheres3d.exe. * * Reference: Martin Newell's method, as described in Ivan Sutherland, * Robert Sproull, Robert Schumacker, "A Characterization of Ten * Hidden-Surface Algorithms", ACM Computing Surveys 6(1):1-55, 1974. *) let face_normal (points : t list) : t = match points with | [] | [ _ ] | [ _; _ ] -> failwith "a polygon needs at least 3 points" | first :: rest -> (* each point with the next one, the last with the first *) let edges = List.combine points (rest @ [ first ]) in normalize (List.fold_left (fun (nx, ny, nz) ((x0, y0, z0), (x1, y1, z1)) -> ( nx +. ((y0 -. y1) *. (z0 +. z1)), ny +. ((z0 -. z1) *. (x0 +. x1)), nz +. ((x0 -. x1) *. (y0 +. y1)) )) (0., 0., 0.) edges)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>