🛟 Updated.
e41fbe6 parent: a334114 deleted
demos/qwen-style/workspace/HANDOFF.md +0 -118 | deleted file mode 100644 | ||
| @@ -1,118 +0,0 @@ | ||
| 1 | -# Handoff — état du projet `povray` | |
| 2 | - | |
| 3 | -Dernière mise à jour : après le path tracing et le tutoriel. | |
| 4 | -Ce fichier permet de reprendre le travail sans re-lire toute l'historique. | |
| 5 | - | |
| 6 | -## Ce qu'est le projet | |
| 7 | - | |
| 8 | -Bibliothèque Go **sans dépendances** qui rend des scènes façon POV-Ray : | |
| 9 | -on décrit objets, matériaux, lumières et caméra en code Go, la | |
| 10 | -bibliothèque trace des rayons et produit une image PNG. | |
| 11 | - | |
| 12 | -- Module : `github.com/user/povray` (**placeholder** — voir "À faire") | |
| 13 | -- Go 1.27, encodeur PNG maison (stdlib only), rendu parallèle en | |
| 14 | - chunks de lignes (goroutines + `sync.WaitGroup`) | |
| 15 | - | |
| 16 | -## État : ce qui marche | |
| 17 | - | |
| 18 | -Tout est vérifié : `gofmt` propre, `go vet` propre sur la bibliothèque, | |
| 19 | -`go test ./...` passe, les 5 exemples rendent des PNG 800×600 valides. | |
| 20 | - | |
| 21 | -| Fichier | Rôle | | |
| 22 | -|---|---| | |
| 23 | -| `vector.go` | `Vec3`, `Ray`, `Hit`, `Flip` | | |
| 24 | -| `objects.go` | interface `Object` ; `Sphere`, `Plane`, `Box`, `Group` | | |
| 25 | -| `material.go` | `Color` (+ presets nommés), `Material` (dont `Emission`), `Light`, `LightMaterial` | | |
| 26 | -| `camera.go` | caméra trou d'épingle ; `RayAt` (centre pixel) et `RayAtUV` (point fractionnaire, pour le supersampling) | | |
| 27 | -| `transform.go` | `Mat4` 4×4 : `Translate`/`RotateX/Y/Z`/`Scale`/`Multiply`/`Inverse` (Gauss-Jordan) ; `Transform{Object, Matrix}` | | |
| 28 | -| `renderer.go` | `Scene`, `Renderer` (réflexions/réfractions, ombres, `Samples` = grille n×n), `Image`, PPM | | |
| 29 | -| `pathtracer.go` | `PathTracer` : GI, ombres douces, cosinus sampling, russe roulette, déterministe (graines PCG par goroutine) | | |
| 30 | -| `png.go` | encodeur PNG maison, `Image.SavePNG` | | |
| 31 | -| `povray_test.go` | tests unitaires + smoke tests de rendu (dont `TestSupersampling`, `TestPathTracer`) | | |
| 32 | -| `examples/` | `spheres`, `mirror`, `procedural`, `transform`, `pathtrace` — chacun écrit son `.png` | | |
| 33 | -| `docs/TUTORIEL.md` | guide FR en 7 niveaux (0→6) pour composer de belles images | | |
| 34 | -| `README.md` | référence API (EN) | | |
| 35 | -| `ROADMAP.md` | ce qui manque, classé par impact | | |
| 36 | - | |
| 37 | -## Décisions importantes (ne pas re-discuter sans raison) | |
| 38 | - | |
| 39 | -- **Zéro dépendance** : PNG écrit à la main (`compress/zlib`, `hash/crc32`, | |
| 40 | - `encoding/binary`). PPM reste dispo. | |
| 41 | -- **Littéraux non-keyed** `Vec3{1, 0, 0}` / `Color{...}` : style | |
| 42 | - bibliothèque de maths, assumé ; `go vet` le signale dans les exemples, | |
| 43 | - c'est documenté dans le README. | |
| 44 | -- **Ordre des transformations** : `Multiply` s'applique de droite à | |
| 45 | - gauche (inside-out) — `Translate(...).Multiply(RotateX(...))` = | |
| 46 | - rotation d'abord. Documenté dans le tutoriel (niveau 3). | |
| 47 | -- **Normales sous transformation** : transposée de l'inverse (correct | |
| 48 | - sous scale non uniforme), renormalisées. | |
| 49 | -- **Path tracer** : lumières = surfaces émissives uniquement | |
| 50 | - (`LightMaterial`) ; `Scene.Lights` ignorées. `Samples` = rayons/pixel | |
| 51 | - (pas une grille). Déterministe par design. | |
| 52 | -- **Ombres** : les objets avec `Transparency > 0.5` ne portent pas | |
| 53 | - d'ombre (documenté). | |
| 54 | -- **Gamma 2.2** appliqué dans `Image.Set` (toujours, pas optionnel). | |
| 55 | -- **Tutoriel en français**, README en anglais — volontaire. | |
| 56 | - | |
| 57 | -## Pièges connus (déjà tombés dedans, ne pas re-tomber) | |
| 58 | - | |
| 59 | -- `RayAtUV` prend des `float64` : `px+0.5` sur un `int` est une | |
| 60 | - troncature silencieuse (vet le catche). Toujours `float64(px)+0.5`. | |
| 61 | -- `Hit.Dist` est un `float64` : `.Len()` pas `.Norm()` (qui renvoie un | |
| 62 | - `Vec3`). | |
| 63 | -- **Boucle infinie de rebonds** : verre/miroir peuvent rebondir | |
| 64 | - indéfiniment → stack overflow. Le `PathTracer` a une limite dure de | |
| 65 | - 50 (le `Renderer` est borné par `MaxDepth`). Tout nouveau type de | |
| 66 | - rebond doit respecter une limite. | |
| 67 | -- `Box` : les normales doivent pointer vers l'extérieur (le `Copysign` | |
| 68 | - est négatif dans `objects.go` — ne pas "corriger" en le retournant). | |
| 69 | -- Le `Transform` avec `Matrix` zéro (non initialisé) est singulier : | |
| 70 | - `Inverse` renvoie `ok=false`, l'objet est simplement invisible. | |
| 71 | - Utiliser `povray.Identity()` si on ne transforme pas. | |
| 72 | -- Tests de rendu : les couleurs après gamma ne sont pas les valeurs | |
| 73 | - attendues naïvement (ex. gris 0.5 → 186, pas 128). Mesurer avant | |
| 74 | - d'écrire les seuils. | |
| 75 | -- IEND d'un PNG : length(4) à `len-12`, "IEND" à `len-8`, CRC | |
| 76 | - `AE426082` à `len-4`. | |
| 77 | - | |
| 78 | -## Commandes utiles | |
| 79 | - | |
| 80 | -```sh | |
| 81 | -cd workspace | |
| 82 | -go test ./... # tous les tests | |
| 83 | -go vet . # propre sur la bibliothèque | |
| 84 | -go build ./... | |
| 85 | -go run ./examples/spheres # ~1 s | |
| 86 | -go run ./examples/transform # ~1 s | |
| 87 | -go run ./examples/pathtrace # ~40 s (64 samples/pixel) | |
| 88 | -``` | |
| 89 | - | |
| 90 | -## À faire (ordre suggéré, détail dans ROADMAP.md) | |
| 91 | - | |
| 92 | -Les 3 gros items du rendu sont faits (transformations, anti-aliasing, | |
| 93 | -path tracing). Reste : | |
| 94 | - | |
| 95 | -1. **Hygiène de package** (~1 h, rend le projet publiable) : | |
| 96 | - - module path réel (remplacer `github.com/user/povray` — demander à | |
| 97 | - l'utilisateur son identifiant GitHub) | |
| 98 | - - `LICENSE` (MIT ou Apache — demander laquelle) | |
| 99 | - - CI GitHub Actions (`go test` + `go vet` + `gofmt -l`) | |
| 100 | - - `Makefile` (`test`, `render`, `clean`) | |
| 101 | - - doc de package (`// Package povray ...` dans un fichier) | |
| 102 | -2. **Textures par position** (checker, gradient, bruit) — le matériau | |
| 103 | - est encore une couleur fixe. | |
| 104 | -3. **Maillage triangles** (import `.obj`) — la primitive manquante pour | |
| 105 | - les vraies scènes. | |
| 106 | -4. Divers : torus/cylindre/cône, normal mapping, importance sampling | |
| 107 | - des lumières, gamma optionnel. | |
| 108 | - | |
| 109 | -## Si on reprend | |
| 110 | - | |
| 111 | -1. Lire ce fichier. | |
| 112 | -2. `go test ./...` pour vérifier l'état de base. | |
| 113 | -3. Choisir un item "À faire" (demander à l'utilisateur s'il n'a pas | |
| 114 | - déjà tranché). | |
| 115 | -4. Le tutoriel (`docs/TUTORIEL.md`) est la référence de ce que l'API | |
| 116 | - doit continuer à rendre lisible — toute évolution d'API doit être | |
| 117 | - reflétée dedans, dans le README et dans le ROADMAP (les trois | |
| 118 | - restent synchronisés). | |
| deleted file mode 100644 | |||
| @@ -1,118 +0,0 @@ | |||
| 1 | -# Handoff — état du projet `povray` | ||
| 2 | - | ||
| 3 | -Dernière mise à jour : après le path tracing et le tutoriel. | ||
| 4 | -Ce fichier permet de reprendre le travail sans re-lire toute l'historique. | ||
| 5 | - | ||
| 6 | -## Ce qu'est le projet | ||
| 7 | - | ||
| 8 | -Bibliothèque Go **sans dépendances** qui rend des scènes façon POV-Ray : | ||
| 9 | -on décrit objets, matériaux, lumières et caméra en code Go, la | ||
| 10 | -bibliothèque trace des rayons et produit une image PNG. | ||
| 11 | - | ||
| 12 | -- Module : `github.com/user/povray` (**placeholder** — voir "À faire") | ||
| 13 | -- Go 1.27, encodeur PNG maison (stdlib only), rendu parallèle en | ||
| 14 | - chunks de lignes (goroutines + `sync.WaitGroup`) | ||
| 15 | - | ||
| 16 | -## État : ce qui marche | ||
| 17 | - | ||
| 18 | -Tout est vérifié : `gofmt` propre, `go vet` propre sur la bibliothèque, | ||
| 19 | -`go test ./...` passe, les 5 exemples rendent des PNG 800×600 valides. | ||
| 20 | - | ||
| 21 | -| Fichier | Rôle | | ||
| 22 | -|---|---| | ||
| 23 | -| `vector.go` | `Vec3`, `Ray`, `Hit`, `Flip` | | ||
| 24 | -| `objects.go` | interface `Object` ; `Sphere`, `Plane`, `Box`, `Group` | | ||
| 25 | -| `material.go` | `Color` (+ presets nommés), `Material` (dont `Emission`), `Light`, `LightMaterial` | | ||
| 26 | -| `camera.go` | caméra trou d'épingle ; `RayAt` (centre pixel) et `RayAtUV` (point fractionnaire, pour le supersampling) | | ||
| 27 | -| `transform.go` | `Mat4` 4×4 : `Translate`/`RotateX/Y/Z`/`Scale`/`Multiply`/`Inverse` (Gauss-Jordan) ; `Transform{Object, Matrix}` | | ||
| 28 | -| `renderer.go` | `Scene`, `Renderer` (réflexions/réfractions, ombres, `Samples` = grille n×n), `Image`, PPM | | ||
| 29 | -| `pathtracer.go` | `PathTracer` : GI, ombres douces, cosinus sampling, russe roulette, déterministe (graines PCG par goroutine) | | ||
| 30 | -| `png.go` | encodeur PNG maison, `Image.SavePNG` | | ||
| 31 | -| `povray_test.go` | tests unitaires + smoke tests de rendu (dont `TestSupersampling`, `TestPathTracer`) | | ||
| 32 | -| `examples/` | `spheres`, `mirror`, `procedural`, `transform`, `pathtrace` — chacun écrit son `.png` | | ||
| 33 | -| `docs/TUTORIEL.md` | guide FR en 7 niveaux (0→6) pour composer de belles images | | ||
| 34 | -| `README.md` | référence API (EN) | | ||
| 35 | -| `ROADMAP.md` | ce qui manque, classé par impact | | ||
| 36 | - | ||
| 37 | -## Décisions importantes (ne pas re-discuter sans raison) | ||
| 38 | - | ||
| 39 | -- **Zéro dépendance** : PNG écrit à la main (`compress/zlib`, `hash/crc32`, | ||
| 40 | - `encoding/binary`). PPM reste dispo. | ||
| 41 | -- **Littéraux non-keyed** `Vec3{1, 0, 0}` / `Color{...}` : style | ||
| 42 | - bibliothèque de maths, assumé ; `go vet` le signale dans les exemples, | ||
| 43 | - c'est documenté dans le README. | ||
| 44 | -- **Ordre des transformations** : `Multiply` s'applique de droite à | ||
| 45 | - gauche (inside-out) — `Translate(...).Multiply(RotateX(...))` = | ||
| 46 | - rotation d'abord. Documenté dans le tutoriel (niveau 3). | ||
| 47 | -- **Normales sous transformation** : transposée de l'inverse (correct | ||
| 48 | - sous scale non uniforme), renormalisées. | ||
| 49 | -- **Path tracer** : lumières = surfaces émissives uniquement | ||
| 50 | - (`LightMaterial`) ; `Scene.Lights` ignorées. `Samples` = rayons/pixel | ||
| 51 | - (pas une grille). Déterministe par design. | ||
| 52 | -- **Ombres** : les objets avec `Transparency > 0.5` ne portent pas | ||
| 53 | - d'ombre (documenté). | ||
| 54 | -- **Gamma 2.2** appliqué dans `Image.Set` (toujours, pas optionnel). | ||
| 55 | -- **Tutoriel en français**, README en anglais — volontaire. | ||
| 56 | - | ||
| 57 | -## Pièges connus (déjà tombés dedans, ne pas re-tomber) | ||
| 58 | - | ||
| 59 | -- `RayAtUV` prend des `float64` : `px+0.5` sur un `int` est une | ||
| 60 | - troncature silencieuse (vet le catche). Toujours `float64(px)+0.5`. | ||
| 61 | -- `Hit.Dist` est un `float64` : `.Len()` pas `.Norm()` (qui renvoie un | ||
| 62 | - `Vec3`). | ||
| 63 | -- **Boucle infinie de rebonds** : verre/miroir peuvent rebondir | ||
| 64 | - indéfiniment → stack overflow. Le `PathTracer` a une limite dure de | ||
| 65 | - 50 (le `Renderer` est borné par `MaxDepth`). Tout nouveau type de | ||
| 66 | - rebond doit respecter une limite. | ||
| 67 | -- `Box` : les normales doivent pointer vers l'extérieur (le `Copysign` | ||
| 68 | - est négatif dans `objects.go` — ne pas "corriger" en le retournant). | ||
| 69 | -- Le `Transform` avec `Matrix` zéro (non initialisé) est singulier : | ||
| 70 | - `Inverse` renvoie `ok=false`, l'objet est simplement invisible. | ||
| 71 | - Utiliser `povray.Identity()` si on ne transforme pas. | ||
| 72 | -- Tests de rendu : les couleurs après gamma ne sont pas les valeurs | ||
| 73 | - attendues naïvement (ex. gris 0.5 → 186, pas 128). Mesurer avant | ||
| 74 | - d'écrire les seuils. | ||
| 75 | -- IEND d'un PNG : length(4) à `len-12`, "IEND" à `len-8`, CRC | ||
| 76 | - `AE426082` à `len-4`. | ||
| 77 | - | ||
| 78 | -## Commandes utiles | ||
| 79 | - | ||
| 80 | -```sh | ||
| 81 | -cd workspace | ||
| 82 | -go test ./... # tous les tests | ||
| 83 | -go vet . # propre sur la bibliothèque | ||
| 84 | -go build ./... | ||
| 85 | -go run ./examples/spheres # ~1 s | ||
| 86 | -go run ./examples/transform # ~1 s | ||
| 87 | -go run ./examples/pathtrace # ~40 s (64 samples/pixel) | ||
| 88 | -``` | ||
| 89 | - | ||
| 90 | -## À faire (ordre suggéré, détail dans ROADMAP.md) | ||
| 91 | - | ||
| 92 | -Les 3 gros items du rendu sont faits (transformations, anti-aliasing, | ||
| 93 | -path tracing). Reste : | ||
| 94 | - | ||
| 95 | -1. **Hygiène de package** (~1 h, rend le projet publiable) : | ||
| 96 | - - module path réel (remplacer `github.com/user/povray` — demander à | ||
| 97 | - l'utilisateur son identifiant GitHub) | ||
| 98 | - - `LICENSE` (MIT ou Apache — demander laquelle) | ||
| 99 | - - CI GitHub Actions (`go test` + `go vet` + `gofmt -l`) | ||
| 100 | - - `Makefile` (`test`, `render`, `clean`) | ||
| 101 | - - doc de package (`// Package povray ...` dans un fichier) | ||
| 102 | -2. **Textures par position** (checker, gradient, bruit) — le matériau | ||
| 103 | - est encore une couleur fixe. | ||
| 104 | -3. **Maillage triangles** (import `.obj`) — la primitive manquante pour | ||
| 105 | - les vraies scènes. | ||
| 106 | -4. Divers : torus/cylindre/cône, normal mapping, importance sampling | ||
| 107 | - des lumières, gamma optionnel. | ||
| 108 | - | ||
| 109 | -## Si on reprend | ||
| 110 | - | ||
| 111 | -1. Lire ce fichier. | ||
| 112 | -2. `go test ./...` pour vérifier l'état de base. | ||
| 113 | -3. Choisir un item "À faire" (demander à l'utilisateur s'il n'a pas | ||
| 114 | - déjà tranché). | ||
| 115 | -4. Le tutoriel (`docs/TUTORIEL.md`) est la référence de ce que l'API | ||
| 116 | - doit continuer à rendre lisible — toute évolution d'API doit être | ||
| 117 | - reflétée dedans, dans le README et dans le ROADMAP (les trois | ||
| 118 | - restent synchronisés). | ||
deleted
demos/qwen-style/workspace/README.md +0 -127 | deleted file mode 100644 | ||
| @@ -1,127 +0,0 @@ | ||
| 1 | -# povray — a small Go ray tracer | |
| 2 | - | |
| 3 | -A dependency-free Go library for rendering scenes the way POV-Ray does: | |
| 4 | -you describe objects, materials, lights and a camera in Go code, and the | |
| 5 | -library traces rays to produce an image. | |
| 6 | - | |
| 7 | -``` | |
| 8 | -workspace/ | |
| 9 | -├── go.mod | |
| 10 | -├── HANDOFF.md state snapshot for resuming work later | |
| 11 | -├── vector.go Vec3 math, Ray, Hit | |
| 12 | -├── objects.go Sphere, Plane, Box, Group + the Object interface | |
| 13 | -├── material.go Color, Material, Light | |
| 14 | -├── camera.go pinhole Camera | |
| 15 | -├── transform.go Mat4 (translate/rotate/scale, inverse) + Transform object | |
| 16 | -├── renderer.go Scene, Renderer, Image, PPM writer | |
| 17 | -├── pathtracer.go PathTracer (global illumination, soft shadows) | |
| 18 | -├── png.go Image.SavePNG (dependency-free PNG encoder) | |
| 19 | -├── docs/ | |
| 20 | -│ └── TUTORIEL.md step-by-step guide (French) for making good images | |
| 21 | -└── examples/ | |
| 22 | - ├── spheres/ basic diffuse/specular scene | |
| 23 | - ├── mirror/ reflections + a glass sphere | |
| 24 | - ├── procedural/ a scene generated in loops (hue sweep grid) | |
| 25 | - ├── transform/ tilted boxes, ellipsoid, ring of rotated boxes | |
| 26 | - └── pathtrace/ path-traced room with emissive light panels | |
| 27 | -``` | |
| 28 | - | |
| 29 | -## Quick start | |
| 30 | - | |
| 31 | -```go | |
| 32 | -scene := povray.NewScene() | |
| 33 | -scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, | |
| 34 | - povray.Material{Diffuse: povray.Red, Specular: povray.White, Shininess: 60, Ambient: 0.1}) | |
| 35 | -scene.AddObject(povray.NewPlaneXY(0), | |
| 36 | - povray.Material{Diffuse: povray.Gray, Ambient: 0.3}) | |
| 37 | -scene.Lights = []povray.Light{povray.NewLight(povray.Vec3{4, 8, 6}, povray.White)} | |
| 38 | - | |
| 39 | -cam := povray.NewCamera(povray.Vec3{0, 2.5, 10}) | |
| 40 | -r := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | |
| 41 | -img := r.Render(cam) | |
| 42 | -img.SavePNG("out.png") // or img.SavePPM("out.ppm") | |
| 43 | -``` | |
| 44 | - | |
| 45 | -## Concepts | |
| 46 | - | |
| 47 | -- **Objects** implement `Object` (`Intersect(Ray) (Hit, bool)`). Built in: | |
| 48 | - `Sphere`, `Plane` (infinite), `Box` (axis-aligned), `Group` (closest of | |
| 49 | - many). Add your own by implementing the interface. | |
| 50 | -- **Transforms** place any object anywhere: `Translate`, `RotateX/Y/Z`, | |
| 51 | - `Scale` build a `Mat4` (composed with `Multiply`, applied inside-out), | |
| 52 | - and `Transform{Object, Matrix}` maps the ray into the object's local | |
| 53 | - space, intersects there, and maps the hit point and normal back to | |
| 54 | - world space — normals go through the inverse transpose, so non-uniform | |
| 55 | - scale stays correct. | |
| 56 | -- **Materials** control the look: `Diffuse` (base color), `Specular` + | |
| 57 | - `Shininess` (highlights), `Ambient`, `Reflectivity` (mirror, 0..1) and | |
| 58 | - `Transparency` + `IOR` (glass, 0..1). | |
| 59 | -- **Lights** are colored point lights with inverse-square falloff; the | |
| 60 | - renderer casts shadow rays for each. | |
| 61 | -- **Camera** is a pinhole camera: position, look-at point, up vector and | |
| 62 | - vertical field of view. | |
| 63 | -- **Renderer** is parallel (row chunks across goroutines) and limits | |
| 64 | - reflection/refraction bounces with `Scene.MaxDepth`. Set `Samples` | |
| 65 | - for anti-aliasing: `Samples: 2` shoots a 2×2 grid of rays per pixel | |
| 66 | - and averages them (0 or 1 = one ray per pixel, no anti-aliasing). | |
| 67 | -- **PathTracer** renders the same scenes with path tracing: rays | |
| 68 | - bounce between surfaces, giving global illumination and soft | |
| 69 | - shadows. Lights are emissive surfaces (`LightMaterial(color, | |
| 70 | - intensity)`); `Scene.Lights` are ignored. `Samples` is the number of | |
| 71 | - rays per pixel (16 for previews, 64+ for clean output). Renders are | |
| 72 | - deterministic. | |
| 73 | - | |
| 74 | -## Run the examples | |
| 75 | - | |
| 76 | -```sh | |
| 77 | -go run ./examples/spheres # writes spheres.png | |
| 78 | -go run ./examples/mirror # writes mirror.png | |
| 79 | -go run ./examples/procedural # writes procedural.png | |
| 80 | -go run ./examples/transform # writes transform.png | |
| 81 | -go run ./examples/pathtrace # writes pathtrace.png (slower) | |
| 82 | -``` | |
| 83 | - | |
| 84 | -For a step-by-step guide to composing good-looking scenes (materials, | |
| 85 | -camera, transforms, path tracing, color recipes), see | |
| 86 | -[docs/TUTORIEL.md](docs/TUTORIEL.md). | |
| 87 | - | |
| 88 | -Output is PNG (8-bit truecolor, written by the library's own encoder — no | |
| 89 | -dependencies). `SavePPM` is still available if you prefer PPM. | |
| 90 | - | |
| 91 | -## Inspiration | |
| 92 | - | |
| 93 | -The architecture and math follow the classic educational ray-tracing books; | |
| 94 | -the Go idioms are ours. | |
| 95 | - | |
| 96 | -- **Ray Tracing in One Weekend** (Peter Shirley) — the main influence: | |
| 97 | - the `Vec3`/`Ray`/`Hit` model, the quadratic-discriminant sphere | |
| 98 | - intersection, and the incremental build-up (sphere → ground → light → | |
| 99 | - specular → shadows → reflections → refraction). | |
| 100 | -- **POV-Ray** — the library's name and scene model come from it: describe | |
| 101 | - objects, materials, lights and a camera; the engine traces rays. | |
| 102 | -- **The Ray Tracer Challenge** (Shirley) — the unit-test style, | |
| 103 | - validating each primitive with simple geometric cases (head-on hit, | |
| 104 | - miss, ray from inside). | |
| 105 | - | |
| 106 | -Specific to this implementation (not in the books, which are C++/Java): | |
| 107 | - | |
| 108 | -- the Go `Object` interface with `Group` as a generic combinator | |
| 109 | -- parallel rendering in row chunks across goroutines (`sync.WaitGroup`) | |
| 110 | -- the hand-rolled PNG encoder (`png.go`) — the books only write PPM | |
| 111 | -- inverse-square light falloff with clamping in `shade` | |
| 112 | - | |
| 113 | -If you want to go further, *Ray Tracing in One Weekend* (and its sequel, | |
| 114 | -*Realistic Rendering with Path Tracing*) is the reference to read — this | |
| 115 | -codebase closely follows its progression. | |
| 116 | - | |
| 117 | -## Limitations | |
| 118 | - | |
| 119 | -No textures, no area lights (emissive surfaces are the light source for | |
| 120 | -the path tracer), no triangle meshes. Anti-aliasing is grid | |
| 121 | -supersampling (`Renderer.Samples`) plus stochastic sampling in the | |
| 122 | -`PathTracer`. Transforms are per-object (`Transform` wrapper); there is | |
| 123 | -no scene-wide model/view matrix yet. | |
| 124 | - | |
| 125 | -Note: `Vec3` and `Color` are intentionally written unkeyed | |
| 126 | -(`povray.Vec3{1, 0, 0}`) — `go vet` will flag this in example code; that's | |
| 127 | -expected for a math library. | |
| deleted file mode 100644 | |||
| @@ -1,127 +0,0 @@ | |||
| 1 | -# povray — a small Go ray tracer | ||
| 2 | - | ||
| 3 | -A dependency-free Go library for rendering scenes the way POV-Ray does: | ||
| 4 | -you describe objects, materials, lights and a camera in Go code, and the | ||
| 5 | -library traces rays to produce an image. | ||
| 6 | - | ||
| 7 | -``` | ||
| 8 | -workspace/ | ||
| 9 | -├── go.mod | ||
| 10 | -├── HANDOFF.md state snapshot for resuming work later | ||
| 11 | -├── vector.go Vec3 math, Ray, Hit | ||
| 12 | -├── objects.go Sphere, Plane, Box, Group + the Object interface | ||
| 13 | -├── material.go Color, Material, Light | ||
| 14 | -├── camera.go pinhole Camera | ||
| 15 | -├── transform.go Mat4 (translate/rotate/scale, inverse) + Transform object | ||
| 16 | -├── renderer.go Scene, Renderer, Image, PPM writer | ||
| 17 | -├── pathtracer.go PathTracer (global illumination, soft shadows) | ||
| 18 | -├── png.go Image.SavePNG (dependency-free PNG encoder) | ||
| 19 | -├── docs/ | ||
| 20 | -│ └── TUTORIEL.md step-by-step guide (French) for making good images | ||
| 21 | -└── examples/ | ||
| 22 | - ├── spheres/ basic diffuse/specular scene | ||
| 23 | - ├── mirror/ reflections + a glass sphere | ||
| 24 | - ├── procedural/ a scene generated in loops (hue sweep grid) | ||
| 25 | - ├── transform/ tilted boxes, ellipsoid, ring of rotated boxes | ||
| 26 | - └── pathtrace/ path-traced room with emissive light panels | ||
| 27 | -``` | ||
| 28 | - | ||
| 29 | -## Quick start | ||
| 30 | - | ||
| 31 | -```go | ||
| 32 | -scene := povray.NewScene() | ||
| 33 | -scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, | ||
| 34 | - povray.Material{Diffuse: povray.Red, Specular: povray.White, Shininess: 60, Ambient: 0.1}) | ||
| 35 | -scene.AddObject(povray.NewPlaneXY(0), | ||
| 36 | - povray.Material{Diffuse: povray.Gray, Ambient: 0.3}) | ||
| 37 | -scene.Lights = []povray.Light{povray.NewLight(povray.Vec3{4, 8, 6}, povray.White)} | ||
| 38 | - | ||
| 39 | -cam := povray.NewCamera(povray.Vec3{0, 2.5, 10}) | ||
| 40 | -r := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | ||
| 41 | -img := r.Render(cam) | ||
| 42 | -img.SavePNG("out.png") // or img.SavePPM("out.ppm") | ||
| 43 | -``` | ||
| 44 | - | ||
| 45 | -## Concepts | ||
| 46 | - | ||
| 47 | -- **Objects** implement `Object` (`Intersect(Ray) (Hit, bool)`). Built in: | ||
| 48 | - `Sphere`, `Plane` (infinite), `Box` (axis-aligned), `Group` (closest of | ||
| 49 | - many). Add your own by implementing the interface. | ||
| 50 | -- **Transforms** place any object anywhere: `Translate`, `RotateX/Y/Z`, | ||
| 51 | - `Scale` build a `Mat4` (composed with `Multiply`, applied inside-out), | ||
| 52 | - and `Transform{Object, Matrix}` maps the ray into the object's local | ||
| 53 | - space, intersects there, and maps the hit point and normal back to | ||
| 54 | - world space — normals go through the inverse transpose, so non-uniform | ||
| 55 | - scale stays correct. | ||
| 56 | -- **Materials** control the look: `Diffuse` (base color), `Specular` + | ||
| 57 | - `Shininess` (highlights), `Ambient`, `Reflectivity` (mirror, 0..1) and | ||
| 58 | - `Transparency` + `IOR` (glass, 0..1). | ||
| 59 | -- **Lights** are colored point lights with inverse-square falloff; the | ||
| 60 | - renderer casts shadow rays for each. | ||
| 61 | -- **Camera** is a pinhole camera: position, look-at point, up vector and | ||
| 62 | - vertical field of view. | ||
| 63 | -- **Renderer** is parallel (row chunks across goroutines) and limits | ||
| 64 | - reflection/refraction bounces with `Scene.MaxDepth`. Set `Samples` | ||
| 65 | - for anti-aliasing: `Samples: 2` shoots a 2×2 grid of rays per pixel | ||
| 66 | - and averages them (0 or 1 = one ray per pixel, no anti-aliasing). | ||
| 67 | -- **PathTracer** renders the same scenes with path tracing: rays | ||
| 68 | - bounce between surfaces, giving global illumination and soft | ||
| 69 | - shadows. Lights are emissive surfaces (`LightMaterial(color, | ||
| 70 | - intensity)`); `Scene.Lights` are ignored. `Samples` is the number of | ||
| 71 | - rays per pixel (16 for previews, 64+ for clean output). Renders are | ||
| 72 | - deterministic. | ||
| 73 | - | ||
| 74 | -## Run the examples | ||
| 75 | - | ||
| 76 | -```sh | ||
| 77 | -go run ./examples/spheres # writes spheres.png | ||
| 78 | -go run ./examples/mirror # writes mirror.png | ||
| 79 | -go run ./examples/procedural # writes procedural.png | ||
| 80 | -go run ./examples/transform # writes transform.png | ||
| 81 | -go run ./examples/pathtrace # writes pathtrace.png (slower) | ||
| 82 | -``` | ||
| 83 | - | ||
| 84 | -For a step-by-step guide to composing good-looking scenes (materials, | ||
| 85 | -camera, transforms, path tracing, color recipes), see | ||
| 86 | -[docs/TUTORIEL.md](docs/TUTORIEL.md). | ||
| 87 | - | ||
| 88 | -Output is PNG (8-bit truecolor, written by the library's own encoder — no | ||
| 89 | -dependencies). `SavePPM` is still available if you prefer PPM. | ||
| 90 | - | ||
| 91 | -## Inspiration | ||
| 92 | - | ||
| 93 | -The architecture and math follow the classic educational ray-tracing books; | ||
| 94 | -the Go idioms are ours. | ||
| 95 | - | ||
| 96 | -- **Ray Tracing in One Weekend** (Peter Shirley) — the main influence: | ||
| 97 | - the `Vec3`/`Ray`/`Hit` model, the quadratic-discriminant sphere | ||
| 98 | - intersection, and the incremental build-up (sphere → ground → light → | ||
| 99 | - specular → shadows → reflections → refraction). | ||
| 100 | -- **POV-Ray** — the library's name and scene model come from it: describe | ||
| 101 | - objects, materials, lights and a camera; the engine traces rays. | ||
| 102 | -- **The Ray Tracer Challenge** (Shirley) — the unit-test style, | ||
| 103 | - validating each primitive with simple geometric cases (head-on hit, | ||
| 104 | - miss, ray from inside). | ||
| 105 | - | ||
| 106 | -Specific to this implementation (not in the books, which are C++/Java): | ||
| 107 | - | ||
| 108 | -- the Go `Object` interface with `Group` as a generic combinator | ||
| 109 | -- parallel rendering in row chunks across goroutines (`sync.WaitGroup`) | ||
| 110 | -- the hand-rolled PNG encoder (`png.go`) — the books only write PPM | ||
| 111 | -- inverse-square light falloff with clamping in `shade` | ||
| 112 | - | ||
| 113 | -If you want to go further, *Ray Tracing in One Weekend* (and its sequel, | ||
| 114 | -*Realistic Rendering with Path Tracing*) is the reference to read — this | ||
| 115 | -codebase closely follows its progression. | ||
| 116 | - | ||
| 117 | -## Limitations | ||
| 118 | - | ||
| 119 | -No textures, no area lights (emissive surfaces are the light source for | ||
| 120 | -the path tracer), no triangle meshes. Anti-aliasing is grid | ||
| 121 | -supersampling (`Renderer.Samples`) plus stochastic sampling in the | ||
| 122 | -`PathTracer`. Transforms are per-object (`Transform` wrapper); there is | ||
| 123 | -no scene-wide model/view matrix yet. | ||
| 124 | - | ||
| 125 | -Note: `Vec3` and `Color` are intentionally written unkeyed | ||
| 126 | -(`povray.Vec3{1, 0, 0}`) — `go vet` will flag this in example code; that's | ||
| 127 | -expected for a math library. | ||
deleted
demos/qwen-style/workspace/ROADMAP.md +0 -62 | deleted file mode 100644 | ||
| @@ -1,62 +0,0 @@ | ||
| 1 | -# Roadmap — what's missing | |
| 2 | - | |
| 3 | -Status snapshot of the `povray` library and the work that remains, in | |
| 4 | -order of impact. The core is solid (primitives, materials, shadows, | |
| 5 | -reflections, refraction, parallel rendering, PNG, tests); this is a | |
| 6 | -*minimal* ray tracer with a clear path to grow. | |
| 7 | - | |
| 8 | -## 1. Rendering — biggest visual impact | |
| 9 | - | |
| 10 | -- **Transformations** (done — see `transform.go`). Objects can now be | |
| 11 | - translated, rotated and (non-uniformly) scaled via the `Transform` | |
| 12 | - wrapper; normals are mapped with the inverse transpose so lighting | |
| 13 | - stays correct under non-uniform scale. | |
| 14 | -- **Anti-aliasing** (done — see `Renderer.Samples`). Grid supersampling: | |
| 15 | - `Samples: n` shoots an n×n grid of rays per pixel and averages them. | |
| 16 | - All examples now render with `Samples: 2`. | |
| 17 | -- **Textures** — color by position (checker, gradient, noise). Today a | |
| 18 | - material is a single fixed color. | |
| 19 | -- **Normal mapping / bump** — non-smooth surfaces without meshes. | |
| 20 | -- **Path tracing** (done — see `pathtracer.go`). The `PathTracer` | |
| 21 | - bounces rays between surfaces: global illumination, soft shadows, | |
| 22 | - colored bounce light. Lights are emissive surfaces | |
| 23 | - (`LightMaterial`); renders are deterministic. | |
| 24 | -- **Area lights** — partially covered: emissive surfaces (boxes) act as | |
| 25 | - area lights in the path tracer. A dedicated, more efficient area-light | |
| 26 | - sampler (importance sampling over the light) is still missing. | |
| 27 | -- **More primitives**: torus, cylinder, cone, and especially a | |
| 28 | - **triangle mesh** (to import `.obj` models). | |
| 29 | - | |
| 30 | -## 2. Physical quality | |
| 31 | - | |
| 32 | -- **Gamma / tone mapping** — the current output is linear; gamma 2.2 | |
| 33 | - makes colors closer to what the eye expects. | |
| 34 | -- **Refraction and shadows** — transparent objects are ignored by | |
| 35 | - shadow rays (documented limitation); acceptable, but noted. | |
| 36 | - | |
| 37 | -## 3. Engineering — what a publishable package needs | |
| 38 | - | |
| 39 | -- **Real module path** — `github.com/user/povray` is a placeholder; | |
| 40 | - nobody can `go get` the package. | |
| 41 | -- **CI** — no `.github/workflows`; a simple `go test` + `go vet` + | |
| 42 | - `gofmt -l` on GitHub Actions. | |
| 43 | -- **Test coverage** — examples have no tests; missing cases: `Box` seen | |
| 44 | - from inside, nested `Group`, refraction through the glass sphere, | |
| 45 | - `SavePNG` on a rendered (not 4×3) image. | |
| 46 | -- **`go vet` clean everywhere** — unkeyed `Vec3{...}`/`Color{...}` | |
| 47 | - literals in examples are still flagged; key them, add a `//nolint`, | |
| 48 | - or provide constructors. | |
| 49 | -- **License** — no `LICENSE` (MIT/Apache), so the code is "all rights | |
| 50 | - reserved" by default. | |
| 51 | -- **`Makefile`** — `test`, `render`, `clean` targets. | |
| 52 | -- **Docs** — public types are commented, but there is no package-level | |
| 53 | - doc (`// Package povray ...`). | |
| 54 | - | |
| 55 | -## Suggested order | |
| 56 | - | |
| 57 | -1. ~~**Transformations**~~ — done (`transform.go`, `examples/transform`). | |
| 58 | -2. ~~**Anti-aliasing**~~ — done (`Renderer.Samples`, grid supersampling). | |
| 59 | -3. ~~**Path tracing**~~ — done (`pathtracer.go`, `examples/pathtrace`). | |
| 60 | - | |
| 61 | -Package hygiene (real module path + `LICENSE` + CI + `Makefile`) is | |
| 62 | -about an hour of work and makes the package publishable. | |
| deleted file mode 100644 | |||
| @@ -1,62 +0,0 @@ | |||
| 1 | -# Roadmap — what's missing | ||
| 2 | - | ||
| 3 | -Status snapshot of the `povray` library and the work that remains, in | ||
| 4 | -order of impact. The core is solid (primitives, materials, shadows, | ||
| 5 | -reflections, refraction, parallel rendering, PNG, tests); this is a | ||
| 6 | -*minimal* ray tracer with a clear path to grow. | ||
| 7 | - | ||
| 8 | -## 1. Rendering — biggest visual impact | ||
| 9 | - | ||
| 10 | -- **Transformations** (done — see `transform.go`). Objects can now be | ||
| 11 | - translated, rotated and (non-uniformly) scaled via the `Transform` | ||
| 12 | - wrapper; normals are mapped with the inverse transpose so lighting | ||
| 13 | - stays correct under non-uniform scale. | ||
| 14 | -- **Anti-aliasing** (done — see `Renderer.Samples`). Grid supersampling: | ||
| 15 | - `Samples: n` shoots an n×n grid of rays per pixel and averages them. | ||
| 16 | - All examples now render with `Samples: 2`. | ||
| 17 | -- **Textures** — color by position (checker, gradient, noise). Today a | ||
| 18 | - material is a single fixed color. | ||
| 19 | -- **Normal mapping / bump** — non-smooth surfaces without meshes. | ||
| 20 | -- **Path tracing** (done — see `pathtracer.go`). The `PathTracer` | ||
| 21 | - bounces rays between surfaces: global illumination, soft shadows, | ||
| 22 | - colored bounce light. Lights are emissive surfaces | ||
| 23 | - (`LightMaterial`); renders are deterministic. | ||
| 24 | -- **Area lights** — partially covered: emissive surfaces (boxes) act as | ||
| 25 | - area lights in the path tracer. A dedicated, more efficient area-light | ||
| 26 | - sampler (importance sampling over the light) is still missing. | ||
| 27 | -- **More primitives**: torus, cylinder, cone, and especially a | ||
| 28 | - **triangle mesh** (to import `.obj` models). | ||
| 29 | - | ||
| 30 | -## 2. Physical quality | ||
| 31 | - | ||
| 32 | -- **Gamma / tone mapping** — the current output is linear; gamma 2.2 | ||
| 33 | - makes colors closer to what the eye expects. | ||
| 34 | -- **Refraction and shadows** — transparent objects are ignored by | ||
| 35 | - shadow rays (documented limitation); acceptable, but noted. | ||
| 36 | - | ||
| 37 | -## 3. Engineering — what a publishable package needs | ||
| 38 | - | ||
| 39 | -- **Real module path** — `github.com/user/povray` is a placeholder; | ||
| 40 | - nobody can `go get` the package. | ||
| 41 | -- **CI** — no `.github/workflows`; a simple `go test` + `go vet` + | ||
| 42 | - `gofmt -l` on GitHub Actions. | ||
| 43 | -- **Test coverage** — examples have no tests; missing cases: `Box` seen | ||
| 44 | - from inside, nested `Group`, refraction through the glass sphere, | ||
| 45 | - `SavePNG` on a rendered (not 4×3) image. | ||
| 46 | -- **`go vet` clean everywhere** — unkeyed `Vec3{...}`/`Color{...}` | ||
| 47 | - literals in examples are still flagged; key them, add a `//nolint`, | ||
| 48 | - or provide constructors. | ||
| 49 | -- **License** — no `LICENSE` (MIT/Apache), so the code is "all rights | ||
| 50 | - reserved" by default. | ||
| 51 | -- **`Makefile`** — `test`, `render`, `clean` targets. | ||
| 52 | -- **Docs** — public types are commented, but there is no package-level | ||
| 53 | - doc (`// Package povray ...`). | ||
| 54 | - | ||
| 55 | -## Suggested order | ||
| 56 | - | ||
| 57 | -1. ~~**Transformations**~~ — done (`transform.go`, `examples/transform`). | ||
| 58 | -2. ~~**Anti-aliasing**~~ — done (`Renderer.Samples`, grid supersampling). | ||
| 59 | -3. ~~**Path tracing**~~ — done (`pathtracer.go`, `examples/pathtrace`). | ||
| 60 | - | ||
| 61 | -Package hygiene (real module path + `LICENSE` + CI + `Makefile`) is | ||
| 62 | -about an hour of work and makes the package publishable. | ||
deleted
demos/qwen-style/workspace/camera.go +0 -47 | deleted file mode 100644 | ||
| @@ -1,47 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import "math" | |
| 4 | - | |
| 5 | -// Camera is a pinhole camera looking from Position toward LookAt. | |
| 6 | -type Camera struct { | |
| 7 | - Position Vec3 | |
| 8 | - LookAt Vec3 | |
| 9 | - Up Vec3 | |
| 10 | - // Fov is the vertical field of view in degrees. | |
| 11 | - Fov float64 | |
| 12 | -} | |
| 13 | - | |
| 14 | -// NewCamera makes a camera looking at the origin from a position, | |
| 15 | -// with the world Y axis as up and a 60 degree vertical field of view. | |
| 16 | -func NewCamera(pos Vec3) Camera { | |
| 17 | - return Camera{Position: pos, LookAt: Vec3{}, Up: Vec3{0, 1, 0}, Fov: 60} | |
| 18 | -} | |
| 19 | - | |
| 20 | -// Basis returns the camera's right, up and forward unit vectors. | |
| 21 | -func (c Camera) Basis() (right, up, fwd Vec3) { | |
| 22 | - fwd = c.LookAt.Sub(c.Position).Norm() | |
| 23 | - right = fwd.Cross(c.Up).Norm() | |
| 24 | - up = right.Cross(fwd) | |
| 25 | - return | |
| 26 | -} | |
| 27 | - | |
| 28 | -// RayAt returns the ray through the center of pixel (px, py). | |
| 29 | -func (c Camera) RayAt(px, py, w, h int) Ray { | |
| 30 | - return c.RayAtUV(float64(px)+0.5, float64(py)+0.5, w, h) | |
| 31 | -} | |
| 32 | - | |
| 33 | -// RayAtUV returns the ray through the point (u, v) in pixel units | |
| 34 | -// (u in [0, w), v in [0, h)); used for supersampling. | |
| 35 | -func (c Camera) RayAtUV(u, v float64, w, h int) Ray { | |
| 36 | - right, up, fwd := c.Basis() | |
| 37 | - // Vertical half-angle; horizontal scaled by aspect. | |
| 38 | - tanHalf := math.Tan(radians(c.Fov) / 2) | |
| 39 | - du := 2 * tanHalf | |
| 40 | - dv := du * float64(w) / float64(h) | |
| 41 | - uu := (2*u/float64(w) - 1) * dv | |
| 42 | - vv := (1 - 2*v/float64(h)) * du | |
| 43 | - dir := fwd.Add(right.Scale(uu)).Add(up.Scale(vv)).Norm() | |
| 44 | - return Ray{Origin: c.Position, Dir: dir} | |
| 45 | -} | |
| 46 | - | |
| 47 | -func radians(deg float64) float64 { return deg * math.Pi / 180 } | |
| deleted file mode 100644 | |||
| @@ -1,47 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import "math" | ||
| 4 | - | ||
| 5 | -// Camera is a pinhole camera looking from Position toward LookAt. | ||
| 6 | -type Camera struct { | ||
| 7 | - Position Vec3 | ||
| 8 | - LookAt Vec3 | ||
| 9 | - Up Vec3 | ||
| 10 | - // Fov is the vertical field of view in degrees. | ||
| 11 | - Fov float64 | ||
| 12 | -} | ||
| 13 | - | ||
| 14 | -// NewCamera makes a camera looking at the origin from a position, | ||
| 15 | -// with the world Y axis as up and a 60 degree vertical field of view. | ||
| 16 | -func NewCamera(pos Vec3) Camera { | ||
| 17 | - return Camera{Position: pos, LookAt: Vec3{}, Up: Vec3{0, 1, 0}, Fov: 60} | ||
| 18 | -} | ||
| 19 | - | ||
| 20 | -// Basis returns the camera's right, up and forward unit vectors. | ||
| 21 | -func (c Camera) Basis() (right, up, fwd Vec3) { | ||
| 22 | - fwd = c.LookAt.Sub(c.Position).Norm() | ||
| 23 | - right = fwd.Cross(c.Up).Norm() | ||
| 24 | - up = right.Cross(fwd) | ||
| 25 | - return | ||
| 26 | -} | ||
| 27 | - | ||
| 28 | -// RayAt returns the ray through the center of pixel (px, py). | ||
| 29 | -func (c Camera) RayAt(px, py, w, h int) Ray { | ||
| 30 | - return c.RayAtUV(float64(px)+0.5, float64(py)+0.5, w, h) | ||
| 31 | -} | ||
| 32 | - | ||
| 33 | -// RayAtUV returns the ray through the point (u, v) in pixel units | ||
| 34 | -// (u in [0, w), v in [0, h)); used for supersampling. | ||
| 35 | -func (c Camera) RayAtUV(u, v float64, w, h int) Ray { | ||
| 36 | - right, up, fwd := c.Basis() | ||
| 37 | - // Vertical half-angle; horizontal scaled by aspect. | ||
| 38 | - tanHalf := math.Tan(radians(c.Fov) / 2) | ||
| 39 | - du := 2 * tanHalf | ||
| 40 | - dv := du * float64(w) / float64(h) | ||
| 41 | - uu := (2*u/float64(w) - 1) * dv | ||
| 42 | - vv := (1 - 2*v/float64(h)) * du | ||
| 43 | - dir := fwd.Add(right.Scale(uu)).Add(up.Scale(vv)).Norm() | ||
| 44 | - return Ray{Origin: c.Position, Dir: dir} | ||
| 45 | -} | ||
| 46 | - | ||
| 47 | -func radians(deg float64) float64 { return deg * math.Pi / 180 } | ||
deleted
demos/qwen-style/workspace/docs/TUTORIEL.md +0 -302 | deleted file mode 100644 | ||
| @@ -1,302 +0,0 @@ | ||
| 1 | -# Tutoriel — créer de belles images | |
| 2 | - | |
| 3 | -Ce guide vous apprend à composer des scènes avec la bibliothèque | |
| 4 | -`povray`, du plus simple au plus avancé. Chaque niveau s'appuie sur le | |
| 5 | -précédent ; chaque code est complet et rendable tel quel | |
| 6 | -(`go run` dans le dossier du projet). | |
| 7 | - | |
| 8 | -Les exemples finis sont dans `examples/` — quand un niveau vous semble | |
| 9 | -flou, lisez le fichier correspondant. | |
| 10 | - | |
| 11 | -## Niveau 0 — Votre première image | |
| 12 | - | |
| 13 | -Une scène, une sphère, une lumière, une caméra. C'est tout. | |
| 14 | - | |
| 15 | -```go | |
| 16 | -package main | |
| 17 | - | |
| 18 | -import ( | |
| 19 | - "log" | |
| 20 | - | |
| 21 | - "github.com/user/povray" | |
| 22 | -) | |
| 23 | - | |
| 24 | -func main() { | |
| 25 | - scene := povray.NewScene() | |
| 26 | - scene.AddObject( | |
| 27 | - povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, | |
| 28 | - povray.Material{Diffuse: povray.Red, Specular: povray.White, Shininess: 30}, | |
| 29 | - ) | |
| 30 | - scene.AddObject(povray.NewPlaneXY(0), | |
| 31 | - povray.Material{Diffuse: povray.Gray, Ambient: 0.3}) | |
| 32 | - scene.Lights = []povray.Light{povray.NewLight(povray.Vec3{4, 8, 6}, povray.White)} | |
| 33 | - | |
| 34 | - cam := povray.NewCamera(povray.Vec3{0, 2, 8}) | |
| 35 | - r := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | |
| 36 | - if err := r.Render(cam).SavePNG("hello.png"); err != nil { | |
| 37 | - log.Fatal(err) | |
| 38 | - } | |
| 39 | -} | |
| 40 | -``` | |
| 41 | - | |
| 42 | -Ce qu'il faut retenir : | |
| 43 | - | |
| 44 | -- **`NewScene()`** crée une scène (fond blanc, gamma 2.2). | |
| 45 | -- **`AddObject(obj, mat)`** ajoute un objet *et* son matériau. | |
| 46 | -- **`Lights`** est une liste de lumières ponctuelles. | |
| 47 | -- **`Renderer`** : `Width`/`Height` en pixels, `Samples: 2` = 4 rayons | |
| 48 | - par pixel (anti-aliasing). Sans `Samples`, les bords sont en dents de | |
| 49 | - scie. | |
| 50 | -- **`SavePNG`** écrit le fichier. | |
| 51 | - | |
| 52 | -## Niveau 1 — Matériaux et couleurs | |
| 53 | - | |
| 54 | -Le matériau est ce qui fait l'image. Les champs utiles : | |
| 55 | - | |
| 56 | -| Champ | Effet | Valeurs typiques | | |
| 57 | -|---|---|---| | |
| 58 | -| `Diffuse` | couleur de base | `povray.Red`, `povray.Color{0.8, 0.2, 0.1}` | | |
| 59 | -| `Specular` | couleur du reflet | `povray.White` | | |
| 60 | -| `Shininess` | taille du reflet (plus = plus petit) | 10 (mat) à 100 (chrome) | | |
| 61 | -| `Ambient` | lumière résiduelle dans l'ombre | 0.1 à 0.4 | | |
| 62 | -| `Reflectivity` | force du miroir | 0 à 1 | | |
| 63 | -| `Transparency` | force du verre | 0 à 1 | | |
| 64 | -| `IOR` | indice de réfraction du verre | 1.5 (verre), 1.33 (eau) | | |
| 65 | - | |
| 66 | -**Règle d'or des couleurs** : les valeurs de `Diffuse` sont *linéaires*. | |
| 67 | -Un gris `0.5` rend plus sombre qu'il n'en a l'air (le gamma 2.2 | |
| 68 | -l'éclaircit au rendu, mais pas autant que le direct). Pour des couleurs | |
| 69 | -"vives", montez les valeurs : `Color{0.9, 0.1, 0.1}` est plus rouge que | |
| 70 | -`Color{0.5, 0.05, 0.05}`. | |
| 71 | - | |
| 72 | -Deux matériaux opposés sur des objets voisins créent du contraste — | |
| 73 | -c'est la base de toute belle image : | |
| 74 | - | |
| 75 | -```go | |
| 76 | -// Un sol sombre porte des objets clairs (et inversement). | |
| 77 | -scene.AddObject(povray.NewPlaneXY(0), | |
| 78 | - povray.Material{Diffuse: povray.Color{0.12, 0.12, 0.15}, Ambient: 0.3}) | |
| 79 | - | |
| 80 | -scene.AddObject(povray.Sphere{Center: povray.Vec3{-2, 1, 0}, Radius: 1}, | |
| 81 | - povray.Material{Diffuse: povray.Color{0.9, 0.3, 0.2}, Specular: povray.White, Shininess: 40}) | |
| 82 | -scene.AddObject(povray.Sphere{Center: povray.Vec3{2, 1, 0}, Radius: 1}, | |
| 83 | - povray.Material{Diffuse: povray.Color{0.2, 0.7, 0.9}, Specular: povray.White, Shininess: 40}) | |
| 84 | -``` | |
| 85 | - | |
| 86 | -Voir `examples/spheres/` pour une scène complète. | |
| 87 | - | |
| 88 | -## Niveau 2 — La caméra et la composition | |
| 89 | - | |
| 90 | -La caméra est un trou d'épingle : elle est à `Position`, regarde | |
| 91 | -`LookAt`, avec `Fov` (champ vertical en degrés). | |
| 92 | - | |
| 93 | -```go | |
| 94 | -cam := povray.NewCamera(povray.Vec3{0, 3, 10}) // reculer et monter | |
| 95 | -cam.LookAt = povray.Vec3{0, 1, -2} // regarder plus loin que l'objet | |
| 96 | -cam.Fov = 50 // < 60 = téléobjectif, > 70 = grand angle | |
| 97 | -``` | |
| 98 | - | |
| 99 | -**Principes de composition** : | |
| 100 | - | |
| 101 | -1. **Éloignez la caméra** plutôt que de zoomer : un Fov large | |
| 102 | - (grand angle) déforme les objets proches ; un Fov étroit les | |
| 103 | - comprime et aplatit la scène. | |
| 104 | -2. **Regardez légèrement au-dessus ou en dessous** du sujet, pas | |
| 105 | - exactement au milieu : `LookAt` un peu plus haut que le centre des | |
| 106 | - objets donne une vue "de dessus" naturelle. | |
| 107 | -3. **Laissez de la place** : le sujet au centre exact est rarement | |
| 108 | - joli. Décalez-le d'un tiers (règle des tiers) en déplaçant | |
| 109 | - `LookAt` dans la direction opposée. | |
| 110 | -4. **Le plan du sol doit sortir du cadre** : si l'horizon est visible, | |
| 111 | - la scène a l'air petite. Montez la caméra ou rapprochez le sol. | |
| 112 | - | |
| 113 | -## Niveau 3 — Transformations : placer les objets | |
| 114 | - | |
| 115 | -Les objets bruts vivent dans l'espace monde. Pour les incliner, | |
| 116 | -étirer ou les disposer en boucle, on les enveloppe dans un | |
| 117 | -`Transform` : | |
| 118 | - | |
| 119 | -```go | |
| 120 | -// Une boîte inclinée de 30° autour de X, déplacée à (-3, 0, -2). | |
| 121 | -tilted := povray.Transform{ | |
| 122 | - Object: povray.Box{Min: povray.Vec3{-1, 0, -1}, Max: povray.Vec3{1, 2, 1}}, | |
| 123 | - Matrix: povray.Translate(-3, 0, -2).Multiply(povray.RotateX(math.Pi / 6)), | |
| 124 | -} | |
| 125 | -scene.AddObject(tilted, mat) | |
| 126 | -``` | |
| 127 | - | |
| 128 | -**L'ordre compte** : `Multiply` s'applique de droite à gauche. | |
| 129 | -`Translate(...).Multiply(RotateX(...))` = *d'abord* la rotation, | |
| 130 | -*puis* la translation. Pour "se déplacer puis tourner sur place", | |
| 131 | -inversez : `RotateX(...).Multiply(Translate(...))`. | |
| 132 | - | |
| 133 | -Les primitives de base : | |
| 134 | - | |
| 135 | -- `Translate(x, y, z)` — déplacer | |
| 136 | -- `RotateX/Y/Z(angle)` — tourner (radians : `math.Pi/2` = 90°) | |
| 137 | -- `Scale(x, y, z)` — étirer (une sphère échelonnée = ellipsoïde) | |
| 138 | -- `Identity()` — rien | |
| 139 | - | |
| 140 | -**Disposer en boucle** est le moyen le plus simple de créer des | |
| 141 | -scènes riches : | |
| 142 | - | |
| 143 | -```go | |
| 144 | -var ring []povray.Object | |
| 145 | -for i := 0; i < 12; i++ { | |
| 146 | - a := 2 * math.Pi * float64(i) / 12 | |
| 147 | - m := povray.RotateY(a).Multiply(povray.Translate(0, 0, -5.5)) | |
| 148 | - ring = append(ring, povray.Transform{ | |
| 149 | - Object: povray.Box{Min: povray.Vec3{-0.4, 0, -0.4}, Max: povray.Vec3{0.4, 1.6, 0.4}}, | |
| 150 | - Matrix: m, | |
| 151 | - }) | |
| 152 | -} | |
| 153 | -scene.AddObjects(ring, povray.Material{Diffuse: povray.Gold, ...}) | |
| 154 | -``` | |
| 155 | - | |
| 156 | -Voir `examples/transform/` (anneau de boîtes, ellipsoïde miroir) et | |
| 157 | -`examples/procedural/` (grille de 200 sphères dont la couleur varie | |
| 158 | -par position). | |
| 159 | - | |
| 160 | -## Niveau 4 — Miroirs et verre | |
| 161 | - | |
| 162 | -Deux champs de matériau suffisent : | |
| 163 | - | |
| 164 | -```go | |
| 165 | -// Miroir : Reflectivity élevé, Diffuse sombre. | |
| 166 | -mirror := povray.Material{ | |
| 167 | - Diffuse: povray.Color{0.1, 0.1, 0.12}, | |
| 168 | - Reflectivity: 0.9, | |
| 169 | - Specular: povray.White, Shininess: 80, | |
| 170 | -} | |
| 171 | - | |
| 172 | -// Verre : Transparency élevé + IOR. | |
| 173 | -glass := povray.Material{ | |
| 174 | - Diffuse: povray.White, | |
| 175 | - Transparency: 0.95, | |
| 176 | - IOR: 1.5, | |
| 177 | - Specular: povray.White, Shininess: 100, | |
| 178 | - Ambient: 0.05, | |
| 179 | -} | |
| 180 | -``` | |
| 181 | - | |
| 182 | -**Astuces** : | |
| 183 | - | |
| 184 | -- Un miroir pur (`Reflectivity: 1`, `Diffuse: Black`) est joli mais | |
| 185 | - plat. Un `Reflectivity: 0.5` avec un `Diffuse` coloré donne un | |
| 186 | - "métal teinté" très photogénique. | |
| 187 | -- Le verre est plus beau quand il *contient* ou *cache* quelque chose | |
| 188 | - de coloré. Une sphère de verre seule est presque invisible. | |
| 189 | -- L'effet "sol miroir" : un plan avec `Reflectivity: 0.3` et | |
| 190 | - `Diffuse` sombre fait des reflets doux sous les objets. | |
| 191 | - | |
| 192 | -Voir `examples/mirror/`. | |
| 193 | - | |
| 194 | -## Niveau 5 — Path tracing : lumière réaliste | |
| 195 | - | |
| 196 | -Le `Renderer` classique éclaire les surfaces une à une. Le | |
| 197 | -**`PathTracer`** fait rebondir la lumière de surface en surface : | |
| 198 | -éclairage indirect (une sphère rouge teintée le sol autour d'elle), | |
| 199 | -ombres douces, lumière de couleur. C'est ce qui donne des images | |
| 200 | -"réelles". | |
| 201 | - | |
| 202 | -```go | |
| 203 | -// Les lumières du path tracer sont des SURFACES ÉMISSIVES. | |
| 204 | -panel := povray.Transform{ | |
| 205 | - Object: povray.Box{Min: povray.Vec3{-1.5, 0, -1.5}, Max: povray.Vec3{1.5, 0.15, 1.5}}, | |
| 206 | - Matrix: povray.Translate(0, 9, -2), | |
| 207 | -} | |
| 208 | -scene.AddObject(panel, povray.LightMaterial(povray.Color{1, 0.85, 0.65}, 14)) | |
| 209 | - | |
| 210 | -tracer := povray.PathTracer{Scene: scene, Width: 800, Height: 600, Samples: 64} | |
| 211 | -img := tracer.Render(cam) | |
| 212 | -``` | |
| 213 | - | |
| 214 | -Différences avec le `Renderer` : | |
| 215 | - | |
| 216 | -- **Les `Scene.Lights` sont ignorées** — la lumière vient des matériaux | |
| 217 | - avec `Emission` (utilisez `LightMaterial(couleur, intensité)`). | |
| 218 | -- **`Samples` = nombre de rayons par pixel**, pas une grille. 16 = | |
| 219 | - rapide (un peu de bruit), 64 = propre, 256 = publication. | |
| 220 | -- **Le bruit est normal** : c'est un estimateur aléatoire. Plus de | |
| 221 | - samples = moins de bruit. Le rendu est déterministe (même image à | |
| 222 | - chaque fois pour la même config). | |
| 223 | -- **Les intensités** : un panneau de lumière a besoin d'une intensité | |
| 224 | - de 10 à 20 pour bien éclairer une scène à cette échelle. | |
| 225 | - | |
| 226 | -Voir `examples/pathtrace/` : une pièce avec panneau chaud + bandeau | |
| 227 | -froid (la lumière rebondit et teinte les murs), sphère rouge, miroir | |
| 228 | -bleu, verre. | |
| 229 | - | |
| 230 | -## Niveau 6 — Recettes pour de belles images | |
| 231 | - | |
| 232 | -### La palette | |
| 233 | - | |
| 234 | -Choisissez 2 à 3 couleurs de base et un neutre. Les complémentaires | |
| 235 | -se font face : | |
| 236 | - | |
| 237 | -```go | |
| 238 | -var ( | |
| 239 | - warm = povray.Color{0.9, 0.45, 0.2} // orange | |
| 240 | - cool = povray.Color{0.2, 0.4, 0.85} // bleu | |
| 241 | - neutral = povray.Color{0.25, 0.25, 0.28} // gris foncé pour le sol | |
| 242 | -) | |
| 243 | -``` | |
| 244 | - | |
| 245 | -Un sol neutre et sombre est le fond le plus polyvalent : il ne | |
| 246 | -concurrence pas les objets. | |
| 247 | - | |
| 248 | -### La lumière | |
| 249 | - | |
| 250 | -- **Deux lumières** valent mieux qu'une : une clé (forte, chaude, | |
| 251 | - en hauteur) + une rembourrage (faible, froide, en face) : | |
| 252 | - | |
| 253 | -```go | |
| 254 | -scene.Lights = []povray.Light{ | |
| 255 | - povray.NewLight(povray.Vec3{6, 10, 8}, povray.Color{1, 0.95, 0.85}), | |
| 256 | - povray.NewLight(povray.Vec3{-8, 4, -4}, povray.Color{0.5, 0.6, 1.0}), | |
| 257 | -} | |
| 258 | -``` | |
| 259 | - | |
| 260 | -- Placez la lumière **en hauteur et de côté** : en face, les ombres | |
| 261 | - disparaissent derrière les objets ; en haut, tout est plat. | |
| 262 | -- Dans le path tracer, la **couleur de la lumière** est plus | |
| 263 | - importante que son intensité : un panneau `Color{1, 0.85, 0.65}` | |
| 264 | - (chaud) + un bandeau `Color{0.4, 0.6, 1}` (froid) crée une ambiance | |
| 265 | - immédiatement. | |
| 266 | - | |
| 267 | -### La profondeur | |
| 268 | - | |
| 269 | -- **Tailles variées** : un grand + un petit objet donnent l'échelle. | |
| 270 | -- **Écartez en Z** : les objets à des distances différentes créent de | |
| 271 | - la profondeur ; à la même Z, la scène est plate. | |
| 272 | -- **Un élément au premier plan** (partiellement hors cadre) donne un | |
| 273 | - effet "regard à travers". | |
| 274 | - | |
| 275 | -### Les réglages finaux | |
| 276 | - | |
| 277 | -| Réglage | Pour… | | |
| 278 | -|---|---| | |
| 279 | -| `Samples: 2` (Renderer) | aperçu rapide, propre | | |
| 280 | -| `Samples: 4` (Renderer) | rendu final du Renderer | | |
| 281 | -| `Samples: 16` (PathTracer) | itération (bruit visible) | | |
| 282 | -| `Samples: 64` (PathTracer) | rendu final path traced | | |
| 283 | -| `Width: 1920, Height: 1080` | image grande taille | | |
| 284 | -| `Fov: 45` | "photo" compressée, élégante | | |
| 285 | -| `Fov: 75` | vue immersive, scène large | | |
| 286 | - | |
| 287 | -### Ce qui fait une image "finie" | |
| 288 | - | |
| 289 | -1. Le sujet occupe environ **un tiers** du cadre. | |
| 290 | -2. Il y a **du contraste** : au moins une zone claire et une sombre. | |
| 291 | -3. Les **couleurs s'équilibrent** : pas de zone qui crie plus fort que | |
| 292 | - le sujet. | |
| 293 | -4. Le **sol a une teinte** (jamais du blanc pur, jamais du noir pur). | |
| 294 | -5. On a regardé le rendu **à taille réduite** : si l'image fonctionne | |
| 295 | - en 200 px de large, elle fonctionne en grand. | |
| 296 | - | |
| 297 | -## Où aller ensuite | |
| 298 | - | |
| 299 | -- `examples/` — cinq scènes complètes à décompiler. | |
| 300 | -- `README.md` — référence de l'API (anglais). | |
| 301 | -- `ROADMAP.md` — ce que la bibliothèque ne sait pas encore faire | |
| 302 | - (textures, maillages `.obj`, area lights…). | |
| deleted file mode 100644 | |||
| @@ -1,302 +0,0 @@ | |||
| 1 | -# Tutoriel — créer de belles images | ||
| 2 | - | ||
| 3 | -Ce guide vous apprend à composer des scènes avec la bibliothèque | ||
| 4 | -`povray`, du plus simple au plus avancé. Chaque niveau s'appuie sur le | ||
| 5 | -précédent ; chaque code est complet et rendable tel quel | ||
| 6 | -(`go run` dans le dossier du projet). | ||
| 7 | - | ||
| 8 | -Les exemples finis sont dans `examples/` — quand un niveau vous semble | ||
| 9 | -flou, lisez le fichier correspondant. | ||
| 10 | - | ||
| 11 | -## Niveau 0 — Votre première image | ||
| 12 | - | ||
| 13 | -Une scène, une sphère, une lumière, une caméra. C'est tout. | ||
| 14 | - | ||
| 15 | -```go | ||
| 16 | -package main | ||
| 17 | - | ||
| 18 | -import ( | ||
| 19 | - "log" | ||
| 20 | - | ||
| 21 | - "github.com/user/povray" | ||
| 22 | -) | ||
| 23 | - | ||
| 24 | -func main() { | ||
| 25 | - scene := povray.NewScene() | ||
| 26 | - scene.AddObject( | ||
| 27 | - povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, | ||
| 28 | - povray.Material{Diffuse: povray.Red, Specular: povray.White, Shininess: 30}, | ||
| 29 | - ) | ||
| 30 | - scene.AddObject(povray.NewPlaneXY(0), | ||
| 31 | - povray.Material{Diffuse: povray.Gray, Ambient: 0.3}) | ||
| 32 | - scene.Lights = []povray.Light{povray.NewLight(povray.Vec3{4, 8, 6}, povray.White)} | ||
| 33 | - | ||
| 34 | - cam := povray.NewCamera(povray.Vec3{0, 2, 8}) | ||
| 35 | - r := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | ||
| 36 | - if err := r.Render(cam).SavePNG("hello.png"); err != nil { | ||
| 37 | - log.Fatal(err) | ||
| 38 | - } | ||
| 39 | -} | ||
| 40 | -``` | ||
| 41 | - | ||
| 42 | -Ce qu'il faut retenir : | ||
| 43 | - | ||
| 44 | -- **`NewScene()`** crée une scène (fond blanc, gamma 2.2). | ||
| 45 | -- **`AddObject(obj, mat)`** ajoute un objet *et* son matériau. | ||
| 46 | -- **`Lights`** est une liste de lumières ponctuelles. | ||
| 47 | -- **`Renderer`** : `Width`/`Height` en pixels, `Samples: 2` = 4 rayons | ||
| 48 | - par pixel (anti-aliasing). Sans `Samples`, les bords sont en dents de | ||
| 49 | - scie. | ||
| 50 | -- **`SavePNG`** écrit le fichier. | ||
| 51 | - | ||
| 52 | -## Niveau 1 — Matériaux et couleurs | ||
| 53 | - | ||
| 54 | -Le matériau est ce qui fait l'image. Les champs utiles : | ||
| 55 | - | ||
| 56 | -| Champ | Effet | Valeurs typiques | | ||
| 57 | -|---|---|---| | ||
| 58 | -| `Diffuse` | couleur de base | `povray.Red`, `povray.Color{0.8, 0.2, 0.1}` | | ||
| 59 | -| `Specular` | couleur du reflet | `povray.White` | | ||
| 60 | -| `Shininess` | taille du reflet (plus = plus petit) | 10 (mat) à 100 (chrome) | | ||
| 61 | -| `Ambient` | lumière résiduelle dans l'ombre | 0.1 à 0.4 | | ||
| 62 | -| `Reflectivity` | force du miroir | 0 à 1 | | ||
| 63 | -| `Transparency` | force du verre | 0 à 1 | | ||
| 64 | -| `IOR` | indice de réfraction du verre | 1.5 (verre), 1.33 (eau) | | ||
| 65 | - | ||
| 66 | -**Règle d'or des couleurs** : les valeurs de `Diffuse` sont *linéaires*. | ||
| 67 | -Un gris `0.5` rend plus sombre qu'il n'en a l'air (le gamma 2.2 | ||
| 68 | -l'éclaircit au rendu, mais pas autant que le direct). Pour des couleurs | ||
| 69 | -"vives", montez les valeurs : `Color{0.9, 0.1, 0.1}` est plus rouge que | ||
| 70 | -`Color{0.5, 0.05, 0.05}`. | ||
| 71 | - | ||
| 72 | -Deux matériaux opposés sur des objets voisins créent du contraste — | ||
| 73 | -c'est la base de toute belle image : | ||
| 74 | - | ||
| 75 | -```go | ||
| 76 | -// Un sol sombre porte des objets clairs (et inversement). | ||
| 77 | -scene.AddObject(povray.NewPlaneXY(0), | ||
| 78 | - povray.Material{Diffuse: povray.Color{0.12, 0.12, 0.15}, Ambient: 0.3}) | ||
| 79 | - | ||
| 80 | -scene.AddObject(povray.Sphere{Center: povray.Vec3{-2, 1, 0}, Radius: 1}, | ||
| 81 | - povray.Material{Diffuse: povray.Color{0.9, 0.3, 0.2}, Specular: povray.White, Shininess: 40}) | ||
| 82 | -scene.AddObject(povray.Sphere{Center: povray.Vec3{2, 1, 0}, Radius: 1}, | ||
| 83 | - povray.Material{Diffuse: povray.Color{0.2, 0.7, 0.9}, Specular: povray.White, Shininess: 40}) | ||
| 84 | -``` | ||
| 85 | - | ||
| 86 | -Voir `examples/spheres/` pour une scène complète. | ||
| 87 | - | ||
| 88 | -## Niveau 2 — La caméra et la composition | ||
| 89 | - | ||
| 90 | -La caméra est un trou d'épingle : elle est à `Position`, regarde | ||
| 91 | -`LookAt`, avec `Fov` (champ vertical en degrés). | ||
| 92 | - | ||
| 93 | -```go | ||
| 94 | -cam := povray.NewCamera(povray.Vec3{0, 3, 10}) // reculer et monter | ||
| 95 | -cam.LookAt = povray.Vec3{0, 1, -2} // regarder plus loin que l'objet | ||
| 96 | -cam.Fov = 50 // < 60 = téléobjectif, > 70 = grand angle | ||
| 97 | -``` | ||
| 98 | - | ||
| 99 | -**Principes de composition** : | ||
| 100 | - | ||
| 101 | -1. **Éloignez la caméra** plutôt que de zoomer : un Fov large | ||
| 102 | - (grand angle) déforme les objets proches ; un Fov étroit les | ||
| 103 | - comprime et aplatit la scène. | ||
| 104 | -2. **Regardez légèrement au-dessus ou en dessous** du sujet, pas | ||
| 105 | - exactement au milieu : `LookAt` un peu plus haut que le centre des | ||
| 106 | - objets donne une vue "de dessus" naturelle. | ||
| 107 | -3. **Laissez de la place** : le sujet au centre exact est rarement | ||
| 108 | - joli. Décalez-le d'un tiers (règle des tiers) en déplaçant | ||
| 109 | - `LookAt` dans la direction opposée. | ||
| 110 | -4. **Le plan du sol doit sortir du cadre** : si l'horizon est visible, | ||
| 111 | - la scène a l'air petite. Montez la caméra ou rapprochez le sol. | ||
| 112 | - | ||
| 113 | -## Niveau 3 — Transformations : placer les objets | ||
| 114 | - | ||
| 115 | -Les objets bruts vivent dans l'espace monde. Pour les incliner, | ||
| 116 | -étirer ou les disposer en boucle, on les enveloppe dans un | ||
| 117 | -`Transform` : | ||
| 118 | - | ||
| 119 | -```go | ||
| 120 | -// Une boîte inclinée de 30° autour de X, déplacée à (-3, 0, -2). | ||
| 121 | -tilted := povray.Transform{ | ||
| 122 | - Object: povray.Box{Min: povray.Vec3{-1, 0, -1}, Max: povray.Vec3{1, 2, 1}}, | ||
| 123 | - Matrix: povray.Translate(-3, 0, -2).Multiply(povray.RotateX(math.Pi / 6)), | ||
| 124 | -} | ||
| 125 | -scene.AddObject(tilted, mat) | ||
| 126 | -``` | ||
| 127 | - | ||
| 128 | -**L'ordre compte** : `Multiply` s'applique de droite à gauche. | ||
| 129 | -`Translate(...).Multiply(RotateX(...))` = *d'abord* la rotation, | ||
| 130 | -*puis* la translation. Pour "se déplacer puis tourner sur place", | ||
| 131 | -inversez : `RotateX(...).Multiply(Translate(...))`. | ||
| 132 | - | ||
| 133 | -Les primitives de base : | ||
| 134 | - | ||
| 135 | -- `Translate(x, y, z)` — déplacer | ||
| 136 | -- `RotateX/Y/Z(angle)` — tourner (radians : `math.Pi/2` = 90°) | ||
| 137 | -- `Scale(x, y, z)` — étirer (une sphère échelonnée = ellipsoïde) | ||
| 138 | -- `Identity()` — rien | ||
| 139 | - | ||
| 140 | -**Disposer en boucle** est le moyen le plus simple de créer des | ||
| 141 | -scènes riches : | ||
| 142 | - | ||
| 143 | -```go | ||
| 144 | -var ring []povray.Object | ||
| 145 | -for i := 0; i < 12; i++ { | ||
| 146 | - a := 2 * math.Pi * float64(i) / 12 | ||
| 147 | - m := povray.RotateY(a).Multiply(povray.Translate(0, 0, -5.5)) | ||
| 148 | - ring = append(ring, povray.Transform{ | ||
| 149 | - Object: povray.Box{Min: povray.Vec3{-0.4, 0, -0.4}, Max: povray.Vec3{0.4, 1.6, 0.4}}, | ||
| 150 | - Matrix: m, | ||
| 151 | - }) | ||
| 152 | -} | ||
| 153 | -scene.AddObjects(ring, povray.Material{Diffuse: povray.Gold, ...}) | ||
| 154 | -``` | ||
| 155 | - | ||
| 156 | -Voir `examples/transform/` (anneau de boîtes, ellipsoïde miroir) et | ||
| 157 | -`examples/procedural/` (grille de 200 sphères dont la couleur varie | ||
| 158 | -par position). | ||
| 159 | - | ||
| 160 | -## Niveau 4 — Miroirs et verre | ||
| 161 | - | ||
| 162 | -Deux champs de matériau suffisent : | ||
| 163 | - | ||
| 164 | -```go | ||
| 165 | -// Miroir : Reflectivity élevé, Diffuse sombre. | ||
| 166 | -mirror := povray.Material{ | ||
| 167 | - Diffuse: povray.Color{0.1, 0.1, 0.12}, | ||
| 168 | - Reflectivity: 0.9, | ||
| 169 | - Specular: povray.White, Shininess: 80, | ||
| 170 | -} | ||
| 171 | - | ||
| 172 | -// Verre : Transparency élevé + IOR. | ||
| 173 | -glass := povray.Material{ | ||
| 174 | - Diffuse: povray.White, | ||
| 175 | - Transparency: 0.95, | ||
| 176 | - IOR: 1.5, | ||
| 177 | - Specular: povray.White, Shininess: 100, | ||
| 178 | - Ambient: 0.05, | ||
| 179 | -} | ||
| 180 | -``` | ||
| 181 | - | ||
| 182 | -**Astuces** : | ||
| 183 | - | ||
| 184 | -- Un miroir pur (`Reflectivity: 1`, `Diffuse: Black`) est joli mais | ||
| 185 | - plat. Un `Reflectivity: 0.5` avec un `Diffuse` coloré donne un | ||
| 186 | - "métal teinté" très photogénique. | ||
| 187 | -- Le verre est plus beau quand il *contient* ou *cache* quelque chose | ||
| 188 | - de coloré. Une sphère de verre seule est presque invisible. | ||
| 189 | -- L'effet "sol miroir" : un plan avec `Reflectivity: 0.3` et | ||
| 190 | - `Diffuse` sombre fait des reflets doux sous les objets. | ||
| 191 | - | ||
| 192 | -Voir `examples/mirror/`. | ||
| 193 | - | ||
| 194 | -## Niveau 5 — Path tracing : lumière réaliste | ||
| 195 | - | ||
| 196 | -Le `Renderer` classique éclaire les surfaces une à une. Le | ||
| 197 | -**`PathTracer`** fait rebondir la lumière de surface en surface : | ||
| 198 | -éclairage indirect (une sphère rouge teintée le sol autour d'elle), | ||
| 199 | -ombres douces, lumière de couleur. C'est ce qui donne des images | ||
| 200 | -"réelles". | ||
| 201 | - | ||
| 202 | -```go | ||
| 203 | -// Les lumières du path tracer sont des SURFACES ÉMISSIVES. | ||
| 204 | -panel := povray.Transform{ | ||
| 205 | - Object: povray.Box{Min: povray.Vec3{-1.5, 0, -1.5}, Max: povray.Vec3{1.5, 0.15, 1.5}}, | ||
| 206 | - Matrix: povray.Translate(0, 9, -2), | ||
| 207 | -} | ||
| 208 | -scene.AddObject(panel, povray.LightMaterial(povray.Color{1, 0.85, 0.65}, 14)) | ||
| 209 | - | ||
| 210 | -tracer := povray.PathTracer{Scene: scene, Width: 800, Height: 600, Samples: 64} | ||
| 211 | -img := tracer.Render(cam) | ||
| 212 | -``` | ||
| 213 | - | ||
| 214 | -Différences avec le `Renderer` : | ||
| 215 | - | ||
| 216 | -- **Les `Scene.Lights` sont ignorées** — la lumière vient des matériaux | ||
| 217 | - avec `Emission` (utilisez `LightMaterial(couleur, intensité)`). | ||
| 218 | -- **`Samples` = nombre de rayons par pixel**, pas une grille. 16 = | ||
| 219 | - rapide (un peu de bruit), 64 = propre, 256 = publication. | ||
| 220 | -- **Le bruit est normal** : c'est un estimateur aléatoire. Plus de | ||
| 221 | - samples = moins de bruit. Le rendu est déterministe (même image à | ||
| 222 | - chaque fois pour la même config). | ||
| 223 | -- **Les intensités** : un panneau de lumière a besoin d'une intensité | ||
| 224 | - de 10 à 20 pour bien éclairer une scène à cette échelle. | ||
| 225 | - | ||
| 226 | -Voir `examples/pathtrace/` : une pièce avec panneau chaud + bandeau | ||
| 227 | -froid (la lumière rebondit et teinte les murs), sphère rouge, miroir | ||
| 228 | -bleu, verre. | ||
| 229 | - | ||
| 230 | -## Niveau 6 — Recettes pour de belles images | ||
| 231 | - | ||
| 232 | -### La palette | ||
| 233 | - | ||
| 234 | -Choisissez 2 à 3 couleurs de base et un neutre. Les complémentaires | ||
| 235 | -se font face : | ||
| 236 | - | ||
| 237 | -```go | ||
| 238 | -var ( | ||
| 239 | - warm = povray.Color{0.9, 0.45, 0.2} // orange | ||
| 240 | - cool = povray.Color{0.2, 0.4, 0.85} // bleu | ||
| 241 | - neutral = povray.Color{0.25, 0.25, 0.28} // gris foncé pour le sol | ||
| 242 | -) | ||
| 243 | -``` | ||
| 244 | - | ||
| 245 | -Un sol neutre et sombre est le fond le plus polyvalent : il ne | ||
| 246 | -concurrence pas les objets. | ||
| 247 | - | ||
| 248 | -### La lumière | ||
| 249 | - | ||
| 250 | -- **Deux lumières** valent mieux qu'une : une clé (forte, chaude, | ||
| 251 | - en hauteur) + une rembourrage (faible, froide, en face) : | ||
| 252 | - | ||
| 253 | -```go | ||
| 254 | -scene.Lights = []povray.Light{ | ||
| 255 | - povray.NewLight(povray.Vec3{6, 10, 8}, povray.Color{1, 0.95, 0.85}), | ||
| 256 | - povray.NewLight(povray.Vec3{-8, 4, -4}, povray.Color{0.5, 0.6, 1.0}), | ||
| 257 | -} | ||
| 258 | -``` | ||
| 259 | - | ||
| 260 | -- Placez la lumière **en hauteur et de côté** : en face, les ombres | ||
| 261 | - disparaissent derrière les objets ; en haut, tout est plat. | ||
| 262 | -- Dans le path tracer, la **couleur de la lumière** est plus | ||
| 263 | - importante que son intensité : un panneau `Color{1, 0.85, 0.65}` | ||
| 264 | - (chaud) + un bandeau `Color{0.4, 0.6, 1}` (froid) crée une ambiance | ||
| 265 | - immédiatement. | ||
| 266 | - | ||
| 267 | -### La profondeur | ||
| 268 | - | ||
| 269 | -- **Tailles variées** : un grand + un petit objet donnent l'échelle. | ||
| 270 | -- **Écartez en Z** : les objets à des distances différentes créent de | ||
| 271 | - la profondeur ; à la même Z, la scène est plate. | ||
| 272 | -- **Un élément au premier plan** (partiellement hors cadre) donne un | ||
| 273 | - effet "regard à travers". | ||
| 274 | - | ||
| 275 | -### Les réglages finaux | ||
| 276 | - | ||
| 277 | -| Réglage | Pour… | | ||
| 278 | -|---|---| | ||
| 279 | -| `Samples: 2` (Renderer) | aperçu rapide, propre | | ||
| 280 | -| `Samples: 4` (Renderer) | rendu final du Renderer | | ||
| 281 | -| `Samples: 16` (PathTracer) | itération (bruit visible) | | ||
| 282 | -| `Samples: 64` (PathTracer) | rendu final path traced | | ||
| 283 | -| `Width: 1920, Height: 1080` | image grande taille | | ||
| 284 | -| `Fov: 45` | "photo" compressée, élégante | | ||
| 285 | -| `Fov: 75` | vue immersive, scène large | | ||
| 286 | - | ||
| 287 | -### Ce qui fait une image "finie" | ||
| 288 | - | ||
| 289 | -1. Le sujet occupe environ **un tiers** du cadre. | ||
| 290 | -2. Il y a **du contraste** : au moins une zone claire et une sombre. | ||
| 291 | -3. Les **couleurs s'équilibrent** : pas de zone qui crie plus fort que | ||
| 292 | - le sujet. | ||
| 293 | -4. Le **sol a une teinte** (jamais du blanc pur, jamais du noir pur). | ||
| 294 | -5. On a regardé le rendu **à taille réduite** : si l'image fonctionne | ||
| 295 | - en 200 px de large, elle fonctionne en grand. | ||
| 296 | - | ||
| 297 | -## Où aller ensuite | ||
| 298 | - | ||
| 299 | -- `examples/` — cinq scènes complètes à décompiler. | ||
| 300 | -- `README.md` — référence de l'API (anglais). | ||
| 301 | -- `ROADMAP.md` — ce que la bibliothèque ne sait pas encore faire | ||
| 302 | - (textures, maillages `.obj`, area lights…). | ||
deleted
demos/qwen-style/workspace/examples/mirror/main.go +0 -86 | deleted file mode 100644 | ||
| @@ -1,86 +0,0 @@ | ||
| 1 | -// Command mirror shows reflections and refraction: a glass sphere on | |
| 2 | -// a mirror floor, with colored spheres behind it. | |
| 3 | -package main | |
| 4 | - | |
| 5 | -import ( | |
| 6 | - "fmt" | |
| 7 | - "log" | |
| 8 | - | |
| 9 | - "github.com/user/povray" | |
| 10 | -) | |
| 11 | - | |
| 12 | -func main() { | |
| 13 | - scene := povray.NewScene() | |
| 14 | - scene.Background = povray.Color{0.05, 0.05, 0.1} | |
| 15 | - scene.MaxDepth = 10 | |
| 16 | - | |
| 17 | - // Mirror floor. | |
| 18 | - floorMat := povray.Material{ | |
| 19 | - Diffuse: povray.Color{0.2, 0.2, 0.25}, | |
| 20 | - Specular: povray.White, | |
| 21 | - Shininess: 100, | |
| 22 | - Ambient: 0.1, | |
| 23 | - Reflectivity: 0.7, | |
| 24 | - } | |
| 25 | - scene.AddObject(povray.NewPlaneXY(0), floorMat) | |
| 26 | - | |
| 27 | - // Glass sphere in front. | |
| 28 | - glass := povray.Material{ | |
| 29 | - Diffuse: povray.Color{0.9, 0.95, 1}, | |
| 30 | - Specular: povray.White, | |
| 31 | - Shininess: 120, | |
| 32 | - Ambient: 0.05, | |
| 33 | - Reflectivity: 0.2, | |
| 34 | - Transparency: 0.9, | |
| 35 | - IOR: 1.5, | |
| 36 | - } | |
| 37 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, glass) | |
| 38 | - | |
| 39 | - // Colored spheres behind the glass. | |
| 40 | - behind := []struct { | |
| 41 | - x float64 | |
| 42 | - c povray.Color | |
| 43 | - }{ | |
| 44 | - {-3, povray.Red}, | |
| 45 | - {0, povray.Yellow}, | |
| 46 | - {3, povray.Cyan}, | |
| 47 | - } | |
| 48 | - for _, b := range behind { | |
| 49 | - mat := povray.Material{ | |
| 50 | - Diffuse: b.c, | |
| 51 | - Specular: povray.White, | |
| 52 | - Shininess: 40, | |
| 53 | - Ambient: 0.15, | |
| 54 | - } | |
| 55 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{b.x, 1.2, -5}, Radius: 1.2}, mat) | |
| 56 | - } | |
| 57 | - | |
| 58 | - // A gold, mirror-like box. | |
| 59 | - gold := povray.Material{ | |
| 60 | - Diffuse: povray.Gold, | |
| 61 | - Specular: povray.White, | |
| 62 | - Shininess: 80, | |
| 63 | - Ambient: 0.15, | |
| 64 | - Reflectivity: 0.5, | |
| 65 | - } | |
| 66 | - scene.AddObject(povray.Box{ | |
| 67 | - Min: povray.Vec3{2.2, 0, -2.5}, | |
| 68 | - Max: povray.Vec3{3.4, 1.2, -1.3}, | |
| 69 | - }, gold) | |
| 70 | - | |
| 71 | - scene.Lights = []povray.Light{ | |
| 72 | - povray.NewLight(povray.Vec3{0, 10, 4}, povray.White), | |
| 73 | - povray.NewLight(povray.Vec3{-5, 3, 8}, povray.Color{0.7, 0.8, 1}), | |
| 74 | - povray.NewLight(povray.Vec3{5, 3, 8}, povray.Color{1, 0.85, 0.7}), | |
| 75 | - } | |
| 76 | - | |
| 77 | - cam := povray.NewCamera(povray.Vec3{0, 2, 8}) | |
| 78 | - cam.Fov = 55 | |
| 79 | - | |
| 80 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | |
| 81 | - img := renderer.Render(cam) | |
| 82 | - if err := img.SavePNG("mirror.png"); err != nil { | |
| 83 | - log.Fatal(err) | |
| 84 | - } | |
| 85 | - fmt.Println("wrote mirror.png") | |
| 86 | -} | |
| deleted file mode 100644 | |||
| @@ -1,86 +0,0 @@ | |||
| 1 | -// Command mirror shows reflections and refraction: a glass sphere on | ||
| 2 | -// a mirror floor, with colored spheres behind it. | ||
| 3 | -package main | ||
| 4 | - | ||
| 5 | -import ( | ||
| 6 | - "fmt" | ||
| 7 | - "log" | ||
| 8 | - | ||
| 9 | - "github.com/user/povray" | ||
| 10 | -) | ||
| 11 | - | ||
| 12 | -func main() { | ||
| 13 | - scene := povray.NewScene() | ||
| 14 | - scene.Background = povray.Color{0.05, 0.05, 0.1} | ||
| 15 | - scene.MaxDepth = 10 | ||
| 16 | - | ||
| 17 | - // Mirror floor. | ||
| 18 | - floorMat := povray.Material{ | ||
| 19 | - Diffuse: povray.Color{0.2, 0.2, 0.25}, | ||
| 20 | - Specular: povray.White, | ||
| 21 | - Shininess: 100, | ||
| 22 | - Ambient: 0.1, | ||
| 23 | - Reflectivity: 0.7, | ||
| 24 | - } | ||
| 25 | - scene.AddObject(povray.NewPlaneXY(0), floorMat) | ||
| 26 | - | ||
| 27 | - // Glass sphere in front. | ||
| 28 | - glass := povray.Material{ | ||
| 29 | - Diffuse: povray.Color{0.9, 0.95, 1}, | ||
| 30 | - Specular: povray.White, | ||
| 31 | - Shininess: 120, | ||
| 32 | - Ambient: 0.05, | ||
| 33 | - Reflectivity: 0.2, | ||
| 34 | - Transparency: 0.9, | ||
| 35 | - IOR: 1.5, | ||
| 36 | - } | ||
| 37 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, glass) | ||
| 38 | - | ||
| 39 | - // Colored spheres behind the glass. | ||
| 40 | - behind := []struct { | ||
| 41 | - x float64 | ||
| 42 | - c povray.Color | ||
| 43 | - }{ | ||
| 44 | - {-3, povray.Red}, | ||
| 45 | - {0, povray.Yellow}, | ||
| 46 | - {3, povray.Cyan}, | ||
| 47 | - } | ||
| 48 | - for _, b := range behind { | ||
| 49 | - mat := povray.Material{ | ||
| 50 | - Diffuse: b.c, | ||
| 51 | - Specular: povray.White, | ||
| 52 | - Shininess: 40, | ||
| 53 | - Ambient: 0.15, | ||
| 54 | - } | ||
| 55 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{b.x, 1.2, -5}, Radius: 1.2}, mat) | ||
| 56 | - } | ||
| 57 | - | ||
| 58 | - // A gold, mirror-like box. | ||
| 59 | - gold := povray.Material{ | ||
| 60 | - Diffuse: povray.Gold, | ||
| 61 | - Specular: povray.White, | ||
| 62 | - Shininess: 80, | ||
| 63 | - Ambient: 0.15, | ||
| 64 | - Reflectivity: 0.5, | ||
| 65 | - } | ||
| 66 | - scene.AddObject(povray.Box{ | ||
| 67 | - Min: povray.Vec3{2.2, 0, -2.5}, | ||
| 68 | - Max: povray.Vec3{3.4, 1.2, -1.3}, | ||
| 69 | - }, gold) | ||
| 70 | - | ||
| 71 | - scene.Lights = []povray.Light{ | ||
| 72 | - povray.NewLight(povray.Vec3{0, 10, 4}, povray.White), | ||
| 73 | - povray.NewLight(povray.Vec3{-5, 3, 8}, povray.Color{0.7, 0.8, 1}), | ||
| 74 | - povray.NewLight(povray.Vec3{5, 3, 8}, povray.Color{1, 0.85, 0.7}), | ||
| 75 | - } | ||
| 76 | - | ||
| 77 | - cam := povray.NewCamera(povray.Vec3{0, 2, 8}) | ||
| 78 | - cam.Fov = 55 | ||
| 79 | - | ||
| 80 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | ||
| 81 | - img := renderer.Render(cam) | ||
| 82 | - if err := img.SavePNG("mirror.png"); err != nil { | ||
| 83 | - log.Fatal(err) | ||
| 84 | - } | ||
| 85 | - fmt.Println("wrote mirror.png") | ||
| 86 | -} | ||
deleted
demos/qwen-style/workspace/examples/pathtrace/main.go +0 -67 | deleted file mode 100644 | ||
| @@ -1,67 +0,0 @@ | ||
| 1 | -// Command pathtrace renders a scene with the path tracer: light is | |
| 2 | -// carried by rays bouncing between surfaces, which gives global | |
| 3 | -// illumination, soft shadows and colored bounce light. | |
| 4 | -package main | |
| 5 | - | |
| 6 | -import ( | |
| 7 | - "fmt" | |
| 8 | - "log" | |
| 9 | - "math" | |
| 10 | - | |
| 11 | - "github.com/user/povray" | |
| 12 | -) | |
| 13 | - | |
| 14 | -func main() { | |
| 15 | - scene := povray.NewScene() | |
| 16 | - scene.Background = povray.Color{0.02, 0.03, 0.05} | |
| 17 | - | |
| 18 | - // A large room: floor and two walls, slightly tinted so the bounce | |
| 19 | - // light picks up color. | |
| 20 | - room := povray.Material{Diffuse: povray.Color{0.75, 0.72, 0.68}} | |
| 21 | - scene.AddObject(povray.NewPlaneXY(0), room) | |
| 22 | - scene.AddObject(povray.Box{Min: povray.Vec3{-12, 0, -12}, Max: povray.Vec3{12, 12, -11.9}}, | |
| 23 | - povray.Material{Diffuse: povray.Color{0.55, 0.6, 0.75}}) | |
| 24 | - | |
| 25 | - // A warm light panel hanging above the scene. | |
| 26 | - panel := povray.Transform{ | |
| 27 | - Object: povray.Box{Min: povray.Vec3{-1.5, 0, -1.5}, Max: povray.Vec3{1.5, 0.15, 1.5}}, | |
| 28 | - Matrix: povray.Translate(0, 9, -2), | |
| 29 | - } | |
| 30 | - scene.AddObject(panel, povray.LightMaterial(povray.Color{1, 0.85, 0.65}, 14)) | |
| 31 | - | |
| 32 | - // A cool light strip along the back wall, for colored bounce. | |
| 33 | - strip := povray.Transform{ | |
| 34 | - Object: povray.Box{Min: povray.Vec3{-4, 0, -0.2}, Max: povray.Vec3{4, 2.5, 0}}, | |
| 35 | - Matrix: povray.Translate(0, 0, -11.8), | |
| 36 | - } | |
| 37 | - scene.AddObject(strip, povray.LightMaterial(povray.Color{0.4, 0.6, 1}, 6)) | |
| 38 | - | |
| 39 | - // The subjects: a red sphere, a blue mirror, a glass sphere. | |
| 40 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{-2.5, 1, -2}, Radius: 1}, | |
| 41 | - povray.Material{Diffuse: povray.Color{0.85, 0.15, 0.15}}) | |
| 42 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{2.5, 1, -2}, Radius: 1}, | |
| 43 | - povray.Material{Diffuse: povray.Color{0.1, 0.2, 0.8}, Reflectivity: 0.6}) | |
| 44 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, | |
| 45 | - povray.Material{Diffuse: povray.White, Transparency: 0.95, IOR: 1.5}) | |
| 46 | - | |
| 47 | - // A few boxes, tilted, to catch the light. | |
| 48 | - for i := 0; i < 3; i++ { | |
| 49 | - b := povray.Transform{ | |
| 50 | - Object: povray.Box{Min: povray.Vec3{-0.5, 0, -0.5}, Max: povray.Vec3{0.5, 1.5, 0.5}}, | |
| 51 | - Matrix: povray.Translate(float64(i-1)*-4, 0, 2). | |
| 52 | - Multiply(povray.RotateY(math.Pi / 8 * float64(i))), | |
| 53 | - } | |
| 54 | - scene.AddObject(b, povray.Material{Diffuse: povray.Color{0.8, 0.6, 0.3}}) | |
| 55 | - } | |
| 56 | - | |
| 57 | - cam := povray.NewCamera(povray.Vec3{0, 3, 10}) | |
| 58 | - cam.LookAt = povray.Vec3{0, 1.5, -2} | |
| 59 | - cam.Fov = 50 | |
| 60 | - | |
| 61 | - tracer := povray.PathTracer{Scene: scene, Width: 800, Height: 600, Samples: 64} | |
| 62 | - img := tracer.Render(cam) | |
| 63 | - if err := img.SavePNG("pathtrace.png"); err != nil { | |
| 64 | - log.Fatal(err) | |
| 65 | - } | |
| 66 | - fmt.Println("wrote pathtrace.png") | |
| 67 | -} | |
| deleted file mode 100644 | |||
| @@ -1,67 +0,0 @@ | |||
| 1 | -// Command pathtrace renders a scene with the path tracer: light is | ||
| 2 | -// carried by rays bouncing between surfaces, which gives global | ||
| 3 | -// illumination, soft shadows and colored bounce light. | ||
| 4 | -package main | ||
| 5 | - | ||
| 6 | -import ( | ||
| 7 | - "fmt" | ||
| 8 | - "log" | ||
| 9 | - "math" | ||
| 10 | - | ||
| 11 | - "github.com/user/povray" | ||
| 12 | -) | ||
| 13 | - | ||
| 14 | -func main() { | ||
| 15 | - scene := povray.NewScene() | ||
| 16 | - scene.Background = povray.Color{0.02, 0.03, 0.05} | ||
| 17 | - | ||
| 18 | - // A large room: floor and two walls, slightly tinted so the bounce | ||
| 19 | - // light picks up color. | ||
| 20 | - room := povray.Material{Diffuse: povray.Color{0.75, 0.72, 0.68}} | ||
| 21 | - scene.AddObject(povray.NewPlaneXY(0), room) | ||
| 22 | - scene.AddObject(povray.Box{Min: povray.Vec3{-12, 0, -12}, Max: povray.Vec3{12, 12, -11.9}}, | ||
| 23 | - povray.Material{Diffuse: povray.Color{0.55, 0.6, 0.75}}) | ||
| 24 | - | ||
| 25 | - // A warm light panel hanging above the scene. | ||
| 26 | - panel := povray.Transform{ | ||
| 27 | - Object: povray.Box{Min: povray.Vec3{-1.5, 0, -1.5}, Max: povray.Vec3{1.5, 0.15, 1.5}}, | ||
| 28 | - Matrix: povray.Translate(0, 9, -2), | ||
| 29 | - } | ||
| 30 | - scene.AddObject(panel, povray.LightMaterial(povray.Color{1, 0.85, 0.65}, 14)) | ||
| 31 | - | ||
| 32 | - // A cool light strip along the back wall, for colored bounce. | ||
| 33 | - strip := povray.Transform{ | ||
| 34 | - Object: povray.Box{Min: povray.Vec3{-4, 0, -0.2}, Max: povray.Vec3{4, 2.5, 0}}, | ||
| 35 | - Matrix: povray.Translate(0, 0, -11.8), | ||
| 36 | - } | ||
| 37 | - scene.AddObject(strip, povray.LightMaterial(povray.Color{0.4, 0.6, 1}, 6)) | ||
| 38 | - | ||
| 39 | - // The subjects: a red sphere, a blue mirror, a glass sphere. | ||
| 40 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{-2.5, 1, -2}, Radius: 1}, | ||
| 41 | - povray.Material{Diffuse: povray.Color{0.85, 0.15, 0.15}}) | ||
| 42 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{2.5, 1, -2}, Radius: 1}, | ||
| 43 | - povray.Material{Diffuse: povray.Color{0.1, 0.2, 0.8}, Reflectivity: 0.6}) | ||
| 44 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 0}, Radius: 1}, | ||
| 45 | - povray.Material{Diffuse: povray.White, Transparency: 0.95, IOR: 1.5}) | ||
| 46 | - | ||
| 47 | - // A few boxes, tilted, to catch the light. | ||
| 48 | - for i := 0; i < 3; i++ { | ||
| 49 | - b := povray.Transform{ | ||
| 50 | - Object: povray.Box{Min: povray.Vec3{-0.5, 0, -0.5}, Max: povray.Vec3{0.5, 1.5, 0.5}}, | ||
| 51 | - Matrix: povray.Translate(float64(i-1)*-4, 0, 2). | ||
| 52 | - Multiply(povray.RotateY(math.Pi / 8 * float64(i))), | ||
| 53 | - } | ||
| 54 | - scene.AddObject(b, povray.Material{Diffuse: povray.Color{0.8, 0.6, 0.3}}) | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - cam := povray.NewCamera(povray.Vec3{0, 3, 10}) | ||
| 58 | - cam.LookAt = povray.Vec3{0, 1.5, -2} | ||
| 59 | - cam.Fov = 50 | ||
| 60 | - | ||
| 61 | - tracer := povray.PathTracer{Scene: scene, Width: 800, Height: 600, Samples: 64} | ||
| 62 | - img := tracer.Render(cam) | ||
| 63 | - if err := img.SavePNG("pathtrace.png"); err != nil { | ||
| 64 | - log.Fatal(err) | ||
| 65 | - } | ||
| 66 | - fmt.Println("wrote pathtrace.png") | ||
| 67 | -} | ||
deleted
demos/qwen-style/workspace/examples/procedural/main.go +0 -115 | deleted file mode 100644 | ||
| @@ -1,115 +0,0 @@ | ||
| 1 | -// Command procedural shows that a scene is just data: a grid of | |
| 2 | -// spheres whose colors and heights are computed in a loop, plus a | |
| 3 | -// ring of boxes, all generated from a few parameters. | |
| 4 | -package main | |
| 5 | - | |
| 6 | -import ( | |
| 7 | - "fmt" | |
| 8 | - "log" | |
| 9 | - "math" | |
| 10 | - | |
| 11 | - "github.com/user/povray" | |
| 12 | -) | |
| 13 | - | |
| 14 | -func main() { | |
| 15 | - scene := povray.NewScene() | |
| 16 | - scene.Background = povray.Color{0.02, 0.03, 0.08} | |
| 17 | - | |
| 18 | - // Ground. | |
| 19 | - groundMat := povray.Material{ | |
| 20 | - Diffuse: povray.Color{0.3, 0.32, 0.38}, | |
| 21 | - Specular: povray.Color{0.2, 0.2, 0.2}, | |
| 22 | - Shininess: 15, | |
| 23 | - Ambient: 0.25, | |
| 24 | - } | |
| 25 | - scene.AddObject(povray.NewPlaneXY(0), groundMat) | |
| 26 | - | |
| 27 | - // A 6x6 grid of spheres; color and height vary with position. | |
| 28 | - const n = 6 | |
| 29 | - step := 1.6 | |
| 30 | - for i := 0; i < n; i++ { | |
| 31 | - for j := 0; j < n; j++ { | |
| 32 | - x := (float64(i) - float64(n-1)/2) * step | |
| 33 | - z := (float64(j) - float64(n-1)/2) * step | |
| 34 | - dist := math.Hypot(x, z) | |
| 35 | - r := 0.55 - 0.1*math.Min(1, dist/6) | |
| 36 | - h := r + 0.25*math.Sin(dist*0.8) | |
| 37 | - | |
| 38 | - // Hue sweep across the grid. | |
| 39 | - t := float64(i+j) / float64(2*n) | |
| 40 | - c := hsv(0.6-0.5*t, 0.7, 0.9) | |
| 41 | - | |
| 42 | - mat := povray.Material{ | |
| 43 | - Diffuse: c, | |
| 44 | - Specular: povray.White, | |
| 45 | - Shininess: 50, | |
| 46 | - Ambient: 0.1, | |
| 47 | - } | |
| 48 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{x, h, z}, Radius: r}, mat) | |
| 49 | - } | |
| 50 | - } | |
| 51 | - | |
| 52 | - // A ring of boxes around the grid. | |
| 53 | - const m = 12 | |
| 54 | - for k := 0; k < m; k++ { | |
| 55 | - a := 2 * math.Pi * float64(k) / m | |
| 56 | - x, z := 7.5*math.Cos(a), 7.5*math.Sin(a) | |
| 57 | - h := 1 + 0.8*math.Sin(3*a) | |
| 58 | - c := hsv(0.55+0.08*math.Sin(2*a), 0.5, 0.8) | |
| 59 | - mat := povray.Material{ | |
| 60 | - Diffuse: c, | |
| 61 | - Specular: povray.White, | |
| 62 | - Shininess: 25, | |
| 63 | - Ambient: 0.15, | |
| 64 | - } | |
| 65 | - scene.AddObject(povray.Box{ | |
| 66 | - Min: povray.Vec3{x - 0.4, 0, z - 0.4}, | |
| 67 | - Max: povray.Vec3{x + 0.4, h, z + 0.4}, | |
| 68 | - }, mat) | |
| 69 | - } | |
| 70 | - | |
| 71 | - // Three colored lights for a dramatic look. | |
| 72 | - scene.Lights = []povray.Light{ | |
| 73 | - povray.NewLight(povray.Vec3{0, 12, 0}, povray.White), | |
| 74 | - povray.NewLight(povray.Vec3{10, 5, 10}, povray.Color{1, 0.4, 0.3}), | |
| 75 | - povray.NewLight(povray.Vec3{-10, 5, -10}, povray.Color{0.3, 0.4, 1}), | |
| 76 | - } | |
| 77 | - | |
| 78 | - cam := povray.NewCamera(povray.Vec3{0, 9, 16}) | |
| 79 | - cam.LookAt = povray.Vec3{0, 0.5, 0} | |
| 80 | - cam.Fov = 50 | |
| 81 | - | |
| 82 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | |
| 83 | - img := renderer.Render(cam) | |
| 84 | - if err := img.SavePNG("procedural.png"); err != nil { | |
| 85 | - log.Fatal(err) | |
| 86 | - } | |
| 87 | - fmt.Println("wrote procedural.png") | |
| 88 | -} | |
| 89 | - | |
| 90 | -// hsv converts hue [0,1), saturation and value [0,1] to RGB. | |
| 91 | -func hsv(h, s, v float64) povray.Color { | |
| 92 | - h = math.Mod(h, 1) | |
| 93 | - if h < 0 { | |
| 94 | - h += 1 | |
| 95 | - } | |
| 96 | - i := int(h * 6) | |
| 97 | - f := h*6 - float64(i) | |
| 98 | - p := v * (1 - s) | |
| 99 | - q := v * (1 - s*f) | |
| 100 | - t := v * (1 - s*(1-f)) | |
| 101 | - switch i % 6 { | |
| 102 | - case 0: | |
| 103 | - return povray.Color{v, t, p} | |
| 104 | - case 1: | |
| 105 | - return povray.Color{q, v, p} | |
| 106 | - case 2: | |
| 107 | - return povray.Color{p, v, t} | |
| 108 | - case 3: | |
| 109 | - return povray.Color{p, q, v} | |
| 110 | - case 4: | |
| 111 | - return povray.Color{t, p, v} | |
| 112 | - default: | |
| 113 | - return povray.Color{v, p, q} | |
| 114 | - } | |
| 115 | -} | |
| deleted file mode 100644 | |||
| @@ -1,115 +0,0 @@ | |||
| 1 | -// Command procedural shows that a scene is just data: a grid of | ||
| 2 | -// spheres whose colors and heights are computed in a loop, plus a | ||
| 3 | -// ring of boxes, all generated from a few parameters. | ||
| 4 | -package main | ||
| 5 | - | ||
| 6 | -import ( | ||
| 7 | - "fmt" | ||
| 8 | - "log" | ||
| 9 | - "math" | ||
| 10 | - | ||
| 11 | - "github.com/user/povray" | ||
| 12 | -) | ||
| 13 | - | ||
| 14 | -func main() { | ||
| 15 | - scene := povray.NewScene() | ||
| 16 | - scene.Background = povray.Color{0.02, 0.03, 0.08} | ||
| 17 | - | ||
| 18 | - // Ground. | ||
| 19 | - groundMat := povray.Material{ | ||
| 20 | - Diffuse: povray.Color{0.3, 0.32, 0.38}, | ||
| 21 | - Specular: povray.Color{0.2, 0.2, 0.2}, | ||
| 22 | - Shininess: 15, | ||
| 23 | - Ambient: 0.25, | ||
| 24 | - } | ||
| 25 | - scene.AddObject(povray.NewPlaneXY(0), groundMat) | ||
| 26 | - | ||
| 27 | - // A 6x6 grid of spheres; color and height vary with position. | ||
| 28 | - const n = 6 | ||
| 29 | - step := 1.6 | ||
| 30 | - for i := 0; i < n; i++ { | ||
| 31 | - for j := 0; j < n; j++ { | ||
| 32 | - x := (float64(i) - float64(n-1)/2) * step | ||
| 33 | - z := (float64(j) - float64(n-1)/2) * step | ||
| 34 | - dist := math.Hypot(x, z) | ||
| 35 | - r := 0.55 - 0.1*math.Min(1, dist/6) | ||
| 36 | - h := r + 0.25*math.Sin(dist*0.8) | ||
| 37 | - | ||
| 38 | - // Hue sweep across the grid. | ||
| 39 | - t := float64(i+j) / float64(2*n) | ||
| 40 | - c := hsv(0.6-0.5*t, 0.7, 0.9) | ||
| 41 | - | ||
| 42 | - mat := povray.Material{ | ||
| 43 | - Diffuse: c, | ||
| 44 | - Specular: povray.White, | ||
| 45 | - Shininess: 50, | ||
| 46 | - Ambient: 0.1, | ||
| 47 | - } | ||
| 48 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{x, h, z}, Radius: r}, mat) | ||
| 49 | - } | ||
| 50 | - } | ||
| 51 | - | ||
| 52 | - // A ring of boxes around the grid. | ||
| 53 | - const m = 12 | ||
| 54 | - for k := 0; k < m; k++ { | ||
| 55 | - a := 2 * math.Pi * float64(k) / m | ||
| 56 | - x, z := 7.5*math.Cos(a), 7.5*math.Sin(a) | ||
| 57 | - h := 1 + 0.8*math.Sin(3*a) | ||
| 58 | - c := hsv(0.55+0.08*math.Sin(2*a), 0.5, 0.8) | ||
| 59 | - mat := povray.Material{ | ||
| 60 | - Diffuse: c, | ||
| 61 | - Specular: povray.White, | ||
| 62 | - Shininess: 25, | ||
| 63 | - Ambient: 0.15, | ||
| 64 | - } | ||
| 65 | - scene.AddObject(povray.Box{ | ||
| 66 | - Min: povray.Vec3{x - 0.4, 0, z - 0.4}, | ||
| 67 | - Max: povray.Vec3{x + 0.4, h, z + 0.4}, | ||
| 68 | - }, mat) | ||
| 69 | - } | ||
| 70 | - | ||
| 71 | - // Three colored lights for a dramatic look. | ||
| 72 | - scene.Lights = []povray.Light{ | ||
| 73 | - povray.NewLight(povray.Vec3{0, 12, 0}, povray.White), | ||
| 74 | - povray.NewLight(povray.Vec3{10, 5, 10}, povray.Color{1, 0.4, 0.3}), | ||
| 75 | - povray.NewLight(povray.Vec3{-10, 5, -10}, povray.Color{0.3, 0.4, 1}), | ||
| 76 | - } | ||
| 77 | - | ||
| 78 | - cam := povray.NewCamera(povray.Vec3{0, 9, 16}) | ||
| 79 | - cam.LookAt = povray.Vec3{0, 0.5, 0} | ||
| 80 | - cam.Fov = 50 | ||
| 81 | - | ||
| 82 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | ||
| 83 | - img := renderer.Render(cam) | ||
| 84 | - if err := img.SavePNG("procedural.png"); err != nil { | ||
| 85 | - log.Fatal(err) | ||
| 86 | - } | ||
| 87 | - fmt.Println("wrote procedural.png") | ||
| 88 | -} | ||
| 89 | - | ||
| 90 | -// hsv converts hue [0,1), saturation and value [0,1] to RGB. | ||
| 91 | -func hsv(h, s, v float64) povray.Color { | ||
| 92 | - h = math.Mod(h, 1) | ||
| 93 | - if h < 0 { | ||
| 94 | - h += 1 | ||
| 95 | - } | ||
| 96 | - i := int(h * 6) | ||
| 97 | - f := h*6 - float64(i) | ||
| 98 | - p := v * (1 - s) | ||
| 99 | - q := v * (1 - s*f) | ||
| 100 | - t := v * (1 - s*(1-f)) | ||
| 101 | - switch i % 6 { | ||
| 102 | - case 0: | ||
| 103 | - return povray.Color{v, t, p} | ||
| 104 | - case 1: | ||
| 105 | - return povray.Color{q, v, p} | ||
| 106 | - case 2: | ||
| 107 | - return povray.Color{p, v, t} | ||
| 108 | - case 3: | ||
| 109 | - return povray.Color{p, q, v} | ||
| 110 | - case 4: | ||
| 111 | - return povray.Color{t, p, v} | ||
| 112 | - default: | ||
| 113 | - return povray.Color{v, p, q} | ||
| 114 | - } | ||
| 115 | -} | ||
deleted
demos/qwen-style/workspace/examples/spheres/main.go +0 -73 | deleted file mode 100644 | ||
| @@ -1,73 +0,0 @@ | ||
| 1 | -// Command spheres is the classic "first render" scene: a row of | |
| 2 | -// colored spheres and a box on a floor, lit by two point lights. | |
| 3 | -package main | |
| 4 | - | |
| 5 | -import ( | |
| 6 | - "fmt" | |
| 7 | - "log" | |
| 8 | - | |
| 9 | - "github.com/user/povray" | |
| 10 | -) | |
| 11 | - | |
| 12 | -func main() { | |
| 13 | - scene := povray.NewScene() | |
| 14 | - scene.Background = povray.Color{0.9, 0.9, 0.95} | |
| 15 | - | |
| 16 | - // Floor. | |
| 17 | - floor := povray.NewPlaneXY(0) | |
| 18 | - floorMat := povray.Material{ | |
| 19 | - Diffuse: povray.Color{0.75, 0.75, 0.8}, | |
| 20 | - Specular: povray.Color{0.3, 0.3, 0.3}, | |
| 21 | - Shininess: 20, | |
| 22 | - Ambient: 0.35, | |
| 23 | - } | |
| 24 | - scene.AddObject(floor, floorMat) | |
| 25 | - | |
| 26 | - // A row of colored spheres. | |
| 27 | - spheres := []struct { | |
| 28 | - x float64 | |
| 29 | - c povray.Color | |
| 30 | - }{ | |
| 31 | - {-2.2, povray.Red}, | |
| 32 | - {-0.8, povray.Green}, | |
| 33 | - {0.6, povray.Blue}, | |
| 34 | - {2.0, povray.Gold}, | |
| 35 | - } | |
| 36 | - for _, s := range spheres { | |
| 37 | - mat := povray.Material{ | |
| 38 | - Diffuse: s.c, | |
| 39 | - Specular: povray.White, | |
| 40 | - Shininess: 60, | |
| 41 | - Ambient: 0.1, | |
| 42 | - } | |
| 43 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{s.x, 1, 0}, Radius: 1}, mat) | |
| 44 | - } | |
| 45 | - | |
| 46 | - // A box for variety. | |
| 47 | - boxMat := povray.Material{ | |
| 48 | - Diffuse: povray.Purple, | |
| 49 | - Specular: povray.White, | |
| 50 | - Shininess: 30, | |
| 51 | - Ambient: 0.15, | |
| 52 | - } | |
| 53 | - scene.AddObject(povray.Box{ | |
| 54 | - Min: povray.Vec3{-0.9, 0, -3.5}, | |
| 55 | - Max: povray.Vec3{0.9, 1.8, -1.7}, | |
| 56 | - }, boxMat) | |
| 57 | - | |
| 58 | - // Lights: a warm key light and a cool fill light. | |
| 59 | - scene.Lights = []povray.Light{ | |
| 60 | - povray.NewLight(povray.Vec3{4, 8, 6}, povray.Color{1, 0.95, 0.85}), | |
| 61 | - povray.NewLight(povray.Vec3{-6, 4, -4}, povray.Color{0.6, 0.7, 0.9}), | |
| 62 | - } | |
| 63 | - | |
| 64 | - cam := povray.NewCamera(povray.Vec3{0, 2.5, 10}) | |
| 65 | - cam.Fov = 55 | |
| 66 | - | |
| 67 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | |
| 68 | - img := renderer.Render(cam) | |
| 69 | - if err := img.SavePNG("spheres.png"); err != nil { | |
| 70 | - log.Fatal(err) | |
| 71 | - } | |
| 72 | - fmt.Println("wrote spheres.png") | |
| 73 | -} | |
| deleted file mode 100644 | |||
| @@ -1,73 +0,0 @@ | |||
| 1 | -// Command spheres is the classic "first render" scene: a row of | ||
| 2 | -// colored spheres and a box on a floor, lit by two point lights. | ||
| 3 | -package main | ||
| 4 | - | ||
| 5 | -import ( | ||
| 6 | - "fmt" | ||
| 7 | - "log" | ||
| 8 | - | ||
| 9 | - "github.com/user/povray" | ||
| 10 | -) | ||
| 11 | - | ||
| 12 | -func main() { | ||
| 13 | - scene := povray.NewScene() | ||
| 14 | - scene.Background = povray.Color{0.9, 0.9, 0.95} | ||
| 15 | - | ||
| 16 | - // Floor. | ||
| 17 | - floor := povray.NewPlaneXY(0) | ||
| 18 | - floorMat := povray.Material{ | ||
| 19 | - Diffuse: povray.Color{0.75, 0.75, 0.8}, | ||
| 20 | - Specular: povray.Color{0.3, 0.3, 0.3}, | ||
| 21 | - Shininess: 20, | ||
| 22 | - Ambient: 0.35, | ||
| 23 | - } | ||
| 24 | - scene.AddObject(floor, floorMat) | ||
| 25 | - | ||
| 26 | - // A row of colored spheres. | ||
| 27 | - spheres := []struct { | ||
| 28 | - x float64 | ||
| 29 | - c povray.Color | ||
| 30 | - }{ | ||
| 31 | - {-2.2, povray.Red}, | ||
| 32 | - {-0.8, povray.Green}, | ||
| 33 | - {0.6, povray.Blue}, | ||
| 34 | - {2.0, povray.Gold}, | ||
| 35 | - } | ||
| 36 | - for _, s := range spheres { | ||
| 37 | - mat := povray.Material{ | ||
| 38 | - Diffuse: s.c, | ||
| 39 | - Specular: povray.White, | ||
| 40 | - Shininess: 60, | ||
| 41 | - Ambient: 0.1, | ||
| 42 | - } | ||
| 43 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{s.x, 1, 0}, Radius: 1}, mat) | ||
| 44 | - } | ||
| 45 | - | ||
| 46 | - // A box for variety. | ||
| 47 | - boxMat := povray.Material{ | ||
| 48 | - Diffuse: povray.Purple, | ||
| 49 | - Specular: povray.White, | ||
| 50 | - Shininess: 30, | ||
| 51 | - Ambient: 0.15, | ||
| 52 | - } | ||
| 53 | - scene.AddObject(povray.Box{ | ||
| 54 | - Min: povray.Vec3{-0.9, 0, -3.5}, | ||
| 55 | - Max: povray.Vec3{0.9, 1.8, -1.7}, | ||
| 56 | - }, boxMat) | ||
| 57 | - | ||
| 58 | - // Lights: a warm key light and a cool fill light. | ||
| 59 | - scene.Lights = []povray.Light{ | ||
| 60 | - povray.NewLight(povray.Vec3{4, 8, 6}, povray.Color{1, 0.95, 0.85}), | ||
| 61 | - povray.NewLight(povray.Vec3{-6, 4, -4}, povray.Color{0.6, 0.7, 0.9}), | ||
| 62 | - } | ||
| 63 | - | ||
| 64 | - cam := povray.NewCamera(povray.Vec3{0, 2.5, 10}) | ||
| 65 | - cam.Fov = 55 | ||
| 66 | - | ||
| 67 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | ||
| 68 | - img := renderer.Render(cam) | ||
| 69 | - if err := img.SavePNG("spheres.png"); err != nil { | ||
| 70 | - log.Fatal(err) | ||
| 71 | - } | ||
| 72 | - fmt.Println("wrote spheres.png") | ||
| 73 | -} | ||
deleted
demos/qwen-style/workspace/examples/transform/main.go +0 -77 | deleted file mode 100644 | ||
| @@ -1,77 +0,0 @@ | ||
| 1 | -// Command transform renders a scene built entirely from transformed | |
| 2 | -// primitives: tilted boxes, a stretched ellipsoid and a ring of rotated | |
| 3 | -// boxes, to show that objects can live in any orientation. | |
| 4 | -package main | |
| 5 | - | |
| 6 | -import ( | |
| 7 | - "fmt" | |
| 8 | - "log" | |
| 9 | - "math" | |
| 10 | - | |
| 11 | - "github.com/user/povray" | |
| 12 | -) | |
| 13 | - | |
| 14 | -func main() { | |
| 15 | - scene := povray.NewScene() | |
| 16 | - scene.MaxDepth = 4 | |
| 17 | - scene.Lights = []povray.Light{ | |
| 18 | - povray.NewLight(povray.Vec3{6, 10, 8}, povray.White), | |
| 19 | - povray.NewLight(povray.Vec3{-8, 4, -4}, povray.Color{0.5, 0.6, 1.0}), | |
| 20 | - } | |
| 21 | - | |
| 22 | - ground := povray.Material{Diffuse: povray.Color{0.18, 0.18, 0.22}, Ambient: 0.35} | |
| 23 | - scene.AddObject(povray.NewPlaneXY(0), ground) | |
| 24 | - | |
| 25 | - // A box tilted 45° around X, leaning on the ground. | |
| 26 | - tilted := povray.Transform{ | |
| 27 | - Object: povray.Box{Min: povray.Vec3{-1, 0, -1}, Max: povray.Vec3{1, 2, 1}}, | |
| 28 | - Matrix: povray.Translate(-3, 0, -2).Multiply(povray.RotateX(math.Pi / 5)), | |
| 29 | - } | |
| 30 | - scene.AddObject(tilted, povray.Material{ | |
| 31 | - Diffuse: povray.Color{0.9, 0.3, 0.2}, Specular: povray.White, | |
| 32 | - Shininess: 40, Ambient: 0.1, | |
| 33 | - }) | |
| 34 | - | |
| 35 | - // A stretched ellipsoid (scaled sphere), mirror-finished. | |
| 36 | - ellipsoid := povray.Transform{ | |
| 37 | - Object: povray.Sphere{Center: povray.Vec3{0, 0, 0}, Radius: 1}, | |
| 38 | - Matrix: povray.Translate(3, 1.2, -3).Multiply(povray.Scale(1.6, 1, 1)).Multiply(povray.RotateY(math.Pi / 6)), | |
| 39 | - } | |
| 40 | - scene.AddObject(ellipsoid, povray.Material{ | |
| 41 | - Diffuse: povray.Color{0.2, 0.7, 0.9}, Specular: povray.White, | |
| 42 | - Shininess: 80, Ambient: 0.1, Reflectivity: 0.5, | |
| 43 | - }) | |
| 44 | - | |
| 45 | - // A ring of 12 boxes, each rotated around Y and tilted around X. | |
| 46 | - ring := make([]povray.Object, 0, 12) | |
| 47 | - for i := 0; i < 12; i++ { | |
| 48 | - a := 2 * math.Pi * float64(i) / 12 | |
| 49 | - m := povray.Translate(0, 0, 0). | |
| 50 | - Multiply(povray.RotateY(a)). | |
| 51 | - Multiply(povray.Translate(0, 0, -5.5)). | |
| 52 | - Multiply(povray.RotateX(math.Pi / 4)) | |
| 53 | - ring = append(ring, povray.Transform{ | |
| 54 | - Object: povray.Box{Min: povray.Vec3{-0.4, 0, -0.4}, Max: povray.Vec3{0.4, 1.6, 0.4}}, | |
| 55 | - Matrix: m, | |
| 56 | - }) | |
| 57 | - } | |
| 58 | - scene.AddObject(povray.Group{Objects: ring}, povray.Material{ | |
| 59 | - Diffuse: povray.Color{0.85, 0.75, 0.3}, Specular: povray.White, | |
| 60 | - Shininess: 30, Ambient: 0.15, | |
| 61 | - }) | |
| 62 | - | |
| 63 | - // A glass sphere for refraction, sitting on the ground. | |
| 64 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 1}, Radius: 1}, | |
| 65 | - povray.Material{Diffuse: povray.White, Transparency: 0.9, IOR: 1.5, | |
| 66 | - Shininess: 100, Specular: povray.White, Ambient: 0.05}) | |
| 67 | - | |
| 68 | - cam := povray.NewCamera(povray.Vec3{0, 3.5, 12}) | |
| 69 | - cam.LookAt = povray.Vec3{0, 1, -2} | |
| 70 | - | |
| 71 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | |
| 72 | - img := renderer.Render(cam) | |
| 73 | - if err := img.SavePNG("transform.png"); err != nil { | |
| 74 | - log.Fatal(err) | |
| 75 | - } | |
| 76 | - fmt.Println("wrote transform.png") | |
| 77 | -} | |
| deleted file mode 100644 | |||
| @@ -1,77 +0,0 @@ | |||
| 1 | -// Command transform renders a scene built entirely from transformed | ||
| 2 | -// primitives: tilted boxes, a stretched ellipsoid and a ring of rotated | ||
| 3 | -// boxes, to show that objects can live in any orientation. | ||
| 4 | -package main | ||
| 5 | - | ||
| 6 | -import ( | ||
| 7 | - "fmt" | ||
| 8 | - "log" | ||
| 9 | - "math" | ||
| 10 | - | ||
| 11 | - "github.com/user/povray" | ||
| 12 | -) | ||
| 13 | - | ||
| 14 | -func main() { | ||
| 15 | - scene := povray.NewScene() | ||
| 16 | - scene.MaxDepth = 4 | ||
| 17 | - scene.Lights = []povray.Light{ | ||
| 18 | - povray.NewLight(povray.Vec3{6, 10, 8}, povray.White), | ||
| 19 | - povray.NewLight(povray.Vec3{-8, 4, -4}, povray.Color{0.5, 0.6, 1.0}), | ||
| 20 | - } | ||
| 21 | - | ||
| 22 | - ground := povray.Material{Diffuse: povray.Color{0.18, 0.18, 0.22}, Ambient: 0.35} | ||
| 23 | - scene.AddObject(povray.NewPlaneXY(0), ground) | ||
| 24 | - | ||
| 25 | - // A box tilted 45° around X, leaning on the ground. | ||
| 26 | - tilted := povray.Transform{ | ||
| 27 | - Object: povray.Box{Min: povray.Vec3{-1, 0, -1}, Max: povray.Vec3{1, 2, 1}}, | ||
| 28 | - Matrix: povray.Translate(-3, 0, -2).Multiply(povray.RotateX(math.Pi / 5)), | ||
| 29 | - } | ||
| 30 | - scene.AddObject(tilted, povray.Material{ | ||
| 31 | - Diffuse: povray.Color{0.9, 0.3, 0.2}, Specular: povray.White, | ||
| 32 | - Shininess: 40, Ambient: 0.1, | ||
| 33 | - }) | ||
| 34 | - | ||
| 35 | - // A stretched ellipsoid (scaled sphere), mirror-finished. | ||
| 36 | - ellipsoid := povray.Transform{ | ||
| 37 | - Object: povray.Sphere{Center: povray.Vec3{0, 0, 0}, Radius: 1}, | ||
| 38 | - Matrix: povray.Translate(3, 1.2, -3).Multiply(povray.Scale(1.6, 1, 1)).Multiply(povray.RotateY(math.Pi / 6)), | ||
| 39 | - } | ||
| 40 | - scene.AddObject(ellipsoid, povray.Material{ | ||
| 41 | - Diffuse: povray.Color{0.2, 0.7, 0.9}, Specular: povray.White, | ||
| 42 | - Shininess: 80, Ambient: 0.1, Reflectivity: 0.5, | ||
| 43 | - }) | ||
| 44 | - | ||
| 45 | - // A ring of 12 boxes, each rotated around Y and tilted around X. | ||
| 46 | - ring := make([]povray.Object, 0, 12) | ||
| 47 | - for i := 0; i < 12; i++ { | ||
| 48 | - a := 2 * math.Pi * float64(i) / 12 | ||
| 49 | - m := povray.Translate(0, 0, 0). | ||
| 50 | - Multiply(povray.RotateY(a)). | ||
| 51 | - Multiply(povray.Translate(0, 0, -5.5)). | ||
| 52 | - Multiply(povray.RotateX(math.Pi / 4)) | ||
| 53 | - ring = append(ring, povray.Transform{ | ||
| 54 | - Object: povray.Box{Min: povray.Vec3{-0.4, 0, -0.4}, Max: povray.Vec3{0.4, 1.6, 0.4}}, | ||
| 55 | - Matrix: m, | ||
| 56 | - }) | ||
| 57 | - } | ||
| 58 | - scene.AddObject(povray.Group{Objects: ring}, povray.Material{ | ||
| 59 | - Diffuse: povray.Color{0.85, 0.75, 0.3}, Specular: povray.White, | ||
| 60 | - Shininess: 30, Ambient: 0.15, | ||
| 61 | - }) | ||
| 62 | - | ||
| 63 | - // A glass sphere for refraction, sitting on the ground. | ||
| 64 | - scene.AddObject(povray.Sphere{Center: povray.Vec3{0, 1, 1}, Radius: 1}, | ||
| 65 | - povray.Material{Diffuse: povray.White, Transparency: 0.9, IOR: 1.5, | ||
| 66 | - Shininess: 100, Specular: povray.White, Ambient: 0.05}) | ||
| 67 | - | ||
| 68 | - cam := povray.NewCamera(povray.Vec3{0, 3.5, 12}) | ||
| 69 | - cam.LookAt = povray.Vec3{0, 1, -2} | ||
| 70 | - | ||
| 71 | - renderer := povray.Renderer{Scene: scene, Width: 800, Height: 600, Samples: 2} | ||
| 72 | - img := renderer.Render(cam) | ||
| 73 | - if err := img.SavePNG("transform.png"); err != nil { | ||
| 74 | - log.Fatal(err) | ||
| 75 | - } | ||
| 76 | - fmt.Println("wrote transform.png") | ||
| 77 | -} | ||
deleted
demos/qwen-style/workspace/go.mod +0 -3 | deleted file mode 100644 | ||
| @@ -1,3 +0,0 @@ | ||
| 1 | -module github.com/user/povray | |
| 2 | - | |
| 3 | -go 1.27.3 | |
| deleted file mode 100644 | |||
| @@ -1,3 +0,0 @@ | |||
| 1 | -module github.com/user/povray | ||
| 2 | - | ||
| 3 | -go 1.27.3 | ||
deleted
demos/qwen-style/workspace/material.go +0 -75 | deleted file mode 100644 | ||
| @@ -1,75 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -// Color is an RGB value, any magnitude (values > 1 are clipped or | |
| 4 | -// tone-mapped at render time). | |
| 5 | -type Color struct { | |
| 6 | - R, G, B float64 | |
| 7 | -} | |
| 8 | - | |
| 9 | -func (c Color) Add(o Color) Color { return Color{c.R + o.R, c.G + o.G, c.B + o.B} } | |
| 10 | -func (c Color) Scale(s float64) Color { | |
| 11 | - return Color{c.R * s, c.G * s, c.B * s} | |
| 12 | -} | |
| 13 | -func (c Color) Mul(o Color) Color { return Color{c.R * o.R, c.G * o.G, c.B * o.B} } | |
| 14 | - | |
| 15 | -// Common colors. | |
| 16 | -var ( | |
| 17 | - White = Color{1, 1, 1} | |
| 18 | - Black = Color{0, 0, 0} | |
| 19 | - Red = Color{0.9, 0.1, 0.1} | |
| 20 | - Green = Color{0.1, 0.8, 0.2} | |
| 21 | - Blue = Color{0.15, 0.3, 0.9} | |
| 22 | - Yellow = Color{0.95, 0.85, 0.2} | |
| 23 | - Orange = Color{0.95, 0.5, 0.1} | |
| 24 | - Purple = Color{0.6, 0.2, 0.8} | |
| 25 | - Cyan = Color{0.1, 0.8, 0.9} | |
| 26 | - Gray = Color{0.5, 0.5, 0.5} | |
| 27 | - Silver = Color{0.85, 0.85, 0.88} | |
| 28 | - Gold = Color{1.0, 0.75, 0.25} | |
| 29 | -) | |
| 30 | - | |
| 31 | -// Material describes how a surface reacts to light. | |
| 32 | -type Material struct { | |
| 33 | - // Diffuse is the base surface color. | |
| 34 | - Diffuse Color | |
| 35 | - // Specular is the highlight color (White for a plain white glint). | |
| 36 | - Specular Color | |
| 37 | - // Shininess controls highlight size (higher = tighter). | |
| 38 | - Shininess float64 | |
| 39 | - // Ambient is the fraction of diffuse kept with no light at all. | |
| 40 | - Ambient float64 | |
| 41 | - // Reflectivity is the mirror strength, 0..1. | |
| 42 | - Reflectivity float64 | |
| 43 | - // Transparency is the glass strength, 0..1 (1 = fully see-through). | |
| 44 | - Transparency float64 | |
| 45 | - // IOR is the index of refraction for transparent surfaces. | |
| 46 | - IOR float64 | |
| 47 | - // Emission is the light the surface itself radiates (used by the | |
| 48 | - // path tracer; see LightMaterial). Values above 1 make a brighter | |
| 49 | - // light. | |
| 50 | - Emission Color | |
| 51 | -} | |
| 52 | - | |
| 53 | -// DefaultMaterial is a plain white matte surface. | |
| 54 | -func DefaultMaterial() Material { | |
| 55 | - return Material{Diffuse: White, Specular: White, Shininess: 10, Ambient: 0.1} | |
| 56 | -} | |
| 57 | - | |
| 58 | -// LightMaterial makes an emissive surface — a light source for the path | |
| 59 | -// tracer. intensity scales the color (1 is a dim panel, 10 a bright one). | |
| 60 | -func LightMaterial(c Color, intensity float64) Material { | |
| 61 | - return Material{Emission: c.Scale(intensity), Specular: White, Shininess: 10} | |
| 62 | -} | |
| 63 | - | |
| 64 | -// Light is a point light. | |
| 65 | -type Light struct { | |
| 66 | - Color Color | |
| 67 | - Position Vec3 | |
| 68 | - // Intensity scales the light (default 1). | |
| 69 | - Intensity float64 | |
| 70 | -} | |
| 71 | - | |
| 72 | -// NewLight makes a light with full intensity. | |
| 73 | -func NewLight(pos Vec3, c Color) Light { | |
| 74 | - return Light{Color: c, Position: pos, Intensity: 1} | |
| 75 | -} | |
| deleted file mode 100644 | |||
| @@ -1,75 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -// Color is an RGB value, any magnitude (values > 1 are clipped or | ||
| 4 | -// tone-mapped at render time). | ||
| 5 | -type Color struct { | ||
| 6 | - R, G, B float64 | ||
| 7 | -} | ||
| 8 | - | ||
| 9 | -func (c Color) Add(o Color) Color { return Color{c.R + o.R, c.G + o.G, c.B + o.B} } | ||
| 10 | -func (c Color) Scale(s float64) Color { | ||
| 11 | - return Color{c.R * s, c.G * s, c.B * s} | ||
| 12 | -} | ||
| 13 | -func (c Color) Mul(o Color) Color { return Color{c.R * o.R, c.G * o.G, c.B * o.B} } | ||
| 14 | - | ||
| 15 | -// Common colors. | ||
| 16 | -var ( | ||
| 17 | - White = Color{1, 1, 1} | ||
| 18 | - Black = Color{0, 0, 0} | ||
| 19 | - Red = Color{0.9, 0.1, 0.1} | ||
| 20 | - Green = Color{0.1, 0.8, 0.2} | ||
| 21 | - Blue = Color{0.15, 0.3, 0.9} | ||
| 22 | - Yellow = Color{0.95, 0.85, 0.2} | ||
| 23 | - Orange = Color{0.95, 0.5, 0.1} | ||
| 24 | - Purple = Color{0.6, 0.2, 0.8} | ||
| 25 | - Cyan = Color{0.1, 0.8, 0.9} | ||
| 26 | - Gray = Color{0.5, 0.5, 0.5} | ||
| 27 | - Silver = Color{0.85, 0.85, 0.88} | ||
| 28 | - Gold = Color{1.0, 0.75, 0.25} | ||
| 29 | -) | ||
| 30 | - | ||
| 31 | -// Material describes how a surface reacts to light. | ||
| 32 | -type Material struct { | ||
| 33 | - // Diffuse is the base surface color. | ||
| 34 | - Diffuse Color | ||
| 35 | - // Specular is the highlight color (White for a plain white glint). | ||
| 36 | - Specular Color | ||
| 37 | - // Shininess controls highlight size (higher = tighter). | ||
| 38 | - Shininess float64 | ||
| 39 | - // Ambient is the fraction of diffuse kept with no light at all. | ||
| 40 | - Ambient float64 | ||
| 41 | - // Reflectivity is the mirror strength, 0..1. | ||
| 42 | - Reflectivity float64 | ||
| 43 | - // Transparency is the glass strength, 0..1 (1 = fully see-through). | ||
| 44 | - Transparency float64 | ||
| 45 | - // IOR is the index of refraction for transparent surfaces. | ||
| 46 | - IOR float64 | ||
| 47 | - // Emission is the light the surface itself radiates (used by the | ||
| 48 | - // path tracer; see LightMaterial). Values above 1 make a brighter | ||
| 49 | - // light. | ||
| 50 | - Emission Color | ||
| 51 | -} | ||
| 52 | - | ||
| 53 | -// DefaultMaterial is a plain white matte surface. | ||
| 54 | -func DefaultMaterial() Material { | ||
| 55 | - return Material{Diffuse: White, Specular: White, Shininess: 10, Ambient: 0.1} | ||
| 56 | -} | ||
| 57 | - | ||
| 58 | -// LightMaterial makes an emissive surface — a light source for the path | ||
| 59 | -// tracer. intensity scales the color (1 is a dim panel, 10 a bright one). | ||
| 60 | -func LightMaterial(c Color, intensity float64) Material { | ||
| 61 | - return Material{Emission: c.Scale(intensity), Specular: White, Shininess: 10} | ||
| 62 | -} | ||
| 63 | - | ||
| 64 | -// Light is a point light. | ||
| 65 | -type Light struct { | ||
| 66 | - Color Color | ||
| 67 | - Position Vec3 | ||
| 68 | - // Intensity scales the light (default 1). | ||
| 69 | - Intensity float64 | ||
| 70 | -} | ||
| 71 | - | ||
| 72 | -// NewLight makes a light with full intensity. | ||
| 73 | -func NewLight(pos Vec3, c Color) Light { | ||
| 74 | - return Light{Color: c, Position: pos, Intensity: 1} | ||
| 75 | -} | ||
deleted
demos/qwen-style/workspace/objects.go +0 -129 | deleted file mode 100644 | ||
| @@ -1,129 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import "math" | |
| 4 | - | |
| 5 | -// Object is anything a ray can intersect. | |
| 6 | -type Object interface { | |
| 7 | - Intersect(r Ray) (Hit, bool) | |
| 8 | -} | |
| 9 | - | |
| 10 | -// Sphere | |
| 11 | -type Sphere struct { | |
| 12 | - Center Vec3 | |
| 13 | - Radius float64 | |
| 14 | -} | |
| 15 | - | |
| 16 | -func (s Sphere) Intersect(r Ray) (Hit, bool) { | |
| 17 | - oc := r.Origin.Sub(s.Center) | |
| 18 | - b := 2 * oc.Dot(r.Dir) | |
| 19 | - c := oc.Dot(oc) - s.Radius*s.Radius | |
| 20 | - disc := b*b - 4*c | |
| 21 | - if disc < 0 { | |
| 22 | - return Hit{}, false | |
| 23 | - } | |
| 24 | - var t float64 | |
| 25 | - sqrtDisc := math.Sqrt(disc) | |
| 26 | - t0 := (-b - sqrtDisc) / 2 | |
| 27 | - t1 := (-b + sqrtDisc) / 2 | |
| 28 | - const eps = 1e-4 | |
| 29 | - switch { | |
| 30 | - case t0 > eps: | |
| 31 | - t = t0 | |
| 32 | - case t1 > eps: | |
| 33 | - t = t1 | |
| 34 | - default: | |
| 35 | - return Hit{}, false | |
| 36 | - } | |
| 37 | - p := r.Origin.Add(r.Dir.Scale(t)) | |
| 38 | - return Hit{Point: p, Dist: t, Normal: p.Sub(s.Center).Norm()}, true | |
| 39 | -} | |
| 40 | - | |
| 41 | -// Plane is the infinite surface ax + by + cz + d = 0, with the normal | |
| 42 | -// pointing in the (a,b,c) direction. | |
| 43 | -type Plane struct { | |
| 44 | - Normal Vec3 | |
| 45 | - D float64 | |
| 46 | -} | |
| 47 | - | |
| 48 | -// NewPlaneXY makes the horizontal plane y = h. | |
| 49 | -func NewPlaneXY(h float64) Plane { return Plane{Normal: Vec3{0, 1, 0}, D: -h} } | |
| 50 | - | |
| 51 | -func (p Plane) Intersect(r Ray) (Hit, bool) { | |
| 52 | - denom := p.Normal.Dot(r.Dir) | |
| 53 | - if math.Abs(denom) < 1e-8 { | |
| 54 | - return Hit{}, false | |
| 55 | - } | |
| 56 | - t := -(p.Normal.Dot(r.Origin) + p.D) / denom | |
| 57 | - if t < 1e-4 { | |
| 58 | - return Hit{}, false | |
| 59 | - } | |
| 60 | - pt := r.Origin.Add(r.Dir.Scale(t)) | |
| 61 | - n := p.Normal | |
| 62 | - if n.Dot(r.Dir) > 0 { | |
| 63 | - n = n.Neg() | |
| 64 | - } | |
| 65 | - return Hit{Point: pt, Dist: t, Normal: n}, true | |
| 66 | -} | |
| 67 | - | |
| 68 | -// Box is an axis-aligned box. | |
| 69 | -type Box struct { | |
| 70 | - Min, Max Vec3 | |
| 71 | -} | |
| 72 | - | |
| 73 | -func (b Box) Intersect(r Ray) (Hit, bool) { | |
| 74 | - var tmin, tmax float64 | |
| 75 | - tmin, tmax = -1e30, 1e30 | |
| 76 | - var n Vec3 | |
| 77 | - for i, o := range [3]float64{r.Origin.X, r.Origin.Y, r.Origin.Z} { | |
| 78 | - d := [3]float64{r.Dir.X, r.Dir.Y, r.Dir.Z}[i] | |
| 79 | - mn := [3]float64{b.Min.X, b.Min.Y, b.Min.Z}[i] | |
| 80 | - mx := [3]float64{b.Max.X, b.Max.Y, b.Max.Z}[i] | |
| 81 | - if math.Abs(d) < 1e-8 { | |
| 82 | - if o < mn || o > mx { | |
| 83 | - return Hit{}, false | |
| 84 | - } | |
| 85 | - continue | |
| 86 | - } | |
| 87 | - t0 := (mn - o) / d | |
| 88 | - t1 := (mx - o) / d | |
| 89 | - if t0 > t1 { | |
| 90 | - t0, t1 = t1, t0 | |
| 91 | - } | |
| 92 | - if t0 > tmin { | |
| 93 | - tmin = t0 | |
| 94 | - if i == 0 { | |
| 95 | - n = Vec3{-math.Copysign(1, d), 0, 0} | |
| 96 | - } else if i == 1 { | |
| 97 | - n = Vec3{0, -math.Copysign(1, d), 0} | |
| 98 | - } else { | |
| 99 | - n = Vec3{0, 0, -math.Copysign(1, d)} | |
| 100 | - } | |
| 101 | - } | |
| 102 | - if t1 < tmax { | |
| 103 | - tmax = t1 | |
| 104 | - } | |
| 105 | - } | |
| 106 | - if tmax < math.Max(tmin, 1e-4) { | |
| 107 | - return Hit{}, false | |
| 108 | - } | |
| 109 | - t := tmin | |
| 110 | - if t < 1e-4 { | |
| 111 | - t = tmax | |
| 112 | - } | |
| 113 | - return Hit{Point: r.Origin.Add(r.Dir.Scale(t)), Dist: t, Normal: n}, true | |
| 114 | -} | |
| 115 | - | |
| 116 | -// Group intersects all children and keeps the closest hit. | |
| 117 | -type Group struct { | |
| 118 | - Objects []Object | |
| 119 | -} | |
| 120 | - | |
| 121 | -func (g Group) Intersect(r Ray) (Hit, bool) { | |
| 122 | - best, found := Hit{}, false | |
| 123 | - for _, o := range g.Objects { | |
| 124 | - if h, ok := o.Intersect(r); ok && (!found || h.Dist < best.Dist) { | |
| 125 | - best, found = h, true | |
| 126 | - } | |
| 127 | - } | |
| 128 | - return best, found | |
| 129 | -} | |
| deleted file mode 100644 | |||
| @@ -1,129 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import "math" | ||
| 4 | - | ||
| 5 | -// Object is anything a ray can intersect. | ||
| 6 | -type Object interface { | ||
| 7 | - Intersect(r Ray) (Hit, bool) | ||
| 8 | -} | ||
| 9 | - | ||
| 10 | -// Sphere | ||
| 11 | -type Sphere struct { | ||
| 12 | - Center Vec3 | ||
| 13 | - Radius float64 | ||
| 14 | -} | ||
| 15 | - | ||
| 16 | -func (s Sphere) Intersect(r Ray) (Hit, bool) { | ||
| 17 | - oc := r.Origin.Sub(s.Center) | ||
| 18 | - b := 2 * oc.Dot(r.Dir) | ||
| 19 | - c := oc.Dot(oc) - s.Radius*s.Radius | ||
| 20 | - disc := b*b - 4*c | ||
| 21 | - if disc < 0 { | ||
| 22 | - return Hit{}, false | ||
| 23 | - } | ||
| 24 | - var t float64 | ||
| 25 | - sqrtDisc := math.Sqrt(disc) | ||
| 26 | - t0 := (-b - sqrtDisc) / 2 | ||
| 27 | - t1 := (-b + sqrtDisc) / 2 | ||
| 28 | - const eps = 1e-4 | ||
| 29 | - switch { | ||
| 30 | - case t0 > eps: | ||
| 31 | - t = t0 | ||
| 32 | - case t1 > eps: | ||
| 33 | - t = t1 | ||
| 34 | - default: | ||
| 35 | - return Hit{}, false | ||
| 36 | - } | ||
| 37 | - p := r.Origin.Add(r.Dir.Scale(t)) | ||
| 38 | - return Hit{Point: p, Dist: t, Normal: p.Sub(s.Center).Norm()}, true | ||
| 39 | -} | ||
| 40 | - | ||
| 41 | -// Plane is the infinite surface ax + by + cz + d = 0, with the normal | ||
| 42 | -// pointing in the (a,b,c) direction. | ||
| 43 | -type Plane struct { | ||
| 44 | - Normal Vec3 | ||
| 45 | - D float64 | ||
| 46 | -} | ||
| 47 | - | ||
| 48 | -// NewPlaneXY makes the horizontal plane y = h. | ||
| 49 | -func NewPlaneXY(h float64) Plane { return Plane{Normal: Vec3{0, 1, 0}, D: -h} } | ||
| 50 | - | ||
| 51 | -func (p Plane) Intersect(r Ray) (Hit, bool) { | ||
| 52 | - denom := p.Normal.Dot(r.Dir) | ||
| 53 | - if math.Abs(denom) < 1e-8 { | ||
| 54 | - return Hit{}, false | ||
| 55 | - } | ||
| 56 | - t := -(p.Normal.Dot(r.Origin) + p.D) / denom | ||
| 57 | - if t < 1e-4 { | ||
| 58 | - return Hit{}, false | ||
| 59 | - } | ||
| 60 | - pt := r.Origin.Add(r.Dir.Scale(t)) | ||
| 61 | - n := p.Normal | ||
| 62 | - if n.Dot(r.Dir) > 0 { | ||
| 63 | - n = n.Neg() | ||
| 64 | - } | ||
| 65 | - return Hit{Point: pt, Dist: t, Normal: n}, true | ||
| 66 | -} | ||
| 67 | - | ||
| 68 | -// Box is an axis-aligned box. | ||
| 69 | -type Box struct { | ||
| 70 | - Min, Max Vec3 | ||
| 71 | -} | ||
| 72 | - | ||
| 73 | -func (b Box) Intersect(r Ray) (Hit, bool) { | ||
| 74 | - var tmin, tmax float64 | ||
| 75 | - tmin, tmax = -1e30, 1e30 | ||
| 76 | - var n Vec3 | ||
| 77 | - for i, o := range [3]float64{r.Origin.X, r.Origin.Y, r.Origin.Z} { | ||
| 78 | - d := [3]float64{r.Dir.X, r.Dir.Y, r.Dir.Z}[i] | ||
| 79 | - mn := [3]float64{b.Min.X, b.Min.Y, b.Min.Z}[i] | ||
| 80 | - mx := [3]float64{b.Max.X, b.Max.Y, b.Max.Z}[i] | ||
| 81 | - if math.Abs(d) < 1e-8 { | ||
| 82 | - if o < mn || o > mx { | ||
| 83 | - return Hit{}, false | ||
| 84 | - } | ||
| 85 | - continue | ||
| 86 | - } | ||
| 87 | - t0 := (mn - o) / d | ||
| 88 | - t1 := (mx - o) / d | ||
| 89 | - if t0 > t1 { | ||
| 90 | - t0, t1 = t1, t0 | ||
| 91 | - } | ||
| 92 | - if t0 > tmin { | ||
| 93 | - tmin = t0 | ||
| 94 | - if i == 0 { | ||
| 95 | - n = Vec3{-math.Copysign(1, d), 0, 0} | ||
| 96 | - } else if i == 1 { | ||
| 97 | - n = Vec3{0, -math.Copysign(1, d), 0} | ||
| 98 | - } else { | ||
| 99 | - n = Vec3{0, 0, -math.Copysign(1, d)} | ||
| 100 | - } | ||
| 101 | - } | ||
| 102 | - if t1 < tmax { | ||
| 103 | - tmax = t1 | ||
| 104 | - } | ||
| 105 | - } | ||
| 106 | - if tmax < math.Max(tmin, 1e-4) { | ||
| 107 | - return Hit{}, false | ||
| 108 | - } | ||
| 109 | - t := tmin | ||
| 110 | - if t < 1e-4 { | ||
| 111 | - t = tmax | ||
| 112 | - } | ||
| 113 | - return Hit{Point: r.Origin.Add(r.Dir.Scale(t)), Dist: t, Normal: n}, true | ||
| 114 | -} | ||
| 115 | - | ||
| 116 | -// Group intersects all children and keeps the closest hit. | ||
| 117 | -type Group struct { | ||
| 118 | - Objects []Object | ||
| 119 | -} | ||
| 120 | - | ||
| 121 | -func (g Group) Intersect(r Ray) (Hit, bool) { | ||
| 122 | - best, found := Hit{}, false | ||
| 123 | - for _, o := range g.Objects { | ||
| 124 | - if h, ok := o.Intersect(r); ok && (!found || h.Dist < best.Dist) { | ||
| 125 | - best, found = h, true | ||
| 126 | - } | ||
| 127 | - } | ||
| 128 | - return best, found | ||
| 129 | -} | ||
deleted
demos/qwen-style/workspace/pathtracer.go +0 -148 | deleted file mode 100644 | ||
| @@ -1,148 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import ( | |
| 4 | - "math" | |
| 5 | - "math/rand/v2" | |
| 6 | - "sync" | |
| 7 | -) | |
| 8 | - | |
| 9 | -// PathTracer renders a scene with path tracing: each ray bounces around | |
| 10 | -// the scene, scattering diffusely at matte surfaces, until it hits an | |
| 11 | -// emissive surface or leaves the scene. This gives global illumination | |
| 12 | -// and soft shadows, at the cost of a longer render (use Samples to | |
| 13 | -// control quality vs. time). | |
| 14 | -// | |
| 15 | -// Lights for path tracing are emissive surfaces: give an object a | |
| 16 | -// material with Emission set (see LightMaterial). Point lights in | |
| 17 | -// Scene.Lights are ignored by the path tracer. | |
| 18 | -type PathTracer struct { | |
| 19 | - Scene *Scene | |
| 20 | - // Width and Height of the image in pixels. | |
| 21 | - Width, Height int | |
| 22 | - // Samples is the number of primary rays per pixel. | |
| 23 | - Samples int | |
| 24 | - // Threads is the number of worker goroutines (0 = 4). | |
| 25 | - Threads int | |
| 26 | -} | |
| 27 | - | |
| 28 | -// Render renders the scene and returns the image. Each worker goroutine | |
| 29 | -// uses its own random stream, so a given configuration always produces | |
| 30 | -// the same image. | |
| 31 | -func (p *PathTracer) Render(cam Camera) *Image { | |
| 32 | - samples := p.Samples | |
| 33 | - if samples < 1 { | |
| 34 | - samples = 1 | |
| 35 | - } | |
| 36 | - threads := p.Threads | |
| 37 | - if threads <= 0 { | |
| 38 | - threads = 4 | |
| 39 | - } | |
| 40 | - img := NewImage(p.Width, p.Height) | |
| 41 | - | |
| 42 | - var wg sync.WaitGroup | |
| 43 | - rowsPer := (p.Height + threads - 1) / threads | |
| 44 | - for c := 0; c < threads; c++ { | |
| 45 | - y0, y1 := c*rowsPer, (c+1)*rowsPer | |
| 46 | - if y1 > p.Height { | |
| 47 | - y1 = p.Height | |
| 48 | - } | |
| 49 | - if y0 >= y1 { | |
| 50 | - break | |
| 51 | - } | |
| 52 | - wg.Add(1) | |
| 53 | - go func(y0, y1 int, rng *rand.Rand) { | |
| 54 | - defer wg.Done() | |
| 55 | - for y := y0; y < y1; y++ { | |
| 56 | - for x := 0; x < p.Width; x++ { | |
| 57 | - var col Color | |
| 58 | - for smp := 0; smp < samples; smp++ { | |
| 59 | - u := float64(x) + rng.Float64() | |
| 60 | - v := float64(y) + rng.Float64() | |
| 61 | - col = col.Add(p.trace(cam.RayAtUV(u, v, p.Width, p.Height), 0, rng)) | |
| 62 | - } | |
| 63 | - img.Set(x, y, col.Scale(1.0/float64(samples))) | |
| 64 | - } | |
| 65 | - } | |
| 66 | - }(y0, y1, rand.New(rand.NewPCG(uint64(12345+c), uint64(987654321+c)))) | |
| 67 | - } | |
| 68 | - wg.Wait() | |
| 69 | - return img | |
| 70 | -} | |
| 71 | - | |
| 72 | -// trace follows one path through the scene and returns the accumulated | |
| 73 | -// radiance. | |
| 74 | -func (p *PathTracer) trace(ray Ray, depth int, rng *rand.Rand) Color { | |
| 75 | - // Hard bounce limit: glass and mirrors can bounce a ray forever; | |
| 76 | - // past 50 bounces the remaining contribution is negligible. | |
| 77 | - if depth > 50 { | |
| 78 | - return Black | |
| 79 | - } | |
| 80 | - s := p.Scene | |
| 81 | - idx, hit := closestHit(s.Objects, ray) | |
| 82 | - if idx < 0 { | |
| 83 | - return s.Background | |
| 84 | - } | |
| 85 | - hit.Flip(ray) | |
| 86 | - mat := s.Materials[idx] | |
| 87 | - | |
| 88 | - // An emissive surface radiates its own light. | |
| 89 | - col := mat.Emission | |
| 90 | - | |
| 91 | - // Specular: mirror the ray and keep going. | |
| 92 | - if mat.Reflectivity > 0 { | |
| 93 | - r := ray.Dir.Sub(hit.Normal.Scale(2 * ray.Dir.Dot(hit.Normal))).Norm() | |
| 94 | - rr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: r} | |
| 95 | - col = col.Add(p.trace(rr, depth+1, rng).Scale(mat.Reflectivity)) | |
| 96 | - } | |
| 97 | - | |
| 98 | - // Glass: bend the ray through the surface and keep going. | |
| 99 | - if mat.Transparency > 0 { | |
| 100 | - ior := 1.0 | |
| 101 | - if mat.IOR > 0 { | |
| 102 | - ior = mat.IOR | |
| 103 | - } | |
| 104 | - if refr, ok := refract(ray.Dir, hit.Normal, 1/ior); ok { | |
| 105 | - tr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: refr} | |
| 106 | - col = col.Add(p.trace(tr, depth+1, rng).Scale(mat.Transparency)) | |
| 107 | - } | |
| 108 | - } | |
| 109 | - | |
| 110 | - // Diffuse: scatter in a cosine-weighted random direction. This is | |
| 111 | - // what carries light from one surface to another (global | |
| 112 | - // illumination). | |
| 113 | - if depth < 8 && (mat.Diffuse.R > 0 || mat.Diffuse.G > 0 || mat.Diffuse.B > 0) { | |
| 114 | - d := cosineSample(hit.Normal, rng) | |
| 115 | - dr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: d} | |
| 116 | - col = col.Add(p.trace(dr, depth+1, rng).Mul(mat.Diffuse)) | |
| 117 | - } | |
| 118 | - | |
| 119 | - // Russian roulette: terminate the path with probability 1-max(col), | |
| 120 | - // unbiased. | |
| 121 | - q := math.Max(col.R, math.Max(col.G, col.B)) | |
| 122 | - q = math.Min(1, math.Max(q, 0.05)) | |
| 123 | - if rng.Float64() > q { | |
| 124 | - return col | |
| 125 | - } | |
| 126 | - return col.Scale(1 / q) | |
| 127 | -} | |
| 128 | - | |
| 129 | -// cosineSample returns a random direction in the hemisphere around n, | |
| 130 | -// weighted by cos(theta) — the distribution a perfect diffuse surface | |
| 131 | -// scatters into. | |
| 132 | -func cosineSample(n Vec3, rng *rand.Rand) Vec3 { | |
| 133 | - // A random point on the unit disk, mapped onto the hemisphere. | |
| 134 | - r := math.Sqrt(rng.Float64()) | |
| 135 | - phi := 2 * math.Pi * rng.Float64() | |
| 136 | - rx, ry := r*math.Cos(phi), r*math.Sin(phi) | |
| 137 | - rz := math.Sqrt(math.Max(0, 1-r*r)) | |
| 138 | - // A basis with n as its z axis. | |
| 139 | - var up Vec3 | |
| 140 | - if math.Abs(n.Y) < 0.99 { | |
| 141 | - up = Vec3{0, 1, 0} | |
| 142 | - } else { | |
| 143 | - up = Vec3{1, 0, 0} | |
| 144 | - } | |
| 145 | - u := n.Cross(up).Norm() | |
| 146 | - v := n.Cross(u) | |
| 147 | - return u.Scale(rx).Add(v.Scale(ry)).Add(n.Scale(rz)) | |
| 148 | -} | |
| deleted file mode 100644 | |||
| @@ -1,148 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import ( | ||
| 4 | - "math" | ||
| 5 | - "math/rand/v2" | ||
| 6 | - "sync" | ||
| 7 | -) | ||
| 8 | - | ||
| 9 | -// PathTracer renders a scene with path tracing: each ray bounces around | ||
| 10 | -// the scene, scattering diffusely at matte surfaces, until it hits an | ||
| 11 | -// emissive surface or leaves the scene. This gives global illumination | ||
| 12 | -// and soft shadows, at the cost of a longer render (use Samples to | ||
| 13 | -// control quality vs. time). | ||
| 14 | -// | ||
| 15 | -// Lights for path tracing are emissive surfaces: give an object a | ||
| 16 | -// material with Emission set (see LightMaterial). Point lights in | ||
| 17 | -// Scene.Lights are ignored by the path tracer. | ||
| 18 | -type PathTracer struct { | ||
| 19 | - Scene *Scene | ||
| 20 | - // Width and Height of the image in pixels. | ||
| 21 | - Width, Height int | ||
| 22 | - // Samples is the number of primary rays per pixel. | ||
| 23 | - Samples int | ||
| 24 | - // Threads is the number of worker goroutines (0 = 4). | ||
| 25 | - Threads int | ||
| 26 | -} | ||
| 27 | - | ||
| 28 | -// Render renders the scene and returns the image. Each worker goroutine | ||
| 29 | -// uses its own random stream, so a given configuration always produces | ||
| 30 | -// the same image. | ||
| 31 | -func (p *PathTracer) Render(cam Camera) *Image { | ||
| 32 | - samples := p.Samples | ||
| 33 | - if samples < 1 { | ||
| 34 | - samples = 1 | ||
| 35 | - } | ||
| 36 | - threads := p.Threads | ||
| 37 | - if threads <= 0 { | ||
| 38 | - threads = 4 | ||
| 39 | - } | ||
| 40 | - img := NewImage(p.Width, p.Height) | ||
| 41 | - | ||
| 42 | - var wg sync.WaitGroup | ||
| 43 | - rowsPer := (p.Height + threads - 1) / threads | ||
| 44 | - for c := 0; c < threads; c++ { | ||
| 45 | - y0, y1 := c*rowsPer, (c+1)*rowsPer | ||
| 46 | - if y1 > p.Height { | ||
| 47 | - y1 = p.Height | ||
| 48 | - } | ||
| 49 | - if y0 >= y1 { | ||
| 50 | - break | ||
| 51 | - } | ||
| 52 | - wg.Add(1) | ||
| 53 | - go func(y0, y1 int, rng *rand.Rand) { | ||
| 54 | - defer wg.Done() | ||
| 55 | - for y := y0; y < y1; y++ { | ||
| 56 | - for x := 0; x < p.Width; x++ { | ||
| 57 | - var col Color | ||
| 58 | - for smp := 0; smp < samples; smp++ { | ||
| 59 | - u := float64(x) + rng.Float64() | ||
| 60 | - v := float64(y) + rng.Float64() | ||
| 61 | - col = col.Add(p.trace(cam.RayAtUV(u, v, p.Width, p.Height), 0, rng)) | ||
| 62 | - } | ||
| 63 | - img.Set(x, y, col.Scale(1.0/float64(samples))) | ||
| 64 | - } | ||
| 65 | - } | ||
| 66 | - }(y0, y1, rand.New(rand.NewPCG(uint64(12345+c), uint64(987654321+c)))) | ||
| 67 | - } | ||
| 68 | - wg.Wait() | ||
| 69 | - return img | ||
| 70 | -} | ||
| 71 | - | ||
| 72 | -// trace follows one path through the scene and returns the accumulated | ||
| 73 | -// radiance. | ||
| 74 | -func (p *PathTracer) trace(ray Ray, depth int, rng *rand.Rand) Color { | ||
| 75 | - // Hard bounce limit: glass and mirrors can bounce a ray forever; | ||
| 76 | - // past 50 bounces the remaining contribution is negligible. | ||
| 77 | - if depth > 50 { | ||
| 78 | - return Black | ||
| 79 | - } | ||
| 80 | - s := p.Scene | ||
| 81 | - idx, hit := closestHit(s.Objects, ray) | ||
| 82 | - if idx < 0 { | ||
| 83 | - return s.Background | ||
| 84 | - } | ||
| 85 | - hit.Flip(ray) | ||
| 86 | - mat := s.Materials[idx] | ||
| 87 | - | ||
| 88 | - // An emissive surface radiates its own light. | ||
| 89 | - col := mat.Emission | ||
| 90 | - | ||
| 91 | - // Specular: mirror the ray and keep going. | ||
| 92 | - if mat.Reflectivity > 0 { | ||
| 93 | - r := ray.Dir.Sub(hit.Normal.Scale(2 * ray.Dir.Dot(hit.Normal))).Norm() | ||
| 94 | - rr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: r} | ||
| 95 | - col = col.Add(p.trace(rr, depth+1, rng).Scale(mat.Reflectivity)) | ||
| 96 | - } | ||
| 97 | - | ||
| 98 | - // Glass: bend the ray through the surface and keep going. | ||
| 99 | - if mat.Transparency > 0 { | ||
| 100 | - ior := 1.0 | ||
| 101 | - if mat.IOR > 0 { | ||
| 102 | - ior = mat.IOR | ||
| 103 | - } | ||
| 104 | - if refr, ok := refract(ray.Dir, hit.Normal, 1/ior); ok { | ||
| 105 | - tr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: refr} | ||
| 106 | - col = col.Add(p.trace(tr, depth+1, rng).Scale(mat.Transparency)) | ||
| 107 | - } | ||
| 108 | - } | ||
| 109 | - | ||
| 110 | - // Diffuse: scatter in a cosine-weighted random direction. This is | ||
| 111 | - // what carries light from one surface to another (global | ||
| 112 | - // illumination). | ||
| 113 | - if depth < 8 && (mat.Diffuse.R > 0 || mat.Diffuse.G > 0 || mat.Diffuse.B > 0) { | ||
| 114 | - d := cosineSample(hit.Normal, rng) | ||
| 115 | - dr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: d} | ||
| 116 | - col = col.Add(p.trace(dr, depth+1, rng).Mul(mat.Diffuse)) | ||
| 117 | - } | ||
| 118 | - | ||
| 119 | - // Russian roulette: terminate the path with probability 1-max(col), | ||
| 120 | - // unbiased. | ||
| 121 | - q := math.Max(col.R, math.Max(col.G, col.B)) | ||
| 122 | - q = math.Min(1, math.Max(q, 0.05)) | ||
| 123 | - if rng.Float64() > q { | ||
| 124 | - return col | ||
| 125 | - } | ||
| 126 | - return col.Scale(1 / q) | ||
| 127 | -} | ||
| 128 | - | ||
| 129 | -// cosineSample returns a random direction in the hemisphere around n, | ||
| 130 | -// weighted by cos(theta) — the distribution a perfect diffuse surface | ||
| 131 | -// scatters into. | ||
| 132 | -func cosineSample(n Vec3, rng *rand.Rand) Vec3 { | ||
| 133 | - // A random point on the unit disk, mapped onto the hemisphere. | ||
| 134 | - r := math.Sqrt(rng.Float64()) | ||
| 135 | - phi := 2 * math.Pi * rng.Float64() | ||
| 136 | - rx, ry := r*math.Cos(phi), r*math.Sin(phi) | ||
| 137 | - rz := math.Sqrt(math.Max(0, 1-r*r)) | ||
| 138 | - // A basis with n as its z axis. | ||
| 139 | - var up Vec3 | ||
| 140 | - if math.Abs(n.Y) < 0.99 { | ||
| 141 | - up = Vec3{0, 1, 0} | ||
| 142 | - } else { | ||
| 143 | - up = Vec3{1, 0, 0} | ||
| 144 | - } | ||
| 145 | - u := n.Cross(up).Norm() | ||
| 146 | - v := n.Cross(u) | ||
| 147 | - return u.Scale(rx).Add(v.Scale(ry)).Add(n.Scale(rz)) | ||
| 148 | -} | ||
deleted
demos/qwen-style/workspace/png.go +0 -62 | deleted file mode 100644 | ||
| @@ -1,62 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import ( | |
| 4 | - "bytes" | |
| 5 | - "compress/zlib" | |
| 6 | - "encoding/binary" | |
| 7 | - "hash/crc32" | |
| 8 | - "os" | |
| 9 | -) | |
| 10 | - | |
| 11 | -// SavePNG writes the image as a PNG file (truecolor, 8 bits per channel). | |
| 12 | -// It uses only the standard library. | |
| 13 | -func (img *Image) SavePNG(path string) error { | |
| 14 | - var body bytes.Buffer | |
| 15 | - | |
| 16 | - // Signature. | |
| 17 | - body.Write([]byte{137, 80, 78, 71, 13, 10, 26, 10}) | |
| 18 | - | |
| 19 | - // IHDR: width, height, bit depth 8, color type 2 (RGB), | |
| 20 | - // compression 0, filter 0, interlace 0. | |
| 21 | - var ihdr [13]byte | |
| 22 | - binary.BigEndian.PutUint32(ihdr[0:4], uint32(img.Width)) | |
| 23 | - binary.BigEndian.PutUint32(ihdr[4:8], uint32(img.Height)) | |
| 24 | - ihdr[8] = 8 // bit depth | |
| 25 | - ihdr[9] = 2 // color type: truecolor | |
| 26 | - body.Write(pngChunk("IHDR", ihdr[:])) | |
| 27 | - | |
| 28 | - // IDAT: each scanline prefixed with filter byte 0, then zlib-compressed. | |
| 29 | - raw := make([]byte, 0, img.Height*(1+img.Width*3)) | |
| 30 | - for y := 0; y < img.Height; y++ { | |
| 31 | - raw = append(raw, 0) | |
| 32 | - row := img.Pix[y*img.Width*3 : (y+1)*img.Width*3] | |
| 33 | - raw = append(raw, row...) | |
| 34 | - } | |
| 35 | - var compressed bytes.Buffer | |
| 36 | - zw, err := zlib.NewWriterLevel(&compressed, zlib.DefaultCompression) | |
| 37 | - if err != nil { | |
| 38 | - return err | |
| 39 | - } | |
| 40 | - if _, err := zw.Write(raw); err != nil { | |
| 41 | - return err | |
| 42 | - } | |
| 43 | - if err := zw.Close(); err != nil { | |
| 44 | - return err | |
| 45 | - } | |
| 46 | - body.Write(pngChunk("IDAT", compressed.Bytes())) | |
| 47 | - | |
| 48 | - // IEND. | |
| 49 | - body.Write(pngChunk("IEND", nil)) | |
| 50 | - | |
| 51 | - return os.WriteFile(path, body.Bytes(), 0o644) | |
| 52 | -} | |
| 53 | - | |
| 54 | -// pngChunk builds a PNG chunk: 4-byte length, type, data, CRC32(type+data). | |
| 55 | -func pngChunk(typ string, data []byte) []byte { | |
| 56 | - out := make([]byte, 12+len(data)) | |
| 57 | - binary.BigEndian.PutUint32(out[0:4], uint32(len(data))) | |
| 58 | - copy(out[4:8], typ) | |
| 59 | - copy(out[8:], data) | |
| 60 | - binary.BigEndian.PutUint32(out[8+len(data):], crc32.ChecksumIEEE(out[4:8+len(data)])) | |
| 61 | - return out | |
| 62 | -} | |
| deleted file mode 100644 | |||
| @@ -1,62 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import ( | ||
| 4 | - "bytes" | ||
| 5 | - "compress/zlib" | ||
| 6 | - "encoding/binary" | ||
| 7 | - "hash/crc32" | ||
| 8 | - "os" | ||
| 9 | -) | ||
| 10 | - | ||
| 11 | -// SavePNG writes the image as a PNG file (truecolor, 8 bits per channel). | ||
| 12 | -// It uses only the standard library. | ||
| 13 | -func (img *Image) SavePNG(path string) error { | ||
| 14 | - var body bytes.Buffer | ||
| 15 | - | ||
| 16 | - // Signature. | ||
| 17 | - body.Write([]byte{137, 80, 78, 71, 13, 10, 26, 10}) | ||
| 18 | - | ||
| 19 | - // IHDR: width, height, bit depth 8, color type 2 (RGB), | ||
| 20 | - // compression 0, filter 0, interlace 0. | ||
| 21 | - var ihdr [13]byte | ||
| 22 | - binary.BigEndian.PutUint32(ihdr[0:4], uint32(img.Width)) | ||
| 23 | - binary.BigEndian.PutUint32(ihdr[4:8], uint32(img.Height)) | ||
| 24 | - ihdr[8] = 8 // bit depth | ||
| 25 | - ihdr[9] = 2 // color type: truecolor | ||
| 26 | - body.Write(pngChunk("IHDR", ihdr[:])) | ||
| 27 | - | ||
| 28 | - // IDAT: each scanline prefixed with filter byte 0, then zlib-compressed. | ||
| 29 | - raw := make([]byte, 0, img.Height*(1+img.Width*3)) | ||
| 30 | - for y := 0; y < img.Height; y++ { | ||
| 31 | - raw = append(raw, 0) | ||
| 32 | - row := img.Pix[y*img.Width*3 : (y+1)*img.Width*3] | ||
| 33 | - raw = append(raw, row...) | ||
| 34 | - } | ||
| 35 | - var compressed bytes.Buffer | ||
| 36 | - zw, err := zlib.NewWriterLevel(&compressed, zlib.DefaultCompression) | ||
| 37 | - if err != nil { | ||
| 38 | - return err | ||
| 39 | - } | ||
| 40 | - if _, err := zw.Write(raw); err != nil { | ||
| 41 | - return err | ||
| 42 | - } | ||
| 43 | - if err := zw.Close(); err != nil { | ||
| 44 | - return err | ||
| 45 | - } | ||
| 46 | - body.Write(pngChunk("IDAT", compressed.Bytes())) | ||
| 47 | - | ||
| 48 | - // IEND. | ||
| 49 | - body.Write(pngChunk("IEND", nil)) | ||
| 50 | - | ||
| 51 | - return os.WriteFile(path, body.Bytes(), 0o644) | ||
| 52 | -} | ||
| 53 | - | ||
| 54 | -// pngChunk builds a PNG chunk: 4-byte length, type, data, CRC32(type+data). | ||
| 55 | -func pngChunk(typ string, data []byte) []byte { | ||
| 56 | - out := make([]byte, 12+len(data)) | ||
| 57 | - binary.BigEndian.PutUint32(out[0:4], uint32(len(data))) | ||
| 58 | - copy(out[4:8], typ) | ||
| 59 | - copy(out[8:], data) | ||
| 60 | - binary.BigEndian.PutUint32(out[8+len(data):], crc32.ChecksumIEEE(out[4:8+len(data)])) | ||
| 61 | - return out | ||
| 62 | -} | ||
deleted
demos/qwen-style/workspace/povray_test.go +0 -336 | deleted file mode 100644 | ||
| @@ -1,336 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import ( | |
| 4 | - "bytes" | |
| 5 | - "encoding/binary" | |
| 6 | - "math" | |
| 7 | - "os" | |
| 8 | - "testing" | |
| 9 | -) | |
| 10 | - | |
| 11 | -func TestSphereIntersect(t *testing.T) { | |
| 12 | - s := Sphere{Center: Vec3{0, 0, 0}, Radius: 1} | |
| 13 | - // Head-on hit. | |
| 14 | - h, ok := s.Intersect(Ray{Origin: Vec3{0, 0, 5}, Dir: Vec3{0, 0, -1}}) | |
| 15 | - if !ok { | |
| 16 | - t.Fatal("expected a hit") | |
| 17 | - } | |
| 18 | - if h.Dist != 4 { | |
| 19 | - t.Errorf("dist = %v, want 4", h.Dist) | |
| 20 | - } | |
| 21 | - if h.Point.Z != 1 { | |
| 22 | - t.Errorf("point = %v, want z=1", h.Point) | |
| 23 | - } | |
| 24 | - // Miss. | |
| 25 | - if _, ok := s.Intersect(Ray{Origin: Vec3{0, 5, 5}, Dir: Vec3{0, 0, -1}}); ok { | |
| 26 | - t.Error("expected a miss") | |
| 27 | - } | |
| 28 | - // Ray starting inside: should still hit the far side. | |
| 29 | - h, ok = s.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 0, -1}}) | |
| 30 | - if !ok || h.Dist <= 0 { | |
| 31 | - t.Errorf("inside hit = %+v, %v", h, ok) | |
| 32 | - } | |
| 33 | -} | |
| 34 | - | |
| 35 | -func TestPlaneIntersect(t *testing.T) { | |
| 36 | - p := NewPlaneXY(2) | |
| 37 | - h, ok := p.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 1, 0}}) | |
| 38 | - if !ok || h.Dist != 2 { | |
| 39 | - t.Errorf("hit = %+v, %v", h, ok) | |
| 40 | - } | |
| 41 | - // Parallel ray misses. | |
| 42 | - if _, ok := p.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{1, 0, 0}}); ok { | |
| 43 | - t.Error("expected a miss for parallel ray") | |
| 44 | - } | |
| 45 | -} | |
| 46 | - | |
| 47 | -func TestBoxIntersect(t *testing.T) { | |
| 48 | - b := Box{Min: Vec3{-1, -1, -1}, Max: Vec3{1, 1, 1}} | |
| 49 | - h, ok := b.Intersect(Ray{Origin: Vec3{0, 0, 5}, Dir: Vec3{0, 0, -1}}) | |
| 50 | - if !ok || h.Dist != 4 || h.Normal.Z != 1 { | |
| 51 | - t.Errorf("hit = %+v, %v", h, ok) | |
| 52 | - } | |
| 53 | - if _, ok := b.Intersect(Ray{Origin: Vec3{5, 0, 0}, Dir: Vec3{0, 0, -1}}); ok { | |
| 54 | - t.Error("expected a miss") | |
| 55 | - } | |
| 56 | -} | |
| 57 | - | |
| 58 | -func TestGroupClosest(t *testing.T) { | |
| 59 | - g := Group{Objects: []Object{ | |
| 60 | - Sphere{Center: Vec3{0, 0, -2}, Radius: 0.5}, | |
| 61 | - Sphere{Center: Vec3{0, 0, -5}, Radius: 0.5}, | |
| 62 | - }} | |
| 63 | - h, ok := g.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 0, -1}}) | |
| 64 | - if !ok || h.Dist != 1.5 { | |
| 65 | - t.Errorf("expected closest hit at 1.5, got %+v, %v", h, ok) | |
| 66 | - } | |
| 67 | -} | |
| 68 | - | |
| 69 | -func TestRefract(t *testing.T) { | |
| 70 | - // Straight through: direction parallel to normal is unchanged. | |
| 71 | - d, ok := refract(Vec3{0, 0, -1}, Vec3{0, 0, 1}, 1.5) | |
| 72 | - if !ok || d.Z >= 0 { | |
| 73 | - t.Errorf("got %+v, %v", d, ok) | |
| 74 | - } | |
| 75 | - // Grazing incidence: total internal reflection. | |
| 76 | - if _, ok := refract(Vec3{0.999, 0, -0.04}, Vec3{0, 0, 1}, 1.5); ok { | |
| 77 | - t.Error("expected total internal reflection") | |
| 78 | - } | |
| 79 | -} | |
| 80 | - | |
| 81 | -func TestTransformMat4(t *testing.T) { | |
| 82 | - // Identity leaves points and dirs alone. | |
| 83 | - if p := Identity().TransformPoint(Vec3{1, 2, 3}); p != (Vec3{1, 2, 3}) { | |
| 84 | - t.Errorf("identity point = %v", p) | |
| 85 | - } | |
| 86 | - // Translate moves a point, not a direction. | |
| 87 | - m := Translate(1, -2, 3) | |
| 88 | - if p := m.TransformPoint(Vec3{0, 0, 0}); p != (Vec3{1, -2, 3}) { | |
| 89 | - t.Errorf("translate point = %v", p) | |
| 90 | - } | |
| 91 | - if d := m.TransformDir(Vec3{1, 0, 0}); d != (Vec3{1, 0, 0}) { | |
| 92 | - t.Errorf("translate dir = %v", d) | |
| 93 | - } | |
| 94 | - // Scale stretches a direction. | |
| 95 | - if d := Scale(2, 3, 4).TransformDir(Vec3{1, 1, 1}); d != (Vec3{2, 3, 4}) { | |
| 96 | - t.Errorf("scale dir = %v", d) | |
| 97 | - } | |
| 98 | - // RotateY by pi/2 sends +Z to +X. | |
| 99 | - d := RotateY(math.Pi / 2).TransformDir(Vec3{0, 0, 1}) | |
| 100 | - if math.Abs(d.X-1) > 1e-9 || math.Abs(d.Z) > 1e-9 { | |
| 101 | - t.Errorf("rotateY dir = %v", d) | |
| 102 | - } | |
| 103 | - // Multiply: translate then scale (scale first, then translate). | |
| 104 | - m = Translate(1, 0, 0).Multiply(Scale(2, 2, 2)) | |
| 105 | - if p := m.TransformPoint(Vec3{1, 0, 0}); p != (Vec3{3, 0, 0}) { | |
| 106 | - t.Errorf("composed point = %v", p) | |
| 107 | - } | |
| 108 | - // Inverse round-trips a point; singular matrix reports failure. | |
| 109 | - m = Translate(1, 2, 3).Multiply(RotateX(0.7)).Multiply(Scale(2, 3, 4)) | |
| 110 | - inv, ok := m.Inverse() | |
| 111 | - if !ok { | |
| 112 | - t.Fatal("expected an inverse") | |
| 113 | - } | |
| 114 | - p := Vec3{1.3, -0.4, 2.2} | |
| 115 | - if rt := inv.TransformPoint(m.TransformPoint(p)); rt.Sub(p).Len() > 1e-9 { | |
| 116 | - t.Errorf("round trip = %v", rt) | |
| 117 | - } | |
| 118 | - if _, ok := Scale(0, 1, 1).Inverse(); ok { | |
| 119 | - t.Error("expected singular matrix") | |
| 120 | - } | |
| 121 | - // Non-uniform scale: normals go through the inverse transpose. | |
| 122 | - // A sphere scaled by (2,1,1) is the ellipsoid x²/4+y²+z²=1; at the | |
| 123 | - // point where the local normal is (1,1,0), the world normal is | |
| 124 | - // (0.5,1,0)/|(0.5,1,0)| = (0.447, 0.894, 0). | |
| 125 | - n := Scale(2, 1, 1).TransformNormal(Vec3{1, 1, 0}) | |
| 126 | - want := Vec3{0.5, 1, 0}.Norm() | |
| 127 | - if n.Sub(want).Len() > 1e-9 { | |
| 128 | - t.Errorf("scaled normal = %v, want %v", n, want) | |
| 129 | - } | |
| 130 | -} | |
| 131 | - | |
| 132 | -func TestTransformObject(t *testing.T) { | |
| 133 | - // A translated sphere: hit at the shifted point, normal preserved. | |
| 134 | - s := Transform{Object: Sphere{Center: Vec3{0, 0, 0}, Radius: 1}, Matrix: Translate(0, 0, -5)} | |
| 135 | - h, ok := s.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 0, -1}}) | |
| 136 | - if !ok || math.Abs(h.Dist-4) > 1e-6 || h.Point.Z != -4 { | |
| 137 | - t.Errorf("hit = %+v, %v", h, ok) | |
| 138 | - } | |
| 139 | - if h.Normal.Z != 1 { | |
| 140 | - t.Errorf("normal = %v, want (0,0,1)", h.Normal) | |
| 141 | - } | |
| 142 | - // A box rotated 45° around Z is symmetric about the Z axis, so a | |
| 143 | - // head-on -Z ray still hits the front face at the same distance. | |
| 144 | - b := Transform{Object: Box{Min: Vec3{-1, -1, -1}, Max: Vec3{1, 1, 1}}, Matrix: RotateZ(math.Pi / 4)} | |
| 145 | - h, ok = b.Intersect(Ray{Origin: Vec3{0, 0, 5}, Dir: Vec3{0, 0, -1}}) | |
| 146 | - if !ok || math.Abs(h.Dist-4) > 1e-6 { | |
| 147 | - t.Errorf("rotated box hit = %+v, %v", h, ok) | |
| 148 | - } | |
| 149 | - // Rotate the box 45° around X. A -Z ray at y=0.5 strikes the tilted | |
| 150 | - // former top face (local y=1); its world normal is RotateX(45°)·(0,1,0) | |
| 151 | - // = (0, cos45, sin45). | |
| 152 | - b = Transform{Object: Box{Min: Vec3{-1, -1, -1}, Max: Vec3{1, 1, 1}}, Matrix: RotateX(math.Pi / 4)} | |
| 153 | - h, ok = b.Intersect(Ray{Origin: Vec3{0, 0.5, 5}, Dir: Vec3{0, 0, -1}}) | |
| 154 | - if !ok { | |
| 155 | - t.Fatal("expected a hit on the rotated box") | |
| 156 | - } | |
| 157 | - if math.Abs(h.Normal.Y-math.Sqrt2/2) > 1e-6 || math.Abs(h.Normal.Z-math.Sqrt2/2) > 1e-6 { | |
| 158 | - t.Errorf("rotated normal = %v, want (0, 0.707, 0.707)", h.Normal) | |
| 159 | - } | |
| 160 | - // Miss: a ray that misses the local object misses the transformed one. | |
| 161 | - if _, ok := s.Intersect(Ray{Origin: Vec3{3, 0, 0}, Dir: Vec3{0, 0, -1}}); ok { | |
| 162 | - t.Error("expected a miss") | |
| 163 | - } | |
| 164 | -} | |
| 165 | - | |
| 166 | -func TestRayAtUV(t *testing.T) { | |
| 167 | - cam := NewCamera(Vec3{0, 0, 5}) | |
| 168 | - // The center of an odd-sized image aims straight at the look-at point. | |
| 169 | - c := cam.RayAt(5, 5, 11, 11) | |
| 170 | - if math.Abs(c.Dir.X) > 1e-9 || math.Abs(c.Dir.Y) > 1e-9 || c.Dir.Z > 0 { | |
| 171 | - t.Errorf("center ray = %v, want (0,0,-1)", c.Dir) | |
| 172 | - } | |
| 173 | - // RayAt(px, py) is the center of the pixel: same as RayAtUV(px+0.5, py+0.5). | |
| 174 | - c2 := cam.RayAtUV(5.5, 5.5, 11, 11) | |
| 175 | - if c.Dir.Sub(c2.Dir).Len() > 1e-12 { | |
| 176 | - t.Errorf("RayAt != RayAtUV center: %v vs %v", c.Dir, c2.Dir) | |
| 177 | - } | |
| 178 | - // A sample in the top-left quadrant points up and left. | |
| 179 | - tl := cam.RayAtUV(0.25, 0.25, 11, 11) | |
| 180 | - if tl.Dir.X >= 0 || tl.Dir.Y <= 0 { | |
| 181 | - t.Errorf("top-left sample = %v, want X<0, Y>0", tl.Dir) | |
| 182 | - } | |
| 183 | - // A sample in the bottom-right quadrant points down and right. | |
| 184 | - br := cam.RayAtUV(10.75, 10.75, 11, 11) | |
| 185 | - if br.Dir.X <= 0 || br.Dir.Y >= 0 { | |
| 186 | - t.Errorf("bottom-right sample = %v, want X>0, Y<0", br.Dir) | |
| 187 | - } | |
| 188 | -} | |
| 189 | - | |
| 190 | -func TestSupersampling(t *testing.T) { | |
| 191 | - // A scene with a hard edge: a red sphere against a mid-gray | |
| 192 | - // background. With 1 sample every pixel is pure red or pure gray; | |
| 193 | - // with 2x2 samples the silhouette pixels must be a blend. | |
| 194 | - build := func() *Scene { | |
| 195 | - s := NewScene() | |
| 196 | - s.AddObject(Sphere{Center: Vec3{0, 0, 0}, Radius: 1}, | |
| 197 | - Material{Diffuse: Color{1, 0, 0}, Ambient: 1}) | |
| 198 | - s.Background = Color{0.5, 0.5, 0.5} | |
| 199 | - return s | |
| 200 | - } | |
| 201 | - cam := NewCamera(Vec3{0, 0, 5}) | |
| 202 | - | |
| 203 | - // Count pixels in row 20 (where the sphere's silhouette has a steep | |
| 204 | - // slope, so a pixel's 2x2 sub-samples straddle the edge) whose green | |
| 205 | - // channel is strictly between the sphere (pure red, G=0) and the | |
| 206 | - // background (gray, G≈186 after gamma): a "mixed" pixel is an | |
| 207 | - // anti-aliased edge. | |
| 208 | - mixed := func(img *Image) int { | |
| 209 | - var n int | |
| 210 | - for x := 0; x < img.Width; x++ { | |
| 211 | - i := (20*img.Width + x) * 3 | |
| 212 | - if img.Pix[i+1] > 5 && img.Pix[i+1] < 181 { | |
| 213 | - n++ | |
| 214 | - } | |
| 215 | - } | |
| 216 | - return n | |
| 217 | - } | |
| 218 | - | |
| 219 | - r1 := Renderer{Scene: build(), Width: 64, Height: 48, Samples: 1} | |
| 220 | - if m := mixed(r1.Render(cam)); m > 0 { | |
| 221 | - t.Errorf("1-sample render has %d mixed pixels, want 0", m) | |
| 222 | - } | |
| 223 | - | |
| 224 | - r2 := Renderer{Scene: build(), Width: 64, Height: 48, Samples: 2} | |
| 225 | - if m := mixed(r2.Render(cam)); m == 0 { | |
| 226 | - t.Error("2x2 render has no mixed pixels: edge not anti-aliased") | |
| 227 | - } | |
| 228 | -} | |
| 229 | - | |
| 230 | -func TestPathTracer(t *testing.T) { | |
| 231 | - // A room: white floor, a sphere, and an emissive panel above. | |
| 232 | - // Path tracing must light the floor from the panel, and the floor | |
| 233 | - // behind the sphere (toward the camera) must be in shadow. | |
| 234 | - build := func() *Scene { | |
| 235 | - s := NewScene() | |
| 236 | - s.Background = Black | |
| 237 | - s.AddObject(NewPlaneXY(0), Material{Diffuse: White}) | |
| 238 | - s.AddObject(Sphere{Center: Vec3{0, 1, 0}, Radius: 1}, Material{Diffuse: White}) | |
| 239 | - // A downward-facing emissive panel. | |
| 240 | - panel := Transform{ | |
| 241 | - Object: Box{Min: Vec3{-2, 0, -2}, Max: Vec3{2, 0.1, 2}}, | |
| 242 | - Matrix: Translate(0, 8, 0), | |
| 243 | - } | |
| 244 | - s.AddObject(panel, LightMaterial(White, 12)) | |
| 245 | - return s | |
| 246 | - } | |
| 247 | - cam := NewCamera(Vec3{0, 2, 8}) | |
| 248 | - p := PathTracer{Scene: build(), Width: 64, Height: 48, Samples: 8} | |
| 249 | - img := p.Render(cam) | |
| 250 | - | |
| 251 | - // The floor occupies the bottom half of the image. It must be | |
| 252 | - // lit (not black) and must have a bright region (direct light from | |
| 253 | - // the panel) and a dark region (the sphere's shadow). | |
| 254 | - var min, max byte | |
| 255 | - for y := 24; y < 48; y++ { | |
| 256 | - for x := 0; x < 64; x++ { | |
| 257 | - v := img.Pix[(y*64+x)*3] | |
| 258 | - if x == 0 || v < min { | |
| 259 | - min = v | |
| 260 | - } | |
| 261 | - if x == 0 || v > max { | |
| 262 | - max = v | |
| 263 | - } | |
| 264 | - } | |
| 265 | - } | |
| 266 | - if max < 40 { | |
| 267 | - t.Errorf("floor not lit by the panel: max = %d", max) | |
| 268 | - } | |
| 269 | - if float64(max) < 2*float64(min+1) { | |
| 270 | - t.Errorf("no shadow visible: min = %d, max = %d", min, max) | |
| 271 | - } | |
| 272 | - | |
| 273 | - // Determinism: the same configuration must give the same image. | |
| 274 | - img2 := p.Render(cam) | |
| 275 | - if !bytes.Equal(img.Pix, img2.Pix) { | |
| 276 | - t.Error("two renders of the same scene differ") | |
| 277 | - } | |
| 278 | -} | |
| 279 | - | |
| 280 | -func TestRenderSmoke(t *testing.T) { | |
| 281 | - s := NewScene() | |
| 282 | - s.AddObject(Sphere{Center: Vec3{0, 1, 0}, Radius: 1}, | |
| 283 | - Material{Diffuse: Red, Specular: White, Shininess: 30, Ambient: 0.1}) | |
| 284 | - s.AddObject(NewPlaneXY(0), Material{Diffuse: Gray, Ambient: 0.3}) | |
| 285 | - s.Lights = []Light{NewLight(Vec3{4, 8, 6}, White)} | |
| 286 | - | |
| 287 | - cam := NewCamera(Vec3{0, 2, 8}) | |
| 288 | - r := Renderer{Scene: s, Width: 64, Height: 48} | |
| 289 | - img := r.Render(cam) | |
| 290 | - if img.Width != 64 || img.Height != 48 { | |
| 291 | - t.Fatalf("image size = %dx%d", img.Width, img.Height) | |
| 292 | - } | |
| 293 | - // The center pixel should hit the red sphere: red channel dominant. | |
| 294 | - i := (24*64 + 32) * 3 | |
| 295 | - if img.Pix[i] < 60 { | |
| 296 | - t.Errorf("center pixel too dark: %v", img.Pix[i:i+3]) | |
| 297 | - } | |
| 298 | - if img.Pix[i+1] > img.Pix[i] || img.Pix[i+2] > img.Pix[i] { | |
| 299 | - t.Errorf("center pixel not red-dominant: %v", img.Pix[i:i+3]) | |
| 300 | - } | |
| 301 | -} | |
| 302 | - | |
| 303 | -func TestSavePNG(t *testing.T) { | |
| 304 | - img := NewImage(4, 3) | |
| 305 | - for y := 0; y < 3; y++ { | |
| 306 | - for x := 0; x < 4; x++ { | |
| 307 | - img.Set(x, y, Color{float64(x) / 3, float64(y) / 2, 1}) | |
| 308 | - } | |
| 309 | - } | |
| 310 | - path := t.TempDir() + "/test.png" | |
| 311 | - if err := img.SavePNG(path); err != nil { | |
| 312 | - t.Fatal(err) | |
| 313 | - } | |
| 314 | - data, err := os.ReadFile(path) | |
| 315 | - if err != nil { | |
| 316 | - t.Fatal(err) | |
| 317 | - } | |
| 318 | - // PNG signature. | |
| 319 | - sig := []byte{137, 80, 78, 71, 13, 10, 26, 10} | |
| 320 | - if !bytes.Equal(data[:8], sig) { | |
| 321 | - t.Fatalf("bad signature: %v", data[:8]) | |
| 322 | - } | |
| 323 | - // IHDR must say 4x3, 8-bit truecolor. | |
| 324 | - if binary.BigEndian.Uint32(data[16:20]) != 4 || binary.BigEndian.Uint32(data[20:24]) != 3 { | |
| 325 | - t.Errorf("IHDR size wrong: %v", data[16:24]) | |
| 326 | - } | |
| 327 | - if data[24] != 8 || data[25] != 2 { | |
| 328 | - t.Errorf("IHDR depth/type wrong: %d %d", data[24], data[25]) | |
| 329 | - } | |
| 330 | - // The file must end with an IEND chunk: length 0, "IEND", CRC AE426082. | |
| 331 | - if !bytes.Equal(data[len(data)-8:len(data)-4], []byte("IEND")) || | |
| 332 | - !bytes.Equal(data[len(data)-4:], []byte{0xAE, 0x42, 0x60, 0x82}) || | |
| 333 | - !bytes.Equal(data[len(data)-12:len(data)-8], []byte{0, 0, 0, 0}) { | |
| 334 | - t.Errorf("missing IEND trailer: %v", data[len(data)-12:]) | |
| 335 | - } | |
| 336 | -} | |
| deleted file mode 100644 | |||
| @@ -1,336 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import ( | ||
| 4 | - "bytes" | ||
| 5 | - "encoding/binary" | ||
| 6 | - "math" | ||
| 7 | - "os" | ||
| 8 | - "testing" | ||
| 9 | -) | ||
| 10 | - | ||
| 11 | -func TestSphereIntersect(t *testing.T) { | ||
| 12 | - s := Sphere{Center: Vec3{0, 0, 0}, Radius: 1} | ||
| 13 | - // Head-on hit. | ||
| 14 | - h, ok := s.Intersect(Ray{Origin: Vec3{0, 0, 5}, Dir: Vec3{0, 0, -1}}) | ||
| 15 | - if !ok { | ||
| 16 | - t.Fatal("expected a hit") | ||
| 17 | - } | ||
| 18 | - if h.Dist != 4 { | ||
| 19 | - t.Errorf("dist = %v, want 4", h.Dist) | ||
| 20 | - } | ||
| 21 | - if h.Point.Z != 1 { | ||
| 22 | - t.Errorf("point = %v, want z=1", h.Point) | ||
| 23 | - } | ||
| 24 | - // Miss. | ||
| 25 | - if _, ok := s.Intersect(Ray{Origin: Vec3{0, 5, 5}, Dir: Vec3{0, 0, -1}}); ok { | ||
| 26 | - t.Error("expected a miss") | ||
| 27 | - } | ||
| 28 | - // Ray starting inside: should still hit the far side. | ||
| 29 | - h, ok = s.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 0, -1}}) | ||
| 30 | - if !ok || h.Dist <= 0 { | ||
| 31 | - t.Errorf("inside hit = %+v, %v", h, ok) | ||
| 32 | - } | ||
| 33 | -} | ||
| 34 | - | ||
| 35 | -func TestPlaneIntersect(t *testing.T) { | ||
| 36 | - p := NewPlaneXY(2) | ||
| 37 | - h, ok := p.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 1, 0}}) | ||
| 38 | - if !ok || h.Dist != 2 { | ||
| 39 | - t.Errorf("hit = %+v, %v", h, ok) | ||
| 40 | - } | ||
| 41 | - // Parallel ray misses. | ||
| 42 | - if _, ok := p.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{1, 0, 0}}); ok { | ||
| 43 | - t.Error("expected a miss for parallel ray") | ||
| 44 | - } | ||
| 45 | -} | ||
| 46 | - | ||
| 47 | -func TestBoxIntersect(t *testing.T) { | ||
| 48 | - b := Box{Min: Vec3{-1, -1, -1}, Max: Vec3{1, 1, 1}} | ||
| 49 | - h, ok := b.Intersect(Ray{Origin: Vec3{0, 0, 5}, Dir: Vec3{0, 0, -1}}) | ||
| 50 | - if !ok || h.Dist != 4 || h.Normal.Z != 1 { | ||
| 51 | - t.Errorf("hit = %+v, %v", h, ok) | ||
| 52 | - } | ||
| 53 | - if _, ok := b.Intersect(Ray{Origin: Vec3{5, 0, 0}, Dir: Vec3{0, 0, -1}}); ok { | ||
| 54 | - t.Error("expected a miss") | ||
| 55 | - } | ||
| 56 | -} | ||
| 57 | - | ||
| 58 | -func TestGroupClosest(t *testing.T) { | ||
| 59 | - g := Group{Objects: []Object{ | ||
| 60 | - Sphere{Center: Vec3{0, 0, -2}, Radius: 0.5}, | ||
| 61 | - Sphere{Center: Vec3{0, 0, -5}, Radius: 0.5}, | ||
| 62 | - }} | ||
| 63 | - h, ok := g.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 0, -1}}) | ||
| 64 | - if !ok || h.Dist != 1.5 { | ||
| 65 | - t.Errorf("expected closest hit at 1.5, got %+v, %v", h, ok) | ||
| 66 | - } | ||
| 67 | -} | ||
| 68 | - | ||
| 69 | -func TestRefract(t *testing.T) { | ||
| 70 | - // Straight through: direction parallel to normal is unchanged. | ||
| 71 | - d, ok := refract(Vec3{0, 0, -1}, Vec3{0, 0, 1}, 1.5) | ||
| 72 | - if !ok || d.Z >= 0 { | ||
| 73 | - t.Errorf("got %+v, %v", d, ok) | ||
| 74 | - } | ||
| 75 | - // Grazing incidence: total internal reflection. | ||
| 76 | - if _, ok := refract(Vec3{0.999, 0, -0.04}, Vec3{0, 0, 1}, 1.5); ok { | ||
| 77 | - t.Error("expected total internal reflection") | ||
| 78 | - } | ||
| 79 | -} | ||
| 80 | - | ||
| 81 | -func TestTransformMat4(t *testing.T) { | ||
| 82 | - // Identity leaves points and dirs alone. | ||
| 83 | - if p := Identity().TransformPoint(Vec3{1, 2, 3}); p != (Vec3{1, 2, 3}) { | ||
| 84 | - t.Errorf("identity point = %v", p) | ||
| 85 | - } | ||
| 86 | - // Translate moves a point, not a direction. | ||
| 87 | - m := Translate(1, -2, 3) | ||
| 88 | - if p := m.TransformPoint(Vec3{0, 0, 0}); p != (Vec3{1, -2, 3}) { | ||
| 89 | - t.Errorf("translate point = %v", p) | ||
| 90 | - } | ||
| 91 | - if d := m.TransformDir(Vec3{1, 0, 0}); d != (Vec3{1, 0, 0}) { | ||
| 92 | - t.Errorf("translate dir = %v", d) | ||
| 93 | - } | ||
| 94 | - // Scale stretches a direction. | ||
| 95 | - if d := Scale(2, 3, 4).TransformDir(Vec3{1, 1, 1}); d != (Vec3{2, 3, 4}) { | ||
| 96 | - t.Errorf("scale dir = %v", d) | ||
| 97 | - } | ||
| 98 | - // RotateY by pi/2 sends +Z to +X. | ||
| 99 | - d := RotateY(math.Pi / 2).TransformDir(Vec3{0, 0, 1}) | ||
| 100 | - if math.Abs(d.X-1) > 1e-9 || math.Abs(d.Z) > 1e-9 { | ||
| 101 | - t.Errorf("rotateY dir = %v", d) | ||
| 102 | - } | ||
| 103 | - // Multiply: translate then scale (scale first, then translate). | ||
| 104 | - m = Translate(1, 0, 0).Multiply(Scale(2, 2, 2)) | ||
| 105 | - if p := m.TransformPoint(Vec3{1, 0, 0}); p != (Vec3{3, 0, 0}) { | ||
| 106 | - t.Errorf("composed point = %v", p) | ||
| 107 | - } | ||
| 108 | - // Inverse round-trips a point; singular matrix reports failure. | ||
| 109 | - m = Translate(1, 2, 3).Multiply(RotateX(0.7)).Multiply(Scale(2, 3, 4)) | ||
| 110 | - inv, ok := m.Inverse() | ||
| 111 | - if !ok { | ||
| 112 | - t.Fatal("expected an inverse") | ||
| 113 | - } | ||
| 114 | - p := Vec3{1.3, -0.4, 2.2} | ||
| 115 | - if rt := inv.TransformPoint(m.TransformPoint(p)); rt.Sub(p).Len() > 1e-9 { | ||
| 116 | - t.Errorf("round trip = %v", rt) | ||
| 117 | - } | ||
| 118 | - if _, ok := Scale(0, 1, 1).Inverse(); ok { | ||
| 119 | - t.Error("expected singular matrix") | ||
| 120 | - } | ||
| 121 | - // Non-uniform scale: normals go through the inverse transpose. | ||
| 122 | - // A sphere scaled by (2,1,1) is the ellipsoid x²/4+y²+z²=1; at the | ||
| 123 | - // point where the local normal is (1,1,0), the world normal is | ||
| 124 | - // (0.5,1,0)/|(0.5,1,0)| = (0.447, 0.894, 0). | ||
| 125 | - n := Scale(2, 1, 1).TransformNormal(Vec3{1, 1, 0}) | ||
| 126 | - want := Vec3{0.5, 1, 0}.Norm() | ||
| 127 | - if n.Sub(want).Len() > 1e-9 { | ||
| 128 | - t.Errorf("scaled normal = %v, want %v", n, want) | ||
| 129 | - } | ||
| 130 | -} | ||
| 131 | - | ||
| 132 | -func TestTransformObject(t *testing.T) { | ||
| 133 | - // A translated sphere: hit at the shifted point, normal preserved. | ||
| 134 | - s := Transform{Object: Sphere{Center: Vec3{0, 0, 0}, Radius: 1}, Matrix: Translate(0, 0, -5)} | ||
| 135 | - h, ok := s.Intersect(Ray{Origin: Vec3{0, 0, 0}, Dir: Vec3{0, 0, -1}}) | ||
| 136 | - if !ok || math.Abs(h.Dist-4) > 1e-6 || h.Point.Z != -4 { | ||
| 137 | - t.Errorf("hit = %+v, %v", h, ok) | ||
| 138 | - } | ||
| 139 | - if h.Normal.Z != 1 { | ||
| 140 | - t.Errorf("normal = %v, want (0,0,1)", h.Normal) | ||
| 141 | - } | ||
| 142 | - // A box rotated 45° around Z is symmetric about the Z axis, so a | ||
| 143 | - // head-on -Z ray still hits the front face at the same distance. | ||
| 144 | - b := Transform{Object: Box{Min: Vec3{-1, -1, -1}, Max: Vec3{1, 1, 1}}, Matrix: RotateZ(math.Pi / 4)} | ||
| 145 | - h, ok = b.Intersect(Ray{Origin: Vec3{0, 0, 5}, Dir: Vec3{0, 0, -1}}) | ||
| 146 | - if !ok || math.Abs(h.Dist-4) > 1e-6 { | ||
| 147 | - t.Errorf("rotated box hit = %+v, %v", h, ok) | ||
| 148 | - } | ||
| 149 | - // Rotate the box 45° around X. A -Z ray at y=0.5 strikes the tilted | ||
| 150 | - // former top face (local y=1); its world normal is RotateX(45°)·(0,1,0) | ||
| 151 | - // = (0, cos45, sin45). | ||
| 152 | - b = Transform{Object: Box{Min: Vec3{-1, -1, -1}, Max: Vec3{1, 1, 1}}, Matrix: RotateX(math.Pi / 4)} | ||
| 153 | - h, ok = b.Intersect(Ray{Origin: Vec3{0, 0.5, 5}, Dir: Vec3{0, 0, -1}}) | ||
| 154 | - if !ok { | ||
| 155 | - t.Fatal("expected a hit on the rotated box") | ||
| 156 | - } | ||
| 157 | - if math.Abs(h.Normal.Y-math.Sqrt2/2) > 1e-6 || math.Abs(h.Normal.Z-math.Sqrt2/2) > 1e-6 { | ||
| 158 | - t.Errorf("rotated normal = %v, want (0, 0.707, 0.707)", h.Normal) | ||
| 159 | - } | ||
| 160 | - // Miss: a ray that misses the local object misses the transformed one. | ||
| 161 | - if _, ok := s.Intersect(Ray{Origin: Vec3{3, 0, 0}, Dir: Vec3{0, 0, -1}}); ok { | ||
| 162 | - t.Error("expected a miss") | ||
| 163 | - } | ||
| 164 | -} | ||
| 165 | - | ||
| 166 | -func TestRayAtUV(t *testing.T) { | ||
| 167 | - cam := NewCamera(Vec3{0, 0, 5}) | ||
| 168 | - // The center of an odd-sized image aims straight at the look-at point. | ||
| 169 | - c := cam.RayAt(5, 5, 11, 11) | ||
| 170 | - if math.Abs(c.Dir.X) > 1e-9 || math.Abs(c.Dir.Y) > 1e-9 || c.Dir.Z > 0 { | ||
| 171 | - t.Errorf("center ray = %v, want (0,0,-1)", c.Dir) | ||
| 172 | - } | ||
| 173 | - // RayAt(px, py) is the center of the pixel: same as RayAtUV(px+0.5, py+0.5). | ||
| 174 | - c2 := cam.RayAtUV(5.5, 5.5, 11, 11) | ||
| 175 | - if c.Dir.Sub(c2.Dir).Len() > 1e-12 { | ||
| 176 | - t.Errorf("RayAt != RayAtUV center: %v vs %v", c.Dir, c2.Dir) | ||
| 177 | - } | ||
| 178 | - // A sample in the top-left quadrant points up and left. | ||
| 179 | - tl := cam.RayAtUV(0.25, 0.25, 11, 11) | ||
| 180 | - if tl.Dir.X >= 0 || tl.Dir.Y <= 0 { | ||
| 181 | - t.Errorf("top-left sample = %v, want X<0, Y>0", tl.Dir) | ||
| 182 | - } | ||
| 183 | - // A sample in the bottom-right quadrant points down and right. | ||
| 184 | - br := cam.RayAtUV(10.75, 10.75, 11, 11) | ||
| 185 | - if br.Dir.X <= 0 || br.Dir.Y >= 0 { | ||
| 186 | - t.Errorf("bottom-right sample = %v, want X>0, Y<0", br.Dir) | ||
| 187 | - } | ||
| 188 | -} | ||
| 189 | - | ||
| 190 | -func TestSupersampling(t *testing.T) { | ||
| 191 | - // A scene with a hard edge: a red sphere against a mid-gray | ||
| 192 | - // background. With 1 sample every pixel is pure red or pure gray; | ||
| 193 | - // with 2x2 samples the silhouette pixels must be a blend. | ||
| 194 | - build := func() *Scene { | ||
| 195 | - s := NewScene() | ||
| 196 | - s.AddObject(Sphere{Center: Vec3{0, 0, 0}, Radius: 1}, | ||
| 197 | - Material{Diffuse: Color{1, 0, 0}, Ambient: 1}) | ||
| 198 | - s.Background = Color{0.5, 0.5, 0.5} | ||
| 199 | - return s | ||
| 200 | - } | ||
| 201 | - cam := NewCamera(Vec3{0, 0, 5}) | ||
| 202 | - | ||
| 203 | - // Count pixels in row 20 (where the sphere's silhouette has a steep | ||
| 204 | - // slope, so a pixel's 2x2 sub-samples straddle the edge) whose green | ||
| 205 | - // channel is strictly between the sphere (pure red, G=0) and the | ||
| 206 | - // background (gray, G≈186 after gamma): a "mixed" pixel is an | ||
| 207 | - // anti-aliased edge. | ||
| 208 | - mixed := func(img *Image) int { | ||
| 209 | - var n int | ||
| 210 | - for x := 0; x < img.Width; x++ { | ||
| 211 | - i := (20*img.Width + x) * 3 | ||
| 212 | - if img.Pix[i+1] > 5 && img.Pix[i+1] < 181 { | ||
| 213 | - n++ | ||
| 214 | - } | ||
| 215 | - } | ||
| 216 | - return n | ||
| 217 | - } | ||
| 218 | - | ||
| 219 | - r1 := Renderer{Scene: build(), Width: 64, Height: 48, Samples: 1} | ||
| 220 | - if m := mixed(r1.Render(cam)); m > 0 { | ||
| 221 | - t.Errorf("1-sample render has %d mixed pixels, want 0", m) | ||
| 222 | - } | ||
| 223 | - | ||
| 224 | - r2 := Renderer{Scene: build(), Width: 64, Height: 48, Samples: 2} | ||
| 225 | - if m := mixed(r2.Render(cam)); m == 0 { | ||
| 226 | - t.Error("2x2 render has no mixed pixels: edge not anti-aliased") | ||
| 227 | - } | ||
| 228 | -} | ||
| 229 | - | ||
| 230 | -func TestPathTracer(t *testing.T) { | ||
| 231 | - // A room: white floor, a sphere, and an emissive panel above. | ||
| 232 | - // Path tracing must light the floor from the panel, and the floor | ||
| 233 | - // behind the sphere (toward the camera) must be in shadow. | ||
| 234 | - build := func() *Scene { | ||
| 235 | - s := NewScene() | ||
| 236 | - s.Background = Black | ||
| 237 | - s.AddObject(NewPlaneXY(0), Material{Diffuse: White}) | ||
| 238 | - s.AddObject(Sphere{Center: Vec3{0, 1, 0}, Radius: 1}, Material{Diffuse: White}) | ||
| 239 | - // A downward-facing emissive panel. | ||
| 240 | - panel := Transform{ | ||
| 241 | - Object: Box{Min: Vec3{-2, 0, -2}, Max: Vec3{2, 0.1, 2}}, | ||
| 242 | - Matrix: Translate(0, 8, 0), | ||
| 243 | - } | ||
| 244 | - s.AddObject(panel, LightMaterial(White, 12)) | ||
| 245 | - return s | ||
| 246 | - } | ||
| 247 | - cam := NewCamera(Vec3{0, 2, 8}) | ||
| 248 | - p := PathTracer{Scene: build(), Width: 64, Height: 48, Samples: 8} | ||
| 249 | - img := p.Render(cam) | ||
| 250 | - | ||
| 251 | - // The floor occupies the bottom half of the image. It must be | ||
| 252 | - // lit (not black) and must have a bright region (direct light from | ||
| 253 | - // the panel) and a dark region (the sphere's shadow). | ||
| 254 | - var min, max byte | ||
| 255 | - for y := 24; y < 48; y++ { | ||
| 256 | - for x := 0; x < 64; x++ { | ||
| 257 | - v := img.Pix[(y*64+x)*3] | ||
| 258 | - if x == 0 || v < min { | ||
| 259 | - min = v | ||
| 260 | - } | ||
| 261 | - if x == 0 || v > max { | ||
| 262 | - max = v | ||
| 263 | - } | ||
| 264 | - } | ||
| 265 | - } | ||
| 266 | - if max < 40 { | ||
| 267 | - t.Errorf("floor not lit by the panel: max = %d", max) | ||
| 268 | - } | ||
| 269 | - if float64(max) < 2*float64(min+1) { | ||
| 270 | - t.Errorf("no shadow visible: min = %d, max = %d", min, max) | ||
| 271 | - } | ||
| 272 | - | ||
| 273 | - // Determinism: the same configuration must give the same image. | ||
| 274 | - img2 := p.Render(cam) | ||
| 275 | - if !bytes.Equal(img.Pix, img2.Pix) { | ||
| 276 | - t.Error("two renders of the same scene differ") | ||
| 277 | - } | ||
| 278 | -} | ||
| 279 | - | ||
| 280 | -func TestRenderSmoke(t *testing.T) { | ||
| 281 | - s := NewScene() | ||
| 282 | - s.AddObject(Sphere{Center: Vec3{0, 1, 0}, Radius: 1}, | ||
| 283 | - Material{Diffuse: Red, Specular: White, Shininess: 30, Ambient: 0.1}) | ||
| 284 | - s.AddObject(NewPlaneXY(0), Material{Diffuse: Gray, Ambient: 0.3}) | ||
| 285 | - s.Lights = []Light{NewLight(Vec3{4, 8, 6}, White)} | ||
| 286 | - | ||
| 287 | - cam := NewCamera(Vec3{0, 2, 8}) | ||
| 288 | - r := Renderer{Scene: s, Width: 64, Height: 48} | ||
| 289 | - img := r.Render(cam) | ||
| 290 | - if img.Width != 64 || img.Height != 48 { | ||
| 291 | - t.Fatalf("image size = %dx%d", img.Width, img.Height) | ||
| 292 | - } | ||
| 293 | - // The center pixel should hit the red sphere: red channel dominant. | ||
| 294 | - i := (24*64 + 32) * 3 | ||
| 295 | - if img.Pix[i] < 60 { | ||
| 296 | - t.Errorf("center pixel too dark: %v", img.Pix[i:i+3]) | ||
| 297 | - } | ||
| 298 | - if img.Pix[i+1] > img.Pix[i] || img.Pix[i+2] > img.Pix[i] { | ||
| 299 | - t.Errorf("center pixel not red-dominant: %v", img.Pix[i:i+3]) | ||
| 300 | - } | ||
| 301 | -} | ||
| 302 | - | ||
| 303 | -func TestSavePNG(t *testing.T) { | ||
| 304 | - img := NewImage(4, 3) | ||
| 305 | - for y := 0; y < 3; y++ { | ||
| 306 | - for x := 0; x < 4; x++ { | ||
| 307 | - img.Set(x, y, Color{float64(x) / 3, float64(y) / 2, 1}) | ||
| 308 | - } | ||
| 309 | - } | ||
| 310 | - path := t.TempDir() + "/test.png" | ||
| 311 | - if err := img.SavePNG(path); err != nil { | ||
| 312 | - t.Fatal(err) | ||
| 313 | - } | ||
| 314 | - data, err := os.ReadFile(path) | ||
| 315 | - if err != nil { | ||
| 316 | - t.Fatal(err) | ||
| 317 | - } | ||
| 318 | - // PNG signature. | ||
| 319 | - sig := []byte{137, 80, 78, 71, 13, 10, 26, 10} | ||
| 320 | - if !bytes.Equal(data[:8], sig) { | ||
| 321 | - t.Fatalf("bad signature: %v", data[:8]) | ||
| 322 | - } | ||
| 323 | - // IHDR must say 4x3, 8-bit truecolor. | ||
| 324 | - if binary.BigEndian.Uint32(data[16:20]) != 4 || binary.BigEndian.Uint32(data[20:24]) != 3 { | ||
| 325 | - t.Errorf("IHDR size wrong: %v", data[16:24]) | ||
| 326 | - } | ||
| 327 | - if data[24] != 8 || data[25] != 2 { | ||
| 328 | - t.Errorf("IHDR depth/type wrong: %d %d", data[24], data[25]) | ||
| 329 | - } | ||
| 330 | - // The file must end with an IEND chunk: length 0, "IEND", CRC AE426082. | ||
| 331 | - if !bytes.Equal(data[len(data)-8:len(data)-4], []byte("IEND")) || | ||
| 332 | - !bytes.Equal(data[len(data)-4:], []byte{0xAE, 0x42, 0x60, 0x82}) || | ||
| 333 | - !bytes.Equal(data[len(data)-12:len(data)-8], []byte{0, 0, 0, 0}) { | ||
| 334 | - t.Errorf("missing IEND trailer: %v", data[len(data)-12:]) | ||
| 335 | - } | ||
| 336 | -} | ||
deleted
demos/qwen-style/workspace/renderer.go +0 -256 | deleted file mode 100644 | ||
| @@ -1,256 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import ( | |
| 4 | - "fmt" | |
| 5 | - "math" | |
| 6 | - "os" | |
| 7 | - "sync" | |
| 8 | -) | |
| 9 | - | |
| 10 | -// Scene bundles everything a render needs. | |
| 11 | -type Scene struct { | |
| 12 | - // Objects are the geometry; pass a Group to combine several. | |
| 13 | - Objects []Object | |
| 14 | - // Materials are looked up by index; Objects[i] uses Materials[i]. | |
| 15 | - Materials []Material | |
| 16 | - // Lights light the scene. | |
| 17 | - Lights []Light | |
| 18 | - // Background is the color of empty space. | |
| 19 | - Background Color | |
| 20 | - // Gamma applied at the end (2.2 is a good default). | |
| 21 | - Gamma float64 | |
| 22 | - // MaxDepth limits reflection/refraction bounces. | |
| 23 | - MaxDepth int | |
| 24 | -} | |
| 25 | - | |
| 26 | -// NewScene makes a scene with a white background, gamma 2.2 and 8 bounces. | |
| 27 | -func NewScene() *Scene { | |
| 28 | - return &Scene{Background: White, Gamma: 2.2, MaxDepth: 8} | |
| 29 | -} | |
| 30 | - | |
| 31 | -// AddObject registers an object and its material. | |
| 32 | -func (s *Scene) AddObject(o Object, m Material) { | |
| 33 | - s.Objects = append(s.Objects, o) | |
| 34 | - s.Materials = append(s.Materials, m) | |
| 35 | -} | |
| 36 | - | |
| 37 | -// AddObjects registers several objects sharing one material. | |
| 38 | -func (s *Scene) AddObjects(o []Object, m Material) { | |
| 39 | - for _, x := range o { | |
| 40 | - s.AddObject(x, m) | |
| 41 | - } | |
| 42 | -} | |
| 43 | - | |
| 44 | -// Renderer renders scenes. | |
| 45 | -type Renderer struct { | |
| 46 | - Scene *Scene | |
| 47 | - // Width and Height of the image in pixels. | |
| 48 | - Width, Height int | |
| 49 | - // Threads is the number of worker goroutines (0 = GOMAXPROCS). | |
| 50 | - Threads int | |
| 51 | - // Samples is the number of rays per pixel, arranged in a grid | |
| 52 | - // (1 = no anti-aliasing; 2 means 2x2 = 4 rays, 3 means 3x3 = 9). | |
| 53 | - Samples int | |
| 54 | -} | |
| 55 | - | |
| 56 | -// Render renders the scene from the camera and returns the image. | |
| 57 | -func (r *Renderer) Render(cam Camera) *Image { | |
| 58 | - s := r.Scene | |
| 59 | - img := NewImage(r.Width, r.Height) | |
| 60 | - if s.Gamma <= 0 { | |
| 61 | - s.Gamma = 2.2 | |
| 62 | - } | |
| 63 | - if s.MaxDepth <= 0 { | |
| 64 | - s.MaxDepth = 8 | |
| 65 | - } | |
| 66 | - threads := r.Threads | |
| 67 | - if threads <= 0 { | |
| 68 | - threads = 4 | |
| 69 | - } | |
| 70 | - samples := r.Samples | |
| 71 | - if samples < 1 { | |
| 72 | - samples = 1 | |
| 73 | - } | |
| 74 | - | |
| 75 | - var wg sync.WaitGroup | |
| 76 | - chunks := threads | |
| 77 | - rowsPer := (r.Height + chunks - 1) / chunks | |
| 78 | - for c := 0; c < chunks; c++ { | |
| 79 | - y0 := c * rowsPer | |
| 80 | - y1 := y0 + rowsPer | |
| 81 | - if y1 > r.Height { | |
| 82 | - y1 = r.Height | |
| 83 | - } | |
| 84 | - if y0 >= y1 { | |
| 85 | - break | |
| 86 | - } | |
| 87 | - wg.Add(1) | |
| 88 | - go func(y0, y1 int) { | |
| 89 | - defer wg.Done() | |
| 90 | - for y := y0; y < y1; y++ { | |
| 91 | - for x := 0; x < r.Width; x++ { | |
| 92 | - var col Color | |
| 93 | - for sy := 0; sy < samples; sy++ { | |
| 94 | - for sx := 0; sx < samples; sx++ { | |
| 95 | - u := float64(x) + (float64(sx)+0.5)/float64(samples) | |
| 96 | - v := float64(y) + (float64(sy)+0.5)/float64(samples) | |
| 97 | - col = col.Add(r.trace(cam.RayAtUV(u, v, r.Width, r.Height), 0)) | |
| 98 | - } | |
| 99 | - } | |
| 100 | - img.Set(x, y, col.Scale(1.0/float64(samples*samples))) | |
| 101 | - } | |
| 102 | - } | |
| 103 | - }(y0, y1) | |
| 104 | - } | |
| 105 | - wg.Wait() | |
| 106 | - return img | |
| 107 | -} | |
| 108 | - | |
| 109 | -// trace follows a ray until it leaves the scene or the bounce budget. | |
| 110 | -func (r *Renderer) trace(ray Ray, depth int) Color { | |
| 111 | - s := r.Scene | |
| 112 | - if depth > s.MaxDepth { | |
| 113 | - return Black | |
| 114 | - } | |
| 115 | - | |
| 116 | - idx, hit := closestHit(s.Objects, ray) | |
| 117 | - if idx < 0 { | |
| 118 | - return s.Background | |
| 119 | - } | |
| 120 | - hit.Flip(ray) | |
| 121 | - mat := s.Materials[idx] | |
| 122 | - | |
| 123 | - col := r.shade(ray, hit, mat, depth) | |
| 124 | - | |
| 125 | - // Reflection: mirror the ray, keep the surface color as a tint. | |
| 126 | - if mat.Reflectivity > 0 { | |
| 127 | - reflect := ray.Dir.Sub(hit.Normal.Scale(2 * ray.Dir.Dot(hit.Normal))).Norm() | |
| 128 | - rr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: reflect} | |
| 129 | - col = col.Add(r.trace(rr, depth+1).Mul(mat.Diffuse).Scale(mat.Reflectivity)) | |
| 130 | - } | |
| 131 | - | |
| 132 | - // Refraction: bend the ray through the surface. | |
| 133 | - if mat.Transparency > 0 { | |
| 134 | - ior := 1.0 | |
| 135 | - if mat.IOR > 0 { | |
| 136 | - ior = mat.IOR | |
| 137 | - } | |
| 138 | - refr, ok := refract(ray.Dir, hit.Normal, 1/ior) | |
| 139 | - if ok { | |
| 140 | - tr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: refr} | |
| 141 | - col = col.Add(r.trace(tr, depth+1).Scale(mat.Transparency)) | |
| 142 | - } | |
| 143 | - } | |
| 144 | - return col | |
| 145 | -} | |
| 146 | - | |
| 147 | -// shade computes ambient + diffuse + specular with shadows for one hit. | |
| 148 | -func (r *Renderer) shade(ray Ray, hit Hit, mat Material, depth int) Color { | |
| 149 | - s := r.Scene | |
| 150 | - col := mat.Diffuse.Scale(mat.Ambient) | |
| 151 | - for _, l := range s.Lights { | |
| 152 | - toLight := l.Position.Sub(hit.Point) | |
| 153 | - dist := toLight.Len() | |
| 154 | - if dist < 1e-6 { | |
| 155 | - continue | |
| 156 | - } | |
| 157 | - dir := toLight.Scale(1 / dist) | |
| 158 | - // Shadow: is the light occluded? | |
| 159 | - if occluded(s.Objects, s.Materials, Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: dir}, dist) { | |
| 160 | - continue | |
| 161 | - } | |
| 162 | - // Inverse-square falloff, clamped so close lights don't blow out. | |
| 163 | - fall := 1.0 / math.Max(1.0, dist*dist*0.1) | |
| 164 | - lc := l.Color.Scale(l.Intensity * fall) | |
| 165 | - | |
| 166 | - diff := math.Max(0, hit.Normal.Dot(dir)) | |
| 167 | - col = col.Add(mat.Diffuse.Mul(lc).Scale(diff)) | |
| 168 | - | |
| 169 | - if mat.Shininess > 0 && diff > 0 { | |
| 170 | - half := dir.Add(ray.Dir.Neg()).Norm() | |
| 171 | - spec := math.Max(0, hit.Normal.Dot(half)) | |
| 172 | - col = col.Add(mat.Specular.Mul(lc).Scale(math.Pow(spec, mat.Shininess))) | |
| 173 | - } | |
| 174 | - } | |
| 175 | - return col | |
| 176 | -} | |
| 177 | - | |
| 178 | -// closestHit finds the nearest object hit for the ray. | |
| 179 | -func closestHit(objects []Object, ray Ray) (int, Hit) { | |
| 180 | - bestIdx, best := -1, Hit{} | |
| 181 | - for i, o := range objects { | |
| 182 | - if h, ok := o.Intersect(ray); ok && (bestIdx < 0 || h.Dist < best.Dist) { | |
| 183 | - bestIdx, best = i, h | |
| 184 | - } | |
| 185 | - } | |
| 186 | - return bestIdx, best | |
| 187 | -} | |
| 188 | - | |
| 189 | -// occluded reports whether an opaque object is hit before maxDist. | |
| 190 | -func occluded(objects []Object, mats []Material, ray Ray, maxDist float64) bool { | |
| 191 | - for i, o := range objects { | |
| 192 | - if mats[i].Transparency > 0.5 { | |
| 193 | - continue | |
| 194 | - } | |
| 195 | - if h, ok := o.Intersect(ray); ok && h.Dist < maxDist { | |
| 196 | - return true | |
| 197 | - } | |
| 198 | - } | |
| 199 | - return false | |
| 200 | -} | |
| 201 | - | |
| 202 | -// refract bends dir across a surface with normal n and ratio ior | |
| 203 | -// (1/ior when exiting the medium). Returns (direction, ok). | |
| 204 | -func refract(dir, n Vec3, ior float64) (Vec3, bool) { | |
| 205 | - cosI := -dir.Dot(n) | |
| 206 | - sin2T := ior * ior * (1 - cosI*cosI) | |
| 207 | - if sin2T > 1 { | |
| 208 | - return Vec3{}, false // total internal reflection | |
| 209 | - } | |
| 210 | - cosT := math.Sqrt(1 - sin2T) | |
| 211 | - return dir.Scale(ior).Sub(n.Scale(ior*cosT - cosI)).Norm(), true | |
| 212 | -} | |
| 213 | - | |
| 214 | -// Image is a plain RGB image. | |
| 215 | -type Image struct { | |
| 216 | - Width, Height int | |
| 217 | - Pix []byte // RGB, row-major, 3 bytes per pixel | |
| 218 | -} | |
| 219 | - | |
| 220 | -// NewImage allocates a black image. | |
| 221 | -func NewImage(w, h int) *Image { | |
| 222 | - return &Image{Width: w, Height: h, Pix: make([]byte, w*h*3)} | |
| 223 | -} | |
| 224 | - | |
| 225 | -// Set stores a color at (x, y) with gamma correction and clipping. | |
| 226 | -func (img *Image) Set(x, y int, c Color) { | |
| 227 | - i := (y*img.Width + x) * 3 | |
| 228 | - img.Pix[i] = toByte(c.R) | |
| 229 | - img.Pix[i+1] = toByte(c.G) | |
| 230 | - img.Pix[i+2] = toByte(c.B) | |
| 231 | -} | |
| 232 | - | |
| 233 | -func toByte(v float64) byte { | |
| 234 | - if v < 0 { | |
| 235 | - v = 0 | |
| 236 | - } | |
| 237 | - if v > 1 { | |
| 238 | - v = 1 | |
| 239 | - } | |
| 240 | - return byte(255 * math.Pow(v, 1/2.2)) | |
| 241 | -} | |
| 242 | - | |
| 243 | -// SavePPM writes the image as a binary PPM (P6) file. | |
| 244 | -func (img *Image) SavePPM(path string) error { | |
| 245 | - f, err := os.Create(path) | |
| 246 | - if err != nil { | |
| 247 | - return err | |
| 248 | - } | |
| 249 | - defer f.Close() | |
| 250 | - _, err = fmt.Fprintf(f, "P6\n%d %d\n255\n", img.Width, img.Height) | |
| 251 | - if err != nil { | |
| 252 | - return err | |
| 253 | - } | |
| 254 | - _, err = f.Write(img.Pix) | |
| 255 | - return err | |
| 256 | -} | |
| deleted file mode 100644 | |||
| @@ -1,256 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import ( | ||
| 4 | - "fmt" | ||
| 5 | - "math" | ||
| 6 | - "os" | ||
| 7 | - "sync" | ||
| 8 | -) | ||
| 9 | - | ||
| 10 | -// Scene bundles everything a render needs. | ||
| 11 | -type Scene struct { | ||
| 12 | - // Objects are the geometry; pass a Group to combine several. | ||
| 13 | - Objects []Object | ||
| 14 | - // Materials are looked up by index; Objects[i] uses Materials[i]. | ||
| 15 | - Materials []Material | ||
| 16 | - // Lights light the scene. | ||
| 17 | - Lights []Light | ||
| 18 | - // Background is the color of empty space. | ||
| 19 | - Background Color | ||
| 20 | - // Gamma applied at the end (2.2 is a good default). | ||
| 21 | - Gamma float64 | ||
| 22 | - // MaxDepth limits reflection/refraction bounces. | ||
| 23 | - MaxDepth int | ||
| 24 | -} | ||
| 25 | - | ||
| 26 | -// NewScene makes a scene with a white background, gamma 2.2 and 8 bounces. | ||
| 27 | -func NewScene() *Scene { | ||
| 28 | - return &Scene{Background: White, Gamma: 2.2, MaxDepth: 8} | ||
| 29 | -} | ||
| 30 | - | ||
| 31 | -// AddObject registers an object and its material. | ||
| 32 | -func (s *Scene) AddObject(o Object, m Material) { | ||
| 33 | - s.Objects = append(s.Objects, o) | ||
| 34 | - s.Materials = append(s.Materials, m) | ||
| 35 | -} | ||
| 36 | - | ||
| 37 | -// AddObjects registers several objects sharing one material. | ||
| 38 | -func (s *Scene) AddObjects(o []Object, m Material) { | ||
| 39 | - for _, x := range o { | ||
| 40 | - s.AddObject(x, m) | ||
| 41 | - } | ||
| 42 | -} | ||
| 43 | - | ||
| 44 | -// Renderer renders scenes. | ||
| 45 | -type Renderer struct { | ||
| 46 | - Scene *Scene | ||
| 47 | - // Width and Height of the image in pixels. | ||
| 48 | - Width, Height int | ||
| 49 | - // Threads is the number of worker goroutines (0 = GOMAXPROCS). | ||
| 50 | - Threads int | ||
| 51 | - // Samples is the number of rays per pixel, arranged in a grid | ||
| 52 | - // (1 = no anti-aliasing; 2 means 2x2 = 4 rays, 3 means 3x3 = 9). | ||
| 53 | - Samples int | ||
| 54 | -} | ||
| 55 | - | ||
| 56 | -// Render renders the scene from the camera and returns the image. | ||
| 57 | -func (r *Renderer) Render(cam Camera) *Image { | ||
| 58 | - s := r.Scene | ||
| 59 | - img := NewImage(r.Width, r.Height) | ||
| 60 | - if s.Gamma <= 0 { | ||
| 61 | - s.Gamma = 2.2 | ||
| 62 | - } | ||
| 63 | - if s.MaxDepth <= 0 { | ||
| 64 | - s.MaxDepth = 8 | ||
| 65 | - } | ||
| 66 | - threads := r.Threads | ||
| 67 | - if threads <= 0 { | ||
| 68 | - threads = 4 | ||
| 69 | - } | ||
| 70 | - samples := r.Samples | ||
| 71 | - if samples < 1 { | ||
| 72 | - samples = 1 | ||
| 73 | - } | ||
| 74 | - | ||
| 75 | - var wg sync.WaitGroup | ||
| 76 | - chunks := threads | ||
| 77 | - rowsPer := (r.Height + chunks - 1) / chunks | ||
| 78 | - for c := 0; c < chunks; c++ { | ||
| 79 | - y0 := c * rowsPer | ||
| 80 | - y1 := y0 + rowsPer | ||
| 81 | - if y1 > r.Height { | ||
| 82 | - y1 = r.Height | ||
| 83 | - } | ||
| 84 | - if y0 >= y1 { | ||
| 85 | - break | ||
| 86 | - } | ||
| 87 | - wg.Add(1) | ||
| 88 | - go func(y0, y1 int) { | ||
| 89 | - defer wg.Done() | ||
| 90 | - for y := y0; y < y1; y++ { | ||
| 91 | - for x := 0; x < r.Width; x++ { | ||
| 92 | - var col Color | ||
| 93 | - for sy := 0; sy < samples; sy++ { | ||
| 94 | - for sx := 0; sx < samples; sx++ { | ||
| 95 | - u := float64(x) + (float64(sx)+0.5)/float64(samples) | ||
| 96 | - v := float64(y) + (float64(sy)+0.5)/float64(samples) | ||
| 97 | - col = col.Add(r.trace(cam.RayAtUV(u, v, r.Width, r.Height), 0)) | ||
| 98 | - } | ||
| 99 | - } | ||
| 100 | - img.Set(x, y, col.Scale(1.0/float64(samples*samples))) | ||
| 101 | - } | ||
| 102 | - } | ||
| 103 | - }(y0, y1) | ||
| 104 | - } | ||
| 105 | - wg.Wait() | ||
| 106 | - return img | ||
| 107 | -} | ||
| 108 | - | ||
| 109 | -// trace follows a ray until it leaves the scene or the bounce budget. | ||
| 110 | -func (r *Renderer) trace(ray Ray, depth int) Color { | ||
| 111 | - s := r.Scene | ||
| 112 | - if depth > s.MaxDepth { | ||
| 113 | - return Black | ||
| 114 | - } | ||
| 115 | - | ||
| 116 | - idx, hit := closestHit(s.Objects, ray) | ||
| 117 | - if idx < 0 { | ||
| 118 | - return s.Background | ||
| 119 | - } | ||
| 120 | - hit.Flip(ray) | ||
| 121 | - mat := s.Materials[idx] | ||
| 122 | - | ||
| 123 | - col := r.shade(ray, hit, mat, depth) | ||
| 124 | - | ||
| 125 | - // Reflection: mirror the ray, keep the surface color as a tint. | ||
| 126 | - if mat.Reflectivity > 0 { | ||
| 127 | - reflect := ray.Dir.Sub(hit.Normal.Scale(2 * ray.Dir.Dot(hit.Normal))).Norm() | ||
| 128 | - rr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: reflect} | ||
| 129 | - col = col.Add(r.trace(rr, depth+1).Mul(mat.Diffuse).Scale(mat.Reflectivity)) | ||
| 130 | - } | ||
| 131 | - | ||
| 132 | - // Refraction: bend the ray through the surface. | ||
| 133 | - if mat.Transparency > 0 { | ||
| 134 | - ior := 1.0 | ||
| 135 | - if mat.IOR > 0 { | ||
| 136 | - ior = mat.IOR | ||
| 137 | - } | ||
| 138 | - refr, ok := refract(ray.Dir, hit.Normal, 1/ior) | ||
| 139 | - if ok { | ||
| 140 | - tr := Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: refr} | ||
| 141 | - col = col.Add(r.trace(tr, depth+1).Scale(mat.Transparency)) | ||
| 142 | - } | ||
| 143 | - } | ||
| 144 | - return col | ||
| 145 | -} | ||
| 146 | - | ||
| 147 | -// shade computes ambient + diffuse + specular with shadows for one hit. | ||
| 148 | -func (r *Renderer) shade(ray Ray, hit Hit, mat Material, depth int) Color { | ||
| 149 | - s := r.Scene | ||
| 150 | - col := mat.Diffuse.Scale(mat.Ambient) | ||
| 151 | - for _, l := range s.Lights { | ||
| 152 | - toLight := l.Position.Sub(hit.Point) | ||
| 153 | - dist := toLight.Len() | ||
| 154 | - if dist < 1e-6 { | ||
| 155 | - continue | ||
| 156 | - } | ||
| 157 | - dir := toLight.Scale(1 / dist) | ||
| 158 | - // Shadow: is the light occluded? | ||
| 159 | - if occluded(s.Objects, s.Materials, Ray{Origin: hit.Point.Add(hit.Normal.Scale(1e-4)), Dir: dir}, dist) { | ||
| 160 | - continue | ||
| 161 | - } | ||
| 162 | - // Inverse-square falloff, clamped so close lights don't blow out. | ||
| 163 | - fall := 1.0 / math.Max(1.0, dist*dist*0.1) | ||
| 164 | - lc := l.Color.Scale(l.Intensity * fall) | ||
| 165 | - | ||
| 166 | - diff := math.Max(0, hit.Normal.Dot(dir)) | ||
| 167 | - col = col.Add(mat.Diffuse.Mul(lc).Scale(diff)) | ||
| 168 | - | ||
| 169 | - if mat.Shininess > 0 && diff > 0 { | ||
| 170 | - half := dir.Add(ray.Dir.Neg()).Norm() | ||
| 171 | - spec := math.Max(0, hit.Normal.Dot(half)) | ||
| 172 | - col = col.Add(mat.Specular.Mul(lc).Scale(math.Pow(spec, mat.Shininess))) | ||
| 173 | - } | ||
| 174 | - } | ||
| 175 | - return col | ||
| 176 | -} | ||
| 177 | - | ||
| 178 | -// closestHit finds the nearest object hit for the ray. | ||
| 179 | -func closestHit(objects []Object, ray Ray) (int, Hit) { | ||
| 180 | - bestIdx, best := -1, Hit{} | ||
| 181 | - for i, o := range objects { | ||
| 182 | - if h, ok := o.Intersect(ray); ok && (bestIdx < 0 || h.Dist < best.Dist) { | ||
| 183 | - bestIdx, best = i, h | ||
| 184 | - } | ||
| 185 | - } | ||
| 186 | - return bestIdx, best | ||
| 187 | -} | ||
| 188 | - | ||
| 189 | -// occluded reports whether an opaque object is hit before maxDist. | ||
| 190 | -func occluded(objects []Object, mats []Material, ray Ray, maxDist float64) bool { | ||
| 191 | - for i, o := range objects { | ||
| 192 | - if mats[i].Transparency > 0.5 { | ||
| 193 | - continue | ||
| 194 | - } | ||
| 195 | - if h, ok := o.Intersect(ray); ok && h.Dist < maxDist { | ||
| 196 | - return true | ||
| 197 | - } | ||
| 198 | - } | ||
| 199 | - return false | ||
| 200 | -} | ||
| 201 | - | ||
| 202 | -// refract bends dir across a surface with normal n and ratio ior | ||
| 203 | -// (1/ior when exiting the medium). Returns (direction, ok). | ||
| 204 | -func refract(dir, n Vec3, ior float64) (Vec3, bool) { | ||
| 205 | - cosI := -dir.Dot(n) | ||
| 206 | - sin2T := ior * ior * (1 - cosI*cosI) | ||
| 207 | - if sin2T > 1 { | ||
| 208 | - return Vec3{}, false // total internal reflection | ||
| 209 | - } | ||
| 210 | - cosT := math.Sqrt(1 - sin2T) | ||
| 211 | - return dir.Scale(ior).Sub(n.Scale(ior*cosT - cosI)).Norm(), true | ||
| 212 | -} | ||
| 213 | - | ||
| 214 | -// Image is a plain RGB image. | ||
| 215 | -type Image struct { | ||
| 216 | - Width, Height int | ||
| 217 | - Pix []byte // RGB, row-major, 3 bytes per pixel | ||
| 218 | -} | ||
| 219 | - | ||
| 220 | -// NewImage allocates a black image. | ||
| 221 | -func NewImage(w, h int) *Image { | ||
| 222 | - return &Image{Width: w, Height: h, Pix: make([]byte, w*h*3)} | ||
| 223 | -} | ||
| 224 | - | ||
| 225 | -// Set stores a color at (x, y) with gamma correction and clipping. | ||
| 226 | -func (img *Image) Set(x, y int, c Color) { | ||
| 227 | - i := (y*img.Width + x) * 3 | ||
| 228 | - img.Pix[i] = toByte(c.R) | ||
| 229 | - img.Pix[i+1] = toByte(c.G) | ||
| 230 | - img.Pix[i+2] = toByte(c.B) | ||
| 231 | -} | ||
| 232 | - | ||
| 233 | -func toByte(v float64) byte { | ||
| 234 | - if v < 0 { | ||
| 235 | - v = 0 | ||
| 236 | - } | ||
| 237 | - if v > 1 { | ||
| 238 | - v = 1 | ||
| 239 | - } | ||
| 240 | - return byte(255 * math.Pow(v, 1/2.2)) | ||
| 241 | -} | ||
| 242 | - | ||
| 243 | -// SavePPM writes the image as a binary PPM (P6) file. | ||
| 244 | -func (img *Image) SavePPM(path string) error { | ||
| 245 | - f, err := os.Create(path) | ||
| 246 | - if err != nil { | ||
| 247 | - return err | ||
| 248 | - } | ||
| 249 | - defer f.Close() | ||
| 250 | - _, err = fmt.Fprintf(f, "P6\n%d %d\n255\n", img.Width, img.Height) | ||
| 251 | - if err != nil { | ||
| 252 | - return err | ||
| 253 | - } | ||
| 254 | - _, err = f.Write(img.Pix) | ||
| 255 | - return err | ||
| 256 | -} | ||
deleted
demos/qwen-style/workspace/transform.go +0 -181 | deleted file mode 100644 | ||
| @@ -1,181 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import "math" | |
| 4 | - | |
| 5 | -// Mat4 is a 4x4 transformation matrix, row-major (m[row][col]). | |
| 6 | -// It maps a world point p to m.TransformPoint(p). | |
| 7 | -type Mat4 [4][4]float64 | |
| 8 | - | |
| 9 | -// Identity returns the 4x4 identity matrix. | |
| 10 | -func Identity() Mat4 { | |
| 11 | - var m Mat4 | |
| 12 | - for i := range m { | |
| 13 | - m[i][i] = 1 | |
| 14 | - } | |
| 15 | - return m | |
| 16 | -} | |
| 17 | - | |
| 18 | -// Multiply returns the product m * o, i.e. a transformation that | |
| 19 | -// applies o first, then m. | |
| 20 | -func (m Mat4) Multiply(o Mat4) Mat4 { | |
| 21 | - var r Mat4 | |
| 22 | - for i := 0; i < 4; i++ { | |
| 23 | - for j := 0; j < 4; j++ { | |
| 24 | - var s float64 | |
| 25 | - for k := 0; k < 4; k++ { | |
| 26 | - s += m[i][k] * o[k][j] | |
| 27 | - } | |
| 28 | - r[i][j] = s | |
| 29 | - } | |
| 30 | - } | |
| 31 | - return r | |
| 32 | -} | |
| 33 | - | |
| 34 | -// Translate returns a translation matrix. | |
| 35 | -func Translate(x, y, z float64) Mat4 { | |
| 36 | - m := Identity() | |
| 37 | - m[0][3], m[1][3], m[2][3] = x, y, z | |
| 38 | - return m | |
| 39 | -} | |
| 40 | - | |
| 41 | -// Scale returns a (possibly non-uniform) scale matrix. | |
| 42 | -func Scale(x, y, z float64) Mat4 { | |
| 43 | - m := Identity() | |
| 44 | - m[0][0], m[1][1], m[2][2] = x, y, z | |
| 45 | - return m | |
| 46 | -} | |
| 47 | - | |
| 48 | -// RotateX returns a rotation of angle a (radians) around the X axis. | |
| 49 | -func RotateX(a float64) Mat4 { | |
| 50 | - c, s := math.Cos(a), math.Sin(a) | |
| 51 | - return Mat4{ | |
| 52 | - {1, 0, 0, 0}, | |
| 53 | - {0, c, -s, 0}, | |
| 54 | - {0, s, c, 0}, | |
| 55 | - {0, 0, 0, 1}, | |
| 56 | - } | |
| 57 | -} | |
| 58 | - | |
| 59 | -// RotateY returns a rotation of angle a (radians) around the Y axis. | |
| 60 | -func RotateY(a float64) Mat4 { | |
| 61 | - c, s := math.Cos(a), math.Sin(a) | |
| 62 | - return Mat4{ | |
| 63 | - {c, 0, s, 0}, | |
| 64 | - {0, 1, 0, 0}, | |
| 65 | - {-s, 0, c, 0}, | |
| 66 | - {0, 0, 0, 1}, | |
| 67 | - } | |
| 68 | -} | |
| 69 | - | |
| 70 | -// RotateZ returns a rotation of angle a (radians) around the Z axis. | |
| 71 | -func RotateZ(a float64) Mat4 { | |
| 72 | - c, s := math.Cos(a), math.Sin(a) | |
| 73 | - return Mat4{ | |
| 74 | - {c, -s, 0, 0}, | |
| 75 | - {s, c, 0, 0}, | |
| 76 | - {0, 0, 1, 0}, | |
| 77 | - {0, 0, 0, 1}, | |
| 78 | - } | |
| 79 | -} | |
| 80 | - | |
| 81 | -// TransformPoint applies the matrix to a point (translation included). | |
| 82 | -func (m Mat4) TransformPoint(p Vec3) Vec3 { | |
| 83 | - return Vec3{ | |
| 84 | - m[0][0]*p.X + m[0][1]*p.Y + m[0][2]*p.Z + m[0][3], | |
| 85 | - m[1][0]*p.X + m[1][1]*p.Y + m[1][2]*p.Z + m[1][3], | |
| 86 | - m[2][0]*p.X + m[2][1]*p.Y + m[2][2]*p.Z + m[2][3], | |
| 87 | - } | |
| 88 | -} | |
| 89 | - | |
| 90 | -// TransformDir applies the matrix to a direction (no translation). | |
| 91 | -// The result is not normalized; the intersection math does not require it. | |
| 92 | -func (m Mat4) TransformDir(d Vec3) Vec3 { | |
| 93 | - return Vec3{ | |
| 94 | - m[0][0]*d.X + m[0][1]*d.Y + m[0][2]*d.Z, | |
| 95 | - m[1][0]*d.X + m[1][1]*d.Y + m[1][2]*d.Z, | |
| 96 | - m[2][0]*d.X + m[2][1]*d.Y + m[2][2]*d.Z, | |
| 97 | - } | |
| 98 | -} | |
| 99 | - | |
| 100 | -// Inverse returns the inverse matrix. ok is false if the matrix is | |
| 101 | -// singular. | |
| 102 | -func (m Mat4) Inverse() (Mat4, bool) { | |
| 103 | - // Gauss-Jordan on the augmented system [m | I]. | |
| 104 | - a := make([][]float64, 4) | |
| 105 | - for i := range a { | |
| 106 | - a[i] = make([]float64, 8) | |
| 107 | - copy(a[i][:4], m[i][:]) | |
| 108 | - a[i][4+i] = 1 | |
| 109 | - } | |
| 110 | - for col := 0; col < 4; col++ { | |
| 111 | - piv := col | |
| 112 | - for r := col + 1; r < 4; r++ { | |
| 113 | - if math.Abs(a[r][col]) > math.Abs(a[piv][col]) { | |
| 114 | - piv = r | |
| 115 | - } | |
| 116 | - } | |
| 117 | - if math.Abs(a[piv][col]) < 1e-12 { | |
| 118 | - return Mat4{}, false | |
| 119 | - } | |
| 120 | - a[col], a[piv] = a[piv], a[col] | |
| 121 | - pv := a[col][col] | |
| 122 | - for c := col; c < 8; c++ { | |
| 123 | - a[col][c] /= pv | |
| 124 | - } | |
| 125 | - for r := 0; r < 4; r++ { | |
| 126 | - if r == col || a[r][col] == 0 { | |
| 127 | - continue | |
| 128 | - } | |
| 129 | - f := a[r][col] | |
| 130 | - for c := col; c < 8; c++ { | |
| 131 | - a[r][c] -= f * a[col][c] | |
| 132 | - } | |
| 133 | - } | |
| 134 | - } | |
| 135 | - var inv Mat4 | |
| 136 | - for i := 0; i < 4; i++ { | |
| 137 | - copy(inv[i][:], a[i][4:]) | |
| 138 | - } | |
| 139 | - return inv, true | |
| 140 | -} | |
| 141 | - | |
| 142 | -// TransformNormal maps a surface normal into world space using the | |
| 143 | -// inverse-transpose of the linear part (correct under non-uniform | |
| 144 | -// scale), and returns it normalized. | |
| 145 | -func (m Mat4) TransformNormal(n Vec3) Vec3 { | |
| 146 | - inv, ok := m.Inverse() | |
| 147 | - if !ok { | |
| 148 | - return n | |
| 149 | - } | |
| 150 | - return Vec3{ | |
| 151 | - inv[0][0]*n.X + inv[1][0]*n.Y + inv[2][0]*n.Z, | |
| 152 | - inv[0][1]*n.X + inv[1][1]*n.Y + inv[2][1]*n.Z, | |
| 153 | - inv[0][2]*n.X + inv[1][2]*n.Y + inv[2][2]*n.Z, | |
| 154 | - }.Norm() | |
| 155 | -} | |
| 156 | - | |
| 157 | -// Transform is an object wrapped in a 4x4 matrix: the ray is mapped | |
| 158 | -// into the object's local space, intersected there, and the hit is | |
| 159 | -// mapped back to world space. | |
| 160 | -type Transform struct { | |
| 161 | - Object Object | |
| 162 | - Matrix Mat4 | |
| 163 | -} | |
| 164 | - | |
| 165 | -func (t Transform) Intersect(r Ray) (Hit, bool) { | |
| 166 | - inv, ok := t.Matrix.Inverse() | |
| 167 | - if !ok { | |
| 168 | - return Hit{}, false | |
| 169 | - } | |
| 170 | - lr := Ray{Origin: inv.TransformPoint(r.Origin), Dir: inv.TransformDir(r.Dir)} | |
| 171 | - h, ok := t.Object.Intersect(lr) | |
| 172 | - if !ok { | |
| 173 | - return Hit{}, false | |
| 174 | - } | |
| 175 | - p := t.Matrix.TransformPoint(h.Point) | |
| 176 | - return Hit{ | |
| 177 | - Point: p, | |
| 178 | - Dist: p.Sub(r.Origin).Len(), | |
| 179 | - Normal: t.Matrix.TransformNormal(h.Normal), | |
| 180 | - }, true | |
| 181 | -} | |
| deleted file mode 100644 | |||
| @@ -1,181 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import "math" | ||
| 4 | - | ||
| 5 | -// Mat4 is a 4x4 transformation matrix, row-major (m[row][col]). | ||
| 6 | -// It maps a world point p to m.TransformPoint(p). | ||
| 7 | -type Mat4 [4][4]float64 | ||
| 8 | - | ||
| 9 | -// Identity returns the 4x4 identity matrix. | ||
| 10 | -func Identity() Mat4 { | ||
| 11 | - var m Mat4 | ||
| 12 | - for i := range m { | ||
| 13 | - m[i][i] = 1 | ||
| 14 | - } | ||
| 15 | - return m | ||
| 16 | -} | ||
| 17 | - | ||
| 18 | -// Multiply returns the product m * o, i.e. a transformation that | ||
| 19 | -// applies o first, then m. | ||
| 20 | -func (m Mat4) Multiply(o Mat4) Mat4 { | ||
| 21 | - var r Mat4 | ||
| 22 | - for i := 0; i < 4; i++ { | ||
| 23 | - for j := 0; j < 4; j++ { | ||
| 24 | - var s float64 | ||
| 25 | - for k := 0; k < 4; k++ { | ||
| 26 | - s += m[i][k] * o[k][j] | ||
| 27 | - } | ||
| 28 | - r[i][j] = s | ||
| 29 | - } | ||
| 30 | - } | ||
| 31 | - return r | ||
| 32 | -} | ||
| 33 | - | ||
| 34 | -// Translate returns a translation matrix. | ||
| 35 | -func Translate(x, y, z float64) Mat4 { | ||
| 36 | - m := Identity() | ||
| 37 | - m[0][3], m[1][3], m[2][3] = x, y, z | ||
| 38 | - return m | ||
| 39 | -} | ||
| 40 | - | ||
| 41 | -// Scale returns a (possibly non-uniform) scale matrix. | ||
| 42 | -func Scale(x, y, z float64) Mat4 { | ||
| 43 | - m := Identity() | ||
| 44 | - m[0][0], m[1][1], m[2][2] = x, y, z | ||
| 45 | - return m | ||
| 46 | -} | ||
| 47 | - | ||
| 48 | -// RotateX returns a rotation of angle a (radians) around the X axis. | ||
| 49 | -func RotateX(a float64) Mat4 { | ||
| 50 | - c, s := math.Cos(a), math.Sin(a) | ||
| 51 | - return Mat4{ | ||
| 52 | - {1, 0, 0, 0}, | ||
| 53 | - {0, c, -s, 0}, | ||
| 54 | - {0, s, c, 0}, | ||
| 55 | - {0, 0, 0, 1}, | ||
| 56 | - } | ||
| 57 | -} | ||
| 58 | - | ||
| 59 | -// RotateY returns a rotation of angle a (radians) around the Y axis. | ||
| 60 | -func RotateY(a float64) Mat4 { | ||
| 61 | - c, s := math.Cos(a), math.Sin(a) | ||
| 62 | - return Mat4{ | ||
| 63 | - {c, 0, s, 0}, | ||
| 64 | - {0, 1, 0, 0}, | ||
| 65 | - {-s, 0, c, 0}, | ||
| 66 | - {0, 0, 0, 1}, | ||
| 67 | - } | ||
| 68 | -} | ||
| 69 | - | ||
| 70 | -// RotateZ returns a rotation of angle a (radians) around the Z axis. | ||
| 71 | -func RotateZ(a float64) Mat4 { | ||
| 72 | - c, s := math.Cos(a), math.Sin(a) | ||
| 73 | - return Mat4{ | ||
| 74 | - {c, -s, 0, 0}, | ||
| 75 | - {s, c, 0, 0}, | ||
| 76 | - {0, 0, 1, 0}, | ||
| 77 | - {0, 0, 0, 1}, | ||
| 78 | - } | ||
| 79 | -} | ||
| 80 | - | ||
| 81 | -// TransformPoint applies the matrix to a point (translation included). | ||
| 82 | -func (m Mat4) TransformPoint(p Vec3) Vec3 { | ||
| 83 | - return Vec3{ | ||
| 84 | - m[0][0]*p.X + m[0][1]*p.Y + m[0][2]*p.Z + m[0][3], | ||
| 85 | - m[1][0]*p.X + m[1][1]*p.Y + m[1][2]*p.Z + m[1][3], | ||
| 86 | - m[2][0]*p.X + m[2][1]*p.Y + m[2][2]*p.Z + m[2][3], | ||
| 87 | - } | ||
| 88 | -} | ||
| 89 | - | ||
| 90 | -// TransformDir applies the matrix to a direction (no translation). | ||
| 91 | -// The result is not normalized; the intersection math does not require it. | ||
| 92 | -func (m Mat4) TransformDir(d Vec3) Vec3 { | ||
| 93 | - return Vec3{ | ||
| 94 | - m[0][0]*d.X + m[0][1]*d.Y + m[0][2]*d.Z, | ||
| 95 | - m[1][0]*d.X + m[1][1]*d.Y + m[1][2]*d.Z, | ||
| 96 | - m[2][0]*d.X + m[2][1]*d.Y + m[2][2]*d.Z, | ||
| 97 | - } | ||
| 98 | -} | ||
| 99 | - | ||
| 100 | -// Inverse returns the inverse matrix. ok is false if the matrix is | ||
| 101 | -// singular. | ||
| 102 | -func (m Mat4) Inverse() (Mat4, bool) { | ||
| 103 | - // Gauss-Jordan on the augmented system [m | I]. | ||
| 104 | - a := make([][]float64, 4) | ||
| 105 | - for i := range a { | ||
| 106 | - a[i] = make([]float64, 8) | ||
| 107 | - copy(a[i][:4], m[i][:]) | ||
| 108 | - a[i][4+i] = 1 | ||
| 109 | - } | ||
| 110 | - for col := 0; col < 4; col++ { | ||
| 111 | - piv := col | ||
| 112 | - for r := col + 1; r < 4; r++ { | ||
| 113 | - if math.Abs(a[r][col]) > math.Abs(a[piv][col]) { | ||
| 114 | - piv = r | ||
| 115 | - } | ||
| 116 | - } | ||
| 117 | - if math.Abs(a[piv][col]) < 1e-12 { | ||
| 118 | - return Mat4{}, false | ||
| 119 | - } | ||
| 120 | - a[col], a[piv] = a[piv], a[col] | ||
| 121 | - pv := a[col][col] | ||
| 122 | - for c := col; c < 8; c++ { | ||
| 123 | - a[col][c] /= pv | ||
| 124 | - } | ||
| 125 | - for r := 0; r < 4; r++ { | ||
| 126 | - if r == col || a[r][col] == 0 { | ||
| 127 | - continue | ||
| 128 | - } | ||
| 129 | - f := a[r][col] | ||
| 130 | - for c := col; c < 8; c++ { | ||
| 131 | - a[r][c] -= f * a[col][c] | ||
| 132 | - } | ||
| 133 | - } | ||
| 134 | - } | ||
| 135 | - var inv Mat4 | ||
| 136 | - for i := 0; i < 4; i++ { | ||
| 137 | - copy(inv[i][:], a[i][4:]) | ||
| 138 | - } | ||
| 139 | - return inv, true | ||
| 140 | -} | ||
| 141 | - | ||
| 142 | -// TransformNormal maps a surface normal into world space using the | ||
| 143 | -// inverse-transpose of the linear part (correct under non-uniform | ||
| 144 | -// scale), and returns it normalized. | ||
| 145 | -func (m Mat4) TransformNormal(n Vec3) Vec3 { | ||
| 146 | - inv, ok := m.Inverse() | ||
| 147 | - if !ok { | ||
| 148 | - return n | ||
| 149 | - } | ||
| 150 | - return Vec3{ | ||
| 151 | - inv[0][0]*n.X + inv[1][0]*n.Y + inv[2][0]*n.Z, | ||
| 152 | - inv[0][1]*n.X + inv[1][1]*n.Y + inv[2][1]*n.Z, | ||
| 153 | - inv[0][2]*n.X + inv[1][2]*n.Y + inv[2][2]*n.Z, | ||
| 154 | - }.Norm() | ||
| 155 | -} | ||
| 156 | - | ||
| 157 | -// Transform is an object wrapped in a 4x4 matrix: the ray is mapped | ||
| 158 | -// into the object's local space, intersected there, and the hit is | ||
| 159 | -// mapped back to world space. | ||
| 160 | -type Transform struct { | ||
| 161 | - Object Object | ||
| 162 | - Matrix Mat4 | ||
| 163 | -} | ||
| 164 | - | ||
| 165 | -func (t Transform) Intersect(r Ray) (Hit, bool) { | ||
| 166 | - inv, ok := t.Matrix.Inverse() | ||
| 167 | - if !ok { | ||
| 168 | - return Hit{}, false | ||
| 169 | - } | ||
| 170 | - lr := Ray{Origin: inv.TransformPoint(r.Origin), Dir: inv.TransformDir(r.Dir)} | ||
| 171 | - h, ok := t.Object.Intersect(lr) | ||
| 172 | - if !ok { | ||
| 173 | - return Hit{}, false | ||
| 174 | - } | ||
| 175 | - p := t.Matrix.TransformPoint(h.Point) | ||
| 176 | - return Hit{ | ||
| 177 | - Point: p, | ||
| 178 | - Dist: p.Sub(r.Origin).Len(), | ||
| 179 | - Normal: t.Matrix.TransformNormal(h.Normal), | ||
| 180 | - }, true | ||
| 181 | -} | ||
deleted
demos/qwen-style/workspace/vector.go +0 -54 | deleted file mode 100644 | ||
| @@ -1,54 +0,0 @@ | ||
| 1 | -package povray | |
| 2 | - | |
| 3 | -import "math" | |
| 4 | - | |
| 5 | -// Vec3 is a 3D vector. | |
| 6 | -type Vec3 struct { | |
| 7 | - X, Y, Z float64 | |
| 8 | -} | |
| 9 | - | |
| 10 | -func (v Vec3) Add(o Vec3) Vec3 { return Vec3{v.X + o.X, v.Y + o.Y, v.Z + o.Z} } | |
| 11 | -func (v Vec3) Sub(o Vec3) Vec3 { return Vec3{v.X - o.X, v.Y - o.Y, v.Z - o.Z} } | |
| 12 | -func (v Vec3) Scale(s float64) Vec3 { | |
| 13 | - return Vec3{v.X * s, v.Y * s, v.Z * s} | |
| 14 | -} | |
| 15 | -func (v Vec3) Mul(o Vec3) Vec3 { return Vec3{v.X * o.X, v.Y * o.Y, v.Z * o.Z} } | |
| 16 | - | |
| 17 | -func (v Vec3) Dot(o Vec3) float64 { return v.X*o.X + v.Y*o.Y + v.Z*o.Z } | |
| 18 | -func (v Vec3) Cross(o Vec3) Vec3 { | |
| 19 | - return Vec3{ | |
| 20 | - v.Y*o.Z - v.Z*o.Y, | |
| 21 | - v.Z*o.X - v.X*o.Z, | |
| 22 | - v.X*o.Y - v.Y*o.X, | |
| 23 | - } | |
| 24 | -} | |
| 25 | - | |
| 26 | -func (v Vec3) Len() float64 { return math.Sqrt(v.Dot(v)) } | |
| 27 | -func (v Vec3) Norm() Vec3 { | |
| 28 | - l := v.Len() | |
| 29 | - if l == 0 { | |
| 30 | - return Vec3{} | |
| 31 | - } | |
| 32 | - return v.Scale(1 / l) | |
| 33 | -} | |
| 34 | -func (v Vec3) Neg() Vec3 { return Vec3{-v.X, -v.Y, -v.Z} } | |
| 35 | - | |
| 36 | -// Ray is a point and a (usually normalized) direction. | |
| 37 | -type Ray struct { | |
| 38 | - Origin, Dir Vec3 | |
| 39 | -} | |
| 40 | - | |
| 41 | -// Hit describes where a ray met a surface. | |
| 42 | -type Hit struct { | |
| 43 | - Point Vec3 | |
| 44 | - Dist float64 | |
| 45 | - Normal Vec3 | |
| 46 | -} | |
| 47 | - | |
| 48 | -// Flip turns the normal toward the ray origin if needed, so lighting | |
| 49 | -// always sees the face the ray struck. | |
| 50 | -func (h *Hit) Flip(r Ray) { | |
| 51 | - if h.Normal.Dot(r.Dir) > 0 { | |
| 52 | - h.Normal = h.Normal.Neg() | |
| 53 | - } | |
| 54 | -} | |
| deleted file mode 100644 | |||
| @@ -1,54 +0,0 @@ | |||
| 1 | -package povray | ||
| 2 | - | ||
| 3 | -import "math" | ||
| 4 | - | ||
| 5 | -// Vec3 is a 3D vector. | ||
| 6 | -type Vec3 struct { | ||
| 7 | - X, Y, Z float64 | ||
| 8 | -} | ||
| 9 | - | ||
| 10 | -func (v Vec3) Add(o Vec3) Vec3 { return Vec3{v.X + o.X, v.Y + o.Y, v.Z + o.Z} } | ||
| 11 | -func (v Vec3) Sub(o Vec3) Vec3 { return Vec3{v.X - o.X, v.Y - o.Y, v.Z - o.Z} } | ||
| 12 | -func (v Vec3) Scale(s float64) Vec3 { | ||
| 13 | - return Vec3{v.X * s, v.Y * s, v.Z * s} | ||
| 14 | -} | ||
| 15 | -func (v Vec3) Mul(o Vec3) Vec3 { return Vec3{v.X * o.X, v.Y * o.Y, v.Z * o.Z} } | ||
| 16 | - | ||
| 17 | -func (v Vec3) Dot(o Vec3) float64 { return v.X*o.X + v.Y*o.Y + v.Z*o.Z } | ||
| 18 | -func (v Vec3) Cross(o Vec3) Vec3 { | ||
| 19 | - return Vec3{ | ||
| 20 | - v.Y*o.Z - v.Z*o.Y, | ||
| 21 | - v.Z*o.X - v.X*o.Z, | ||
| 22 | - v.X*o.Y - v.Y*o.X, | ||
| 23 | - } | ||
| 24 | -} | ||
| 25 | - | ||
| 26 | -func (v Vec3) Len() float64 { return math.Sqrt(v.Dot(v)) } | ||
| 27 | -func (v Vec3) Norm() Vec3 { | ||
| 28 | - l := v.Len() | ||
| 29 | - if l == 0 { | ||
| 30 | - return Vec3{} | ||
| 31 | - } | ||
| 32 | - return v.Scale(1 / l) | ||
| 33 | -} | ||
| 34 | -func (v Vec3) Neg() Vec3 { return Vec3{-v.X, -v.Y, -v.Z} } | ||
| 35 | - | ||
| 36 | -// Ray is a point and a (usually normalized) direction. | ||
| 37 | -type Ray struct { | ||
| 38 | - Origin, Dir Vec3 | ||
| 39 | -} | ||
| 40 | - | ||
| 41 | -// Hit describes where a ray met a surface. | ||
| 42 | -type Hit struct { | ||
| 43 | - Point Vec3 | ||
| 44 | - Dist float64 | ||
| 45 | - Normal Vec3 | ||
| 46 | -} | ||
| 47 | - | ||
| 48 | -// Flip turns the normal toward the ray origin if needed, so lighting | ||
| 49 | -// always sees the face the ray struck. | ||
| 50 | -func (h *Hit) Flip(r Ray) { | ||
| 51 | - if h.Normal.Dot(r.Dir) > 0 { | ||
| 52 | - h.Normal = h.Normal.Neg() | ||
| 53 | - } | ||
| 54 | -} | ||