Skip to content

Commit

Permalink
Some refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
monman53 committed Jun 14, 2024
1 parent d04cb7e commit 4c01542
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 98 deletions.
49 changes: 17 additions & 32 deletions src/Canvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { watch, onMounted, ref } from 'vue'
import { state, lights, lens, sensor, sensorData, apple, options, style, lensR, lensD, infR } from './globals'
import { Vec, vec, getIntersectionY, getIntersectionLens, crossAngle } from './math'
import { Vec, vec, getIntersectionY, getIntersectionLens, crossAngle, fGaussian } from './math'
// Reference to the canvas
const canvas = ref()
Expand All @@ -16,13 +16,6 @@ if (!ctx) {
throw new Error()
}
// const drawSegment = (sx: number, sy: number, tx: number, ty: number) => {
// ctx.beginPath();
// ctx.moveTo(sx, sy);
// ctx.lineTo(tx, ty);
// ctx.stroke();
// };
const drawSegment = (p: Vec, v: Vec, length: number) => {
const q = p.copy().add(v.copy().normalize().mul(length))
ctx.beginPath();
Expand All @@ -32,7 +25,7 @@ const drawSegment = (p: Vec, v: Vec, length: number) => {
return q
};
const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sensorDataTmp: any[]) => {
const drawRay = (image: Vec, light: any, s: Vec, v: Vec, sensorDataTmp: any[]) => {
let innerLens = false
//--------------------------------
Expand All @@ -42,7 +35,7 @@ const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sen
// Center of lens curvature circle
const c = vec(lens.value.x - lensD.value / 2 + lensR.value, 0)
const p = getIntersectionLens(s.x, s.y, v, c.x, c.y, lens.value.r, lensR.value, true)
const p = getIntersectionLens(s, v, c, lens.value.r, lensR.value, true)
if (p) {
v = p.copy().sub(s)
s = drawSegment(s, v, v.length())
Expand All @@ -61,7 +54,7 @@ const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sen
// Collision to aperture
//--------------------------------
if (options.value.aperture) {
const p = getIntersectionY(s.x, s.y, v, lens.value.x, -lens.value.r, lens.value.r)
const p = getIntersectionY(s, v, lens.value.x, -lens.value.r, lens.value.r)
if (p) {
const upperHit = p.y > lens.value.aperture * lens.value.r
const lowerHit = p.y < -lens.value.aperture * lens.value.r
Expand All @@ -80,7 +73,7 @@ const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sen
// Center of lens curvature circle
const c = vec(lens.value.x + lensD.value / 2 - lensR.value, 0)
const p = getIntersectionLens(s.x, s.y, v, c.x, c.y, lens.value.r, lensR.value, false)
const p = getIntersectionLens(s, v, c, lens.value.r, lensR.value, false)
if (p) {
v = p.copy().sub(s)
const nextS = drawSegment(s, v, v.length())
Expand All @@ -98,14 +91,14 @@ const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sen
// Collision to ideal lens
//--------------------------------
if (options.value.lensIdeal && options.value.lens) {
const p = getIntersectionY(s.x, s.y, v, lens.value.x, -lens.value.r, lens.value.r)
const p = getIntersectionY(s, v, lens.value.x, -lens.value.r, lens.value.r)
if (p) {
v = p.copy().sub(s)
s = drawSegment(s, v, v.length())
// Refracted ray
let theta = Math.atan2(imageY - s.y, imageX);
if (imageX === Infinity) {
let theta = Math.atan2(image.y - s.y, image.x);
if (image.x === Infinity) {
theta = Math.atan2(-light.y, -(light.x - lens.value.x));
}
if (lens.value.x - lens.value.f < light.x) {
Expand All @@ -121,7 +114,7 @@ const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sen
if (options.value.body) {
// Upper
{
const p = getIntersectionY(s.x, s.y, v, lens.value.x, lens.value.r, infR.value);
const p = getIntersectionY(s, v, lens.value.x, lens.value.r, infR.value);
if (p) {
v = p.copy().sub(s)
drawSegment(s, v, v.length())
Expand All @@ -130,7 +123,7 @@ const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sen
}
// Lower
{
const p = getIntersectionY(s.x, s.y, v, lens.value.x, -infR.value, -lens.value.r);
const p = getIntersectionY(s, v, lens.value.x, -infR.value, -lens.value.r);
if (p) {
v = p.copy().sub(s)
drawSegment(s, v, v.length())
Expand All @@ -143,7 +136,7 @@ const drawRay = (imageX: number, imageY: number, light: any, s: Vec, v: Vec, sen
// Collision to sensor
//--------------------------------
if (options.value.sensor) {
const p = getIntersectionY(s.x, s.y, v, sensor.value.x, -sensor.value.r, sensor.value.r);
const p = getIntersectionY(s, v, sensor.value.x, -sensor.value.r, sensor.value.r);
if (p) {
v = p.copy().sub(s)
drawSegment(s, v, v.length())
Expand Down Expand Up @@ -178,20 +171,16 @@ const draw = () => {
ctx.lineWidth = style.value.rayWidth
// Find image position of the light source
const s1 = lens.value.x - light.x;
const s2 = lens.value.f * s1 / (s1 - lens.value.f);
const imageX = s2;
const imageY = -light.y * (s2 / s1)
const image = fGaussian(lens.value.f, lens.value.x - light.x, -light.y)
// Draw 2^nRaysLog rays from light center
const nRays = (1 << params.nRaysLog);
for (let i = 0; i < nRays; i++) {
// Initial position and direction
const sx = light.x
const sy = light.y
const s = vec(light.x, light.y)
const theta = 2 * Math.PI * i / nRays
const v = vec(Math.cos(theta), Math.sin(theta))
drawRay(imageX, imageY, light, vec(sx, sy), v, sensorDataTmp)
drawRay(image, light, s, v, sensorDataTmp)
}
}
Expand All @@ -202,20 +191,16 @@ const draw = () => {
ctx.lineWidth = style.value.rayWidth
// Find image position of the light source
const s1 = lens.value.x - light.x;
const s2 = lens.value.f * s1 / (s1 - lens.value.f);
const imageX = s2;
const imageY = -light.y * (s2 / s1)
const image = fGaussian(lens.value.f, lens.value.x - light.x, -light.y)
// Draw 2^nRaysLog rays from light center
const nRays = (1 << params.nRaysLog);
for (let i = 0; i < nRays; i++) {
// Initial position and direction
const sx = light.x
const sy = light.y
const s = vec(light.x, light.y)
const theta = 2 * Math.PI * i / nRays
const v = vec(Math.cos(theta), Math.sin(theta))
drawRay(imageX, imageY, light, vec(sx, sy), v, sensorDataTmp)
drawRay(image, light, s, v, sensorDataTmp)
}
}
}
Expand Down
25 changes: 12 additions & 13 deletions src/SVG/Guideline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { computed } from 'vue'
import { lens, sensor, options, infR } from '../globals'
import { vec } from '../math'
import { vec, fGaussian } from '../math'
// Effective lens radius
const re = computed(() => {
Expand All @@ -17,13 +17,12 @@ const re = computed(() => {
const focal = computed(() => {
const f = lens.value.f;
const b = sensor.value.x - lens.value.x;
const a = f * b / (b - f);
const bx = sensor.value.x - lens.value.x;
const by = sensor.value.r
const focalPosX = lens.value.x - a;
const focalPosSize = sensor.value.r * (a / b);
const a = fGaussian(lens.value.f, bx, by)
return { x: focalPosX, d: focalPosSize }
return { x: lens.value.x - a.x, d: a.y }
})
// Angle of view
Expand Down Expand Up @@ -56,25 +55,25 @@ const aov = computed(() => {
// Depth of field
const dof = computed(() => {
const f = lens.value.f
// const r = lens.value.r
const r = re.value
const delta = lens.value.circleOfConfusion
const b = sensor.value.x - lens.value.x
const a = f * b / (b - f)
const bx = sensor.value.x - lens.value.x
const by = sensor.value.r // Unused
const a = fGaussian(f, bx, by)
// Image space
const bFront = b / (1 + delta / (2 * r)) // Lens side
const bBack = b / (1 - delta / (2 * r))
const bFront = bx / (1 + delta / (2 * r)) // Lens side
const bBack = bx / (1 - delta / (2 * r))
// Object space
const aFront = 1 / (1 / f - 1 / bFront)
const aBack = 1 / (1 / f - 1 / bBack) // Lens side
// Radius of planes
const c = a * (re.value / (re.value + focal.value.d))
const c = a.x * (re.value / (re.value + focal.value.d))
const dFront = re.value / c * (aFront - c)
const dBack = focal.value.d * (aBack / a) + re.value * (1 - aBack / a) // Lens side
const dBack = focal.value.d * (aBack / a.x) + re.value * (1 - aBack / a.x) // Lens side
const inner = { x: aBack, d: dBack } // Lens side
const outer = { x: aFront, d: dFront }
Expand Down
45 changes: 1 addition & 44 deletions src/globals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ref, computed } from "vue"
import { calcLensR } from "./math"

//================================
// States
Expand Down Expand Up @@ -174,50 +175,6 @@ export const style = ref(style0())
// Computed
//================================

const calcLensR = (n: number, f: number, r: number) => {
// This formula is made from lens-maker's formula and d = 2(R-sqrt(R^2-r^2))
const func = (R: number) => {
let res = 0;
res += n * n * Math.pow(R, 4);
res += - 4 * n * (n - 1) * f * Math.pow(R, 3);
res += 4 * (n - 1) * (n - 1) * f * f * (1 - (n - 1) * (n - 1)) * R * R;
res += 4 * Math.pow(n - 1, 4) * f * f * r * r;

return res;
};

// Derivative of func
const funcc = (R: number) => {
let res = 0;
res += 4 * n * n * Math.pow(R, 3);
res += - 12 * n * (n - 1) * f * Math.pow(R, 2);
res += 8 * (n - 1) * (n - 1) * f * f * (1 - (n - 1) * (n - 1)) * R;
return res;
};

// Solve func(R) = 0 by Newton' method
let R = 100 * n; // NOTICE: Very heuristic
for (let i = 0; i < 100; i++) {
// console.log(R, func(R), funcc(R))
R = R - func(R) / funcc(R);

// Validation R with desired focal length
// Calculate focal length by lens maker's formula
const d = 2 * (R - Math.sqrt(R * R - r * r));
const desiredLensF = f;
const actualLensF = 1.0 / (2 * (n - 1) / R - (n - 1) * (n - 1) * d / (n * R * R));

const absError = Math.abs(desiredLensF - actualLensF);
const relError = absError / desiredLensF;
if (relError < 1e-8) {
return R;
}
}

// Failed
return -1.0;
}

export const lensR = computed(() => {
return calcLensR(lens.value.n, lens.value.f, lens.value.r)
// return 2 * (lens.value.n - 1) * (2 * lens.value.f)
Expand Down
72 changes: 63 additions & 9 deletions src/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,23 @@ export class Vec {
// }
// }

export const getIntersectionY = (px: number, py: number, v: Vec, x: number, minY: number, maxY: number) => {
export const getIntersectionY = (s: Vec, v: Vec, x: number, minY: number, maxY: number) => {
const n = v.copy().normalize()
const r = (x - px) / n.x;
const y = py + r * n.y;
const r = (x - s.x) / n.x;
const y = s.y + r * n.y;
if (r >= 0 && minY <= y && y <= maxY) {
return vec(x, y)
} else {
return null
}
}

export const getIntersectionLens = (x: number, y: number, v: Vec, cx: number, cy: number, r: number /* lens diameter */, R: number /* lens curvature radius */, select: boolean) => {
export const getIntersectionLens = (s: Vec, v: Vec, cl: Vec, r: number /* lens diameter */, R: number /* lens curvature radius */, select: boolean) => {
const n = v.copy().normalize()

const a = 1;
const b = 2 * ((x - cx) * n.x + (y - cy) * n.y);
const c = Math.pow(x - cx, 2) + Math.pow(y - cy, 2) - R * R;
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) - R * R;
const cond = b * b - 4 * a * c;
if (cond < 0) {
return null
Expand All @@ -118,8 +118,8 @@ export const getIntersectionLens = (x: number, y: number, v: Vec, cx: number, cy
const d1 = (-b - Math.sqrt(cond)) / (2 * a);
const d2 = (-b + Math.sqrt(cond)) / (2 * a);
const d = select ? d1 : d2;
const tx = x + d * n.x;
const ty = y + d * n.y;
const tx = s.x + d * n.x;
const ty = s.y + d * n.y;
if (Math.abs(ty) > r || d < 0) {
return null
} else {
Expand Down Expand Up @@ -180,4 +180,58 @@ export const crossAngle = (p: Vec, q: Vec) => {
// } else {
// return [hit3, x3, y3, r3];
// }
// };
// };

//================================
// Lens
//================================

export const fGaussian = (f: number, px: number, py: number) => {
const qx = f * px / (px - f)
const qy = py * (qx / px)
return vec(qx, qy)
}

export const calcLensR = (n: number, f: number, r: number) => {
// This formula is made from lens-maker's formula and d = 2(R-sqrt(R^2-r^2))
const func = (R: number) => {
let res = 0;
res += n * n * Math.pow(R, 4);
res += - 4 * n * (n - 1) * f * Math.pow(R, 3);
res += 4 * (n - 1) * (n - 1) * f * f * (1 - (n - 1) * (n - 1)) * R * R;
res += 4 * Math.pow(n - 1, 4) * f * f * r * r;

return res;
};

// Derivative of func
const funcc = (R: number) => {
let res = 0;
res += 4 * n * n * Math.pow(R, 3);
res += - 12 * n * (n - 1) * f * Math.pow(R, 2);
res += 8 * (n - 1) * (n - 1) * f * f * (1 - (n - 1) * (n - 1)) * R;
return res;
};

// Solve func(R) = 0 by Newton' method
let R = 100 * n; // NOTICE: Very heuristic
for (let i = 0; i < 100; i++) {
// console.log(R, func(R), funcc(R))
R = R - func(R) / funcc(R);

// Validation R with desired focal length
// Calculate focal length by lens maker's formula
const d = 2 * (R - Math.sqrt(R * R - r * r));
const desiredLensF = f;
const actualLensF = 1.0 / (2 * (n - 1) / R - (n - 1) * (n - 1) * d / (n * R * R));

const absError = Math.abs(desiredLensF - actualLensF);
const relError = absError / desiredLensF;
if (relError < 1e-8) {
return R;
}
}

// Failed
return -1.0;
}

0 comments on commit 4c01542

Please sign in to comment.