/*
* Copyright 2024 The Ray Optics Simulation authors and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Glass from './Glass.js';
import geometry from '../../geometry.js';
import i18next from 'i18next';
/**
* Spherical lens.
*
* Tools -> Glass -> Spherical Lens
*
* It is essentially a special case of `sceneObjs['Glass']` that has the shape of a lens, but it provides a more user-friendly interface to create a lens using more familliar parameters.
* In the state where the lens is built, it behaves exactly like `sceneObjs['Glass']`, and `p1`, `p2`, and `parames` are null. But when the lens is not built (which happens when the user enters a set of invalid parameters, or if the JSON data is defined using parameters directly), it is defined using `p1`, `p2`, and `params`, and `path` is null.
* @class
* @extends sceneObjs.Glass
* @memberof sceneObjs
* @property {Array<object>} path - The path of the lens if it is built.
* @property {string} defBy - The way the lens is defined. Either 'DR1R2' or 'DFfdBfd'.
* @property {Point} p1 - The intersection of the perpendicular bisector of the segment for the `d` parameter with the top edge of the lens, if it is not built.
* @property {Point} p2 - The intersection of the perpendicular bisector of the segment for the `d` parameter with the bottom edge of the lens, if it is not built.
* @property {object} params - The parameters of the lens if it is not built. It has the following properties: `d`, `r1`, and `r2` if `defBy` is 'DR1R2', and `d`, `ffd`, and `bfd` if `defBy` is 'DFfdBfd'.
* @property {number} refIndex - The refractive index of the glass, or the Cauchy coefficient A of the glass if "Simulate Colors" is on.
* @property {number} cauchyB - The Cauchy coefficient B of the glass if "Simulate Colors" is on, in micrometer squared.
*/
class SphericalLens extends Glass {
static type = 'SphericalLens';
static isOptical = true;
static supportsSurfaceMerging = true;
static serializableDefaults = {
path: null,
defBy: 'DR1R2',
p1: null,
p2: null,
params: null,
refIndex: 1.5,
cauchyB: 0.004
};
constructor(scene, jsonObj) {
super(scene, jsonObj);
if (!this.path && this.p1 && this.p2) {
// If the lens is not built in the JSON data, build the lens here.
if (this.defBy == 'DR1R2') {
this.createLensWithDR1R2(this.params.d, this.params.r1, this.params.r2);
} else if (this.defBy == 'DFfdBfd') {
this.createLensWithDFfdBfd(this.params.d, this.params.ffd, this.params.bfd);
}
}
}
populateObjBar(objBar) {
objBar.setTitle(i18next.t('main:tools.SphericalLens.title'));
objBar.createDropdown('', this.defBy, {
'DR1R2': i18next.t('simulator:sceneObjs.SphericalLens.defBy.radiiOfCurvature'),
'DFfdBfd': i18next.t('simulator:sceneObjs.SphericalLens.defBy.focalDistances')
}, function (obj, value) {
obj.defBy = value;
}, null, true);
if (this.defBy == 'DR1R2') {
var params = this.getDR1R2();
var r1 = params.r1;
var r2 = params.r2;
var d = params.d;
objBar.createNumber('R<sub>1</sub>', 0, 100, 1, r1, function (obj, value) {
var params = obj.getDR1R2();
var r2 = params.r2;
var d = params.d;
obj.createLensWithDR1R2(d, value, r2);
}, null, true);
objBar.createNumber('R<sub>2</sub>', 0, 100, 1, r2, function (obj, value) {
var params = obj.getDR1R2();
var r1 = params.r1;
var d = params.d;
obj.createLensWithDR1R2(d, r1, value);
}, null, true);
objBar.createNumber('d', 0, 100, 1, d, function (obj, value) {
var params = obj.getDR1R2();
var r1 = params.r1;
var r2 = params.r2;
obj.createLensWithDR1R2(value, r1, r2);
}, null, true);
} else if (this.defBy == 'DFfdBfd') {
objBar.createInfoBox('<img src="../img/FFD_BFD.svg" width=100%>');
var params = this.getDFfdBfd();
var d = params.d;
var ffd = params.ffd;
var bfd = params.bfd;
objBar.createNumber('FFD', 0, 100, 1, ffd, function (obj, value) {
var params = obj.getDFfdBfd();
var d = params.d;
var bfd = params.bfd;
obj.createLensWithDFfdBfd(d, value, bfd);
}, null, true);
objBar.createNumber('BFD', 0, 100, 1, bfd, function (obj, value) {
var params = obj.getDFfdBfd();
var d = params.d;
var ffd = params.ffd;
obj.createLensWithDFfdBfd(d, ffd, value);
}, null, true);
objBar.createNumber('d', 0, 100, 1, d, function (obj, value) {
var params = obj.getDFfdBfd();
var ffd = params.ffd;
var bfd = params.bfd;
obj.createLensWithDFfdBfd(value, ffd, bfd);
}, null, true);
}
if (this.scene.simulateColors) {
objBar.createNumber(i18next.t('simulator:sceneObjs.BaseGlass.cauchyCoeff') + " A", 1, 3, 0.01, this.refIndex, function (obj, value) {
var old_params = obj.getDFfdBfd();
obj.refIndex = value * 1;
if (obj.defBy == 'DFfdBfd') {
// If the lens is defined by d,ffd,bfd, we need to rebuild the lens with the new refractive index so that the focal distances are correct.
obj.createLensWithDFfdBfd(old_params.d, old_params.ffd, old_params.bfd);
}
}, '<p>*' + i18next.t('simulator:sceneObjs.BaseGlass.refIndexInfo.relative') + '</p><p>' + i18next.t('simulator:sceneObjs.BaseGlass.refIndexInfo.effective') + '</p>');
objBar.createNumber("B(μm²)", 0.0001, 0.02, 0.0001, this.cauchyB, function (obj, value) {
var old_params = obj.getDFfdBfd();
obj.cauchyB = value;
if (obj.defBy == 'DFfdBfd') {
// If the lens is defined by d,ffd,bfd, we need to rebuild the lens with the new refractive index so that the focal distances are correct.
obj.createLensWithDFfdBfd(old_params.d, old_params.ffd, old_params.bfd);
}
});
} else {
objBar.createNumber(i18next.t('simulator:sceneObjs.BaseGlass.refIndex') + '*', 0.5, 2.5, 0.01, this.refIndex, function (obj, value) {
var old_params = obj.getDFfdBfd();
obj.refIndex = value * 1;
if (obj.defBy == 'DFfdBfd') {
// If the lens is defined by d,ffd,bfd, we need to rebuild the lens with the new refractive index so that the focal distances are correct.
obj.createLensWithDFfdBfd(old_params.d, old_params.ffd, old_params.bfd);
}
}, '<p>*' + i18next.t('simulator:sceneObjs.BaseGlass.refIndexInfo.relative') + '</p><p>' + i18next.t('simulator:sceneObjs.BaseGlass.refIndexInfo.effective') + '</p>');
}
}
draw(canvasRenderer, isAboveLight, isHovered) {
const ctx = canvasRenderer.ctx;
const ls = canvasRenderer.lengthScale;
if (this.p1 && this.p2 && this.p1.x == this.p2.x && this.p1.y == this.p2.y) {
ctx.fillStyle = 'rgb(128,128,128)';
ctx.fillRect(this.p1.x - 1.5 * ls, this.p1.y - 1.5 * ls, 3 * ls, 3 * ls);
return;
}
if (!this.path) {
if (this.p1 && this.p2) {
ctx.fillStyle = 'rgb(255,0,0)';
ctx.fillRect(this.p1.x - 1.5 * ls, this.p1.y - 1.5 * ls, 3 * ls, 3 * ls);
ctx.fillRect(this.p2.x - 1.5 * ls, this.p2.y - 1.5 * ls, 3 * ls, 3 * ls);
}
return;
}
super.draw(canvasRenderer, isAboveLight, isHovered);
var p1 = geometry.midpoint(this.path[0], this.path[1]);
var p2 = geometry.midpoint(this.path[3], this.path[4]);
var len = Math.hypot(p1.x - p2.x, p1.y - p2.y);
var dx = (p2.x - p1.x) / len;
var dy = (p2.y - p1.y) / len;
var dpx = dy;
var dpy = -dx;
if (isHovered) {
// Draw the focal points
var params = this.getDFfdBfd();
var ffd = params.ffd;
var bfd = params.bfd;
ctx.fillStyle = 'rgb(255,0,255)';
ctx.fillRect(this.path[2].x + bfd * dpx - 1.5 * ls, this.path[2].y + bfd * dpy - 1.5 * ls, 3 * ls, 3 * ls);
ctx.fillRect(this.path[5].x - ffd * dpx - 1.5 * ls, this.path[5].y - ffd * dpy - 1.5 * ls, 3 * ls, 3 * ls);
}
}
move(diffX, diffY) {
if (this.path) {
super.move(diffX, diffY);
} else if (this.p1 && this.p2) {
this.p1.x += diffX;
this.p1.y += diffY;
this.p2.x += diffX;
this.p2.y += diffY;
}
}
onConstructMouseDown(mouse, ctrl, shift) {
if (!this.constructionPoint) {
// Initialize the construction stage.
this.constructionPoint = mouse.getPosSnappedToGrid();
this.p1 = this.constructionPoint;
this.p2 = this.constructionPoint;
this.path = null;
this.params = null;
}
if (shift) {
this.p2 = mouse.getPosSnappedToDirection(this.constructionPoint, [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: -1 }]);
} else {
this.p2 = mouse.getPosSnappedToGrid();
}
this.createLens();
return {
requiresObjBarUpdate: true
}
}
onConstructMouseMove(mouse, ctrl, shift) {
if (shift) {
this.p2 = mouse.getPosSnappedToDirection(this.constructionPoint, [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: -1 }]);
} else {
this.p2 = mouse.getPosSnappedToGrid();
}
this.p1 = ctrl ? geometry.point(2 * this.constructionPoint.x - this.p2.x, 2 * this.constructionPoint.y - this.p2.y) : this.constructionPoint;
this.createLens();
return {
requiresObjBarUpdate: true
}
}
onConstructMouseUp(mouse, ctrl, shift) {
if (!mouse.snapsOnPoint(this.p1)) {
this.p1 = null;
this.p2 = null;
this.params = null;
delete this.constructionPoint;
return {
isDone: true,
requiresObjBarUpdate: true
};
}
}
onConstructUndo() {
return {
isCancelled: true
}
}
checkMouseOver(mouse) {
let dragContext = {};
if (!this.path) {
if (this.p1 && this.p2) {
if (mouse.isOnPoint(this.p1)) {
dragContext.part = -1;
dragContext.targetPoint = this.p1;
return dragContext;
}
if (mouse.isOnPoint(this.p2)) {
dragContext.part = -1;
dragContext.targetPoint = this.p2;
return dragContext;
}
}
return null;
};
dragContext = super.checkMouseOver(mouse);
if (dragContext) {
if (dragContext.part != 0) {
dragContext.requiresObjBarUpdate = true;
}
return dragContext;
}
}
onDrag(mouse, dragContext, ctrl, shift) {
if (dragContext.part == -1) return;
var p1 = geometry.midpoint(this.path[0], this.path[1]);
var p2 = geometry.midpoint(this.path[3], this.path[4]);
var len = Math.hypot(p1.x - p2.x, p1.y - p2.y);
var dx = (p2.x - p1.x) / len;
var dy = (p2.y - p1.y) / len;
var dpx = dy;
var dpy = -dx;
var cx = (p1.x + p2.x) * .5;
var cy = (p1.y + p2.y) * .5;
var othick = Math.hypot(this.path[0].x - p1.x, this.path[0].y - p1.y);
var cthick2 = Math.hypot(this.path[2].x - cx, this.path[2].y - cy);
var cthick5 = Math.hypot(this.path[5].x - cx, this.path[5].y - cy);
var oldx, oldy;
if (dragContext.part == 1) {
oldx = this.path[dragContext.index].x;
oldy = this.path[dragContext.index].y;
}
super.onDrag(mouse, dragContext, ctrl, shift);
if (dragContext.isByHandle) return;
if (dragContext.part != 1)
return;
const mousePos = mouse.getPosSnappedToGrid();
if (dragContext.index == 2 || dragContext.index == 5) {
// keep center points on optical axis
var off = (mousePos.x - cx) * dpx + (mousePos.y - cy) * dpy;
this.path[dragContext.index] = { x: cx + dpx * off, y: cy + dpy * off, arc: true };
} else {
var thick = Math.abs(((mousePos.x - cx) * dpx + (mousePos.y - cy) * dpy));
// adjust center thickness to match so curvature is the same
cthick2 += thick - othick;
cthick5 += thick - othick;
var mpt = this.path[dragContext.index];
var lchange = (mpt.x - oldx) * dx + (mpt.y - oldy) * dy;
// adjust length
if (dragContext.index < 2) {
p1.x += lchange * dx;
p1.y += lchange * dy;
} else {
p2.x += lchange * dx;
p2.y += lchange * dy;
}
len = Math.hypot(p1.x - p2.x, p1.y - p2.y);
dx = (p2.x - p1.x) / len;
dy = (p2.y - p1.y) / len;
dpx = dy;
dpy = -dx;
cx = (p1.x + p2.x) * .5;
cy = (p1.y + p2.y) * .5;
// recreate lens
this.path[0] = { x: p1.x - dpx * thick, y: p1.y - dpy * thick, arc: false };
this.path[1] = { x: p1.x + dpx * thick, y: p1.y + dpy * thick, arc: false };
this.path[2] = { x: cx + dpx * cthick2, y: cy + dpy * cthick2, arc: true };
this.path[3] = { x: p2.x + dpx * thick, y: p2.y + dpy * thick, arc: false };
this.path[4] = { x: p2.x - dpx * thick, y: p2.y - dpy * thick, arc: false };
this.path[5] = { x: cx - dpx * cthick5, y: cy - dpy * cthick5, arc: true };
}
}
checkRayIntersects(ray) {
if (!this.path) return false;
return super.checkRayIntersects(ray);
}
/* Utility methods */
createLens() {
var p1 = this.p1 || geometry.midpoint(this.path[0], this.path[1]);
var p2 = this.p2 || geometry.midpoint(this.path[3], this.path[4]);
var len = Math.hypot(p1.x - p2.x, p1.y - p2.y);
var dx = (p2.x - p1.x) / len;
var dy = (p2.y - p1.y) / len;
var dpx = dy;
var dpy = -dx;
var cx = (p1.x + p2.x) * .5;
var cy = (p1.y + p2.y) * .5;
const thick = 10 * this.scene.lengthScale;
// create lens
if (!this.path) this.path = [];
this.path[0] = { x: p1.x - dpx * thick, y: p1.y - dpy * thick, arc: false };
this.path[1] = { x: p1.x + dpx * thick, y: p1.y + dpy * thick, arc: false };
this.path[2] = { x: cx + dpx * thick * 2, y: cy + dpy * thick * 2, arc: true };
this.path[3] = { x: p2.x + dpx * thick, y: p2.y + dpy * thick, arc: false };
this.path[4] = { x: p2.x - dpx * thick, y: p2.y - dpy * thick, arc: false };
this.path[5] = { x: cx - dpx * thick * 2, y: cy - dpy * thick * 2, arc: true };
}
createLensWithDR1R2(d, r1, r2) {
this.error = null;
if (!this.path) {
var p1 = this.p1;
var p2 = this.p2;
} else {
var old_params = this.getDR1R2();
var p1 = geometry.midpoint(this.path[0], this.path[1]);
var p2 = geometry.midpoint(this.path[3], this.path[4]);
var old_d = old_params.d;
var old_r1 = old_params.r1;
var old_r2 = old_params.r2;
var old_length = Math.hypot(p1.x - p2.x, p1.y - p2.y);
if (old_r1 < Infinity && old_r1 > -Infinity) {
var old_curveShift1 = old_r1 - Math.sqrt(old_r1 * old_r1 - old_length * old_length / 4) * Math.sign(old_r1);
} else {
var old_curveShift1 = 0;
}
if (old_r2 < Infinity && old_r2 > -Infinity) {
var old_curveShift2 = old_r2 - Math.sqrt(old_r2 * old_r2 - old_length * old_length / 4) * Math.sign(old_r2);
} else {
var old_curveShift2 = 0;
}
var old_edgeShift1 = old_d / 2 - old_curveShift1;
var old_edgeShift2 = old_d / 2 + old_curveShift2;
}
var len = Math.hypot(p1.x - p2.x, p1.y - p2.y);
var dx = (p2.x - p1.x) / len;
var dy = (p2.y - p1.y) / len;
var dpx = dy;
var dpy = -dx;
if (old_params) {
// Correct p1 and p2 so that this.path[2] and this.path[5] will move symmetrically from the old lens.
var correction = (old_edgeShift1 - old_edgeShift2) / 2;
p1.x += dpx * correction;
p1.y += dpy * correction;
p2.x += dpx * correction;
p2.y += dpy * correction;
}
var cx = (p1.x + p2.x) * .5;
var cy = (p1.y + p2.y) * .5;
if (r1 < Infinity && r1 > -Infinity) {
var curveShift1 = r1 - Math.sqrt(r1 * r1 - len * len / 4) * Math.sign(r1);
} else {
var curveShift1 = 0;
}
if (r2 < Infinity && r2 > -Infinity) {
var curveShift2 = r2 - Math.sqrt(r2 * r2 - len * len / 4) * Math.sign(r2);
} else {
var curveShift2 = 0;
}
var edgeShift1 = d / 2 - curveShift1;
var edgeShift2 = d / 2 + curveShift2;
//console.log([edgeShift1,edgeShift2,d])
if (isNaN(curveShift1) || isNaN(curveShift2)) {
// invalid lens. Store the parameters so we can restore them later.
this.p1 = p1;
this.p2 = p2;
this.path = null;
this.params = { r1: r1, r2: r2, d: d };
this.error = 'Invalid lens parameters';
} else {
// create lens
if (!this.path) this.path = [];
this.path[0] = { x: p1.x - dpx * edgeShift1, y: p1.y - dpy * edgeShift1, arc: false };
this.path[1] = { x: p1.x + dpx * edgeShift2, y: p1.y + dpy * edgeShift2, arc: false };
this.path[2] = { x: cx + dpx * (d / 2), y: cy + dpy * (d / 2), arc: true };
this.path[3] = { x: p2.x + dpx * edgeShift2, y: p2.y + dpy * edgeShift2, arc: false };
this.path[4] = { x: p2.x - dpx * edgeShift1, y: p2.y - dpy * edgeShift1, arc: false };
this.path[5] = { x: cx - dpx * (d / 2), y: cy - dpy * (d / 2), arc: true };
if (this.p1) {
this.p1 = null;
this.p2 = null;
}
if (this.params) this.params = null;
}
}
createLensWithDFfdBfd(d, ffd, bfd) {
this.error = null;
if (!this.path) {
var p1 = this.p1;
var p2 = this.p2;
} else {
var old_params = this.getDR1R2();
var p1 = geometry.midpoint(this.path[0], this.path[1]);
var p2 = geometry.midpoint(this.path[3], this.path[4]);
var old_d = old_params.d;
var old_r1 = old_params.r1;
var old_r2 = old_params.r2;
var old_length = Math.hypot(p1.x - p2.x, p1.y - p2.y);
if (old_r1 < Infinity && old_r1 > -Infinity) {
var old_curveShift1 = old_r1 - Math.sqrt(old_r1 * old_r1 - old_length * old_length / 4) * Math.sign(old_r1);
} else {
var old_curveShift1 = 0;
}
if (old_r2 < Infinity && old_r2 > -Infinity) {
var old_curveShift2 = old_r2 - Math.sqrt(old_r2 * old_r2 - old_length * old_length / 4) * Math.sign(old_r2);
} else {
var old_curveShift2 = 0;
}
var old_edgeShift1 = old_d / 2 - old_curveShift1;
var old_edgeShift2 = old_d / 2 + old_curveShift2;
}
var len = Math.hypot(p1.x - p2.x, p1.y - p2.y);
var dx = (p2.x - p1.x) / len;
var dy = (p2.y - p1.y) / len;
var dpx = dy;
var dpy = -dx;
if (old_params) {
// Correct p1 and p2 so that this.path[2] and this.path[5] will move symmetrically from the old lens.
var correction = (old_edgeShift1 - old_edgeShift2) / 2;
p1.x += dpx * correction;
p1.y += dpy * correction;
p2.x += dpx * correction;
p2.y += dpy * correction;
}
var n = this.getRefIndexAt(null, {wavelength: 546});
// Solve for r1 and r2
var r1_1 = (d * (-1 + n) * (d + 2 * ffd * n - Math.sqrt(d ** 2 + 4 * bfd * ffd * n ** 2))) / (2 * n * (d + (-bfd + ffd) * n));
var r2_1 = -(d * (-1 + n) * (d + 2 * bfd * n - Math.sqrt(d ** 2 + 4 * bfd * ffd * n ** 2))) / (2 * n * (d + (bfd - ffd) * n));
var r1_2 = (d * (-1 + n) * (d + 2 * ffd * n + Math.sqrt(d ** 2 + 4 * bfd * ffd * n ** 2))) / (2 * n * (d + (-bfd + ffd) * n));
var r2_2 = -(d * (-1 + n) * (d + 2 * bfd * n + Math.sqrt(d ** 2 + 4 * bfd * ffd * n ** 2))) / (2 * n * (d + (bfd - ffd) * n));
if (!isNaN(r1_1) && !isNaN(r2_1) && (isNaN(r1_2) || isNaN(r2_2))) {
// If only the first solution is valid, use that.
var r1 = r1_1;
var r2 = r2_1;
this.createLensWithDR1R2(d, r1, r2);
if (!this.path) {
// If the lens is invalid as defined by d,r1,r2, still store the d,ffl,bfl parameters instead so the user may enter another set of d,ffl,bfl to get a valid lens.
this.params = { d: d, ffd: ffd, bfd: bfd };
this.error = 'Invalid lens parameters';
}
} else if (!isNaN(r1_2) && !isNaN(r2_2) && (isNaN(r1_1) || isNaN(r2_1))) {
// If only the second solution is valid, use that.
var r1 = r1_2;
var r2 = r2_2;
this.createLensWithDR1R2(d, r1, r2);
if (!this.path) {
// If the lens is invalid as defined by d,r1,r2, still store the d,ffl,bfl parameters instead so the user may enter another set of d,ffl,bfl to get a valid lens.
this.params = { d: d, ffd: ffd, bfd: bfd };
this.error = 'Invalid lens parameters';
}
} else if (!isNaN(r1_1) && !isNaN(r2_1) && !isNaN(r1_2) && !isNaN(r2_2)) {
// If both solutions are valid, and if the old lens is valid, prefer the solution that is closest to the old lens.
if (old_params) {
var old_r1_1_diff = Math.abs(old_r1 - r1_1);
var old_r2_1_diff = Math.abs(old_r2 - r2_1);
var old_r1_2_diff = Math.abs(old_r1 - r1_2);
var old_r2_2_diff = Math.abs(old_r2 - r2_2);
if (old_r1_1_diff + old_r2_1_diff < old_r1_2_diff + old_r2_2_diff) {
var r1_a = r1_1;
var r2_a = r2_1;
var r1_b = r1_2;
var r2_b = r2_2;
} else {
var r1_a = r1_2;
var r2_a = r2_2;
var r1_b = r1_1;
var r2_b = r2_1;
}
} else {
// If the old lens is invalid, prefer the solution with the smaller radius of curvature.
if (Math.abs(r1_1) + Math.abs(r2_1) < Math.abs(r1_2) + Math.abs(r2_2)) {
var r1_a = r1_1;
var r2_a = r2_1;
var r1_b = r1_2;
var r2_b = r2_2;
} else {
var r1_a = r1_2;
var r2_a = r2_2;
var r1_b = r1_1;
var r2_b = r2_1;
}
}
// Try the preferred solution first.
var r1 = r1_a;
var r2 = r2_a;
this.createLensWithDR1R2(d, r1, r2);
if (!this.path) {
// Try the other solution if the preferred solution gives an invalid set of d,r1,r2.
r1 = r1_b;
r2 = r2_b;
this.createLensWithDR1R2(d, r1, r2);
if (!this.path) {
// If the lens is still invalid, still store the d,ffl,bfl parameters instead so the user may enter another set of d,ffl,bfl to get a valid lens.
this.params = { d: d, ffd: ffd, bfd: bfd };
this.error = 'Invalid lens parameters';
}
}
} else {
// invalid lens. Store the parameters so we can restore them later.
this.p1 = p1;
this.p2 = p2;
this.path = null;
this.params = { d: d, ffd: ffd, bfd: bfd };
this.error = 'Invalid lens parameters';
return;
}
}
getDR1R2() {
if (this.params) return this.params;
// get radii of curvature
var center1 = geometry.linesIntersection(geometry.perpendicularBisector(geometry.line(this.path[1], this.path[2])),
geometry.perpendicularBisector(geometry.line(this.path[3], this.path[2])));
var r2 = geometry.distance(center1, this.path[2]);
var center2 = geometry.linesIntersection(geometry.perpendicularBisector(geometry.line(this.path[4], this.path[5])),
geometry.perpendicularBisector(geometry.line(this.path[0], this.path[5])));
var r1 = geometry.distance(center2, this.path[5]);
var p1 = geometry.midpoint(this.path[0], this.path[1]);
var p2 = geometry.midpoint(this.path[3], this.path[4]);
var len = Math.hypot(p1.x - p2.x, p1.y - p2.y);
var dx = (p2.x - p1.x) / len;
var dy = (p2.y - p1.y) / len;
var dpx = dy;
var dpy = -dx;
var cx = (p1.x + p2.x) * .5;
var cy = (p1.y + p2.y) * .5;
var d = geometry.distance(this.path[2], this.path[5]);
// correct sign
if (dpx * (center1.x - this.path[2].x) + dpy * (center1.y - this.path[2].y) < 0)
r2 = -r2;
if (dpx * (center2.x - this.path[5].x) + dpy * (center2.y - this.path[5].y) < 0)
r1 = -r1;
if (isNaN(r1)) {
r1 = Infinity;
}
if (isNaN(r2)) {
r2 = Infinity;
}
return { d: d, r1: r1, r2: r2 };
}
getDFfdBfd() {
if (this.params) return this.params;
var dR1R2 = this.getDR1R2();
var r1 = dR1R2.r1;
var r2 = dR1R2.r2;
var d = dR1R2.d;
var n = this.getRefIndexAt(null, {wavelength: 546});
var f = 1 / ((n - 1) * (1 / r1 - 1 / r2 + (n - 1) * d / (n * r1 * r2)));
var ffd = f * (1 + (n - 1) * d / (n * r2));
var bfd = f * (1 - (n - 1) * d / (n * r1));
return { d: d, ffd: ffd, bfd: bfd };
}
};
export default SphericalLens;