blob: d0e3cd2f52a89b38e7cc453391cd0f4781ac3975 [file] [log] [blame]
// @dart=2.8
// The ray tracer code in this file is written by Adam Burmister. It
// is available in its original form from:
//
// http://labs.flog.co.nz/raytracer/
//
// Ported from the v8 benchmark suite by Google 2012.
part of ray_trace;
class IntersectionInfo {
bool isHit = false;
int hitCount = 0;
BaseShape shape;
Vector position;
Vector normal;
Color color;
double distance;
IntersectionInfo() {
color = Color(0.0, 0.0, 0.0);
}
@override
String toString() => 'Intersection [$position]';
}
class Engine {
int canvasWidth;
int canvasHeight;
int pixelWidth, pixelHeight;
bool renderDiffuse, renderShadows, renderHighlights, renderReflections;
int rayDepth;
CanvasRenderingContext2D canvas;
Engine(
{canvasWidth = 100,
this.canvasHeight = 100,
this.pixelWidth = 2,
this.pixelHeight = 2,
this.renderDiffuse = false,
this.renderShadows = false,
this.renderHighlights = false,
this.renderReflections = false,
this.rayDepth = 2}) {
canvasHeight = canvasHeight ~/ pixelHeight;
canvasWidth = canvasWidth ~/ pixelWidth;
}
void setPixel(int x, int y, Color color) {
int pxW, pxH;
pxW = pixelWidth;
pxH = pixelHeight;
if (canvas != null) {
canvas.fillStyle = color.toString();
canvas.fillRect(x * pxW, y * pxH, pxW, pxH);
} else {
if (x == y) {
checkNumber += color.brightness();
}
}
}
// 'canvas' can be null if raytracer runs as benchmark
void renderScene(Scene scene, CanvasElement canvas) {
checkNumber = 0;
/* Get canvas */
this.canvas = canvas?.context2D;
var canvasHeight = this.canvasHeight;
var canvasWidth = this.canvasWidth;
for (var y = 0; y < canvasHeight; y++) {
for (var x = 0; x < canvasWidth; x++) {
var yp = y * 1.0 / canvasHeight * 2 - 1;
var xp = x * 1.0 / canvasWidth * 2 - 1;
var ray = scene.camera.getRay(xp, yp);
setPixel(x, y, getPixelColor(ray, scene));
}
}
if ((canvas == null) && (checkNumber != 2321)) {
// Used for benchmarking.
throw 'Scene rendered incorrectly';
}
}
Color getPixelColor(Ray ray, Scene scene) {
var info = testIntersection(ray, scene, null);
if (info.isHit) {
var color = rayTrace(info, ray, scene, 0);
return color;
}
return scene.background.color;
}
IntersectionInfo testIntersection(Ray ray, Scene scene, BaseShape exclude) {
var hits = 0;
var best = IntersectionInfo();
best.distance = 2000;
for (var i = 0; i < scene.shapes.length; i++) {
var shape = scene.shapes[i];
if (shape != exclude) {
var info = shape.intersect(ray);
if (info.isHit &&
(info.distance >= 0) &&
(info.distance < best.distance)) {
best = info;
hits++;
}
}
}
best.hitCount = hits;
return best;
}
Ray getReflectionRay(Vector P, Vector N, Vector V) {
var c1 = -N.dot(V);
var R1 = N.multiplyScalar(2 * c1) + V;
return Ray(P, R1);
}
Color rayTrace(IntersectionInfo info, Ray ray, Scene scene, int depth) {
// Calc ambient
var color = info.color.multiplyScalar(scene.background.ambience);
var shininess = pow(10, info.shape.material.gloss + 1);
for (var i = 0; i < scene.lights.length; i++) {
var light = scene.lights[i];
// Calc diffuse lighting
var v = (light.position - info.position).normalize();
if (renderDiffuse) {
var L = v.dot(info.normal);
if (L > 0.0) {
color = color + info.color * light.color.multiplyScalar(L);
}
}
// The greater the depth the more accurate the colours, but
// this is exponentially (!) expensive
if (depth <= rayDepth) {
// calculate reflection ray
if (renderReflections && info.shape.material.reflection > 0) {
var reflectionRay =
getReflectionRay(info.position, info.normal, ray.direction);
var refl = testIntersection(reflectionRay, scene, info.shape);
if (refl.isHit && refl.distance > 0) {
refl.color = rayTrace(refl, reflectionRay, scene, depth + 1);
} else {
refl.color = scene.background.color;
}
color = color.blend(refl.color, info.shape.material.reflection);
}
// Refraction
/* TODO */
}
/* Render shadows and highlights */
var shadowInfo = IntersectionInfo();
if (renderShadows) {
var shadowRay = Ray(info.position, v);
shadowInfo = testIntersection(shadowRay, scene, info.shape);
if (shadowInfo.isHit &&
shadowInfo.shape !=
info.shape /*&& shadowInfo.shape.type != 'PLANE'*/) {
var vA = color.multiplyScalar(0.5);
var dB = 0.5 * pow(shadowInfo.shape.material.transparency, 0.5);
color = vA.addScalar(dB);
}
}
// Phong specular highlights
if (renderHighlights &&
!shadowInfo.isHit &&
(info.shape.material.gloss > 0)) {
var Lv = (info.shape.position - light.position).normalize();
var E = (scene.camera.position - info.shape.position).normalize();
var H = (E - Lv).normalize();
var glossWeight = pow(max(info.normal.dot(H), 0), shininess).toDouble();
color = light.color.multiplyScalar(glossWeight) + color;
}
}
color.limit();
return color;
}
@override
String toString() {
return 'Engine [canvasWidth: $canvasWidth, canvasHeight: $canvasHeight]';
}
}