From 592739bf1e9bf67289e57762d52f708ecb618511 Mon Sep 17 00:00:00 2001 From: monman53 Date: Wed, 3 Jul 2024 23:44:54 +0900 Subject: [PATCH 01/11] lenses --- src/Canvas.vue | 72 +++++++++++------------------------------- src/globals.ts | 10 +++--- src/handlers.ts | 8 ++--- src/math.ts | 31 ++++++++++++++++-- src/rayTrace.ts | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 65 deletions(-) create mode 100644 src/rayTrace.ts diff --git a/src/Canvas.vue b/src/Canvas.vue index 4a304de..25fc36d 100644 --- a/src/Canvas.vue +++ b/src/Canvas.vue @@ -5,6 +5,7 @@ import { state, lights, lensGroups, sensor, sensorData, apple, options, style, i import { Vec, vec, vecRad, getIntersectionLens, crossAngle, fGaussian, intersectionSS, intersectionX, intersectionY, calcLensNWavelength } from './math' import { Light } from './type' +import { collisionIdealLens, collisionLens } from './rayTrace'; // Reference to the canvas const canvas = ref() @@ -102,37 +103,23 @@ const drawRay = (s: Vec, v: Vec, color: number, sensorDataTmp: any[]) => { // Center of lens curvature circle const c = vec(lens.x1 + lens.R1, 0) - let pl = getIntersectionLens(s, v, c, r, lens.R1) + const ni = lens.R1 > 0 ? n : 1 + const no = lens.R1 > 0 ? 1 : n + const pl = collisionLens(s, v, c.x, lens.R1, r, ni, no) const pb = intersectionBody(s, v) - if ((pb.p && pl.p && pb.d < pl.d) || (!pl.p && pb.p)) { + if ((pb.p && pl !== null && pb.d < pl.d) || (pl === null && pb.p)) { v = pb.p.sub(s) drawSegment(s, v, v.length()) return } - if (pl.p) { + if (pl !== null) { const p = pl.p v = p.sub(s) s = drawSegment(s, v, v.length()) - - // Refraction (inner lens rays) - const phi1 = crossAngle(Vec.sub(p, c).mul(lens.R1 > 0 ? 1 : -1), Vec.sub(s0, p)); - const phi2 = Math.asin(Math.sin(phi1) / n); - const theta = Math.atan2(p.y - c.y, p.x - c.x) + Math.PI + phi2; - if (lens.R1 > 0) { - v = vecRad(theta) - } else { - v = vecRad(theta + Math.PI) - } - + v = pl.vn() innerLens = true } - - // Optimization - if (lensIdx === 0 && !pl.p && !pb.p) { - drawSegment(s, v, infR.value) - return - } } //-------------------------------- @@ -142,27 +129,20 @@ const drawRay = (s: Vec, v: Vec, color: number, sensorDataTmp: any[]) => { // Center of lens curvature circle const c = vec(lens.x2 + lens.R2, 0) - const pl = getIntersectionLens(s, v, c, r, lens.R2) + const ni = lens.R2 > 0 ? 1 : n + const no = lens.R2 > 0 ? n : 1 + const pl = collisionLens(s, v, c.x, lens.R2, r, ni, no) const pb = intersectionBody(s, v) - if ((pb.p && pl.p && pb.d < pl.d) || (!pl.p && pb.p)) { + if ((pb.p && pl !== null && pb.d < pl.d) || (pl === null && pb.p)) { v = pb.p.sub(s) drawSegment(s, v, v.length()) return } - if (pl.p) { + if (pl !== null) { const p = pl.p v = p.sub(s) const nextS = drawSegment(s, v, v.length()) - - // Refraction (inner lens rays) - const phi1 = crossAngle(Vec.sub(p, c).mul(lens.R2 < 0 ? 1 : -1), Vec.sub(p, s)); - const phi2 = Math.asin(Math.sin(phi1) * n); - const theta = Math.atan2(p.y - c.y, p.x - c.x) + phi2; - if (lens.R2 > 0) { - v = vecRad(theta + Math.PI) - } else { - v = vecRad(theta) - } + v = pl.vn() s = nextS } } @@ -171,40 +151,24 @@ const drawRay = (s: Vec, v: Vec, color: number, sensorDataTmp: any[]) => { // Collision to ideal lens //-------------------------------- if (options.value.lensIdeal && options.value.lens) { - const pl = intersectionY(s, v, xm, -r, r) + const pl = collisionIdealLens(s, v, xm, r, f) const pb = intersectionBody(s, v) // TODO: Find better condition const eps = 1e-9 - if ((pb.p && pl.p && pb.d < pl.d + eps) || (!pl.p && pb.p)) { + if ((pb.p && pl !== null && pb.d < pl.d + eps) || (pl === null && pb.p)) { v = pb.p.sub(s) drawSegment(s, v, v.length()) return } - if (pl.p) { + if (pl !== null) { const p = pl.p v = p.sub(s) s = drawSegment(s, v, v.length()) - - // Find image position of the light source - // TODO: - const image = fGaussian(f, vec(s0.x - xm, s0.y)) - - // Refracted ray - let theta = Math.atan2(image.y - s.y, image.x); - if (image.x === Infinity) { - theta = Math.atan2(-s0.y, -(s0.x - xm)); - } - if (f < 0) { - theta += Math.PI - } - if (xm - f < s0.x) { - theta += Math.PI - } - v = vecRad(theta) + v = pl.vn() } // Optimization - if (lensIdx === 0 && !pl.p && !pb.p) { + if (lensIdx === 0 && pl === null && !pb.p) { drawSegment(s, v, infR.value) return } diff --git a/src/globals.ts b/src/globals.ts index 0db1b43..ebd7302 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -207,15 +207,15 @@ export const options0 = () => { return { advanced: false, lens: true, - lensIdeal: true, - lensFocalPoints: false, + lensIdeal: false, + lensFocalPoints: true, lensDoubleFocalPoints: false, hyperfocalPoint: false, - sensor: true, + sensor: false, sensorPreview: true, sensorMemory: false, - body: true, - aperture: true, + body: false, + aperture: false, grid: false, opticalAxis: false, curvature: false, diff --git a/src/handlers.ts b/src/handlers.ts index e3c5a37..c7fddef 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -102,11 +102,11 @@ export const lightMoveStartHandler = (e: any, idx: number) => { const c0 = light.c.copy() moveHandler = (e_: any) => { const d = getPositionDiffOnSvgApp(e_, m0) - if (c0.x + d.x > minLensX.value) { - light.c.x = minLensX.value - } else { + // if (c0.x + d.x > minLensX.value) { + // light.c.x = minLensX.value + // } else { light.c.x = c0.x + d.x - } + // } light.c.y = c0.y + d.y } } diff --git a/src/math.ts b/src/math.ts index dc2591c..fdc292c 100644 --- a/src/math.ts +++ b/src/math.ts @@ -128,7 +128,7 @@ export class Vec { // TODO: toString } -const dot = (p: Vec, q: Vec) => { +export const dot = (p: Vec, q: Vec) => { return p.x * q.x + p.y * q.y } @@ -138,7 +138,7 @@ const dot = (p: Vec, q: Vec) => { // return Math.acos((x1 * x2 + y1 * y2) / (norm1 * norm2)); // }; -const cross = (p: Vec, q: Vec) => { +export const cross = (p: Vec, q: Vec) => { return p.x * q.y - q.x * p.y } @@ -220,6 +220,33 @@ export const intersectionCC = (c1: Vec, r1: number, c2: Vec, r2: number) => { return [c1.add(n.rotate(theta).inplaceMul(r1)), c1.add(n.rotate(-theta).inplaceMul(r1))] } +export const intersectionCLNearest = (cx: number, r: number, s: Vec, v: Vec) => { + const absR = Math.abs(r) + const n = v.normalize() + const a = 1; + const b = 2 * ((s.x - cx) * n.x + (s.y) * n.y); + const c = (s.x - cx) * (s.x - cx) + s.y * s.y - absR * absR; + const cond = b * b - 4 * a * c; + if (cond < 0) { + return null + } + const d1 = (-b - Math.sqrt(cond)) / (2 * a); + const d2 = (-b + Math.sqrt(cond)) / (2 * a); + if (d1 >= 0 && d2 >= 0) { + if (d1 < d2) { + return { p: s.add(n.mul(d1)), d: d1 } + } else { + return { p: s.add(n.mul(d2)), d: d2 } + } + } else if (d1 >= 0) { + return { p: s.add(n.mul(d1)), d: d1 } + } else if (d2 >= 0) { + return { p: s.add(n.mul(d2)), d: d2 } + } else { + return null + } +} + export const intersectionY = (s: Vec, v: Vec, x: number, yMin: number, yMax: number) => { v = v.normalize() const d = (x - s.x) / v.x diff --git a/src/rayTrace.ts b/src/rayTrace.ts new file mode 100644 index 0000000..bdb794f --- /dev/null +++ b/src/rayTrace.ts @@ -0,0 +1,83 @@ +import { cross, crossAngle, dot, fGaussian, intersectionCLNearest, intersectionY, vec, vecRad, type Vec } from "./math"; + +export const collisionLens = (s: Vec, v: Vec, cx: number, r: number, h: number, ni: number, no: number) => { + v = v.normalize() + const pl = intersectionCLNearest(cx, r, s, v) + if (pl === null) { + return null + } + const lx = Math.sqrt(r * r - h * h) + const e1 = r > 0 ? vec(-lx, h) : vec(lx, h) + const e2 = r > 0 ? vec(-lx, -h) : vec(lx, -h) + const n = vec(pl.p.x - cx, pl.p.y) + if (cross(n, e1) * cross(n, e2) < 0 && dot(n, e1) > 0 && dot(n, e2) > 0) { + return { + p: pl.p, + d: pl.d, + vn: () => { + if (dot(n, v) < 0) { + // Outside to inside + const phi1 = crossAngle(v, n) + const phi2 = Math.asin(Math.sin(phi1) * no / ni) + const theta = Math.atan2(n.y, n.x) + phi2 + Math.PI + return vecRad(theta) + } else { + // Inside to outside + const phi1 = crossAngle(v.minus(), n) + const phi2 = Math.asin(Math.sin(phi1) * ni / no) + const theta = Math.atan2(n.y, n.x) + phi2 + return vecRad(theta) + } + } + } + } else { + return null + } +} + +export const collisionIdealLens = (s: Vec, v: Vec, x: number, h: number, f: number) => { + const pl = intersectionY(s, v, x, -h, h) + if (pl.p === null) { + return null + } else { + return { + p: pl.p, + d: pl.d, + vn: () => { + const ss = fGaussian(f, vec(s.x - x, s.y)) + ss.x += x + if (s.x > x - f) { + if (f > 0) { + return ss.sub(pl.p).normalize().minus() + } else { + return ss.sub(pl.p).normalize() + } + } else { + if (f > 0) { + return ss.sub(pl.p).normalize() + } else { + return ss.sub(pl.p).normalize().minus() + } + } + } + } + } +} + +export const collisionAperture = (s: Vec, v: Vec, x: number, rMin: number, rMax: number) => { + const pa = intersectionY(s, v, x, -rMax, rMax) + if (pa.p !== null) { + if (pa.p.y < Math.abs(rMin)) { + return { + p: pa.p, + d: pa.d, + } + } else { + return null + } + } +} + +const collisionAll = (s: Vec, v: Vec) => { + +} \ No newline at end of file From 7f2863f1af47d9ee3552734829ce75781c9041d2 Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 00:02:06 +0900 Subject: [PATCH 02/11] Ideal lens both side --- src/rayTrace.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/rayTrace.ts b/src/rayTrace.ts index bdb794f..09dd619 100644 --- a/src/rayTrace.ts +++ b/src/rayTrace.ts @@ -44,21 +44,17 @@ export const collisionIdealLens = (s: Vec, v: Vec, x: number, h: number, f: numb p: pl.p, d: pl.d, vn: () => { - const ss = fGaussian(f, vec(s.x - x, s.y)) + const ff = x - s.x > 0 ? f : -f + const ss = fGaussian(ff, vec(s.x - x, s.y)) ss.x += x - if (s.x > x - f) { - if (f > 0) { - return ss.sub(pl.p).normalize().minus() - } else { - return ss.sub(pl.p).normalize() - } - } else { - if (f > 0) { - return ss.sub(pl.p).normalize() - } else { - return ss.sub(pl.p).normalize().minus() - } + ss.inplaceSub(pl.p).inplaceNormalize() + if (Math.abs(x - s.x) < f) { + ss.inplaceMinus() + } + if (f < 0) { + ss.inplaceMinus() } + return ss } } } From 35fbb73f2c4b809bca5b2519f9e26dd4f48e5e03 Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 00:09:50 +0900 Subject: [PATCH 03/11] Fix collisionLens --- src/math.ts | 23 +++++++++-------------- src/rayTrace.ts | 47 ++++++++++++++++++++++++----------------------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/math.ts b/src/math.ts index fdc292c..772d779 100644 --- a/src/math.ts +++ b/src/math.ts @@ -220,31 +220,26 @@ export const intersectionCC = (c1: Vec, r1: number, c2: Vec, r2: number) => { return [c1.add(n.rotate(theta).inplaceMul(r1)), c1.add(n.rotate(-theta).inplaceMul(r1))] } -export const intersectionCLNearest = (cx: number, r: number, s: Vec, v: Vec) => { +export const intersectionCL = (cx: number, r: number, s: Vec, v: Vec) => { const absR = Math.abs(r) const n = v.normalize() const a = 1; const b = 2 * ((s.x - cx) * n.x + (s.y) * n.y); const c = (s.x - cx) * (s.x - cx) + s.y * s.y - absR * absR; const cond = b * b - 4 * a * c; + const res: { p: Vec, d: number }[] = [] if (cond < 0) { - return null + return res } const d1 = (-b - Math.sqrt(cond)) / (2 * a); const d2 = (-b + Math.sqrt(cond)) / (2 * a); - if (d1 >= 0 && d2 >= 0) { - if (d1 < d2) { - return { p: s.add(n.mul(d1)), d: d1 } - } else { - return { p: s.add(n.mul(d2)), d: d2 } - } - } else if (d1 >= 0) { - return { p: s.add(n.mul(d1)), d: d1 } - } else if (d2 >= 0) { - return { p: s.add(n.mul(d2)), d: d2 } - } else { - return null + if (d1 >= 0) { + res.push({ p: s.add(n.mul(d1)), d: d1 }) + } + if (d2 >= 0) { + res.push({ p: s.add(n.mul(d2)), d: d2 }) } + return res } export const intersectionY = (s: Vec, v: Vec, x: number, yMin: number, yMax: number) => { diff --git a/src/rayTrace.ts b/src/rayTrace.ts index 09dd619..a72e499 100644 --- a/src/rayTrace.ts +++ b/src/rayTrace.ts @@ -1,38 +1,39 @@ -import { cross, crossAngle, dot, fGaussian, intersectionCLNearest, intersectionY, vec, vecRad, type Vec } from "./math"; +import { cross, crossAngle, dot, fGaussian, intersectionCL, intersectionY, vec, vecRad, type Vec } from "./math"; export const collisionLens = (s: Vec, v: Vec, cx: number, r: number, h: number, ni: number, no: number) => { v = v.normalize() - const pl = intersectionCLNearest(cx, r, s, v) - if (pl === null) { + const pls = intersectionCL(cx, r, s, v) + if (pls.length === 0) { return null } const lx = Math.sqrt(r * r - h * h) const e1 = r > 0 ? vec(-lx, h) : vec(lx, h) const e2 = r > 0 ? vec(-lx, -h) : vec(lx, -h) - const n = vec(pl.p.x - cx, pl.p.y) - if (cross(n, e1) * cross(n, e2) < 0 && dot(n, e1) > 0 && dot(n, e2) > 0) { - return { - p: pl.p, - d: pl.d, - vn: () => { - if (dot(n, v) < 0) { - // Outside to inside - const phi1 = crossAngle(v, n) - const phi2 = Math.asin(Math.sin(phi1) * no / ni) - const theta = Math.atan2(n.y, n.x) + phi2 + Math.PI - return vecRad(theta) - } else { - // Inside to outside - const phi1 = crossAngle(v.minus(), n) - const phi2 = Math.asin(Math.sin(phi1) * ni / no) - const theta = Math.atan2(n.y, n.x) + phi2 - return vecRad(theta) + for (const pl of pls) { + const n = vec(pl.p.x - cx, pl.p.y) + if (cross(n, e1) * cross(n, e2) < 0 && dot(n, e1) > 0 && dot(n, e2) > 0) { + return { + p: pl.p, + d: pl.d, + vn: () => { + if (dot(n, v) < 0) { + // Outside to inside + const phi1 = crossAngle(v, n) + const phi2 = Math.asin(Math.sin(phi1) * no / ni) + const theta = Math.atan2(n.y, n.x) + phi2 + Math.PI + return vecRad(theta) + } else { + // Inside to outside + const phi1 = crossAngle(v.minus(), n) + const phi2 = Math.asin(Math.sin(phi1) * ni / no) + const theta = Math.atan2(n.y, n.x) + phi2 + return vecRad(theta) + } } } } - } else { - return null } + return null } export const collisionIdealLens = (s: Vec, v: Vec, x: number, h: number, f: number) => { From 0ab0e63a8f3bc1e3b51e33c362a96392b0e3a3e5 Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 01:22:29 +0900 Subject: [PATCH 04/11] Some elements support --- src/Canvas.vue | 16 ++++++-- src/math.ts | 2 +- src/rayTrace.ts | 99 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/src/Canvas.vue b/src/Canvas.vue index 25fc36d..7077bfb 100644 --- a/src/Canvas.vue +++ b/src/Canvas.vue @@ -5,7 +5,7 @@ import { state, lights, lensGroups, sensor, sensorData, apple, options, style, i import { Vec, vec, vecRad, getIntersectionLens, crossAngle, fGaussian, intersectionSS, intersectionX, intersectionY, calcLensNWavelength } from './math' import { Light } from './type' -import { collisionIdealLens, collisionLens } from './rayTrace'; +import { collisionIdealLens, collisionLens, rayTrace } from './rayTrace'; // Reference to the canvas const canvas = ref() @@ -19,13 +19,13 @@ if (!ctx) { throw new Error() } -const drawSegment = (p: Vec, v: Vec, length: number) => { - const q = p.add(v.normalize().mul(length)) +const drawSegment = (p: Vec, q: Vec) => { + // const q = p.add(v.normalize().mul(length)) ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(q.x, q.y); ctx.stroke(); - return q + // return q }; const intersectionBody = (s: Vec, v: Vec) => { @@ -86,6 +86,13 @@ const intersectionBody = (s: Vec, v: Vec) => { } const drawRay = (s: Vec, v: Vec, color: number, sensorDataTmp: any[]) => { + const ps = rayTrace(s.copy(), v.copy()) + ps.forEach((p) => { + drawSegment(s, p) + s = p + }) + return + /* // Multiple lens for (let lensIdx = 0; lensIdx < lensesSorted.value.length; lensIdx++) { const lens = lensesSorted.value[lensIdx] @@ -205,6 +212,7 @@ const drawRay = (s: Vec, v: Vec, color: number, sensorDataTmp: any[]) => { // to infinity drawSegment(s, v, infR.value) return + */ } const draw = () => { diff --git a/src/math.ts b/src/math.ts index 772d779..8911b81 100644 --- a/src/math.ts +++ b/src/math.ts @@ -150,7 +150,7 @@ export const crossAngle = (p: Vec, q: Vec) => { // Geometry //================================ -const eps = 1e-9 +export const eps = 1e-9 // ccw const ccw = (a: Vec, b: Vec, c: Vec) => { diff --git a/src/rayTrace.ts b/src/rayTrace.ts index a72e499..f5ba344 100644 --- a/src/rayTrace.ts +++ b/src/rayTrace.ts @@ -1,4 +1,5 @@ -import { cross, crossAngle, dot, fGaussian, intersectionCL, intersectionY, vec, vecRad, type Vec } from "./math"; +import { infR, lensFs, lensRs, lensesSorted, options, sensor } from "./globals"; +import { cross, crossAngle, dot, eps, fGaussian, intersectionCL, intersectionSS, intersectionY, vec, vecRad, type Vec } from "./math"; export const collisionLens = (s: Vec, v: Vec, cx: number, r: number, h: number, ni: number, no: number) => { v = v.normalize() @@ -69,12 +70,104 @@ export const collisionAperture = (s: Vec, v: Vec, x: number, rMin: number, rMax: p: pa.p, d: pa.d, } - } else { - return null } } + return null +} + +const collisionSensor = (s: Vec, v: Vec) => { + const ps = intersectionSS(s, s.add(v.mul(infR.value)), sensor.value.s, sensor.value.t) + if (ps !== null) { + return { + p: ps, + d: ps.sub(s).length(), + isSensor: true, + } + } else { + return null + } } const collisionAll = (s: Vec, v: Vec) => { + //-------------------------------- + // Collisions + //-------------------------------- + + let ps: ({ p: Vec, d: number, isSensor?: boolean, vn?: () => Vec } | null)[] = [] + lensesSorted.value.forEach((lens, i) => { + // Lenses + const h = lensRs.value[i] + const f = lensFs.value[i] + const n = lens.n + const xm = (lens.x1 + lens.x2) / 2 + if (options.value.lensIdeal) { + ps.push(collisionIdealLens(s, v, xm, h, f)) + } else { + { + const ni = lens.R1 > 0 ? n : 1 + const no = lens.R1 > 0 ? 1 : n + ps.push(collisionLens(s, v, lens.x1 + lens.R1, lens.R1, h, ni, no)) + } + { + const ni = lens.R2 > 0 ? 1 : n + const no = lens.R2 > 0 ? n : 1 + ps.push(collisionLens(s, v, lens.x2 + lens.R2, lens.R2, h, ni, no)) + } + } + + // Lens aperture + // ps.push(collisionAperture(s, v, xm, h * lens.aperture, h)) + + // Lens to body + // TODO + }) + + // Sensor + if (options.value.sensor) { + ps.push(collisionSensor(s, v)) + } + + //-------------------------------- + // Find nearest collision + //-------------------------------- + ps = ps.filter((p) => p !== null && p.d > eps) + ps.sort((a, b) => { + if (a !== null && b !== null) { + return a.d - b.d + } else { + return 0 + } + }) + + if (ps.length > 0) { + return ps[0] + } else { + return null + } +} + +export const rayTrace = (s: Vec, v: Vec) => { + const ps = [] + while (true) { + // for (let i = 0; i < 100; i++) { + const c = collisionAll(s, v) + + if (c === null) { + ps.push(s.add(v.mul(infR.value))) + break + } + + if (c.isSensor) { + ps.push(c.p) + break + } + + ps.push(c.p) + s = c.p + if (c.vn) { + v = c.vn() + } + } + return ps } \ No newline at end of file From a6f704145f8329cb2826eba8bdfe0aa7b0f294fc Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 08:11:02 +0900 Subject: [PATCH 05/11] Sensor --- src/Canvas.vue | 8 ++++++-- src/rayTrace.ts | 10 +++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Canvas.vue b/src/Canvas.vue index 7077bfb..38a10ee 100644 --- a/src/Canvas.vue +++ b/src/Canvas.vue @@ -88,8 +88,12 @@ const intersectionBody = (s: Vec, v: Vec) => { const drawRay = (s: Vec, v: Vec, color: number, sensorDataTmp: any[]) => { const ps = rayTrace(s.copy(), v.copy()) ps.forEach((p) => { - drawSegment(s, p) - s = p + drawSegment(s, p.p) + if (p.isSensor) { + sensorDataTmp.push({y: p.p.sub(sensor.value.s).length(), color}) + return + } + s = p.p }) return /* diff --git a/src/rayTrace.ts b/src/rayTrace.ts index f5ba344..2e272c1 100644 --- a/src/rayTrace.ts +++ b/src/rayTrace.ts @@ -148,21 +148,21 @@ const collisionAll = (s: Vec, v: Vec) => { export const rayTrace = (s: Vec, v: Vec) => { const ps = [] - while (true) { - // for (let i = 0; i < 100; i++) { + const maxItr = 100 + for (let i = 0; i < maxItr; i++) { const c = collisionAll(s, v) if (c === null) { - ps.push(s.add(v.mul(infR.value))) + ps.push({p: s.add(v.mul(infR.value))}) break } if (c.isSensor) { - ps.push(c.p) + ps.push({p: c.p, isSensor: true}) break } - ps.push(c.p) + ps.push({p: c.p}) s = c.p if (c.vn) { v = c.vn() From 42d7c76d3b0b9cb36b69788e507b07cf3754aaa3 Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 08:20:16 +0900 Subject: [PATCH 06/11] Lens aperture --- src/rayTrace.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/rayTrace.ts b/src/rayTrace.ts index 2e272c1..fdb8708 100644 --- a/src/rayTrace.ts +++ b/src/rayTrace.ts @@ -65,10 +65,11 @@ export const collisionIdealLens = (s: Vec, v: Vec, x: number, h: number, f: numb export const collisionAperture = (s: Vec, v: Vec, x: number, rMin: number, rMax: number) => { const pa = intersectionY(s, v, x, -rMax, rMax) if (pa.p !== null) { - if (pa.p.y < Math.abs(rMin)) { + if (Math.abs(pa.p.y) > Math.abs(rMin)) { return { p: pa.p, d: pa.d, + isEnd: true, } } } @@ -93,15 +94,15 @@ const collisionAll = (s: Vec, v: Vec) => { // Collisions //-------------------------------- - let ps: ({ p: Vec, d: number, isSensor?: boolean, vn?: () => Vec } | null)[] = [] + let ps: ({ p: Vec, d: number, isSensor?: boolean, isEnd?: boolean, vn?: () => Vec } | null)[] = [] lensesSorted.value.forEach((lens, i) => { - // Lenses + // Lense const h = lensRs.value[i] const f = lensFs.value[i] const n = lens.n const xm = (lens.x1 + lens.x2) / 2 if (options.value.lensIdeal) { - ps.push(collisionIdealLens(s, v, xm, h, f)) + ps.push(collisionIdealLens(s, v, xm, h * lens.aperture, f)) } else { { const ni = lens.R1 > 0 ? n : 1 @@ -116,7 +117,7 @@ const collisionAll = (s: Vec, v: Vec) => { } // Lens aperture - // ps.push(collisionAperture(s, v, xm, h * lens.aperture, h)) + ps.push(collisionAperture(s, v, xm, h * lens.aperture, h)) // Lens to body // TODO @@ -153,16 +154,21 @@ export const rayTrace = (s: Vec, v: Vec) => { const c = collisionAll(s, v) if (c === null) { - ps.push({p: s.add(v.mul(infR.value))}) + ps.push({ p: s.add(v.mul(infR.value)) }) + break + } + + if (c.isEnd) { + ps.push({ p: c.p }) break } if (c.isSensor) { - ps.push({p: c.p, isSensor: true}) + ps.push({ p: c.p, isSensor: true }) break } - ps.push({p: c.p}) + ps.push({ p: c.p }) s = c.p if (c.vn) { v = c.vn() From 21e4a6196dcc6e6dc356d6376f2b3c73ad441930 Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 08:37:38 +0900 Subject: [PATCH 07/11] Body and aperture --- src/rayTrace.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/rayTrace.ts b/src/rayTrace.ts index fdb8708..83b3764 100644 --- a/src/rayTrace.ts +++ b/src/rayTrace.ts @@ -1,5 +1,5 @@ -import { infR, lensFs, lensRs, lensesSorted, options, sensor } from "./globals"; -import { cross, crossAngle, dot, eps, fGaussian, intersectionCL, intersectionSS, intersectionY, vec, vecRad, type Vec } from "./math"; +import { aperture, body, infR, lensBacks, lensFronts, lensFs, lensRs, lensesSorted, options, sensor } from "./globals"; +import { cross, crossAngle, dot, eps, fGaussian, intersectionCL, intersectionSS, intersectionX, intersectionY, vec, vecRad, type Vec } from "./math"; export const collisionLens = (s: Vec, v: Vec, cx: number, r: number, h: number, ni: number, no: number) => { v = v.normalize() @@ -89,12 +89,46 @@ const collisionSensor = (s: Vec, v: Vec) => { } } +const collisionX = (s: Vec, v: Vec, y: number, xMin: number, xMax: number) => { + const ps = intersectionX(s, v, y, xMin, xMax) + if (ps.p !== null) { + return { + p: ps.p, + d: ps.d, + isEnd: true, + } + } + return null +} + +const collisionY = (s: Vec, v: Vec, x: number, yMin: number, yMax: number) => { + const ps = intersectionY(s, v, x, yMin, yMax) + if (ps.p !== null) { + return { + p: ps.p, + d: ps.d, + isEnd: true, + } + } + return null +} + const collisionAll = (s: Vec, v: Vec) => { //-------------------------------- // Collisions //-------------------------------- - let ps: ({ p: Vec, d: number, isSensor?: boolean, isEnd?: boolean, vn?: () => Vec } | null)[] = [] + + // Body outline + if (options.value.body && body.value.r && body.value.front && body.value.back) { + ps.push(collisionX(s, v, -body.value.r, body.value.front, body.value.back)) + ps.push(collisionX(s, v, body.value.r, body.value.front, body.value.back)) + if (options.value.sensor) { + ps.push(collisionY(s, v, body.value.back, -body.value.r, body.value.r)) + } + } + + // Around lenses lensesSorted.value.forEach((lens, i) => { // Lense const h = lensRs.value[i] @@ -120,9 +154,21 @@ const collisionAll = (s: Vec, v: Vec) => { ps.push(collisionAperture(s, v, xm, h * lens.aperture, h)) // Lens to body + if (options.value.body && body.value.r !== null) { + const x = options.value.lensIdeal ? xm : lensFronts.value[i] + ps.push(collisionAperture(s, v, x, h, body.value.r)) + } + + // Lens upper and bottom + ps.push(collisionX(s, v, -h, lensFronts.value[i], lensBacks.value[i])) // TODO }) + // Aperture + if (options.value.aperture && body.value.r) { + ps.push(collisionAperture(s, v, aperture.value.x, aperture.value.r, body.value.r)) + } + // Sensor if (options.value.sensor) { ps.push(collisionSensor(s, v)) From 6e5bc6a47f48ba7e21b903ee78660cab2a990aaa Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 08:43:07 +0900 Subject: [PATCH 08/11] Refactoring --- src/Canvas.vue | 188 ++---------------------------------------------- src/math.ts | 28 -------- src/rayTrace.ts | 8 ++- 3 files changed, 9 insertions(+), 215 deletions(-) diff --git a/src/Canvas.vue b/src/Canvas.vue index 38a10ee..9373b25 100644 --- a/src/Canvas.vue +++ b/src/Canvas.vue @@ -2,10 +2,10 @@ import { watch, onMounted, ref } from 'vue' import { state, lights, lensGroups, sensor, sensorData, apple, options, style, infR, lensesSorted, lensRs, lensFs, body, lensFronts, aperture, lensBacks } from './globals' -import { Vec, vec, vecRad, getIntersectionLens, crossAngle, fGaussian, intersectionSS, intersectionX, intersectionY, calcLensNWavelength } from './math' +import { Vec, vec, vecRad } from './math' import { Light } from './type' -import { collisionIdealLens, collisionLens, rayTrace } from './rayTrace'; +import { rayTrace } from './rayTrace'; // Reference to the canvas const canvas = ref() @@ -20,203 +20,23 @@ if (!ctx) { } const drawSegment = (p: Vec, q: Vec) => { - // const q = p.add(v.normalize().mul(length)) ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(q.x, q.y); ctx.stroke(); - // return q }; -const intersectionBody = (s: Vec, v: Vec) => { - v = v.normalize() - let ps = [] - - // Outlines - if (options.value.body && body.value.r && body.value.front && body.value.back) { - ps.push(intersectionX(s, v, -body.value.r, body.value.front, body.value.back)) - ps.push(intersectionX(s, v, body.value.r, body.value.front, body.value.back)) - if (options.value.sensor) { - ps.push(intersectionY(s, v, body.value.back, -body.value.r, body.value.r)) - } - } - - // Lenses - if (options.value.lens) { - lensesSorted.value.forEach((lens, idx) => { - const lensR = lensRs.value[idx] - const front = lensFronts.value[idx] - const back = lensBacks.value[idx] - const xm = (lens.x1 + lens.x2) / 2 - // Lens to body - if (options.value.body && body.value.r) { - if (options.value.lensIdeal) { - ps.push(intersectionY(s, v, xm, -body.value.r, -lensR)) - ps.push(intersectionY(s, v, xm, lensR, body.value.r)) - } else { - ps.push(intersectionY(s, v, front, -body.value.r, -lensR)) - ps.push(intersectionY(s, v, front, lensR, body.value.r)) - } - } - // Upper / Bottom - if (!options.value.lensIdeal) { - ps.push(intersectionX(s, v, -lensR, front, back)) - ps.push(intersectionX(s, v, lensR, front, back)) - } - // Aperture - ps.push(intersectionY(s, v, xm, -lensR, -lensR * lens.aperture)) - ps.push(intersectionY(s, v, xm, lensR * lens.aperture, lensR)) - }) - } - - // Aperture - if (options.value.aperture && body.value.r) { - ps.push(intersectionY(s, v, aperture.value.x, -body.value.r, -aperture.value.r)) - ps.push(intersectionY(s, v, aperture.value.x, aperture.value.r, body.value.r)) - } - - ps = ps.filter((p) => p.p !== null) - ps.sort((p, q) => p.d - q.d) - - if (ps.length === 0) { - return { p: null, d: null } - } else { - return ps[0] - } -} - const drawRay = (s: Vec, v: Vec, color: number, sensorDataTmp: any[]) => { const ps = rayTrace(s.copy(), v.copy()) ps.forEach((p) => { drawSegment(s, p.p) if (p.isSensor) { - sensorDataTmp.push({y: p.p.sub(sensor.value.s).length(), color}) + sensorDataTmp.push({ y: p.p.sub(sensor.value.s).length(), color }) return } s = p.p }) return - /* - // Multiple lens - for (let lensIdx = 0; lensIdx < lensesSorted.value.length; lensIdx++) { - const lens = lensesSorted.value[lensIdx] - const s0 = s.copy() - const xm = (lens.x1 + lens.x2) / 2 - const f = lensFs.value[lensIdx] - const r = lensRs.value[lensIdx] - const n = options.value.wavelength ? calcLensNWavelength(lens.n, color) : lens.n - let innerLens = false - - //-------------------------------- - // Collision to lens surface (left-side) - //-------------------------------- - if (!options.value.lensIdeal && options.value.lens) { - // Center of lens curvature circle - const c = vec(lens.x1 + lens.R1, 0) - - const ni = lens.R1 > 0 ? n : 1 - const no = lens.R1 > 0 ? 1 : n - const pl = collisionLens(s, v, c.x, lens.R1, r, ni, no) - const pb = intersectionBody(s, v) - if ((pb.p && pl !== null && pb.d < pl.d) || (pl === null && pb.p)) { - v = pb.p.sub(s) - drawSegment(s, v, v.length()) - return - } - - if (pl !== null) { - const p = pl.p - v = p.sub(s) - s = drawSegment(s, v, v.length()) - v = pl.vn() - innerLens = true - } - } - - //-------------------------------- - // Collision to lens surface (right-side) - //-------------------------------- - if (!options.value.lensIdeal && innerLens) { - // Center of lens curvature circle - const c = vec(lens.x2 + lens.R2, 0) - - const ni = lens.R2 > 0 ? 1 : n - const no = lens.R2 > 0 ? n : 1 - const pl = collisionLens(s, v, c.x, lens.R2, r, ni, no) - const pb = intersectionBody(s, v) - if ((pb.p && pl !== null && pb.d < pl.d) || (pl === null && pb.p)) { - v = pb.p.sub(s) - drawSegment(s, v, v.length()) - return - } - if (pl !== null) { - const p = pl.p - v = p.sub(s) - const nextS = drawSegment(s, v, v.length()) - v = pl.vn() - s = nextS - } - } - - //-------------------------------- - // Collision to ideal lens - //-------------------------------- - if (options.value.lensIdeal && options.value.lens) { - const pl = collisionIdealLens(s, v, xm, r, f) - const pb = intersectionBody(s, v) - // TODO: Find better condition - const eps = 1e-9 - if ((pb.p && pl !== null && pb.d < pl.d + eps) || (pl === null && pb.p)) { - v = pb.p.sub(s) - drawSegment(s, v, v.length()) - return - } - if (pl !== null) { - const p = pl.p - v = p.sub(s) - s = drawSegment(s, v, v.length()) - v = pl.vn() - } - - // Optimization - if (lensIdx === 0 && pl === null && !pb.p) { - drawSegment(s, v, infR.value) - return - } - } - } - - //-------------------------------- - // Collision to sensor - //-------------------------------- - if (options.value.sensor) { - const ps = intersectionSS(s, s.add(v.mul(infR.value)), sensor.value.s, sensor.value.t) - const pb = intersectionBody(s, v) - if ((pb.p && ps && pb.d < (ps.sub(s).length())) || (!ps && pb.p)) { - v = pb.p.sub(s) - drawSegment(s, v, v.length()) - return - } - if (ps) { - const p = ps - v = p.sub(s) - drawSegment(s, v, v.length()) - sensorDataTmp.push({ y: p.sub(sensor.value.s).length(), color }) - return - } - } - - const pb = intersectionBody(s, v) - if (pb.p) { - v = pb.p.sub(s) - drawSegment(s, v, v.length()) - return - } - - // to infinity - drawSegment(s, v, infR.value) - return - */ } const draw = () => { @@ -231,7 +51,7 @@ const draw = () => { const sensorDataTmp: any[] = [] //================================ - // Light path drawing like ray-tracing + // Light path drawing //================================ // Light sources diff --git a/src/math.ts b/src/math.ts index 8911b81..16df430 100644 --- a/src/math.ts +++ b/src/math.ts @@ -276,34 +276,6 @@ export const intersectionX = (s: Vec, v: Vec, y: number, xMin: number, xMax: num // Lens //================================ -export const getIntersectionLens = (s: Vec, v: Vec, cl: Vec, r: number /* lens diameter */, R: number /* lens curvature radius */) => { - const absR = Math.abs(R) - const n = v.normalize() - const a = 1; - const b = 2 * ((s.x - cl.x) * n.x + (s.y - cl.y) * n.y); - const c = Math.pow(s.x - cl.x, 2) + Math.pow(s.y - cl.y, 2) - absR * absR; - const cond = b * b - 4 * a * c; - if (cond < 0) { - return { p: null, d: 0 } - } - const d1 = (-b - Math.sqrt(cond)) / (2 * a); - const d2 = (-b + Math.sqrt(cond)) / (2 * a); - const d = R > 0 ? d1 : d2; - const tx = s.x + d * n.x; - const ty = s.y + d * n.y; - // TODO: Workarounds - if (Math.abs(ty) > r || d < 0) { - return { p: null, d } - } - if (R >= 0 && tx >= cl.x) { - return { p: null, d } - } - if (R < 0 && tx < cl.x) { - return { p: null, d } - } - return { p: vec(tx, ty), d } -} - export const fGaussian = (f: number, a: Vec) => { const bx = f * a.x / (a.x + f) const by = a.y * (bx / a.x) diff --git a/src/rayTrace.ts b/src/rayTrace.ts index 83b3764..815e909 100644 --- a/src/rayTrace.ts +++ b/src/rayTrace.ts @@ -1,7 +1,7 @@ import { aperture, body, infR, lensBacks, lensFronts, lensFs, lensRs, lensesSorted, options, sensor } from "./globals"; import { cross, crossAngle, dot, eps, fGaussian, intersectionCL, intersectionSS, intersectionX, intersectionY, vec, vecRad, type Vec } from "./math"; -export const collisionLens = (s: Vec, v: Vec, cx: number, r: number, h: number, ni: number, no: number) => { +const collisionLens = (s: Vec, v: Vec, cx: number, r: number, h: number, ni: number, no: number) => { v = v.normalize() const pls = intersectionCL(cx, r, s, v) if (pls.length === 0) { @@ -37,7 +37,7 @@ export const collisionLens = (s: Vec, v: Vec, cx: number, r: number, h: number, return null } -export const collisionIdealLens = (s: Vec, v: Vec, x: number, h: number, f: number) => { +const collisionIdealLens = (s: Vec, v: Vec, x: number, h: number, f: number) => { const pl = intersectionY(s, v, x, -h, h) if (pl.p === null) { return null @@ -62,7 +62,7 @@ export const collisionIdealLens = (s: Vec, v: Vec, x: number, h: number, f: numb } } -export const collisionAperture = (s: Vec, v: Vec, x: number, rMin: number, rMax: number) => { +const collisionAperture = (s: Vec, v: Vec, x: number, rMin: number, rMax: number) => { const pa = intersectionY(s, v, x, -rMax, rMax) if (pa.p !== null) { if (Math.abs(pa.p.y) > Math.abs(rMin)) { @@ -215,6 +215,8 @@ export const rayTrace = (s: Vec, v: Vec) => { } ps.push({ p: c.p }) + + // Next ray s = c.p if (c.vn) { v = c.vn() From 9146d59de2bf1f27f712768de0c57d5535008695 Mon Sep 17 00:00:00 2001 From: monman53 Date: Thu, 4 Jul 2024 08:46:49 +0900 Subject: [PATCH 09/11] Light any place --- src/SVG/Aperture.vue | 6 ++---- src/SVG/Body.vue | 5 +---- src/SVG/Lens.vue | 9 ++------- src/SVG/LensGroup.vue | 8 ++------ src/globals.ts | 14 -------------- 5 files changed, 7 insertions(+), 35 deletions(-) diff --git a/src/SVG/Aperture.vue b/src/SVG/Aperture.vue index a6ef8c5..6b829ca 100644 --- a/src/SVG/Aperture.vue +++ b/src/SVG/Aperture.vue @@ -1,5 +1,5 @@